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 89eb30349b2..d500faaa8f2 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3260,7 +3260,7 @@ private void StepRunLoop_Core(bool force = false) 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 770481fe4cb..611e4196d28 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 @@ private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitm ? 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) { 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 56ada6ce1af..22571e42d36 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 @@ private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = 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 93519700d65..2111e792be9 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -50,7 +50,7 @@ public static Icon ToolIcon /// 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 @@ public TAStudio() 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 @@ private void SetTasViewRowCount() 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 611e4196d28..86bb32c1fef 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 @@ public void JumpToGreenzone(bool OnLeftMouseDown = false) private void StartSeeking(int? frame, bool fromMiddleClick = false) { - if (!frame.HasValue) + if (!frame.HasValue || frame <= Emulator.Frame) { return; } @@ -524,7 +524,7 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 2111e792be9..903b8cfa942 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -830,10 +830,7 @@ public void DoAutoRestore() { 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 @@ private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRew // 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 17ad218b943..6085159feba 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -177,32 +177,11 @@ private TasBranch CreateBranch() }; } - 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 @@ private void UndoBranchToolStripMenuItem_Click(object sender, EventArgs e) { 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 903b8cfa942..6a8c07e49ae 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -1250,5 +1250,28 @@ private void HandleRotationChanged(object sender, EventArgs e) 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 d500faaa8f2..7faf496185c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3256,16 +3256,7 @@ private void StepRunLoop_Core(bool force = false) 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 f8860f73d97..272afddc2b7 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 void CaptureRewind() 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 4cdad461d63..1aee961bfe3 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 @@ public partial class TAStudio : IToolForm 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 @@ protected override void UpdateAfter() _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 86bb32c1fef..df528f66da5 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 @@ private int AxisEditRow 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 @@ private void StartSeeking(int? frame, bool fromMiddleClick = false) 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 @@ public void StopSeeking(bool skipRecModeCheck = false) 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 @@ public void StopSeeking(bool skipRecModeCheck = false) } } + 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 @@ private void TasView_QueryItemIcon(int index, RollColumn column, ref Bitmap bitm 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 @@ private void TasView_QueryRowBkColor(int index, ref Color color) 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 @@ private void TasView_MouseUp(object sender, MouseEventArgs e) _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 @@ private void TasView_MouseWheel(object sender, MouseEventArgs e) 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } } - 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 5448462118a..feab6897eaa 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 @@ private void RightClickMenu_Opened(object sender, EventArgs e) 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 @@ private void RightClickMenu_Opened(object sender, EventArgs e) 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 22571e42d36..ebfdf3da362 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 @@ private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = { if (frame <= Emulator.Frame) { - if ((MainForm.EmulatorPaused || !MainForm.IsSeeking) + if ((MainForm.EmulatorPaused || _seekingTo == -1) && !CurrentTasMovie.LastPositionStable) { RestorePositionFrame = Emulator.Frame; @@ -67,14 +67,6 @@ public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = fals } } - 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 6a8c07e49ae..e83747a40ae 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -52,6 +52,8 @@ public static Icon ToolIcon /// public int RestorePositionFrame { get; private set; } + private int _seekingTo = -1; + [ConfigPersist] public TAStudioSettings Settings { get; set; } = new TAStudioSettings(); @@ -832,16 +834,6 @@ public void DoAutoRestore() { 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 @@ private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRew 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 @@ private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRew // 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 @@ private void DoTriggeredAutoRestoreIfNeeded() DoAutoRestore(); _triggerAutoRestore = false; - _autoRestorePaused = null; } } @@ -1270,7 +1270,7 @@ public void LoadBranch(TasBranch branch) 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 e83747a40ae..05593f71dee 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -861,7 +861,7 @@ private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRew 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 df528f66da5..3e2c629dc40 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } } - 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 ebfdf3da362..1daae75f897 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 @@ private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = } } - 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 05593f71dee..50dc99111e4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -845,71 +845,6 @@ private KeyValuePair GetPriorStateForFramebuffer(int frame) 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 3e2c629dc40..22bf8bf088e 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 22bf8bf088e..894d58a0d11 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) } } + /// + /// 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 @@ private void ClearLeftMouseStates() if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) { AxisEditRow = -1; - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + FrameEdited(_axisEditRow); RefreshDialog(); } _axisPaintState = 0; @@ -940,7 +950,6 @@ private void TasView_MouseDoubleClick(object sender, MouseEventArgs e) } else { - ClearLeftMouseStates(); MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value); } } @@ -1345,10 +1354,7 @@ public void EditAnalogProgrammatically(KeyEventArgs e) if (_axisBackupState != _axisPaintState) { CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); - _triggerAutoRestore = Emulator.Frame > _axisEditRow; - TastudioPlayMode(true); - JumpToGreenzone(); - DoTriggeredAutoRestoreIfNeeded(); + FrameEdited(_axisEditRow); } AxisEditRow = -1; @@ -1406,12 +1412,9 @@ public void EditAnalogProgrammatically(KeyEventArgs e) 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 894d58a0d11..31b0c9e43d1 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) _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 f565d8b4d68..657f8edf57a 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 @@ private void InitializeComponent() 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 50dc99111e4..aba4049497f 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -1004,7 +1004,19 @@ private void TAStudio_DragDrop(object sender, DragEventArgs e) 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 31b0c9e43d1..38cd64e404b 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 @@ public partial class TAStudio // Input Painting private string _startBoolDrawColumn = ""; private string _startAxisDrawColumn = ""; - private bool _drewAxis; private bool _boolPaintState; private int _axisPaintState; private int _axisBackupState; @@ -26,7 +25,7 @@ public partial class TAStudio 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 @@ private int AxisEditRow 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 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e // 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) } 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - _triggerAutoRestore = true; - TastudioPlayMode(true); + FrameEdited(CurrentTasMovie.LastEditedFrame); RefreshDialog(); } } @@ -788,25 +781,40 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) /// /// 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 @@ private void ClearLeftMouseStates() } _axisPaintState = 0; _axisEditYPos = -1; - _leftButtonHeld = false; if (!AxisEditingMode) { @@ -870,17 +877,8 @@ private void TasView_MouseUp(object sender, MouseEventArgs e) } 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 @@ private void TasView_MouseUp(object sender, MouseEventArgs e) } } + if (_mouseEditMinFrame != -1) + { + FrameEdited(_mouseEditMinFrame); + } + _suppressContextMenu = false; } @@ -968,11 +971,6 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e if (_rightClickAlt || _rightClickControl || _rightClickShift) { - _triggerAutoRestore = true; - TastudioPlayMode(true); - JumpToGreenzone(); + FrameEdited(CurrentTasMovie.LastEditedFrame); _suppressContextMenu = true; } } @@ -1150,11 +1146,7 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } 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 1daae75f897..296040cc3ea 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 @@ private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = 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 aba4049497f..18a60755a02 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -885,17 +885,6 @@ private void SetSplicer() 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 feab6897eaa..aa5adf686f2 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 @@ private void PasteMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -473,7 +473,7 @@ private void PasteInsertMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -514,7 +514,7 @@ private void CutMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -540,7 +540,7 @@ private void ClearFramesMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -554,7 +554,7 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) if (rollBackFrame >= CurrentTasMovie.InputLogLength) { // Cannot delete non-existent frames - FullRefresh(); + RefreshDialog(); return; } @@ -568,7 +568,7 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -608,7 +608,7 @@ private void CloneFramesXTimes(int timesToClone) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } } @@ -629,7 +629,7 @@ private void InsertFrameMenuItem_Click(object sender, EventArgs e) DoAutoRestore(); } - FullRefresh(); + RefreshDialog(); } } @@ -661,7 +661,7 @@ private void TruncateMenuItem_Click(object sender, EventArgs e) 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 18a60755a02..419b018fd14 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -775,20 +775,6 @@ protected override string WindowTitle 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 1593a24edb6..4a7f3a9422c 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 @@ public void SubmitClearFrames(int frame, int number) [LuaMethod("applyinputchanges", "")] public void ApplyInputChanges() { + if (_changeList.Count == 0) + { + return; + } + if (Engaged()) { if (_luaLibsImpl.IsInInputOrMemoryCallback) @@ -309,7 +314,7 @@ public void ApplyInputChanges() _luaLibsImpl.IsUpdateSupressed = true; - if (_changeList.Count > 0) + Tastudio.ApiHawkBatchEdit(() => { int size = _changeList.Count; @@ -327,7 +332,7 @@ public void ApplyInputChanges() 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 @@ public void ApplyInputChanges() } } _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 38cd64e404b..789beba5c2a 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 @@ public partial class TAStudio 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 @@ private int AxisEditRow 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 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e if (columnName == FrameColumnName) { CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, ""); + RefreshDialog(); } else if (columnName != CursorColumnName) { @@ -433,8 +428,6 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e FrameEdited(CurrentTasMovie.LastEditedFrame); } - - RefreshDialog(); } } @@ -653,7 +646,6 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); FrameEdited(CurrentTasMovie.LastEditedFrame); - RefreshDialog(); } } else @@ -685,6 +676,7 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) { 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) } } + /// + /// 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 @@ private void ClearLeftMouseStates() { AxisEditRow = -1; FrameEdited(_axisEditRow); - RefreshDialog(); } _axisPaintState = 0; _axisEditYPos = -1; @@ -892,10 +943,7 @@ private void TasView_MouseUp(object sender, MouseEventArgs e) } } - 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } SetSplicer(); + RefreshDialog(); } else if (_rightClickFrame != -1) { @@ -1171,7 +1220,6 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e { TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed } - - SetTasViewRowCount(); } private void TasView_MouseMove(object sender, MouseEventArgs e) @@ -1405,7 +1451,11 @@ public void EditAnalogProgrammatically(KeyEventArgs e) } } - RefreshDialog(); + bool didRefresh = EndBatchEdit(); + if (!didRefresh && (prevTyped != _axisTypedValue || !AxisEditingMode)) + { + RefreshDialog(); + } } private void TasView_KeyDown(object sender, KeyEventArgs e) @@ -1436,8 +1486,6 @@ private void TasView_KeyDown(object sender, KeyEventArgs e) { 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 aa5adf686f2..2e9e6017795 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 @@ private void PasteMenuItem_Click(object sender, EventArgs e) 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 @@ private void PasteInsertMenuItem_Click(object sender, EventArgs e) } 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 @@ private void CutMenuItem_Click(object sender, EventArgs e) if (TasView.Focused && TasView.AnyRowsSelected) { var selectionStart = TasView.SelectionStartIndex; - var needsToRollback = selectionStart < Emulator.Frame; var rollBackFrame = selectionStart ?? 0; _tasClipboard.Clear(); @@ -508,13 +497,7 @@ private void CutMenuItem_Click(object sender, EventArgs e) CurrentTasMovie.RemoveFrames(list); SetSplicer(); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(rollBackFrame); } } @@ -536,8 +519,7 @@ private void ClearFramesMenuItem_Click(object sender, EventArgs e) if (needsToRollback) { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); + FrameEdited(rollBackFrame); } RefreshDialog(); @@ -549,7 +531,6 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) 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 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) SetTasViewRowCount(); SetSplicer(); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(rollBackFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(rollBackFrame); } } @@ -594,7 +569,6 @@ private void CloneFramesXTimes(int timesToClone) { 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 @@ private void CloneFramesXTimes(int timesToClone) CurrentTasMovie.InsertInput(insertionFrame, inputLog); - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(insertionFrame); - DoAutoRestore(); - } - - RefreshDialog(); + FrameEdited(insertionFrame); } } } @@ -619,17 +587,9 @@ private void InsertFrameMenuItem_Click(object sender, EventArgs e) { 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 @@ private void TruncateMenuItem_Click(object sender, EventArgs e) 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 419b018fd14..c02b17c8a1e 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -800,28 +800,12 @@ public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = tru } } - 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 @@ public void InsertNumFrames(int insertionFrame, int numberOfFrames) { 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 @@ public void DeleteFrames(int beginningFrame, int numberOfFrames) CurrentTasMovie.RemoveFrames(framesToRemove); SetSplicer(); - var needsToRollback = beginningFrame < Emulator.Frame; - if (needsToRollback) - { - GoToLastEmulatedFrameIfNecessary(beginningFrame); - DoAutoRestore(); - } - else - { - RefreshForInputChange(beginningFrame); - } + FrameEdited(beginningFrame); } } @@ -916,22 +880,13 @@ public void ClearFrames(int beginningFrame, int numberOfFrames) { 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 @@ private bool AutoAdjustInput() } 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 789beba5c2a..a31d6b90dc8 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 @@ public bool FrameEdited(int frame) 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 296040cc3ea..1d5f735e663 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 @@ public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = fals } } - /// - /// 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 a31d6b90dc8..2f67d627598 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 @@ private int AxisEditRow private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; private int? _seekStartFrame; - private bool _unpauseAfterSeeking; + private bool _pauseAfterSeeking; private readonly Dictionary _alternateRowColor = new(); @@ -105,13 +105,7 @@ public void StopSeeking(bool skipRecModeCheck = false) _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 @@ private void CancelSeek() { _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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) var record = CurrentTasMovie[RestorePositionFrame]; if (record.Lagged is null) { + _pauseAfterSeeking = true; StartSeeking(RestorePositionFrame, true); return; } @@ -587,7 +582,7 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 @@ public bool FrameEdited(int frame) 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 1d5f735e663..c11122c4498 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 @@ public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = fals // 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 @@ public void GoToFrame(int frame, bool fromLua = false, bool fromRewinding = fals // 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 0752930020c..35685b12119 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -20,6 +20,7 @@ public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable 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 9b72f801272..fce764f599a 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 @@ public override void RecordFrame(int frame, IController source) 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 9fb2bb5dace..d9b65f10d80 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -73,6 +73,7 @@ public override bool StartsFromSavestate 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 @@ private void InvalidateAfter(int frame) } 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 4a7f3a9422c..f554e29119c 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 @@ public void ApplyInputChanges() 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 a8de69c7d02..8c41e3972dc 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 @@ public partial class TAStudio 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 2f67d627598..9e8a49af720 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 @@ private void TasView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e // 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) } 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) CurrentTasMovie.ToggleBoolState(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); - FrameEdited(CurrentTasMovie.LastEditedFrame); } } else @@ -671,7 +667,6 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) { 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 @@ public void ApiHawkBatchEdit(Action action) /// 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 @@ private void ClearLeftMouseStates() if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) { AxisEditRow = -1; - FrameEdited(_axisEditRow); } _axisPaintState = 0; _axisEditYPos = -1; @@ -1170,7 +1171,6 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e if (_rightClickAlt || _rightClickControl || _rightClickShift) { - FrameEdited(CurrentTasMovie.LastEditedFrame); _suppressContextMenu = true; } } @@ -1197,8 +1197,6 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } 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 @@ private void TasView_PointedCellChanged(object sender, InputRoll.CellEventArgs e } 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 @@ public void EditAnalogProgrammatically(KeyEventArgs e) return; } + BeginBatchEdit(); + int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn); - int prev = value; string prevTyped = _axisTypedValue; var range = ControllerType.Axes[_axisEditColumn]; @@ -1390,7 +1388,6 @@ public void EditAnalogProgrammatically(KeyEventArgs e) if (_axisBackupState != _axisPaintState) { CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); - FrameEdited(_axisEditRow); } AxisEditRow = -1; @@ -1447,11 +1444,6 @@ public void EditAnalogProgrammatically(KeyEventArgs e) { 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 2e9e6017795..8fe80742f08 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 @@ private void PasteMenuItem_Click(object sender, EventArgs e) _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 @@ private void PasteInsertMenuItem_Click(object sender, EventArgs e) var selectionStart = TasView.SelectionStartIndex; CurrentTasMovie.InsertInput(selectionStart ?? 0, _tasClipboard.Select(static x => x.ControllerState)); - FrameEdited(selectionStart!.Value); } } } @@ -474,8 +469,6 @@ private void CutMenuItem_Click(object sender, EventArgs e) { if (TasView.Focused && TasView.AnyRowsSelected) { - var selectionStart = TasView.SelectionStartIndex; - var rollBackFrame = selectionStart ?? 0; _tasClipboard.Clear(); var list = TasView.SelectedRows.ToArray(); @@ -494,10 +487,11 @@ private void CutMenuItem_Click(object sender, EventArgs e) } Clipboard.SetDataObject(sb.ToString()); + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(list); + EndBatchEdit(); SetSplicer(); - FrameEdited(rollBackFrame); } } @@ -505,10 +499,7 @@ private void ClearFramesMenuItem_Click(object sender, EventArgs e) { 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 @@ private void ClearFramesMenuItem_Click(object sender, EventArgs e) } CurrentTasMovie.ChangeLog.EndBatch(); - - if (needsToRollback) - { - FrameEdited(rollBackFrame); - } - - RefreshDialog(); + EndBatchEdit(); } } @@ -539,11 +524,11 @@ private void DeleteFramesMenuItem_Click(object sender, EventArgs e) return; } + BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations CurrentTasMovie.RemoveFrames(TasView.SelectedRows.ToArray()); + EndBatchEdit(); SetTasViewRowCount(); SetSplicer(); - - FrameEdited(rollBackFrame); } } @@ -563,6 +548,7 @@ private void CloneFramesXTimesMenuItem_Click(object sender, EventArgs e) private void CloneFramesXTimes(int timesToClone) { + BeginBatchEdit(); for (int i = 0; i < timesToClone; i++) { if (TasView.Focused && TasView.AnyRowsSelected) @@ -575,21 +561,16 @@ private void CloneFramesXTimes(int timesToClone) .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 @@ private void TruncateMenuItem_Click(object sender, EventArgs e) { 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 c02b17c8a1e..d9bd0785ef0 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -551,7 +551,7 @@ private bool StartNewMovieWrapper(ITasMovie movie, bool isNew) 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 @@ public void InsertNumFrames(int insertionFrame, int numberOfFrames) if (insertionFrame <= CurrentTasMovie.InputLogLength) { CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames); - FrameEdited(insertionFrame); } } @@ -868,11 +867,14 @@ public void DeleteFrames(int beginningFrame, int numberOfFrames) { 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 @@ public void ClearFrames(int beginningFrame, int numberOfFrames) { 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 @@ private bool AutoAdjustInput() } 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 fce764f599a..e9f41a70d27 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 @@ public void SetFrame(int frame, string source) 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 @@ public int CopyOverInput(int frame, IEnumerable inputStates) if (Log.Count < states.Count + frame) { + firstChangedFrame = Log.Count; ExtendMovieForEdit(states.Count + frame - Log.Count); } @@ -225,7 +229,7 @@ public int CopyOverInput(int frame, IEnumerable inputStates) } 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 @@ public int CopyOverInput(int frame, IEnumerable inputStates) 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 f554e29119c..7f4db1f976c 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 @@ public void SetPlayback(object frame) 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 9e8a49af720..a69090820c1 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) 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 c11122c4498..c82dca328a6 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 @@ 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 OnLeftMouseDown = false) + public void GoToFrame(int frame, bool OnLeftMouseDown = false) { if (frame == Emulator.Frame) { @@ -30,33 +30,6 @@ public void GoToFrame(int frame, bool fromLua = false, bool OnLeftMouseDown = fa } 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 7989f8fcd28..a3c378d17db 100644 --- a/src/BizHawk.Client.Common/config/Binding.cs +++ b/src/BizHawk.Client.Common/config/Binding.cs @@ -133,6 +133,7 @@ void Bind(string tabGroup, string displayName, string defaultBinding = "", strin 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 2ca37938bda..86c5da113c9 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs @@ -378,6 +378,10 @@ void SelectAndLoadFromSlot(int slot) 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 a7e520b520e..d44d4fbc3e4 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs @@ -62,7 +62,7 @@ private void InitializeComponent() // 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() // 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 @@ private void InitializeComponent() // 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() // // 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 @@ private void InitializeComponent() // 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 @@ private void InitializeComponent() 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 05cb11b0145..98838b41e49 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs @@ -95,7 +95,7 @@ private void DoTabs() .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 a69090820c1..c172436338c 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 @@ private int AxisEditRow 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) var record = CurrentTasMovie[RestorePositionFrame]; if (record.Lagged is null) { - _pauseAfterSeeking = true; - StartSeeking(RestorePositionFrame, true); + RestorePosition(); return; } } @@ -835,10 +831,9 @@ public bool FrameEdited(int frame) } 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 c82dca328a6..85eff833df7 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 @@ public void GoToNextMarker() 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 c172436338c..0355190f2df 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 @@ private void TasView_MouseDown(object sender, MouseEventArgs e) { 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 35685b12119..ab9ab063ba5 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 d9b65f10d80..b0bf0a1268f 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -180,14 +180,6 @@ private static string CreateDisplayValueForButton(IController adapter, string bu 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 1aee961bfe3..cd8f54a8ef4 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 @@ protected override void UpdateAfter() _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 0355190f2df..7c4a41566df 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 @@ private void StartSeeking(int frame) } _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 @@ public void StopSeeking(bool skipRecModeCheck = false) 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 @@ public void StopSeeking(bool skipRecModeCheck = false) 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 @@ public bool FrameEdited(int frame) 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 d9bd0785ef0..52dd89bd596 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -51,6 +51,8 @@ public static Icon ToolIcon /// 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 c525292d553..88fe584b880 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs @@ -102,37 +102,11 @@ public string GetLuaEngine() 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 7faf496185c..b4a3ad3e39d 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -1072,7 +1072,6 @@ private set /// 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 cd8f54a8ef4..c17606e201e 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 @@ protected override void UpdateAfter() 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 c17606e201e..e5057f76f5f 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 @@ private void UpdateProgressBar() { 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 7c4a41566df..3b278c27f0c 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 @@ private int AxisEditRow private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; - private int? _seekStartFrame; + private int _seekStartFrame; private bool _pauseAfterSeeking; private readonly Dictionary _alternateRowColor = new(); @@ -80,13 +80,12 @@ private void StartSeeking(int frame) _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 3b278c27f0c..7fe625350b2 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 @@ private void StartSeeking(int frame) _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 7f4db1f976c..eccb3d31adb 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 @@ public void SetPlayback(object frame) 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 eccb3d31adb..7b844ed783f 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 @@ public void SetPlayback(object frame) 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 7fe625350b2..5b5b99cac85 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 @@ public void ApiHawkBatchEdit(Action action) // This is only caled from Lua. _editIsFromLua = true; BeginBatchEdit(); - action(); - EndBatchEdit(); - _editIsFromLua = false; + try + { + action(); + } + finally + { + EndBatchEdit(); + _editIsFromLua = false; + } } ///