Merge pull request #4347 from SuuperW/auto_restore

Fixes to seeking and auto restore
This commit is contained in:
SuuperW 2025-06-21 14:35:17 -05:00 committed by GitHub
commit b9d78a6046
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 360 additions and 560 deletions

View File

@ -133,6 +133,7 @@ namespace BizHawk.Client.Common
Bind("TAStudio", "Show Cursor");
Bind("TAStudio", "Toggle Follow Cursor", "Shift+F");
Bind("TAStudio", "Toggle Auto-Restore", "Shift+R");
Bind("TAStudio", "Seek To Green Arrow", "R");
Bind("TAStudio", "Toggle Turbo Seek", "Shift+S");
Bind("TAStudio", "Undo", "Ctrl+Z"); // TODO: these are getting not unique enough
Bind("TAStudio", "Redo", "Ctrl+Y");

View File

@ -102,37 +102,11 @@ namespace BizHawk.Client.Common
public void InvisibleEmulation(bool invisible)
=> APIs.EmuClient.InvisibleEmulation(invisible);
[LuaMethodExample("client.seekframe( 100 );")]
[LuaMethod("seekframe", "Makes the emulator seek to the frame specified")]
[LuaDeprecatedMethod]
[LuaMethod("seekframe", "Does nothing. Use the pause/unpause functions instead and a loop that waits for the desired frame.")]
public void SeekFrame(int frame)
{
if (_luaLibsImpl.IsInInputOrMemoryCallback)
{
throw new InvalidOperationException("client.seekframe() is not allowed during input/memory callbacks");
}
if (frame < Emulator.Frame)
{
Log("client.seekframe: cannot seek backwards");
return;
}
if (frame == Emulator.Frame) return;
bool wasPaused = MainForm.EmulatorPaused;
// can't re-enter lua while doing this
_luaLibsImpl.IsUpdateSupressed = true;
while (Emulator.Frame != frame)
{
MainForm.SeekFrameAdvance();
}
_luaLibsImpl.IsUpdateSupressed = false;
if (!wasPaused)
{
MainForm.UnpauseEmulator();
}
Log("Deprecated function client.seekframe() used. Replace the call with pause/unpause functions and a loop that waits for the desired frame.");
}
[LuaMethodExample("local sounds_terrible = client.get_approx_framerate() < 55;")]

View File

@ -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; }
@ -20,6 +19,7 @@ namespace BizHawk.Client.Common
TasLagLog LagLog { get; }
IStringLog VerificationLog { get; }
int LastEditedFrame { get; }
bool LastEditWasRecording { get; }
Action<int> GreenzoneInvalidated { get; set; }

View File

@ -27,6 +27,7 @@ namespace BizHawk.Client.Common
if (this.IsRecording())
{
LastEditWasRecording = true;
InvalidateAfter(frame);
}
@ -82,9 +83,12 @@ namespace BizHawk.Client.Common
public void ClearFrame(int frame)
{
string empty = Bk2LogEntryGenerator.EmptyEntry(Session.MovieController);
if (GetInputLogEntry(frame) == empty) return;
ChangeLog.AddGeneralUndo(frame, frame, $"Clear Frame: {frame}");
SetFrameAt(frame, Bk2LogEntryGenerator.EmptyEntry(Session.MovieController));
SetFrameAt(frame, empty);
Changes = true;
InvalidateAfter(frame);
@ -211,6 +215,7 @@ namespace BizHawk.Client.Common
if (Log.Count < states.Count + frame)
{
firstChangedFrame = Log.Count;
ExtendMovieForEdit(states.Count + frame - Log.Count);
}
@ -224,7 +229,7 @@ namespace BizHawk.Client.Common
}
var entry = Bk2LogEntryGenerator.GenerateLogEntry(states[i]);
if (firstChangedFrame == -1 && Log[frame + i] != entry)
if ((firstChangedFrame == -1 || firstChangedFrame > frame + i) && Log[frame + i] != entry)
{
firstChangedFrame = frame + i;
}
@ -234,7 +239,11 @@ namespace BizHawk.Client.Common
ChangeLog.EndBatch();
Changes = true;
InvalidateAfter(frame);
if (firstChangedFrame != -1)
{
// TODO: Throw out the undo action if there are no changes.
InvalidateAfter(firstChangedFrame);
}
ChangeLog.SetGeneralRedo();
return firstChangedFrame;

View File

@ -73,6 +73,7 @@ namespace BizHawk.Client.Common
public ITasSession TasSession { get; private set; } = new TasSession();
public int LastEditedFrame { get; private set; } = -1;
public bool LastEditWasRecording { get; private set; }
public bool LastPositionStable { get; set; } = true;
public TasMovieMarkerList Markers { get; private set; }
public bool BindMarkersToInput { get; set; }
@ -129,6 +130,7 @@ namespace BizHawk.Client.Common
}
LastEditedFrame = frame;
LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action.
if (anyStateInvalidated && IsCountingRerecords)
{
@ -178,14 +180,6 @@ namespace BizHawk.Client.Common
public void GreenzoneCurrentFrame()
{
// todo: this isn't working quite right when autorestore is off and we're editing while seeking
// but accounting for that requires access to Mainform.IsSeeking
if (Emulator.Frame != LastEditedFrame)
{
// emulated a new frame, current editing segment may change now. taseditor logic
LastPositionStable = false;
}
LagLog[Emulator.Frame] = _inputPollable.IsLagFrame;
// We will forbibly capture a state for the last edited frame (requested by #916 for case of "platforms with analog stick")

View File

@ -378,6 +378,10 @@ namespace BizHawk.Client.EmuHawk
var playbackBox1 = Tools.TAStudio.TasPlaybackBox;
playbackBox1.AutoRestore = !playbackBox1.AutoRestore;
break;
case "Seek To Green Arrow":
if (!Tools.IsLoaded<TAStudio>()) return false;
Tools.TAStudio.RestorePosition();
break;
case "Toggle Turbo Seek":
if (!Tools.IsLoaded<TAStudio>()) return false;
var playbackBox2 = Tools.TAStudio.TasPlaybackBox;

View File

@ -1073,7 +1073,6 @@ namespace BizHawk.Client.EmuHawk
/// 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; }
@ -3258,16 +3257,7 @@ namespace BizHawk.Client.EmuHawk
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;
}
}

