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
This commit is contained in:
Bastien Montagne
2023-06-05 13:54:49 +02:00
committed by Bastien Montagne
parent cdd4beeb5e
commit ebb5643e59
13 changed files with 1108 additions and 1008 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -69,7 +69,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu,
BlendFileReadReport bf_reports{};
BlendFileData *bfd = BKE_blendfile_read(mfu->filepath, &params, &bf_reports);
if (bfd != nullptr) {
BKE_blendfile_read_setup(C, bfd, &params, &bf_reports);
BKE_blendfile_read_setup_undo(C, bfd, &params, &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, &params, nullptr);
if (bfd != nullptr) {
BKE_blendfile_read_setup(C, bfd, &params, &blend_file_read_report);
BKE_blendfile_read_setup_undo(C, bfd, &params, &blend_file_read_report);
success = true;
}
}

View File

@@ -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<ID *>(old_lb->first);
ID *reused_id_iter = static_cast<ID *>(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<ID *>(discarded_id_iter->next);
reused_id_iter = static_cast<ID *>(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<ID *>(discarded_id_iter->next);
}
else {
reused_id_iter = static_cast<ID *>(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<ID *>(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<wmWindowManager *>(old_wm_list->first);
wmWindowManager *new_wm = static_cast<wmWindowManager *>(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<wmWindowManager *>(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<ReuseOldBMainData *>(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<View3D *>(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<Base *>(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<RegionView3D *>(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<Scene *>(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<Scene *>(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<wmWindowManager *>(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<wmWindowManager *>(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<wmWindowManager *>(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<Scene *>(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;

View File

@@ -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);
/**

View File

@@ -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<Main *>(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);

View File

@@ -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<IDNameLib_Map *>(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<ID *>(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<Object *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(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<View3D *>(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<Base *>(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<RegionView3D *>(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<IDViewerPathElem *>(elem);
typed_elem->id = static_cast<ID *>(
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<eSpace_Type>(sl->spacetype)) {
case SPACE_VIEW3D: {
View3D *v3d = reinterpret_cast<View3D *>(sl);
v3d->camera = static_cast<Object *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(v3d->camera), USER_REAL));
v3d->ob_center = static_cast<Object *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(v3d->ob_center), USER_REAL));
lib_link_restore_viewer_path(id_map, &v3d->viewer_path);
break;
}
case SPACE_GRAPH: {
SpaceGraph *sipo = reinterpret_cast<SpaceGraph *>(sl);
bDopeSheet *ads = sipo->ads;
if (ads) {
ads->source = static_cast<ID *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(ads->source), USER_REAL));
if (ads->filter_grp) {
ads->filter_grp = static_cast<Collection *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(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<SpaceProperties *>(sl);
sbuts->pinid = static_cast<ID *>(
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<SpaceFile *>(sl);
sfile->op = nullptr;
sfile->tags = FILE_TAG_REBUILD_MAIN_FILES;
break;
}
case SPACE_ACTION: {
SpaceAction *saction = reinterpret_cast<SpaceAction *>(sl);
saction->action = static_cast<bAction *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(saction->action), USER_REAL));
saction->ads.source = static_cast<ID *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(saction->ads.source), USER_REAL));
if (saction->ads.filter_grp) {
saction->ads.filter_grp = static_cast<Collection *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(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<SpaceImage *>(sl);
sima->image = static_cast<Image *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(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<bGPdata *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(sima->gpd), USER_REAL));
sima->mask_info.mask = static_cast<Mask *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(sima->mask_info.mask), USER_REAL));
break;
}
case SPACE_SEQ: {
SpaceSeq *sseq = reinterpret_cast<SpaceSeq *>(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<bGPdata *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(sseq->gpd), USER_REAL));
break;
}
case SPACE_NLA: {
SpaceNla *snla = reinterpret_cast<SpaceNla *>(sl);
bDopeSheet *ads = snla->ads;
if (ads) {
ads->source = static_cast<ID *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(ads->source), USER_REAL));
if (ads->filter_grp) {
ads->filter_grp = static_cast<Collection *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(ads->filter_grp), USER_IGNORE));
}
}
break;
}
case SPACE_TEXT: {
SpaceText *st = reinterpret_cast<SpaceText *>(sl);
st->text = static_cast<Text *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(st->text), USER_IGNORE));
if (st->text == nullptr) {
st->text = static_cast<Text *>(newmain->texts.first);
}
} break;
case SPACE_SCRIPT: {
SpaceScript *scpt = reinterpret_cast<SpaceScript *>(sl);
scpt->script = static_cast<Script *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(scpt->script), USER_REAL));
// screen->script = nullptr; /* 2.45 set to null, better re-run the script. */
if (scpt->script) {
SCRIPT_SET_NULL(scpt->script);
}
break;
}
case SPACE_OUTLINER: {
SpaceOutliner *space_outliner = reinterpret_cast<SpaceOutliner *>(sl);
if (space_outliner->treestore) {
TreeStoreElem *tselem;
BLI_mempool_iter iter;
BLI_mempool_iternew(space_outliner->treestore, &iter);
while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
/* Do not try to restore pointers to drivers/sequence/etc.,
* can crash in undo case! */
if (TSE_IS_REAL_ID(tselem)) {
tselem->id = static_cast<ID *>(
restore_pointer_by_name(id_map, tselem->id, USER_IGNORE));
}
else {
tselem->id = nullptr;
}
}
/* rebuild hash table, because it depends on ids too */
space_outliner->storeflag |= SO_TREESTORE_REBUILD;
}
break;
}
case SPACE_NODE: {
SpaceNode *snode = reinterpret_cast<SpaceNode *>(sl);
bNodeTreePath *path, *path_next;
bNodeTree *ntree;
/* node tree can be stored locally in id too, link this first */
snode->id = static_cast<ID *>(restore_pointer_by_name(id_map, snode->id, USER_REAL));
snode->from = static_cast<ID *>(
restore_pointer_by_name(id_map, snode->from, USER_IGNORE));
ntree = snode->id ? ntreeFromID(snode->id) : nullptr;
snode->nodetree = ntree ?
ntree :
static_cast<bNodeTree *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(snode->nodetree), USER_REAL));
for (path = static_cast<bNodeTreePath *>(snode->treepath.first); path;
path = path->next) {
if (path == snode->treepath.first) {
/* first nodetree in path is same as snode->nodetree */
path->nodetree = snode->nodetree;
}
else {
path->nodetree = static_cast<bNodeTree *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(path->nodetree), USER_REAL));
}
if (!path->nodetree) {
break;
}
}
/* remaining path entries are invalid, remove */
for (; path; path = path_next) {
path_next = path->next;
BLI_remlink(&snode->treepath, path);
MEM_freeN(path);
}
/* edittree is just the last in the path,
* set this directly since the path may have been shortened above */
if (snode->treepath.last) {
path = static_cast<bNodeTreePath *>(snode->treepath.last);
snode->edittree = path->nodetree;
}
else {
snode->edittree = nullptr;
}
break;
}
case SPACE_CLIP: {
SpaceClip *sclip = reinterpret_cast<SpaceClip *>(sl);
sclip->clip = static_cast<MovieClip *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(sclip->clip), USER_REAL));
sclip->mask_info.mask = static_cast<Mask *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(sclip->mask_info.mask), USER_REAL));
sclip->scopes.ok = 0;
break;
}
case SPACE_SPREADSHEET: {
SpaceSpreadsheet *sspreadsheet = reinterpret_cast<SpaceSpreadsheet *>(sl);
lib_link_restore_viewer_path(id_map, &sspreadsheet->viewer_path);
break;
}
case SPACE_INFO:
case SPACE_IMASEL:
case SPACE_SOUND:
case SPACE_TIME:
case SPACE_LOGIC:
case SPACE_CONSOLE:
case SPACE_USERPREF:
case SPACE_TOPBAR:
case SPACE_STATUSBAR:
case SPACE_EMPTY:
/* Nothing to do here. */
break;
}
}
}
}
}
void blo_lib_link_restore(Main *oldmain,
Main *newmain,
wmWindowManager *curwm,
Scene *curscene,
ViewLayer *cur_view_layer)
{
IDNameLib_Map *id_map = BKE_main_idmap_create(newmain, true, oldmain, MAIN_IDMAP_TYPE_NAME);
LISTBASE_FOREACH (WorkSpace *, workspace, &newmain->workspaces) {
LISTBASE_FOREACH (WorkSpaceLayout *, layout, &workspace->layouts) {
lib_link_workspace_layout_restore(id_map, newmain, layout);
}
workspace->pin_scene = static_cast<Scene *>(restore_pointer_by_name(
id_map, reinterpret_cast<ID *>(workspace->pin_scene), USER_IGNORE));
lib_link_restore_viewer_path(id_map, &workspace->viewer_path);
}
LISTBASE_FOREACH (wmWindow *, win, &curwm->windows) {
WorkSpace *workspace = BKE_workspace_active_get(win->workspace_hook);
ID *workspace_id = reinterpret_cast<ID *>(workspace);
workspace = static_cast<WorkSpace *>(restore_pointer_by_name(id_map, workspace_id, USER_REAL));
BKE_workspace_active_set(win->workspace_hook, workspace);
win->scene = static_cast<Scene *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(win->scene), USER_REAL));
if (win->scene == nullptr) {
win->scene = curscene;
}
win->unpinned_scene = static_cast<Scene *>(
restore_pointer_by_name(id_map, reinterpret_cast<ID *>(win->unpinned_scene), USER_IGNORE));
if (BKE_view_layer_find(win->scene, win->view_layer_name) == nullptr) {
STRNCPY(win->view_layer_name, cur_view_layer->name);
}
BKE_workspace_active_set(win->workspace_hook, workspace);
/* NOTE: even though that function seems to redo part of what is done by
* `lib_link_workspace_layout_restore()` above, it seems to have a slightly different scope:
* while the former updates the whole UI pointers from Main db (going over all layouts of
* all workspaces), that one only focuses one current active screen, takes care of
* potential local view, and needs window's scene pointer to be final... */
lib_link_window_scene_data_restore(win, win->scene, cur_view_layer);
}
lib_link_wm_xr_data_restore(id_map, &curwm->xr);
/* Restore all ID pointers in Main database itself
* (especially IDProperties might point to some word-space of other 'weirdly unchanged' ID
* pointers, see #69146).
* Note that this will re-apply again a few pointers in workspaces or so,
* but since we are remapping final ones already set above,
* that is just some minor harmless double-processing. */
lib_link_main_data_restore(id_map, newmain);
BKE_main_idmap_destroy(id_map);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read ID: Library
* \{ */
@@ -3011,8 +2507,73 @@ static bool read_libblock_is_identical(FileData *fd, BHead *bhead)
return true;
}
/* Re-use the whole 'noundo' local IDs by moving them from old to new main. Linked ones are handled
* separately together with their libraries.
*
* NOTE: While in theory Library IDs (and their related linked IDs) are also 'noundo' data, in
* practice they need to be handled separately, to ensure that their order in the new bmain list
* matches the one from the read blendfile. Reading linked 'placeholder' entries in a memfile
* relies on current library being the last item in the new main list. */
static void read_undo_reuse_noundo_local_ids(FileData *fd)
{
Main *old_bmain = static_cast<Main *>(fd->old_mainlist->first);
ListBase *lbarray[INDEX_ID_MAX];
BLI_assert(old_bmain->curlib == nullptr);
BLI_assert(BLI_listbase_count_at_most(fd->mainlist, 2) == 1);
int i = set_listbasepointers(old_bmain, lbarray);
while (i--) {
if (BLI_listbase_is_empty(lbarray[i])) {
continue;
}
/* Only move 'noundo' local IDs. */
ID *id = static_cast<ID *>(lbarray[i]->first);
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
if ((id_type->flags & IDTYPE_FLAGS_NO_MEMFILE_UNDO) == 0) {
continue;
}
Main *new_bmain = static_cast<Main *>(fd->mainlist->first);
ListBase *new_lb = which_libbase(new_bmain, id_type->id_code);
BLI_assert(BLI_listbase_is_empty(new_lb));
BLI_movelisttolist(new_lb, lbarray[i]);
/* Update mappings accordingly. */
LISTBASE_FOREACH (ID *, id_iter, new_lb) {
BKE_main_idmap_insert_id(fd->new_idmap_uuid, id_iter);
id_iter->tag |= LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO;
}
}
}
static void read_undo_move_libmain_data(
FileData *fd, Main *new_main, Main *old_main, Main *libmain, BHead *bhead)
{
Library *curlib = libmain->curlib;
BLI_remlink(fd->old_mainlist, libmain);
BLI_remlink_safe(&old_main->libraries, libmain->curlib);
BLI_addtail(fd->mainlist, libmain);
BLI_addtail(&new_main->libraries, libmain->curlib);
curlib->id.tag |= LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO;
BKE_main_idmap_insert_id(fd->new_idmap_uuid, &curlib->id);
if (bhead != nullptr) {
oldnewmap_lib_insert(fd, bhead->old, &curlib->id, GS(curlib->id.name));
}
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (libmain, id_iter) {
BKE_main_idmap_insert_id(fd->new_idmap_uuid, id_iter);
}
FOREACH_MAIN_ID_END;
}
/* For undo, restore matching library datablock from the old main. */
static bool read_libblock_undo_restore_library(FileData *fd, Main *main, const ID *id)
static bool read_libblock_undo_restore_library(
FileData *fd, Main *new_main, const ID *id, ID *id_old, BHead *bhead)
{
/* In undo case, most libraries and linked data should be kept as is from previous state
* (see BLO_read_from_memfile).
@@ -3024,75 +2585,104 @@ static bool read_libblock_undo_restore_library(FileData *fd, Main *main, const I
* otherwise we have to do a full read of that bhead... */
CLOG_INFO(&LOG_UNDO, 2, "UNDO: restore library %s", id->name);
if (id_old == nullptr) {
CLOG_INFO(&LOG_UNDO, 2, " -> NO match");
return false;
}
Main *libmain = static_cast<Main *>(fd->old_mainlist->first);
/* Skip oldmain itself... */
for (libmain = libmain->next; libmain; libmain = libmain->next) {
if (libmain->curlib && STREQ(id->name, libmain->curlib->id.name)) {
Main *oldmain = static_cast<Main *>(fd->old_mainlist->first);
if (&libmain->curlib->id == id_old) {
Main *old_main = static_cast<Main *>(fd->old_mainlist->first);
CLOG_INFO(&LOG_UNDO,
2,
" compare with %s -> match",
libmain->curlib ? libmain->curlib->id.name : "<nullptr>");
" compare with %s -> match (existing libpath: %s)",
libmain->curlib ? libmain->curlib->id.name : "<none>",
libmain->curlib ? libmain->curlib->filepath_abs : "<none>");
/* In case of a library, we need to re-add its main to fd->mainlist,
* because if we have later a missing ID_LINK_PLACEHOLDER,
* we need to get the correct lib it is linked to!
* Order is crucial, we cannot bulk-add it in BLO_read_from_memfile()
* like it used to be. */
BLI_remlink(fd->old_mainlist, libmain);
BLI_remlink_safe(&oldmain->libraries, libmain->curlib);
BLI_addtail(fd->mainlist, libmain);
BLI_addtail(&main->libraries, libmain->curlib);
read_undo_move_libmain_data(fd, new_main, old_main, libmain, bhead);
return true;
}
CLOG_INFO(&LOG_UNDO,
2,
" compare with %s -> NO match",
libmain->curlib ? libmain->curlib->id.name : "<nullptr>");
}
return false;
}
/* For undo, restore existing linked datablock from the old main. */
static bool read_libblock_undo_restore_linked(FileData *fd, Main *main, const ID *id, BHead *bhead)
static ID *library_id_is_yet_read(FileData *fd, Main *mainvar, BHead *bhead);
/* For undo, restore existing linked datablock from the old main.
*
* Note that IDs from existing libs have already been moved into the new main when their (local)
* ID_LI library ID was handled by #read_libblock_undo_restore_library, so this function has very
* little to do. */
static bool read_libblock_undo_restore_linked(
FileData *fd, Main *libmain, const ID *id, ID **r_id_old, BHead *bhead)
{
CLOG_INFO(&LOG_UNDO, 2, "UNDO: restore linked datablock %s", id->name);
ID *id_old = BKE_libblock_find_name(main, GS(id->name), id->name + 2);
if (id_old != nullptr) {
CLOG_INFO(&LOG_UNDO,
2,
" from %s (%s): found",
main->curlib ? main->curlib->id.name : "<nullptr>",
main->curlib ? main->curlib->filepath : "<nullptr>");
/* Even though we found our linked ID, there is no guarantee its address
* is still the same. */
if (id_old != bhead->old) {
oldnewmap_lib_insert(fd, bhead->old, id_old, GS(id_old->name));
if (*r_id_old == nullptr) {
/* If the linked ID had to be re-read at some point, its session_uuid may not be the same as
* its reference stored in the memfile anymore. Do a search by name then. */
*r_id_old = library_id_is_yet_read(fd, libmain, bhead);
if (*r_id_old == nullptr) {
CLOG_INFO(&LOG_UNDO,
2,
" from %s (%s): NOT found",
libmain->curlib ? libmain->curlib->id.name : "<nullptr>",
libmain->curlib ? libmain->curlib->filepath : "<nullptr>");
return false;
}
/* No need to do anything else for ID_LINK_PLACEHOLDER, it's assumed
* already present in its lib's main. */
return true;
CLOG_INFO(&LOG_UNDO,
2,
" from %s (%s): found by name",
libmain->curlib ? libmain->curlib->id.name : "<nullptr>",
libmain->curlib ? libmain->curlib->filepath : "<nullptr>");
/* The Library ID 'owning' this linked ID should already have been moved to new main by a call
* to #read_libblock_undo_restore_library. */
BLI_assert(*r_id_old == static_cast<ID *>(BKE_main_idmap_lookup_uuid(
fd->new_idmap_uuid, (*r_id_old)->session_uuid)));
}
else {
CLOG_INFO(&LOG_UNDO,
2,
" from %s (%s): found by session_uuid",
libmain->curlib ? libmain->curlib->id.name : "<nullptr>",
libmain->curlib ? libmain->curlib->filepath : "<nullptr>");
/* The Library ID 'owning' this linked ID should already have been moved to new main by a call
* to #read_libblock_undo_restore_library. */
BLI_assert(*r_id_old == static_cast<ID *>(
BKE_main_idmap_lookup_uuid(fd->new_idmap_uuid, id->session_uuid)));
}
CLOG_INFO(&LOG_UNDO,
2,
" from %s (%s): NOT found",
main->curlib ? main->curlib->id.name : "<nullptr>",
main->curlib ? main->curlib->filepath : "<nullptr>");
return false;
oldnewmap_lib_insert(fd, bhead->old, *r_id_old, GS((*r_id_old)->name));
/* No need to do anything else for ID_LINK_PLACEHOLDER, it's assumed
* already present in its lib's main. */
return true;
}
/* For undo, restore unchanged datablock from old main. */
/* For undo, restore unchanged local datablock from old main. */
static void read_libblock_undo_restore_identical(
FileData *fd, Main *main, const ID * /*id*/, ID *id_old, const int id_tag)
FileData *fd, Main *main, const ID * /*id*/, ID *id_old, BHead *bhead, const int id_tag)
{
BLI_assert((fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0);
BLI_assert(id_old != nullptr);
/* Do not add LIB_TAG_NEW here, this should not be needed/used in undo case anyway (as
* this is only for do_version-like code), but for sake of consistency, and also because
* it will tell us which ID is re-used from old Main, and which one is actually newly read. */
/* Also do not add LIB_TAG_NEED_LINK, this ID will never be re-liblinked, hence that tag will
* never be cleared, leading to critical issue in link/append code. */
/* Some tags need to be preserved here. */
id_old->tag = (id_tag & ~LIB_TAG_KEEP_ON_UNDO) | (id_old->tag & LIB_TAG_KEEP_ON_UNDO);
id_old->tag = ((id_tag | LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) & ~LIB_TAG_KEEP_ON_UNDO) |
(id_old->tag & LIB_TAG_KEEP_ON_UNDO);
id_old->lib = main->curlib;
id_old->us = ID_FAKE_USERS(id_old);
/* Do not reset id->icon_id here, memory allocated for it remains valid. */
@@ -3111,6 +2701,13 @@ static void read_libblock_undo_restore_identical(
id_old->recalc |= direct_link_id_restore_recalc_exceptions(id_old);
id_old->recalc_after_undo_push = 0;
/* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
* Note that existing datablocks in memory (which pointer value would be id_old) are not
* remapped, so no need to store this info here. */
oldnewmap_lib_insert(fd, bhead->old, id_old, bhead->code);
BKE_main_idmap_insert_id(fd->new_idmap_uuid, id_old);
if (GS(id_old->name) == ID_OB) {
Object *ob = (Object *)id_old;
/* For undo we stay in object mode during undo presses, so keep editmode disabled for re-used
@@ -3154,7 +2751,7 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
* lib-linking to restore some data that should never be affected by undo, e.g. the 3D cursor of
* #Scene. */
id_old->orig_id = id;
id_old->tag |= LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE;
id_old->tag |= LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE | LIB_TAG_NEED_LINK;
BLI_addtail(new_lb, id_old);
BLI_addtail(old_lb, id);
@@ -3163,43 +2760,67 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
static bool read_libblock_undo_restore(
FileData *fd, Main *main, BHead *bhead, int id_tag, ID **r_id_old)
{
BLI_assert(fd->old_idmap_uuid != nullptr);
/* Get pointer to memory of new ID that we will be reading. */
const ID *id = static_cast<const ID *>(peek_struct_undo(fd, bhead));
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
#ifndef NDEBUG
if (do_partial_undo && (bhead->code != ID_LINK_PLACEHOLDER)) {
/* This code should only ever be reached for local data-blocks. */
BLI_assert(main->curlib == nullptr);
}
#endif
/* Find the 'current' existing ID we want to reuse instead of the one we
* would read from the undo memfile. */
ID *id_old = (fd->old_idmap_uuid != nullptr) ?
BKE_main_idmap_lookup_uuid(fd->old_idmap_uuid, id->session_uuid) :
nullptr;
if (bhead->code == ID_LI) {
/* Restore library datablock. */
if (read_libblock_undo_restore_library(fd, main, id)) {
/* Restore library datablock, if possible. */
if (read_libblock_undo_restore_library(fd, main, id, id_old, bhead)) {
return true;
}
}
else if (bhead->code == ID_LINK_PLACEHOLDER) {
/* Restore linked datablock. */
if (read_libblock_undo_restore_linked(fd, main, id, &id_old, bhead)) {
return true;
}
}
else if (id_type->flags & IDTYPE_FLAGS_NO_MEMFILE_UNDO) {
/* Skip reading any 'no undo' datablocks (typically UI-like ones), existing ones are kept.
* See `setup_app_data` for details. */
CLOG_INFO(
&LOG_UNDO, 2, "UNDO: skip restore datablock %s, 'NO_MEMFILE_UNDO' type of ID", id->name);
/* If that local noundo ID still exists currently, the call to
* #read_undo_reuse_noundo_local_ids at the beginning of #blo_read_file_internal will already
* have moved it into the new main, and populated accordingly the new_idmap_uuid.
*
* If this is the case, it can also be remapped for newly read data. Otherwise, this is 'lost'
* data that cannot be restored on undo, so no remapping should exist for it in the ID
* oldnewmap. */
if (id_old) {
BLI_assert(id_old == static_cast<ID *>(
BKE_main_idmap_lookup_uuid(fd->new_idmap_uuid, id->session_uuid)));
oldnewmap_lib_insert(fd, bhead->old, id_old, bhead->code);
}
return true;
}
else if (bhead->code == ID_LINK_PLACEHOLDER) {
/* Restore linked datablock. */
if (read_libblock_undo_restore_linked(fd, main, id, bhead)) {
return true;
}
if (!do_partial_undo) {
CLOG_INFO(&LOG_UNDO,
2,
"UNDO: read %s (uuid %u) -> no partial undo, always read at new address",
id->name,
id->session_uuid);
return false;
}
/* Restore local datablocks. */
ID *id_old = nullptr;
const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
if (do_partial_undo && (bhead->code != ID_LINK_PLACEHOLDER)) {
/* This code should only ever be reached for local data-blocks. */
BLI_assert(main->curlib == nullptr);
/* Find the 'current' existing ID we want to reuse instead of the one we
* would read from the undo memfile. */
BLI_assert(fd->old_idmap_uuid != nullptr);
id_old = BKE_main_idmap_lookup_uuid(fd->old_idmap_uuid, id->session_uuid);
}
if (id_old != nullptr && read_libblock_is_identical(fd, bhead)) {
/* Local datablock was unchanged, restore from the old main. */
CLOG_INFO(&LOG_UNDO,
@@ -3208,18 +2829,7 @@ static bool read_libblock_undo_restore(
id->name,
id->session_uuid);
/* Do not add LIB_TAG_NEW here, this should not be needed/used in undo case anyway (as
* this is only for do_version-like code), but for sake of consistency, and also because
* it will tell us which ID is re-used from old Main, and which one is actually new. */
/* Also do not add LIB_TAG_NEED_LINK, those IDs will never be re-liblinked, hence that tag will
* never be cleared, leading to critical issue in link/append code. */
id_tag |= LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED;
read_libblock_undo_restore_identical(fd, main, id, id_old, id_tag);
/* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
* Note that existing datablocks in memory (which pointer value would be id_old) are not
* remapped anymore, so no need to store this info here. */
oldnewmap_lib_insert(fd, bhead->old, id_old, bhead->code);
read_libblock_undo_restore_identical(fd, main, id, id_old, bhead, id_tag);
*r_id_old = id_old;
return true;
@@ -3255,23 +2865,13 @@ static BHead *read_libblock(FileData *fd,
const bool placeholder_set_indirect_extern,
ID **r_id)
{
const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
/* First attempt to restore existing datablocks for undo.
* When datablocks are changed but still exist, we restore them at the old
* address and inherit recalc flags for the dependency graph. */
ID *id_old = nullptr;
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
/* FIXME `read_libblock_undo_restore` currently often skips setting `id_old` even if there
* would be a valid matching old ID (libraries, linked data, and `IDTYPE_FLAGS_NO_MEMFILE_UNDO`
* id types, at least).
*
* It is unclear whether this is currently an issue:
* * `r_id` is currently only requested by linking code (both independent one, and as part of
* loading .blend file through `read_library_linked_ids`).
* * `main->id_map` seems to always be `nullptr` in undo case at this point.
*
* So undo case does not seem to be affected by this. A future cleanup should try to remove
* most of this related code in the future, and instead assert that both `r_id` and
* `main->id_map` are `nullptr`. */
if (read_libblock_undo_restore(fd, main, bhead, id_tag, &id_old)) {
if (r_id) {
*r_id = id_old;
@@ -3313,7 +2913,7 @@ static BHead *read_libblock(FileData *fd,
/* Insert into library map for lookup by newly read datablocks (with pointer value bhead->old).
* Note that existing datablocks in memory (which pointer value would be id_old) are not remapped
* remapped anymore, so no need to store this info here. */
ID *id_target = id_old ? id_old : id;
ID *id_target = (do_partial_undo && id_old != nullptr) ? id_old : id;
oldnewmap_lib_insert(fd, bhead->old, id_target, bhead->code);
if (r_id) {
@@ -3363,16 +2963,17 @@ static BHead *read_libblock(FileData *fd,
*r_id = nullptr;
}
}
else if (id_old) {
/* For undo, store contents read into id at id_old. */
read_libblock_undo_restore_at_old_address(fd, main, id, id_old);
if (main->id_map != nullptr) {
BKE_main_idmap_insert_id(main->id_map, id_old);
else {
if (do_partial_undo && id_old != nullptr) {
/* For undo, store contents read into id at id_old. */
read_libblock_undo_restore_at_old_address(fd, main, id, id_old);
}
if (fd->new_idmap_uuid != nullptr) {
BKE_main_idmap_insert_id(fd->new_idmap_uuid, id_target);
}
if (main->id_map != nullptr) {
BKE_main_idmap_insert_id(main->id_map, id_target);
}
}
else if (main->id_map != nullptr) {
BKE_main_idmap_insert_id(main->id_map, id);
}
return bhead;
@@ -3598,48 +3199,40 @@ static void do_versions_after_linking(FileData *fd, Main *main)
static void lib_link_all(FileData *fd, Main *bmain)
{
const bool do_partial_undo = (fd->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0;
BlendLibReader reader = {fd, bmain};
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if ((id->tag & LIB_TAG_NEED_LINK) == 0) {
/* This ID does not need liblink, just skip to next one. */
continue;
}
if ((fd->flags & FD_FLAGS_IS_MEMFILE) && GS(id->name) == ID_WM) {
/* No load UI for undo memfiles.
* Only WM currently, SCR needs it still (see below), and so does WS? */
continue;
}
if ((fd->flags & FD_FLAGS_IS_MEMFILE) && do_partial_undo &&
(id->tag & LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) != 0)
{
if ((id->tag & (LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED | LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO)) !=
0) {
BLI_assert(fd->flags & FD_FLAGS_IS_MEMFILE);
/* This ID has been re-used from 'old' bmain. Since it was therefore unchanged across
* current undo step, and old IDs re-use their old memory address, we do not need to liblink
* it at all. */
BLI_assert((id->tag & LIB_TAG_NEED_LINK) == 0);
continue;
}
lib_link_id(&reader, id);
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
if (id_type->blend_read_lib != nullptr) {
id_type->blend_read_lib(&reader, id);
}
if (GS(id->name) == ID_LI) {
lib_link_library(&reader, (Library *)id); /* Only init users. */
}
if ((id->tag & LIB_TAG_NEED_LINK) != 0) {
lib_link_id(&reader, id);
id->tag &= ~LIB_TAG_NEED_LINK;
if (id_type->blend_read_lib != nullptr) {
id_type->blend_read_lib(&reader, id);
}
if (GS(id->name) == ID_LI) {
lib_link_library(&reader, (Library *)id); /* Only init users. */
}
id->tag &= ~LIB_TAG_NEED_LINK;
}
/* Some data that should be persistent, like the 3DCursor or the tool settings, are
* stored in IDs affected by undo, like Scene. So this requires some specific handling. */
if (id_type->blend_read_undo_preserve != nullptr && id->orig_id != nullptr) {
BLI_assert(fd->flags & FD_FLAGS_IS_MEMFILE);
id_type->blend_read_undo_preserve(&reader, id, id->orig_id);
}
}
@@ -3791,6 +3384,46 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead)
/** \name Read File (Internal)
* \{ */
static int read_undo_remap_noundo_data_cb(LibraryIDLinkCallbackData *cb_data)
{
if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING)) {
return IDWALK_RET_NOP;
}
IDNameLib_Map *new_idmap_uuid = static_cast<IDNameLib_Map *>(cb_data->user_data);
ID **id_pointer = cb_data->id_pointer;
if (*id_pointer != nullptr) {
*id_pointer = BKE_main_idmap_lookup_uuid(new_idmap_uuid, (*id_pointer)->session_uuid);
}
return IDWALK_RET_NOP;
}
/* Remap 'no undo' ID usages to matching IDs in new main.
*
* 'no undo' IDs have simply be moved from old to new main so far. However, unlike the other
* re-used IDs (the 'unchanged' ones), there is no guarantee that all the ID pointers they use are
* still valid.
*
* This code performs a remapping based on the session_uuid. */
static void read_undo_remap_noundo_data(FileData *fd)
{
Main *new_bmain = static_cast<Main *>(fd->mainlist->first);
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (new_bmain, id_iter) {
if (ID_IS_LINKED(id_iter)) {
continue;
}
if ((id_iter->tag & LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO) == 0) {
continue;
}
BKE_library_foreach_ID_link(
new_bmain, id_iter, read_undo_remap_noundo_data_cb, fd->new_idmap_uuid, IDWALK_INCLUDE_UI);
}
FOREACH_MAIN_ID_END;
}
/** Contains sanity/debug checks to be performed at the very end of the reading process (i.e. after
* data, liblink, linked data, etc. has been done). */
static void blo_read_file_checks(Main *bmain)
@@ -3815,7 +3448,8 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
BlendFileData *bfd;
ListBase mainlist = {nullptr, nullptr};
if (fd->flags & FD_FLAGS_IS_MEMFILE) {
const bool is_undo = (fd->flags & FD_FLAGS_IS_MEMFILE) != 0;
if (is_undo) {
CLOG_INFO(&LOG_UNDO, 2, "UNDO: read step");
}
@@ -3854,6 +3488,16 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
}
}
if (is_undo) {
/* This idmap will store uuids of all IDs ending up in the new main, whether they are newly
* read, or re-used from the old main. */
fd->new_idmap_uuid = BKE_main_idmap_create(
static_cast<Main *>(fd->mainlist->first), false, nullptr, MAIN_IDMAP_TYPE_UUID);
/* Copy all 'no undo' local data from old to new bmain. */
read_undo_reuse_noundo_local_ids(fd);
}
while (bhead) {
switch (bhead->code) {
case BLO_CODE_DATA:
@@ -3909,8 +3553,29 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
}
}
/* do before read_libraries, but skip undo case */
if ((fd->flags & FD_FLAGS_IS_MEMFILE) == 0) {
if (is_undo) {
/* Move the remaining Library IDs and their linked data to the new main.
*
* NOTE: These linked IDs have not been detected as used in newly read main. However, they
* could be dependencies from some 'no undo' IDs that were unconditionally moved from the old
* to the new main.
*
* While there could be some more refined check here to detect such cases and only move these
* into the new bmain, in practice it is simpler to systematically move all linked data. The
* handling of libraries already moves all their linked IDs too, regardless of whether they are
* effectively used or not. */
Main *new_main = bfd->main;
Main *old_main = static_cast<Main *>(fd->old_mainlist->first);
BLI_assert(old_main != nullptr);
BLI_assert(old_main->curlib == nullptr);
for (Main *libmain = old_main->next; libmain != nullptr; libmain = libmain->next) {
read_undo_move_libmain_data(fd, new_main, old_main, libmain, nullptr);
}
}
/* Do versioning before read_libraries, but skip in undo case. */
if (!is_undo) {
if ((fd->skip_flags & BLO_READ_SKIP_DATA) == 0) {
do_versions(fd, nullptr, bfd->main);
}
@@ -3933,6 +3598,13 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
lib_link_all(fd, bfd->main);
after_liblink_merged_bmain_process(bfd->main);
if (is_undo) {
/* Ensure ID usages of reused 'no undo' IDs remain valid. */
/* Although noundo data was reused as-is from the old main, it may have ID pointers to data
* that has been removed, or that have a new address. */
read_undo_remap_noundo_data(fd);
}
fd->reports->duration.libraries = PIL_check_seconds_timer() - fd->reports->duration.libraries;
/* Skip in undo case. */
@@ -4164,7 +3836,7 @@ static BHead *find_bhead_from_idname(FileData *fd, const char *idname)
#endif
}
static ID *is_yet_read(FileData *fd, Main *mainvar, BHead *bhead)
static ID *library_id_is_yet_read(FileData *fd, Main *mainvar, BHead *bhead)
{
if (mainvar->id_map == nullptr) {
mainvar->id_map = BKE_main_idmap_create(mainvar, false, nullptr, MAIN_IDMAP_TYPE_NAME);
@@ -4218,7 +3890,7 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old)
return;
}
ID *id = is_yet_read(fd, libmain, bhead);
ID *id = library_id_is_yet_read(fd, libmain, bhead);
if (id == nullptr) {
/* ID has not been read yet, add placeholder to the main of the
@@ -4279,7 +3951,7 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old)
bhead->code = ID_SCR;
}
ID *id = is_yet_read(fd, mainvar, bhead);
ID *id = library_id_is_yet_read(fd, mainvar, bhead);
if (id == nullptr) {
read_libblock(fd,
mainvar,
@@ -4405,7 +4077,7 @@ static ID *link_named_part(
BLI_assert(BKE_idtype_idcode_is_linkable(idcode) && BKE_idtype_idcode_is_valid(idcode));
if (bhead) {
id = is_yet_read(fd, mainl, bhead);
id = library_id_is_yet_read(fd, mainl, bhead);
if (id == nullptr) {
/* not read yet */
const int tag = ((force_indirect ? LIB_TAG_INDIRECT : LIB_TAG_EXTERN) | fd->id_tag_extra);

View File

@@ -119,6 +119,16 @@ typedef struct FileData {
* IDMap using UUID's as keys of all the old IDs in the old bmain. Used during undo to find a
* matching old data when reading a new ID. */
struct IDNameLib_Map *old_idmap_uuid;
/**
* IDMap using uuids as keys of the IDs read (or moved) in the new main(s).
*
* Used during undo to ensure that the ID pointers from the 'no undo' IDs remain valid (these
* IDs are re-used from old main even if their content is not the same as in the memfile undo
* step, so they could point e.g. to an ID that does not exist in the newly read undo step).
*
* Also used to find current valid pointers (or none) of these 'no undo' IDs existing in
* read memfile. */
struct IDNameLib_Map *new_idmap_uuid;
struct BlendFileReadReport *reports;
} FileData;
@@ -151,10 +161,6 @@ void blo_make_packed_pointer_map(FileData *fd, struct Main *oldmain);
* this works because freeing old main only happens after this call.
*/
void blo_end_packed_pointer_map(FileData *fd, struct Main *oldmain);
/**
* Undo file support: add all library pointers in lookup.
*/
void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd);
/**
* Build a #GSet of old main (we only care about local data here,
* so we can do that after #blo_split_main() call.

View File

@@ -280,7 +280,8 @@ static void memfile_undosys_step_decode(
FOREACH_MAIN_ID_BEGIN (bmain, id) {
/* Clear temporary tag. */
id->tag &= ~(LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED | LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE);
id->tag &= ~(LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED | LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO |
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE);
/* We only start accumulating from this point, any tags set up to here
* are already part of the current undo state. This is done in a second

View File

@@ -874,12 +874,23 @@ enum {
*/
LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED = 1 << 17,
/**
* ID has be re-read in-place, the ID address is the same as in the old BMain, but the content is
* ID is being re-used from the old Main (instead of read from memfile), during memfile undo
* processing, because it is a 'NO_UNDO' type of ID.
*
* \note: Also means that such ID does not need to be lib-linked during undo readfile process. It
* does need to be relinked in a different way however, doing a `session_uuid`-based lookup into
* the newly read main database.
*
* RESET_AFTER_USE
*/
LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO = 1 << 18,
/**
* ID has be re-read in-place, the ID address is the same as in the old main, but the content is
* different.
*
* RESET_AFTER_USE
*/
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE = 1 << 18,
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE = 1 << 19,
/* ------------------------------------------------------------------------------------------- */
/**

View File

@@ -617,21 +617,6 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm)
}
}
void wm_close_and_free_all(bContext *C, ListBase *wmlist)
{
wmWindowManager *wm;
while ((wm = wmlist->first)) {
wm_close_and_free(C, wm);
BLI_remlink(wmlist, wm);
/* Don't handle user counts as this is only ever called once #G_MAIN has already been freed via
* #BKE_main_free so any ID's referenced by the window-manager (from ID properties) will crash.
* See: #100703. */
BKE_libblock_free_data(&wm->id, false);
BKE_libblock_free_data_py(&wm->id);
MEM_freeN(wm);
}
}
void WM_main(bContext *C)
{
/* Single refresh before handling events.

View File

@@ -177,45 +177,46 @@ bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWind
* - restoring the screens from non-active windows
* Best case is all screens match, in that case they get assigned to proper window.
*/
static void wm_window_match_init(bContext *C, ListBase *wmlist)
/**
* Clear several WM/UI runtime data that would make later complex WM handling impossible.
*
* Return data should be cleared by #wm_file_read_setup_wm_finalize. */
static BlendFileReadWMSetupData *wm_file_read_setup_wm_init(bContext *C, Main *bmain)
{
*wmlist = G_MAIN->wm;
BLI_assert(BLI_listbase_count_at_most(&bmain->wm, 2) <= 1);
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
BlendFileReadWMSetupData *wm_setup_data = MEM_cnew<BlendFileReadWMSetupData>(__func__);
if (wm == nullptr) {
return wm_setup_data;
}
/* First wrap up running stuff.
*
* Code copied from wm_init_exit.cc */
WM_jobs_kill_all(wm);
wmWindow *active_win = CTX_wm_window(C);
/* first wrap up running stuff */
/* code copied from wm_init_exit.cc */
LISTBASE_FOREACH (wmWindowManager *, wm, wmlist) {
WM_jobs_kill_all(wm);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
CTX_wm_window_set(C, win); /* needed by operator close callbacks */
WM_event_remove_handlers(C, &win->handlers);
WM_event_remove_handlers(C, &win->modalhandlers);
ED_screen_exit(C, win, WM_window_get_active_screen(win));
}
/* NOTE(@ideasman42): Clear the message bus so it's always cleared on file load.
* Otherwise it's cleared when "Load UI" is set (see #USER_FILENOUI & #wm_close_and_free).
* However it's _not_ cleared when the UI is kept. This complicates use from add-ons
* which can re-register subscribers on file-load. To support this use case,
* it's best to have predictable behavior - always clear. */
if (wm->message_bus != nullptr) {
WM_msgbus_destroy(wm->message_bus);
wm->message_bus = nullptr;
}
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
CTX_wm_window_set(C, win); /* Needed by operator close callbacks. */
WM_event_remove_handlers(C, &win->handlers);
WM_event_remove_handlers(C, &win->modalhandlers);
ED_screen_exit(C, win, WM_window_get_active_screen(win));
}
BLI_listbase_clear(&G_MAIN->wm);
if (G_MAIN->name_map != nullptr) {
/* NOTE: UI IDs are assumed to be only local data-blocks, so no need to call
* #BKE_main_namemap_clear here. */
BKE_main_namemap_destroy(&G_MAIN->name_map);
}
/* reset active window */
/* Reset active window. */
CTX_wm_window_set(C, active_win);
/* NOTE(@ideasman42): Clear the message bus so it's always cleared on file load.
* Otherwise it's cleared when "Load UI" is set (see #USER_FILENOUI and #wm_close_and_free).
* However it's _not_ cleared when the UI is kept. This complicates use from add-ons
* which can re-register subscribers on file-load. To support this use case,
* it's best to have predictable behavior - always clear. */
if (wm->message_bus != nullptr) {
WM_msgbus_destroy(wm->message_bus);
wm->message_bus = nullptr;
}
/* XXX Hack! We have to clear context menu here, because removing all modalhandlers
* above frees the active menu (at least, in the 'startup splash' case),
* causing use-after-free error in later handling of the button callbacks in UI code
@@ -225,18 +226,23 @@ static void wm_window_match_init(bContext *C, ListBase *wmlist)
* so for now just handling this specific case here. */
CTX_wm_menu_set(C, nullptr);
ED_editors_exit(G_MAIN, true);
ED_editors_exit(bmain, true);
/* Asset loading is done by the UI/editors and they keep pointers into it. So make sure to clear
* it after UI/editors. */
ED_assetlist_storage_exit();
AS_asset_libraries_exit();
/* NOTE: `wm_setup_data->old_wm` cannot be set here, as this pointer may be swapped with the
* newly read one in `setup_app_data` process (See #swap_wm_data_for_blendfile). */
return wm_setup_data;
}
static void wm_window_substitute_old(wmWindowManager *oldwm,
wmWindowManager *wm,
wmWindow *oldwin,
wmWindow *win)
static void wm_file_read_setup_wm_substitute_old_window(wmWindowManager *oldwm,
wmWindowManager *wm,
wmWindow *oldwin,
wmWindow *win)
{
win->ghostwin = oldwin->ghostwin;
win->gpuctx = oldwin->gpuctx;
@@ -279,20 +285,24 @@ static void wm_window_substitute_old(wmWindowManager *oldwm,
* An alternative solution could also be to close all windows except the first however this is
* enough of a corner case that it's the current behavior is acceptable.
*/
static void wm_window_match_keep_current_wm(const bContext *C,
ListBase *current_wm_list,
const bool load_ui,
ListBase *r_new_wm_list)
static void wm_file_read_setup_wm_keep_old(const bContext *C,
Main *bmain,
BlendFileReadWMSetupData * /*wm_setup_data*/,
wmWindowManager *wm,
const bool load_ui)
{
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = static_cast<wmWindowManager *>(current_wm_list->first);
bScreen *screen = nullptr;
if (!load_ui) {
/* When loading without UI (i.e. keeping existing UI), no matching needed.
*
* The other UI data (workspaces, layouts, screens) has also been re-used from old Main, and
* newly read one from file has already been discarded in #setup_app_data. */
return;
}
/* match oldwm to new dbase, only old files */
wm->init_flag &= ~WM_INIT_FLAG_WINDOW;
/* when loading without UI, no matching needed */
if (load_ui && (screen = CTX_wm_screen(C))) {
/* Old WM is being reused, but other UI data (workspaces, layouts, screens) comes from the new
* file, so the WM needs to be updated to use these. */
bScreen *screen = CTX_wm_screen(C);
if (screen != nullptr) {
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
WorkSpace *workspace;
@@ -320,140 +330,102 @@ static void wm_window_match_keep_current_wm(const bContext *C,
win_screen->winid = win->winid;
}
}
/* we'll be using the current wm list directly; make sure
* the names are validated and in the name map. */
LISTBASE_FOREACH (wmWindowManager *, wm_item, current_wm_list) {
BKE_main_namemap_get_name(bmain, &wm_item->id, wm_item->id.name + 2);
}
*r_new_wm_list = *current_wm_list;
}
static void wm_window_match_replace_by_file_wm(bContext *C,
ListBase *current_wm_list,
ListBase *readfile_wm_list,
ListBase *r_new_wm_list)
static void wm_file_read_setup_wm_use_new(bContext *C,
Main * /* bmain */,
BlendFileReadWMSetupData *wm_setup_data,
wmWindowManager *wm)
{
wmWindowManager *oldwm = static_cast<wmWindowManager *>(current_wm_list->first);
/* will become our new WM */
wmWindowManager *wm = static_cast<wmWindowManager *>(readfile_wm_list->first);
wmWindowManager *old_wm = wm_setup_data->old_wm;
/* 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 */
{
/* Referencing the window-manager pointer from elsewhere in the file is highly unlikely
* however it's possible with ID-properties & animation-drivers.
* At some point we could check on disallowing this since it doesn't seem practical. */
Main *bmain = G_MAIN;
BLI_assert(bmain->relations == nullptr);
BKE_libblock_remap(bmain, wm, oldwm, ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_USER_CLEAR);
wm->op_undo_depth = old_wm->op_undo_depth;
/* Maintain the undo-depth between file loads. Useful so Python can perform
* nested operator calls that exit with the proper undo-depth. */
wm->op_undo_depth = oldwm->op_undo_depth;
/* Move existing key configurations into the new WM. */
wm->keyconfigs = old_wm->keyconfigs;
wm->addonconf = old_wm->addonconf;
wm->defaultconf = old_wm->defaultconf;
wm->userconf = old_wm->userconf;
/* Simple pointer swapping step. */
BLI_remlink(current_wm_list, oldwm);
BLI_remlink(readfile_wm_list, wm);
SWAP(wmWindowManager, *oldwm, *wm);
SWAP(wmWindowManager *, oldwm, wm);
BLI_addhead(current_wm_list, oldwm);
BLI_addhead(readfile_wm_list, wm);
BLI_listbase_clear(&old_wm->keyconfigs);
old_wm->addonconf = nullptr;
old_wm->defaultconf = nullptr;
old_wm->userconf = nullptr;
/* Don't leave the old pointer in the context. */
CTX_wm_manager_set(C, wm);
}
bool has_match = false;
/* this code could move to setup_appdata */
/* preserve key configurations in new wm, to preserve their keymaps */
wm->keyconfigs = oldwm->keyconfigs;
wm->addonconf = oldwm->addonconf;
wm->defaultconf = oldwm->defaultconf;
wm->userconf = oldwm->userconf;
BLI_listbase_clear(&oldwm->keyconfigs);
oldwm->addonconf = nullptr;
oldwm->defaultconf = nullptr;
oldwm->userconf = nullptr;
/* ensure making new keymaps and set space types */
/* Ensure new keymaps are made, and space types are set. */
wm->init_flag = 0;
wm->winactive = nullptr;
/* Clearing drawable of before deleting any context
* to avoid clearing the wrong wm. */
wm_window_clear_drawable(oldwm);
/* Clearing drawable of old WM before deleting any context to avoid clearing the wrong wm. */
wm_window_clear_drawable(old_wm);
/* Only first `wm` in list has GHOST-windows. */
bool has_match = false;
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
LISTBASE_FOREACH (wmWindow *, oldwin, &oldwm->windows) {
if (oldwin->winid == win->winid) {
LISTBASE_FOREACH (wmWindow *, old_win, &old_wm->windows) {
if (old_win->winid == win->winid) {
has_match = true;
wm_window_substitute_old(oldwm, wm, oldwin, win);
wm_file_read_setup_wm_substitute_old_window(old_wm, wm, old_win, win);
}
}
}
/* make sure at least one window is kept open so we don't lose the context, check #42303 */
/* Ensure that at least one window is kept open so we don't lose the context, see #42303. */
if (!has_match) {
wm_window_substitute_old(oldwm,
wm,
static_cast<wmWindow *>(oldwm->windows.first),
static_cast<wmWindow *>(wm->windows.first));
wm_file_read_setup_wm_substitute_old_window(old_wm,
wm,
static_cast<wmWindow *>(old_wm->windows.first),
static_cast<wmWindow *>(wm->windows.first));
}
wm_close_and_free_all(C, current_wm_list);
*r_new_wm_list = *readfile_wm_list;
wm_setup_data->old_wm = nullptr;
wm_close_and_free(C, old_wm);
/* Don't handle user counts as this is only ever called once #G_MAIN has already been freed via
* #BKE_main_free so any access to ID's referenced by the window-manager (from ID properties)
* will crash. See: #100703. */
BKE_libblock_free_data(&old_wm->id, false);
BKE_libblock_free_data_py(&old_wm->id);
MEM_freeN(old_wm);
}
/**
* Match old WM with new, 4 cases:
* 1) No current WM, no WM in file: Make new default.
* 2) No current WM, but WM in file: Keep current WM, do nothing else.
* 3) Current WM, but not in file: Keep current WM, update windows with screens from file.
* 4) Current WM, and WM in file: Try to keep current GHOST windows, use WM from file.
* Finalize setting up the WM for the newly read file, transferring GHOST windows from the old WM
* if needed, updating other UI data, etc. And free the old WM if any.
*
* \param r_new_wm_list: Return argument for the wm list to be used from now on.
* Counterpart of #wm_file_read_setup_wm_init.
*/
static void wm_window_match_do(bContext *C,
ListBase *current_wm_list,
ListBase *readfile_wm_list,
ListBase *r_new_wm_list)
static void wm_file_read_setup_wm_finalize(bContext *C,
Main *bmain,
BlendFileReadWMSetupData *wm_setup_data)
{
if (BLI_listbase_is_empty(current_wm_list)) {
/* case 1 */
if (BLI_listbase_is_empty(readfile_wm_list)) {
Main *bmain = CTX_data_main(C);
/* Neither current, no newly read file have a WM -> add the default one. */
wm_add_default(bmain, C);
*r_new_wm_list = bmain->wm;
BLI_assert(BLI_listbase_count_at_most(&bmain->wm, 2) <= 1);
BLI_assert(wm_setup_data != nullptr);
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
if (wm == nullptr) {
/* Add a default WM in case none exists in newly read main (should only happen when opening
* an old pre-2.5 .blend file at startup). */
wm_add_default(bmain, C);
}
else if (wm_setup_data->old_wm != nullptr) {
if (wm_setup_data->old_wm == wm) {
/* Old WM was kept, update it with new workspaces/layouts/screens read from file.
*
* Happens when not loading UI, or when the newly read file has no WM (pre-2.5 files). */
wm_file_read_setup_wm_keep_old(
C, bmain, wm_setup_data, wm, (G.fileflags & G_FILE_NO_UI) == 0);
}
/* case 2 */
else {
*r_new_wm_list = *readfile_wm_list;
}
}
else {
/* case 3 */
if (BLI_listbase_is_empty(readfile_wm_list)) {
/* We've read file without wm, keep current one entirely alive.
* Happens when reading pre 2.5 files (no WM back then) */
wm_window_match_keep_current_wm(
C, current_wm_list, (G.fileflags & G_FILE_NO_UI) == 0, r_new_wm_list);
}
/* case 4 */
else {
wm_window_match_replace_by_file_wm(C, current_wm_list, readfile_wm_list, r_new_wm_list);
/* Using new WM from read file, try to keep current GHOST windows, transfer keymaps, etc.,
* from old WM.
*
* Also takes care of clearing old WM data (temporarily stored in `wm_setup_data->old_wm`).
*/
wm_file_read_setup_wm_use_new(C, bmain, wm_setup_data, wm);
}
}
/* Else just using the new WM read from file, nothing to do. */
BLI_assert(wm_setup_data->old_wm == nullptr);
MEM_delete(wm_setup_data);
}
/** \} */
@@ -992,6 +964,8 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
/* NOTE: a matching #wm_read_callback_post_wrapper must be called. */
wm_read_callback_pre_wrapper(C, filepath);
Main *bmain = CTX_data_main(C);
/* so we can get the error message */
errno = 0;
@@ -1018,16 +992,22 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
if (bfd != nullptr) {
wm_file_read_pre(use_data, use_userdef);
/* Put aside screens to match with persistent windows later,
* also exit screens and editors. */
ListBase wmbase;
wm_window_match_init(C, &wmbase);
/* Put WM into a stable state for post-readfile processes (kill jobs, removes event handlers,
* message bus, and so on). */
BlendFileReadWMSetupData *wm_setup_data = wm_file_read_setup_wm_init(C, bmain);
/* This flag is initialized by the operator but overwritten on read.
* need to re-enable it here else drivers and registered scripts won't work. */
const int G_f_orig = G.f;
BKE_blendfile_read_setup(C, bfd, &params, &bf_reports);
/* Frees the current main and replaces it with the new one read from file. */
BKE_blendfile_read_setup_readfile(
C, bfd, &params, wm_setup_data, &bf_reports, false, nullptr);
bmain = CTX_data_main(C);
/* Finalize handling of WM, using the read WM and/or the current WM depending on things like
* whether the UI is loaded from the .blend file or not, etc. */
wm_file_read_setup_wm_finalize(C, bmain, wm_setup_data);
if (G.f != G_f_orig) {
const int flags_keep = G_FLAG_ALL_RUNTIME;
@@ -1035,11 +1015,6 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
G.f = (G.f & ~flags_keep) | (G_f_orig & flags_keep);
}
/* #BKE_blendfile_read_result_setup sets new Main into context. */
Main *bmain = CTX_data_main(C);
/* match the read WM with current WM */
wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
WM_check(C); /* opens window(s), checks keymaps */
if (do_history_file_update) {
@@ -1186,6 +1161,9 @@ void wm_homefile_read_ex(bContext *C,
const char *app_template = nullptr;
bool update_defaults = false;
/* Current Main is not always available in context here. */
Main *bmain = G_MAIN;
if (filepath_startup_override != nullptr) {
/* pass */
}
@@ -1241,9 +1219,11 @@ void wm_homefile_read_ex(bContext *C,
* so we know this will work if all else fails. */
wm_file_read_pre(use_data, use_userdef);
BlendFileReadWMSetupData *wm_setup_data = nullptr;
if (use_data) {
/* put aside screens to match with persistent windows later */
wm_window_match_init(C, &wmbase);
/* Put WM into a stable state for post-readfile processes (kill jobs, removes event handlers,
* message bus, and so on). */
wm_setup_data = wm_file_read_setup_wm_init(C, bmain);
}
filepath_startup[0] = '\0';
@@ -1333,9 +1313,16 @@ void wm_homefile_read_ex(bContext *C,
BlendFileData *bfd = BKE_blendfile_read(filepath_startup, &params, &bf_reports);
if (bfd != nullptr) {
BKE_blendfile_read_setup_ex(
C, bfd, &params, &bf_reports, update_defaults && use_data, app_template);
/* Frees the current main and replaces it with the new one read from file. */
BKE_blendfile_read_setup_readfile(C,
bfd,
&params,
wm_setup_data,
&bf_reports,
update_defaults && use_data,
app_template);
success = true;
bmain = CTX_data_main(C);
}
}
if (success) {
@@ -1364,8 +1351,11 @@ void wm_homefile_read_ex(bContext *C,
datatoc_startup_blend, datatoc_startup_blend_size, &read_file_params, nullptr);
if (bfd != nullptr) {
BlendFileReadReport read_report{};
BKE_blendfile_read_setup_ex(C, bfd, &read_file_params, &read_report, true, nullptr);
/* Frees the current main and replaces it with the new one read from file. */
BKE_blendfile_read_setup_readfile(
C, bfd, &read_file_params, wm_setup_data, &read_report, true, nullptr);
success = true;
bmain = CTX_data_main(C);
}
if (use_data && BLI_listbase_is_empty(&wmbase)) {
@@ -1415,16 +1405,15 @@ void wm_homefile_read_ex(bContext *C,
STRNCPY(U.app_template, app_template_override);
}
Main *bmain = CTX_data_main(C);
if (use_userdef) {
/* check userdef before open window, keymaps etc */
wm_init_userdef(bmain);
}
if (use_data) {
/* match the read WM with current WM */
wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
/* Finalize handling of WM, using the read WM and/or the current WM depending on things like
* whether the UI is loaded from the .blend file or not, etc. */
wm_file_read_setup_wm_finalize(C, bmain, wm_setup_data);
}
if (use_userdef) {

View File

@@ -38,7 +38,6 @@ void wm_exit_schedule_delayed(const bContext *C);
* Context is allowed to be NULL, do not free wm itself (lib_id.c).
*/
extern void wm_close_and_free(bContext *C, wmWindowManager *);
extern void wm_close_and_free_all(bContext *C, ListBase *);
/**
* On startup, it adds all data, for matching.