diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index b631d61126..260c84872e 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -48,7 +48,7 @@ namespace BizHawk.Client.Common void InsertInput(int frame, IEnumerable inputStates); void InsertEmptyFrame(int frame, int count = 1); - int CopyOverInput(int frame, IEnumerable inputStates); + void CopyOverInput(int frame, IEnumerable inputStates); void RemoveFrame(int frame); void RemoveFrames(ICollection frames); diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index fc14a3a86e..7a84dc31c3 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -9,64 +9,60 @@ namespace BizHawk.Client.Common { public IMovieChangeLog ChangeLog { get; set; } + // In each editing method we do things in this order: + // 1) Any special logic (such as short-circuit) + // 2) Begin an undo batch, if needed. + // 3) Edit the movie. + // 4) End the undo batch, if needed. + // 5) Call InvalidateAfter. + + // InvalidateAfter being last ensures that the GreenzoneInvalidated callback sees all edits. + public override void RecordFrame(int frame, IController source) { ChangeLog.AddGeneralUndo(frame, frame, $"Record Frame: {frame}"); - SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); - - Changes = true; + ChangeLog.SetGeneralRedo(); LagLog[frame] = _inputPollable.IsLagFrame; - LastEditWasRecording = true; - InvalidateAfter(frame); - ChangeLog.SetGeneralRedo(); + InvalidateAfter(frame); } public override void Truncate(int frame) { + if (frame >= Log.Count - 1) return; + bool endBatch = ChangeLog.BeginNewBatch($"Truncate Movie: {frame}", true); + ChangeLog.AddGeneralUndo(frame, InputLogLength - 1); - - if (frame < Log.Count - 1) - { - Changes = true; - } - base.Truncate(frame); + ChangeLog.SetGeneralRedo(); - LagLog.RemoveFrom(frame); - TasStateManager.InvalidateAfter(frame); - GreenzoneInvalidated?.Invoke(frame); Markers.TruncateAt(frame); - ChangeLog.SetGeneralRedo(); - if (endBatch) - { - ChangeLog.EndBatch(); - } + if (endBatch) ChangeLog.EndBatch(); + + InvalidateAfter(frame); } public override void PokeFrame(int frame, IController source) { ChangeLog.AddGeneralUndo(frame, frame, $"Set Frame At: {frame}"); - base.PokeFrame(frame, source); - InvalidateAfter(frame); - ChangeLog.SetGeneralRedo(); + + InvalidateAfter(frame); } public void SetFrame(int frame, string source) { ChangeLog.AddGeneralUndo(frame, frame, $"Set Frame At: {frame}"); - SetFrameAt(frame, source); - InvalidateAfter(frame); - ChangeLog.SetGeneralRedo(); + + InvalidateAfter(frame); } public void ClearFrame(int frame) @@ -75,12 +71,10 @@ namespace BizHawk.Client.Common if (GetInputLogEntry(frame) == empty) return; ChangeLog.AddGeneralUndo(frame, frame, $"Clear Frame: {frame}"); - SetFrameAt(frame, empty); - Changes = true; + ChangeLog.SetGeneralRedo(); InvalidateAfter(frame); - ChangeLog.SetGeneralRedo(); } private void ShiftBindedMarkers(int frame, int offset) @@ -122,10 +116,6 @@ namespace BizHawk.Client.Common public void RemoveFrames(int removeStart, int removeUpTo) { - // Log.GetRange() might be preferrable, but Log's type complicates that. - string[] removedInputs = new string[removeUpTo - removeStart]; - Log.CopyTo(removeStart, removedInputs, 0, removedInputs.Length); - bool endBatch = ChangeLog.BeginNewBatch($"Remove frames {removeStart}-{removeUpTo - 1}", true); if (BindMarkersToInput) { @@ -136,13 +126,12 @@ namespace BizHawk.Client.Common Markers.Remove(marker); } } - - Log.RemoveRange(removeStart, removeUpTo - removeStart); - ShiftBindedMarkers(removeUpTo, removeStart - removeUpTo); - Changes = true; - InvalidateAfter(removeStart); + // Log.GetRange() might be preferrable, but Log's type complicates that. + string[] removedInputs = new string[removeUpTo - removeStart]; + Log.CopyTo(removeStart, removedInputs, 0, removedInputs.Length); + Log.RemoveRange(removeStart, removeUpTo - removeStart); ChangeLog.AddRemoveFrames( removeStart, @@ -151,6 +140,8 @@ namespace BizHawk.Client.Common BindMarkersToInput ); if (endBatch) ChangeLog.EndBatch(); + + InvalidateAfter(removeStart); } public void InsertInput(int frame, string inputState) @@ -162,18 +153,14 @@ namespace BizHawk.Client.Common public void InsertInput(int frame, IEnumerable inputLog) { Log.InsertRange(frame, inputLog); - ShiftBindedMarkers(frame, inputLog.Count()); - - Changes = true; - InvalidateAfter(frame); - ChangeLog.AddInsertInput(frame, inputLog.ToList(), BindMarkersToInput, $"Insert {inputLog.Count()} frame(s) at {frame}"); + + InvalidateAfter(frame); } public void InsertInput(int frame, IEnumerable inputStates) { - // ChangeLog is done in the InsertInput call. var inputLog = new List(); foreach (var input in inputStates) @@ -184,13 +171,13 @@ namespace BizHawk.Client.Common InsertInput(frame, inputLog); // Sets the ChangeLog } - public int CopyOverInput(int frame, IEnumerable inputStates) + public void CopyOverInput(int frame, IEnumerable inputStates) { int firstChangedFrame = -1; - bool endBatch = ChangeLog.BeginNewBatch($"Copy Over Input: {frame}", true); - var states = inputStates.ToList(); + bool endBatch = ChangeLog.BeginNewBatch($"Copy Over Input: {frame}", true); + if (Log.Count < states.Count + frame) { firstChangedFrame = Log.Count; @@ -198,14 +185,8 @@ namespace BizHawk.Client.Common } ChangeLog.AddGeneralUndo(frame, frame + states.Count - 1, $"Copy Over Input: {frame}"); - for (int i = 0; i < states.Count; i++) { - if (Log.Count <= frame + i) - { - break; - } - var entry = Bk2LogEntryGenerator.GenerateLogEntry(states[i]); if ((firstChangedFrame == -1 || firstChangedFrame > frame + i) && Log[frame + i] != entry) { @@ -214,17 +195,15 @@ namespace BizHawk.Client.Common Log[frame + i] = entry; } + ChangeLog.SetGeneralRedo(); if (endBatch) ChangeLog.EndBatch(); - Changes = true; + if (firstChangedFrame != -1) { // TODO: Throw out the undo action if there are no changes. InvalidateAfter(firstChangedFrame); } - - ChangeLog.SetGeneralRedo(); - return firstChangedFrame; } public void InsertEmptyFrame(int frame, int count = 1) @@ -232,13 +211,10 @@ namespace BizHawk.Client.Common frame = Math.Min(frame, Log.Count); Log.InsertRange(frame, Enumerable.Repeat(Bk2LogEntryGenerator.EmptyEntry(Session.MovieController), count)); - ShiftBindedMarkers(frame, count); - - Changes = true; - InvalidateAfter(frame); - ChangeLog.AddInsertFrames(frame, count, BindMarkersToInput, $"Insert {count} empty frame(s) at {frame}"); + + InvalidateAfter(frame); } private void ExtendMovieForEdit(int numFrames) @@ -252,7 +228,6 @@ namespace BizHawk.Client.Common Log.Add(inputs); } - Changes = true; ChangeLog.AddExtend(oldLength, numFrames, inputs); } @@ -269,49 +244,51 @@ namespace BizHawk.Client.Common adapter.SetBool(buttonName, !adapter.IsPressed(buttonName)); Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); - Changes = true; - InvalidateAfter(frame); - ChangeLog.AddBoolToggle(frame, buttonName, !adapter.IsPressed(buttonName)); + if (endBatch) ChangeLog.EndBatch(); + + InvalidateAfter(frame); } public void SetBoolState(int frame, string buttonName, bool val) { bool endBatch = ChangeLog.BeginNewBatch($"Set {buttonName}({(val ? "On" : "Off")}): {frame}", true); + bool extended = false; if (frame >= Log.Count) // Insert blank frames up to this point { ExtendMovieForEdit(frame - Log.Count + 1); + extended = true; } var adapter = GetInputState(frame); var old = adapter.IsPressed(buttonName); - adapter.SetBool(buttonName, val); - - Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); if (old != val) { - InvalidateAfter(frame); - Changes = true; + adapter.SetBool(buttonName, val); + Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); ChangeLog.AddBoolToggle(frame, buttonName, old); - if (endBatch) ChangeLog.EndBatch(); } + + if (endBatch) ChangeLog.EndBatch(); + + if (old != val || extended) InvalidateAfter(frame); } public void SetBoolStates(int frame, int count, string buttonName, bool val) { bool endBatch = ChangeLog.BeginNewBatch($"Set {buttonName}({(val ? "On" : "Off")}): {frame}-{frame + count - 1}", true); + int firstChangedFrame = -1; if (Log.Count < frame + count) { + firstChangedFrame = Log.Count; ExtendMovieForEdit(frame + count - Log.Count); } ChangeLog.AddGeneralUndo(frame, frame + count - 1); - - int changed = -1; for (int i = 0; i < count; i++) { var adapter = GetInputState(frame + i); @@ -320,58 +297,56 @@ namespace BizHawk.Client.Common Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); - if (changed == -1 && old != val) + if (firstChangedFrame == -1 && old != val) { - changed = frame + i; + firstChangedFrame = frame + i; } } - - if (changed != -1) - { - InvalidateAfter(changed); - Changes = true; - } - ChangeLog.SetGeneralRedo(); + if (endBatch) ChangeLog.EndBatch(); + + if (firstChangedFrame != -1) InvalidateAfter(firstChangedFrame); } public void SetAxisState(int frame, string buttonName, int val) { bool endBatch = ChangeLog.BeginNewBatch($"Set {buttonName}({val}): {frame}", true); + bool extended = false; if (frame >= Log.Count) // Insert blank frames up to this point { ExtendMovieForEdit(frame - Log.Count + 1); + extended = true; } var adapter = GetInputState(frame); var old = adapter.AxisValue(buttonName); - adapter.SetAxis(buttonName, val); - - Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); if (old != val) { - InvalidateAfter(frame); - Changes = true; + adapter.SetAxis(buttonName, val); + Log[frame] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); ChangeLog.AddAxisChange(frame, buttonName, old, val); - if (endBatch) ChangeLog.EndBatch(); } + + if (endBatch) ChangeLog.EndBatch(); + + if (old != val || extended) InvalidateAfter(frame); } public void SetAxisStates(int frame, int count, string buttonName, int val) { bool endBatch = ChangeLog.BeginNewBatch($"Set {buttonName}({val}): {frame}-{frame + count - 1}", true); + int firstChangedFrame = -1; if (frame + count >= Log.Count) // Insert blank frames up to this point { + firstChangedFrame = Log.Count; ExtendMovieForEdit(frame + count - Log.Count); } ChangeLog.AddGeneralUndo(frame, frame + count - 1); - - int changed = -1; for (int i = 0; i < count; i++) { var adapter = GetInputState(frame + i); @@ -380,20 +355,16 @@ namespace BizHawk.Client.Common Log[frame + i] = Bk2LogEntryGenerator.GenerateLogEntry(adapter); - if (changed == -1 && old != val) + if (firstChangedFrame == -1 && old != val) { - changed = frame + i; + firstChangedFrame = frame + i; } } - - if (changed != -1) - { - InvalidateAfter(changed); - Changes = true; - } - ChangeLog.SetGeneralRedo(); + if (endBatch) ChangeLog.EndBatch(); + + if (firstChangedFrame != -1) InvalidateAfter(firstChangedFrame); } } } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs index 11729b6603..02be24c963 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.History.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace BizHawk.Client.Common @@ -311,6 +312,7 @@ namespace BizHawk.Client.Common { if (IsRecording || force) { + Debug.Assert(_lastGeneral == LatestBatch.Count - 1, "GeneralRedo should not see changes from other undo actions."); ((MovieAction) LatestBatch[_lastGeneral]).SetRedoLog(_movie); } } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index b0bf0a1268..f3da193d52 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -121,21 +121,18 @@ namespace BizHawk.Client.Common // Removes lag log and greenzone after this frame private void InvalidateAfter(int frame) { - var anyLagInvalidated = LagLog.RemoveFrom(frame); + LagLog.RemoveFrom(frame); var anyStateInvalidated = TasStateManager.InvalidateAfter(frame); - GreenzoneInvalidated?.Invoke(frame); - if (anyLagInvalidated || anyStateInvalidated) - { - Changes = true; - } + Changes = true; LastEditedFrame = frame; - LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action. - if (anyStateInvalidated && IsCountingRerecords) { Rerecords++; } + + GreenzoneInvalidated?.Invoke(frame); + LastEditWasRecording = false; // We can set it here; it's only used in the GreenzoneInvalidated action. } public void InvalidateEntireGreenzone() @@ -293,14 +290,12 @@ namespace BizHawk.Client.Common Log?.Dispose(); Log = branch.InputLog.Clone(); - InvalidateAfter(divergentPoint ?? branch.InputLog.Count); - if (BindMarkersToInput) // pretty critical not to erase them { Markers = branch.Markers.DeepClone(); } - Changes = true; + InvalidateAfter(divergentPoint ?? branch.InputLog.Count); } public event PropertyChangedEventHandler PropertyChanged;