View File

@ -62,7 +62,7 @@
//
this.AutoTabCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.AutoTabCheckBox.AutoSize = true;
this.AutoTabCheckBox.Location = new System.Drawing.Point(432, 440);
this.AutoTabCheckBox.Location = new System.Drawing.Point(462, 440);
this.AutoTabCheckBox.Name = "AutoTabCheckBox";
this.AutoTabCheckBox.Size = new System.Drawing.Size(70, 17);
this.AutoTabCheckBox.TabIndex = 101;
@ -79,7 +79,7 @@
this.HotkeyTabControl.Location = new System.Drawing.Point(12, 28);
this.HotkeyTabControl.Name = "HotkeyTabControl";
this.HotkeyTabControl.SelectedIndex = 0;
this.HotkeyTabControl.Size = new System.Drawing.Size(729, 396);
this.HotkeyTabControl.Size = new System.Drawing.Size(759, 396);
this.HotkeyTabControl.TabIndex = 102;
this.HotkeyTabControl.SelectedIndexChanged += new System.EventHandler(this.HotkeyTabControl_SelectedIndexChanged);
//
@ -88,7 +88,7 @@
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(721, 370);
this.tabPage1.Size = new System.Drawing.Size(751, 370);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "For designer";
this.tabPage1.UseVisualStyleBackColor = true;
@ -97,7 +97,7 @@
//
this.IDB_CANCEL.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.IDB_CANCEL.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.IDB_CANCEL.Location = new System.Drawing.Point(681, 436);
this.IDB_CANCEL.Location = new System.Drawing.Point(711, 436);
this.IDB_CANCEL.Name = "IDB_CANCEL";
this.IDB_CANCEL.Size = new System.Drawing.Size(60, 22);
this.IDB_CANCEL.TabIndex = 103;
@ -109,7 +109,7 @@
// IDB_SAVE
//
this.IDB_SAVE.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.IDB_SAVE.Location = new System.Drawing.Point(615, 436);
this.IDB_SAVE.Location = new System.Drawing.Point(645, 436);
this.IDB_SAVE.Name = "IDB_SAVE";
this.IDB_SAVE.Size = new System.Drawing.Size(60, 22);
this.IDB_SAVE.TabIndex = 104;
@ -123,7 +123,7 @@
this.SearchBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.SearchBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.SearchBox.Location = new System.Drawing.Point(592, 9);
this.SearchBox.Location = new System.Drawing.Point(622, 9);
this.SearchBox.Name = "SearchBox";
this.SearchBox.Size = new System.Drawing.Size(149, 20);
this.SearchBox.TabIndex = 106;
@ -131,7 +131,7 @@
//
// label1
//
this.label1.Location = new System.Drawing.Point(556, 12);
this.label1.Location = new System.Drawing.Point(586, 12);
this.label1.Name = "label1";
this.label1.Text = "Find:";
//
@ -152,7 +152,7 @@
// MiscButton
//
this.MiscButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.MiscButton.Location = new System.Drawing.Point(526, 436);
this.MiscButton.Location = new System.Drawing.Point(556, 436);
this.MiscButton.Menu = this.clearBtnContextMenu;
this.MiscButton.Name = "MiscButton";
this.MiscButton.Size = new System.Drawing.Size(60, 22);
@ -198,7 +198,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.IDB_CANCEL;
this.ClientSize = new System.Drawing.Size(753, 463);
this.ClientSize = new System.Drawing.Size(783, 463);
this.Controls.Add(this.MiscButton);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);

View File

@ -95,7 +95,7 @@ namespace BizHawk.Client.EmuHawk
.OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName);
int x = UIHelper.ScaleX(6);
int y = UIHelper.ScaleY(14);
int iwOffsetX = UIHelper.ScaleX(110);
int iwOffsetX = UIHelper.ScaleX(120);
int iwOffsetY = UIHelper.ScaleY(-4);
int iwWidth = UIHelper.ScaleX(120);

View File

@ -143,30 +143,30 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks");
}
_luaLibsImpl.IsUpdateSupressed = true;
int f;
if (frame is long frameNumber)
{
f = (int)frameNumber;
}
else if (frame is double frameNumber2)
{
f = (int)frameNumber2;
}
else
{
f = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame);
if (f == -1)
{
return;
}
int markerIndex = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame);
if (markerIndex == -1) return;
f = Tastudio.CurrentTasMovie.Markers[f].Frame;
f = Tastudio.CurrentTasMovie.Markers[markerIndex].Frame;
}
if (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;
}
}
@ -300,6 +300,11 @@ namespace BizHawk.Client.EmuHawk
[LuaMethod("applyinputchanges", "")]
public void ApplyInputChanges()
{
if (_changeList.Count == 0)
{
return;
}
if (Engaged())
{
if (_luaLibsImpl.IsInInputOrMemoryCallback)
@ -309,7 +314,7 @@ namespace BizHawk.Client.EmuHawk
_luaLibsImpl.IsUpdateSupressed = true;
if (_changeList.Count > 0)
Tastudio.ApiHawkBatchEdit(() =>
{
int size = _changeList.Count;
@ -327,7 +332,6 @@ namespace BizHawk.Client.EmuHawk
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);
@ -341,9 +345,7 @@ namespace BizHawk.Client.EmuHawk
}
}
_changeList.Clear();
Tastudio.JumpToGreenzone();
Tastudio.DoAutoRestore();
}
});
_luaLibsImpl.IsUpdateSupressed = false;
}

