From 651b8fb14eb6ee5cbfa98bffe80a966a0753b14e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 19 Mar 2018 14:17:59 +0100 Subject: Undo: unified undo system w/ linear history - Use a single undo history for all operations. - UndoType's are registered and poll the context to check if they should be used when performing an undo push. - Mode switching is used to ensure the state is correct before undo data is restored. - Some undo types accumulate changes (image & text editing) others store the state multiple times (with de-duplication). This is supported by checking UndoStack.mode `ACCUMULATE` / `STORE`. - Each undo step stores ID datablocks they use with utilities to help manage restoring correct ID's. Needed since global undo is now mixed with other modes undo. - Currently performs each undo step when going up/down history Previously this wasn't done, making history fail in some cases. This can be optimized to skip some combinations of undo steps. grease-pencil is an exception which has not been updated since it integrates undo into the draw-session. See D3113 --- source/blender/blenkernel/intern/blender_undo.c | 303 ++---------------------- 1 file changed, 15 insertions(+), 288 deletions(-) (limited to 'source/blender/blenkernel/intern/blender_undo.c') diff --git a/source/blender/blenkernel/intern/blender_undo.c b/source/blender/blenkernel/intern/blender_undo.c index 4efca5bcc41..6b31c8c96f9 100644 --- a/source/blender/blenkernel/intern/blender_undo.c +++ b/source/blender/blenkernel/intern/blender_undo.c @@ -42,28 +42,19 @@ #include "DNA_scene_types.h" -#include "BLI_fileops.h" -#include "BLI_listbase.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_utildefines.h" -#include "IMB_imbuf.h" -#include "IMB_moviecache.h" - #include "BKE_blender_undo.h" /* own include */ #include "BKE_blendfile.h" #include "BKE_appdir.h" -#include "BKE_brush.h" #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_global.h" -#include "BKE_image.h" #include "BKE_main.h" -#include "RE_pipeline.h" #include "BLO_undofile.h" -#include "BLO_readfile.h" #include "BLO_writefile.h" /* -------------------------------------------------------------------- */ @@ -73,46 +64,21 @@ #define UNDO_DISK 0 -typedef struct UndoElem { - struct UndoElem *next, *prev; - /* Only for 'UNDO_DISK' */ - char filename[FILE_MAX]; - char name[BKE_UNDO_STR_MAX]; - MemFile memfile; - size_t undo_size; -} UndoElem; - -static ListBase undobase = {NULL, NULL}; -static UndoElem *curundo = NULL; - -/** - * Avoid bad-level call to #WM_jobs_kill_all_except() - */ -static void (*undo_wm_job_kill_callback)(struct bContext *C) = NULL; - -void BKE_undo_callback_wm_kill_jobs_set(void (*callback)(struct bContext *C)) -{ - undo_wm_job_kill_callback = callback; -} - -static int read_undosave(bContext *C, UndoElem *uel) +bool BKE_memfile_undo_decode(MemFileUndoData *mfu, bContext *C) { char mainstr[sizeof(G.main->name)]; int success = 0, fileflags; - /* This is needed so undoing/redoing doesn't crash with threaded previews going */ - undo_wm_job_kill_callback(C); - BLI_strncpy(mainstr, G.main->name, sizeof(mainstr)); /* temporal store */ fileflags = G.fileflags; G.fileflags |= G_FILE_NO_UI; if (UNDO_DISK) { - success = (BKE_blendfile_read(C, uel->filename, NULL, 0) != BKE_BLENDFILE_READ_FAIL); + success = (BKE_blendfile_read(C, mfu->filename, NULL, 0) != BKE_BLENDFILE_READ_FAIL); } else { - success = BKE_blendfile_read_from_memfile(C, &uel->memfile, NULL, 0); + success = BKE_blendfile_read_from_memfile(C, &mfu->memfile, NULL, 0); } /* restore */ @@ -127,51 +93,9 @@ static int read_undosave(bContext *C, UndoElem *uel) return success; } -/* name can be a dynamic string */ -void BKE_undo_write(bContext *C, const char *name) +MemFileUndoData *BKE_memfile_undo_encode(Main *bmain, MemFileUndoData *mfu_prev) { - int nr /*, success */ /* UNUSED */; - UndoElem *uel; - - if ((U.uiflag & USER_GLOBALUNDO) == 0) { - return; - } - - if (U.undosteps == 0) { - return; - } - - /* remove all undos after (also when curundo == NULL) */ - while (undobase.last != curundo) { - uel = undobase.last; - BLI_remlink(&undobase, uel); - BLO_memfile_free(&uel->memfile); - MEM_freeN(uel); - } - - /* make new */ - curundo = uel = MEM_callocN(sizeof(UndoElem), "undo file"); - BLI_strncpy(uel->name, name, sizeof(uel->name)); - BLI_addtail(&undobase, uel); - - /* and limit amount to the maximum */ - nr = 0; - uel = undobase.last; - while (uel) { - nr++; - if (nr == U.undosteps) break; - uel = uel->prev; - } - if (uel) { - while (undobase.first != uel) { - UndoElem *first = undobase.first; - BLI_remlink(&undobase, first); - /* the merge is because of compression */ - BLO_memfile_merge(&first->memfile, &first->next->memfile); - MEM_freeN(first); - } - } - + MemFileUndoData *mfu = MEM_callocN(sizeof(MemFileUndoData), __func__); /* disk save version */ if (UNDO_DISK) { @@ -187,222 +111,25 @@ void BKE_undo_write(bContext *C, const char *name) BLI_snprintf(numstr, sizeof(numstr), "%d.blend", counter); BLI_make_file_string("/", filename, BKE_tempdir_session(), numstr); - /* success = */ /* UNUSED */ BLO_write_file(CTX_data_main(C), filename, fileflags, NULL, NULL); + /* success = */ /* UNUSED */ BLO_write_file(bmain, filename, fileflags, NULL, NULL); - BLI_strncpy(curundo->filename, filename, sizeof(curundo->filename)); + BLI_strncpy(mfu->filename, filename, sizeof(mfu->filename)); } else { - MemFile *prevfile = NULL; - - if (curundo->prev) prevfile = &(curundo->prev->memfile); - - /* success = */ /* UNUSED */ BLO_write_file_mem(CTX_data_main(C), prevfile, &curundo->memfile, G.fileflags); - curundo->undo_size = curundo->memfile.size; - } - - if (U.undomemory != 0) { - size_t maxmem, totmem; - /* limit to maximum memory (afterwards, we can't know in advance) */ - totmem = 0; - maxmem = ((size_t)U.undomemory) * 1024 * 1024; - - /* keep at least two (original + other) */ - uel = undobase.last; - while (uel && uel->prev) { - totmem += uel->undo_size; - if (totmem > maxmem) break; - uel = uel->prev; - } - - if (uel) { - if (uel->prev && uel->prev->prev) - uel = uel->prev; - - while (undobase.first != uel) { - UndoElem *first = undobase.first; - BLI_remlink(&undobase, first); - /* the merge is because of compression */ - BLO_memfile_merge(&first->memfile, &first->next->memfile); - MEM_freeN(first); - } - } - } -} - -/* 1 = an undo, -1 is a redo. we have to make sure 'curundo' remains at current situation */ -void BKE_undo_step(bContext *C, int step) -{ - - if (step == 0) { - read_undosave(C, curundo); - } - else if (step == 1) { - /* curundo should never be NULL, after restart or load file it should call undo_save */ - if (curundo == NULL || curundo->prev == NULL) { - // XXX error("No undo available"); - } - else { - if (G.debug & G_DEBUG) printf("undo %s\n", curundo->name); - curundo = curundo->prev; - read_undosave(C, curundo); - } - } - else { - /* curundo has to remain current situation! */ - - if (curundo == NULL || curundo->next == NULL) { - // XXX error("No redo available"); - } - else { - read_undosave(C, curundo->next); - curundo = curundo->next; - if (G.debug & G_DEBUG) printf("redo %s\n", curundo->name); - } - } -} - -void BKE_undo_reset(void) -{ - UndoElem *uel; - - uel = undobase.first; - while (uel) { - BLO_memfile_free(&uel->memfile); - uel = uel->next; - } - - BLI_freelistN(&undobase); - curundo = NULL; -} - -/* based on index nr it does a restore */ -void BKE_undo_number(bContext *C, int nr) -{ - curundo = BLI_findlink(&undobase, nr); - BKE_undo_step(C, 0); -} - -/* go back to the last occurance of name in stack */ -void BKE_undo_name(bContext *C, const char *name) -{ - UndoElem *uel = BLI_rfindstring(&undobase, name, offsetof(UndoElem, name)); - - if (uel && uel->prev) { - curundo = uel->prev; - BKE_undo_step(C, 0); - } -} - -/* name optional */ -bool BKE_undo_is_valid(const char *name) -{ - if (name) { - UndoElem *uel = BLI_rfindstring(&undobase, name, offsetof(UndoElem, name)); - return uel && uel->prev; + MemFile *prevfile = (mfu_prev) ? &(mfu_prev->memfile) : NULL; + /* success = */ /* UNUSED */ BLO_write_file_mem(bmain, prevfile, &mfu->memfile, G.fileflags); + mfu->undo_size = mfu->memfile.size; } - return undobase.last != undobase.first; -} - -/* get name of undo item, return null if no item with this index */ -/* if active pointer, set it to 1 if true */ -const char *BKE_undo_get_name(int nr, bool *r_active) -{ - UndoElem *uel = BLI_findlink(&undobase, nr); + bmain->is_memfile_undo_written = true; - if (r_active) *r_active = false; - - if (uel) { - if (r_active && (uel == curundo)) { - *r_active = true; - } - return uel->name; - } - return NULL; + return mfu; } -/* return the name of the last item */ -const char *BKE_undo_get_name_last(void) +void BKE_memfile_undo_free(MemFileUndoData *mfu) { - UndoElem *uel = undobase.last; - return (uel ? uel->name : NULL); -} - -/** - * Saves .blend using undo buffer. - * - * \return success. - */ -bool BKE_undo_save_file(const char *filename) -{ - UndoElem *uel; - MemFileChunk *chunk; - int file, oflags; - - if ((U.uiflag & USER_GLOBALUNDO) == 0) { - return false; - } - - uel = curundo; - if (uel == NULL) { - fprintf(stderr, "No undo buffer to save recovery file\n"); - return false; - } - - /* note: This is currently used for autosave and 'quit.blend', where _not_ following symlinks is OK, - * however if this is ever executed explicitly by the user, we may want to allow writing to symlinks. - */ - - oflags = O_BINARY | O_WRONLY | O_CREAT | O_TRUNC; -#ifdef O_NOFOLLOW - /* use O_NOFOLLOW to avoid writing to a symlink - use 'O_EXCL' (CVE-2008-1103) */ - oflags |= O_NOFOLLOW; -#else - /* TODO(sergey): How to deal with symlinks on windows? */ -# ifndef _MSC_VER -# warning "Symbolic links will be followed on undo save, possibly causing CVE-2008-1103" -# endif -#endif - file = BLI_open(filename, oflags, 0666); - - if (file == -1) { - fprintf(stderr, "Unable to save '%s': %s\n", - filename, errno ? strerror(errno) : "Unknown error opening file"); - return false; - } - - for (chunk = uel->memfile.chunks.first; chunk; chunk = chunk->next) { - if (write(file, chunk->buf, chunk->size) != chunk->size) { - break; - } - } - - close(file); - - if (chunk) { - fprintf(stderr, "Unable to save '%s': %s\n", - filename, errno ? strerror(errno) : "Unknown error writing file"); - return false; - } - return true; -} - -/* sets curscene */ -Main *BKE_undo_get_main(Scene **r_scene) -{ - Main *mainp = NULL; - BlendFileData *bfd = BLO_read_from_memfile(G.main, G.main->name, &curundo->memfile, NULL, BLO_READ_SKIP_NONE); - - if (bfd) { - mainp = bfd->main; - if (r_scene) { - *r_scene = bfd->curscene; - } - - MEM_freeN(bfd); - } - - return mainp; + BLO_memfile_free(&mfu->memfile); + MEM_freeN(mfu); } /** \} */ -- cgit v1.2.3