Blender V4.3
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
11#include "BLI_sys_types.h"
12#include "BLI_utildefines.h"
13
14#include "BLI_ghash.h"
15#include "BLI_listbase.h"
16
17#include "DNA_ID.h"
19#include "DNA_node_types.h"
20#include "DNA_object_enums.h"
21#include "DNA_object_types.h"
22#include "DNA_scene_types.h"
23
24#include "BKE_blender_undo.hh"
25#include "BKE_context.hh"
26#include "BKE_lib_query.hh"
27#include "BKE_main.hh"
28#include "BKE_node.hh"
29#include "BKE_preview_image.hh"
30#include "BKE_scene.hh"
31#include "BKE_undo_system.hh"
32
34
35#include "WM_api.hh"
36#include "WM_types.hh"
37
38#include "ED_render.hh"
39#include "ED_undo.hh"
40#include "ED_util.hh"
41
43
44#include "undo_intern.hh"
45
46#include <cstdio>
47
48/* -------------------------------------------------------------------- */
56
58{
59 /* other poll functions must run first, this is a catch-all. */
60
61 if ((U.uiflag & USER_GLOBALUNDO) == 0) {
62 return false;
63 }
64
65 /* Allow a single memfile undo step (the first). */
66 UndoStack *ustack = ED_undo_stack_get();
67 if ((ustack->step_active != nullptr) && (ED_undo_is_memfile_compatible(C) == false)) {
68 return false;
69 }
70 return true;
71}
72
73static bool memfile_undosys_step_encode(bContext * /*C*/, Main *bmain, UndoStep *us_p)
74{
75 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
76
77 /* Important we only use 'main' from the context (see: BKE_undosys_stack_init_from_main). */
78 UndoStack *ustack = ED_undo_stack_get();
79
81 ED_editors_flush_edits_ex(bmain, false, true);
82 }
83
84 /* can be null, use when set. */
87 us->data = BKE_memfile_undo_encode(bmain, us_prev ? us_prev->data : nullptr);
88 us->step.data_size = us->data->undo_size;
89
90 /* Store the fact that we should not re-use old data with that undo step, and reset the Main
91 * flag. */
93 bmain->use_memfile_full_barrier = false;
94
95 return true;
96}
97
99{
100 ID *self_id = cb_data->self_id;
101 ID **id_pointer = cb_data->id_pointer;
103
104 ID *id = *id_pointer;
105 if (id != nullptr && !ID_IS_LINKED(id) && (id->tag & ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) == 0) {
106 bool do_stop_iter = true;
107 if (GS(self_id->name) == ID_OB) {
108 Object *ob_self = (Object *)self_id;
109 if (ob_self->type == OB_ARMATURE) {
110 if (ob_self->data == id) {
111 BLI_assert(GS(id->name) == ID_AR);
112 if (ob_self->pose != nullptr) {
113 /* We have a changed/re-read armature used by an unchanged armature object: our beloved
114 * Bone pointers from the object's pose need their usual special treatment. */
115 ob_self->pose->flag |= POSE_RECALC;
116 }
117 }
118 else {
119 /* Cannot stop iteration until we checked ob_self->data pointer... */
120 do_stop_iter = false;
121 }
122 }
123 }
124
125 return do_stop_iter ? IDWALK_RET_STOP_ITER : IDWALK_RET_NOP;
126 }
127
128 return IDWALK_RET_NOP;
129}
130
139{
140 PreviewImage *preview = BKE_previewimg_id_get(id);
141 if (!preview) {
142 return;
143 }
144
145 for (int i = 0; i < NUM_ICON_SIZES; i++) {
146 if (preview->flag[i] & PRV_USER_EDITED) {
147 /* Don't modify custom previews. */
148 continue;
149 }
150
151 if (!BKE_previewimg_is_finished(preview, i)) {
153 }
154 }
155}
156
158 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir undo_direction, bool /*is_final*/)
159{
160 BLI_assert(undo_direction != STEP_INVALID);
161
162 bool use_old_bmain_data = true;
163
164 if (USER_EXPERIMENTAL_TEST(&U, use_undo_legacy) || !(U.uiflag & USER_GLOBALUNDO)) {
165 use_old_bmain_data = false;
166 }
167 else if (undo_direction == STEP_REDO) {
168 /* The only time we should have to force a complete redo is when current step is tagged as a
169 * redo barrier.
170 * If previous step was not a memfile one should not matter here, current data in old bmain
171 * should still always be valid for unchanged data-blocks. */
172 if (us_p->use_old_bmain_data == false) {
173 use_old_bmain_data = false;
174 }
175 }
176 else if (undo_direction == STEP_UNDO) {
177 /* Here we do not care whether current step is an undo barrier, since we are coming from
178 * 'the future' we can still re-use old data. However, if *next* undo step
179 * (i.e. the one immediately in the future, the one we are coming from)
180 * is a barrier, then we have to force a complete undo.
181 * Note that non-memfile undo steps **should** not be an issue anymore, since we handle
182 * fine-grained update flags now.
183 */
184 UndoStep *us_next = us_p->next;
185 if (us_next != nullptr) {
186 if (us_next->use_old_bmain_data == false) {
187 use_old_bmain_data = false;
188 }
189 }
190 }
191
192 /* Extract depsgraphs from current bmain (which may be freed during undo step reading),
193 * and store them for re-use. */
194 GHash *depsgraphs = nullptr;
195 if (use_old_bmain_data) {
196 depsgraphs = BKE_scene_undo_depsgraphs_extract(bmain);
197 }
198
199 ED_editors_exit(bmain, false);
200 /* Ensure there's no preview job running. Unfinished previews will be scheduled for regeneration
201 * via #memfile_undosys_unfinished_id_previews_restart(). */
203
204 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
205 BKE_memfile_undo_decode(us->data, undo_direction, use_old_bmain_data, C);
206
207 for (UndoStep *us_iter = us_p->next; us_iter; us_iter = us_iter->next) {
208 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
209 continue;
210 }
211 us_iter->is_applied = false;
212 }
213 for (UndoStep *us_iter = us_p; us_iter; us_iter = us_iter->prev) {
214 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
215 continue;
216 }
217 us_iter->is_applied = true;
218 }
219
220 /* bmain has been freed. */
221 bmain = CTX_data_main(C);
223
224 if (use_old_bmain_data) {
225 /* Restore previous depsgraphs into current bmain. */
226 BKE_scene_undo_depsgraphs_restore(bmain, depsgraphs);
227
228 /* We need to inform depsgraph about re-used old IDs that would be using newly read
229 * data-blocks, at least evaluated copies need to be updated... */
230 ID *id = nullptr;
231 FOREACH_MAIN_ID_BEGIN (bmain, id) {
235 }
236
237 /* NOTE: Tagging `ID_RECALC_SYNC_TO_EVAL` here should not be needed in practice, since
238 * modified IDs should already have other depsgraph update tags anyway.
239 * However, for the sake of consistency, it's better to effectively use it,
240 * since content of that ID pointer does have been modified. */
241 uint recalc_flags = id->recalc | ((id->tag & ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE) ?
243 IDRecalcFlag(0));
244 /* Tag depsgraph to update data-block for changes that happened between the
245 * current and the target state, see direct_link_id_restore_recalc(). */
246 if (recalc_flags != 0) {
247 DEG_id_tag_update_ex(bmain, id, recalc_flags);
248 }
249
251 if (nodetree != nullptr) {
252 recalc_flags = nodetree->id.recalc;
254 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
255 }
256 if (recalc_flags != 0) {
257 DEG_id_tag_update_ex(bmain, &nodetree->id, recalc_flags);
258 }
259 }
260 if (GS(id->name) == ID_SCE) {
261 Scene *scene = (Scene *)id;
262 if (scene->master_collection != nullptr) {
263 recalc_flags = scene->master_collection->id.recalc;
265 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
266 }
267 if (recalc_flags != 0) {
268 DEG_id_tag_update_ex(bmain, &scene->master_collection->id, recalc_flags);
269 }
270 }
271 }
272
273 /* Restart preview generation if the undo state was generating previews. */
275 }
277
278 FOREACH_MAIN_ID_BEGIN (bmain, id) {
279 /* Clear temporary tag. */
282
283 /* We only start accumulating from this point, any tags set up to here
284 * are already part of the current undo state. This is done in a second
285 * loop because DEG_id_tag_update may set tags on other datablocks. */
286 id->recalc_after_undo_push = 0;
288 if (nodetree != nullptr) {
289 nodetree->id.recalc_after_undo_push = 0;
290 }
291 if (GS(id->name) == ID_SCE) {
292 Scene *scene = (Scene *)id;
293 if (scene->master_collection != nullptr) {
295 }
296 }
297 }
299 }
300 else {
301 ID *id = nullptr;
302 FOREACH_MAIN_ID_BEGIN (bmain, id) {
303 /* Restart preview generation if the undo state was generating previews. */
305 }
307 }
308
310}
311
313{
314 /* To avoid unnecessary slow down, free backwards
315 * (so we don't need to merge when clearing all). */
316 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
317 if (us_p->next != nullptr) {
318 UndoStep *us_next_p = BKE_undosys_step_same_type_next(us_p);
319 if (us_next_p != nullptr) {
320 MemFileUndoStep *us_next = (MemFileUndoStep *)us_next_p;
321 BLO_memfile_merge(&us->data->memfile, &us_next->data->memfile);
322 }
323 }
324
326}
327
329{
330 ut->name = "Global Undo";
335
336 ut->flags = 0;
337
338 ut->step_size = sizeof(MemFileUndoStep);
339}
340
343/* -------------------------------------------------------------------- */
352{
353 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
354 return &us->data->memfile;
355}
356
358{
359 if (!ustack->step_active) {
360 return nullptr;
361 }
362 if (ustack->step_active->type != BKE_UNDOSYS_TYPE_MEMFILE) {
363 return nullptr;
364 }
366}
367
369{
370 UndoStep *us = ustack->step_active;
371 if (id == nullptr || us == nullptr || us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
372 return;
373 }
374
375 MemFile *memfile = &((MemFileUndoStep *)us)->data->memfile;
376 LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &memfile->chunks) {
377 if (mem_chunk->id_session_uid == id->session_uid) {
378 mem_chunk->is_identical_future = false;
379 break;
380 }
381 }
382}
383
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, int flag)
Definition lib_query.cc:416
@ IDWALK_READONLY
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:500
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:494
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:3429
GHash * BKE_scene_undo_depsgraphs_extract(Main *bmain)
Definition scene.cc:3400
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:50
#define LISTBASE_FOREACH(type, var, list)
unsigned int uint
void BLO_memfile_merge(MemFile *first, MemFile *second)
Definition undofile.cc:62
void DEG_id_tag_update_ex(Main *bmain, ID *id, unsigned int flags)
ID and Library types, which are fundamental for SDNA.
IDRecalcFlag
Definition DNA_ID.h:1016
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1085
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:654
@ ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED
Definition DNA_ID.h:903
@ ID_TAG_UNDO_OLD_ID_REUSED_NOUNDO
Definition DNA_ID.h:914
@ ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE
Definition DNA_ID.h:921
@ PRV_USER_EDITED
Definition DNA_ID.h:583
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_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:455
bool ED_undo_is_memfile_compatible(const bContext *C)
Definition ed_undo.cc:409
void ED_editors_exit(Main *bmain, bool do_undo_system)
Definition ed_util.cc:218
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:64
bool ED_editors_flush_edits_ex(Main *bmain, bool for_render, bool check_needs_flush)
Definition ed_util.cc:315
#define NC_SCENE
Definition WM_types.hh:345
#define ND_LAYER_CONTENT
Definition WM_types.hh:420
unsigned int U
Definition btGjkEpa3.h:78
#define GS(x)
Definition iris.cc:202
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:3732
Definition DNA_ID.h:413
unsigned int recalc_after_undo_push
Definition DNA_ID.h:448
unsigned int recalc
Definition DNA_ID.h:437
int tag
Definition DNA_ID.h:434
char name[66]
Definition DNA_ID.h:425
bool is_memfile_undo_flush_needed
Definition BKE_main.hh:165
bool use_memfile_full_barrier
Definition BKE_main.hh:170
MemFileUndoData * data
ListBase chunks
struct bPose * pose
short flag[2]
Definition DNA_ID.h:605
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)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)