Blender  V3.3
undo_system.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include "CLG_log.h"
13 
14 #include "BLI_listbase.h"
15 #include "BLI_string.h"
16 #include "BLI_sys_types.h"
17 #include "BLI_utildefines.h"
18 
19 #include "BLT_translation.h"
20 
21 #include "DNA_listBase.h"
23 
24 #include "BKE_context.h"
25 #include "BKE_global.h"
26 #include "BKE_lib_override.h"
27 #include "BKE_main.h"
28 #include "BKE_undo_system.h"
29 
30 #include "MEM_guardedalloc.h"
31 
32 #define undo_stack _wm_undo_stack_disallow /* pass in as a variable always. */
33 
35 #define WITH_GLOBAL_UNDO_KEEP_ONE
36 
38 #define WITH_GLOBAL_UNDO_ENSURE_UPDATED
39 
44 #define WITH_GLOBAL_UNDO_CORRECT_ORDER
45 
47 static CLG_LogRef LOG = {"bke.undosys"};
48 
49 /* -------------------------------------------------------------------- */
59 
61 
63 {
64  LISTBASE_FOREACH (const UndoType *, ut, &g_undo_types) {
65  /* No poll means we don't check context. */
66  if (ut->poll && ut->poll(C)) {
67  return ut;
68  }
69  }
70  return NULL;
71 }
72 
75 /* -------------------------------------------------------------------- */
83 #define WITH_NESTED_UNDO_CHECK
84 
85 #ifdef WITH_NESTED_UNDO_CHECK
86 static bool g_undo_callback_running = false;
87 # define UNDO_NESTED_ASSERT(state) BLI_assert(g_undo_callback_running == state)
88 # define UNDO_NESTED_CHECK_BEGIN \
89  { \
90  UNDO_NESTED_ASSERT(false); \
91  g_undo_callback_running = true; \
92  } \
93  ((void)0)
94 # define UNDO_NESTED_CHECK_END \
95  { \
96  UNDO_NESTED_ASSERT(true); \
97  g_undo_callback_running = false; \
98  } \
99  ((void)0)
100 #else
101 # define UNDO_NESTED_ASSERT(state) ((void)0)
102 # define UNDO_NESTED_CHECK_BEGIN ((void)0)
103 # define UNDO_NESTED_CHECK_END ((void)0)
104 #endif
105 
108 /* -------------------------------------------------------------------- */
116 static void undosys_id_ref_store(void *UNUSED(user_data), UndoRefID *id_ref)
117 {
118  BLI_assert(id_ref->name[0] == '\0');
119  if (id_ref->ptr) {
120  BLI_strncpy(id_ref->name, id_ref->ptr->name, sizeof(id_ref->name));
121  /* Not needed, just prevents stale data access. */
122  id_ref->ptr = NULL;
123  }
124 }
125 
126 static void undosys_id_ref_resolve(void *user_data, UndoRefID *id_ref)
127 {
128  /* NOTE: we could optimize this,
129  * for now it's not too bad since it only runs when we access undo! */
130  Main *bmain = user_data;
131  ListBase *lb = which_libbase(bmain, GS(id_ref->name));
132  LISTBASE_FOREACH (ID *, id, lb) {
133  if (STREQ(id_ref->name, id->name) && !ID_IS_LINKED(id)) {
134  id_ref->ptr = id;
135  break;
136  }
137  }
138 }
139 
140 static bool undosys_step_encode(bContext *C, Main *bmain, UndoStack *ustack, UndoStep *us)
141 {
142  CLOG_INFO(&LOG, 2, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name);
144  bool ok = us->type->step_encode(C, bmain, us);
146  if (ok) {
147  if (us->type->step_foreach_ID_ref != NULL) {
148  /* Don't use from context yet because sometimes context is fake and
149  * not all members are filled in. */
151  }
152 
153 #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
154  if (us->type == BKE_UNDOSYS_TYPE_MEMFILE) {
155  ustack->step_active_memfile = us;
156  }
157 #endif
158  }
159  if (ok == false) {
160  CLOG_INFO(&LOG, 2, "encode callback didn't create undo step");
161  }
162  return ok;
163 }
164 
166  Main *bmain,
167  UndoStack *ustack,
168  UndoStep *us,
169  const eUndoStepDir dir,
170  bool is_final)
171 {
172  CLOG_INFO(&LOG, 2, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name);
173 
174  if (us->type->step_foreach_ID_ref) {
175 #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
176  if (us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
177  for (UndoStep *us_iter = us->prev; us_iter; us_iter = us_iter->prev) {
178  if (us_iter->type == BKE_UNDOSYS_TYPE_MEMFILE) {
179  if (us_iter == ustack->step_active_memfile) {
180  /* Common case, we're already using the last memfile state. */
181  }
182  else {
183  /* Load the previous memfile state so any ID's referenced in this
184  * undo step will be correctly resolved, see: T56163. */
185  undosys_step_decode(C, bmain, ustack, us_iter, dir, false);
186  /* May have been freed on memfile read. */
187  bmain = G_MAIN;
188  }
189  break;
190  }
191  }
192  }
193 #endif
194  /* Don't use from context yet because sometimes context is fake and
195  * not all members are filled in. */
197  }
198 
200  us->type->step_decode(C, bmain, us, dir, is_final);
202 
203 #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
204  if (us->type == BKE_UNDOSYS_TYPE_MEMFILE) {
205  ustack->step_active_memfile = us;
206  }
207 #endif
208 }
209 
211 {
212  CLOG_INFO(&LOG, 2, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name);
214  us->type->step_free(us);
216 
217  BLI_remlink(&ustack->steps, us);
218  MEM_freeN(us);
219 
220 #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
221  if (ustack->step_active_memfile == us) {
222  ustack->step_active_memfile = NULL;
223  }
224 #endif
225 }
226 
229 /* -------------------------------------------------------------------- */
233 #ifndef NDEBUG
234 static void undosys_stack_validate(UndoStack *ustack, bool expect_non_empty)
235 {
236  if (ustack->step_active != NULL) {
238  BLI_assert(BLI_findindex(&ustack->steps, ustack->step_active) != -1);
239  }
240  if (expect_non_empty) {
242  }
243 }
244 #else
245 static void undosys_stack_validate(UndoStack *UNUSED(ustack), bool UNUSED(expect_non_empty))
246 {
247 }
248 #endif
249 
251 {
252  UndoStack *ustack = MEM_callocN(sizeof(UndoStack), __func__);
253  return ustack;
254 }
255 
257 {
258  BKE_undosys_stack_clear(ustack);
259  MEM_freeN(ustack);
260 }
261 
263 {
264  UNDO_NESTED_ASSERT(false);
265  CLOG_INFO(&LOG, 1, "steps=%d", BLI_listbase_count(&ustack->steps));
266  for (UndoStep *us = ustack->steps.last, *us_prev; us; us = us_prev) {
267  us_prev = us->prev;
268  undosys_step_free_and_unlink(ustack, us);
269  }
270  BLI_listbase_clear(&ustack->steps);
271  ustack->step_active = NULL;
272 }
273 
275 {
276  /* Remove active and all following undo-steps. */
277  UndoStep *us = ustack->step_active;
278 
279  if (us) {
280  ustack->step_active = us->prev;
281  bool is_not_empty = ustack->step_active != NULL;
282 
283  while (ustack->steps.last != ustack->step_active) {
284  UndoStep *us_iter = ustack->steps.last;
285  undosys_step_free_and_unlink(ustack, us_iter);
286  undosys_stack_validate(ustack, is_not_empty);
287  }
288  }
289 }
290 
291 /* Caller is responsible for handling active. */
293 {
294  if (us) {
295  bool is_not_empty = true;
296  UndoStep *us_iter;
297  do {
298  us_iter = ustack->steps.last;
299  BLI_assert(us_iter != ustack->step_active);
300  undosys_step_free_and_unlink(ustack, us_iter);
301  undosys_stack_validate(ustack, is_not_empty);
302  } while ((us != us_iter));
303  }
304 }
305 
306 static void undosys_stack_clear_all_first(UndoStack *ustack, UndoStep *us, UndoStep *us_exclude)
307 {
308  if (us && us == us_exclude) {
309  us = us->prev;
310  }
311 
312  if (us) {
313  bool is_not_empty = true;
314  UndoStep *us_iter;
315  do {
316  us_iter = ustack->steps.first;
317  if (us_iter == us_exclude) {
318  us_iter = us_iter->next;
319  }
320  BLI_assert(us_iter != ustack->step_active);
321  undosys_step_free_and_unlink(ustack, us_iter);
322  undosys_stack_validate(ustack, is_not_empty);
323  } while ((us != us_iter));
324  }
325 }
326 
327 static bool undosys_stack_push_main(UndoStack *ustack, const char *name, struct Main *bmain)
328 {
329  UNDO_NESTED_ASSERT(false);
330  BLI_assert(ustack->step_init == NULL);
331  CLOG_INFO(&LOG, 1, "'%s'", name);
332  bContext *C_temp = CTX_create();
333  CTX_data_main_set(C_temp, bmain);
335  ustack, C_temp, name, BKE_UNDOSYS_TYPE_MEMFILE);
336  CTX_free(C_temp);
337  return (ret & UNDO_PUSH_RET_SUCCESS);
338 }
339 
340 void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain)
341 {
342  UNDO_NESTED_ASSERT(false);
343  undosys_stack_push_main(ustack, IFACE_("Original"), bmain);
344 }
345 
347 {
349  if (!ELEM(ut, NULL, BKE_UNDOSYS_TYPE_MEMFILE)) {
350  BKE_undosys_step_push_with_type(ustack, C, IFACE_("Original Mode"), ut);
351  }
352 }
353 
354 bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name)
355 {
356  if (name) {
357  const UndoStep *us = BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name));
358  return us && us->prev;
359  }
360 
361  return !BLI_listbase_is_empty(&ustack->steps);
362 }
363 
365 {
366  UndoStep *us = ustack->step_active;
367  while (us && (us->type != ut)) {
368  us = us->prev;
369  }
370  return us;
371 }
372 
374 {
375  UNDO_NESTED_ASSERT(false);
376  CLOG_INFO(&LOG, 1, "type='%s'", ut->name);
377  if (ustack->step_init && (ustack->step_init->type == ut)) {
378  return ustack->step_init;
379  }
380  return BKE_undosys_stack_active_with_type(ustack, ut);
381 }
382 
384 {
385  UNDO_NESTED_ASSERT(false);
386  if ((steps == -1) && (memory_limit == 0)) {
387  return;
388  }
389 
390  CLOG_INFO(&LOG, 1, "steps=%d, memory_limit=%zu", steps, memory_limit);
391  UndoStep *us;
392  UndoStep *us_exclude = NULL;
393  /* keep at least two (original + other) */
394  size_t data_size_all = 0;
395  size_t us_count = 0;
396  for (us = ustack->steps.last; us && us->prev; us = us->prev) {
397  if (memory_limit) {
398  data_size_all += us->data_size;
399  if (data_size_all > memory_limit) {
400  CLOG_INFO(&LOG,
401  1,
402  "At step %zu: data_size_all=%zu >= memory_limit=%zu",
403  us_count,
404  data_size_all,
405  memory_limit);
406  break;
407  }
408  }
409  if (steps != -1) {
410  if (us_count == steps) {
411  break;
412  }
413  if (us->skip == false) {
414  us_count += 1;
415  }
416  }
417  }
418 
419  CLOG_INFO(&LOG, 1, "Total steps %zu: data_size_all=%zu", us_count, data_size_all);
420 
421  if (us) {
422 #ifdef WITH_GLOBAL_UNDO_KEEP_ONE
423  /* Hack, we need to keep at least one BKE_UNDOSYS_TYPE_MEMFILE. */
424  if (us->type != BKE_UNDOSYS_TYPE_MEMFILE) {
425  us_exclude = us->prev;
426  while (us_exclude && us_exclude->type != BKE_UNDOSYS_TYPE_MEMFILE) {
427  us_exclude = us_exclude->prev;
428  }
429  /* Once this is outside the given number of 'steps', undoing onto this state
430  * may skip past many undo steps which is confusing, instead,
431  * disallow stepping onto this state entirely. */
432  if (us_exclude) {
433  us_exclude->skip = true;
434  }
435  }
436 #endif
437  /* Free from first to last, free functions may update de-duplication info
438  * (see #MemFileUndoStep). */
439  undosys_stack_clear_all_first(ustack, us->prev, us_exclude);
440  }
441 }
442 
445 /* -------------------------------------------------------------------- */
450  bContext *C,
451  const char *name,
452  const UndoType *ut)
453 {
454  UNDO_NESTED_ASSERT(false);
455  /* We could detect and clean this up (but it should never happen!). */
456  BLI_assert(ustack->step_init == NULL);
457  if (ut->step_encode_init) {
458  undosys_stack_validate(ustack, false);
459 
460  if (ustack->step_active) {
462  }
463 
464  UndoStep *us = MEM_callocN(ut->step_size, __func__);
465  if (name != NULL) {
466  BLI_strncpy(us->name, name, sizeof(us->name));
467  }
468  us->type = ut;
469  ustack->step_init = us;
470  CLOG_INFO(&LOG, 1, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name);
471  ut->step_encode_init(C, us);
472  undosys_stack_validate(ustack, false);
473  return us;
474  }
475 
476  return NULL;
477 }
478 
480 {
481  UNDO_NESTED_ASSERT(false);
482  /* We could detect and clean this up (but it should never happen!). */
483  BLI_assert(ustack->step_init == NULL);
485  if (ut == NULL) {
486  return NULL;
487  }
488  return BKE_undosys_step_push_init_with_type(ustack, C, name, ut);
489 }
490 
492  bContext *C,
493  const char *name,
494  const UndoType *ut)
495 {
497 
498  UNDO_NESTED_ASSERT(false);
499  undosys_stack_validate(ustack, false);
500  bool is_not_empty = ustack->step_active != NULL;
502 
503  /* Might not be final place for this to be called - probably only want to call it from some
504  * undo handlers, not all of them? */
507  }
508 
509  /* Remove all undo-steps after (also when 'ustack->step_active == NULL'). */
510  while (ustack->steps.last != ustack->step_active) {
511  UndoStep *us_iter = ustack->steps.last;
512  undosys_step_free_and_unlink(ustack, us_iter);
513  undosys_stack_validate(ustack, is_not_empty);
514  }
515 
516  if (ustack->step_active) {
517  BLI_assert(BLI_findindex(&ustack->steps, ustack->step_active) != -1);
518  }
519 
520 #ifdef WITH_GLOBAL_UNDO_ENSURE_UPDATED
521  if (ut->step_foreach_ID_ref != NULL) {
522  if (G_MAIN->is_memfile_undo_written == false) {
523  const char *name_internal = "MemFile Internal (pre)";
524  /* Don't let 'step_init' cause issues when adding memfile undo step. */
525  void *step_init = ustack->step_init;
526  ustack->step_init = NULL;
527  const bool ok = undosys_stack_push_main(ustack, name_internal, G_MAIN);
528  /* Restore 'step_init'. */
529  ustack->step_init = step_init;
530  if (ok) {
531  UndoStep *us = ustack->steps.last;
532  BLI_assert(STREQ(us->name, name_internal));
533  us->skip = true;
534 # ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
535  ustack->step_active_memfile = us;
536 # endif
537  }
538  }
539  }
540 #endif
541 
542  bool use_memfile_step = false;
543  {
544  UndoStep *us = ustack->step_init ? ustack->step_init : MEM_callocN(ut->step_size, __func__);
545  ustack->step_init = NULL;
546  if (us->name[0] == '\0') {
547  BLI_strncpy(us->name, name, sizeof(us->name));
548  }
549  us->type = ut;
550  /* True by default, code needs to explicitly set it to false if necessary. */
551  us->use_old_bmain_data = true;
552  /* Initialized, not added yet. */
553 
554  CLOG_INFO(&LOG, 1, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name);
555 
556  if (!undosys_step_encode(C, G_MAIN, ustack, us)) {
557  MEM_freeN(us);
558  undosys_stack_validate(ustack, true);
559  return retval;
560  }
561  ustack->step_active = us;
562  BLI_addtail(&ustack->steps, us);
563  use_memfile_step = us->use_memfile_step;
564  }
565 
566  if (use_memfile_step) {
567  /* Make this the user visible undo state, so redo always applies
568  * on top of the mem-file undo instead of skipping it. see: T67256. */
569  UndoStep *us_prev = ustack->step_active;
570  const char *name_internal = us_prev->name;
571  const bool ok = undosys_stack_push_main(ustack, name_internal, G_MAIN);
572  if (ok) {
573  UndoStep *us = ustack->steps.last;
574  BLI_assert(STREQ(us->name, name_internal));
575  us_prev->skip = true;
576 #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER
577  ustack->step_active_memfile = us;
578 #endif
579  ustack->step_active = us;
580  }
581  }
582 
583  if (ustack->group_level > 0) {
584  /* Temporarily set skip for the active step.
585  * This is an invalid state which must be corrected once the last group ends. */
586  ustack->step_active->skip = true;
587  }
588 
589  undosys_stack_validate(ustack, true);
590  return (retval | UNDO_PUSH_RET_SUCCESS);
591 }
592 
594 {
595  UNDO_NESTED_ASSERT(false);
596  const UndoType *ut = ustack->step_init ? ustack->step_init->type :
598  if (ut == NULL) {
599  return false;
600  }
601  return BKE_undosys_step_push_with_type(ustack, C, name, ut);
602 }
603 
605 {
606  if (us) {
607  const UndoType *ut = us->type;
608  while ((us = us->next)) {
609  if (us->type == ut) {
610  return us;
611  }
612  }
613  }
614  return us;
615 }
616 
618 {
619  if (us) {
620  const UndoType *ut = us->type;
621  while ((us = us->prev)) {
622  if (us->type == ut) {
623  return us;
624  }
625  }
626  }
627  return us;
628 }
629 
631  const char *name,
632  const UndoType *ut)
633 {
634  for (UndoStep *us = ustack->steps.last; us; us = us->prev) {
635  if (us->type == ut) {
636  if (STREQ(name, us->name)) {
637  return us;
638  }
639  }
640  }
641  return NULL;
642 }
643 
645 {
646  return BLI_rfindstring(&ustack->steps, name, offsetof(UndoStep, name));
647 }
648 
650 {
651  for (UndoStep *us = ustack->steps.last; us; us = us->prev) {
652  if (us->type == ut) {
653  return us;
654  }
655  }
656  return NULL;
657 }
658 
660  const UndoStep *us_target,
661  const UndoStep *us_reference)
662 {
663  if (us_reference == NULL) {
664  us_reference = ustack->step_active;
665  }
666 
667  BLI_assert(us_reference != NULL);
668 
669  /* Note that we use heuristics to make this lookup as fast as possible in most common cases,
670  * assuming that:
671  * - Most cases are just undo or redo of one step from active one.
672  * - Otherwise, it is typically faster to check future steps since active one is usually close
673  * to the end of the list, rather than its start. */
674  /* NOTE: in case target step is the active one, we assume we are in an undo case... */
675  if (ELEM(us_target, us_reference, us_reference->prev)) {
676  return STEP_UNDO;
677  }
678  if (us_target == us_reference->next) {
679  return STEP_REDO;
680  }
681 
682  /* Search forward, and then backward. */
683  for (UndoStep *us_iter = us_reference->next; us_iter != NULL; us_iter = us_iter->next) {
684  if (us_iter == us_target) {
685  return STEP_REDO;
686  }
687  }
688  for (UndoStep *us_iter = us_reference->prev; us_iter != NULL; us_iter = us_iter->prev) {
689  if (us_iter == us_target) {
690  return STEP_UNDO;
691  }
692  }
693 
694  BLI_assert_msg(0,
695  "Target undo step not found, this should not happen and may indicate an undo "
696  "stack corruption");
697  return STEP_INVALID;
698 }
699 
704 static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStepDir undo_dir)
705 {
706  if (us_reference->type->flags & UNDOTYPE_FLAG_DECODE_ACTIVE_STEP) {
707  /* Reading this step means an undo action reads undo twice.
708  * This should be avoided where possible, however some undo systems require it.
709  *
710  * Redo skips the current state as this represents the currently loaded state. */
711  return (undo_dir == -1) ? us_reference : us_reference->next;
712  }
713 
714  /* Typical case, skip reading the current undo step. */
715  return (undo_dir == -1) ? us_reference->prev : us_reference->next;
716 }
717 
719  bContext *C,
720  UndoStep *us_target,
721  UndoStep *us_reference,
722  const bool use_skip)
723 {
724  UNDO_NESTED_ASSERT(false);
725  if (us_target == NULL) {
726  CLOG_ERROR(&LOG, "called with a NULL target step");
727  return false;
728  }
729  undosys_stack_validate(ustack, true);
730 
731  if (us_reference == NULL) {
732  us_reference = ustack->step_active;
733  }
734  if (us_reference == NULL) {
735  CLOG_ERROR(&LOG, "could not find a valid initial active target step as reference");
736  return false;
737  }
738 
739  /* This considers we are in undo case if both `us_target` and `us_reference` are the same. */
740  const eUndoStepDir undo_dir = BKE_undosys_step_calc_direction(ustack, us_target, us_reference);
741  BLI_assert(undo_dir != STEP_INVALID);
742 
743  /* This will be the active step once the undo process is complete.
744  *
745  * In case we do skip 'skipped' steps, the final active step may be several steps backward from
746  * the one passed as parameter. */
747  UndoStep *us_target_active = us_target;
748  if (use_skip) {
749  while (us_target_active != NULL && us_target_active->skip) {
750  us_target_active = (undo_dir == -1) ? us_target_active->prev : us_target_active->next;
751  }
752  if (us_target_active == NULL) {
753  CLOG_INFO(&LOG,
754  2,
755  "undo/redo did not find a step after stepping over skip-steps "
756  "(undo limit exceeded)");
757  return false;
758  }
759  }
760 
761  CLOG_INFO(&LOG,
762  1,
763  "addr=%p, name='%s', type='%s', undo_dir=%d",
764  us_target,
765  us_target->name,
766  us_target->type->name,
767  undo_dir);
768 
769  /* Undo/Redo steps until we reach given target step (or beyond if it has to be skipped),
770  * from given reference step. */
771  bool is_processing_extra_skipped_steps = false;
772  for (UndoStep *us_iter = undosys_step_iter_first(us_reference, undo_dir); us_iter != NULL;
773  us_iter = (undo_dir == -1) ? us_iter->prev : us_iter->next) {
774  BLI_assert(us_iter != NULL);
775 
776  const bool is_final = (us_iter == us_target_active);
777 
778  if (!is_final && is_processing_extra_skipped_steps) {
779  BLI_assert(us_iter->skip == true);
780  CLOG_INFO(&LOG,
781  2,
782  "undo/redo continue with skip addr=%p, name='%s', type='%s'",
783  us_iter,
784  us_iter->name,
785  us_iter->type->name);
786  }
787 
788  undosys_step_decode(C, G_MAIN, ustack, us_iter, undo_dir, is_final);
789  ustack->step_active = us_iter;
790 
791  if (us_iter == us_target) {
792  is_processing_extra_skipped_steps = true;
793  }
794 
795  if (is_final) {
796  /* Undo/Redo process is finished and successful. */
797  return true;
798  }
799  }
800 
801  BLI_assert(
802  !"This should never be reached, either undo stack is corrupted, or code above is buggy");
803  return false;
804 }
805 
807 {
808  /* Note that here we do not skip 'skipped' steps by default. */
809  return BKE_undosys_step_load_data_ex(ustack, C, us_target, NULL, false);
810 }
811 
812 void BKE_undosys_step_load_from_index(UndoStack *ustack, bContext *C, const int index)
813 {
814  UndoStep *us_target = BLI_findlink(&ustack->steps, index);
815  BLI_assert(us_target->skip == false);
816  if (us_target == ustack->step_active) {
817  return;
818  }
819  BKE_undosys_step_load_data(ustack, C, us_target);
820 }
821 
823  bContext *C,
824  UndoStep *us_target,
825  bool use_skip)
826 {
827  /* In case there is no active step, we consider we just load given step, so reference must be
828  * itself (due to weird 'load current active step in undo case' thing, see comments in
829  * #BKE_undosys_step_load_data_ex). */
830  UndoStep *us_reference = ustack->step_active != NULL ? ustack->step_active : us_target;
831 
832  BLI_assert(BKE_undosys_step_calc_direction(ustack, us_target, us_reference) == -1);
833 
834  return BKE_undosys_step_load_data_ex(ustack, C, us_target, us_reference, use_skip);
835 }
836 
838 {
839  return BKE_undosys_step_undo_with_data_ex(ustack, C, us_target, true);
840 }
841 
843 {
844  if (ustack->step_active != NULL) {
845  return BKE_undosys_step_undo_with_data(ustack, C, ustack->step_active->prev);
846  }
847  return false;
848 }
849 
851  bContext *C,
852  UndoStep *us_target,
853  bool use_skip)
854 {
855  /* In case there is no active step, we consider we just load given step, so reference must be
856  * the previous one. */
857  UndoStep *us_reference = ustack->step_active != NULL ? ustack->step_active : us_target->prev;
858 
859  BLI_assert(BKE_undosys_step_calc_direction(ustack, us_target, us_reference) == 1);
860 
861  return BKE_undosys_step_load_data_ex(ustack, C, us_target, us_reference, use_skip);
862 }
863 
865 {
866  return BKE_undosys_step_redo_with_data_ex(ustack, C, us_target, true);
867 }
868 
870 {
871  if (ustack->step_active != NULL) {
872  return BKE_undosys_step_redo_with_data(ustack, C, ustack->step_active->next);
873  }
874  return false;
875 }
876 
878 {
879  UndoType *ut;
880 
881  ut = MEM_callocN(sizeof(UndoType), __func__);
882 
883  undosys_fn(ut);
884 
886 
887  return ut;
888 }
889 
891 {
892  UndoType *ut;
893  while ((ut = BLI_pophead(&g_undo_types))) {
894  MEM_freeN(ut);
895  }
896 }
897 
900 /* -------------------------------------------------------------------- */
920 {
921  BLI_assert(ustack->group_level >= 0);
922  ustack->group_level += 1;
923 }
924 
926 {
927  ustack->group_level -= 1;
928  BLI_assert(ustack->group_level >= 0);
929 
930  if (ustack->group_level == 0) {
931  if (LIKELY(ustack->step_active != NULL)) {
932  ustack->step_active->skip = false;
933  }
934  }
935 }
936 
939 /* -------------------------------------------------------------------- */
945 static void UNUSED_FUNCTION(BKE_undosys_foreach_ID_ref(UndoStack *ustack,
946  UndoTypeForEachIDRefFn foreach_ID_ref_fn,
947  void *user_data))
948 {
949  LISTBASE_FOREACH (UndoStep *, us, &ustack->steps) {
950  const UndoType *ut = us->type;
951  if (ut->step_foreach_ID_ref != NULL) {
952  ut->step_foreach_ID_ref(us, foreach_ID_ref_fn, user_data);
953  }
954  }
955 }
956 
959 /* -------------------------------------------------------------------- */
964 {
965  printf("Undo %d Steps (*: active, #=applied, M=memfile-active, S=skip)\n",
966  BLI_listbase_count(&ustack->steps));
967  int index = 0;
968  LISTBASE_FOREACH (UndoStep *, us, &ustack->steps) {
969  printf("[%c%c%c%c] %3d {%p} type='%s', name='%s'\n",
970  (us == ustack->step_active) ? '*' : ' ',
971  us->is_applied ? '#' : ' ',
972  (us == ustack->step_active_memfile) ? 'M' : ' ',
973  us->skip ? 'S' : ' ',
974  index,
975  (void *)us,
976  us->type->name,
977  us->name);
978  index++;
979  }
980 }
981 
bContext * CTX_create(void)
Definition: context.c:102
void CTX_free(bContext *C)
Definition: context.c:118
void CTX_data_main_set(bContext *C, struct Main *bmain)
Definition: context.c:1084
#define G_MAIN
Definition: BKE_global.h:267
bool BKE_lib_override_library_main_operations_create(struct Main *bmain, bool force_auto)
struct ListBase * which_libbase(struct Main *bmain, short type)
Definition: main.c:567
@ UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE
@ UNDOTYPE_FLAG_DECODE_ACTIVE_STEP
eUndoStepDir
@ STEP_INVALID
@ STEP_UNDO
@ STEP_REDO
eUndoPushReturn
@ UNDO_PUSH_RET_SUCCESS
@ UNDO_PUSH_RET_OVERRIDE_CHANGED
@ UNDO_PUSH_RET_FAILURE
void(* UndoTypeForEachIDRefFn)(void *user_data, struct UndoRefID *id_ref)
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition: BLI_assert.h:53
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
Definition: BLI_listbase.h:269
void * BLI_pophead(ListBase *listbase) ATTR_NONNULL(1)
Definition: listbase.c:221
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:336
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
Definition: BLI_listbase.h:273
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:80
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:100
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void * BLI_rfindstring(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
#define UNUSED(x)
#define ELEM(...)
#define STREQ(a, b)
#define LIKELY(x)
#define IFACE_(msgid)
#define CLOG_ERROR(clg_ref,...)
Definition: CLG_log.h:190
#define CLOG_INFO(clg_ref, level,...)
Definition: CLG_log.h:187
#define ID_IS_LINKED(_id)
Definition: DNA_ID.h:566
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
#define C
Definition: RandGen.cpp:25
void * user_data
#define GS(x)
Definition: iris.c:225
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:31
return ret
static const int steps
Definition: sky_nishita.cpp:19
CLG_LogType * type
Definition: CLG_log.h:106
Definition: DNA_ID.h:368
char name[66]
Definition: DNA_ID.h:378
void * last
Definition: DNA_listBase.h:31
void * first
Definition: DNA_listBase.h:31
Definition: BKE_main.h:121
char name[MAX_ID_NAME]
struct ID * ptr
struct UndoStep * step_active
struct UndoStep * step_active_memfile
struct UndoStep * step_init
ListBase steps
const struct UndoType * type
size_t data_size
struct UndoStep * prev
bool use_old_bmain_data
struct UndoStep * next
bool use_memfile_step
char name[64]
size_t step_size
void(* step_decode)(struct bContext *C, struct Main *bmain, UndoStep *us, eUndoStepDir dir, bool is_final)
bool(* step_encode)(struct bContext *C, struct Main *bmain, UndoStep *us)
void(* step_encode_init)(struct bContext *C, UndoStep *us)
void(* step_foreach_ID_ref)(UndoStep *us, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
const char * name
void(* step_free)(UndoStep *us)
static void undosys_id_ref_resolve(void *user_data, UndoRefID *id_ref)
Definition: undo_system.c:126
UndoType * BKE_undosys_type_append(void(*undosys_fn)(UndoType *))
Definition: undo_system.c:877
UndoStep * BKE_undosys_step_push_init_with_type(UndoStack *ustack, bContext *C, const char *name, const UndoType *ut)
Definition: undo_system.c:449
UndoStep * BKE_undosys_step_push_init(UndoStack *ustack, bContext *C, const char *name)
Definition: undo_system.c:479
bool BKE_undosys_step_undo_with_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, bool use_skip)
Definition: undo_system.c:822
bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name)
Definition: undo_system.c:354
const UndoType * BKE_UNDOSYS_TYPE_SCULPT
Definition: undo_system.c:57
bool BKE_undosys_step_redo_with_data(UndoStack *ustack, bContext *C, UndoStep *us_target)
Definition: undo_system.c:864
void BKE_undosys_stack_init_from_context(UndoStack *ustack, bContext *C)
Definition: undo_system.c:346
bool BKE_undosys_step_redo(UndoStack *ustack, bContext *C)
Definition: undo_system.c:869
static void undosys_stack_validate(UndoStack *ustack, bool expect_non_empty)
Definition: undo_system.c:234
bool BKE_undosys_step_undo(UndoStack *ustack, bContext *C)
Definition: undo_system.c:842
void BKE_undosys_stack_clear_active(UndoStack *ustack)
Definition: undo_system.c:274
#define UNDO_NESTED_CHECK_END
Definition: undo_system.c:94
static void UNUSED_FUNCTION(BKE_undosys_foreach_ID_ref(UndoStack *ustack, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data))
Definition: undo_system.c:945
static const UndoType * BKE_undosys_type_from_context(bContext *C)
Definition: undo_system.c:62
static ListBase g_undo_types
Definition: undo_system.c:60
bool BKE_undosys_step_undo_with_data(UndoStack *ustack, bContext *C, UndoStep *us_target)
Definition: undo_system.c:837
UndoStack * BKE_undosys_stack_create(void)
Definition: undo_system.c:250
static bool g_undo_callback_running
Definition: undo_system.c:86
#define UNDO_NESTED_CHECK_BEGIN
Definition: undo_system.c:88
eUndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
Definition: undo_system.c:593
eUndoStepDir BKE_undosys_step_calc_direction(const UndoStack *ustack, const UndoStep *us_target, const UndoStep *us_reference)
Definition: undo_system.c:659
UndoStep * BKE_undosys_step_same_type_prev(UndoStep *us)
Definition: undo_system.c:617
UndoStep * BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const UndoType *ut)
Definition: undo_system.c:373
static void undosys_stack_clear_all_last(UndoStack *ustack, UndoStep *us)
Definition: undo_system.c:292
const UndoType * BKE_UNDOSYS_TYPE_MEMFILE
Definition: undo_system.c:54
const UndoType * BKE_UNDOSYS_TYPE_PARTICLE
Definition: undo_system.c:56
static void undosys_stack_clear_all_first(UndoStack *ustack, UndoStep *us, UndoStep *us_exclude)
Definition: undo_system.c:306
void BKE_undosys_stack_clear(UndoStack *ustack)
Definition: undo_system.c:262
const UndoType * BKE_UNDOSYS_TYPE_TEXT
Definition: undo_system.c:58
UndoStep * BKE_undosys_step_find_by_name(UndoStack *ustack, const char *name)
Definition: undo_system.c:644
void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size_t memory_limit)
Definition: undo_system.c:383
UndoStep * BKE_undosys_stack_active_with_type(UndoStack *ustack, const UndoType *ut)
Definition: undo_system.c:364
bool BKE_undosys_step_load_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, UndoStep *us_reference, const bool use_skip)
Definition: undo_system.c:718
UndoStep * BKE_undosys_step_find_by_name_with_type(UndoStack *ustack, const char *name, const UndoType *ut)
Definition: undo_system.c:630
static void undosys_step_free_and_unlink(UndoStack *ustack, UndoStep *us)
Definition: undo_system.c:210
void BKE_undosys_step_load_from_index(UndoStack *ustack, bContext *C, const int index)
Definition: undo_system.c:812
UndoStep * BKE_undosys_step_same_type_next(UndoStep *us)
Definition: undo_system.c:604
eUndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack, bContext *C, const char *name, const UndoType *ut)
Definition: undo_system.c:491
UndoStep * BKE_undosys_step_find_by_type(UndoStack *ustack, const UndoType *ut)
Definition: undo_system.c:649
static void undosys_id_ref_store(void *UNUSED(user_data), UndoRefID *id_ref)
Definition: undo_system.c:116
void BKE_undosys_stack_destroy(UndoStack *ustack)
Definition: undo_system.c:256
#define UNDO_NESTED_ASSERT(state)
Definition: undo_system.c:87
void BKE_undosys_stack_group_end(UndoStack *ustack)
Definition: undo_system.c:925
static bool undosys_step_encode(bContext *C, Main *bmain, UndoStack *ustack, UndoStep *us)
Definition: undo_system.c:140
static CLG_LogRef LOG
Definition: undo_system.c:47
static UndoStep * undosys_step_iter_first(UndoStep *us_reference, const eUndoStepDir undo_dir)
Definition: undo_system.c:704
static bool undosys_stack_push_main(UndoStack *ustack, const char *name, struct Main *bmain)
Definition: undo_system.c:327
bool BKE_undosys_step_load_data(UndoStack *ustack, bContext *C, UndoStep *us_target)
Definition: undo_system.c:806
const UndoType * BKE_UNDOSYS_TYPE_PAINTCURVE
Definition: undo_system.c:55
void BKE_undosys_type_free_all(void)
Definition: undo_system.c:890
bool BKE_undosys_step_redo_with_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, bool use_skip)
Definition: undo_system.c:850
void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain)
Definition: undo_system.c:340
void BKE_undosys_print(UndoStack *ustack)
Definition: undo_system.c:963
static void undosys_step_decode(bContext *C, Main *bmain, UndoStack *ustack, UndoStep *us, const eUndoStepDir dir, bool is_final)
Definition: undo_system.c:165
void BKE_undosys_stack_group_begin(UndoStack *ustack)
Definition: undo_system.c:919
const UndoType * BKE_UNDOSYS_TYPE_IMAGE
Definition: undo_system.c:53
size_t memory_limit
Definition: wm_playanim.c:273