View File

@ -177,32 +177,11 @@ namespace BizHawk.Client.EmuHawk
};
}
private void LoadBranch(TasBranch branch)
{
if (Tastudio.Settings.OldControlSchemeForBranches && !Tastudio.TasPlaybackBox.RecordingMode)
{
JumpToBranchToolStripMenuItem_Click(null, null);
return;
}
Movie.LoadBranch(branch);
Tastudio.LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false)));
Movie.TasStateManager.Capture(Tastudio.Emulator.Frame, Tastudio.Emulator.AsStatable());
QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), Tastudio.VideoProvider);
if (Tastudio.Settings.OldControlSchemeForBranches && Tastudio.TasPlaybackBox.RecordingMode)
Movie.Truncate(branch.Frame);
MainForm.PauseOnFrame = null;
Tastudio.RefreshDialog();
}
private bool LoadSelectedBranch()
{
if (SelectedBranch == null) return false;
Branches.Current = BranchView.FirstSelectedRowIndex;
LoadBranch(SelectedBranch);
Tastudio.LoadBranch(SelectedBranch);
BranchView.Refresh();
Tastudio.MainForm.AddOnScreenMessage($"Loaded branch {Branches.Current + 1}");
return true;
@ -352,7 +331,7 @@ namespace BizHawk.Client.EmuHawk
{
if (_branchUndo == BranchUndo.Load)
{
LoadBranch(_backupBranch);
Tastudio.LoadBranch(_backupBranch);
Tastudio.BranchLoadedCallback?.Invoke(Branches.IndexOf(_backupBranch));
Tastudio.MainForm.AddOnScreenMessage("Branch Load canceled");
}

View File

@ -13,10 +13,5 @@ namespace BizHawk.Client.EmuHawk
public Action<int> BranchLoadedCallback { get; set; }
public Action<int> BranchSavedCallback { get; set; }
public Action<int> BranchRemovedCallback { get; set; }
private void GreenzoneInvalidated(int index)
{
GreenzoneInvalidatedCallback?.Invoke(index);
}
}
}

View File

@ -1180,6 +1180,7 @@ namespace BizHawk.Client.EmuHawk
this.MinimumSize = new System.Drawing.Size(200, 148);
this.Name = "TAStudio";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Deactivate += new System.EventHandler(this.TAStudio_Deactivate);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Tastudio_Closing);
this.Load += new System.EventHandler(this.Tastudio_Load);
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop);

View File

@ -78,27 +78,7 @@
public bool Rewind()
{
int rewindStep = MainForm.IsFastForwarding ? Settings.RewindStepFast : Settings.RewindStep;
// copy pasted from TasView_MouseWheel(), just without notch logic
if (MainForm.IsSeeking && !MainForm.EmulatorPaused)
{
MainForm.PauseOnFrame -= rewindStep;
// that's a weird condition here, but for whatever reason it works best
if (Emulator.Frame >= MainForm.PauseOnFrame)
{
MainForm.PauseEmulator();
StopSeeking();
GoToFrame(Math.Max(0, Emulator.Frame - rewindStep));
}
RefreshDialog();
}
else
{
StopSeeking(); // late breaking memo: don't know whether this is needed
GoToFrame(Math.Max(0, Emulator.Frame - rewindStep));
}
WheelSeek(rewindStep);
return true;
}

View File

@ -24,10 +24,10 @@ namespace BizHawk.Client.EmuHawk
private void UpdateProgressBar()
{
if (MainForm.PauseOnFrame.HasValue)
if (_seekingTo != -1)
{
int diff = Emulator.Frame - _seekStartFrame.Value;
int unit = MainForm.PauseOnFrame.Value - _seekStartFrame.Value;
int diff = Emulator.Frame - _seekStartFrame;
int unit = _seekingTo - _seekStartFrame;
double progress = 0;
if (diff != 0 && unit != 0)
@ -94,18 +94,27 @@ namespace BizHawk.Client.EmuHawk
refreshNeeded = true;
}
if (Settings.AutoPause)
if (Settings.AutoPause && _seekingTo == -1)
{
if (_doPause && CurrentTasMovie.IsAtEnd()) MainForm.PauseEmulator();
_doPause = !CurrentTasMovie.IsAtEnd();
}
if (!_seekingByEdit)
{
_shouldMoveGreenArrow = true;
}
FastUpdateAfter();
RefreshDialog(refreshNeeded, refreshBranches: false);
UpdateProgressBar();
}
protected override void FastUpdateAfter()
{
if (_seekingTo != -1 && Emulator.Frame >= _seekingTo)
{
StopSeeking();
}
UpdateProgressBar();
}

View File

