Improve timeline undo performance - #1897
Open
ddennedy wants to merge 13 commits into
Open
Conversation
- Rework timeline undo/redo to snapshot and restore whole affected track playlists instead of diffing clips individually. - Preserve clip UUIDs across XML round-trips using a temporary serialized property so restored clips keep their identities. - Update timeline command call sites to pass known track scope, reducing unnecessary full-timeline scanning for common edits. - Add a small History undo view wrapper that defers repeated track-filter and background-duration adjustment work during multi-step History dock jumps.
Reintroduce fine-grained per-clip undo/redo alongside the whole-track snapshot restore so single-clip edits no longer rebuild an entire track. UndoHelper regains OptimizationHints (NoHints/SkipXML/RestoreTracks): the fine-grained path diffs clips by uuid and replays only what changed, sourcing restored content from a lazily-parsed shadow of each track's before-state XML; RestoreTracks keeps the whole-track rebuild for structurally complex edits. Use RestoreTracks for insert, overwrite, lift, remove, move, split, align, add-transition, remove-track, and ripple trims; NoHints/SkipXML for trims and other localized edits.
Contributor
There was a problem hiding this comment.
Pull request overview
Optimizes timeline undo/redo by limiting snapshots to affected tracks or clips and batching History dock operations.
Changes:
- Adds fine-grained snapshot and restoration strategies.
- Batches multi-step History operations and defers expensive refreshes.
- Adds progress reporting and XML-call instrumentation.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/shotcut_mlt_properties.h |
Adds temporary serializable UUID property. |
src/models/multitrackmodel.h |
Declares bulk-update state and API. |
src/models/multitrackmodel.cpp |
Implements deferred updates and refreshes. |
src/mltcontroller.h |
Exposes XML-call instrumentation. |
src/mltcontroller.cpp |
Counts XML serialization calls. |
src/mainwindow.cpp |
Installs the specialized History view. |
src/docks/timelinedock.h |
Adds deferred selection state. |
src/docks/timelinedock.cpp |
Defers validation and scopes trim snapshots. |
src/docks/historyundoview.h |
Declares bulk-aware History view. |
src/docks/historyundoview.cpp |
Brackets History interactions as bulk updates. |
src/dialogs/longuitask.h |
Increases progress-dialog responsiveness. |
src/commands/undohelper.h |
Defines scoped snapshot strategies. |
src/commands/undohelper.cpp |
Implements fine-grained and whole-track restoration. |
src/commands/timelinecommands.h |
Adds move-command track scoping. |
src/commands/timelinecommands.cpp |
Applies scoped undo capture across commands. |
src/CMakeLists.txt |
Builds the new History view. |
Suppressed comments (1)
src/commands/timelinecommands.cpp:1153
TrimClipOutCommandalso never callssetText()on its helper. If its fine-grained undo falls back torestoreAffectedTracks(), the progress dialog therefore displays the incomplete labelUndo. Propagate the command text when creating or injecting the trim helper.
m_undoHelper.reset(new UndoHelper(m_model));
if (!m_ripple)
m_undoHelper->setHints(UndoHelper::SkipXML);
m_undoHelper->recordBeforeState(m_rippleAllTracks ? QSet<int>() : QSet<int>{m_trackIndex});
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/docks/historyundoview.cpp:45
- Every key press in the History view enters a bulk update, including keys such as Shift or an unhandled character that do not change the undo-stack index.
endBulkUpdate()nevertheless always callsMLT.refreshConsumer()and emitsbulkUpdateFinished(), so these no-op keys trigger an expensive timeline refresh. Restrict bulk updates to index-changing navigation, or track whether the stack index changed and skip the final flush when no command was replayed.
m_model->beginBulkUpdate();
QUndoView::keyPressEvent(event);
m_model->endBulkUpdate();
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request significantly improves the undo/redo system for timeline editing commands by making undo operations more efficient and context-aware. The changes focus on capturing only the affected tracks or clips for undo, rather than restoring entire tracks unnecessarily. This results in faster, more reliable undos and better user experience. Additionally, command descriptions are now copied to
UndoHelperto show on a progress dialogs when things are slow. Things are usually slow on big projects especially when Ripple All Tracks is on.Undo/Redo System Improvements
Most timeline commands now call
m_undoHelper.recordBeforeState()with a set of affected track indices, instead of always restoring whole tracks. This enables more efficient, fine-grained undo operations.For commands that can affect multiple tracks (such as ripple edits or move/align commands), logic was added to determine the precise scope of affected tracks and fall back to whole-track restores only when necessary (e.g., ripple-all-tracks is enabled).
Here's the complete picture of every timeline command's undo strategy on the branch now.
Fine-grained (fast per-clip undo — no
RestoreTracks)Use
UndoHelperwithNoHints/SkipXML:AppendCommandOverwriteCommandLiftCommandRemoveCommandMergeCommandSplitCommandInsertCommandAddTransitionCommandTrimClipInCommandTrimClipOutCommandUpdateCommandDetachAudioCommandReplaceCommandStill
RestoreTracks(whole-track rebuild)These are the genuinely track-wide or cross-track cases where fine-grained doesn't apply:
MoveClipCommandRemoveTrackCommandAlignClipsCommandInsertCommandRemoveCommandAddTransitionCommandTrimClipInCommandDon't use
UndoHelperat all (own inverse logic)GroupCommand,UngroupCommand,NameTrackCommand,MuteTrackCommand,HideTrackCommand,CompositeTrackCommand,LockTrackCommand,FadeInCommand,FadeOutCommand,TrimTransitionInCommand,TrimTransitionOutCommand,ResizeTransitionCommand,AddTransitionByTrimInCommand,AddTransitionByTrimOutCommand,RemoveTransitionByTrimInCommand,RemoveTransitionByTrimOutCommand,AddTrackCommand,InsertTrackCommand,MoveTrackCommand,ChangeBlendModeCommand,ChangeTransitionPropertyCommand,ApplyFiltersCommand,ChangeGainCommand.Progress Dialog
m_undoHelper.setText(text())to show descriptions in the progress dialogs.Improved Undo Data Capture
Refactoring for Move/Align Commands
MoveClipCommandandAlignClipsCommandnow defer the call torecordBeforeState()until the redo step, after the set of affected clips/tracks is known, further optimizing the undo scope.