Blender V5.0
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_node_types.h"
19#include "DNA_object_types.h"
20#include "DNA_scene_types.h"
21
22#include "BKE_blender_undo.hh"
23#include "BKE_context.hh"
24#include "BKE_lib_query.hh"
25#include "BKE_main.hh"
26#include "BKE_node.hh"
27#include "BKE_preview_image.hh"
28#include "BKE_scene.hh"
29#include "BKE_scene_runtime.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 *owner_id = cb_data->owner_id;
99 ID **id_pointer = cb_data->id_pointer;
100 /* Embedded IDs do not get tagged with #ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED currently (could be,
101 * but would add extra processing, and by definition they always share that state with their
102 * owner, as they are stored as 'regular data' in blend-files, not as independent IDs).
103 *
104 * NOTE: It seems that local IDs using embedded ones are never 'reused unchanged', this was
105 * never caught before. However, if using `self_id` here, this assert gets triggered with
106 * upcoming packed data. Probably because while packed data remains unchanged, it is handled like
107 * regular local data by undo code, and like regular linked data. */
109 UNUSED_VARS_NDEBUG(owner_id);
110
111 ID *id = *id_pointer;
112 if (id != nullptr && !ID_IS_LINKED(id) && (id->tag & ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) == 0) {
113 bool do_stop_iter = true;
114 if (GS(self_id->name) == ID_OB) {
115 Object *ob_self = (Object *)self_id;
116 if (ob_self->type == OB_ARMATURE) {
117 if (ob_self->data == id) {
118 BLI_assert(GS(id->name) == ID_AR);
119 if (ob_self->pose != nullptr) {
120 /* We have a changed/re-read armature used by an unchanged armature object: our beloved
121 * Bone pointers from the object's pose need their usual special treatment. */
122 ob_self->pose->flag |= POSE_RECALC;
123 }
124 }
125 else {
126 /* Cannot stop iteration until we checked ob_self->data pointer... */
127 do_stop_iter = false;
128 }
129 }
130 }
131
132 return do_stop_iter ? IDWALK_RET_STOP_ITER : IDWALK_RET_NOP;
133 }
134
135 return IDWALK_RET_NOP;
136}
137
146{
147 PreviewImage *preview = BKE_previewimg_id_get(id);
148 if (!preview) {
149 return;
150 }
151
152 for (int i = 0; i < NUM_ICON_SIZES; i++) {
153 if (preview->flag[i] & PRV_USER_EDITED) {
154 /* Don't modify custom previews. */
155 continue;
156 }
157
158 if (!BKE_previewimg_is_finished(preview, i)) {
160 }
161 }
162}
163
165 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir undo_direction, bool /*is_final*/)
166{
167 BLI_assert(undo_direction != STEP_INVALID);
168
169 bool use_old_bmain_data = true;
170
171 if (USER_DEVELOPER_TOOL_TEST(&U, use_undo_legacy) || !(U.uiflag & USER_GLOBALUNDO)) {
172 use_old_bmain_data = false;
173 }
174 else if (undo_direction == STEP_REDO) {
175 /* The only time we should have to force a complete redo is when current step is tagged as a
176 * redo barrier.
177 * If previous step was not a memfile one should not matter here, current data in old bmain
178 * should still always be valid for unchanged data-blocks. */
179 if (us_p->use_old_bmain_data == false) {
180 use_old_bmain_data = false;
181 }
182 }
183 else if (undo_direction == STEP_UNDO) {
184 /* Here we do not care whether current step is an undo barrier, since we are coming from
185 * 'the future' we can still re-use old data. However, if *next* undo step
186 * (i.e. the one immediately in the future, the one we are coming from)
187 * is a barrier, then we have to force a complete undo.
188 * Note that non-memfile undo steps **should** not be an issue anymore, since we handle
189 * fine-grained update flags now.
190 */
191 UndoStep *us_next = us_p->next;
192 if (us_next != nullptr) {
193 if (us_next->use_old_bmain_data == false) {
194 use_old_bmain_data = false;
195 }
196 }
197 }
198
199 /* Extract depsgraphs from current bmain (which may be freed during undo step reading),
200 * and store them for re-use. */
201 GHash *depsgraphs = nullptr;
202 if (use_old_bmain_data) {
203 depsgraphs = BKE_scene_undo_depsgraphs_extract(bmain);
204 }
205
206 ED_editors_exit(bmain, false);
207 /* Ensure there's no preview job running. Unfinished previews will be scheduled for regeneration
208 * via #memfile_undosys_unfinished_id_previews_restart(). */
210
211 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
212 BKE_memfile_undo_decode(us->data, undo_direction, use_old_bmain_data, C);
213
214 for (UndoStep *us_iter = us_p->next; us_iter; us_iter = us_iter->next) {
215 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
216 continue;
217 }
218 us_iter->is_applied = false;
219 }
220 for (UndoStep *us_iter = us_p; us_iter; us_iter = us_iter->prev) {
221 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
222 continue;
223 }
224 us_iter->is_applied = true;
225 }
226
227 /* bmain has been freed. */
228 bmain = CTX_data_main(C);
230
231 if (use_old_bmain_data) {
232 /* Restore previous depsgraphs into current bmain. */
233 BKE_scene_undo_depsgraphs_restore(bmain, depsgraphs);
234
235 /* We need to inform depsgraph about re-used old IDs that would be using newly read
236 * data-blocks, at least evaluated copies need to be updated... */
237 ID *id = nullptr;
238 FOREACH_MAIN_ID_BEGIN (bmain, id) {
242 }
243
244 if (GS(id->name) == ID_SCE) {
245 Scene *scene = reinterpret_cast<Scene *>(id);
246 /* TODO: We should be able to restore these depsgraphs properly as part of
247 * #BKE_scene_undo_depsgraphs_restore but this is currently only done for depsgraphs in the
248 * scene.depsgraph_hash map. So the safest option is to just delete the following
249 * depsgraphs for now. */
250 if (scene->compositing_node_group) {
251 /* Ensure undo calls from the UI update the interactive compositor preview depsgraph, see
252 * #compo_initjob. */
253 blender::bke::CompositorRuntime &compositor_runtime = scene->runtime->compositor;
254 DEG_graph_free(compositor_runtime.preview_depsgraph);
255 compositor_runtime.preview_depsgraph = nullptr;
256 }
257
258 if (scene->runtime->sequencer.depsgraph) {
259 /* Ensure that the depsgraph created in #get_depsgraph_for_scene_strip are updated. */
260 blender::bke::SequencerRuntime &seq_runtime = scene->runtime->sequencer;
261 DEG_graph_free(seq_runtime.depsgraph);
262 seq_runtime.depsgraph = nullptr;
263 }
264 }
265
266 /* NOTE: Tagging `ID_RECALC_SYNC_TO_EVAL` here should not be needed in practice, since
267 * modified IDs should already have other depsgraph update tags anyway.
268 * However, for the sake of consistency, it's better to effectively use it,
269 * since content of that ID pointer does have been modified. */
270 uint recalc_flags = id->recalc | ((id->tag & ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE) ?
272 IDRecalcFlag(0));
273 /* Tag depsgraph to update data-block for changes that happened between the
274 * current and the target state, see direct_link_id_restore_recalc(). */
275 if (recalc_flags != 0) {
276 DEG_id_tag_update_ex(bmain, id, recalc_flags);
277 }
278
280 if (nodetree != nullptr) {
281 recalc_flags = nodetree->id.recalc;
283 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
284 }
285 if (recalc_flags != 0) {
286 DEG_id_tag_update_ex(bmain, &nodetree->id, recalc_flags);
287 }
288 }
289 if (GS(id->name) == ID_SCE) {
290 Scene *scene = (Scene *)id;
291 if (scene->master_collection != nullptr) {
292 recalc_flags = scene->master_collection->id.recalc;
294 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
295 }
296 if (recalc_flags != 0) {
297 DEG_id_tag_update_ex(bmain, &scene->master_collection->id, recalc_flags);
298 }
299 }
300 }
301
302 /* Restart preview generation if the undo state was generating previews. */
304 }
306
307 FOREACH_MAIN_ID_BEGIN (bmain, id) {
308 /* Clear temporary tag. */
311
312 /* We only start accumulating from this point, any tags set up to here
313 * are already part of the current undo state. This is done in a second
314 * loop because DEG_id_tag_update may set tags on other datablocks. */
315 id->recalc_after_undo_push = 0;
317 if (nodetree != nullptr) {
318 nodetree->id.recalc_after_undo_push = 0;
319 }
320 if (GS(id->name) == ID_SCE) {
321 Scene *scene = (Scene *)id;
322 if (scene->master_collection != nullptr) {
324 }
325 }
326 }
328 }
329 else {
330 ID *id = nullptr;
331 FOREACH_MAIN_ID_BEGIN (bmain, id) {
332 /* Restart preview generation if the undo state was generating previews. */
334 }
336 }
337
339}
340
342{
343 /* To avoid unnecessary slow down, free backwards
344 * (so we don't need to merge when clearing all). */
345 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
346 if (us_p->next != nullptr) {
347 UndoStep *us_next_p = BKE_undosys_step_same_type_next(us_p);
348 if (us_next_p != nullptr) {
349 MemFileUndoStep *us_next = (MemFileUndoStep *)us_next_p;
350 BLO_memfile_merge(&us->data->memfile, &us_next->data->memfile);
351 }
352 }
353
355}
356
358{
359 ut->name = "Global Undo";
364
365 ut->flags = 0;
366
367 ut->step_size = sizeof(MemFileUndoStep);
368}
369
371
372/* -------------------------------------------------------------------- */
375
381{
382 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
383 return &us->data->memfile;
384}
385
387{
388 if (!ustack->step_active) {
389 return nullptr;
390 }
391 if (ustack->step_active->type != BKE_UNDOSYS_TYPE_MEMFILE) {
392 return nullptr;
393 }
395}
396
398{
399 UndoStep *us = ustack->step_active;
400 if (id == nullptr || us == nullptr || us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
401 return;
402 }
403
404 MemFile *memfile = &((MemFileUndoStep *)us)->data->memfile;
405 LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &memfile->chunks) {
406 if (mem_chunk->id_session_uid == id->session_uid) {
407 mem_chunk->is_identical_future = false;
408 break;
409 }
410 }
411}
412
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)
@ IDWALK_RET_STOP_ITER
@ IDWALK_RET_NOP
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_READONLY
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:583
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:577
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:3468
GHash * BKE_scene_undo_depsgraphs_extract(Main *bmain)
Definition scene.cc:3439
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
#define UNUSED_VARS_NDEBUG(...)
void BLO_memfile_merge(MemFile *first, MemFile *second)
Definition undofile.cc:59
void DEG_graph_free(Depsgraph *graph)
Definition depsgraph.cc:306
ID and Library types, which are fundamental for SDNA.
@ ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED
Definition DNA_ID.h:936
@ ID_TAG_UNDO_OLD_ID_REUSED_NOUNDO
Definition DNA_ID.h:947
@ ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE
Definition DNA_ID.h:954
IDRecalcFlag
Definition DNA_ID.h:1049
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1118
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
@ PRV_USER_EDITED
Definition DNA_ID.h:620
eIconSizes
@ NUM_ICON_SIZES
@ ID_AR
@ ID_SCE
@ ID_OB
@ POSE_RECALC
Object groups, one object can be in many groups at once.
Object is a sort of wrapper for general info.
@ OB_ARMATURE
@ USER_GLOBALUNDO
#define USER_DEVELOPER_TOOL_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:442
bool ED_undo_is_memfile_compatible(const bContext *C)
Definition ed_undo.cc:387
void ED_editors_exit(Main *bmain, bool do_undo_system)
Definition ed_util.cc:224
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:61
bool ED_editors_flush_edits_ex(Main *bmain, bool for_render, bool check_needs_flush)
Definition ed_util.cc:320
#define C
Definition RandGen.cpp:29
#define NC_SCENE
Definition WM_types.hh:378
#define ND_LAYER_CONTENT
Definition WM_types.hh:453
#define U
#define GS(x)
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:4568
Definition DNA_ID.h:414
unsigned int recalc_after_undo_push
Definition DNA_ID.h:456
unsigned int recalc
Definition DNA_ID.h:445
int tag
Definition DNA_ID.h:442
char name[258]
Definition DNA_ID.h:432
unsigned int session_uid
Definition DNA_ID.h:462
bool is_memfile_undo_flush_needed
Definition BKE_main.hh:213
bool use_memfile_full_barrier
Definition BKE_main.hh:218
MemFileUndoData * data
ListBase chunks
struct bPose * pose
short flag[2]
Definition DNA_ID.h:644
struct Collection * master_collection
SceneRuntimeHandle * runtime
struct bNodeTree * compositing_node_group
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)