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", "Show Cursor");
Bind("TAStudio", "Toggle Follow Cursor", "Shift+F"); Bind("TAStudio", "Toggle Follow Cursor", "Shift+F");
Bind("TAStudio", "Toggle Auto-Restore", "Shift+R"); Bind("TAStudio", "Toggle Auto-Restore", "Shift+R");
Bind("TAStudio", "Seek To Green Arrow", "R");
Bind("TAStudio", "Toggle Turbo Seek", "Shift+S"); Bind("TAStudio", "Toggle Turbo Seek", "Shift+S");
Bind("TAStudio", "Undo", "Ctrl+Z"); // TODO: these are getting not unique enough Bind("TAStudio", "Undo", "Ctrl+Z"); // TODO: these are getting not unique enough
Bind("TAStudio", "Redo", "Ctrl+Y"); Bind("TAStudio", "Redo", "Ctrl+Y");

View File

@ -102,37 +102,11 @@ namespace BizHawk.Client.Common
public void InvisibleEmulation(bool invisible) public void InvisibleEmulation(bool invisible)
=> APIs.EmuClient.InvisibleEmulation(invisible); => APIs.EmuClient.InvisibleEmulation(invisible);
[LuaMethodExample("client.seekframe( 100 );")] [LuaDeprecatedMethod]
[LuaMethod("seekframe", "Makes the emulator seek to the frame specified")] [LuaMethod("seekframe", "Does nothing. Use the pause/unpause functions instead and a loop that waits for the desired frame.")]
public void SeekFrame(int frame) public void SeekFrame(int frame)
{ {
if (_luaLibsImpl.IsInInputOrMemoryCallback) Log("Deprecated function client.seekframe() used. Replace the call with pause/unpause functions and a loop that waits for the desired frame.");
{
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();
}
} }
[LuaMethodExample("local sounds_terrible = client.get_approx_framerate() < 55;")] [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 public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable
{ {
bool BindMarkersToInput { get; set; } bool BindMarkersToInput { get; set; }
bool LastPositionStable { get; set; }
IMovieChangeLog ChangeLog { get; } IMovieChangeLog ChangeLog { get; }
IStateManager TasStateManager { get; } IStateManager TasStateManager { get; }
@ -20,6 +19,7 @@ namespace BizHawk.Client.Common
TasLagLog LagLog { get; } TasLagLog LagLog { get; }
IStringLog VerificationLog { get; } IStringLog VerificationLog { get; }
int LastEditedFrame { get; } int LastEditedFrame { get; }
bool LastEditWasRecording { get; }
Action<int> GreenzoneInvalidated { get; set; } Action<int> GreenzoneInvalidated { get; set; }

View File

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

View File

@ -73,6 +73,7 @@ namespace BizHawk.Client.Common
public ITasSession TasSession { get; private set; } = new TasSession(); public ITasSession TasSession { get; private set; } = new TasSession();
public int LastEditedFrame { get; private set; } = -1; public int LastEditedFrame { get; private set; } = -1;
public bool LastEditWasRecording { get; private set; }
public bool LastPositionStable { get; set; } = true; public bool LastPositionStable { get; set; } = true;
public TasMovieMarkerList Markers { get; private set; } public TasMovieMarkerList Markers { get; private set; }
public bool BindMarkersToInput { get; set; } public bool BindMarkersToInput { get; set; }
@ -129,6 +130,7 @@ namespace BizHawk.Client.Common
} }
LastEditedFrame = frame; LastEditedFrame = frame;
LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action.
if (anyStateInvalidated && IsCountingRerecords) if (anyStateInvalidated && IsCountingRerecords)
{ {
@ -178,14 +180,6 @@ namespace BizHawk.Client.Common
public void GreenzoneCurrentFrame() 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; 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") // 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; var playbackBox1 = Tools.TAStudio.TasPlaybackBox;
playbackBox1.AutoRestore = !playbackBox1.AutoRestore; playbackBox1.AutoRestore = !playbackBox1.AutoRestore;
break; break;
case "Seek To Green Arrow":
if (!Tools.IsLoaded<TAStudio>()) return false;
Tools.TAStudio.RestorePosition();
break;
case "Toggle Turbo Seek": case "Toggle Turbo Seek":
if (!Tools.IsLoaded<TAStudio>()) return false; if (!Tools.IsLoaded<TAStudio>()) return false;
var playbackBox2 = Tools.TAStudio.TasPlaybackBox; 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. /// Accessing this from Lua allows to keep internal code hacks to minimum.
/// <list type="bullet"> /// <list type="bullet">
/// <item><description><see cref="ClientLuaLibrary.InvisibleEmulation(bool)"/></description></item> /// <item><description><see cref="ClientLuaLibrary.InvisibleEmulation(bool)"/></description></item>
/// <item><description><see cref="ClientLuaLibrary.SeekFrame(int)"/></description></item>
/// </list> /// </list>
/// </summary> /// </summary>
public bool InvisibleEmulation { get; set; } public bool InvisibleEmulation { get; set; }
@ -3258,16 +3257,7 @@ namespace BizHawk.Client.EmuHawk
if (PauseOnFrame.Value == Emulator.Frame) if (PauseOnFrame.Value == Emulator.Frame)
{ {
PauseEmulator(); PauseEmulator();
if (Tools.IsLoaded<TAStudio>()) Tools.TAStudio.StopSeeking(); PauseOnFrame = null;
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();
} }
} }

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.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.AutoTabCheckBox.AutoSize = true; 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.Name = "AutoTabCheckBox";
this.AutoTabCheckBox.Size = new System.Drawing.Size(70, 17); this.AutoTabCheckBox.Size = new System.Drawing.Size(70, 17);
this.AutoTabCheckBox.TabIndex = 101; this.AutoTabCheckBox.TabIndex = 101;
@ -79,7 +79,7 @@
this.HotkeyTabControl.Location = new System.Drawing.Point(12, 28); this.HotkeyTabControl.Location = new System.Drawing.Point(12, 28);
this.HotkeyTabControl.Name = "HotkeyTabControl"; this.HotkeyTabControl.Name = "HotkeyTabControl";
this.HotkeyTabControl.SelectedIndex = 0; 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.TabIndex = 102;
this.HotkeyTabControl.SelectedIndexChanged += new System.EventHandler(this.HotkeyTabControl_SelectedIndexChanged); this.HotkeyTabControl.SelectedIndexChanged += new System.EventHandler(this.HotkeyTabControl_SelectedIndexChanged);
// //
@ -88,7 +88,7 @@
this.tabPage1.Location = new System.Drawing.Point(4, 22); this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1"; this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3); 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.TabIndex = 0;
this.tabPage1.Text = "For designer"; this.tabPage1.Text = "For designer";
this.tabPage1.UseVisualStyleBackColor = true; 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.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.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.Name = "IDB_CANCEL";
this.IDB_CANCEL.Size = new System.Drawing.Size(60, 22); this.IDB_CANCEL.Size = new System.Drawing.Size(60, 22);
this.IDB_CANCEL.TabIndex = 103; this.IDB_CANCEL.TabIndex = 103;
@ -109,7 +109,7 @@
// IDB_SAVE // IDB_SAVE
// //
this.IDB_SAVE.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.Name = "IDB_SAVE";
this.IDB_SAVE.Size = new System.Drawing.Size(60, 22); this.IDB_SAVE.Size = new System.Drawing.Size(60, 22);
this.IDB_SAVE.TabIndex = 104; 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) this.SearchBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.SearchBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; 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.Name = "SearchBox";
this.SearchBox.Size = new System.Drawing.Size(149, 20); this.SearchBox.Size = new System.Drawing.Size(149, 20);
this.SearchBox.TabIndex = 106; this.SearchBox.TabIndex = 106;
@ -131,7 +131,7 @@
// //
// label1 // 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.Name = "label1";
this.label1.Text = "Find:"; this.label1.Text = "Find:";
// //
@ -152,7 +152,7 @@
// MiscButton // MiscButton
// //
this.MiscButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.Menu = this.clearBtnContextMenu;
this.MiscButton.Name = "MiscButton"; this.MiscButton.Name = "MiscButton";
this.MiscButton.Size = new System.Drawing.Size(60, 22); this.MiscButton.Size = new System.Drawing.Size(60, 22);
@ -198,7 +198,7 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.IDB_CANCEL; 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.MiscButton);
this.Controls.Add(this.label3); this.Controls.Add(this.label3);
this.Controls.Add(this.label2); 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); .OrderBy(static kvp => kvp.Value.Ordinal).ThenBy(static kvp => kvp.Value.DisplayName);
int x = UIHelper.ScaleX(6); int x = UIHelper.ScaleX(6);
int y = UIHelper.ScaleY(14); int y = UIHelper.ScaleY(14);
int iwOffsetX = UIHelper.ScaleX(110); int iwOffsetX = UIHelper.ScaleX(120);
int iwOffsetY = UIHelper.ScaleY(-4); int iwOffsetY = UIHelper.ScaleY(-4);
int iwWidth = UIHelper.ScaleX(120); int iwWidth = UIHelper.ScaleX(120);

