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_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_undo_system.hh"
30
32
33#include "WM_api.hh"
34#include "WM_types.hh"
35
36#include "ED_render.hh"
37#include "ED_undo.hh"
38#include "ED_util.hh"
39
41
42#include "undo_intern.hh"
43
44/* -------------------------------------------------------------------- */
47
52
54{
55 /* other poll functions must run first, this is a catch-all. */
56
57 if ((U.uiflag & USER_GLOBALUNDO) == 0) {
58 return false;
59 }
60
61 /* Allow a single memfile undo step (the first). */
62 UndoStack *ustack = ED_undo_stack_get();
63 if ((ustack->step_active != nullptr) && (ED_undo_is_memfile_compatible(C) == false)) {
64 return false;
65 }
66 return true;
67}
68
69static bool memfile_undosys_step_encode(bContext * /*C*/, Main *bmain, UndoStep *us_p)
70{
71 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
72
73 /* Important we only use 'main' from the context (see: BKE_undosys_stack_init_from_main). */
74 UndoStack *ustack = ED_undo_stack_get();
75
77 ED_editors_flush_edits_ex(bmain, false, true);
78 }
79
80 /* can be null, use when set. */
83 us->data = BKE_memfile_undo_encode(bmain, us_prev ? us_prev->data : nullptr);
84 us->step.data_size = us->data->undo_size;
85
86 /* Store the fact that we should not re-use old data with that undo step, and reset the Main
87 * flag. */
89 bmain->use_memfile_full_barrier = false;
90
91 return true;
92}
93
95{
96 ID *self_id = cb_data->self_id;
97 ID **id_pointer = cb_data->id_pointer;
99
100 ID *id = *id_pointer;
101 if (id != nullptr && !ID_IS_LINKED(id) && (id->tag & ID_TAG_UNDO_OLD_ID_REUSED_UNCHANGED) == 0) {
102 bool do_stop_iter = true;
103 if (GS(self_id->name) == ID_OB) {
104 Object *ob_self = (Object *)self_id;
105 if (ob_self->type == OB_ARMATURE) {
106 if (ob_self->data == id) {
107 BLI_assert(GS(id->name) == ID_AR);
108 if (ob_self->pose != nullptr) {
109 /* We have a changed/re-read armature used by an unchanged armature object: our beloved
110 * Bone pointers from the object's pose need their usual special treatment. */
111 ob_self->pose->flag |= POSE_RECALC;
112 }
113 }
114 else {
115 /* Cannot stop iteration until we checked ob_self->data pointer... */
116 do_stop_iter = false;
117 }
118 }
119 }
120
121 return do_stop_iter ? IDWALK_RET_STOP_ITER : IDWALK_RET_NOP;
122 }
123
124 return IDWALK_RET_NOP;
125}
126
135{
136 PreviewImage *preview = BKE_previewimg_id_get(id);
137 if (!preview) {
138 return;
139 }
140
141 for (int i = 0; i < NUM_ICON_SIZES; i++) {
142 if (preview->flag[i] & PRV_USER_EDITED) {
143 /* Don't modify custom previews. */
144 continue;
145 }
146
147 if (!BKE_previewimg_is_finished(preview, i)) {
149 }
150 }
151}
152
154 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir undo_direction, bool /*is_final*/)
155{
156 BLI_assert(undo_direction != STEP_INVALID);
157
158 bool use_old_bmain_data = true;
159
160 if (USER_EXPERIMENTAL_TEST(&U, use_undo_legacy) || !(U.uiflag & USER_GLOBALUNDO)) {
161 use_old_bmain_data = false;
162 }
163 else if (undo_direction == STEP_REDO) {
164 /* The only time we should have to force a complete redo is when current step is tagged as a
165 * redo barrier.
166 * If previous step was not a memfile one should not matter here, current data in old bmain
167 * should still always be valid for unchanged data-blocks. */
168 if (us_p->use_old_bmain_data == false) {
169 use_old_bmain_data = false;
170 }
171 }
172 else if (undo_direction == STEP_UNDO) {
173 /* Here we do not care whether current step is an undo barrier, since we are coming from
174 * 'the future' we can still re-use old data. However, if *next* undo step
175 * (i.e. the one immediately in the future, the one we are coming from)
176 * is a barrier, then we have to force a complete undo.
177 * Note that non-memfile undo steps **should** not be an issue anymore, since we handle
178 * fine-grained update flags now.
179 */
180 UndoStep *us_next = us_p->next;
181 if (us_next != nullptr) {
182 if (us_next->use_old_bmain_data == false) {
183 use_old_bmain_data = false;
184 }
185 }
186 }
187
188 /* Extract depsgraphs from current bmain (which may be freed during undo step reading),
189 * and store them for re-use. */
190 GHash *depsgraphs = nullptr;
191 if (use_old_bmain_data) {
192 depsgraphs = BKE_scene_undo_depsgraphs_extract(bmain);
193 }
194
195 ED_editors_exit(bmain, false);
196 /* Ensure there's no preview job running. Unfinished previews will be scheduled for regeneration
197 * via #memfile_undosys_unfinished_id_previews_restart(). */
199
200 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
201 BKE_memfile_undo_decode(us->data, undo_direction, use_old_bmain_data, C);
202
203 for (UndoStep *us_iter = us_p->next; us_iter; us_iter = us_iter->next) {
204 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
205 continue;
206 }
207 us_iter->is_applied = false;
208 }
209 for (UndoStep *us_iter = us_p; us_iter; us_iter = us_iter->prev) {
210 if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
211 continue;
212 }
213 us_iter->is_applied = true;
214 }
215
216 /* bmain has been freed. */
217 bmain = CTX_data_main(C);
219
220 if (use_old_bmain_data) {
221 /* Restore previous depsgraphs into current bmain. */
222 BKE_scene_undo_depsgraphs_restore(bmain, depsgraphs);
223
224 /* We need to inform depsgraph about re-used old IDs that would be using newly read
225 * data-blocks, at least evaluated copies need to be updated... */
226 ID *id = nullptr;
227 FOREACH_MAIN_ID_BEGIN (bmain, id) {
231 }
232
233 /* NOTE: Tagging `ID_RECALC_SYNC_TO_EVAL` here should not be needed in practice, since
234 * modified IDs should already have other depsgraph update tags anyway.
235 * However, for the sake of consistency, it's better to effectively use it,
236 * since content of that ID pointer does have been modified. */
237 uint recalc_flags = id->recalc | ((id->tag & ID_TAG_UNDO_OLD_ID_REREAD_IN_PLACE) ?
239 IDRecalcFlag(0));
240 /* Tag depsgraph to update data-block for changes that happened between the
241 * current and the target state, see direct_link_id_restore_recalc(). */
242 if (recalc_flags != 0) {
243 DEG_id_tag_update_ex(bmain, id, recalc_flags);
244 }
245
247 if (nodetree != nullptr) {
248 recalc_flags = nodetree->id.recalc;
250 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
251 }
252 if (recalc_flags != 0) {
253 DEG_id_tag_update_ex(bmain, &nodetree->id, recalc_flags);
254 }
255 }
256 if (GS(id->name) == ID_SCE) {
257 Scene *scene = (Scene *)id;
258 if (scene->master_collection != nullptr) {
259 recalc_flags = scene->master_collection->id.recalc;
261 recalc_flags |= ID_RECALC_SYNC_TO_EVAL;
262 }
263 if (recalc_flags != 0) {
264 DEG_id_tag_update_ex(bmain, &scene->master_collection->id, recalc_flags);
265 }
266 }
267 }
268
269 /* Restart preview generation if the undo state was generating previews. */
271 }
273
274 FOREACH_MAIN_ID_BEGIN (bmain, id) {
275 /* Clear temporary tag. */
278
279 /* We only start accumulating from this point, any tags set up to here
280 * are already part of the current undo state. This is done in a second
281 * loop because DEG_id_tag_update may set tags on other datablocks. */
282 id->recalc_after_undo_push = 0;
284 if (nodetree != nullptr) {
285 nodetree->id.recalc_after_undo_push = 0;
286 }
287 if (GS(id->name) == ID_SCE) {
288 Scene *scene = (Scene *)id;
289 if (scene->master_collection != nullptr) {
291 }
292 }
293 }
295 }
296 else {
297 ID *id = nullptr;
298 FOREACH_MAIN_ID_BEGIN (bmain, id) {
299 /* Restart preview generation if the undo state was generating previews. */
301 }
303 }
304
306}
307
309{
310 /* To avoid unnecessary slow down, free backwards
311 * (so we don't need to merge when clearing all). */
312 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
313 if (us_p->next != nullptr) {
314 UndoStep *us_next_p = BKE_undosys_step_same_type_next(us_p);
315 if (us_next_p != nullptr) {
316 MemFileUndoStep *us_next = (MemFileUndoStep *)us_next_p;
317 BLO_memfile_merge(&us->data->memfile, &us_next->data->memfile);
318 }
319 }
320
322}
323
325{
326 ut->name = "Global Undo";
331
332 ut->flags = 0;
333
334 ut->step_size = sizeof(MemFileUndoStep);
335}
336
338
339/* -------------------------------------------------------------------- */
342
348{
349 MemFileUndoStep *us = (MemFileUndoStep *)us_p;
350 return &us->data->memfile;
351}
352
354{
355 if (!ustack->step_active) {
356 return nullptr;
357 }
358 if (ustack->step_active->type != BKE_UNDOSYS_TYPE_MEMFILE) {
359 return nullptr;
360 }
362}
363
365{
366 UndoStep *us = ustack->step_active;
367 if (id == nullptr || us == nullptr || us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
368 return;
369 }
370
371 MemFile *memfile = &((MemFileUndoStep *)us)->data->memfile;
372 LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &memfile->chunks) {
373 if (mem_chunk->id_session_uid == id->session_uid) {
374 mem_chunk->is_identical_future = false;
375 break;
376 }
377 }
378}
379
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_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.
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)