@ -16,7 +16,6 @@ namespace BizHawk.Client.EmuHawk
// Input Painting
private string _startBoolDrawColumn = "";
private string _startAxisDrawColumn = "";
private bool _drewAxis;
private bool _boolPaintState;
private int _axisPaintState;
private int _axisBackupState;
@ -26,7 +25,9 @@ namespace BizHawk.Client.EmuHawk
private bool _selectionDragState;
private bool _suppressContextMenu;
private int _startRow;
private int _paintingMinFrame = -1;
private int _batchEditMinFrame = -1;
private bool _batchEditing;
private bool _editIsFromLua;
// Editing analog input
private string _axisEditColumn = "";
@ -56,10 +57,8 @@ namespace BizHawk.Client.EmuHawk
private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld;
private bool _triggerAutoRestore; // If true, autorestore will be called on mouse up
private bool? _autoRestorePaused;
private int? _seekStartFrame;
private bool _unpauseAfterSeeking;
private int _seekStartFrame;
private bool _pauseAfterSeeking;
private readonly Dictionary<string, bool> _alternateRowColor = new();
@ -69,38 +68,21 @@ namespace BizHawk.Client.EmuHawk
public AutoPatternBool[] BoolPatterns;
public AutoPatternAxis[] AxisPatterns;
public void JumpToGreenzone(bool OnLeftMouseDown = false)
private void StartSeeking(int frame)
{
if (Emulator.Frame > CurrentTasMovie.LastEditedFrame)
{
GoToLastEmulatedFrameIfNecessary(CurrentTasMovie.LastEditedFrame, OnLeftMouseDown);
}
}
private void StartSeeking(int? frame, bool fromMiddleClick = false)
{
if (!frame.HasValue)
if (frame <= Emulator.Frame)
{
return;
}
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;
}
_seekStartFrame = Emulator.Frame;
_seekingByEdit = false;
MainForm.PauseOnFrame = frame.Value;
int? diff = MainForm.PauseOnFrame - _seekStartFrame;
WasRecording = CurrentTasMovie.IsRecording() || WasRecording;
TastudioPlayMode(); // suspend rec mode until seek ends, to allow mouse editing
_seekingTo = frame;
MainForm.PauseOnFrame = int.MaxValue; // This being set is how MainForm knows we are seeking, and controls TurboSeek.
MainForm.UnpauseEmulator();
if (diff > TasView.VisibleRows)
if (_seekingTo - _seekStartFrame > 1)
{
MessageStatusLabel.Text = "Seeking...";
ProgressBar.Visible = true;
@ -115,11 +97,12 @@ namespace BizHawk.Client.EmuHawk
WasRecording = false;
}
MainForm.PauseOnFrame = null;
if (_unpauseAfterSeeking)
_seekingByEdit = false;
_seekingTo = -1;
MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek.
if (_pauseAfterSeeking)
{
MainForm.UnpauseEmulator();
_unpauseAfterSeeking = false;
MainForm.PauseEmulator();
}
if (CurrentTasMovie != null)
@ -129,6 +112,23 @@ namespace BizHawk.Client.EmuHawk
}
}
private void CancelSeek()
{
_shouldMoveGreenArrow = true;
_seekingByEdit = false;
_seekingTo = -1;
MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek.
_pauseAfterSeeking = false;
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,11 +167,11 @@ namespace BizHawk.Client.EmuHawk
if (index == Emulator.Frame)
{
bitmap = index == MainForm.PauseOnFrame
bitmap = index == _seekingTo
? TasView.HorizontalOrientation ? ts_v_arrow_green_blue : ts_h_arrow_green_blue
: TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue;
}
else if (index == LastPositionFrame)
else if (index == RestorePositionFrame)
{
bitmap = TasView.HorizontalOrientation ?
ts_v_arrow_green :
@ -255,11 +255,11 @@ namespace BizHawk.Client.EmuHawk
var record = CurrentTasMovie[index];
if (MainForm.IsSeeking && MainForm.PauseOnFrame == index)
if (_seekingTo == index)
{
color = Palette.CurrentFrame_InputLog;
}
else if (!MainForm.IsSeeking && Emulator.Frame == index)
else if (_seekingTo == -1 && Emulator.Frame == index)
{
color = Palette.CurrentFrame_InputLog;
}
@ -378,6 +378,7 @@ namespace BizHawk.Client.EmuHawk
if (columnName == FrameColumnName)
{
CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, "");
RefreshDialog();
}
else if (columnName != CursorColumnName)
{
@ -415,13 +416,7 @@ namespace BizHawk.Client.EmuHawk
// feos: there's no default value other than neutral, and we can't go arbitrary here, so do nothing for now
// autohold is ignored for axes too for the same reasons: lack of demand + ambiguity
}
_triggerAutoRestore = true;
TastudioPlayMode(true);
JumpToGreenzone();
}
RefreshDialog();
}
}
@ -523,10 +518,16 @@ namespace BizHawk.Client.EmuHawk
{
if (MainForm.EmulatorPaused)
{
var record = CurrentTasMovie[LastPositionFrame];
if (!record.Lagged.HasValue && LastPositionFrame > Emulator.Frame)
if (_seekingTo != -1)
{
StartSeeking(LastPositionFrame, true);
MainForm.UnpauseEmulator(); // resume seek
return;
}
var record = CurrentTasMovie[RestorePositionFrame];
if (record.Lagged is null)
{
RestorePosition();
return;
}
}
@ -543,7 +544,6 @@ namespace BizHawk.Client.EmuHawk
if (e.Button == MouseButtons.Left)
{
_leftButtonHeld = true;
_paintingMinFrame = frame;
// SuuperW: Exit axis editing mode, or re-enter mouse editing
if (AxisEditingMode)
@ -575,8 +575,6 @@ namespace BizHawk.Client.EmuHawk
_axisEditYPos = e.Y;
_axisPaintState = CurrentTasMovie.GetAxisState(frame, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
return;
}
}
@ -584,7 +582,7 @@ namespace BizHawk.Client.EmuHawk
if (targetCol.Name is CursorColumnName)
{
_startCursorDrag = true;
GoToFrame(frame, fromLua: false, fromRewinding: false, OnLeftMouseDown: true);
GoToFrame(frame, OnLeftMouseDown: true);
}
else if (targetCol.Name is FrameColumnName)
{
@ -642,9 +640,6 @@ namespace BizHawk.Client.EmuHawk
}
CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed);
_boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
RefreshDialog();
}
#if false // to match previous behaviour
else if (altOrShift4State is not 0)
@ -658,9 +653,6 @@ namespace BizHawk.Client.EmuHawk
CurrentTasMovie.ToggleBoolState(frame, buttonName);
_boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
RefreshDialog();
}
}
else
@ -743,8 +735,6 @@ namespace BizHawk.Client.EmuHawk
if (_rightClickAlt || _rightClickControl || _rightClickShift)
{
JumpToGreenzone();
// TODO: Turn off ChangeLog.IsRecording and handle the GeneralUndo here.
string undoStepName = "Right-Click Edit:";
if (_rightClickShift)
@ -773,29 +763,135 @@ namespace BizHawk.Client.EmuHawk
}
}
/// <summary>
/// Begins a batch of edits, for auto-restore purposes. Auto-restore will be delayed until EndBatchEdit is called.
/// </summary>
private void BeginBatchEdit()
{
_batchEditing = true;
}
/// <returns>Returns true if the input list was redrawn.</returns>
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();
try
{
action();
}
finally
{
EndBatchEdit();
_editIsFromLua = false;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="frame">The frame that was just edited, or the earliest one if multiple were edited.</param>
/// <returns>Returns true if the input list was redrawn.</returns>
public bool FrameEdited(int frame)
{
GreenzoneInvalidatedCallback?.Invoke(frame); // lua callback
if (CurrentTasMovie.LastEditWasRecording)
{
return false;
}
bool needsRefresh = !_batchEditing;
if (MouseButtonHeld || _batchEditing)
{
if (_batchEditMinFrame == -1)
{
_batchEditMinFrame = frame;
}
else
{
_batchEditMinFrame = Math.Min(_batchEditMinFrame, frame);
}
}
else
{
if (!_editIsFromLua)
{
// Lua users will want to preserve recording mode.
TastudioPlayMode(true);
}
if (Emulator.Frame > frame)
{
if (_shouldMoveGreenArrow)
{
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.
}
_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()
{
_leftButtonHeld = false;
_startCursorDrag = false;
_startSelectionDrag = false;
_startBoolDrawColumn = "";
_startAxisDrawColumn = "";
_drewAxis = false;
_paintingMinFrame = -1;
TasView.ReleaseCurrentCell();
// Exit axis editing if value was changed with cursor
if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn))
{
AxisEditRow = -1;
_triggerAutoRestore = true;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
RefreshDialog();
}
_axisPaintState = 0;
_axisEditYPos = -1;
_leftButtonHeld = false;
if (!AxisEditingMode)
{
@ -847,17 +943,8 @@ namespace BizHawk.Client.EmuHawk
}
else
{
if (!string.IsNullOrWhiteSpace(_startBoolDrawColumn) || _drewAxis)
{
// If painting up, we have altered frames without loading states (for smoothness)
// So now we have to ensure that all the edited frames are invalidated
GoToLastEmulatedFrameIfNecessary(_paintingMinFrame);
}
ClearLeftMouseStates();
}
DoTriggeredAutoRestoreIfNeeded();
}
if (e.Button == MouseButtons.Right)
@ -871,9 +958,31 @@ namespace BizHawk.Client.EmuHawk
}
}
EndBatchEdit(); // We didn't call BeginBatchEdit, but implicitly began one with mouse down. We must explicitly end it.
_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 +994,7 @@ namespace BizHawk.Client.EmuHawk
notch *= 2;
}
// warning: tastudio rewind hotkey/button logic is copy pasted from here!
if (MainForm.IsSeeking && !MainForm.EmulatorPaused)
{
MainForm.PauseOnFrame -= notch;
// that's a weird condition here, but for whatever reason it works best
if (notch > 0 && Emulator.Frame >= MainForm.PauseOnFrame)
{
MainForm.PauseEmulator();
StopSeeking();
GoToFrame(Emulator.Frame - notch);
}
RefreshDialog();
}
else
{
GoToFrame(Emulator.Frame - notch);
}
WheelSeek(notch);
}
}
@ -923,7 +1014,6 @@ namespace BizHawk.Client.EmuHawk
}
else
{
ClearLeftMouseStates();
MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value);
}
}
@ -944,11 +1034,6 @@ namespace BizHawk.Client.EmuHawk
return;
}
if (_paintingMinFrame >= 0)
{
_paintingMinFrame = Math.Min(_paintingMinFrame, e.NewCell.RowIndex.Value);
}
// skip rerecord counting on drawing entirely, mouse down is enough
// avoid introducing another global
bool wasCountingRerecords = CurrentTasMovie.IsCountingRerecords;
@ -975,7 +1060,7 @@ namespace BizHawk.Client.EmuHawk
}
}
if (_startCursorDrag && !MainForm.IsSeeking)
if (_startCursorDrag)
{
GoToFrame(e.NewCell.RowIndex.Value);
}
@ -992,6 +1077,7 @@ namespace BizHawk.Client.EmuHawk
}
SetSplicer();
RefreshDialog();
}
else if (_rightClickFrame != -1)
{
@ -1096,9 +1182,6 @@ namespace BizHawk.Client.EmuHawk
if (_rightClickAlt || _rightClickControl || _rightClickShift)
{
_triggerAutoRestore = true;
TastudioPlayMode(true);
JumpToGreenzone();
_suppressContextMenu = true;
}
}
@ -1125,12 +1208,6 @@ namespace BizHawk.Client.EmuHawk
}
CurrentTasMovie.SetBoolState(i, _startBoolDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column
if (!_triggerAutoRestore)
{
TastudioPlayMode(true);
JumpToGreenzone();
}
}
}
@ -1154,12 +1231,7 @@ namespace BizHawk.Client.EmuHawk
}
CurrentTasMovie.SetAxisState(i, _startAxisDrawColumn, setVal); // Notice it uses new row, old column, you can only paint across a single column
_triggerAutoRestore = true;
TastudioPlayMode(true);
RefreshDialog();
}
_drewAxis = true;
}
CurrentTasMovie.IsCountingRerecords = wasCountingRerecords;
@ -1168,8 +1240,6 @@ namespace BizHawk.Client.EmuHawk
{
TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed
}
SetTasViewRowCount();
}
private void TasView_MouseMove(object sender, MouseEventArgs e)
@ -1250,8 +1320,9 @@ namespace BizHawk.Client.EmuHawk
return;
}
BeginBatchEdit();
int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn);
int prev = value;
string prevTyped = _axisTypedValue;
var range = ControllerType.Axes[_axisEditColumn];
@ -1328,10 +1399,6 @@ namespace BizHawk.Client.EmuHawk
if (_axisBackupState != _axisPaintState)
{
CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState);
_triggerAutoRestore = Emulator.Frame > _axisEditRow;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
}
AxisEditRow = -1;
@ -1388,17 +1455,13 @@ namespace BizHawk.Client.EmuHawk
{
CurrentTasMovie.SetAxisState(row, _axisEditColumn, value);
}
if (value != prev) // Auto-restore
{
_triggerAutoRestore = Emulator.Frame > _axisEditRow;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
}
}
RefreshDialog();
bool didRefresh = EndBatchEdit();
if (!didRefresh && (prevTyped != _axisTypedValue || !AxisEditingMode))
{
RefreshDialog();
}
}
private void TasView_KeyDown(object sender, KeyEventArgs e)
@ -1429,8 +1492,6 @@ namespace BizHawk.Client.EmuHawk
{
EditAnalogProgrammatically(e);
}
RefreshDialog();
}
}
}

