Skip to content

Fixes to seeking and auto restore #4347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6c48f7c
rename variable
SuuperW Jun 5, 2025
fe9f876
move safety check inside StartSeeking, instead of making caller respo…
SuuperW Jun 4, 2025
e2acfa6
tastudio should own tastudio logic
SuuperW Jun 5, 2025
a3af987
Make TAStudio take ownership of its own seeking logic and simplify so…
SuuperW Jun 5, 2025
31e6eeb
Remove obsolete conditional.
SuuperW Jun 7, 2025
fb231b0
Fix: Mouse drag seeking was broken while seeking. (It would not chang…
SuuperW Jun 7, 2025
3ef7dce
GoToFrame's "future frame" cases were just buggy versions of the norm…
SuuperW Jun 7, 2025
5575739
Fix: modifier key + right click would jump to the last edited frame (…
SuuperW Jun 7, 2025
fe29ffd
simplify a bit of logic
SuuperW Jun 7, 2025
68f7824
Fix: Clicking on an axis value while in axis editing mode would disab…
SuuperW Jun 7, 2025
850d3c5
Windows weirdness: MouseUp event is not guaranteed to be raised.
SuuperW Jun 7, 2025
553336c
Replace some auto restore code with something simpler. Fixes auto-res…
SuuperW Jun 7, 2025
ffc5044
Revert commit a1c8c4ed3a676954358cb493efc878436bd43a4e. It was always…
SuuperW Jun 8, 2025
bbf3560
Convert remaining auto-restore points to the new system. Also include…
SuuperW Jun 8, 2025
6725814
inline function that is only called from one place
SuuperW Jun 8, 2025
23f0716
Fix: turbo seek did not work when navigating while unpaused
SuuperW Jun 8, 2025
0634b37
Call our new FrameEdited thing for auto restore whenever greenzone is…
SuuperW Jun 11, 2025
1cc085e
Fix: Greenzone would be invalidated if pasting or clearing a frame ev…
SuuperW Jun 12, 2025
4379fa7
Fix: Seeks from Lua would freeze the UI and not return to Lua until t…
SuuperW Jun 12, 2025
d889cb1
add hotkey for go to green arrow (manual restore position)
SuuperW Jun 13, 2025
ee35ad2
Feature, from TASeditor: middle-click resumes seek if seek is in prog…
SuuperW Jun 13, 2025
009667e
TAStudio should own TAStudio logic. Fix: The green arrow would move w…
SuuperW Jun 14, 2025
620304e
Deprecate client.seekframe. This had the same issue as tastudio.setpl…
SuuperW Jun 14, 2025
cf86c1d
Fix: Manual seeks past the end of the movie were not respected if Aut…
SuuperW Jun 14, 2025
80127dc
Show seek progress bar if we are seeking more than 1 frame. We don't …
SuuperW Jun 14, 2025
65e4133
Remove redundant code.
SuuperW Jun 14, 2025
2749b1b
Fix: tastudio.setplayback would refuse to seek past the end of a movie
SuuperW Jun 15, 2025
88cf00d
Fix: Using tastudio.setplayback to seek to a non-existent marker woul…
SuuperW Jun 15, 2025
d8bd53d
Safety for any custom or future tools that want to use this.
SuuperW Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/BizHawk.Client.Common/config/Binding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
32 changes: 3 additions & 29 deletions src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;")]
Expand Down
2 changes: 1 addition & 1 deletion src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -20,6 +19,7 @@ public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable
TasLagLog LagLog { get; }
IStringLog VerificationLog { get; }
int LastEditedFrame { get; }
bool LastEditWasRecording { get; }

Action<int> GreenzoneInvalidated { get; set; }

Expand Down
15 changes: 12 additions & 3 deletions src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public override void RecordFrame(int frame, IController source)

if (this.IsRecording())
{
LastEditWasRecording = true;
InvalidateAfter(frame);
}

Expand Down Expand Up @@ -82,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);
Expand Down Expand Up @@ -211,6 +215,7 @@ public int CopyOverInput(int frame, IEnumerable<IController> inputStates)

if (Log.Count < states.Count + frame)
{
firstChangedFrame = Log.Count;
ExtendMovieForEdit(states.Count + frame - Log.Count);
}

Expand All @@ -224,7 +229,7 @@ public int CopyOverInput(int frame, IEnumerable<IController> 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;
}
Expand All @@ -234,7 +239,11 @@ public int CopyOverInput(int frame, IEnumerable<IController> 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;
Expand Down
10 changes: 2 additions & 8 deletions src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -178,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")
Expand Down
4 changes: 4 additions & 0 deletions src/BizHawk.Client.EmuHawk/MainForm.Hotkey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TAStudio>()) return false;
Tools.TAStudio.RestorePosition();
break;
case "Toggle Turbo Seek":
if (!Tools.IsLoaded<TAStudio>()) return false;
var playbackBox2 = Tools.TAStudio.TasPlaybackBox;
Expand Down
12 changes: 1 addition & 11 deletions src/BizHawk.Client.EmuHawk/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,6 @@ private set
/// Accessing this from Lua allows to keep internal code hacks to minimum.
/// <list type="bullet">
/// <item><description><see cref="ClientLuaLibrary.InvisibleEmulation(bool)"/></description></item>
/// <item><description><see cref="ClientLuaLibrary.SeekFrame(int)"/></description></item>
/// </list>
/// </summary>
public bool InvisibleEmulation { get; set; }
Expand Down Expand Up @@ -3256,16 +3255,7 @@ private void StepRunLoop_Core(bool force = false)
if (PauseOnFrame.Value == Emulator.Frame)
{
PauseEmulator();
if (Tools.IsLoaded<TAStudio>()) Tools.TAStudio.StopSeeking();
else PauseOnFrame = null;
}
else if (Tools.IsLoaded<TAStudio>()
&& Tools.TAStudio.LastPositionFrame == 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;
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/BizHawk.Client.EmuHawk/config/HotkeyConfig.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (0.RangeToExclusive(Tastudio.CurrentTasMovie.InputLogLength).Contains(f))
if (f >= 0)
{
Tastudio.GoToFrame(f, true);
_luaLibsImpl.IsUpdateSupressed = true;
Tastudio.GoToFrame(f);
_luaLibsImpl.IsUpdateSupressed = false;
}

_luaLibsImpl.IsUpdateSupressed = false;
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -309,7 +314,7 @@ public void ApplyInputChanges()

_luaLibsImpl.IsUpdateSupressed = true;

if (_changeList.Count > 0)
Tastudio.ApiHawkBatchEdit(() =>
{
int size = _changeList.Count;

Expand All @@ -327,7 +332,6 @@ public void ApplyInputChanges()
Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis);
break;
}
Tastudio.RefreshForInputChange(_changeList[i].Frame);
break;
case LuaChangeTypes.InsertFrames:
Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number);
Expand All @@ -341,9 +345,7 @@ public void ApplyInputChanges()
}
}
_changeList.Clear();
Tastudio.JumpToGreenzone();
Tastudio.DoAutoRestore();
}
});

_luaLibsImpl.IsUpdateSupressed = false;
}
Expand Down
25 changes: 2 additions & 23 deletions src/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand Down
Loading