View File

@ -143,32 +143,32 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks"); throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks");
} }
_luaLibsImpl.IsUpdateSupressed = true;
int f; int f;
if (frame is long frameNumber) if (frame is long frameNumber)
{ {
f = (int)frameNumber; f = (int)frameNumber;
} }
else if (frame is double frameNumber2)
{
f = (int)frameNumber2;
}
else else
{ {
f = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame); int markerIndex = Tastudio.CurrentTasMovie.Markers.FindIndex((string)frame);
if (f == -1) if (markerIndex == -1) return;
f = Tastudio.CurrentTasMovie.Markers[markerIndex].Frame;
}
if (f >= 0)
{ {
return; _luaLibsImpl.IsUpdateSupressed = true;
} Tastudio.GoToFrame(f);
f = Tastudio.CurrentTasMovie.Markers[f].Frame;
}
if (0.RangeToExclusive(Tastudio.CurrentTasMovie.InputLogLength).Contains(f))
{
Tastudio.GoToFrame(f, true);
}
_luaLibsImpl.IsUpdateSupressed = false; _luaLibsImpl.IsUpdateSupressed = false;
} }
} }
}
[LuaMethodExample("local nltasget = tastudio.getselection( );")] [LuaMethodExample("local nltasget = tastudio.getselection( );")]
[LuaMethod("getselection", "gets the currently selected frames")] [LuaMethod("getselection", "gets the currently selected frames")]
@ -300,6 +300,11 @@ namespace BizHawk.Client.EmuHawk
[LuaMethod("applyinputchanges", "")] [LuaMethod("applyinputchanges", "")]
public void ApplyInputChanges() public void ApplyInputChanges()
{ {
if (_changeList.Count == 0)
{
return;
}
if (Engaged()) if (Engaged())
{ {
if (_luaLibsImpl.IsInInputOrMemoryCallback) if (_luaLibsImpl.IsInInputOrMemoryCallback)
@ -309,7 +314,7 @@ namespace BizHawk.Client.EmuHawk
_luaLibsImpl.IsUpdateSupressed = true; _luaLibsImpl.IsUpdateSupressed = true;
if (_changeList.Count > 0) Tastudio.ApiHawkBatchEdit(() =>
{ {
int size = _changeList.Count; int size = _changeList.Count;
@ -327,7 +332,6 @@ namespace BizHawk.Client.EmuHawk
Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis); Tastudio.CurrentTasMovie.SetAxisState(_changeList[i].Frame, _changeList[i].Button, _changeList[i].ValueAxis);
break; break;
} }
Tastudio.RefreshForInputChange(_changeList[i].Frame);
break; break;
case LuaChangeTypes.InsertFrames: case LuaChangeTypes.InsertFrames:
Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number); Tastudio.InsertNumFrames(_changeList[i].Frame, _changeList[i].Number);
@ -341,9 +345,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
_changeList.Clear(); _changeList.Clear();
Tastudio.JumpToGreenzone(); });
Tastudio.DoAutoRestore();
}
_luaLibsImpl.IsUpdateSupressed = false; _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() private bool LoadSelectedBranch()
{ {
if (SelectedBranch == null) return false; if (SelectedBranch == null) return false;
Branches.Current = BranchView.FirstSelectedRowIndex; Branches.Current = BranchView.FirstSelectedRowIndex;
LoadBranch(SelectedBranch); Tastudio.LoadBranch(SelectedBranch);
BranchView.Refresh(); BranchView.Refresh();
Tastudio.MainForm.AddOnScreenMessage($"Loaded branch {Branches.Current + 1}"); Tastudio.MainForm.AddOnScreenMessage($"Loaded branch {Branches.Current + 1}");
return true; return true;
@ -352,7 +331,7 @@ namespace BizHawk.Client.EmuHawk
{ {
if (_branchUndo == BranchUndo.Load) if (_branchUndo == BranchUndo.Load)
{ {
LoadBranch(_backupBranch); Tastudio.LoadBranch(_backupBranch);
Tastudio.BranchLoadedCallback?.Invoke(Branches.IndexOf(_backupBranch)); Tastudio.BranchLoadedCallback?.Invoke(Branches.IndexOf(_backupBranch));
Tastudio.MainForm.AddOnScreenMessage("Branch Load canceled"); 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> BranchLoadedCallback { get; set; }
public Action<int> BranchSavedCallback { get; set; } public Action<int> BranchSavedCallback { get; set; }
public Action<int> BranchRemovedCallback { 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.MinimumSize = new System.Drawing.Size(200, 148);
this.Name = "TAStudio"; this.Name = "TAStudio";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 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.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Tastudio_Closing);
this.Load += new System.EventHandler(this.Tastudio_Load); this.Load += new System.EventHandler(this.Tastudio_Load);
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop); this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TAStudio_DragDrop);

View File

@ -78,27 +78,7 @@
public bool Rewind() public bool Rewind()
{ {
int rewindStep = MainForm.IsFastForwarding ? Settings.RewindStepFast : Settings.RewindStep; int rewindStep = MainForm.IsFastForwarding ? Settings.RewindStepFast : Settings.RewindStep;
// copy pasted from TasView_MouseWheel(), just without notch logic WheelSeek(rewindStep);
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));
}
return true; return true;
} }

View File

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

View File

@ -16,7 +16,6 @@ namespace BizHawk.Client.EmuHawk
// Input Painting // Input Painting
private string _startBoolDrawColumn = ""; private string _startBoolDrawColumn = "";
private string _startAxisDrawColumn = ""; private string _startAxisDrawColumn = "";
private bool _drewAxis;
private bool _boolPaintState; private bool _boolPaintState;
private int _axisPaintState; private int _axisPaintState;
private int _axisBackupState; private int _axisBackupState;
@ -26,7 +25,9 @@ namespace BizHawk.Client.EmuHawk
private bool _selectionDragState; private bool _selectionDragState;
private bool _suppressContextMenu; private bool _suppressContextMenu;
private int _startRow; private int _startRow;
private int _paintingMinFrame = -1; private int _batchEditMinFrame = -1;
private bool _batchEditing;
private bool _editIsFromLua;
// Editing analog input // Editing analog input
private string _axisEditColumn = ""; private string _axisEditColumn = "";
@ -56,10 +57,8 @@ namespace BizHawk.Client.EmuHawk
private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld; private bool MouseButtonHeld => _rightClickFrame != -1 || _leftButtonHeld;
private bool _triggerAutoRestore; // If true, autorestore will be called on mouse up private int _seekStartFrame;
private bool? _autoRestorePaused; private bool _pauseAfterSeeking;
private int? _seekStartFrame;
private bool _unpauseAfterSeeking;
private readonly Dictionary<string, bool> _alternateRowColor = new(); private readonly Dictionary<string, bool> _alternateRowColor = new();
@ -69,38 +68,21 @@ namespace BizHawk.Client.EmuHawk
public AutoPatternBool[] BoolPatterns; public AutoPatternBool[] BoolPatterns;
public AutoPatternAxis[] AxisPatterns; public AutoPatternAxis[] AxisPatterns;
public void JumpToGreenzone(bool OnLeftMouseDown = false) private void StartSeeking(int frame)
{ {
if (Emulator.Frame > CurrentTasMovie.LastEditedFrame) if (frame <= Emulator.Frame)
{
GoToLastEmulatedFrameIfNecessary(CurrentTasMovie.LastEditedFrame, OnLeftMouseDown);
}
}
private void StartSeeking(int? frame, bool fromMiddleClick = false)
{
if (!frame.HasValue)
{ {
return; 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; _seekingTo = frame;
int? diff = MainForm.PauseOnFrame - _seekStartFrame; 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(); MainForm.UnpauseEmulator();
if (diff > TasView.VisibleRows) if (_seekingTo - _seekStartFrame > 1)
{ {
MessageStatusLabel.Text = "Seeking..."; MessageStatusLabel.Text = "Seeking...";
ProgressBar.Visible = true; ProgressBar.Visible = true;
@ -115,11 +97,12 @@ namespace BizHawk.Client.EmuHawk
WasRecording = false; WasRecording = false;
} }
MainForm.PauseOnFrame = null; _seekingByEdit = false;
if (_unpauseAfterSeeking) _seekingTo = -1;
MainForm.PauseOnFrame = null; // This being unset is how MainForm knows we are not seeking, and controls TurboSeek.
if (_pauseAfterSeeking)
{ {
MainForm.UnpauseEmulator(); MainForm.PauseEmulator();
_unpauseAfterSeeking = false;
} }
if (CurrentTasMovie != null) 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_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_h_arrow_green_blue => Properties.Resources.ts_h_arrow_green_blue;
private Bitmap ts_v_arrow_blue => Properties.Resources.ts_v_arrow_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) 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_green_blue : ts_h_arrow_green_blue
: TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue; : TasView.HorizontalOrientation ? ts_v_arrow_blue : ts_h_arrow_blue;
} }
else if (index == LastPositionFrame) else if (index == RestorePositionFrame)
{ {
bitmap = TasView.HorizontalOrientation ? bitmap = TasView.HorizontalOrientation ?
ts_v_arrow_green : ts_v_arrow_green :
@ -255,11 +255,11 @@ namespace BizHawk.Client.EmuHawk
var record = CurrentTasMovie[index]; var record = CurrentTasMovie[index];
if (MainForm.IsSeeking && MainForm.PauseOnFrame == index) if (_seekingTo == index)
{ {
color = Palette.CurrentFrame_InputLog; color = Palette.CurrentFrame_InputLog;
} }
else if (!MainForm.IsSeeking && Emulator.Frame == index) else if (_seekingTo == -1 && Emulator.Frame == index)
{ {
color = Palette.CurrentFrame_InputLog; color = Palette.CurrentFrame_InputLog;
} }
@ -378,6 +378,7 @@ namespace BizHawk.Client.EmuHawk
if (columnName == FrameColumnName) if (columnName == FrameColumnName)
{ {
CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, ""); CurrentTasMovie.Markers.Add(TasView.SelectionEndIndex!.Value, "");
RefreshDialog();
} }
else if (columnName != CursorColumnName) 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 // 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 // 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) if (MainForm.EmulatorPaused)
{ {
var record = CurrentTasMovie[LastPositionFrame]; if (_seekingTo != -1)
if (!record.Lagged.HasValue && LastPositionFrame > Emulator.Frame)
{ {
StartSeeking(LastPositionFrame, true); MainForm.UnpauseEmulator(); // resume seek
return;
}
var record = CurrentTasMovie[RestorePositionFrame];
if (record.Lagged is null)
{
RestorePosition();
return; return;
} }
} }
@ -543,7 +544,6 @@ namespace BizHawk.Client.EmuHawk
if (e.Button == MouseButtons.Left) if (e.Button == MouseButtons.Left)
{ {
_leftButtonHeld = true; _leftButtonHeld = true;
_paintingMinFrame = frame;
// SuuperW: Exit axis editing mode, or re-enter mouse editing // SuuperW: Exit axis editing mode, or re-enter mouse editing
if (AxisEditingMode) if (AxisEditingMode)
@ -575,8 +575,6 @@ namespace BizHawk.Client.EmuHawk
_axisEditYPos = e.Y; _axisEditYPos = e.Y;
_axisPaintState = CurrentTasMovie.GetAxisState(frame, buttonName); _axisPaintState = CurrentTasMovie.GetAxisState(frame, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
return; return;
} }
} }
@ -584,7 +582,7 @@ namespace BizHawk.Client.EmuHawk
if (targetCol.Name is CursorColumnName) if (targetCol.Name is CursorColumnName)
{ {
_startCursorDrag = true; _startCursorDrag = true;
GoToFrame(frame, fromLua: false, fromRewinding: false, OnLeftMouseDown: true); GoToFrame(frame, OnLeftMouseDown: true);
} }
else if (targetCol.Name is FrameColumnName) else if (targetCol.Name is FrameColumnName)
{ {
@ -642,9 +640,6 @@ namespace BizHawk.Client.EmuHawk
} }
CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed); CurrentTasMovie.SetBoolStates(firstSel, lastSel - firstSel + 1, buttonName, !allPressed);
_boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(lastSel, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
RefreshDialog();
} }
#if false // to match previous behaviour #if false // to match previous behaviour
else if (altOrShift4State is not 0) else if (altOrShift4State is not 0)
@ -658,9 +653,6 @@ namespace BizHawk.Client.EmuHawk
CurrentTasMovie.ToggleBoolState(frame, buttonName); CurrentTasMovie.ToggleBoolState(frame, buttonName);
_boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName); _boolPaintState = CurrentTasMovie.BoolIsPressed(frame, buttonName);
_triggerAutoRestore = true;
TastudioPlayMode(true);
RefreshDialog();
} }
} }
else else
@ -743,8 +735,6 @@ namespace BizHawk.Client.EmuHawk
if (_rightClickAlt || _rightClickControl || _rightClickShift) if (_rightClickAlt || _rightClickControl || _rightClickShift)
{ {
JumpToGreenzone();
// TODO: Turn off ChangeLog.IsRecording and handle the GeneralUndo here. // TODO: Turn off ChangeLog.IsRecording and handle the GeneralUndo here.
string undoStepName = "Right-Click Edit:"; string undoStepName = "Right-Click Edit:";
if (_rightClickShift) 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() private void ClearLeftMouseStates()
{ {
_leftButtonHeld = false;
_startCursorDrag = false; _startCursorDrag = false;
_startSelectionDrag = false; _startSelectionDrag = false;
_startBoolDrawColumn = ""; _startBoolDrawColumn = "";
_startAxisDrawColumn = ""; _startAxisDrawColumn = "";
_drewAxis = false;
_paintingMinFrame = -1;
TasView.ReleaseCurrentCell(); TasView.ReleaseCurrentCell();
// Exit axis editing if value was changed with cursor // Exit axis editing if value was changed with cursor
if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn)) if (AxisEditingMode && _axisPaintState != CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn))
{ {
AxisEditRow = -1; AxisEditRow = -1;
_triggerAutoRestore = true;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
RefreshDialog();
} }
_axisPaintState = 0; _axisPaintState = 0;
_axisEditYPos = -1; _axisEditYPos = -1;
_leftButtonHeld = false;
if (!AxisEditingMode) if (!AxisEditingMode)
{ {
@ -847,17 +943,8 @@ namespace BizHawk.Client.EmuHawk
} }
else 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(); ClearLeftMouseStates();
} }
DoTriggeredAutoRestoreIfNeeded();
} }
if (e.Button == MouseButtons.Right) 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; _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) private void TasView_MouseWheel(object sender, MouseEventArgs e)
{ {
if (TasView.RightButtonHeld && TasView?.CurrentCell.RowIndex.HasValue == true) if (TasView.RightButtonHeld && TasView?.CurrentCell.RowIndex.HasValue == true)
@ -885,25 +994,7 @@ namespace BizHawk.Client.EmuHawk
notch *= 2; notch *= 2;
} }
// warning: tastudio rewind hotkey/button logic is copy pasted from here! WheelSeek(notch);
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);
}
} }
} }
@ -923,7 +1014,6 @@ namespace BizHawk.Client.EmuHawk
} }
else else
{ {
ClearLeftMouseStates();
MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value); MarkerControl.AddMarker(TasView.CurrentCell.RowIndex.Value);
} }
} }
@ -944,11 +1034,6 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
if (_paintingMinFrame >= 0)
{
_paintingMinFrame = Math.Min(_paintingMinFrame, e.NewCell.RowIndex.Value);
}
// skip rerecord counting on drawing entirely, mouse down is enough // skip rerecord counting on drawing entirely, mouse down is enough
// avoid introducing another global // avoid introducing another global
bool wasCountingRerecords = CurrentTasMovie.IsCountingRerecords; bool wasCountingRerecords = CurrentTasMovie.IsCountingRerecords;
@ -975,7 +1060,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
if (_startCursorDrag && !MainForm.IsSeeking) if (_startCursorDrag)
{ {
GoToFrame(e.NewCell.RowIndex.Value); GoToFrame(e.NewCell.RowIndex.Value);
} }
@ -992,6 +1077,7 @@ namespace BizHawk.Client.EmuHawk
} }
SetSplicer(); SetSplicer();
RefreshDialog();
} }
else if (_rightClickFrame != -1) else if (_rightClickFrame != -1)
{ {
@ -1096,9 +1182,6 @@ namespace BizHawk.Client.EmuHawk
if (_rightClickAlt || _rightClickControl || _rightClickShift) if (_rightClickAlt || _rightClickControl || _rightClickShift)
{ {
_triggerAutoRestore = true;
TastudioPlayMode(true);
JumpToGreenzone();
_suppressContextMenu = true; _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 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 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; CurrentTasMovie.IsCountingRerecords = wasCountingRerecords;
@ -1168,8 +1240,6 @@ namespace BizHawk.Client.EmuHawk
{ {
TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed TasView.MakeIndexVisible(TasView.CurrentCell.RowIndex.Value); // todo: limit scrolling speed
} }
SetTasViewRowCount();
} }
private void TasView_MouseMove(object sender, MouseEventArgs e) private void TasView_MouseMove(object sender, MouseEventArgs e)
@ -1250,8 +1320,9 @@ namespace BizHawk.Client.EmuHawk
return; return;
} }
BeginBatchEdit();
int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn); int value = CurrentTasMovie.GetAxisState(_axisEditRow, _axisEditColumn);
int prev = value;
string prevTyped = _axisTypedValue; string prevTyped = _axisTypedValue;
var range = ControllerType.Axes[_axisEditColumn]; var range = ControllerType.Axes[_axisEditColumn];
@ -1328,10 +1399,6 @@ namespace BizHawk.Client.EmuHawk
if (_axisBackupState != _axisPaintState) if (_axisBackupState != _axisPaintState)
{ {
CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState); CurrentTasMovie.SetAxisState(_axisEditRow, _axisEditColumn, _axisBackupState);
_triggerAutoRestore = Emulator.Frame > _axisEditRow;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
} }
AxisEditRow = -1; AxisEditRow = -1;
@ -1388,18 +1455,14 @@ namespace BizHawk.Client.EmuHawk
{ {
CurrentTasMovie.SetAxisState(row, _axisEditColumn, value); CurrentTasMovie.SetAxisState(row, _axisEditColumn, value);
} }
}
if (value != prev) // Auto-restore bool didRefresh = EndBatchEdit();
if (!didRefresh && (prevTyped != _axisTypedValue || !AxisEditingMode))
{ {
_triggerAutoRestore = Emulator.Frame > _axisEditRow;
TastudioPlayMode(true);
JumpToGreenzone();
DoTriggeredAutoRestoreIfNeeded();
}
}
RefreshDialog(); RefreshDialog();
} }
}
private void TasView_KeyDown(object sender, KeyEventArgs e) private void TasView_KeyDown(object sender, KeyEventArgs e)
{ {
@ -1429,8 +1492,6 @@ namespace BizHawk.Client.EmuHawk
{ {
EditAnalogProgrammatically(e); EditAnalogProgrammatically(e);
} }
RefreshDialog();
} }
} }
} }

