From ebb5643e598a17b2f21b4e50acac35afe82dbd55 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 5 Jun 2023 13:54:49 +0200 Subject: [PATCH] Readfile: Refactor several parts of the process This commit affects: * Reading undo steps from memfile (aka 'Global Undo'); * Handling of UI IDs (WindowManager, Workspaces and Screens) when opening a .blend file. While no major changes are expected from a user PoV, there may be some unexpected changes in rare edge-cases. None has been identified so far. Undo step loading should be marginally faster (`setup_app_data` itself is 2-3 times faster, as it does not do remapping anymore, which makes the whole 'read undo step' process about 20% faster - but the most time-consuming step on undo is the depsgraph processing, which remains unchanged here). This commit also solves some bugs (crashes) in some relatively uncommon cases, like e.g. if the WM had an IDProperty pointing at an object and UI is not loaded when opening a new .blend file with the 'Load UI' option enabled (as in previous code on file opening WM ID would never be remapped). From a more technical side, this commit aims mainly at cleaning things up, in preparation for the introduction of new 'no undo, no readfile' type of handling (as part of the Brush Assets project): - Prevent WM code from doing (too much) horrible ID 'management' on its WM when opening a new file. It used to remove current WM from the Main database, store it in a temporary own list, and then free it itself... - Trying to make the complex logic behind WM handling on file reading a bit more easy to follow, at least way more documented in code. - Keep the handling of 'IDs being re-used from old Main' in a single place, as much as possible: -- Readfile code itself in undo case (because it's more efficient, and undo case is in a way simpler than actual .blend file reading case). The whole `blo_lib_link_restore` block of code is also removed. -- (Mostly) setup_app_data code in actual file reading case. - Sanitize the usage of the 'libmap' in readfile code in undo case (waaaaay too many pointers were added there, which was hiding some other issues in the related code, and potentially causing (in rare cases) memory addresses collisions. Pull Request: https://projects.blender.org/blender/blender/pulls/108016 --- source/blender/blenkernel/BKE_blendfile.h | 40 +- source/blender/blenkernel/BKE_idtype.h | 4 + .../blender/blenkernel/intern/blender_undo.cc | 4 +- source/blender/blenkernel/intern/blendfile.cc | 709 ++++++++++--- source/blender/blenloader/BLO_readfile.h | 17 +- .../blenloader/intern/readblenentry.cc | 21 +- source/blender/blenloader/intern/readfile.cc | 938 ++++++------------ source/blender/blenloader/intern/readfile.h | 14 +- source/blender/editors/undo/memfile_undo.cc | 3 +- source/blender/makesdna/DNA_ID.h | 15 +- source/blender/windowmanager/intern/wm.c | 15 - .../blender/windowmanager/intern/wm_files.cc | 335 +++---- source/blender/windowmanager/wm.h | 1 - 13 files changed, 1108 insertions(+), 1008 deletions(-) diff --git a/source/blender/blenkernel/BKE_blendfile.h b/source/blender/blenkernel/BKE_blendfile.h index 357aeb4427f..1ce7d440219 100644 --- a/source/blender/blenkernel/BKE_blendfile.h +++ b/source/blender/blenkernel/BKE_blendfile.h @@ -14,6 +14,7 @@ extern "C" { struct BlendFileData; struct BlendFileReadParams; struct BlendFileReadReport; +struct BlendFileReadWMSetupData; struct ID; struct Main; struct MemFile; @@ -50,33 +51,38 @@ bool BKE_blendfile_library_path_explode(const char *path, /** * Shared setup function that makes the data from `bfd` into the current blend file, * replacing the contents of #G.main. - * This uses the bfd #BKE_blendfile_read and similarly named functions. + * This uses the bfd returned by #BKE_blendfile_read and similarly named functions. * * This is done in a separate step so the caller may perform actions after it is known the file * loaded correctly but before the file replaces the existing blend file contents. */ -void BKE_blendfile_read_setup_ex(struct bContext *C, - struct BlendFileData *bfd, - const struct BlendFileReadParams *params, - struct BlendFileReadReport *reports, - /* Extra args. */ - bool startup_update_defaults, - const char *startup_app_template); - -void BKE_blendfile_read_setup(struct bContext *C, - struct BlendFileData *bfd, - const struct BlendFileReadParams *params, - struct BlendFileReadReport *reports); +void BKE_blendfile_read_setup_readfile(struct bContext *C, + struct BlendFileData *bfd, + const struct BlendFileReadParams *params, + struct BlendFileReadWMSetupData *wm_setup_data, + struct BlendFileReadReport *reports, + bool startup_update_defaults, + const char *startup_app_template); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * Simpler version of #BKE_blendfile_read_setup_readfile used when reading undoe steps from + * memfile. */ +void BKE_blendfile_read_setup_undo(struct bContext *C, + struct BlendFileData *bfd, + const struct BlendFileReadParams *params, + struct BlendFileReadReport *reports); + +/** + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. */ struct BlendFileData *BKE_blendfile_read(const char *filepath, const struct BlendFileReadParams *params, struct BlendFileReadReport *reports); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. */ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, int filelength, @@ -84,7 +90,9 @@ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, struct ReportList *reports); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. + * * \note `memfile` is the undo buffer. */ struct BlendFileData *BKE_blendfile_read_from_memfile(struct Main *bmain, diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index 1ee3317bd52..e7f0c137678 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -220,6 +220,10 @@ typedef struct IDTypeInfo { * Allow an ID type to preserve some of its data across (memfile) undo steps. * * \note Called from #setup_app_data when undoing or redoing a memfile step. + * + * \note In case the whole ID should be fully preserved across undo steps, it is better to flag + * its type with `IDTYPE_FLAGS_NO_MEMFILE_UNDO`, since that flag allows more aggressive + * optimizations in readfile code for memfile undo. */ IDTypeBlendReadUndoPreserve blend_read_undo_preserve; diff --git a/source/blender/blenkernel/intern/blender_undo.cc b/source/blender/blenkernel/intern/blender_undo.cc index af353b6d5de..6e6de65bd15 100644 --- a/source/blender/blenkernel/intern/blender_undo.cc +++ b/source/blender/blenkernel/intern/blender_undo.cc @@ -69,7 +69,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, BlendFileReadReport bf_reports{}; BlendFileData *bfd = BKE_blendfile_read(mfu->filepath, ¶ms, &bf_reports); if (bfd != nullptr) { - BKE_blendfile_read_setup(C, bfd, ¶ms, &bf_reports); + BKE_blendfile_read_setup_undo(C, bfd, ¶ms, &bf_reports); success = true; } } @@ -82,7 +82,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, BlendFileReadReport blend_file_read_report{}; BlendFileData *bfd = BKE_blendfile_read_from_memfile(bmain, &mfu->memfile, ¶ms, nullptr); if (bfd != nullptr) { - BKE_blendfile_read_setup(C, bfd, ¶ms, &blend_file_read_report); + BKE_blendfile_read_setup_undo(C, bfd, ¶ms, &blend_file_read_report); success = true; } } diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index a11a9b72ba3..1fdb1afa2ba 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -39,12 +39,16 @@ #include "BKE_colorband.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_idtype.h" #include "BKE_ipo.h" #include "BKE_keyconfig.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_lib_override.h" +#include "BKE_lib_query.h" +#include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_main_idmap.h" #include "BKE_main_namemap.h" #include "BKE_preferences.h" #include "BKE_report.h" @@ -216,6 +220,428 @@ static void setup_app_userdef(BlendFileData *bfd) } } +/** Helper struct to manage IDs that are re-used across blendfile loading (i.e. moved from the old + * Main the the new one). + * + * NOTE: this is only used when actually loading a real .blend file, loading of memfile undo steps + * does not need it. */ +typedef struct ReuseOldBMainData { + Main *new_bmain; + Main *old_bmain; + + /** Data generated and used by calling WM code to handle keeping WM and UI IDs as best as + * possible across file reading. + * + * \note: May be null in undo (memfile) case.. */ + BlendFileReadWMSetupData *wm_setup_data; + + /** Storage for all remapping rules (old_id -> new_id) required by the preservation of old IDs + * into the new Main. */ + IDRemapper *remapper; + bool is_libraries_remapped; + + /** Used to find matching IDs by name/lib in new main, to remap ID usages of data ported over + * from old main. */ + IDNameLib_Map *id_map; +} ReuseOldBMainData; + +/** Search for all libraries in `old_bmain` that are also in `new_bmain` (i.e. different Library + * IDs having the same absolute filepath), and create a remapping rule for these. + * + * NOTE: The case where the `old_bmain` would be a library in the newly read one is not handled + * here, as it does not create explicit issues. The local data from `old_bmain` is either + * discarded, or added to the `new_bmain` as local data as well. Worst case, there will be a + * doublon of a linked data as a local one, without any known relationships between them. In + * practice, this latter case is not expected to commonly happen. + */ +static IDRemapper *reuse_bmain_data_remapper_ensure(ReuseOldBMainData *reuse_data) +{ + if (reuse_data->is_libraries_remapped) { + return reuse_data->remapper; + } + + if (reuse_data->remapper == nullptr) { + reuse_data->remapper = BKE_id_remapper_create(); + } + + Main *new_bmain = reuse_data->new_bmain; + Main *old_bmain = reuse_data->old_bmain; + IDRemapper *remapper = reuse_data->remapper; + + LISTBASE_FOREACH (Library *, old_lib_iter, &old_bmain->libraries) { + /* In case newly opened `new_bmain` is a library of the `old_bmain`, remap it to NULL, since a + * file should never ever have linked data from itself. */ + if (STREQ(old_lib_iter->filepath_abs, new_bmain->filepath)) { + BKE_id_remapper_add(remapper, &old_lib_iter->id, nullptr); + continue; + } + + /* NOTE: Although this is quadratic complexity, it is not expected to be an issue in practice: + * - Files using more than a few tens of libraries are extremely rare. + * - This code is only executed once for every file reading (not on undos). + */ + LISTBASE_FOREACH (Library *, new_lib_iter, &new_bmain->libraries) { + if (!STREQ(old_lib_iter->filepath_abs, new_lib_iter->filepath_abs)) { + continue; + } + + BKE_id_remapper_add(remapper, &old_lib_iter->id, &new_lib_iter->id); + break; + } + } + + reuse_data->is_libraries_remapped = true; + return reuse_data->remapper; +} + +static bool reuse_bmain_data_remapper_is_id_remapped(IDRemapper *remapper, ID *id) +{ + IDRemapperApplyResult result = BKE_id_remapper_get_mapping_result( + remapper, id, ID_REMAP_APPLY_DEFAULT, nullptr); + if (ELEM(result, ID_REMAP_RESULT_SOURCE_REMAPPED, ID_REMAP_RESULT_SOURCE_UNASSIGNED)) { + /* ID is already remapped to its matching ID in the new main, or explicitly remapped to NULL, + * nothing else to do here. */ + return true; + } + BLI_assert_msg(result != ID_REMAP_RESULT_SOURCE_NOT_MAPPABLE, + "There should never be a non-mappable (i.e. NULL) input here."); + BLI_assert(result == ID_REMAP_RESULT_SOURCE_UNAVAILABLE); + return false; +} + +/** Does a complete replacement of data in `new_bmain` by data from `old_bmain. Original new data + * are moved to the `old_bmain`, and will be freed together with it. + * + * WARNING: Currently only expects to work on local data, won't work properly if some of the IDs of + * given type are linked. + * + * NOTE: There is no support at all for potential dependencies of the IDs moved around. This is not + * expected to be necessary for the current use cases (UI-related IDs). */ +static void swap_old_bmain_data_for_blendfile(ReuseOldBMainData *reuse_data, const short id_code) +{ + Main *new_bmain = reuse_data->new_bmain; + Main *old_bmain = reuse_data->old_bmain; + + ListBase *new_lb = which_libbase(new_bmain, id_code); + ListBase *old_lb = which_libbase(old_bmain, id_code); + + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + + /* NOTE: Full swapping is only supported for ID types that are assumed to be only local + * data-blocks (like UI-like ones). Otherwise, the swapping could fail in many funny ways. */ + BLI_assert(BLI_listbase_is_empty(old_lb) || !ID_IS_LINKED(old_lb->last)); + BLI_assert(BLI_listbase_is_empty(new_lb) || !ID_IS_LINKED(new_lb->last)); + + SWAP(ListBase, *new_lb, *old_lb); + + /* Since all IDs here are supposed to be local, no need to call #BKE_main_namemap_clear. */ + /* TODO: Could add per-IDType control over namemaps clearing, if this becomes a performances + * concern. */ + if (old_bmain->name_map != nullptr) { + BKE_main_namemap_destroy(&old_bmain->name_map); + } + if (new_bmain->name_map != nullptr) { + BKE_main_namemap_destroy(&new_bmain->name_map); + } + + /* Original 'new' IDs have been moved into the old listbase and will be discarded (deleted). + * Original 'old' IDs have been moved into the new listbase and are being reused (kept). + * The discarded ones need to be remapped to a matching reused one, based on their names, if + * possible. + * + * Since both lists are ordered, and they are all local, we can do a smart parallel processing of + * both lists here instead of doing complete full list searches. */ + ID *discarded_id_iter = static_cast(old_lb->first); + ID *reused_id_iter = static_cast(new_lb->first); + while (!ELEM(nullptr, discarded_id_iter, reused_id_iter)) { + const int strcmp_result = strcmp(discarded_id_iter->name + 2, reused_id_iter->name + 2); + if (strcmp_result == 0) { + /* Matching IDs, we can remap the discarded 'new' one to the re-used 'old' one. */ + BKE_id_remapper_add(remapper, discarded_id_iter, reused_id_iter); + + discarded_id_iter = static_cast(discarded_id_iter->next); + reused_id_iter = static_cast(reused_id_iter->next); + } + else if (strcmp_result < 0) { + /* No matching reused 'old' ID for this discarded 'new' one. */ + BKE_id_remapper_add(remapper, discarded_id_iter, nullptr); + + discarded_id_iter = static_cast(discarded_id_iter->next); + } + else { + reused_id_iter = static_cast(reused_id_iter->next); + } + } + /* Also remap all remaining non-compared discarded 'new' IDs to null. */ + for (; discarded_id_iter != nullptr; + discarded_id_iter = static_cast(discarded_id_iter->next)) + { + BKE_id_remapper_add(remapper, discarded_id_iter, nullptr); + } + + FOREACH_MAIN_LISTBASE_ID_BEGIN (new_lb, reused_id_iter) { + /* Necessary as all `session_uuid` are renewed on blendfile loading. */ + BKE_lib_libblock_session_uuid_renew(reused_id_iter); + + /* Ensure that the reused ID is remapped to itself, since it is known to be in the `new_bmain`. + */ + BKE_id_remapper_add_overwrite(remapper, reused_id_iter, reused_id_iter); + } + FOREACH_MAIN_LISTBASE_ID_END; +} + +/** Similar to #swap_old_bmain_data_for_blendfile, but with special handling for WM ID. Tightly + * related to further WM post-processing from calling WM code (see #WM_file_read and + * #wm_homefile_read_ex). */ +static void swap_wm_data_for_blendfile(ReuseOldBMainData *reuse_data, const bool load_ui) +{ + Main *old_bmain = reuse_data->old_bmain; + Main *new_bmain = reuse_data->new_bmain; + ListBase *old_wm_list = &old_bmain->wm; + ListBase *new_wm_list = &new_bmain->wm; + + /* Currently there should never be more than one WM in a main. */ + BLI_assert(BLI_listbase_count_at_most(new_wm_list, 2) <= 1); + BLI_assert(BLI_listbase_count_at_most(old_wm_list, 2) <= 1); + + wmWindowManager *old_wm = static_cast(old_wm_list->first); + wmWindowManager *new_wm = static_cast(new_wm_list->first); + + if (old_wm == nullptr) { + /* No current (old) WM. Either (new) WM from file is used, or if none, WM code is responsible + * to add a new default WM. Nothing to do here. */ + return; + } + + /* Current (old) WM, and (new) WM in file, and loading UI: use WM from file, keep old WM around + * for further processing in WM code. */ + if (load_ui && new_wm != nullptr) { + /* Support window-manager ID references being held between file load operations by keeping + * #Main.wm.first memory address in-place, while swapping all of it's contents. + * + * This is needed so items such as key-maps can be held by an add-on, + * without it pointing to invalid memory, see: #86431. */ + BLI_remlink(old_wm_list, old_wm); + BLI_remlink(new_wm_list, new_wm); + BKE_lib_id_swap_full(nullptr, + &old_wm->id, + &new_wm->id, + true, + (ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_SKIP_UPDATE_TAGGING | + ID_REMAP_SKIP_USER_REFCOUNT | ID_REMAP_FORCE_UI_POINTERS)); + /* Not strictly necessary, but helps for readability. */ + std::swap(old_wm, new_wm); + BLI_addhead(new_wm_list, new_wm); + /* Do not add old WM back to `old_bmain`, so that it does not get freed when `old_bmain` is + * freed. Calling WM code will need this old WM to restore some windows etc. data into the + * new WM, and is responsible to free it properly. */ + reuse_data->wm_setup_data->old_wm = old_wm; + + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + BKE_id_remapper_add(remapper, &old_wm->id, &new_wm->id); + } + /* Current (old) WM, but no (new) one in file (should only happen when reading pre 2.5 files, no + * WM back then), or not loading UI: Keep current WM. */ + else { + swap_old_bmain_data_for_blendfile(reuse_data, ID_WM); + old_wm->init_flag &= ~WM_INIT_FLAG_WINDOW; + } +} + +static int swap_old_bmain_data_for_blendfile_dependencies_process_cb( + LibraryIDLinkCallbackData *cb_data) +{ + ID *id = *cb_data->id_pointer; + + if (id == nullptr) { + return IDWALK_RET_NOP; + } + + ReuseOldBMainData *reuse_data = static_cast(cb_data->user_data); + + /* First check if it has already been remapped. */ + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + if (reuse_bmain_data_remapper_is_id_remapped(remapper, id)) { + return IDWALK_RET_NOP; + } + + IDNameLib_Map *id_map = reuse_data->id_map; + BLI_assert(id_map != nullptr); + + ID *id_new = BKE_main_idmap_lookup_id(id_map, id); + BKE_id_remapper_add(remapper, id, id_new); + + return IDWALK_RET_NOP; +} + +static void swap_old_bmain_data_dependencies_process(ReuseOldBMainData *reuse_data, + const short id_code) +{ + Main *new_bmain = reuse_data->new_bmain; + ListBase *new_lb = which_libbase(new_bmain, id_code); + + BLI_assert(reuse_data->id_map != nullptr); + + ID *new_id_iter; + FOREACH_MAIN_LISTBASE_ID_BEGIN (new_lb, new_id_iter) { + /* Check all ID usages and find a matching new ID to remap them to in `new_bmain` if possible + * (matching by names and libraries). + * + * Note that this call does not do any effective remapping, it only adds required remapping + * operations to the remapper. */ + BKE_library_foreach_ID_link(new_bmain, + new_id_iter, + swap_old_bmain_data_for_blendfile_dependencies_process_cb, + reuse_data, + IDWALK_READONLY | IDWALK_INCLUDE_UI | IDWALK_DO_LIBRARY_POINTER); + } + FOREACH_MAIN_LISTBASE_ID_END; +} + +static int reuse_bmain_data_invalid_local_usages_fix_cb(LibraryIDLinkCallbackData *cb_data) +{ + ID *id = *cb_data->id_pointer; + + if (id == nullptr) { + return IDWALK_RET_NOP; + } + + /* Embedded data cannot (yet) be fully trusted to have the same lib pointer as their owner ID, so + * for now ignore them. This code should never have anything to fix for them anyway, otherwise + * there is something extremely wrong going on. */ + if ((cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING)) != 0) { + return IDWALK_RET_NOP; + } + + if (!ID_IS_LINKED(id)) { + ID *owner_id = cb_data->owner_id; + + /* Do not allow linked data to use local data. */ + if (ID_IS_LINKED(owner_id)) { + if (cb_data->cb_flag & IDWALK_CB_USER) { + id_us_min(id); + } + *cb_data->id_pointer = nullptr; + } + /* Do not allow local liboverride data to use local data as reference. */ + else if (ID_IS_OVERRIDE_LIBRARY_REAL(owner_id) && + &owner_id->override_library->reference == cb_data->id_pointer) + { + if (cb_data->cb_flag & IDWALK_CB_USER) { + id_us_min(id); + } + *cb_data->id_pointer = nullptr; + } + } + + return IDWALK_RET_NOP; +} + +/** Detect and fix invalid usages of locale IDs by linked ones (or as reference of liboverrides). + */ +static void reuse_bmain_data_invalid_local_usages_fix(ReuseOldBMainData *reuse_data) +{ + Main *new_bmain = reuse_data->new_bmain; + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (new_bmain, id_iter) { + if (!ID_IS_LINKED(id_iter) && !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) { + continue; + } + + ID *liboverride_reference = ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) ? + id_iter->override_library->reference : + nullptr; + + BKE_library_foreach_ID_link( + new_bmain, id_iter, reuse_bmain_data_invalid_local_usages_fix_cb, reuse_data, 0); + + /* Liboverrides who lost their reference should not be liboverrides anymore, but regular IDs. + */ + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) && + id_iter->override_library->reference != liboverride_reference) + { + BKE_lib_override_library_free(&id_iter->override_library, true); + } + } + FOREACH_MAIN_ID_END; +} + +/* Post-remapping helpers to ensure validity of the UI data. */ + +static void view3d_data_consistency_ensure(wmWindow *win, Scene *scene, ViewLayer *view_layer) +{ + bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype != SPACE_VIEW3D) { + continue; + } + + View3D *v3d = reinterpret_cast(sl); + if (v3d->camera == nullptr || v3d->scenelock) { + v3d->camera = scene->camera; + } + if (v3d->localvd == nullptr) { + continue; + } + + if (v3d->localvd->camera == nullptr || v3d->scenelock) { + v3d->localvd->camera = v3d->camera; + } + /* Local-view can become invalid during undo/redo steps, exit it when no valid object could + * be found. */ + Base *base; + for (base = static_cast(view_layer->object_bases.first); base; base = base->next) { + if (base->local_view_bits & v3d->local_view_uuid) { + break; + } + } + if (base != nullptr) { + /* The local view3D still has a valid object, nothing else to do. */ + continue; + } + + /* No valid object found for the local view3D, it has to be cleared off. */ + MEM_freeN(v3d->localvd); + v3d->localvd = nullptr; + v3d->local_view_uuid = 0; + + /* Region-base storage is different depending on whether the space is active or not. */ + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : &sl->regionbase; + LISTBASE_FOREACH (ARegion *, region, regionbase) { + if (region->regiontype != RGN_TYPE_WINDOW) { + continue; + } + + RegionView3D *rv3d = static_cast(region->regiondata); + MEM_SAFE_FREE(rv3d->localvd); + } + } + } +} + +static void wm_data_consistency_ensure(wmWindowManager *curwm, + Scene *cur_scene, + ViewLayer *cur_view_layer) +{ + /* There may not be any available WM (e.g. when reading `userpref.blend`). */ + if (curwm == nullptr) { + return; + } + + LISTBASE_FOREACH (wmWindow *, win, &curwm->windows) { + if (win->scene == nullptr) { + win->scene = cur_scene; + } + if (BKE_view_layer_find(win->scene, win->view_layer_name) == nullptr) { + STRNCPY(win->view_layer_name, cur_view_layer->name); + } + + view3d_data_consistency_ensure(win, win->scene, cur_view_layer); + } +} + /** * Context matching, handle no-UI case. * @@ -227,10 +653,10 @@ static void setup_app_userdef(BlendFileData *bfd) static void setup_app_data(bContext *C, BlendFileData *bfd, const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, BlendFileReadReport *reports) { Main *bmain = G_MAIN; - Scene *curscene = nullptr; const bool recover = (G.fileflags & G_FILE_RECOVER_READ) != 0; const bool is_startup = params->is_startup; enum { @@ -266,103 +692,134 @@ static void setup_app_data(bContext *C, clean_paths(bfd->main); } - /* The following code blocks performs complex window-manager matching. */ + BLI_assert(BKE_main_namemap_validate(bfd->main)); - /* no load screens? */ + /* Temp data to handle swapping around IDs between old and new mains, and accumulate the + * required remapping accordingly. */ + ReuseOldBMainData reuse_data = {nullptr}; + reuse_data.new_bmain = bfd->main; + reuse_data.old_bmain = bmain; + reuse_data.wm_setup_data = wm_setup_data; + + if (mode != LOAD_UNDO) { + const short ui_id_codes[]{ID_WS, ID_SCR}; + + /* WM needs special complex handling, regardless of whether UI is kept or loaded from file. */ + swap_wm_data_for_blendfile(&reuse_data, mode == LOAD_UI); + if (mode != LOAD_UI) { + /* Re-use UI data from `old_bmain` if keeping existing UI. */ + for (auto id_code : ui_id_codes) { + swap_old_bmain_data_for_blendfile(&reuse_data, id_code); + } + } + + /* Needs to happen after all data from `old_bmain` has been moved into new one. */ + BLI_assert(reuse_data.id_map == nullptr); + reuse_data.id_map = BKE_main_idmap_create( + reuse_data.new_bmain, true, reuse_data.old_bmain, MAIN_IDMAP_TYPE_NAME); + + swap_old_bmain_data_dependencies_process(&reuse_data, ID_WM); + if (mode != LOAD_UI) { + for (auto id_code : ui_id_codes) { + swap_old_bmain_data_dependencies_process(&reuse_data, id_code); + } + } + + BKE_main_idmap_destroy(reuse_data.id_map); + } + + /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has, as long + * as the scene associated with the undo operation is visible in one of the open windows. + * + * - 'curscreen->scene' - scene the user is currently looking at. + * - 'bfd->curscene' - scene undo-step was created in. + * + * This means that users can have 2 or more windows open and undo in both without screens + * switching. But if they close one of the screens, undo will ensure that the scene being + * operated on will be activated (otherwise we'd be undoing on an off-screen scene which isn't + * acceptable). See: #43424. */ + bool track_undo_scene = false; + + /* Always use the Scene and ViewLayer pointers from new file, if possible. */ + ViewLayer *cur_view_layer = bfd->cur_view_layer; + Scene *curscene = bfd->curscene; + + wmWindow *win = nullptr; + bScreen *curscreen = nullptr; + + /* Ensure that there is a valid scene and viewlayer. */ + if (curscene == nullptr) { + curscene = static_cast(bfd->main->scenes.first); + } + /* Empty file, add a scene to make Blender work. */ + if (curscene == nullptr) { + curscene = BKE_scene_add(bfd->main, "Empty"); + } + if (cur_view_layer == nullptr) { + /* Fallback to the active scene view layer. */ + cur_view_layer = BKE_view_layer_default_view(curscene); + } + + /* If UI is not loaded when opening actual .blend file, and always in case of undo memfile + * reading. */ if (mode != LOAD_UI) { - /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has, - * as long as the scene associated with the undo operation is visible - * in one of the open windows. - * - * - 'curscreen->scene' - scene the user is currently looking at. - * - 'bfd->curscene' - scene undo-step was created in. - * - * This means users can have 2+ windows open and undo in both without screens switching. - * But if they close one of the screens, - * undo will ensure that the scene being operated on will be activated - * (otherwise we'd be undoing on an off-screen scene which isn't acceptable). - * see: #43424 - */ - wmWindow *win; - bScreen *curscreen = nullptr; - ViewLayer *cur_view_layer; - bool track_undo_scene; - - /* comes from readfile.c */ - SWAP(ListBase, bmain->wm, bfd->main->wm); - SWAP(ListBase, bmain->workspaces, bfd->main->workspaces); - SWAP(ListBase, bmain->screens, bfd->main->screens); - /* NOTE: UI IDs are assumed to be only local data-blocks, so no need to call - * #BKE_main_namemap_clear here (otherwise, the swapping would fail in many funny ways). */ - if (bmain->name_map != nullptr) { - BKE_main_namemap_destroy(&bmain->name_map); - } - if (bfd->main->name_map != nullptr) { - BKE_main_namemap_destroy(&bfd->main->name_map); - } - - /* In case of actual new file reading without loading UI, we need to regenerate the session - * uuid of the UI-related datablocks we are keeping from previous session, otherwise their uuid - * will collide with some generated for newly read data. */ - if (mode != LOAD_UNDO) { - ID *id; - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->wm, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->workspaces, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->screens, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - } - - /* we re-use current window and screen */ + /* Re-use current window and screen. */ win = CTX_wm_window(C); curscreen = CTX_wm_screen(C); - /* but use Scene pointer from new file */ - curscene = bfd->curscene; - cur_view_layer = bfd->cur_view_layer; track_undo_scene = (mode == LOAD_UNDO && curscreen && curscene && bfd->main->wm.first); - if (curscene == nullptr) { - curscene = static_cast(bfd->main->scenes.first); - } - /* empty file, we add a scene to make Blender work */ - if (curscene == nullptr) { - curscene = BKE_scene_add(bfd->main, "Empty"); - } - if (cur_view_layer == nullptr) { - /* fallback to scene layer */ - cur_view_layer = BKE_view_layer_default_view(curscene); - } - if (track_undo_scene) { - /* keep the old (free'd) scene, let 'blo_lib_link_screen_restore' - * replace it with 'curscene' if its needed */ + /* Keep the old (to-be-freed) scene, remapping below will ensure it's remapped to the + * matching new scene if available, or NULL otherwise, in which case + * #wm_data_consistency_ensure will define `curscene` as the active one. */ } - /* and we enforce curscene to be in current screen */ + /* Enforce curscene to be in current screen. */ else if (win) { /* The window may be nullptr in background-mode. */ win->scene = curscene; } + } - /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */ - blo_lib_link_restore(bmain, bfd->main, CTX_wm_manager(C), curscene, cur_view_layer); + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + /* Apply remapping of ID pointers caused by re-using part of the data from the 'old' main into + * the new one. */ + if (reuse_data.remapper != nullptr) { + /* In undo case all 'keeping old data' and remapping logic is now handled in readfile code + * itself, so there should never be any remapping to do here. */ + BLI_assert(mode != LOAD_UNDO); + + /* Handle all pending remapping from swapping old and new IDs around. */ + BKE_libblock_remap_multiple_raw(bfd->main, + reuse_data.remapper, + (ID_REMAP_FORCE_UI_POINTERS | ID_REMAP_SKIP_USER_REFCOUNT | + ID_REMAP_SKIP_UPDATE_TAGGING | ID_REMAP_SKIP_USER_CLEAR)); + + /* Fix potential invalid usages of now-locale-data created by remapping above. Should never + * be needed in undo case, this is to address cases like 'opening a blendfile that was a + * library of the previous opened blendfile'. */ + reuse_bmain_data_invalid_local_usages_fix(&reuse_data); + + BKE_id_remapper_free(reuse_data.remapper); + reuse_data.remapper = nullptr; + + wm_data_consistency_ensure(CTX_wm_manager(C), curscene, cur_view_layer); + } + + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + if (mode != LOAD_UI) { if (win) { curscene = win->scene; } if (track_undo_scene) { wmWindowManager *wm = static_cast(bfd->main->wm.first); - if (wm_scene_is_visible(wm, bfd->curscene) == false) { + if (!wm_scene_is_visible(wm, bfd->curscene)) { curscene = bfd->curscene; - win->scene = curscene; + if (win) { + win->scene = curscene; + } BKE_screen_view3d_scene_sync(curscreen, curscene); } } @@ -374,48 +831,35 @@ static void setup_app_data(bContext *C, BKE_screen_gizmo_tag_refresh(curscreen); } } + CTX_data_scene_set(C, curscene); + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + /* This frees the `old_bmain`. */ BKE_blender_globals_main_replace(bfd->main); bmain = G_MAIN; bfd->main = nullptr; - CTX_data_main_set(C, bmain); - /* case G_FILE_NO_UI or no screens in file */ - if (mode != LOAD_UI) { - /* leave entire context further unaltered? */ - CTX_data_scene_set(C, curscene); - } - else { + BLI_assert(BKE_main_namemap_validate(bmain)); + + /* These context data should remain valid if old UI is being re-used. */ + if (mode == LOAD_UI) { + /* Setting WindowManager in context clears all other Context UI data (window, area, etc.). So + * only do it when effectively loading a new WM, otherwise just assert that the WM from context + * is still the same as in `new_bmain`. */ CTX_wm_manager_set(C, static_cast(bmain->wm.first)); CTX_wm_screen_set(C, bfd->curscreen); - CTX_data_scene_set(C, bfd->curscene); CTX_wm_area_set(C, nullptr); CTX_wm_region_set(C, nullptr); CTX_wm_menu_set(C, nullptr); - curscene = bfd->curscene; } + BLI_assert(CTX_wm_manager(C) == static_cast(bmain->wm.first)); /* Keep state from preferences. */ const int fileflags_keep = G_FILE_FLAG_ALL_RUNTIME; G.fileflags = (G.fileflags & fileflags_keep) | (bfd->fileflags & ~fileflags_keep); - /* this can happen when active scene was lib-linked, and doesn't exist anymore */ - if (CTX_data_scene(C) == nullptr) { - wmWindow *win = CTX_wm_window(C); - - /* in case we don't even have a local scene, add one */ - if (!bmain->scenes.first) { - BKE_scene_add(bmain, "Empty"); - } - - CTX_data_scene_set(C, static_cast(bmain->scenes.first)); - win->scene = CTX_data_scene(C); - curscene = CTX_data_scene(C); - } - - BLI_assert(curscene == CTX_data_scene(C)); - /* special cases, override loaded flags: */ if (G.f != bfd->globalf) { const int flags_keep = G_FLAG_ALL_RUNTIME; @@ -440,7 +884,7 @@ static void setup_app_data(bContext *C, /* NOTE: readfile's `do_versions` does not allow to create new IDs, and only operates on a single * library at a time. This code needs to operate on the whole Main at once. */ - /* NOTE: Check bmain version (i.e. current blend file version), AND the versions of all the + /* NOTE: Check Main version (i.e. current blend file version), AND the versions of all the * linked libraries. */ if (mode != LOAD_UNDO && !blendfile_or_libraries_versions_atleast(bmain, 302, 1)) { BKE_lib_override_library_main_proxy_convert(bmain, reports); @@ -494,17 +938,11 @@ static void setup_app_data(bContext *C, RE_FreeAllPersistentData(); } - if (mode == LOAD_UNDO) { - /* In undo/redo case, we do a whole lot of magic tricks to avoid having to re-read linked - * data-blocks from libraries (since those are not supposed to change). Unfortunately, that - * means that we do not reset their user count, however we do increase that one when doing - * lib_link on local IDs using linked ones. - * There is no real way to predict amount of changes here, so we have to fully redo - * reference-counting. - * Now that we re-use (and do not liblink in readfile.c) most local data-blocks as well, - * we have to recompute reference-counts for all local IDs too. */ - BKE_main_id_refcount_recompute(bmain, false); - } + /* Both undo and regular file loading can perform some fairly complex ID manipulation, simpler + * and safer to fully redo reference-counting. This is a relatively cheap process anyway. */ + BKE_main_id_refcount_recompute(bmain, false); + + BLI_assert(BKE_main_namemap_validate(bmain)); if (mode != LOAD_UNDO && !USER_EXPERIMENTAL_TEST(&U, no_override_auto_resync)) { reports->duration.lib_overrides_resync = PIL_check_seconds_timer(); @@ -526,13 +964,14 @@ static void setup_app_data(bContext *C, static void setup_app_blend_file_data(bContext *C, BlendFileData *bfd, const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, BlendFileReadReport *reports) { if ((params->skip_flags & BLO_READ_SKIP_USERDEF) == 0) { setup_app_userdef(bfd); } if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) { - setup_app_data(C, bfd, params, reports); + setup_app_data(C, bfd, params, wm_setup_data, reports); } } @@ -550,13 +989,14 @@ static void handle_subversion_warning(Main *main, BlendFileReadReport *reports) } } -void BKE_blendfile_read_setup_ex(bContext *C, - BlendFileData *bfd, - const BlendFileReadParams *params, - BlendFileReadReport *reports, - /* Extra args. */ - const bool startup_update_defaults, - const char *startup_app_template) +void BKE_blendfile_read_setup_readfile(bContext *C, + BlendFileData *bfd, + const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, + BlendFileReadReport *reports, + /* Extra args. */ + const bool startup_update_defaults, + const char *startup_app_template) { if (bfd->main->is_read_invalid) { BKE_reports_prepend(reports->reports, @@ -570,16 +1010,16 @@ void BKE_blendfile_read_setup_ex(bContext *C, BLO_update_defaults_startup_blend(bfd->main, startup_app_template); } } - setup_app_blend_file_data(C, bfd, params, reports); + setup_app_blend_file_data(C, bfd, params, wm_setup_data, reports); BLO_blendfiledata_free(bfd); } -void BKE_blendfile_read_setup(bContext *C, - BlendFileData *bfd, - const BlendFileReadParams *params, - BlendFileReadReport *reports) +void BKE_blendfile_read_setup_undo(bContext *C, + BlendFileData *bfd, + const BlendFileReadParams *params, + BlendFileReadReport *reports) { - BKE_blendfile_read_setup_ex(C, bfd, params, reports, false, nullptr); + BKE_blendfile_read_setup_readfile(C, bfd, params, nullptr, reports, false, nullptr); } BlendFileData *BKE_blendfile_read(const char *filepath, @@ -636,16 +1076,7 @@ BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain, BLO_blendfiledata_free(bfd); bfd = nullptr; } - if (bfd) { - /* Removing the unused workspaces, screens and wm is useless here, setup_app_data will switch - * those lists with the ones from old bmain, which freeing is much more efficient than - * individual calls to `BKE_id_free()`. - * Further more, those are expected to be empty anyway with new memfile reading code. */ - BLI_assert(BLI_listbase_is_empty(&bfd->main->wm)); - BLI_assert(BLI_listbase_is_empty(&bfd->main->workspaces)); - BLI_assert(BLI_listbase_is_empty(&bfd->main->screens)); - } - else { + if (bfd == nullptr) { BKE_reports_prepend(reports, "Loading failed: "); } return bfd; diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 7e4bfc4d4cf..3fbd960f77f 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -66,6 +66,12 @@ typedef struct BlendFileData { eBlenFileType type; } BlendFileData; +/** Data used by WM readfile code and BKE's setup_app_data to handle the complex preservation logic + * of WindowManager and other UI data-blocks across blendfile reading prcess. */ +typedef struct BlendFileReadWMSetupData { + struct wmWindowManager *old_wm; /** The existing WM when filereading process is started. */ +} BlendFileReadWMSetupData; + struct BlendFileReadParams { uint skip_flags : 3; /* #eBLOReadSkip */ uint is_startup : 1; @@ -446,17 +452,6 @@ void BLO_library_temp_free(TempLibraryContext *temp_lib_ctx); void *BLO_library_read_struct(struct FileData *fd, struct BHead *bh, const char *blockname); -/* internal function but we need to expose it */ -/** - * Used to link a file (without UI) to the current UI. - * Note that it assumes the old pointers in UI are still valid, so old Main is not freed. - */ -void blo_lib_link_restore(struct Main *oldmain, - struct Main *newmain, - struct wmWindowManager *curwm, - struct Scene *curscene, - struct ViewLayer *cur_view_layer); - typedef void (*BLOExpandDoitCallback)(void *fdhandle, struct Main *mainvar, void *idv); /** diff --git a/source/blender/blenloader/intern/readblenentry.cc b/source/blender/blenloader/intern/readblenentry.cc index 92a23e494a5..96b36d5192e 100644 --- a/source/blender/blenloader/intern/readblenentry.cc +++ b/source/blender/blenloader/intern/readblenentry.cc @@ -455,21 +455,20 @@ BlendFileData *BLO_read_from_memfile(Main *oldmain, fd->skip_flags = eBLOReadSkip(params->skip_flags); STRNCPY(fd->relabase, filepath); - /* separate libraries from old main */ + /* Build old ID map for all old IDs. */ + blo_make_old_idmap_from_main(fd, oldmain); + + /* Separate linked data from old main. */ blo_split_main(&old_mainlist, oldmain); - /* add the library pointers in oldmap lookup */ - blo_add_library_pointer_map(&old_mainlist, fd); + fd->old_mainlist = &old_mainlist; - if ((params->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0) { - /* Build idmap of old main (we only care about local data here, so we can do that after - * split_main() call. */ - blo_make_old_idmap_from_main(fd, static_cast
(old_mainlist.first)); - } - - /* removed packed data from this trick - it's internal data that needs saves */ + /* Removed packed data from this trick - it's internal data that needs saves. */ /* Store all existing ID caches pointers into a mapping, to allow restoring them into newly - * read IDs whenever possible. */ + * read IDs whenever possible. + * + * Note that this is only required for local data, since linked data are always re-used + * 'as-is'. */ blo_cache_storage_init(fd, oldmain); bfd = blo_read_file_internal(fd, filepath); diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 8750eca2b68..bc60cd7a4e5 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -1302,6 +1302,9 @@ void blo_filedata_free(FileData *fd) if (fd->old_idmap_uuid != nullptr) { BKE_main_idmap_destroy(fd->old_idmap_uuid); } + if (fd->new_idmap_uuid != nullptr) { + BKE_main_idmap_destroy(fd->new_idmap_uuid); + } blo_cache_storage_end(fd); if (fd->bheadmap) { MEM_freeN(fd->bheadmap); @@ -1523,22 +1526,6 @@ void blo_end_packed_pointer_map(FileData *fd, Main *oldmain) } } -void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd) -{ - ListBase *lbarray[INDEX_ID_MAX]; - - LISTBASE_FOREACH (Main *, ptr, old_mainlist) { - int i = set_listbasepointers(ptr, lbarray); - while (i--) { - LISTBASE_FOREACH (ID *, id, lbarray[i]) { - oldnewmap_lib_insert(fd, id, id, GS(id->name)); - } - } - } - - fd->old_mainlist = old_mainlist; -} - void blo_make_old_idmap_from_main(FileData *fd, Main *bmain) { if (fd->old_idmap_uuid != nullptr) { @@ -2176,498 +2163,7 @@ static void lib_link_scenes_check_set(Main *bmain) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Read ID: Screen - * \{ */ -/* how to handle user count on pointer restore */ -enum ePointerUserMode { - USER_IGNORE = 0, /* ignore user count */ - USER_REAL = 1, /* ensure at least one real user (fake user ignored) */ -}; - -static void restore_pointer_user(ID *id, ID *newid, ePointerUserMode user) -{ - BLI_assert(STREQ(newid->name + 2, id->name + 2)); - BLI_assert(newid->lib == id->lib); - UNUSED_VARS_NDEBUG(id); - - if (user == USER_REAL) { - id_us_ensure_real(newid); - } -} - -#ifndef USE_GHASH_RESTORE_POINTER -/** - * A version of #restore_pointer_by_name that performs a full search (slow!). - * Use only for limited lookups, when the overhead of - * creating a #IDNameLib_Map for a single lookup isn't worthwhile. - */ -static void *restore_pointer_by_name_main(Main *mainp, ID *id, ePointerUserMode user) -{ - if (id) { - ListBase *lb = which_libbase(mainp, GS(id->name)); - if (lb) { /* there's still risk of checking corrupt mem (freed Ids in oops) */ - ID *idn = lb->first; - for (; idn; idn = idn->next) { - if (STREQ(idn->name + 2, id->name + 2)) { - if (idn->lib == id->lib) { - restore_pointer_user(id, idn, user); - break; - } - } - } - return idn; - } - } - return nullptr; -} -#endif - -/** - * Only for undo files, or to restore a screen after reading without UI... - * - * \param user: - * - USER_IGNORE: no user-count change. - * - USER_REAL: ensure a real user (even if a fake one is set). - * \param id_map: lookup table, use when performing many lookups. - * this could be made an optional argument (falling back to a full lookup), - * however at the moment it's always available. - */ -static void *restore_pointer_by_name(IDNameLib_Map *id_map, ID *id, ePointerUserMode user) -{ -#ifdef USE_GHASH_RESTORE_POINTER - if (id) { - /* use fast lookup when available */ - ID *idn = BKE_main_idmap_lookup_id(id_map, id); - if (idn) { - restore_pointer_user(id, idn, user); - } - return idn; - } - return nullptr; -#else - Main *mainp = BKE_main_idmap_main_get(id_map); - return restore_pointer_by_name_main(mainp, id, user); -#endif -} - -static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data) -{ - const int cb_flag = cb_data->cb_flag; - ID **id_pointer = cb_data->id_pointer; - if (cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING) || *id_pointer == nullptr) { - return IDWALK_RET_NOP; - } - - IDNameLib_Map *id_map = static_cast(cb_data->user_data); - - /* NOTE: Handling of user-count here is really bad, defining its own system... - * Will have to be refactored at some point, but that is not top priority task for now. - * And all user-counts are properly recomputed at the end of the undo management code anyway. */ - *id_pointer = static_cast(restore_pointer_by_name( - id_map, *id_pointer, (cb_flag & IDWALK_CB_USER_ONE) ? USER_REAL : USER_IGNORE)); - - return IDWALK_RET_NOP; -} - -static void lib_link_main_data_restore(IDNameLib_Map *id_map, Main *newmain) -{ - ID *id; - FOREACH_MAIN_ID_BEGIN (newmain, id) { - BKE_library_foreach_ID_link(newmain, id, lib_link_main_data_restore_cb, id_map, IDWALK_NOP); - } - FOREACH_MAIN_ID_END; -} - -static void lib_link_wm_xr_data_restore(IDNameLib_Map *id_map, wmXrData *xr_data) -{ - xr_data->session_settings.base_pose_object = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(xr_data->session_settings.base_pose_object), USER_REAL)); -} - -static void lib_link_window_scene_data_restore(wmWindow *win, Scene *scene, ViewLayer *view_layer) -{ - bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); - - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - if (sl->spacetype == SPACE_VIEW3D) { - View3D *v3d = reinterpret_cast(sl); - - if (v3d->camera == nullptr || v3d->scenelock) { - v3d->camera = scene->camera; - } - - if (v3d->localvd) { - Base *base = nullptr; - - v3d->localvd->camera = scene->camera; - - /* Local-view can become invalid during undo/redo steps, - * so we exit it when no could be found. */ - for (base = static_cast(view_layer->object_bases.first); base; base = base->next) - { - if (base->local_view_bits & v3d->local_view_uuid) { - break; - } - } - if (base == nullptr) { - MEM_freeN(v3d->localvd); - v3d->localvd = nullptr; - v3d->local_view_uuid = 0; - - /* Region-base storage is different depending if the space is active. */ - ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : - &sl->regionbase; - LISTBASE_FOREACH (ARegion *, region, regionbase) { - if (region->regiontype == RGN_TYPE_WINDOW) { - RegionView3D *rv3d = static_cast(region->regiondata); - if (rv3d->localvd) { - MEM_freeN(rv3d->localvd); - rv3d->localvd = nullptr; - } - } - } - } - } - } - } - } -} - -static void lib_link_restore_viewer_path(IDNameLib_Map *id_map, ViewerPath *viewer_path) -{ - LISTBASE_FOREACH (ViewerPathElem *, elem, &viewer_path->path) { - if (elem->type == VIEWER_PATH_ELEM_TYPE_ID) { - IDViewerPathElem *typed_elem = reinterpret_cast(elem); - typed_elem->id = static_cast( - restore_pointer_by_name(id_map, typed_elem->id, USER_IGNORE)); - } - } -} - -static void lib_link_workspace_layout_restore(IDNameLib_Map *id_map, - Main *newmain, - WorkSpaceLayout *layout) -{ - bScreen *screen = BKE_workspace_layout_screen_get(layout); - - /* avoid conflicts with 2.8x branch */ - { - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - switch (static_cast(sl->spacetype)) { - case SPACE_VIEW3D: { - View3D *v3d = reinterpret_cast(sl); - - v3d->camera = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(v3d->camera), USER_REAL)); - v3d->ob_center = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(v3d->ob_center), USER_REAL)); - - lib_link_restore_viewer_path(id_map, &v3d->viewer_path); - break; - } - case SPACE_GRAPH: { - SpaceGraph *sipo = reinterpret_cast(sl); - bDopeSheet *ads = sipo->ads; - - if (ads) { - ads->source = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(ads->source), USER_REAL)); - - if (ads->filter_grp) { - ads->filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(ads->filter_grp), USER_IGNORE)); - } - } - - /* force recalc of list of channels (i.e. includes calculating F-Curve colors) - * thus preventing the "black curves" problem post-undo - */ - sipo->runtime.flag |= SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR; - break; - } - case SPACE_PROPERTIES: { - SpaceProperties *sbuts = reinterpret_cast(sl); - sbuts->pinid = static_cast( - restore_pointer_by_name(id_map, sbuts->pinid, USER_IGNORE)); - if (sbuts->pinid == nullptr) { - sbuts->flag &= ~SB_PIN_CONTEXT; - } - - /* TODO: restore path pointers: #40046 - * (complicated because this contains data pointers too, not just ID). */ - MEM_SAFE_FREE(sbuts->path); - break; - } - case SPACE_FILE: { - SpaceFile *sfile = reinterpret_cast(sl); - sfile->op = nullptr; - sfile->tags = FILE_TAG_REBUILD_MAIN_FILES; - break; - } - case SPACE_ACTION: { - SpaceAction *saction = reinterpret_cast(sl); - - saction->action = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->action), USER_REAL)); - saction->ads.source = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->ads.source), USER_REAL)); - - if (saction->ads.filter_grp) { - saction->ads.filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->ads.filter_grp), USER_IGNORE)); - } - - /* force recalc of list of channels, potentially updating the active action - * while we're at it (as it can only be updated that way) #28962. - */ - saction->runtime.flag |= SACTION_RUNTIME_FLAG_NEED_CHAN_SYNC; - break; - } - case SPACE_IMAGE: { - SpaceImage *sima = reinterpret_cast(sl); - - sima->image = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sima->image), USER_REAL)); - - /* this will be freed, not worth attempting to find same scene, - * since it gets initialized later */ - sima->iuser.scene = nullptr; - -#if 0 - /* Those are allocated and freed by space code, no need to handle them here. */ - MEM_SAFE_FREE(sima->scopes.waveform_1); - MEM_SAFE_FREE(sima->scopes.waveform_2); - MEM_SAFE_FREE(sima->scopes.waveform_3); - MEM_SAFE_FREE(sima->scopes.vecscope); -#endif - sima->scopes.ok = 0; - - /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data - * so assume that here we're doing for undo only... - */ - sima->gpd = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sima->gpd), USER_REAL)); - sima->mask_info.mask = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(sima->mask_info.mask), USER_REAL)); - break; - } - case SPACE_SEQ: { - SpaceSeq *sseq = reinterpret_cast(sl); - - /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data - * so assume that here we're doing for undo only... - */ - sseq->gpd = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sseq->gpd), USER_REAL)); - break; - } - case SPACE_NLA: { - SpaceNla *snla = reinterpret_cast(sl); - bDopeSheet *ads = snla->ads; - - if (ads) { - ads->source = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(ads->source), USER_REAL)); - - if (ads->filter_grp) { - ads->filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(ads->filter_grp), USER_IGNORE)); - } - } - break; - } - case SPACE_TEXT: { - SpaceText *st = reinterpret_cast(sl); - - st->text = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(st->text), USER_IGNORE)); - if (st->text == nullptr) { - st->text = static_cast(newmain->texts.first); - } - } break; - case SPACE_SCRIPT: { - SpaceScript *scpt = reinterpret_cast(sl); - - scpt->script = static_cast