Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/undo_system.c
| Show First 20 Lines • Show All 179 Lines • ▼ Show 20 Lines | static void undosys_step_decode(bContext *C, | ||||
| Main *bmain, | Main *bmain, | ||||
| UndoStack *ustack, | UndoStack *ustack, | ||||
| UndoStep *us, | UndoStep *us, | ||||
| const eUndoStepDir dir, | const eUndoStepDir dir, | ||||
| bool is_final) | bool is_final) | ||||
| { | { | ||||
| CLOG_INFO(&LOG, 2, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name); | CLOG_INFO(&LOG, 2, "addr=%p, name='%s', type='%s'", us, us->name, us->type->name); | ||||
| BKE_undosys_print(ustack); | |||||
sergey: Debug print left by accident? | |||||
| if (us->type->step_foreach_ID_ref) { | if (us->type->step_foreach_ID_ref) { | ||||
| #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER | /* Don't use from context yet because sometimes context is fake and | ||||
| if (us->type != BKE_UNDOSYS_TYPE_MEMFILE) { | * not all members are filled in. */ | ||||
| for (UndoStep *us_iter = us->prev; us_iter; us_iter = us_iter->prev) { | us->type->step_foreach_ID_ref(us, undosys_id_ref_resolve, bmain); | ||||
| if (us_iter->type == BKE_UNDOSYS_TYPE_MEMFILE) { | |||||
| if (us_iter == ustack->step_active_memfile) { | |||||
| /* Common case, we're already using the last memfile state. */ | |||||
| } | } | ||||
| else { | |||||
| /* Load the previous memfile state so any ID's referenced in this | /* Differential steps types need special care in undo case. */ | ||||
| * undo step will be correctly resolved, see: T56163. */ | if (dir == STEP_UNDO) { | ||||
| undosys_step_decode(C, bmain, ustack, us_iter, dir, false); | /* NOTE: Because current active step is always also processed when undoing (when triggered by | ||||
| /* May have been freed on memfile read. */ | * user), this can lead to attempt to process twice a given step. This is fine, just check if | ||||
| bmain = G_MAIN; | * the affected differential step has already been un-applied, and do nothing in that case. */ | ||||
| if (us->type->flags & UNDOTYPE_DIFFERENTIAL) { | |||||
| /* Cannot undo current step if it is a differential one, instead the next step needs to be | |||||
| * un-applied if possible. */ | |||||
| BLI_assert(us->is_applied); | |||||
| BLI_assert(us->type != BKE_UNDOSYS_TYPE_MEMFILE); | |||||
| if (us->next == NULL || (us->next->type->flags & UNDOTYPE_DIFFERENTIAL) == 0 || | |||||
| !us->next->is_applied) { | |||||
| /* No next step, or next step is a stateful type and was therefore already 'un-applied', or | |||||
| * it's a differential one that was already un-applied, nothing to do. */ | |||||
| return; | |||||
| } | } | ||||
| break; | |||||
| /* The next step is also a differential one, time to un-apply it, such that current 'applied' | |||||
| * step becomes the active state. */ | |||||
| us = us->next; | |||||
| } | } | ||||
| else { | |||||
| /* Undoing a regular stateful step (which needs to be loaded). Then if the next one (i.e. | |||||
| * current active one) is an applied differential type, it has to be un-applied first. | |||||
| */ | |||||
| if (us->next != NULL && (us->next->type->flags & UNDOTYPE_DIFFERENTIAL) != 0 && | |||||
| us->next->is_applied) { | |||||
| us->next->type->step_decode(C, bmain, us->next, dir, false); | |||||
| } | } | ||||
| } | } | ||||
| #endif | |||||
| /* Don't use from context yet because sometimes context is fake and | |||||
| * not all members are filled in. */ | |||||
| us->type->step_foreach_ID_ref(us, undosys_id_ref_resolve, bmain); | |||||
| } | } | ||||
| UNDO_NESTED_CHECK_BEGIN; | UNDO_NESTED_CHECK_BEGIN; | ||||
| us->type->step_decode(C, bmain, us, dir, is_final); | us->type->step_decode(C, bmain, us, dir, is_final); | ||||
| UNDO_NESTED_CHECK_END; | UNDO_NESTED_CHECK_END; | ||||
| #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER | #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER | ||||
| if (us->type == BKE_UNDOSYS_TYPE_MEMFILE) { | if (us->type == BKE_UNDOSYS_TYPE_MEMFILE) { | ||||
| ▲ Show 20 Lines • Show All 496 Lines • ▼ Show 20 Lines | if (us_iter == us_target) { | ||||
| return STEP_UNDO; | return STEP_UNDO; | ||||
| } | } | ||||
| } | } | ||||
| BLI_assert(!"Target undo step not found, this should not happen and may indicate an undo stack corruption"); | BLI_assert(!"Target undo step not found, this should not happen and may indicate an undo stack corruption"); | ||||
| return STEP_INVALID; | return STEP_INVALID; | ||||
| } | } | ||||
| /* Find the memfile undo step which matches the target undo step, i.e. the one corresponding to the | |||||
| * main data-base status (list of existing IDs), if the current (already loaded) one is not usable. | |||||
| */ | |||||
| static UndoStep *undosys_valid_active_target_memfile_step_search(UndoStack *ustack, | |||||
| UndoStep *us_target, | |||||
| UndoStep *us_reference) | |||||
| { | |||||
| /* This special process is only needed if some of the steps to be undone are non-memfile ones and | |||||
| * require valid ID data (pointers). */ | |||||
| bool need_memfile_restore = false; | |||||
| for (UndoStep *us_iter = us_reference; us_iter != us_target->prev; us_iter = us_iter->prev) { | |||||
| if (us_iter->type != BKE_UNDOSYS_TYPE_MEMFILE && us_iter->type->step_foreach_ID_ref != NULL) { | |||||
| need_memfile_restore = true; | |||||
| } | |||||
| } | |||||
| if (!need_memfile_restore) { | |||||
| return NULL; | |||||
| } | |||||
| /* Find the last memfile step before the target one, if it is not the active memfile one, then it | |||||
| * needs to be reloaded. | |||||
| * | |||||
| * Note: If the undo stack is in consistent valid state, the only case where this special process | |||||
| * may not be needed is if there is only non memfile steps between current reference and target | |||||
| * ones (i.e. for example all steps are from Edit mode). Otherwise, steps between active and | |||||
| * target ones **will not be processed anymore** (since others will be redone from found memfile | |||||
| * to target one instead), so no need to check whether we have valid memfile/main state for those | |||||
| * anymore. Hence the start of this loop on `us_target`. */ | |||||
| for (UndoStep *us_iter = us_target; us_iter != NULL; us_iter = us_iter->prev) { | |||||
| if (us_iter->type == BKE_UNDOSYS_TYPE_MEMFILE) { | |||||
| if (us_iter == ustack->step_active_memfile) { | |||||
| /* Common case, the last memfile state is already being used, nothing else to do. */ | |||||
| return NULL; | |||||
| } | |||||
| else { | |||||
| /* This memfile undo step needs to be loaded, and then from there redo to the target step. | |||||
| */ | |||||
| return us_iter; | |||||
| } | |||||
| } | |||||
| } | |||||
| BLI_assert(!"Undo stack is invalid or code above is broken."); | |||||
| return NULL; | |||||
| } | |||||
| /** | /** | ||||
| * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. | * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. | ||||
| * | * | ||||
| * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` will become the | * \note Unless `us_target` is a 'skipped' one and `use_skip` is true, `us_target` will become the | ||||
| * active step. | * active step. | ||||
| * | * | ||||
| * \note In case `use_skip` is true, the final target will always be **beyond** the given one (if | * \note In case `use_skip` is true, the final target will always be **beyond** the given one (if | ||||
| * the given one has to be skipped). | * the given one has to be skipped). | ||||
| Show All 20 Lines | if (us_reference == NULL) { | ||||
| us_reference = ustack->step_active; | us_reference = ustack->step_active; | ||||
| } | } | ||||
| if (us_reference == NULL) { | if (us_reference == NULL) { | ||||
| CLOG_ERROR(&LOG, "could not find a valid initial active target step as reference"); | CLOG_ERROR(&LOG, "could not find a valid initial active target step as reference"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| /* This considers we are in undo case if both `us_target` and `us_reference` are the same. */ | /* This considers we are in undo case if both `us_target` and `us_reference` are the same. */ | ||||
| const eUndoStepDir undo_dir = BKE_undosys_step_calc_direction(ustack, us_target, us_reference); | eUndoStepDir undo_dir = BKE_undosys_step_calc_direction(ustack, us_target, us_reference); | ||||
| BLI_assert(undo_dir != STEP_INVALID); | BLI_assert(undo_dir != STEP_INVALID); | ||||
| /* This will be the active step once the undo process is complete. | /* This will be the active step once the undo process is complete. | ||||
| * | * | ||||
| * In case we do skip 'skipped' steps, the final active step may be several steps backward from | * In case we do skip 'skipped' steps, the final active step may be several steps backward from | ||||
| * the one passed as parameter. */ | * the one passed as parameter. */ | ||||
| UndoStep *us_target_active = us_target; | UndoStep *us_target_active = us_target; | ||||
| if (use_skip) { | if (use_skip) { | ||||
| Show All 9 Lines | bool BKE_undosys_step_load_data_ex(UndoStack *ustack, | ||||
| CLOG_INFO(&LOG, | CLOG_INFO(&LOG, | ||||
| 1, | 1, | ||||
| "addr=%p, name='%s', type='%s', undo_dir=%d", | "addr=%p, name='%s', type='%s', undo_dir=%d", | ||||
| us_target, | us_target, | ||||
| us_target->name, | us_target->name, | ||||
| us_target->type->name, | us_target->type->name, | ||||
| undo_dir); | undo_dir); | ||||
| #ifdef WITH_GLOBAL_UNDO_CORRECT_ORDER | |||||
| /* Another issue: some operations may add, remove or change addresses of IDs. Those operations | |||||
| * should always generate a 'memfile' undo step (which may be skipped, i.e. invisible from user | |||||
| * PoV). But this step will reflect the status *after* changes to Main data. This is fine in case | |||||
| * of redo, but not in case of undo, since non-memfile undos (like edit mode ones e.g.) may hold | |||||
| * references to IDs that would not exist anymore. | |||||
| * | |||||
| * To address this issue, we need to undo until the previous memfile step, then redo towards | |||||
| * target non-memfile step. | |||||
| * | |||||
| * NOTE: This is the only case where we break the 'fully relative' handling of our undostack | |||||
| * currently, since loading that memfile undo step will be done by skipping all intermediary | |||||
| * non-memfile steps. | |||||
| * | |||||
| * NOTE: This could systematically be done when undoing or redoing a lot of steps at once, mixing | |||||
| * memfile and non-memfile ones. This should give a significant speedup in those cases. However, | |||||
| * this is not a very common use case, so not a priority currently. */ | |||||
| if (undo_dir == STEP_UNDO) { | |||||
| UndoStep *us_memfile_target_active = undosys_valid_active_target_memfile_step_search( | |||||
| ustack, us_target_active, us_reference); | |||||
| if (us_memfile_target_active != NULL) { | |||||
| CLOG_INFO(&LOG, | |||||
| 2, | |||||
| "Undo has to reload a previous memfile step to ensure current main data-base is " | |||||
| "valid: addr=%p, name='%s', type='%s'", | |||||
| us_memfile_target_active, | |||||
| us_memfile_target_active->name, | |||||
| us_memfile_target_active->type->name); | |||||
| /* NOTE: We could directly load the target memfile step here, disabling the 'relative' | |||||
| * behavior of re-using existing data. But in practice this would generally be more | |||||
| * expensive. */ | |||||
| for (UndoStep *us_iter = us_reference; us_iter != NULL; us_iter = us_iter->prev) { | |||||
| BLI_assert(us_iter != NULL); | |||||
| if (us_iter->type != BKE_UNDOSYS_TYPE_MEMFILE) { | |||||
| continue; | |||||
| } | |||||
| undosys_step_decode(C, G_MAIN, ustack, us_iter, undo_dir, false); | |||||
| ustack->step_active = us_iter; | |||||
| us_reference = us_iter; | |||||
| } | |||||
| undo_dir = STEP_REDO; | |||||
| /* NOTE: Even though the direction is switched, there is no need to re-calculate | |||||
| * `us_target_active` (to take into account skipped ones), since logically this whole | |||||
| * operation remains an undo. */ | |||||
| /* If loaded step is final target one, we are done here. */ | |||||
| if (us_reference == us_target_active) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif | |||||
| /* Undo/Redo steps until we reach given target step (or beyond if it has to be skipped), from | /* Undo/Redo steps until we reach given target step (or beyond if it has to be skipped), from | ||||
| * given reference step. | * given reference step. | ||||
| * | * | ||||
| * NOTE: Unlike with redo case, where we can expect current active step to fully reflect current | * NOTE: Unlike with redo case, where we can expect current active step to fully reflect current | ||||
| * data status, in undo case we also do reload the active step. | * data status, in undo case we also do reload the active step. | ||||
| * FIXME: this feels weak, and should probably not be actually needed? Or should also be done in | * FIXME: this feels weak, and should probably not be actually needed? Or should also be done in | ||||
| * redo case? */ | * redo case? */ | ||||
| bool is_processing_extra_skipped_steps = false; | bool is_processing_extra_skipped_steps = false; | ||||
| ▲ Show 20 Lines • Show All 256 Lines • Show Last 20 Lines | |||||
Debug print left by accident?