View File

@ -422,14 +422,7 @@ namespace BizHawk.Client.EmuHawk
_tasClipboard.Add(new TasClipboardEntry(i, line));
}
var rollbackFrame = CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState));
if (rollbackFrame > 0)
{
GoToLastEmulatedFrameIfNecessary(rollbackFrame);
DoAutoRestore();
}
FullRefresh();
CurrentTasMovie.CopyOverInput(TasView.SelectionStartIndex ?? 0, _tasClipboard.Select(static x => x.ControllerState));
}
}
}
@ -465,15 +458,7 @@ namespace BizHawk.Client.EmuHawk
}
var selectionStart = TasView.SelectionStartIndex;
var needsToRollback = selectionStart < Emulator.Frame;
CurrentTasMovie.InsertInput(selectionStart ?? 0, _tasClipboard.Select(static x => x.ControllerState));
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(selectionStart!.Value);
DoAutoRestore();
}
FullRefresh();
}
}
}
@ -484,9 +469,6 @@ namespace BizHawk.Client.EmuHawk
{
if (TasView.Focused && TasView.AnyRowsSelected)
{
var selectionStart = TasView.SelectionStartIndex;
var needsToRollback = selectionStart < Emulator.Frame;
var rollBackFrame = selectionStart ?? 0;
_tasClipboard.Clear();
var list = TasView.SelectedRows.ToArray();
@ -505,16 +487,11 @@ namespace BizHawk.Client.EmuHawk
}
Clipboard.SetDataObject(sb.ToString());
BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations
CurrentTasMovie.RemoveFrames(list);
EndBatchEdit();
SetSplicer();
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(rollBackFrame);
DoAutoRestore();
}
FullRefresh();
}
}
@ -522,10 +499,7 @@ namespace BizHawk.Client.EmuHawk
{
if (TasView.Focused && TasView.AnyRowsSelected)
{
var firstWithInput = FirstNonEmptySelectedFrame;
bool needsToRollback = firstWithInput.HasValue && firstWithInput < Emulator.Frame;
var rollBackFrame = TasView.SelectionStartIndex ?? 0;
BeginBatchEdit();
CurrentTasMovie.ChangeLog.BeginNewBatch($"Clear frames {TasView.SelectionStartIndex}-{TasView.SelectionEndIndex}");
foreach (int frame in TasView.SelectedRows)
{
@ -533,14 +507,7 @@ namespace BizHawk.Client.EmuHawk
}
CurrentTasMovie.ChangeLog.EndBatch();
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(rollBackFrame);
DoAutoRestore();
}
FullRefresh();
EndBatchEdit();
}
}
@ -549,26 +516,19 @@ namespace BizHawk.Client.EmuHawk
if (TasView.Focused && TasView.AnyRowsSelected)
{
var selectionStart = TasView.SelectionStartIndex;
var needsToRollback = selectionStart < Emulator.Frame;
var rollBackFrame = selectionStart ?? 0;
if (rollBackFrame >= CurrentTasMovie.InputLogLength)
{
// Cannot delete non-existent frames
FullRefresh();
RefreshDialog();
return;
}
BeginBatchEdit(); // movie's RemoveFrames may make multiple separate invalidations
CurrentTasMovie.RemoveFrames(TasView.SelectedRows.ToArray());
EndBatchEdit();
SetTasViewRowCount();
SetSplicer();
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(rollBackFrame);
DoAutoRestore();
}
FullRefresh();
}
}
@ -588,48 +548,29 @@ namespace BizHawk.Client.EmuHawk
private void CloneFramesXTimes(int timesToClone)
{
BeginBatchEdit();
for (int i = 0; i < timesToClone; i++)
{
if (TasView.Focused && TasView.AnyRowsSelected)
{
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))
.ToList();
CurrentTasMovie.InsertInput(insertionFrame, inputLog);
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(insertionFrame);
DoAutoRestore();
}
FullRefresh();
}
}
EndBatchEdit();
}
private void InsertFrameMenuItem_Click(object sender, EventArgs e)
{
if (TasView.Focused && TasView.AnyRowsSelected)
{
var selectionStart = TasView.SelectionStartIndex;
var insertionFrame = selectionStart ?? 0;
var needsToRollback = selectionStart < Emulator.Frame;
CurrentTasMovie.InsertEmptyFrame(insertionFrame);
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(insertionFrame);
DoAutoRestore();
}
FullRefresh();
CurrentTasMovie.InsertEmptyFrame(TasView.SelectionStartIndex ?? 0);
}
}
@ -650,18 +591,8 @@ namespace BizHawk.Client.EmuHawk
{
if (TasView.Focused && TasView.AnyRowsSelected)
{
var rollbackFrame = TasView.SelectionEndIndex ?? 0;
var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame;
CurrentTasMovie.Truncate(rollbackFrame);
CurrentTasMovie.Truncate(TasView.SelectionEndIndex ?? 0);
MarkerControl.MarkerInputRoll.TruncateSelection(CurrentTasMovie.Markers.Count - 1);
if (needsToRollback)
{
GoToFrame(rollbackFrame);
}
FullRefresh();
}
}
@ -1382,7 +1313,7 @@ namespace BizHawk.Client.EmuHawk
StartFromNowSeparator.Visible = StartNewProjectFromNowMenuItem.Visible || StartANewProjectFromSaveRamMenuItem.Visible;
RemoveMarkersContextMenuItem.Enabled = CurrentTasMovie.Markers.Any(m => TasView.IsRowSelected(m.Frame)); // Disable the option to remove markers if no markers are selected (FCEUX does this).
CancelSeekContextMenuItem.Enabled = MainForm.PauseOnFrame.HasValue;
CancelSeekContextMenuItem.Enabled = _seekingTo != -1;
BranchContextMenuItem.Visible = TasView.CurrentCell?.RowIndex == Emulator.Frame;
SelectBetweenMarkersContextMenuItem.ShortcutKeyDisplayString = Config.HotkeyBindings["Sel. bet. Markers"];
@ -1396,8 +1327,7 @@ namespace BizHawk.Client.EmuHawk
private void CancelSeekContextMenuItem_Click(object sender, EventArgs e)
{
MainForm.PauseOnFrame = null;
TasView.Refresh();
CancelSeek();
}
private void BranchContextMenuItem_Click(object sender, EventArgs e)