View File

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

View File

@ -5,73 +5,36 @@ namespace BizHawk.Client.EmuHawk
public partial class TAStudio public partial class TAStudio
{ {
/// <summary> /// <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> /// </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) return;
{
if ((MainForm.EmulatorPaused || !MainForm.IsSeeking)
&& !CurrentTasMovie.LastPositionStable)
{
LastPositionFrame = Emulator.Frame;
CurrentTasMovie.LastPositionStable = true; // until new frame is emulated
} }
GoToFrame(frame, false, false, OnLeftMouseDown); // 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?
else // 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
_triggerAutoRestore = false; _pauseAfterSeeking = MainForm.EmulatorPaused || (_seekingTo != -1 && _pauseAfterSeeking);
}
}
}
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; 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(); TastudioPlayMode();
var lastState = GetPriorStateForFramebuffer(frame); var closestState = GetPriorStateForFramebuffer(frame);
if (lastState.Key > Emulator.Frame) if (frame < Emulator.Frame || closestState.Key > Emulator.Frame)
{ {
LoadState(lastState, true); LoadState(closestState, true);
} }
closestState.Value.Dispose();
StartSeeking(frame); StartSeeking(frame);
}
}
}
public void GoToPreviousFrame() if (!OnLeftMouseDown)
{ {
if (Emulator.Frame > 0) MaybeFollowCursor();
{
GoToFrame(Emulator.Frame - 1);
} }
} }
@ -92,6 +55,17 @@ namespace BizHawk.Client.EmuHawk
GoToFrame(next); 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> /// <summary>
/// Makes the given frame visible. If no frame is given, makes the current frame visible. /// Makes the given frame visible. If no frame is given, makes the current frame visible.
/// </summary> /// </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. /// 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. /// TASEditor never kills LastPositionFrame, and it only pauses on it, if it hasn't been greenzoned beforehand and middle mouse button was pressed.
/// </summary> /// </summary>
public int LastPositionFrame { get; private set; } public int RestorePositionFrame { get; private set; }
private bool _shouldMoveGreenArrow;
private bool _seekingByEdit;
private int _seekingTo = -1;
[ConfigPersist] [ConfigPersist]
public TAStudioSettings Settings { get; set; } = new TAStudioSettings(); public TAStudioSettings Settings { get; set; } = new TAStudioSettings();
@ -162,7 +166,7 @@ namespace BizHawk.Client.EmuHawk
TasView.QueryItemIcon += TasView_QueryItemIcon; TasView.QueryItemIcon += TasView_QueryItemIcon;
TasView.QueryFrameLag += TasView_QueryFrameLag; TasView.QueryFrameLag += TasView_QueryFrameLag;
TasView.PointedCellChanged += TasView_PointedCellChanged; TasView.PointedCellChanged += TasView_PointedCellChanged;
LastPositionFrame = -1; RestorePositionFrame = -1;
TasView.MouseLeave += TAStudio_MouseLeave; TasView.MouseLeave += TAStudio_MouseLeave;
TasView.CellHovered += (_, e) => TasView.CellHovered += (_, e) =>
@ -549,7 +553,7 @@ namespace BizHawk.Client.EmuHawk
movie.InputRollSettingsForSave = () => TasView.UserSettingsSerialized(); movie.InputRollSettingsForSave = () => TasView.UserSettingsSerialized();
movie.BindMarkersToInput = Settings.BindMarkersToInput; movie.BindMarkersToInput = Settings.BindMarkersToInput;
movie.GreenzoneInvalidated = GreenzoneInvalidated; movie.GreenzoneInvalidated = (f) => _ = FrameEdited(f);
movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps; movie.ChangeLog.MaxSteps = Settings.MaxUndoSteps;
movie.PropertyChanged += TasMovie_OnPropertyChanged; movie.PropertyChanged += TasMovie_OnPropertyChanged;
@ -773,20 +777,6 @@ namespace BizHawk.Client.EmuHawk
public IEnumerable<int> GetSelection() => TasView.SelectedRows; 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) public void RefreshDialog(bool refreshTasView = true, bool refreshBranches = true)
{ {
if (_exiting) 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() private void SetTasViewRowCount()
{ {
TasView.RowCount = CurrentTasMovie.InputLogLength + 1; TasView.RowCount = CurrentTasMovie.InputLogLength + 1;
_lastRefresh = Emulator.Frame; _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> /// <summary>
/// Get a savestate prior to the previous frame so code following the call can frame advance and have a framebuffer. /// 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. /// 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); 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) public void LoadState(KeyValuePair<int, Stream> state, bool discardApiHawkSurfaces = false)
{ {
StatableEmulator.LoadStateBinary(new BinaryReader(state.Value)); StatableEmulator.LoadStateBinary(new BinaryReader(state.Value));
@ -956,35 +857,11 @@ namespace BizHawk.Client.EmuHawk
SplicerStatusLabel.Text = temp; 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) public void InsertNumFrames(int insertionFrame, int numberOfFrames)
{ {
if (insertionFrame <= CurrentTasMovie.InputLogLength) if (insertionFrame <= CurrentTasMovie.InputLogLength)
{ {
var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame;
CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames); CurrentTasMovie.InsertEmptyFrame(insertionFrame, numberOfFrames);
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(insertionFrame);
DoAutoRestore();
}
else
{
RefreshForInputChange(insertionFrame);
}
} }
} }
@ -992,20 +869,14 @@ namespace BizHawk.Client.EmuHawk
{ {
if (beginningFrame < CurrentTasMovie.InputLogLength) if (beginningFrame < CurrentTasMovie.InputLogLength)
{ {
// movie's RemoveFrames might do multiple separate invalidations
BeginBatchEdit();
int[] framesToRemove = Enumerable.Range(beginningFrame, numberOfFrames).ToArray(); int[] framesToRemove = Enumerable.Range(beginningFrame, numberOfFrames).ToArray();
CurrentTasMovie.RemoveFrames(framesToRemove); CurrentTasMovie.RemoveFrames(framesToRemove);
SetSplicer(); SetSplicer();
var needsToRollback = beginningFrame < Emulator.Frame; EndBatchEdit();
if (needsToRollback)
{
GoToLastEmulatedFrameIfNecessary(beginningFrame);
DoAutoRestore();
}
else
{
RefreshForInputChange(beginningFrame);
}
} }
} }
@ -1013,22 +884,15 @@ namespace BizHawk.Client.EmuHawk
{ {
if (beginningFrame < CurrentTasMovie.InputLogLength) if (beginningFrame < CurrentTasMovie.InputLogLength)
{ {
var needsToRollback = TasView.SelectionStartIndex < Emulator.Frame; BeginBatchEdit();
int last = Math.Min(beginningFrame + numberOfFrames, CurrentTasMovie.InputLogLength); int last = Math.Min(beginningFrame + numberOfFrames, CurrentTasMovie.InputLogLength);
for (int i = beginningFrame; i < last; i++) for (int i = beginningFrame; i < last; i++)
{ {
CurrentTasMovie.ClearFrame(i); CurrentTasMovie.ClearFrame(i);
} }
if (needsToRollback) EndBatchEdit();
{
GoToLastEmulatedFrameIfNecessary(beginningFrame);
DoAutoRestore();
}
else
{
RefreshForInputChange(beginningFrame);
}
} }
} }
@ -1076,7 +940,19 @@ namespace BizHawk.Client.EmuHawk
private void TAStudio_MouseLeave(object sender, EventArgs e) private void TAStudio_MouseLeave(object sender, EventArgs e)
{ {
toolTip1.SetToolTip(TasView, null); 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) protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
@ -1117,8 +993,6 @@ namespace BizHawk.Client.EmuHawk
} }
CurrentTasMovie.ChangeLog.IsRecording = wasRecording; CurrentTasMovie.ChangeLog.IsRecording = wasRecording;
GoToLastEmulatedFrameIfNecessary(Emulator.Frame - 1);
DoAutoRestore();
return true; return true;
} }
@ -1251,5 +1125,28 @@ namespace BizHawk.Client.EmuHawk
TasView.AllColumns.ColumnsChanged(); 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();
}
} }
} }