Blender V4.5
memfile_undo.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include "BLI_sys_types.h"
12
13#include "BLI_ghash.h"
14#include "BLI_listbase.h"
15
16#include "DNA_ID.h"
18#include "DNA_mesh_types.h"
19#include "DNA_node_types.h"
20#include "DNA_object_types.h"
21#include "DNA_scene_types.h"
22
23#include "BKE_blender_undo.hh"
24#include "BKE_context.hh"
25#include "BKE_lib_query.hh"
26#include "BKE_main.hh"
27#include "BKE_node.hh"
28#include "BKE_preview_image.hh"
29#include "BKE_scene.hh"
30#include "BKE_undo_system.hh"
31
33
34#include "WM_api.hh"
35#include "WM_types.hh"
36
37#include "ED_render.hh"
38#include "ED_undo.hh"
39#include "ED_util.hh"
40
42
43#include "undo_intern.hh"
44
45/* -------------------------------------------------------------------- */
48
53
55{
56 /* other poll functions must run first, this is a catch-all. */
57
58 if ((U.uiflag & USER_GLOBALUNDO) == 0) {
59 return false;
60 }
61
62 /* Allow a single memfile undo step (the first). */
63 UndoStack *ustack = ED_undo_stack_get();
64 if ((ustack->step_active != nullptr) && (ED_undo_is_memfile_compatible(C) == false)) {
65 return false;
66 }
67 return true;
68}
69
70static bool memfile_undosys_step_encode(bContext * /*C*/, Main *bmain, UndoStep *us_p)
71{
72 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
73
74 /* Important we only use 'main' from the context (see: BKE_undosys_stack_init_from_main). */
75 UndoStack *ustack = ED_undo_stack_get();
76
78 ED_editors_flush_edits_ex(bmain, false, true);
79 }
80
81 /* can be null, use when set. */
84 us->data = BKE_memfile_undo_encode(bmain, us_prev ? us_prev->data : nullptr);
85 us->step.data_size = us->data->undo_size;
86
87 /* Store the fact that we should not re-use old data with that undo step, and reset the Main
88 * flag. */
90 bmain->use_memfile_full_barrier = false;
91
92 return true;
93}
94
96{
97 ID *self_id = cb_data->self_id;
98 ID **id_pointer = cb_data->id_pointer;
100
101 ID *id = *id_pointer;
102 if (id != nullptr && !ID_IS_LINKED(id) && (id->tag & ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) == 0) {
103 bool do_stop_iter = true;
104 if (GS(self_id->name) == ID_OB) {
105 Object *ob_self = (Object *)self_id;
106 if (ob_self->type == OB_ARMATURE) {
107 if (ob_self->data == id) {
108 BLI_assert(GS(id->name) == ID_AR);
109 if (ob_self->pose != nullptr) {
110 /* We have a changed/re-read armature used by an unchanged armature object: our beloved
111 * Bone pointers from the object's pose need their usual special treatment. */
112 ob_self->pose->flag |= POSE_RECALC;
113 }
114 }
115 else {
116 /* Cannot stop iteration until we checked ob_self->data pointer... */
117 do_stop_iter = false;
118 }
119 }
120 }
121
122 return do_stop_iter ? IDWALK_RET_STOP_ITER : IDWALK_RET_NOP;
123 }
124
125 return IDWALK_RET_NOP;
126}
127
136{
137 PreviewImage *preview = BKE_previewimg_id_get(id);
138 if (!preview) {
139 return;
140 }
141
142 for (int i = 0; i < NUM_ICON_SIZES; i++) {
143 if (preview->flag[i] & PRV_USER_EDITED) {
144 /* Don't modify custom previews. */
145 continue;
146 }
147
148 if (!BKE_previewimg_is_finished(preview, i)) {
150 }
151 }
152}
153
155 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir undo_direction, bool /*is_final*/)
156{
157 BLI_assert(undo_direction != STEP_INVALID);
158
159 bool use_old_bmain_data = true;
160
161 if (USER_EXPERIMENTAL_TEST(&U, use_undo_legacy) || !(U.uiflag & USER_GLOBALUNDO)) {
162 use_old_bmain_data = false;
163 }
164 else if (undo_direction == STEP_REDO) {
165 /* The only time we should have to force a complete redo is when current step is tagged as a
166 * redo barrier.
167 * If previous step was not a memfile one should not matter here, current data in old bmain
168 * should still always be valid for unchanged data-blocks. */
169 if (us_p->use_old_bmain_data == false) {
170 use_old_bmain_data = false;
171 }
172 }
173 else if (undo_direction == STEP_UNDO) {
174 /* Here we do not care whether current step is an undo barrier, since we are coming from
175 * 'the future' we can still re-use old data. However, if *next* undo step
176 * (i.e. the one immediately in the future, the one we are coming from)
177 * is a barrier, then we have to force a complete undo.
178 * Note that non-memfile undo steps **should** not be an issue anymore, since we handle
179 * fine-grained update flags now.
180 */
181 UndoStep *us_next = us_p->next;
182 if (us_next != nullptr) {
183 if (us_next->use_old_bmain_data == false) {
184 use_old_bmain_data = false;
185 }
186 }
187 }
188
189 /* Extract depsgraphs from current bmain (which may be freed during undo step reading),
190 * and store them for re-use. */
191 GHash *depsgraphs = nullptr;
192 if (use_old_bmain_data) {
193 depsgraphs = BKE_scene_undo_depsgraphs_extract(bmain);
194 }
195
196 ED_editors_exit(bmain, false);
197 /* Ensure there's no preview job running. Unfinished previews will be scheduled for regeneration
198 * via #memfile_undosys_unfinished_id_previews_restart(). */
200
201 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
202 BKE_memfile_undo_decode(us->data, undo_direction, use_old_bmain_data, C);
203
204 for (UndoStep *us_iter = us_p->next; us_iter; us_iter = us_iter->next) {
205 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
206 continue;
207 }
208 us_iter->is_applied = false;
209 }
210 for (UndoStep *us_iter = us_p; us_iter; us_iter = us_iter->prev) {
211 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
212 continue;
213 }
214 us_iter->is_applied = true;
215 }
216
217 /* bmain has been freed. */
218 bmain = CTX_data_main(C);
220
221 if (use_old_bmain_data) {
222 /* Restore previous depsgraphs into current bmain. */
223 BKE_scene_undo_depsgraphs_restore(bmain, depsgraphs);
224
225 /* We need to inform depsgraph about re-used old IDs that would be using newly read
226 * data-blocks, at least evaluated copies need to be updated... */
227 ID *id = nullptr;
228 FOREACH_MAIN_ID_BEGIN (bmain, id) {
232 }
233
234 /* NOTE: Tagging `ID_RECALC_SYNC_TO_EVAL` here should not be needed in practice, since
235 * modified IDs should already have other depsgraph update tags anyway.
236 * However, for the sake of consistency, it's better to effectively use it,
237 * since content of that ID pointer does have been modified. */
238 uint recalc_flags = id->recalc | ((id->tag & ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE) ?
240 IDRecalcFlag(0));
241 /* Tag depsgraph to update data-block for changes that happened between the
242 * current and the target state, see direct_link_id_restore_recalc(). */
243 if (recalc_flags != 0) {
244 DEG_id_tag_update_ex(bmain, id, recalc_flags);
245 }
246
248 if (nodetree != nullptr) {
249 recalc_flags = nodetree->id.recalc;
251 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
252 }
253 if (recalc_flags != 0) {
254 DEG_id_tag_update_ex(bmain, &nodetree->id, recalc_flags);
255 }
256 }
257 if (GS(id->name) == ID_SCE) {
258 Scene *scene = (Scene *)id;
259 if (scene->master_collection != nullptr) {
260 recalc_flags = scene->master_collection->id.recalc;
262 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
263 }
264 if (recalc_flags != 0) {
265 DEG_id_tag_update_ex(bmain, &scene->master_collection->id, recalc_flags);
266 }
267 }
268 }
269
270 /* Restart preview generation if the undo state was generating previews. */
272 }
274
275 FOREACH_MAIN_ID_BEGIN (bmain, id) {
276 /* Clear temporary tag. */
279
280 /* We only start accumulating from this point, any tags set up to here
281 * are already part of the current undo state. This is done in a second
282 * loop because DEG_id_tag_update may set tags on other datablocks. */
283 id->recalc_after_undo_push = 0;
285 if (nodetree != nullptr) {
286 nodetree->id.recalc_after_undo_push = 0;
287 }
288 if (GS(id->name) == ID_SCE) {
289 Scene *scene = (Scene *)id;
290 if (scene->master_collection != nullptr) {
292 }
293 }
294 else if (GS(id->name) == ID_OB) {
295 /* In some cases when using memfile undo in sculpt mode, the object but not the
296 * corresponding mesh will be tagged for an update, leading to invalid data and crashes.
297 *
298 * This is a band-aid mitigation for the 4.5 LTS release, not a proper fix of the
299 * underlying problem.
300 *
301 * See #152087 for more details. */
302 Object *object = reinterpret_cast<Object *>(id);
303 Mesh *mesh = static_cast<Mesh *>(object->data);
304 if (object->mode == OB_MODE_SCULPT && mesh) {
306 }
307 }
308 }
310 }
311 else {
312 ID *id = nullptr;
313 FOREACH_MAIN_ID_BEGIN (bmain, id) {
314 /* Restart preview generation if the undo state was generating previews. */
316 }
318 }
319
321}
322
324{
325 /* To avoid unnecessary slow down, free backwards
326 * (so we don't need to merge when clearing all). */
327 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
328 if (us_p->next != nullptr) {
329 UndoStep *us_next_p = BKE_undosys_step_same_type_next(us_p);
330 if (us_next_p != nullptr) {
331 MemFileUndoStep *us_next = (MemFileUndoStep *)us_next_p;
332 BLO_memfile_merge(&us->data->memfile, &us_next->data->memfile);
333 }
334 }
335
337}
338
340{
341 ut->name = "Global Undo";
346
347 ut->flags = 0;
348
349 ut->step_size = sizeof(MemFileUndoStep);
350}
351
353
354/* -------------------------------------------------------------------- */
357
363{
364 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
365 return &us->data->memfile;
366}
367
369{
370 if (!ustack->step_active) {
371 return nullptr;
372 }
373 if (ustack->step_active->type != BKE_UNDOSYS_TYPE_MEMFILE) {
374 return nullptr;
375 }
377}
378
380{
381 UndoStep *us = ustack->step_active;
382 if (id == nullptr || us == nullptr || us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
383 return;
384 }
385
386 MemFile *memfile = &((MemFileUndoStep *)us)->data->memfile;
387 LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &memfile->chunks) {
388 if (mem_chunk->id_session_uid == id->session_uid) {
389 mem_chunk->is_identical_future = false;
390 break;
391 }
392 }
393}
394
bool BKE_memfile_undo_decode(MemFileUndoData *mfu, eUndoStepDir undo_direction, bool use_old_bmain_data, bContext *C)
MemFileUndoData * BKE_memfile_undo_encode(Main *bmain, MemFileUndoData *mfu_prev)
void BKE_memfile_undo_free(MemFileUndoData *mfu)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, LibraryForeachIDFlag flag)
Definition lib_query.cc:431
@ IDWALK_RET_STOP_ITER
@ IDWALK_RET_NOP
@ IDWALK_READONLY
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:563
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:557
PreviewImage * BKE_previewimg_id_get(const ID *id)
bool BKE_previewimg_is_finished(const PreviewImage *prv, int size)
void BKE_scene_undo_depsgraphs_restore(Main *bmain, GHash *depsgraph_extract)
Definition scene.cc:3479
GHash * BKE_scene_undo_depsgraphs_extract(Main *bmain)
Definition scene.cc:3450
UndoStep * BKE_undosys_step_find_by_type(UndoStack *ustack, const UndoType *ut)
UndoStep * BKE_undosys_step_same_type_next(UndoStep *us)
const UndoType * BKE_UNDOSYS_TYPE_MEMFILE
eUndoStepDir
@ STEP_INVALID
@ STEP_UNDO
@ STEP_REDO
#define BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(ty)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
unsigned int uint
void BLO_memfile_merge(MemFile *first, MemFile *second)
Definition undofile.cc:59
ID and Library types, which are fundamental for SDNA.
IDRecalcFlag
Definition DNA_ID.h:957
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1026
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED
Definition DNA_ID.h:844
@ ID_TAG_UNDO_OLD_ID_REUSED_NOUNDO
Definition DNA_ID.h:855
@ ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE
Definition DNA_ID.h:862
@ PRV_USER_EDITED
Definition DNA_ID.h:543
eIconSizes
@ NUM_ICON_SIZES
@ ID_AR
@ ID_SCE
@ ID_OB
@ POSE_RECALC
Object groups, one object can be in many groups at once.
@ OB_MODE_SCULPT
Object is a sort of wrapper for general info.
@ OB_ARMATURE
@ USER_GLOBALUNDO
#define USER_EXPERIMENTAL_TEST(userdef, member)
void ED_preview_restart_queue_add(ID *id, enum eIconSizes size)
void ED_preview_kill_jobs(wmWindowManager *wm, Main *bmain)
UndoStack * ED_undo_stack_get()
Definition ed_undo.cc:441
bool ED_undo_is_memfile_compatible(const bContext *C)
Definition ed_undo.cc:386
void ED_editors_exit(Main *bmain, bool do_undo_system)
Definition ed_util.cc:212
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:58
bool ED_editors_flush_edits_ex(Main *bmain, bool for_render, bool check_needs_flush)
Definition ed_util.cc:308
#define C
Definition RandGen.cpp:29
#define NC_SCENE
Definition WM_types.hh:375
#define ND_LAYER_CONTENT
Definition WM_types.hh:450
#define U
#define ID_IS_LINKED(_id)
#define GS(a)
DEG_id_tag_update_ex(cb_data->bmain, cb_data->owner_id, ID_RECALC_TAG_FOR_UNDO|ID_RECALC_SYNC_TO_EVAL)
static MemFile * ed_undosys_step_get_memfile(UndoStep *us_p)
void ED_undosys_stack_memfile_id_changed_tag(UndoStack *ustack, ID *id)
static void memfile_undosys_step_free(UndoStep *us_p)
MemFile * ED_undosys_stack_memfile_get_if_active(UndoStack *ustack)
static int memfile_undosys_step_id_reused_cb(LibraryIDLinkCallbackData *cb_data)
static void memfile_undosys_step_decode(bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir undo_direction, bool)
static bool memfile_undosys_poll(bContext *C)
static bool memfile_undosys_step_encode(bContext *, Main *bmain, UndoStep *us_p)
void ED_memfile_undosys_type(UndoType *ut)
static void memfile_undosys_unfinished_id_previews_restart(ID *id)
bNodeTree * node_tree_from_id(ID *id)
Definition node.cc:4840
Definition DNA_ID.h:404
unsigned int recalc_after_undo_push
Definition DNA_ID.h:438
unsigned int recalc
Definition DNA_ID.h:427
int tag
Definition DNA_ID.h:424
char name[66]
Definition DNA_ID.h:415
unsigned int session_uid
Definition DNA_ID.h:444
bool is_memfile_undo_flush_needed
Definition BKE_main.hh:185
bool use_memfile_full_barrier
Definition BKE_main.hh:190
MemFileUndoData * data
ListBase chunks
struct bPose * pose
short flag[2]
Definition DNA_ID.h:567
struct Collection * master_collection
UndoStep * step_active
size_t data_size
UndoStep * prev
bool use_old_bmain_data
UndoStep * next
const UndoType * type
const char * name
void(* step_free)(UndoStep *us)
bool(* poll)(struct bContext *C)
void(* step_decode)(bContext *C, Main *bmain, UndoStep *us, eUndoStepDir dir, bool is_final)
bool(* step_encode)(bContext *C, Main *bmain, UndoStep *us)
i
Definition text_draw.cc:230
void WM_event_add_notifier(const bContext *C, uint type, void *reference)