Readfile: Refactor several parts of the process #108016

Merged
Bastien Montagne merged 14 commits from mont29/blender:PR-setupappdata-refactor into main 2023-06-05 13:54:54 +02:00
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) {
mont29 marked this conversation as resolved Outdated

We generally still use LISTBASE_FOREACH in C++ code, because writing for loops like you did here does not really improve readability or type safety.

We generally still use `LISTBASE_FOREACH` in C++ code, because writing for loops like you did here does not really improve readability or type safety.
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)) {

Would be good to avoid this kind of quadratic algorithms, even though the number of libraries is probably relatively low. To remove the quadratic run-time, just create a filepath -> Library * map for either the new or old Main first.

Would be good to avoid this kind of quadratic algorithms, even though the number of libraries is probably relatively low. To remove the quadratic run-time, just create a `filepath -> Library *` map for either the new or old `Main` first.

I don't think that this added complexity will ever justify itself. At the very least, I would wait to get a real-life example where this change would give any measurable (and significant) benefit.

I don't think that this added complexity will ever justify itself. At the very least, I would wait to get a real-life example where this change would give any measurable (and significant) benefit.
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) {
mont29 marked this conversation as resolved Outdated

there should be a break here too, btw.

there should be a break here too, btw.
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. */
mont29 marked this conversation as resolved Outdated

Could return early when old_wm == nullptr to avoid indentation.

Could return early when `old_wm == nullptr` to avoid indentation.
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);

File diff suppressed because it is too large Load Diff

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.