View File

@ -5,73 +5,36 @@ namespace BizHawk.Client.EmuHawk
public partial class TAStudio
{
/// <summary>
/// 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
/// 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.
/// </summary>
private void GoToLastEmulatedFrameIfNecessary(int frame, bool OnLeftMouseDown = false)
public void GoToFrame(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 (frame <= Emulator.Frame)
{
if ((MainForm.EmulatorPaused || !MainForm.IsSeeking)
&& !CurrentTasMovie.LastPositionStable)
{
LastPositionFrame = Emulator.Frame;
CurrentTasMovie.LastPositionStable = true; // until new frame is emulated
}
GoToFrame(frame, false, false, OnLeftMouseDown);
}
else
{
_triggerAutoRestore = false;
}
return;
}
}
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.
// 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)
// Other answer: turbo seek, navigating while unpaused
_pauseAfterSeeking = MainForm.EmulatorPaused || (_seekingTo != -1 && _pauseAfterSeeking);
WasRecording = CurrentTasMovie.IsRecording() || WasRecording;
TastudioPlayMode();
if (frame <= CurrentTasMovie.InputLogLength)
var closestState = GetPriorStateForFramebuffer(frame);
if (frame < Emulator.Frame || closestState.Key > Emulator.Frame)
{
// Get as close as we can then emulate there
StartAtNearestFrameAndEmulate(frame, fromLua, fromRewinding);
if (!OnLeftMouseDown) { MaybeFollowCursor(); }
LoadState(closestState, true);
}
else // Emulate to a future frame
closestState.Value.Dispose();
StartSeeking(frame);
if (!OnLeftMouseDown)
{
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 GoToPreviousFrame()
{
if (Emulator.Frame > 0)
{
GoToFrame(Emulator.Frame - 1);
MaybeFollowCursor();
}
}
@ -92,6 +55,17 @@ namespace BizHawk.Client.EmuHawk
GoToFrame(next);
}
public void RestorePosition()
{
if (RestorePositionFrame != -1)
{
// restore makes no sense without pausing
// Pausing here ensures any seek done by GoToFrame pauses after completing.
MainForm.PauseEmulator();
GoToFrame(RestorePositionFrame);
}
}
/// <summary>
/// Makes the given frame visible. If no frame is given, makes the current frame visible.
/// </summary>

