From 6c48f7c7fd60877c5cd10220106673326a168034 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Thu, 5 Jun 2025 00:51:16 -0500 Subject: [PATCH 01/29] rename variable --- src/BizHawk.Client.EmuHawk/MainForm.cs | 2 +- .../tools/TAStudio/TAStudio.ListView.cs | 8 ++++---- .../tools/TAStudio/TAStudio.Navigation.cs | 2 +- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 89eb30349b..d500faaa8f 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3260,7 +3260,7 @@ namespace BizHawk.Client.EmuHawk else PauseOnFrame = null; } else if (Tools.IsLoaded() - && Tools.TAStudio.LastPositionFrame == Emulator.Frame + && Tools.TAStudio.RestorePositionFrame == Emulator.Frame && ((ITasMovie) MovieSession.Movie)[Emulator.Frame].Lagged is null) { // haven't yet greenzoned the frame, hence it's after editing diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 770481fe4c..611e4196d2 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -171,7 +171,7 @@ namespace BizHawk.Client.EmuHawk ? TasView.HorizontalOrientation ? ts_v_arrow_green_blue : ts_h_arrow_green_blue : TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue; } - else if (index == LastPositionFrame) + else if (index == RestorePositionFrame) { bitmap = TasView.HorizontalOrientation ? ts_v_arrow_green : @@ -523,10 +523,10 @@ namespace BizHawk.Client.EmuHawk { if (MainForm.EmulatorPaused) { - var record = CurrentTasMovie[LastPositionFrame]; - if (!record.Lagged.HasValue && LastPositionFrame > Emulator.Frame) + var record = CurrentTasMovie[RestorePositionFrame]; + if (!record.Lagged.HasValue && RestorePositionFrame > Emulator.Frame) { - StartSeeking(LastPositionFrame, true); + StartSeeking(RestorePositionFrame, true); return; } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 56ada6ce1a..22571e42d3 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -16,7 +16,7 @@ namespace BizHawk.Client.EmuHawk if ((MainForm.EmulatorPaused || !MainForm.IsSeeking) && !CurrentTasMovie.LastPositionStable) { - LastPositionFrame = Emulator.Frame; + RestorePositionFrame = Emulator.Frame; CurrentTasMovie.LastPositionStable = true; // until new frame is emulated } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 93519700d6..2111e792be 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -50,7 +50,7 @@ namespace BizHawk.Client.EmuHawk /// Gets a value that separates "restore last position" logic from seeking caused by navigation. /// TASEditor never kills LastPositionFrame, and it only pauses on it, if it hasn't been greenzoned beforehand and middle mouse button was pressed. /// - public int LastPositionFrame { get; private set; } + public int RestorePositionFrame { get; private set; } [ConfigPersist] public TAStudioSettings Settings { get; set; } = new TAStudioSettings(); @@ -162,7 +162,7 @@ namespace BizHawk.Client.EmuHawk TasView.QueryItemIcon += TasView_QueryItemIcon; TasView.QueryFrameLag += TasView_QueryFrameLag; TasView.PointedCellChanged += TasView_PointedCellChanged; - LastPositionFrame = -1; + RestorePositionFrame = -1; TasView.MouseLeave += TAStudio_MouseLeave; TasView.CellHovered += (_, e) => @@ -828,11 +828,11 @@ namespace BizHawk.Client.EmuHawk public void DoAutoRestore() { - if (Settings.AutoRestoreLastPosition && LastPositionFrame != -1) + if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) { - if (LastPositionFrame > Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek + if (RestorePositionFrame > Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek { - StartSeeking(LastPositionFrame); + StartSeeking(RestorePositionFrame); } } else From fe9f876d5574313268f565405056440c712d8e77 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 4 Jun 2025 17:33:54 -0500 Subject: [PATCH 02/29] move safety check inside StartSeeking, instead of making caller responsible --- .../tools/TAStudio/TAStudio.ListView.cs | 4 +-- .../tools/TAStudio/TAStudio.cs | 27 +++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 611e4196d2..86bb32c1fe 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -79,7 +79,7 @@ namespace BizHawk.Client.EmuHawk private void StartSeeking(int? frame, bool fromMiddleClick = false) { - if (!frame.HasValue) + if (!frame.HasValue || frame <= Emulator.Frame) { return; } @@ -524,7 +524,7 @@ namespace BizHawk.Client.EmuHawk if (MainForm.EmulatorPaused) { var record = CurrentTasMovie[RestorePositionFrame]; - if (!record.Lagged.HasValue && RestorePositionFrame > Emulator.Frame) + if (record.Lagged is null) { StartSeeking(RestorePositionFrame, true); return; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 2111e792be..903b8cfa94 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -830,10 +830,7 @@ namespace BizHawk.Client.EmuHawk { if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) { - if (RestorePositionFrame > Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek - { - StartSeeking(RestorePositionFrame); - } + StartSeeking(RestorePositionFrame); } else { @@ -899,20 +896,16 @@ namespace BizHawk.Client.EmuHawk // now the next section won't happen since we're at the right spot } - // frame == Emulator.Frame when frame == 0 - if (frame > Emulator.Frame) + // make seek frame keep up with emulation on fast scrolls + if (MainForm.EmulatorPaused || MainForm.IsSeeking || fromRewinding || WasRecording) { - // make seek frame keep up with emulation on fast scrolls - if (MainForm.EmulatorPaused || MainForm.IsSeeking || fromRewinding || WasRecording) - { - StartSeeking(frame); - } - else - { - // GUI users may want to be protected from clobbering their video when skipping around... - // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) - // users who are clicking around.. I don't know. - } + StartSeeking(frame); + } + else + { + // GUI users may want to be protected from clobbering their video when skipping around... + // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) + // users who are clicking around.. I don't know. } } From e2acfa61ea872703c288cc6f94e709fab9aee215 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Thu, 5 Jun 2025 01:17:20 -0500 Subject: [PATCH 03/29] tastudio should own tastudio logic --- .../tools/TAStudio/BookmarksBranchesBox.cs | 25 ++----------------- .../tools/TAStudio/TAStudio.cs | 23 +++++++++++++++++ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 17ad218b94..6085159feb 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -177,32 +177,11 @@ namespace BizHawk.Client.EmuHawk }; } - private void LoadBranch(TasBranch branch) - { - if (Tastudio.Settings.OldControlSchemeForBranches && !Tastudio.TasPlaybackBox.RecordingMode) - { - JumpToBranchToolStripMenuItem_Click(null, null); - return; - } - - Movie.LoadBranch(branch); - Tastudio.LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false))); - - Movie.TasStateManager.Capture(Tastudio.Emulator.Frame, Tastudio.Emulator.AsStatable()); - QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), Tastudio.VideoProvider); - - if (Tastudio.Settings.OldControlSchemeForBranches && Tastudio.TasPlaybackBox.RecordingMode) - Movie.Truncate(branch.Frame); - - MainForm.PauseOnFrame = null; - Tastudio.RefreshDialog(); - } - private bool LoadSelectedBranch() { if (SelectedBranch == null) return false; Branches.Current = BranchView.FirstSelectedRowIndex; - LoadBranch(SelectedBranch); + Tastudio.LoadBranch(SelectedBranch); BranchView.Refresh(); Tastudio.MainForm.AddOnScreenMessage($"Loaded branch {Branches.Current + 1}"); return true; @@ -352,7 +331,7 @@ namespace BizHawk.Client.EmuHawk { if (_branchUndo == BranchUndo.Load) { - LoadBranch(_backupBranch); + Tastudio.LoadBranch(_backupBranch); Tastudio.BranchLoadedCallback?.Invoke(Branches.IndexOf(_backupBranch)); Tastudio.MainForm.AddOnScreenMessage("Branch Load canceled"); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 903b8cfa94..6a8c07e49a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -1250,5 +1250,28 @@ namespace BizHawk.Client.EmuHawk TasView.AllColumns.ColumnsChanged(); } + + public void LoadBranch(TasBranch branch) + { + if (Settings.OldControlSchemeForBranches && !TasPlaybackBox.RecordingMode) + { + GoToFrame(branch.Frame); + return; + } + + CurrentTasMovie.LoadBranch(branch); + LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false))); + + CurrentTasMovie.TasStateManager.Capture(Emulator.Frame, Emulator.AsStatable()); + QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), VideoProvider); + + if (Settings.OldControlSchemeForBranches && TasPlaybackBox.RecordingMode) + { + CurrentTasMovie.Truncate(branch.Frame); + } + + MainForm.PauseOnFrame = null; + RefreshDialog(); + } } } From a3af98718ffa990aaf1336ba4836127d29ace5d6 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Thu, 5 Jun 2025 14:22:01 -0500 Subject: [PATCH 04/29] Make TAStudio take ownership of its own seeking logic and simplify some code, fixing a few bugs: 1) Auto-restore did not work when painting "axis" inputs. 2) If unpaused in recording mode, manual seek to frame A (click cursor column) then before that seek finishes seek to frame B. It would not unpause after reaching frame B to resume recording. 3) TAStudio would fail to pause with auto-restore if a second edit was made to a non-greenzoned frame while auto-restore seek was in progress. 4) Canceling seek would not remove the seek progress bar. --- src/BizHawk.Client.EmuHawk/MainForm.cs | 11 +-- .../TAStudio/TAStudio.IControlMainForm.cs | 22 +---- .../tools/TAStudio/TAStudio.IToolForm.cs | 10 ++- .../tools/TAStudio/TAStudio.ListView.cs | 83 ++++++++++++------- .../tools/TAStudio/TAStudio.MenuItems.cs | 5 +- .../tools/TAStudio/TAStudio.Navigation.cs | 10 +-- .../tools/TAStudio/TAStudio.cs | 28 +++---- 7 files changed, 77 insertions(+), 92 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index d500faaa8f..7faf496185 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3256,16 +3256,7 @@ namespace BizHawk.Client.EmuHawk if (PauseOnFrame.Value == Emulator.Frame) { PauseEmulator(); - if (Tools.IsLoaded()) Tools.TAStudio.StopSeeking(); - else PauseOnFrame = null; - } - else if (Tools.IsLoaded() - && Tools.TAStudio.RestorePositionFrame == Emulator.Frame - && ((ITasMovie) MovieSession.Movie)[Emulator.Frame].Lagged is null) - { - // haven't yet greenzoned the frame, hence it's after editing - // then we want to pause here. taseditor fashion - PauseEmulator(); + PauseOnFrame = null; } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs index f8860f73d9..272afddc2b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IControlMainForm.cs @@ -78,27 +78,7 @@ public bool Rewind() { int rewindStep = MainForm.IsFastForwarding ? Settings.RewindStepFast : Settings.RewindStep; - // copy pasted from TasView_MouseWheel(), just without notch logic - if (MainForm.IsSeeking && !MainForm.EmulatorPaused) - { - MainForm.PauseOnFrame -= rewindStep; - - // that's a weird condition here, but for whatever reason it works best - if (Emulator.Frame >= MainForm.PauseOnFrame) - { - MainForm.PauseEmulator(); - StopSeeking(); - GoToFrame(Math.Max(0, Emulator.Frame - rewindStep)); - } - - RefreshDialog(); - } - else - { - StopSeeking(); // late breaking memo: don't know whether this is needed - GoToFrame(Math.Max(0, Emulator.Frame - rewindStep)); - } - + WheelSeek(rewindStep); return true; } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 4cdad461d6..1aee961bfe 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -24,10 +24,10 @@ namespace BizHawk.Client.EmuHawk private void UpdateProgressBar() { - if (MainForm.PauseOnFrame.HasValue) + if (_seekingTo != -1) { int diff = Emulator.Frame - _seekStartFrame.Value; - int unit = MainForm.PauseOnFrame.Value - _seekStartFrame.Value; + int unit = _seekingTo - _seekStartFrame.Value; double progress = 0; if (diff != 0 && unit != 0) @@ -100,12 +100,16 @@ namespace BizHawk.Client.EmuHawk _doPause = !CurrentTasMovie.IsAtEnd(); } + FastUpdateAfter(); RefreshDialog(refreshNeeded, refreshBranches: false); - UpdateProgressBar(); } protected override void FastUpdateAfter() { + if (_seekingTo != -1 && Emulator.Frame >= _seekingTo) + { + StopSeeking(); + } UpdateProgressBar(); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 86bb32c1fe..df528f66da 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -57,7 +57,6 @@ namespace BizHawk.Client.EmuHawk private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; private bool _triggerAutoRestore; // If true, autorestore will be called on mouse up - private bool? _autoRestorePaused; private int? _seekStartFrame; private bool _unpauseAfterSeeking; @@ -86,15 +85,12 @@ namespace BizHawk.Client.EmuHawk if (!fromMiddleClick) { - if (MainForm.PauseOnFrame != null) - { - StopSeeking(true); // don't restore rec mode just yet, as with heavy editing checkbox updating causes lag - } _seekStartFrame = Emulator.Frame; } - MainForm.PauseOnFrame = frame.Value; - int? diff = MainForm.PauseOnFrame - _seekStartFrame; + _seekingTo = frame.Value; + MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. + int? diff = _seekingTo - _seekStartFrame; WasRecording = CurrentTasMovie.IsRecording() || WasRecording; TastudioPlayMode(); // suspend rec mode until seek ends, to allow mouse editing @@ -115,12 +111,18 @@ namespace BizHawk.Client.EmuHawk WasRecording = false; } - MainForm.PauseOnFrame = null; + _seekingTo = -1; + MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. if (_unpauseAfterSeeking) { - MainForm.UnpauseEmulator(); + // We don't actually need to unpause, because the fact that we are seeking means we already unpaused to start it. + // It is possible that the user has paused during the seek. But if the user has pasuesd, we should respect that. _unpauseAfterSeeking = false; } + else + { + MainForm.PauseEmulator(); + } if (CurrentTasMovie != null) { @@ -129,6 +131,21 @@ namespace BizHawk.Client.EmuHawk } } + private void CancelSeek() + { + _seekingTo = -1; + MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. + _unpauseAfterSeeking = false; + if (WasRecording) + { + TastudioRecordMode(); + WasRecording = false; + } + + RefreshDialog(); + UpdateProgressBar(); + } + private Bitmap ts_v_arrow_green_blue => Properties.Resources.ts_v_arrow_green_blue; private Bitmap ts_h_arrow_green_blue => Properties.Resources.ts_h_arrow_green_blue; private Bitmap ts_v_arrow_blue => Properties.Resources.ts_v_arrow_blue; @@ -167,7 +184,7 @@ namespace BizHawk.Client.EmuHawk if (index == Emulator.Frame) { - bitmap = index == MainForm.PauseOnFrame + bitmap = index == _seekingTo ? TasView.HorizontalOrientation ? ts_v_arrow_green_blue : ts_h_arrow_green_blue : TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue; } @@ -255,11 +272,11 @@ namespace BizHawk.Client.EmuHawk var record = CurrentTasMovie[index]; - if (MainForm.IsSeeking && MainForm.PauseOnFrame == index) + if (_seekingTo == index) { color = Palette.CurrentFrame_InputLog; } - else if (!MainForm.IsSeeking && Emulator.Frame == index) + else if (_seekingTo == -1 && Emulator.Frame == index) { color = Palette.CurrentFrame_InputLog; } @@ -874,6 +891,26 @@ namespace BizHawk.Client.EmuHawk _suppressContextMenu = false; } + private void WheelSeek(int count) + { + if (_seekingTo != -1) + { + _seekingTo -= count; + + // that's a weird condition here, but for whatever reason it works best + if (count > 0 && Emulator.Frame >= _seekingTo) + { + GoToFrame(Emulator.Frame - count); + } + + RefreshDialog(); + } + else + { + GoToFrame(Emulator.Frame - count); + } + } + private void TasView_MouseWheel(object sender, MouseEventArgs e) { if (TasView.RightButtonHeld && TasView?.CurrentCell.RowIndex.HasValue == true) @@ -885,25 +922,7 @@ namespace BizHawk.Client.EmuHawk notch *= 2; } - // warning: tastudio rewind hotkey/button logic is copy pasted from here! - if (MainForm.IsSeeking && !MainForm.EmulatorPaused) - { - MainForm.PauseOnFrame -= notch; - - // that's a weird condition here, but for whatever reason it works best - if (notch > 0 && Emulator.Frame >= MainForm.PauseOnFrame) - { - MainForm.PauseEmulator(); - StopSeeking(); - GoToFrame(Emulator.Frame - notch); - } - - RefreshDialog(); - } - else - { - GoToFrame(Emulator.Frame - notch); - } + WheelSeek(notch); } } @@ -975,7 +994,7 @@ namespace BizHawk.Client.EmuHawk } } - if (_startCursorDrag && !MainForm.IsSeeking) + if (_startCursorDrag && _seekingTo == -1) { GoToFrame(e.NewCell.RowIndex.Value); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 5448462118..feab6897ea 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -1382,7 +1382,7 @@ namespace BizHawk.Client.EmuHawk StartFromNowSeparator.Visible = StartNewProjectFromNowMenuItem.Visible || StartANewProjectFromSaveRamMenuItem.Visible; RemoveMarkersContextMenuItem.Enabled = CurrentTasMovie.Markers.Any(m => TasView.IsRowSelected(m.Frame)); // Disable the option to remove markers if no markers are selected (FCEUX does this). - CancelSeekContextMenuItem.Enabled = MainForm.PauseOnFrame.HasValue; + CancelSeekContextMenuItem.Enabled = _seekingTo != -1; BranchContextMenuItem.Visible = TasView.CurrentCell?.RowIndex == Emulator.Frame; SelectBetweenMarkersContextMenuItem.ShortcutKeyDisplayString = Config.HotkeyBindings["Sel. bet. Markers"]; @@ -1396,8 +1396,7 @@ namespace BizHawk.Client.EmuHawk private void CancelSeekContextMenuItem_Click(object sender, EventArgs e) { - MainForm.PauseOnFrame = null; - TasView.Refresh(); + CancelSeek(); } private void BranchContextMenuItem_Click(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 22571e42d3..ebfdf3da36 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -13,7 +13,7 @@ namespace BizHawk.Client.EmuHawk { if (frame <= Emulator.Frame) { - if ((MainForm.EmulatorPaused || !MainForm.IsSeeking) + if ((MainForm.EmulatorPaused || _seekingTo == -1) && !CurrentTasMovie.LastPositionStable) { RestorePositionFrame = Emulator.Frame; @@ -67,14 +67,6 @@ namespace BizHawk.Client.EmuHawk } } - public void GoToPreviousFrame() - { - if (Emulator.Frame > 0) - { - GoToFrame(Emulator.Frame - 1); - } - } - public void GoToPreviousMarker() { if (Emulator.Frame > 0) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 6a8c07e49a..e83747a40a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -52,6 +52,8 @@ namespace BizHawk.Client.EmuHawk /// public int RestorePositionFrame { get; private set; } + private int _seekingTo = -1; + [ConfigPersist] public TAStudioSettings Settings { get; set; } = new TAStudioSettings(); @@ -832,16 +834,6 @@ namespace BizHawk.Client.EmuHawk { StartSeeking(RestorePositionFrame); } - else - { - if (_autoRestorePaused.HasValue && !_autoRestorePaused.Value) - { - // this happens when we're holding the left button while unpaused - view scrolls down, new input gets drawn, seek pauses - MainForm.UnpauseEmulator(); - } - - _autoRestorePaused = null; - } } /// @@ -860,8 +852,14 @@ namespace BizHawk.Client.EmuHawk return; } + // Unpausing after a seek may seem like we aren't really seeking at all: + // what is the significance of a seek to frame if we don't pause? + // Answer: We use this in order to temporarily disable recording mode when the user navigates to a frame. (to avoid recording between whatever is the most recent state and the user-specified frame) + // as well as ... to enable turbo-seek when navigating while unpaused? not sure (because this doesn't work) + // ... actually, fromRewinding is never set to true in any call AND NEVER WAS. TODO: Fix that. _unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused; TastudioPlayMode(); + var closestState = GetPriorStateForFramebuffer(frame); if (closestState.Value.Length > 0 && (frame < Emulator.Frame || closestState.Key > Emulator.Frame)) { @@ -896,8 +894,11 @@ namespace BizHawk.Client.EmuHawk // now the next section won't happen since we're at the right spot } - // make seek frame keep up with emulation on fast scrolls - if (MainForm.EmulatorPaused || MainForm.IsSeeking || fromRewinding || WasRecording) + // Seek needs to happen if any of: + // 1) We should pause once reaching the target frame (that's what a seek is): currently paused OR currently seeking + // 2) We are using _unpauseAfterSeeking (e.g. to manage recording state) + // Otherwise, just don't seek and emulation will happily continue. + if (MainForm.EmulatorPaused || _seekingTo != -1 || _unpauseAfterSeeking) { StartSeeking(frame); } @@ -957,7 +958,6 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); _triggerAutoRestore = false; - _autoRestorePaused = null; } } @@ -1270,7 +1270,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.Truncate(branch.Frame); } - MainForm.PauseOnFrame = null; + CancelSeek(); RefreshDialog(); } } From 31e6eeb113903282a2df1cc30077e9ff85e7546c Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 00:55:41 -0500 Subject: [PATCH 05/29] Remove obsolete conditional. --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index e83747a40a..05593f71de 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -861,7 +861,7 @@ namespace BizHawk.Client.EmuHawk TastudioPlayMode(); var closestState = GetPriorStateForFramebuffer(frame); - if (closestState.Value.Length > 0 && (frame < Emulator.Frame || closestState.Key > Emulator.Frame)) + if (frame < Emulator.Frame || closestState.Key > Emulator.Frame) { LoadState(closestState, true); } From fb231b04b5a88e60d88e12fc6680148d525f65bf Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 01:33:04 -0500 Subject: [PATCH 06/29] Fix: Mouse drag seeking was broken while seeking. (It would not change the target seek frame until prior seek had completed.) --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index df528f66da..3e2c629dc4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -994,7 +994,7 @@ namespace BizHawk.Client.EmuHawk } } - if (_startCursorDrag && _seekingTo == -1) + if (_startCursorDrag) { GoToFrame(e.NewCell.RowIndex.Value); } From 3ef7dce1ed7df04243bf83d673308dcdcb44dcb9 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 01:35:21 -0500 Subject: [PATCH 07/29] GoToFrame's "future frame" cases were just buggy versions of the normal case. So just use the code of StartAtNearestFrameAndEmulate instead. Fixes multiple bugs when seeking to frames past the end of the movie. --- .../tools/TAStudio/TAStudio.Navigation.cs | 113 ++++++++++++------ .../tools/TAStudio/TAStudio.cs | 65 ---------- 2 files changed, 75 insertions(+), 103 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index ebfdf3da36..1daae75f89 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -4,6 +4,81 @@ namespace BizHawk.Client.EmuHawk { public partial class TAStudio { + /// + /// Seek to the given frame, past or future, and load a state first if doing so gets us there faster. + /// Does nothing if we are already on the given frame. + /// + public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = false, bool OnLeftMouseDown = false) + { + if (frame == Emulator.Frame) + { + return; + } + + // Unpausing after a seek may seem like we aren't really seeking at all: + // what is the significance of a seek to frame if we don't pause? + // Answer: We use this in order to temporarily disable recording mode when the user navigates to a frame. (to avoid recording between whatever is the most recent state and the user-specified frame) + // as well as ... to enable turbo-seek when navigating while unpaused? not sure (because this doesn't work) + // ... actually, fromRewinding is never set to true in any call AND NEVER WAS. TODO: Fix that. + WasRecording = CurrentTasMovie.IsRecording() || WasRecording; + _unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused; + TastudioPlayMode(); + + var closestState = GetPriorStateForFramebuffer(frame); + if (frame < Emulator.Frame || closestState.Key > Emulator.Frame) + { + LoadState(closestState, true); + } + closestState.Value.Dispose(); + + if (fromLua) + { + bool wasPaused = MainForm.EmulatorPaused; + + // why not use this? because I'm not letting the form freely run. it all has to be under this loop. + // i could use this and then poll StepRunLoop_Core() repeatedly, but.. that's basically what I'm doing + // PauseOnFrame = frame; + + while (Emulator.Frame != frame) + { + MainForm.SeekFrameAdvance(); + } + + if (!wasPaused) + { + MainForm.UnpauseEmulator(); + } + + // lua botting users will want to re-activate record mode automatically -- it should be like nothing ever happened + if (WasRecording) + { + TastudioRecordMode(); + } + + // now the next section won't happen since we're at the right spot + } + + // Seek needs to happen if any of: + // 1) We should pause once reaching the target frame (that's what a seek is): currently paused OR currently seeking + // 2) We are using _unpauseAfterSeeking (e.g. to manage recording state) + // Otherwise, just don't seek and emulation will happily continue. + if (MainForm.EmulatorPaused || _seekingTo != -1 || _unpauseAfterSeeking) + { + StartSeeking(frame); + } + else + { + // GUI users may want to be protected from clobbering their video when skipping around... + // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) + // users who are clicking around.. I don't know. + } + + if (!OnLeftMouseDown) + { + MaybeFollowCursor(); + } + } + /// /// Only goes to go to the frame if it is an event before current emulation, otherwise it is just a future event that can freely be edited /// @@ -29,44 +104,6 @@ namespace BizHawk.Client.EmuHawk } } - public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = false, bool OnLeftMouseDown = false) - { - // If seeking to a frame before or at the end of the movie, use StartAtNearestFrameAndEmulate - // Otherwise, load the latest state (if not already there) and seek while recording. - WasRecording = CurrentTasMovie.IsRecording() || WasRecording; - - if (frame <= CurrentTasMovie.InputLogLength) - { - // Get as close as we can then emulate there - StartAtNearestFrameAndEmulate(frame, fromLua, fromRewinding); - if (!OnLeftMouseDown) { MaybeFollowCursor(); } - } - else // Emulate to a future frame - { - if (frame == Emulator.Frame + 1) // We are at the end of the movie and advancing one frame, therefore we are recording, simply emulate a frame - { - bool wasPaused = MainForm.EmulatorPaused; - MainForm.FrameAdvance(); - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } - } - else - { - TastudioPlayMode(); - - var lastState = GetPriorStateForFramebuffer(frame); - if (lastState.Key > Emulator.Frame) - { - LoadState(lastState, true); - } - - StartSeeking(frame); - } - } - } - public void GoToPreviousMarker() { if (Emulator.Frame > 0) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 05593f71de..50dc99111e 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -845,71 +845,6 @@ namespace BizHawk.Client.EmuHawk return CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame > 0 ? frame - 1 : 0); } - private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRewinding) - { - if (frame == Emulator.Frame) - { - return; - } - - // Unpausing after a seek may seem like we aren't really seeking at all: - // what is the significance of a seek to frame if we don't pause? - // Answer: We use this in order to temporarily disable recording mode when the user navigates to a frame. (to avoid recording between whatever is the most recent state and the user-specified frame) - // as well as ... to enable turbo-seek when navigating while unpaused? not sure (because this doesn't work) - // ... actually, fromRewinding is never set to true in any call AND NEVER WAS. TODO: Fix that. - _unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused; - TastudioPlayMode(); - - var closestState = GetPriorStateForFramebuffer(frame); - if (frame < Emulator.Frame || closestState.Key > Emulator.Frame) - { - LoadState(closestState, true); - } - closestState.Value.Dispose(); - - if (fromLua) - { - bool wasPaused = MainForm.EmulatorPaused; - - // why not use this? because I'm not letting the form freely run. it all has to be under this loop. - // i could use this and then poll StepRunLoop_Core() repeatedly, but.. that's basically what I'm doing - // PauseOnFrame = frame; - - while (Emulator.Frame != frame) - { - MainForm.SeekFrameAdvance(); - } - - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } - - // lua botting users will want to re-activate record mode automatically -- it should be like nothing ever happened - if (WasRecording) - { - TastudioRecordMode(); - } - - // now the next section won't happen since we're at the right spot - } - - // Seek needs to happen if any of: - // 1) We should pause once reaching the target frame (that's what a seek is): currently paused OR currently seeking - // 2) We are using _unpauseAfterSeeking (e.g. to manage recording state) - // Otherwise, just don't seek and emulation will happily continue. - if (MainForm.EmulatorPaused || _seekingTo != -1 || _unpauseAfterSeeking) - { - StartSeeking(frame); - } - else - { - // GUI users may want to be protected from clobbering their video when skipping around... - // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) - // users who are clicking around.. I don't know. - } - } - public void LoadState(KeyValuePair state, bool discardApiHawkSurfaces = false) { StatableEmulator.LoadStateBinary(new BinaryReader(state.Value)); From 55757391878fb43bd1e808273c64be052f1f1f86 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 02:26:16 -0500 Subject: [PATCH 08/29] Fix: modifier key + right click would jump to the last edited frame (even if this right click made no edits) --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 3e2c629dc4..22bf8bf088 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -760,8 +760,6 @@ namespace BizHawk.Client.EmuHawk if (_rightClickAlt || _rightClickControl || _rightClickShift) { - JumpToGreenzone(); - // TODO: Turn off ChangeLog.IsRecording and handle the GeneralUndo here. string undoStepName = "Right-Click Edit:"; if (_rightClickShift) From fe29ffd8d174ff0c59351c4475a7720d2584db50 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 02:44:25 -0500 Subject: [PATCH 09/29] simplify a bit of logic --- .../tools/TAStudio/TAStudio.ListView.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 22bf8bf088..894d58a0d1 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -788,6 +788,19 @@ namespace BizHawk.Client.EmuHawk } } + /// + /// Disables recording mode, ensures we are in the greenzone, and does autorestore if needed. + /// + private void FrameEdited(int frame) + { + TastudioPlayMode(true); + if (Emulator.Frame > frame) + { + JumpToGreenzone(); + DoAutoRestore(); + } + } + private void ClearLeftMouseStates() { _startCursorDrag = false; @@ -802,10 +815,7 @@ namespace BizHawk.Client.EmuHawk if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) { AxisEditRow = -1; - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + FrameEdited(_axisEditRow); RefreshDialog(); } _axisPaintState = 0; @@ -940,7 +950,6 @@ namespace BizHawk.Client.EmuHawk } else { - ClearLeftMouseStates(); MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value); } } @@ -1345,10 +1354,7 @@ namespace BizHawk.Client.EmuHawk if (_axisBackupState != _axisPaintState) { CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); - _triggerAutoRestore = Emulator.Frame > _axisEditRow; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + FrameEdited(_axisEditRow); } AxisEditRow = -1; @@ -1406,12 +1412,9 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.SetAxisState(row, _axisEditColumn, value); } - if (value != prev) // Auto-restore + if (value != prev) { - _triggerAutoRestore = Emulator.Frame > _axisEditRow; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + FrameEdited(_axisEditRow); } } From 68f7824f91fdde7955d38c3c24ed925a353f9c6f Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 03:04:18 -0500 Subject: [PATCH 10/29] Fix: Clicking on an axis value while in axis editing mode would disable recording mode, regardless of whether an edit was made. Fix: Clicking on an axis value while in axis editing mode would trigger auto-restore, even if no edit was made (seek to last edit frame). --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 894d58a0d1..31b0c9e43d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -592,8 +592,6 @@ namespace BizHawk.Client.EmuHawk _axisEditYPos = e.Y; _axisPaintState = CurrentTasMovie.GetAxisState(frame, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); return; } } From 850d3c58a5bac4d51d375d29b00e24bb5d236e21 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 13:45:23 -0500 Subject: [PATCH 11/29] Windows weirdness: MouseUp event is not guaranteed to be raised. --- .../tools/TAStudio/TAStudio.Designer.cs | 1 + .../tools/TAStudio/TAStudio.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs index f565d8b4d6..657f8edf57 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs @@ -1180,6 +1180,7 @@ namespace BizHawk.Client.EmuHawk this.MinimumSize = new System.Drawing.Size(200, 148); this.Name = "TAStudio"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Deactivate += new System.EventHandler(this.TAStudio_Deactivate); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Tastudio_Closing); this.Load += new System.EventHandler(this.Tastudio_Load); this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 50dc99111e..aba4049497 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -1004,7 +1004,19 @@ namespace BizHawk.Client.EmuHawk private void TAStudio_MouseLeave(object sender, EventArgs e) { toolTip1.SetToolTip(TasView, null); - DoTriggeredAutoRestoreIfNeeded(); + } + + private void TAStudio_Deactivate(object sender, EventArgs e) + { + if (_leftButtonHeld) + { + TasView_MouseUp(this, new(MouseButtons.Left, 0, 0, 0, 0)); + } + if (_rightClickFrame != -1) + { + _suppressContextMenu = true; + TasView_MouseUp(this, new(MouseButtons.Right, 0, 0, 0, 0)); + } } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) From 553336ccd5e25df5a9bf38011c270e7f1e56f77d Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 7 Jun 2025 15:35:24 -0500 Subject: [PATCH 12/29] Replace some auto restore code with something simpler. Fixes auto-restore for right-click edits. --- .../tools/TAStudio/TAStudio.ListView.cs | 77 ++++++++----------- .../tools/TAStudio/TAStudio.Navigation.cs | 4 - .../tools/TAStudio/TAStudio.cs | 11 --- 3 files changed, 33 insertions(+), 59 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 31b0c9e43d..38cd64e404 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -16,7 +16,6 @@ namespace BizHawk.Client.EmuHawk // Input Painting private string _startBoolDrawColumn = ""; private string _startAxisDrawColumn = ""; - private bool _drewAxis; private bool _boolPaintState; private int _axisPaintState; private int _axisBackupState; @@ -26,7 +25,7 @@ namespace BizHawk.Client.EmuHawk private bool _selectionDragState; private bool _suppressContextMenu; private int _startRow; - private int _paintingMinFrame = -1; + private int _mouseEditMinFrame = -1; // Editing analog input private string _axisEditColumn = ""; @@ -56,7 +55,6 @@ namespace BizHawk.Client.EmuHawk private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; - private bool _triggerAutoRestore; // If true, autorestore will be called on mouse up private int? _seekStartFrame; private bool _unpauseAfterSeeking; @@ -433,9 +431,7 @@ namespace BizHawk.Client.EmuHawk // autohold is ignored for axes too for the same reasons: lack of demand + ambiguity } - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); + FrameEdited(CurrentTasMovie.LastEditedFrame); } RefreshDialog(); @@ -560,7 +556,6 @@ namespace BizHawk.Client.EmuHawk if (e.Button == MouseButtons.Left) { _leftButtonHeld = true; - _paintingMinFrame = frame; // SuuperW: Exit axis editing mode, or re-enter mouse editing if (AxisEditingMode) @@ -657,8 +652,7 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed); _boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); + FrameEdited(CurrentTasMovie.LastEditedFrame); RefreshDialog(); } #if false // to match previous behaviour @@ -673,8 +667,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); + FrameEdited(CurrentTasMovie.LastEditedFrame); RefreshDialog(); } } @@ -788,25 +781,40 @@ namespace BizHawk.Client.EmuHawk /// /// Disables recording mode, ensures we are in the greenzone, and does autorestore if needed. + /// If a mouse button is down, only tracks the edit so we can do this stuff on mouse up. /// private void FrameEdited(int frame) { - TastudioPlayMode(true); - if (Emulator.Frame > frame) + if (MouseButtonHeld) { - JumpToGreenzone(); - DoAutoRestore(); + if (_mouseEditMinFrame == -1) + { + _mouseEditMinFrame = frame; + } + else + { + _mouseEditMinFrame = Math.Min(_mouseEditMinFrame, frame); + } + } + else + { + TastudioPlayMode(true); + if (Emulator.Frame > frame) + { + GoToLastEmulatedFrameIfNecessary(frame); + DoAutoRestore(); + } + _mouseEditMinFrame = -1; } } private void ClearLeftMouseStates() { + _leftButtonHeld = false; _startCursorDrag = false; _startSelectionDrag = false; _startBoolDrawColumn = ""; _startAxisDrawColumn = ""; - _drewAxis = false; - _paintingMinFrame = -1; TasView.ReleaseCurrentCell(); // Exit axis editing if value was changed with cursor @@ -818,7 +826,6 @@ namespace BizHawk.Client.EmuHawk } _axisPaintState = 0; _axisEditYPos = -1; - _leftButtonHeld = false; if (!AxisEditingMode) { @@ -870,17 +877,8 @@ namespace BizHawk.Client.EmuHawk } else { - if (!string.IsNullOrWhiteSpace(_startBoolDrawColumn) || _drewAxis) - { - // If painting up, we have altered frames without loading states (for smoothness) - // So now we have to ensure that all the edited frames are invalidated - GoToLastEmulatedFrameIfNecessary(_paintingMinFrame); - } - ClearLeftMouseStates(); } - - DoTriggeredAutoRestoreIfNeeded(); } if (e.Button == MouseButtons.Right) @@ -894,6 +892,11 @@ namespace BizHawk.Client.EmuHawk } } + if (_mouseEditMinFrame != -1) + { + FrameEdited(_mouseEditMinFrame); + } + _suppressContextMenu = false; } @@ -968,11 +971,6 @@ namespace BizHawk.Client.EmuHawk return; } - if (_paintingMinFrame >= 0) - { - _paintingMinFrame = Math.Min(_paintingMinFrame, e.NewCell.RowIndex.Value); - } - // skip rerecord counting on drawing entirely, mouse down is enough // avoid introducing another global bool wasCountingRerecords = CurrentTasMovie.IsCountingRerecords; @@ -1120,9 +1118,7 @@ namespace BizHawk.Client.EmuHawk if (_rightClickAlt || _rightClickControl || _rightClickShift) { - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); + FrameEdited(CurrentTasMovie.LastEditedFrame); _suppressContextMenu = true; } } @@ -1150,11 +1146,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - if (!_triggerAutoRestore) - { - TastudioPlayMode(true); - JumpToGreenzone(); - } + FrameEdited(CurrentTasMovie.LastEditedFrame); } } @@ -1178,12 +1170,9 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetAxisState(i, _startAxisDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - _triggerAutoRestore = true; - TastudioPlayMode(true); + FrameEdited(CurrentTasMovie.LastEditedFrame); RefreshDialog(); } - - _drewAxis = true; } CurrentTasMovie.IsCountingRerecords = wasCountingRerecords; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 1daae75f89..296040cc3e 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -97,10 +97,6 @@ namespace BizHawk.Client.EmuHawk GoToFrame(frame, false, false, OnLeftMouseDown); } - else - { - _triggerAutoRestore = false; - } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index aba4049497..18a60755a0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -885,17 +885,6 @@ namespace BizHawk.Client.EmuHawk SplicerStatusLabel.Text = temp; } - private void DoTriggeredAutoRestoreIfNeeded() - { - if (_triggerAutoRestore) - { - TastudioPlayMode(true); // once user started editing, rec mode is unsafe - DoAutoRestore(); - - _triggerAutoRestore = false; - } - } - public void InsertNumFrames(int insertionFrame, int numberOfFrames) { if (insertionFrame <= CurrentTasMovie.InputLogLength) From ffc50444f97e6ee74184fdbbe28bd309e5407a3f Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sun, 8 Jun 2025 01:07:44 -0500 Subject: [PATCH 13/29] Revert commit a1c8c4ed3a676954358cb493efc878436bd43a4e. It was always pointless because it didn't fix the issue it was supposed to fix, and the later commit that actually fixes it was all that was needed all along. --- .../tools/TAStudio/TAStudio.MenuItems.cs | 18 +++++++++--------- .../tools/TAStudio/TAStudio.cs | 14 -------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index feab6897ea..aa5adf686f 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -429,7 +429,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -473,7 +473,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -514,7 +514,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -540,7 +540,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -554,7 +554,7 @@ namespace BizHawk.Client.EmuHawk if (rollBackFrame >= CurrentTasMovie.InputLogLength) { // Cannot delete non-existent frames - FullRefresh(); + RefreshDialog(); return; } @@ -568,7 +568,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -608,7 +608,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -629,7 +629,7 @@ namespace BizHawk.Client.EmuHawk DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -661,7 +661,7 @@ namespace BizHawk.Client.EmuHawk GoToFrame(rollbackFrame); } - FullRefresh(); + RefreshDialog(); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 18a60755a0..419b018fd1 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -775,20 +775,6 @@ namespace BizHawk.Client.EmuHawk public IEnumerable GetSelection() => TasView.SelectedRows; - // Slow but guarantees the entire dialog refreshes - private void FullRefresh() - { - SetTasViewRowCount(); - TasView.Refresh(); // An extra refresh potentially but we need to guarantee - MarkerControl.UpdateValues(); - BookMarkControl.UpdateValues(); - - if (_undoForm != null && !_undoForm.IsDisposed) - { - _undoForm.UpdateValues(); - } - } - public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = true) { if (_exiting) From bbf3560bd00c9d24f81ed384b220d589619b8483 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sun, 8 Jun 2025 02:10:29 -0500 Subject: [PATCH 14/29] Convert remaining auto-restore points to the new system. Also include refreshing in FrameEdited. Fixes more bugs. --- .../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 13 +- .../tools/TAStudio/TAStudio.ListView.cs | 112 +++++++++++++----- .../tools/TAStudio/TAStudio.MenuItems.cs | 62 ++-------- .../tools/TAStudio/TAStudio.cs | 54 +-------- 4 files changed, 100 insertions(+), 141 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index 1593a24edb..4a7f3a9422 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -300,6 +300,11 @@ namespace BizHawk.Client.EmuHawk [LuaMethod("applyinputchanges", "")] public void ApplyInputChanges() { + if (_changeList.Count == 0) + { + return; + } + if (Engaged()) { if (_luaLibsImpl.IsInInputOrMemoryCallback) @@ -309,7 +314,7 @@ namespace BizHawk.Client.EmuHawk _luaLibsImpl.IsUpdateSupressed = true; - if (_changeList.Count > 0) + Tastudio.ApiHawkBatchEdit(() => { int size = _changeList.Count; @@ -327,7 +332,7 @@ namespace BizHawk.Client.EmuHawk Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis); break; } - Tastudio.RefreshForInputChange(_changeList[i].Frame); + Tastudio.FrameEdited(_changeList[i].Frame); break; case LuaChangeTypes.InsertFrames: Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number); @@ -341,9 +346,7 @@ namespace BizHawk.Client.EmuHawk } } _changeList.Clear(); - Tastudio.JumpToGreenzone(); - Tastudio.DoAutoRestore(); - } + }); _luaLibsImpl.IsUpdateSupressed = false; } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 38cd64e404..789beba5c2 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -25,7 +25,9 @@ namespace BizHawk.Client.EmuHawk private bool _selectionDragState; private bool _suppressContextMenu; private int _startRow; - private int _mouseEditMinFrame = -1; + private int _batchEditMinFrame = -1; + private bool _batchEditing; + private bool _editIsFromLua; // Editing analog input private string _axisEditColumn = ""; @@ -66,14 +68,6 @@ namespace BizHawk.Client.EmuHawk public AutoPatternBool[] BoolPatterns; public AutoPatternAxis[] AxisPatterns; - public void JumpToGreenzone(bool OnLeftMouseDown = false) - { - if (Emulator.Frame > CurrentTasMovie.LastEditedFrame) - { - GoToLastEmulatedFrameIfNecessary(CurrentTasMovie.LastEditedFrame, OnLeftMouseDown); - } - } - private void StartSeeking(int? frame, bool fromMiddleClick = false) { if (!frame.HasValue || frame <= Emulator.Frame) @@ -393,6 +387,7 @@ namespace BizHawk.Client.EmuHawk if (columnName == FrameColumnName) { CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, ""); + RefreshDialog(); } else if (columnName != CursorColumnName) { @@ -433,8 +428,6 @@ namespace BizHawk.Client.EmuHawk FrameEdited(CurrentTasMovie.LastEditedFrame); } - - RefreshDialog(); } } @@ -653,7 +646,6 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed); _boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName); FrameEdited(CurrentTasMovie.LastEditedFrame); - RefreshDialog(); } #if false // to match previous behaviour else if (altOrShift4State is not 0) @@ -668,7 +660,6 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); FrameEdited(CurrentTasMovie.LastEditedFrame); - RefreshDialog(); } } else @@ -685,6 +676,7 @@ namespace BizHawk.Client.EmuHawk { AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].Reset(); CurrentTasMovie.SetAxisState(frame, buttonName, AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].GetNextValue()); + FrameEdited(frame); _patternPaint = true; } else @@ -779,33 +771,93 @@ namespace BizHawk.Client.EmuHawk } } + /// + /// Begins a batch of edits, for auto-restore purposes. Auto-restore will be delayed until EndBatchEdit is called. + /// + private void BeginBatchEdit() + { + _batchEditing = true; + } + /// Returns true if the input list was redrawn. + private bool EndBatchEdit() + { + _batchEditing = false; + if (_batchEditMinFrame != -1) + { + return FrameEdited(_batchEditMinFrame); + } + + return false; + } + + public void ApiHawkBatchEdit(Action action) + { + // This is only caled from Lua. + _editIsFromLua = true; + BeginBatchEdit(); + action(); + EndBatchEdit(); + _editIsFromLua = false; + } + /// /// Disables recording mode, ensures we are in the greenzone, and does autorestore if needed. /// If a mouse button is down, only tracks the edit so we can do this stuff on mouse up. /// - private void FrameEdited(int frame) + /// The frame that was just edited, or the earliest one if multiple were edited. + /// Returns true if the input list was redrawn. + public bool FrameEdited(int frame) { - if (MouseButtonHeld) + bool needsRefresh = !_batchEditing; + if (MouseButtonHeld || _batchEditing) { - if (_mouseEditMinFrame == -1) + if (_batchEditMinFrame == -1) { - _mouseEditMinFrame = frame; + _batchEditMinFrame = frame; } else { - _mouseEditMinFrame = Math.Min(_mouseEditMinFrame, frame); + _batchEditMinFrame = Math.Min(_batchEditMinFrame, frame); } } else { - TastudioPlayMode(true); + if (!_editIsFromLua) + { + // Lua users will want to preserve recording mode. + TastudioPlayMode(true); + } + if (Emulator.Frame > frame) { GoToLastEmulatedFrameIfNecessary(frame); - DoAutoRestore(); + if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) + { + StartSeeking(RestorePositionFrame); + } + + needsRefresh = false; // Refresh will happen via GoToFrame. } - _mouseEditMinFrame = -1; + _batchEditMinFrame = -1; } + + if (needsRefresh) + { + if (TasView.IsPartiallyVisible(frame) || frame < TasView.FirstVisibleRow) + { + // frame < FirstVisibleRow: Greenzone in visible rows has been invalidated + RefreshDialog(); + return true; + } + else if (TasView.RowCount != CurrentTasMovie.InputLogLength + 1) + { + // Row count must always be kept up to date even if last row is not directly visible. + TasView.RowCount = CurrentTasMovie.InputLogLength + 1; + return true; + } + } + + return false; } private void ClearLeftMouseStates() @@ -822,7 +874,6 @@ namespace BizHawk.Client.EmuHawk { AxisEditRow = -1; FrameEdited(_axisEditRow); - RefreshDialog(); } _axisPaintState = 0; _axisEditYPos = -1; @@ -892,10 +943,7 @@ namespace BizHawk.Client.EmuHawk } } - if (_mouseEditMinFrame != -1) - { - FrameEdited(_mouseEditMinFrame); - } + EndBatchEdit(); // We didn't call BeginBatchEdit, but implicitly began one with mouse down. We must explicitly end it. _suppressContextMenu = false; } @@ -1014,6 +1062,7 @@ namespace BizHawk.Client.EmuHawk } SetSplicer(); + RefreshDialog(); } else if (_rightClickFrame != -1) { @@ -1171,7 +1220,6 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.SetAxisState(i, _startAxisDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column FrameEdited(CurrentTasMovie.LastEditedFrame); - RefreshDialog(); } } @@ -1181,8 +1229,6 @@ namespace BizHawk.Client.EmuHawk { TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed } - - SetTasViewRowCount(); } private void TasView_MouseMove(object sender, MouseEventArgs e) @@ -1405,7 +1451,11 @@ namespace BizHawk.Client.EmuHawk } } - RefreshDialog(); + bool didRefresh = EndBatchEdit(); + if (!didRefresh && (prevTyped != _axisTypedValue || !AxisEditingMode)) + { + RefreshDialog(); + } } private void TasView_KeyDown(object sender, KeyEventArgs e) @@ -1436,8 +1486,6 @@ namespace BizHawk.Client.EmuHawk { EditAnalogProgrammatically(e); } - - RefreshDialog(); } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index aa5adf686f..2e9e601779 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -425,11 +425,8 @@ namespace BizHawk.Client.EmuHawk var rollbackFrame = CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState)); if (rollbackFrame > 0) { - GoToLastEmulatedFrameIfNecessary(rollbackFrame); - DoAutoRestore(); + FrameEdited(rollbackFrame); } - - RefreshDialog(); } } } @@ -465,15 +462,8 @@ namespace BizHawk.Client.EmuHawk } var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; CurrentTasMovie.InsertInput(selectionStart ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(selectionStart!.Value); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(selectionStart!.Value); } } } @@ -485,7 +475,6 @@ namespace BizHawk.Client.EmuHawk if (TasView.Focused && TasView.AnyRowsSelected) { var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; var rollBackFrame = selectionStart ?? 0; _tasClipboard.Clear(); @@ -508,13 +497,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.RemoveFrames(list); SetSplicer(); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(rollBackFrame); } } @@ -536,8 +519,7 @@ namespace BizHawk.Client.EmuHawk if (needsToRollback) { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); + FrameEdited(rollBackFrame); } RefreshDialog(); @@ -549,7 +531,6 @@ namespace BizHawk.Client.EmuHawk if (TasView.Focused && TasView.AnyRowsSelected) { var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; var rollBackFrame = selectionStart ?? 0; if (rollBackFrame >= CurrentTasMovie.InputLogLength) { @@ -562,13 +543,7 @@ namespace BizHawk.Client.EmuHawk SetTasViewRowCount(); SetSplicer(); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(rollBackFrame); } } @@ -594,7 +569,6 @@ namespace BizHawk.Client.EmuHawk { var framesToInsert = TasView.SelectedRows; var insertionFrame = Math.Min((TasView.SelectionEndIndex ?? 0) + 1, CurrentTasMovie.InputLogLength); - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; var inputLog = framesToInsert .Select(frame => CurrentTasMovie.GetInputLogEntry(frame)) @@ -602,13 +576,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.InsertInput(insertionFrame, inputLog); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(insertionFrame); } } } @@ -619,17 +587,9 @@ namespace BizHawk.Client.EmuHawk { var selectionStart = TasView.SelectionStartIndex; var insertionFrame = selectionStart ?? 0; - var needsToRollback = selectionStart < Emulator.Frame; CurrentTasMovie.InsertEmptyFrame(insertionFrame); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(insertionFrame); } } @@ -651,17 +611,11 @@ namespace BizHawk.Client.EmuHawk if (TasView.Focused && TasView.AnyRowsSelected) { var rollbackFrame = TasView.SelectionEndIndex ?? 0; - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; CurrentTasMovie.Truncate(rollbackFrame); MarkerControl.MarkerInputRoll.TruncateSelection(CurrentTasMovie.Markers.Count - 1); - if (needsToRollback) - { - GoToFrame(rollbackFrame); - } - - RefreshDialog(); + FrameEdited(rollbackFrame); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 419b018fd1..c02b17c8a1 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -800,28 +800,12 @@ namespace BizHawk.Client.EmuHawk } } - public void RefreshForInputChange(int firstChangedFrame) - { - if (TasView.IsPartiallyVisible(firstChangedFrame) || firstChangedFrame < TasView.FirstVisibleRow) - { - RefreshDialog(); - } - } - private void SetTasViewRowCount() { TasView.RowCount = CurrentTasMovie.InputLogLength + 1; _lastRefresh = Emulator.Frame; } - public void DoAutoRestore() - { - if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) - { - StartSeeking(RestorePositionFrame); - } - } - /// /// Get a savestate prior to the previous frame so code following the call can frame advance and have a framebuffer. /// If frame is 0, return the initial state. @@ -875,19 +859,8 @@ namespace BizHawk.Client.EmuHawk { if (insertionFrame <= CurrentTasMovie.InputLogLength) { - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; - CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames); - - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(insertionFrame); - } + FrameEdited(insertionFrame); } } @@ -899,16 +872,7 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.RemoveFrames(framesToRemove); SetSplicer(); - var needsToRollback = beginningFrame < Emulator.Frame; - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(beginningFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(beginningFrame); - } + FrameEdited(beginningFrame); } } @@ -916,22 +880,13 @@ namespace BizHawk.Client.EmuHawk { if (beginningFrame < CurrentTasMovie.InputLogLength) { - var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; int last = Math.Min(beginningFrame + numberOfFrames, CurrentTasMovie.InputLogLength); for (int i = beginningFrame; i < last; i++) { CurrentTasMovie.ClearFrame(i); } - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(beginningFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(beginningFrame); - } + FrameEdited(beginningFrame); } } @@ -1038,8 +993,7 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.ChangeLog.IsRecording = wasRecording; - GoToLastEmulatedFrameIfNecessary(Emulator.Frame - 1); - DoAutoRestore(); + FrameEdited(Emulator.Frame - 1); return true; } From 672581476a5ab9422f0261de09836a79aa7312ce Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sun, 8 Jun 2025 02:39:51 -0500 Subject: [PATCH 15/29] inline function that is only called from one place --- .../tools/TAStudio/TAStudio.ListView.cs | 9 +++++++- .../tools/TAStudio/TAStudio.Navigation.cs | 21 ------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 789beba5c2..a31d6b90dc 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -830,7 +830,14 @@ namespace BizHawk.Client.EmuHawk if (Emulator.Frame > frame) { - GoToLastEmulatedFrameIfNecessary(frame); + if ((MainForm.EmulatorPaused || _seekingTo == -1) + && !CurrentTasMovie.LastPositionStable) + { + RestorePositionFrame = Emulator.Frame; + CurrentTasMovie.LastPositionStable = true; // until new frame is emulated + } + + GoToFrame(frame); if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) { StartSeeking(RestorePositionFrame); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 296040cc3e..1d5f735e66 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -79,27 +79,6 @@ namespace BizHawk.Client.EmuHawk } } - /// - /// Only goes to go to the frame if it is an event before current emulation, otherwise it is just a future event that can freely be edited - /// - private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = false) - { - if (frame != Emulator.Frame) // Don't go to a frame if you are already on it! - { - if (frame <= Emulator.Frame) - { - if ((MainForm.EmulatorPaused || _seekingTo == -1) - && !CurrentTasMovie.LastPositionStable) - { - RestorePositionFrame = Emulator.Frame; - CurrentTasMovie.LastPositionStable = true; // until new frame is emulated - } - - GoToFrame(frame, false, false, OnLeftMouseDown); - } - } - } - public void GoToPreviousMarker() { if (Emulator.Frame > 0) From 23f071619978b92c6e09d2bd5966e4bf576e4110 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sun, 8 Jun 2025 02:54:46 -0500 Subject: [PATCH 16/29] Fix: turbo seek did not work when navigating while unpaused --- .../tools/TAStudio/TAStudio.ListView.cs | 16 +++++-------- .../tools/TAStudio/TAStudio.Navigation.cs | 24 ++++--------------- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index a31d6b90dc..2f67d62759 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -58,7 +58,7 @@ namespace BizHawk.Client.EmuHawk private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; private int? _seekStartFrame; - private bool _unpauseAfterSeeking; + private bool _pauseAfterSeeking; private readonly Dictionary _alternateRowColor = new(); @@ -105,13 +105,7 @@ namespace BizHawk.Client.EmuHawk _seekingTo = -1; MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. - if (_unpauseAfterSeeking) - { - // We don't actually need to unpause, because the fact that we are seeking means we already unpaused to start it. - // It is possible that the user has paused during the seek. But if the user has pasuesd, we should respect that. - _unpauseAfterSeeking = false; - } - else + if (_pauseAfterSeeking) { MainForm.PauseEmulator(); } @@ -127,7 +121,7 @@ namespace BizHawk.Client.EmuHawk { _seekingTo = -1; MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. - _unpauseAfterSeeking = false; + _pauseAfterSeeking = false; if (WasRecording) { TastudioRecordMode(); @@ -532,6 +526,7 @@ namespace BizHawk.Client.EmuHawk var record = CurrentTasMovie[RestorePositionFrame]; if (record.Lagged is null) { + _pauseAfterSeeking = true; StartSeeking(RestorePositionFrame, true); return; } @@ -587,7 +582,7 @@ namespace BizHawk.Client.EmuHawk if (targetCol.Name is CursorColumnName) { _startCursorDrag = true; - GoToFrame(frame, fromLua: false, fromRewinding: false, OnLeftMouseDown: true); + GoToFrame(frame, fromLua: false, OnLeftMouseDown: true); } else if (targetCol.Name is FrameColumnName) { @@ -840,6 +835,7 @@ namespace BizHawk.Client.EmuHawk GoToFrame(frame); if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) { + _pauseAfterSeeking = true; // auto-restore makes no sense without auto-pause StartSeeking(RestorePositionFrame); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 1d5f735e66..c11122c449 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -4,11 +4,11 @@ namespace BizHawk.Client.EmuHawk { public partial class TAStudio { - /// + /// /// Seek to the given frame, past or future, and load a state first if doing so gets us there faster. /// Does nothing if we are already on the given frame. /// - public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = false, bool OnLeftMouseDown = false) + public void GoToFrame(int frame, bool fromLua = false, bool OnLeftMouseDown = false) { if (frame == Emulator.Frame) { @@ -18,10 +18,9 @@ namespace BizHawk.Client.EmuHawk // Unpausing after a seek may seem like we aren't really seeking at all: // what is the significance of a seek to frame if we don't pause? // Answer: We use this in order to temporarily disable recording mode when the user navigates to a frame. (to avoid recording between whatever is the most recent state and the user-specified frame) - // as well as ... to enable turbo-seek when navigating while unpaused? not sure (because this doesn't work) - // ... actually, fromRewinding is never set to true in any call AND NEVER WAS. TODO: Fix that. + // Other answer: turbo seek, navigating while unpaused + _pauseAfterSeeking = MainForm.EmulatorPaused || (_seekingTo != -1 && _pauseAfterSeeking); WasRecording = CurrentTasMovie.IsRecording() || WasRecording; - _unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused; TastudioPlayMode(); var closestState = GetPriorStateForFramebuffer(frame); @@ -58,20 +57,7 @@ namespace BizHawk.Client.EmuHawk // now the next section won't happen since we're at the right spot } - // Seek needs to happen if any of: - // 1) We should pause once reaching the target frame (that's what a seek is): currently paused OR currently seeking - // 2) We are using _unpauseAfterSeeking (e.g. to manage recording state) - // Otherwise, just don't seek and emulation will happily continue. - if (MainForm.EmulatorPaused || _seekingTo != -1 || _unpauseAfterSeeking) - { - StartSeeking(frame); - } - else - { - // GUI users may want to be protected from clobbering their video when skipping around... - // well, users who are rewinding aren't. (that gets done through the seeking system in the call above) - // users who are clicking around.. I don't know. - } + StartSeeking(frame); if (!OnLeftMouseDown) { From 0634b37cc05cc261095a10c0e64c899198948199 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Tue, 10 Jun 2025 22:11:39 -0500 Subject: [PATCH 17/29] Call our new FrameEdited thing for auto restore whenever greenzone is invalidated, instead of manually calling at each edit point. This fixes auto-restore for undo/redo actions. --- .../movie/interfaces/ITasMovie.cs | 1 + .../movie/tasproj/TasMovie.Editing.cs | 1 + .../movie/tasproj/TasMovie.cs | 2 + .../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 1 - .../tools/TAStudio/TAStudio.Callbacks.cs | 5 --- .../tools/TAStudio/TAStudio.ListView.cs | 26 ++++------- .../tools/TAStudio/TAStudio.MenuItems.cs | 45 +++++-------------- .../tools/TAStudio/TAStudio.cs | 13 +++--- 8 files changed, 32 insertions(+), 62 deletions(-) diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 0752930020..35685b1211 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -20,6 +20,7 @@ namespace BizHawk.Client.Common TasLagLog LagLog { get; } IStringLog VerificationLog { get; } int LastEditedFrame { get; } + bool LastEditWasRecording { get; } Action GreenzoneInvalidated { get; set; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index 9b72f80127..fce764f599 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -27,6 +27,7 @@ namespace BizHawk.Client.Common if (this.IsRecording()) { + LastEditWasRecording = true; InvalidateAfter(frame); } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 9fb2bb5dac..d9b65f10d8 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -73,6 +73,7 @@ namespace BizHawk.Client.Common public ITasSession TasSession { get; private set; } = new TasSession(); public int LastEditedFrame { get; private set; } = -1; + public bool LastEditWasRecording { get; private set; } public bool LastPositionStable { get; set; } = true; public TasMovieMarkerList Markers { get; private set; } public bool BindMarkersToInput { get; set; } @@ -129,6 +130,7 @@ namespace BizHawk.Client.Common } LastEditedFrame = frame; + LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action. if (anyStateInvalidated && IsCountingRerecords) { diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index 4a7f3a9422..f554e29119 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -332,7 +332,6 @@ namespace BizHawk.Client.EmuHawk Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis); break; } - Tastudio.FrameEdited(_changeList[i].Frame); break; case LuaChangeTypes.InsertFrames: Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs index a8de69c7d0..8c41e3972d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Callbacks.cs @@ -13,10 +13,5 @@ namespace BizHawk.Client.EmuHawk public Action BranchLoadedCallback { get; set; } public Action BranchSavedCallback { get; set; } public Action BranchRemovedCallback { get; set; } - - private void GreenzoneInvalidated(int index) - { - GreenzoneInvalidatedCallback?.Invoke(index); - } } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 2f67d62759..9e8a49af72 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -419,8 +419,6 @@ namespace BizHawk.Client.EmuHawk // feos: there's no default value other than neutral, and we can't go arbitrary here, so do nothing for now // autohold is ignored for axes too for the same reasons: lack of demand + ambiguity } - - FrameEdited(CurrentTasMovie.LastEditedFrame); } } } @@ -640,7 +638,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed); _boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName); - FrameEdited(CurrentTasMovie.LastEditedFrame); } #if false // to match previous behaviour else if (altOrShift4State is not 0) @@ -654,7 +651,6 @@ namespace BizHawk.Client.EmuHawk CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - FrameEdited(CurrentTasMovie.LastEditedFrame); } } else @@ -671,7 +667,6 @@ namespace BizHawk.Client.EmuHawk { AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].Reset(); CurrentTasMovie.SetAxisState(frame, buttonName, AxisPatterns[ControllerType.Axes.IndexOf(buttonName)].GetNextValue()); - FrameEdited(frame); _patternPaint = true; } else @@ -803,6 +798,13 @@ namespace BizHawk.Client.EmuHawk /// Returns true if the input list was redrawn. public bool FrameEdited(int frame) { + GreenzoneInvalidatedCallback?.Invoke(frame); // lua callback + + if (CurrentTasMovie.LastEditWasRecording) + { + return false; + } + bool needsRefresh = !_batchEditing; if (MouseButtonHeld || _batchEditing) { @@ -876,7 +878,6 @@ namespace BizHawk.Client.EmuHawk if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) { AxisEditRow = -1; - FrameEdited(_axisEditRow); } _axisPaintState = 0; _axisEditYPos = -1; @@ -1170,7 +1171,6 @@ namespace BizHawk.Client.EmuHawk if (_rightClickAlt || _rightClickControl || _rightClickShift) { - FrameEdited(CurrentTasMovie.LastEditedFrame); _suppressContextMenu = true; } } @@ -1197,8 +1197,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - - FrameEdited(CurrentTasMovie.LastEditedFrame); } } @@ -1222,7 +1220,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.SetAxisState(i, _startAxisDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column - FrameEdited(CurrentTasMovie.LastEditedFrame); } } @@ -1312,8 +1309,9 @@ namespace BizHawk.Client.EmuHawk return; } + BeginBatchEdit(); + int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn); - int prev = value; string prevTyped = _axisTypedValue; var range = ControllerType.Axes[_axisEditColumn]; @@ -1390,7 +1388,6 @@ namespace BizHawk.Client.EmuHawk if (_axisBackupState != _axisPaintState) { CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); - FrameEdited(_axisEditRow); } AxisEditRow = -1; @@ -1447,11 +1444,6 @@ namespace BizHawk.Client.EmuHawk { CurrentTasMovie.SetAxisState(row, _axisEditColumn, value); } - - if (value != prev) - { - FrameEdited(_axisEditRow); - } } bool didRefresh = EndBatchEdit(); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 2e9e601779..8fe80742f0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -422,11 +422,7 @@ namespace BizHawk.Client.EmuHawk _tasClipboard.Add(new TasClipboardEntry(i, line)); } - var rollbackFrame = CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - if (rollbackFrame > 0) - { - FrameEdited(rollbackFrame); - } + CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState)); } } } @@ -463,7 +459,6 @@ namespace BizHawk.Client.EmuHawk var selectionStart = TasView.SelectionStartIndex; CurrentTasMovie.InsertInput(selectionStart ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - FrameEdited(selectionStart!.Value); } } } @@ -474,8 +469,6 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var rollBackFrame = selectionStart ?? 0; _tasClipboard.Clear(); var list = TasView.SelectedRows.ToArray(); @@ -494,10 +487,11 @@ namespace BizHawk.Client.EmuHawk } Clipboard.SetDataObject(sb.ToString()); + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(list); + EndBatchEdit(); SetSplicer(); - FrameEdited(rollBackFrame); } } @@ -505,10 +499,7 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var firstWithInput = FirstNonEmptySelectedFrame; - bool needsToRollback = firstWithInput.HasValue && firstWithInput < Emulator.Frame; - var rollBackFrame = TasView.SelectionStartIndex ?? 0; - + BeginBatchEdit(); CurrentTasMovie.ChangeLog.BeginNewBatch($"Clear frames {TasView.SelectionStartIndex}-{TasView.SelectionEndIndex}"); foreach (int frame in TasView.SelectedRows) { @@ -516,13 +507,7 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.ChangeLog.EndBatch(); - - if (needsToRollback) - { - FrameEdited(rollBackFrame); - } - - RefreshDialog(); + EndBatchEdit(); } } @@ -539,11 +524,11 @@ namespace BizHawk.Client.EmuHawk return; } + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(TasView.SelectedRows.ToArray()); + EndBatchEdit(); SetTasViewRowCount(); SetSplicer(); - - FrameEdited(rollBackFrame); } } @@ -563,6 +548,7 @@ namespace BizHawk.Client.EmuHawk private void CloneFramesXTimes(int timesToClone) { + BeginBatchEdit(); for (int i = 0; i < timesToClone; i++) { if (TasView.Focused && TasView.AnyRowsSelected) @@ -575,21 +561,16 @@ namespace BizHawk.Client.EmuHawk .ToList(); CurrentTasMovie.InsertInput(insertionFrame, inputLog); - - FrameEdited(insertionFrame); } } + EndBatchEdit(); } private void InsertFrameMenuItem_Click(object sender, EventArgs e) { if (TasView.Focused && TasView.AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var insertionFrame = selectionStart ?? 0; - - CurrentTasMovie.InsertEmptyFrame(insertionFrame); - FrameEdited(insertionFrame); + CurrentTasMovie.InsertEmptyFrame(TasView.SelectionStartIndex ?? 0); } } @@ -610,12 +591,8 @@ namespace BizHawk.Client.EmuHawk { if (TasView.Focused && TasView.AnyRowsSelected) { - var rollbackFrame = TasView.SelectionEndIndex ?? 0; - - CurrentTasMovie.Truncate(rollbackFrame); + CurrentTasMovie.Truncate(TasView.SelectionEndIndex ?? 0); MarkerControl.MarkerInputRoll.TruncateSelection(CurrentTasMovie.Markers.Count - 1); - - FrameEdited(rollbackFrame); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index c02b17c8a1..d9bd0785ef 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -551,7 +551,7 @@ namespace BizHawk.Client.EmuHawk movie.InputRollSettingsForSave = () => TasView.UserSettingsSerialized(); movie.BindMarkersToInput = Settings.BindMarkersToInput; - movie.GreenzoneInvalidated = GreenzoneInvalidated; + movie.GreenzoneInvalidated = (f) => _ = FrameEdited(f); movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps; movie.PropertyChanged += TasMovie_OnPropertyChanged; @@ -860,7 +860,6 @@ namespace BizHawk.Client.EmuHawk if (insertionFrame <= CurrentTasMovie.InputLogLength) { CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames); - FrameEdited(insertionFrame); } } @@ -868,11 +867,14 @@ namespace BizHawk.Client.EmuHawk { if (beginningFrame < CurrentTasMovie.InputLogLength) { + // movie's RemoveFrames might do multiple separate invalidations + BeginBatchEdit(); + int[] framesToRemove = Enumerable.Range(beginningFrame, numberOfFrames).ToArray(); CurrentTasMovie.RemoveFrames(framesToRemove); SetSplicer(); - FrameEdited(beginningFrame); + EndBatchEdit(); } } @@ -880,13 +882,15 @@ namespace BizHawk.Client.EmuHawk { if (beginningFrame < CurrentTasMovie.InputLogLength) { + BeginBatchEdit(); + int last = Math.Min(beginningFrame + numberOfFrames, CurrentTasMovie.InputLogLength); for (int i = beginningFrame; i < last; i++) { CurrentTasMovie.ClearFrame(i); } - FrameEdited(beginningFrame); + EndBatchEdit(); } } @@ -993,7 +997,6 @@ namespace BizHawk.Client.EmuHawk } CurrentTasMovie.ChangeLog.IsRecording = wasRecording; - FrameEdited(Emulator.Frame - 1); return true; } From 1cc085e26c9f80d3264fd91127c0bd7b35c8a119 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 11 Jun 2025 19:59:43 -0500 Subject: [PATCH 18/29] Fix: Greenzone would be invalidated if pasting or clearing a frame even if no change was made. --- .../movie/tasproj/TasMovie.Editing.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index fce764f599..e9f41a70d2 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -83,9 +83,12 @@ namespace BizHawk.Client.Common public void ClearFrame(int frame) { + string empty = Bk2LogEntryGenerator.EmptyEntry(Session.MovieController); + if (GetInputLogEntry(frame) == empty) return; + ChangeLog.AddGeneralUndo(frame, frame, $"Clear Frame: {frame}"); - SetFrameAt(frame, Bk2LogEntryGenerator.EmptyEntry(Session.MovieController)); + SetFrameAt(frame, empty); Changes = true; InvalidateAfter(frame); @@ -212,6 +215,7 @@ namespace BizHawk.Client.Common if (Log.Count < states.Count + frame) { + firstChangedFrame = Log.Count; ExtendMovieForEdit(states.Count + frame - Log.Count); } @@ -225,7 +229,7 @@ namespace BizHawk.Client.Common } var entry = Bk2LogEntryGenerator.GenerateLogEntry(states[i]); - if (firstChangedFrame == -1 && Log[frame + i] != entry) + if ((firstChangedFrame == -1 || firstChangedFrame > frame + i) && Log[frame + i] != entry) { firstChangedFrame = frame + i; } @@ -235,7 +239,11 @@ namespace BizHawk.Client.Common ChangeLog.EndBatch(); Changes = true; - InvalidateAfter(frame); + if (firstChangedFrame != -1) + { + // TODO: Throw out the undo action if there are no changes. + InvalidateAfter(firstChangedFrame); + } ChangeLog.SetGeneralRedo(); return firstChangedFrame; From 4379fa70c7972be3e5eff4e5d96dcb09f75c9187 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Wed, 11 Jun 2025 21:12:04 -0500 Subject: [PATCH 19/29] Fix: Seeks from Lua would freeze the UI and not return to Lua until the seek has completed, making the Lua script unable to see the frames during the seek. --- .../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 2 +- .../tools/TAStudio/TAStudio.ListView.cs | 2 +- .../tools/TAStudio/TAStudio.Navigation.cs | 29 +------------------ 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index f554e29119..7f4db1f976 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -163,7 +163,7 @@ namespace BizHawk.Client.EmuHawk if (0.RangeToExclusive(Tastudio.CurrentTasMovie.InputLogLength).Contains(f)) { - Tastudio.GoToFrame(f, true); + Tastudio.GoToFrame(f); } _luaLibsImpl.IsUpdateSupressed = false; diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 9e8a49af72..a69090820c 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -580,7 +580,7 @@ namespace BizHawk.Client.EmuHawk if (targetCol.Name is CursorColumnName) { _startCursorDrag = true; - GoToFrame(frame, fromLua: false, OnLeftMouseDown: true); + GoToFrame(frame, OnLeftMouseDown: true); } else if (targetCol.Name is FrameColumnName) { diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index c11122c449..c82dca328a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -8,7 +8,7 @@ namespace BizHawk.Client.EmuHawk /// Seek to the given frame, past or future, and load a state first if doing so gets us there faster. /// Does nothing if we are already on the given frame. /// - public void GoToFrame(int frame, bool fromLua = false, bool OnLeftMouseDown = false) + public void GoToFrame(int frame, bool OnLeftMouseDown = false) { if (frame == Emulator.Frame) { @@ -30,33 +30,6 @@ namespace BizHawk.Client.EmuHawk } closestState.Value.Dispose(); - if (fromLua) - { - bool wasPaused = MainForm.EmulatorPaused; - - // why not use this? because I'm not letting the form freely run. it all has to be under this loop. - // i could use this and then poll StepRunLoop_Core() repeatedly, but.. that's basically what I'm doing - // PauseOnFrame = frame; - - while (Emulator.Frame != frame) - { - MainForm.SeekFrameAdvance(); - } - - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } - - // lua botting users will want to re-activate record mode automatically -- it should be like nothing ever happened - if (WasRecording) - { - TastudioRecordMode(); - } - - // now the next section won't happen since we're at the right spot - } - StartSeeking(frame); if (!OnLeftMouseDown) From d889cb133c823675744ef58c2eea5e0df606d0c8 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Fri, 13 Jun 2025 14:41:51 -0500 Subject: [PATCH 20/29] add hotkey for go to green arrow (manual restore position) Also, fix: Middle-click restore would not update seek begin frame, potentially causing seek progress indicator to be wrong. --- src/BizHawk.Client.Common/config/Binding.cs | 1 + src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs | 4 ++++ .../config/HotkeyConfig.Designer.cs | 18 +++++++++--------- .../config/HotkeyConfig.cs | 2 +- .../tools/TAStudio/TAStudio.ListView.cs | 19 +++++++------------ .../tools/TAStudio/TAStudio.Navigation.cs | 11 +++++++++++ 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/BizHawk.Client.Common/config/Binding.cs b/src/BizHawk.Client.Common/config/Binding.cs index 7989f8fcd2..a3c378d17d 100644 --- a/src/BizHawk.Client.Common/config/Binding.cs +++ b/src/BizHawk.Client.Common/config/Binding.cs @@ -133,6 +133,7 @@ namespace BizHawk.Client.Common Bind("TAStudio", "Show Cursor"); Bind("TAStudio", "Toggle Follow Cursor", "Shift+F"); Bind("TAStudio", "Toggle Auto-Restore", "Shift+R"); + Bind("TAStudio", "Seek To Green Arrow", "R"); Bind("TAStudio", "Toggle Turbo Seek", "Shift+S"); Bind("TAStudio", "Undo", "Ctrl+Z"); // TODO: these are getting not unique enough Bind("TAStudio", "Redo", "Ctrl+Y"); diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs index 2ca37938bd..86c5da113c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs @@ -378,6 +378,10 @@ namespace BizHawk.Client.EmuHawk var playbackBox1 = Tools.TAStudio.TasPlaybackBox; playbackBox1.AutoRestore = !playbackBox1.AutoRestore; break; + case "Seek To Green Arrow": + if (!Tools.IsLoaded()) return false; + Tools.TAStudio.RestorePosition(); + break; case "Toggle Turbo Seek": if (!Tools.IsLoaded()) return false; var playbackBox2 = Tools.TAStudio.TasPlaybackBox; diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs index a7e520b520..d44d4fbc3e 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs @@ -62,7 +62,7 @@ // this.AutoTabCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.AutoTabCheckBox.AutoSize = true; - this.AutoTabCheckBox.Location = new System.Drawing.Point(432, 440); + this.AutoTabCheckBox.Location = new System.Drawing.Point(462, 440); this.AutoTabCheckBox.Name = "AutoTabCheckBox"; this.AutoTabCheckBox.Size = new System.Drawing.Size(70, 17); this.AutoTabCheckBox.TabIndex = 101; @@ -79,7 +79,7 @@ this.HotkeyTabControl.Location = new System.Drawing.Point(12, 28); this.HotkeyTabControl.Name = "HotkeyTabControl"; this.HotkeyTabControl.SelectedIndex = 0; - this.HotkeyTabControl.Size = new System.Drawing.Size(729, 396); + this.HotkeyTabControl.Size = new System.Drawing.Size(759, 396); this.HotkeyTabControl.TabIndex = 102; this.HotkeyTabControl.SelectedIndexChanged += new System.EventHandler(this.HotkeyTabControl_SelectedIndexChanged); // @@ -88,7 +88,7 @@ this.tabPage1.Location = new System.Drawing.Point(4, 22); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(721, 370); + this.tabPage1.Size = new System.Drawing.Size(751, 370); this.tabPage1.TabIndex = 0; this.tabPage1.Text = "For designer"; this.tabPage1.UseVisualStyleBackColor = true; @@ -97,7 +97,7 @@ // this.IDB_CANCEL.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.IDB_CANCEL.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.IDB_CANCEL.Location = new System.Drawing.Point(681, 436); + this.IDB_CANCEL.Location = new System.Drawing.Point(711, 436); this.IDB_CANCEL.Name = "IDB_CANCEL"; this.IDB_CANCEL.Size = new System.Drawing.Size(60, 22); this.IDB_CANCEL.TabIndex = 103; @@ -109,7 +109,7 @@ // IDB_SAVE // this.IDB_SAVE.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.IDB_SAVE.Location = new System.Drawing.Point(615, 436); + this.IDB_SAVE.Location = new System.Drawing.Point(645, 436); this.IDB_SAVE.Name = "IDB_SAVE"; this.IDB_SAVE.Size = new System.Drawing.Size(60, 22); this.IDB_SAVE.TabIndex = 104; @@ -123,7 +123,7 @@ this.SearchBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.SearchBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; - this.SearchBox.Location = new System.Drawing.Point(592, 9); + this.SearchBox.Location = new System.Drawing.Point(622, 9); this.SearchBox.Name = "SearchBox"; this.SearchBox.Size = new System.Drawing.Size(149, 20); this.SearchBox.TabIndex = 106; @@ -131,7 +131,7 @@ // // label1 // - this.label1.Location = new System.Drawing.Point(556, 12); + this.label1.Location = new System.Drawing.Point(586, 12); this.label1.Name = "label1"; this.label1.Text = "Find:"; // @@ -152,7 +152,7 @@ // MiscButton // this.MiscButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.MiscButton.Location = new System.Drawing.Point(526, 436); + this.MiscButton.Location = new System.Drawing.Point(556, 436); this.MiscButton.Menu = this.clearBtnContextMenu; this.MiscButton.Name = "MiscButton"; this.MiscButton.Size = new System.Drawing.Size(60, 22); @@ -198,7 +198,7 @@ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.IDB_CANCEL; - this.ClientSize = new System.Drawing.Size(753, 463); + this.ClientSize = new System.Drawing.Size(783, 463); this.Controls.Add(this.MiscButton); this.Controls.Add(this.label3); this.Controls.Add(this.label2); diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs index 05cb11b014..98838b41e4 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs @@ -95,7 +95,7 @@ namespace BizHawk.Client.EmuHawk .OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName); int x = UIHelper.ScaleX(6); int y = UIHelper.ScaleY(14); - int iwOffsetX = UIHelper.ScaleX(110); + int iwOffsetX = UIHelper.ScaleX(120); int iwOffsetY = UIHelper.ScaleY(-4); int iwWidth = UIHelper.ScaleX(120); diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index a69090820c..c172436338 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -68,19 +68,16 @@ namespace BizHawk.Client.EmuHawk public AutoPatternBool[] BoolPatterns; public AutoPatternAxis[] AxisPatterns; - private void StartSeeking(int? frame, bool fromMiddleClick = false) + private void StartSeeking(int frame) { - if (!frame.HasValue || frame <= Emulator.Frame) + if (frame <= Emulator.Frame) { return; } - if (!fromMiddleClick) - { - _seekStartFrame = Emulator.Frame; - } + _seekStartFrame = Emulator.Frame; - _seekingTo = frame.Value; + _seekingTo = frame; MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. int? diff = _seekingTo - _seekStartFrame; @@ -524,8 +521,7 @@ namespace BizHawk.Client.EmuHawk var record = CurrentTasMovie[RestorePositionFrame]; if (record.Lagged is null) { - _pauseAfterSeeking = true; - StartSeeking(RestorePositionFrame, true); + RestorePosition(); return; } } @@ -835,10 +831,9 @@ namespace BizHawk.Client.EmuHawk } GoToFrame(frame); - if (Settings.AutoRestoreLastPosition && RestorePositionFrame != -1) + if (Settings.AutoRestoreLastPosition) { - _pauseAfterSeeking = true; // auto-restore makes no sense without auto-pause - StartSeeking(RestorePositionFrame); + RestorePosition(); } needsRefresh = false; // Refresh will happen via GoToFrame. diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index c82dca328a..85eff833df 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -55,6 +55,17 @@ namespace BizHawk.Client.EmuHawk GoToFrame(next); } + public void RestorePosition() + { + if (RestorePositionFrame != -1) + { + // restore makes no sense without pausing + // Pausing here ensures any seek done by GoToFrame pauses after completing. + MainForm.PauseEmulator(); + GoToFrame(RestorePositionFrame); + } + } + /// /// Makes the given frame visible. If no frame is given, makes the current frame visible. /// From ee35ad2179e1e54480561ce01d32e178219d4209 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Fri, 13 Jun 2025 14:48:47 -0500 Subject: [PATCH 21/29] Feature, from TASeditor: middle-click resumes seek if seek is in progress. This might be what StartSeeking's fromMiddleClick parameter (removed in last commit) was attempting to do. --- .../tools/TAStudio/TAStudio.ListView.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index c172436338..0355190f2d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -518,6 +518,12 @@ namespace BizHawk.Client.EmuHawk { if (MainForm.EmulatorPaused) { + if (_seekingTo != -1) + { + MainForm.UnpauseEmulator(); // resume seek + return; + } + var record = CurrentTasMovie[RestorePositionFrame]; if (record.Lagged is null) { From 009667e65af4749705793089d8e63fff2141aa52 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 03:19:06 -0500 Subject: [PATCH 22/29] TAStudio should own TAStudio logic. Fix: The green arrow would move when making multiple edits with auto-restore off if the edit caused a seek of >1 frame. --- .../movie/interfaces/ITasMovie.cs | 1 - .../movie/tasproj/TasMovie.cs | 8 -------- .../tools/TAStudio/TAStudio.IToolForm.cs | 5 +++++ .../tools/TAStudio/TAStudio.ListView.cs | 16 ++++++++++++---- .../tools/TAStudio/TAStudio.cs | 2 ++ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 35685b1211..ab9ab063ba 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -7,7 +7,6 @@ namespace BizHawk.Client.Common public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable { bool BindMarkersToInput { get; set; } - bool LastPositionStable { get; set; } IMovieChangeLog ChangeLog { get; } IStateManager TasStateManager { get; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index d9b65f10d8..b0bf0a1268 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -180,14 +180,6 @@ namespace BizHawk.Client.Common public void GreenzoneCurrentFrame() { - // todo: this isn't working quite right when autorestore is off and we're editing while seeking - // but accounting for that requires access to Mainform.IsSeeking - if (Emulator.Frame != LastEditedFrame) - { - // emulated a new frame, current editing segment may change now. taseditor logic - LastPositionStable = false; - } - LagLog[Emulator.Frame] = _inputPollable.IsLagFrame; // We will forbibly capture a state for the last edited frame (requested by #916 for case of "platforms with analog stick") diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 1aee961bfe..cd8f54a8ef 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -100,6 +100,11 @@ namespace BizHawk.Client.EmuHawk _doPause = !CurrentTasMovie.IsAtEnd(); } + if (!_seekingByEdit) + { + _shouldMoveGreenArrow = true; + } + FastUpdateAfter(); RefreshDialog(refreshNeeded, refreshBranches: false); } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 0355190f2d..7c4a41566d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -76,6 +76,7 @@ namespace BizHawk.Client.EmuHawk } _seekStartFrame = Emulator.Frame; + _seekingByEdit = false; _seekingTo = frame; MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. @@ -100,6 +101,7 @@ namespace BizHawk.Client.EmuHawk WasRecording = false; } + _seekingByEdit = false; _seekingTo = -1; MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. if (_pauseAfterSeeking) @@ -116,6 +118,8 @@ namespace BizHawk.Client.EmuHawk private void CancelSeek() { + _shouldMoveGreenArrow = true; + _seekingByEdit = false; _seekingTo = -1; MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek. _pauseAfterSeeking = false; @@ -829,18 +833,22 @@ namespace BizHawk.Client.EmuHawk if (Emulator.Frame > frame) { - if ((MainForm.EmulatorPaused || _seekingTo == -1) - && !CurrentTasMovie.LastPositionStable) + if (_shouldMoveGreenArrow) { - RestorePositionFrame = Emulator.Frame; - CurrentTasMovie.LastPositionStable = true; // until new frame is emulated + RestorePositionFrame = _seekingTo != -1 ? _seekingTo : Emulator.Frame; + // Green arrow should not move again until the user changes frame. + // This means any state load or unpause/frame advance/seek, that is not caused by an input edit. + // This is so that the user can make multiple edits with auto restore off, in any order, before a manual restore. + _shouldMoveGreenArrow = false; } + _seekingByEdit = true; // must be before GoToFrame (it will load a state, and state loading checks _seekingByEdit) GoToFrame(frame); if (Settings.AutoRestoreLastPosition) { RestorePosition(); } + _seekingByEdit = true; // must be after GoToFrame & RestorePosition too (they'll set _seekingByEdit to false) needsRefresh = false; // Refresh will happen via GoToFrame. } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index d9bd0785ef..52dd89bd59 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -51,6 +51,8 @@ namespace BizHawk.Client.EmuHawk /// TASEditor never kills LastPositionFrame, and it only pauses on it, if it hasn't been greenzoned beforehand and middle mouse button was pressed. /// public int RestorePositionFrame { get; private set; } + private bool _shouldMoveGreenArrow; + private bool _seekingByEdit; private int _seekingTo = -1; From 620304e948a46d7c5b4ddb5badcf21e4c3cd393b Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 13:47:16 -0500 Subject: [PATCH 23/29] Deprecate client.seekframe. This had the same issue as tastudio.setplayback, freezing the UI. Since seekframe cannot go backwards, updating it to return immediately results in it doing absolutely nothing. Note that it never was doing a "seek" as defined by MainForm, so we aren't removing that feature. And turbo-seek isn't relevant both because it wasn't a seek and because currently the only way to have a turbo-seek is to use the Play Movie dialog. If true seeking is desired a new lua method should be made. Also also, it did not actually touch InvisibleEmulation. --- .../lua/CommonLibs/ClientLuaLibrary.cs | 32 ++----------------- src/BizHawk.Client.EmuHawk/MainForm.cs | 1 - 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs index c525292d55..88fe584b88 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs @@ -102,37 +102,11 @@ namespace BizHawk.Client.Common public void InvisibleEmulation(bool invisible) => APIs.EmuClient.InvisibleEmulation(invisible); - [LuaMethodExample("client.seekframe( 100 );")] - [LuaMethod("seekframe", "Makes the emulator seek to the frame specified")] + [LuaDeprecatedMethod] + [LuaMethod("seekframe", "Does nothing. Use the pause/unpause functions instead and a loop that waits for the desired frame.")] public void SeekFrame(int frame) { - if (_luaLibsImpl.IsInInputOrMemoryCallback) - { - throw new InvalidOperationException("client.seekframe() is not allowed during input/memory callbacks"); - } - - if (frame < Emulator.Frame) - { - Log("client.seekframe: cannot seek backwards"); - return; - } - if (frame == Emulator.Frame) return; - - bool wasPaused = MainForm.EmulatorPaused; - - // can't re-enter lua while doing this - _luaLibsImpl.IsUpdateSupressed = true; - while (Emulator.Frame != frame) - { - MainForm.SeekFrameAdvance(); - } - - _luaLibsImpl.IsUpdateSupressed = false; - - if (!wasPaused) - { - MainForm.UnpauseEmulator(); - } + Log("Deprecated function client.seekframe() used. Replace the call with pause/unpause functions and a loop that waits for the desired frame."); } [LuaMethodExample("local sounds_terrible = client.get_approx_framerate() < 55;")] diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 7faf496185..b4a3ad3e39 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -1072,7 +1072,6 @@ namespace BizHawk.Client.EmuHawk /// Accessing this from Lua allows to keep internal code hacks to minimum. /// /// - /// /// /// public bool InvisibleEmulation { get; set; } From cf86c1d3b1ec89f1ec57edf0720755b89763a361 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 13:49:56 -0500 Subject: [PATCH 24/29] Fix: Manual seeks past the end of the movie were not respected if AutoPause was on. --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index cd8f54a8ef..c17606e201 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -94,7 +94,7 @@ namespace BizHawk.Client.EmuHawk refreshNeeded = true; } - if (Settings.AutoPause) + if (Settings.AutoPause && _seekingTo == -1) { if (_doPause && CurrentTasMovie.IsAtEnd()) MainForm.PauseEmulator(); _doPause = !CurrentTasMovie.IsAtEnd(); From 80127dcd004efe6d508933ef4ea0a4c6fddc40d8 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 14:05:22 -0500 Subject: [PATCH 25/29] Show seek progress bar if we are seeking more than 1 frame. We don't know how long it's going to take! Also if the user ends up pausing there should be a visual indication of seeking. --- .../tools/TAStudio/TAStudio.IToolForm.cs | 4 ++-- .../tools/TAStudio/TAStudio.ListView.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index c17606e201..e5057f76f5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -26,8 +26,8 @@ namespace BizHawk.Client.EmuHawk { if (_seekingTo != -1) { - int diff = Emulator.Frame - _seekStartFrame.Value; - int unit = _seekingTo - _seekStartFrame.Value; + int diff = Emulator.Frame - _seekStartFrame; + int unit = _seekingTo - _seekStartFrame; double progress = 0; if (diff != 0 && unit != 0) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 7c4a41566d..3b278c27f0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -57,7 +57,7 @@ namespace BizHawk.Client.EmuHawk private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; - private int? _seekStartFrame; + private int _seekStartFrame; private bool _pauseAfterSeeking; private readonly Dictionary _alternateRowColor = new(); @@ -80,13 +80,12 @@ namespace BizHawk.Client.EmuHawk _seekingTo = frame; MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. - int? diff = _seekingTo - _seekStartFrame; WasRecording = CurrentTasMovie.IsRecording() || WasRecording; TastudioPlayMode(); // suspend rec mode until seek ends, to allow mouse editing MainForm.UnpauseEmulator(); - if (diff > TasView.VisibleRows) + if (_seekingTo - _seekStartFrame > 1) { MessageStatusLabel.Text = "Seeking..."; ProgressBar.Visible = true; From 65e413388327cfb505cae22bbe2e49cc5cedcd0e Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 14:08:17 -0500 Subject: [PATCH 26/29] Remove redundant code. --- src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 3b278c27f0..7fe625350b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -80,9 +80,6 @@ namespace BizHawk.Client.EmuHawk _seekingTo = frame; MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek. - - WasRecording = CurrentTasMovie.IsRecording() || WasRecording; - TastudioPlayMode(); // suspend rec mode until seek ends, to allow mouse editing MainForm.UnpauseEmulator(); if (_seekingTo - _seekStartFrame > 1) From 2749b1b675bc74ccd38584e55451570fbe3fb100 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 21:55:52 -0500 Subject: [PATCH 27/29] Fix: tastudio.setplayback would refuse to seek past the end of a movie --- .../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index 7f4db1f976..eccb3d31ad 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -161,7 +161,7 @@ namespace BizHawk.Client.EmuHawk f = Tastudio.CurrentTasMovie.Markers[f].Frame; } - if (0.RangeToExclusive(Tastudio.CurrentTasMovie.InputLogLength).Contains(f)) + if (f >= 0) { Tastudio.GoToFrame(f); } From 88cf00d4b63d7d5708fd2266fbce432ceafabb50 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 14 Jun 2025 22:02:46 -0500 Subject: [PATCH 28/29] Fix: Using tastudio.setplayback to seek to a non-existent marker would permanently suppress Lua. Fix: Using tastudio.setplayback with a Lua number that happens to not currently be represented as an integer would throw. --- .../tools/Lua/Libraries/TAStudioLuaLibrary.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index eccb3d31ad..7b844ed783 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -143,30 +143,30 @@ namespace BizHawk.Client.EmuHawk throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks"); } - _luaLibsImpl.IsUpdateSupressed = true; int f; if (frame is long frameNumber) { f = (int)frameNumber; } + else if (frame is double frameNumber2) + { + f = (int)frameNumber2; + } else { - f = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame); - if (f == -1) - { - return; - } + int markerIndex = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame); + if (markerIndex == -1) return; - f = Tastudio.CurrentTasMovie.Markers[f].Frame; + f = Tastudio.CurrentTasMovie.Markers[markerIndex].Frame; } if (f >= 0) { + _luaLibsImpl.IsUpdateSupressed = true; Tastudio.GoToFrame(f); + _luaLibsImpl.IsUpdateSupressed = false; } - - _luaLibsImpl.IsUpdateSupressed = false; } } From d8bd53d1c66a15ac5994d4a36e38e4bb9154d657 Mon Sep 17 00:00:00 2001 From: SuuperW Date: Sat, 21 Jun 2025 14:29:37 -0500 Subject: [PATCH 29/29] Safety for any custom or future tools that want to use this. --- .../tools/TAStudio/TAStudio.ListView.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 7fe625350b..5b5b99cac8 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -787,9 +787,15 @@ namespace BizHawk.Client.EmuHawk // This is only caled from Lua. _editIsFromLua = true; BeginBatchEdit(); - action(); - EndBatchEdit(); - _editIsFromLua = false; + try + { + action(); + } + finally + { + EndBatchEdit(); + _editIsFromLua = false; + } } ///