From 6437490137f0998d59bb300aec0ff9300a4c1bd6 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sun, 27 Jul 2025 01:03:06 -0500 Subject: [PATCH] Make consecutive recorded frames while unpaused be a single undo action. --- .../movie/tasproj/TasMovie.History.cs | 17 +++++++++++++++++ src/BizHawk.Client.EmuHawk/MainForm.cs | 1 + .../tools/TAStudio/TAStudio.IToolForm.cs | 17 ++++++++++++++++- .../tools/TAStudio/TAStudio.cs | 7 +++++++ .../tools/ToolFormBase.cs | 2 ++ src/BizHawk.Client.EmuHawk/tools/ToolManager.cs | 11 +++++++++++ 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs index ba48adf384..c56955a94e 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs @@ -28,6 +28,12 @@ namespace BizHawk.Client.Common /// If not already recording a batch, does nothing. /// void EndBatch(); + + /// + /// Combine the last two undo actions, making them part of one batch action. + /// + void MergeLastActions(); + /// /// Undoes the most recent action batch, if any exist. /// @@ -178,6 +184,17 @@ namespace BizHawk.Client.Common } } + public void MergeLastActions() + { + Debug.Assert(UndoIndex + 1 == _history.Count, "Don't merge the last actions if they aren't the last actions."); + Debug.Assert(_history.Count > 1, "Don't merge when there aren't actions to merge."); + + _history[_history.Count - 2].AddRange(_history[_history.Count - 1]); + _history.RemoveAt(_history.Count - 1); + Names.RemoveAt(Names.Count - 1); + UndoIndex--; + } + public void Undo() { if (UndoIndex == -1) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 6a8555ef85..0389598938 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -1041,6 +1041,7 @@ namespace BizHawk.Client.EmuHawk InitializeFpsData(); } + if (value != _emulatorPaused) Tools.OnPauseToggle(value); _emulatorPaused = value; } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 2ca7cab249..a5e13e9408 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -21,6 +21,8 @@ namespace BizHawk.Client.EmuHawk private int _lastRefresh; private bool _doPause; + private bool _extended; + private bool _extendNeedsMerge; private void UpdateProgressBar() { @@ -55,7 +57,8 @@ namespace BizHawk.Client.EmuHawk protected override void UpdateBefore() { - if (CurrentTasMovie.IsAtEnd() && !CurrentTasMovie.IsRecording()) + _extended = CurrentTasMovie.IsAtEnd() && !CurrentTasMovie.IsRecording(); + if (_extended) { CurrentTasMovie.RecordFrame(CurrentTasMovie.Emulator.Frame, MovieSession.StickySource); } @@ -108,6 +111,18 @@ namespace BizHawk.Client.EmuHawk protected override void FastUpdateAfter() { + // Recording multiple frames, or auto-extending the movie, while unpaused should count as a single undo action. + // And do this before stopping the seek, which could puase. + if (!MainForm.EmulatorPaused && (CurrentTasMovie.IsRecording() || _extended)) + { + if (_extendNeedsMerge) CurrentTasMovie.ChangeLog.MergeLastActions(); + _extendNeedsMerge = true; + } + else + { + _extendNeedsMerge = false; + } + if (_seekingTo != -1 && Emulator.Frame >= _seekingTo) { StopSeeking(); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index a3b743d8bd..0771265661 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -890,6 +890,13 @@ namespace BizHawk.Client.EmuHawk MainForm.TogglePause(); } + public override void OnPauseToggle(bool newPauseState) + { + // Consecutively recorded frames are merged into one undo action, until we pause. + // Then a new undo action should be used. + if (newPauseState) _extendNeedsMerge = false; + } + private void SetSplicer() { // TODO: columns selected? diff --git a/src/BizHawk.Client.EmuHawk/tools/ToolFormBase.cs b/src/BizHawk.Client.EmuHawk/tools/ToolFormBase.cs index cb1f8e64b8..176a21d712 100644 --- a/src/BizHawk.Client.EmuHawk/tools/ToolFormBase.cs +++ b/src/BizHawk.Client.EmuHawk/tools/ToolFormBase.cs @@ -136,5 +136,7 @@ namespace BizHawk.Client.EmuHawk } public virtual void HandleHotkeyUpdate() { } + + public virtual void OnPauseToggle(bool newPauseState) { } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs index 1b5a361241..bec31d413c 100644 --- a/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/src/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -804,6 +804,17 @@ namespace BizHawk.Client.EmuHawk } } + public void OnPauseToggle(bool newPauseState) + { + foreach (var tool in _tools) + { + if (tool.IsActive && tool is ToolFormBase toolForm) + { + toolForm.OnPauseToggle(newPauseState); + } + } + } + private static readonly IList PossibleToolTypeNames = EmuHawk.ReflectionCache.Types.Select(t => t.AssemblyQualifiedName).ToList(); public bool IsAvailable(Type tool)