View File

@ -50,7 +50,11 @@ namespace BizHawk.Client.EmuHawk
/// Gets a value that separates "restore last position" logic from seeking caused by navigation.
/// TASEditor never kills LastPositionFrame, and it only pauses on it, if it hasn't been greenzoned beforehand and middle mouse button was pressed.
/// </summary>
public int LastPositionFrame { get; private set; }
public int RestorePositionFrame { get; private set; }
private bool _shouldMoveGreenArrow;
private bool _seekingByEdit;
private int _seekingTo = -1;
[ConfigPersist]
public TAStudioSettings Settings { get; set; } = new TAStudioSettings();
@ -162,7 +166,7 @@ namespace BizHawk.Client.EmuHawk
TasView.QueryItemIcon += TasView_QueryItemIcon;
TasView.QueryFrameLag += TasView_QueryFrameLag;
TasView.PointedCellChanged += TasView_PointedCellChanged;
LastPositionFrame = -1;
RestorePositionFrame = -1;
TasView.MouseLeave += TAStudio_MouseLeave;
TasView.CellHovered += (_, e) =>
@ -549,7 +553,7 @@ namespace BizHawk.Client.EmuHawk
movie.InputRollSettingsForSave = () => TasView.UserSettingsSerialized();
movie.BindMarkersToInput = Settings.BindMarkersToInput;
movie.GreenzoneInvalidated = GreenzoneInvalidated;
movie.GreenzoneInvalidated = (f) => _ = FrameEdited(f);
movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps;
movie.PropertyChanged += TasMovie_OnPropertyChanged;
@ -773,20 +777,6 @@ namespace BizHawk.Client.EmuHawk
public IEnumerable<int> 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)
@ -812,41 +802,12 @@ namespace BizHawk.Client.EmuHawk
}
}
public void RefreshForInputChange(int firstChangedFrame)
{
if (TasView.IsPartiallyVisible(firstChangedFrame) || firstChangedFrame < TasView.FirstVisibleRow)
{
RefreshDialog();
}
}
private void SetTasViewRowCount()
{
TasView.RowCount = CurrentTasMovie.InputLogLength + 1;
_lastRefresh = Emulator.Frame;
}
public void DoAutoRestore()
{
if (Settings.AutoRestoreLastPosition && LastPositionFrame != -1)
{
if (LastPositionFrame > Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek
{
StartSeeking(LastPositionFrame);
}
}
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;
}
}
/// <summary>
/// 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.
@ -856,66 +817,6 @@ namespace BizHawk.Client.EmuHawk
return CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame > 0 ? frame - 1 : 0);
}
private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRewinding)
{
if (frame == Emulator.Frame)
{
return;
}
_unpauseAfterSeeking = (fromRewinding || WasRecording) && !MainForm.EmulatorPaused;
TastudioPlayMode();
var closestState = GetPriorStateForFramebuffer(frame);
if (closestState.Value.Length > 0 && (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
}
// 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)
{
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<int, Stream> state, bool discardApiHawkSurfaces = false)
{
StatableEmulator.LoadStateBinary(new BinaryReader(state.Value));
@ -956,35 +857,11 @@ namespace BizHawk.Client.EmuHawk
SplicerStatusLabel.Text = temp;
}
private void DoTriggeredAutoRestoreIfNeeded()
{
if (_triggerAutoRestore)
{
TastudioPlayMode(true); // once user started editing, rec mode is unsafe
DoAutoRestore();
_triggerAutoRestore = false;
_autoRestorePaused = null;
}
}
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);
}
}
}
@ -992,20 +869,14 @@ namespace BizHawk.Client.EmuHawk
{
if (beginningFrame < CurrentTasMovie.InputLogLength)
{
// movie's RemoveFrames might do multiple separate invalidations
BeginBatchEdit();
int[] framesToRemove = Enumerable.Range(beginningFrame, numberOfFrames).ToArray();
CurrentTasMovie.RemoveFrames(framesToRemove);
SetSplicer();
var needsToRollback = beginningFrame < Emulator.Frame;
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(beginningFrame);
DoAutoRestore();
}
else
{
RefreshForInputChange(beginningFrame);
}
EndBatchEdit();
}
}
@ -1013,22 +884,15 @@ namespace BizHawk.Client.EmuHawk
{
if (beginningFrame < CurrentTasMovie.InputLogLength)
{
var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame;
BeginBatchEdit();
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);
}
EndBatchEdit();
}
}
@ -1076,7 +940,19 @@ namespace BizHawk.Client.EmuHawk
private void TAStudio_MouseLeave(object sender, EventArgs e)
{
toolTip1.SetToolTip(TasView, null);
DoTriggeredAutoRestoreIfNeeded();
}
private void TAStudio_Deactivate(object sender, EventArgs e)
{
if (_leftButtonHeld)
{
TasView_MouseUp(this, new(MouseButtons.Left, 0, 0, 0, 0));
}
if (_rightClickFrame != -1)
{
_suppressContextMenu = true;
TasView_MouseUp(this, new(MouseButtons.Right, 0, 0, 0, 0));
}
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
@ -1117,8 +993,6 @@ namespace BizHawk.Client.EmuHawk
}
CurrentTasMovie.ChangeLog.IsRecording = wasRecording;
GoToLastEmulatedFrameIfNecessary(Emulator.Frame - 1);
DoAutoRestore();
return true;
}
@ -1251,5 +1125,28 @@ namespace BizHawk.Client.EmuHawk
TasView.AllColumns.ColumnsChanged();
}
public void LoadBranch(TasBranch branch)
{
if (Settings.OldControlSchemeForBranches && !TasPlaybackBox.RecordingMode)
{
GoToFrame(branch.Frame);
return;
}
CurrentTasMovie.LoadBranch(branch);
LoadState(new(branch.Frame, new MemoryStream(branch.CoreData, false)));
CurrentTasMovie.TasStateManager.Capture(Emulator.Frame, Emulator.AsStatable());
QuickBmpFile.Copy(new BitmapBufferVideoProvider(branch.CoreFrameBuffer), VideoProvider);
if (Settings.OldControlSchemeForBranches && TasPlaybackBox.RecordingMode)
{
CurrentTasMovie.Truncate(branch.Frame);
}
CancelSeek();
RefreshDialog();
}
}
}