diff --git a/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs b/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs index 50672fb7965..94ce1d1cfdc 100644 --- a/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/MovieApi.cs @@ -69,7 +69,7 @@ public void Save(string filename) } _movieSession.Movie.Filename = filename; } - _movieSession.Movie.Save(); + _movieSession.Movie.Save(_mainForm.Emulator); } public IReadOnlyDictionary GetHeader() diff --git a/src/BizHawk.Client.Common/movie/BasicMovieInfo.cs b/src/BizHawk.Client.Common/movie/BasicMovieInfo.cs index ebc7571938c..1e86bd628b6 100644 --- a/src/BizHawk.Client.Common/movie/BasicMovieInfo.cs +++ b/src/BizHawk.Client.Common/movie/BasicMovieInfo.cs @@ -142,7 +142,7 @@ public virtual string FirmwareHash public IDictionary HeaderEntries => Header; - public bool Load() + public bool Load(IEmulator emulator) { if (!File.Exists(Filename)) { @@ -154,7 +154,7 @@ public bool Load() using var bl = ZipStateLoader.LoadAndDetect(Filename, true); if (bl is null) return false; ClearBeforeLoad(); - LoadFields(bl); + LoadFields(bl, emulator); if (FrameCount == 0) { // only iterate the input log if it hasn't been loaded already @@ -176,7 +176,7 @@ protected virtual void ClearBeforeLoad() Comments.Clear(); } - protected virtual void LoadFields(ZipStateLoader bl) + protected virtual void LoadFields(ZipStateLoader bl, IEmulator emulator) { bl.GetLump(BinaryStateLump.Movieheader, abort: true, tr => { diff --git a/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs b/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs index 945a216c7a8..962edcf3371 100644 --- a/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs +++ b/src/BizHawk.Client.Common/movie/MovieConversionExtensions.cs @@ -2,6 +2,7 @@ using System.Linq; using BizHawk.Common.PathExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -71,7 +72,7 @@ public static IMovie ToBk2(this IMovie old) return bk2; } - public static ITasMovie ConvertToSavestateAnchoredMovie(this ITasMovie old, int frame, byte[] savestate) + public static ITasMovie ConvertToSavestateAnchoredMovie(this ITasMovie old, int frame, byte[] savestate, IEmulator emulator) { string newFilename = ConvertFileNameToTasMovie(old.Filename); @@ -115,11 +116,11 @@ public static ITasMovie ConvertToSavestateAnchoredMovie(this ITasMovie old, int } } - tas.Save(); + tas.Save(emulator); return tas; } - public static ITasMovie ConvertToSaveRamAnchoredMovie(this ITasMovie old, byte[] saveRam) + public static ITasMovie ConvertToSaveRamAnchoredMovie(this ITasMovie old, byte[] saveRam, IEmulator emulator) { string newFilename = ConvertFileNameToTasMovie(old.Filename); @@ -146,7 +147,7 @@ public static ITasMovie ConvertToSaveRamAnchoredMovie(this ITasMovie old, byte[] tas.Subtitles.Add(sub); } - tas.Save(); + tas.Save(emulator); return tas; } diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index eaaeef1fdc5..cb5a4521397 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -48,6 +48,8 @@ public MovieSession( public string BackupDirectory { get; set; } + private IEmulator/*?*/ Emulator = null; + public IMovie Movie { get; private set; } public bool ReadOnly { get; set; } = true; public bool NewMovieQueued => _queuedMovie != null; @@ -81,7 +83,7 @@ public void HandleFrameBefore() } else if (Movie.IsFinished()) { - if (Movie.Emulator.Frame < Movie.FrameCount) // This scenario can happen from rewinding (suddenly we are back in the movie, so hook back up to the movie + if (Emulator.Frame < Movie.FrameCount) // This scenario can happen from rewinding (suddenly we are back in the movie, so hook back up to the movie { Movie.SwitchToPlay(); LatchInputToLog(); @@ -98,7 +100,8 @@ public void HandleFrameBefore() else if (Movie.IsRecording()) { LatchInputToUser(); - Movie.RecordFrame(Movie.Emulator.Frame, MovieOut.Source); + var frame = Emulator.Frame; + Movie.RecordFrame(frame, frame, MovieOut.Source); } } @@ -106,10 +109,10 @@ public void HandleFrameAfter(bool ignoreMovieEndAction) { if (Movie is ITasMovie tasMovie) { - tasMovie.GreenzoneCurrentFrame(); + tasMovie.GreenzoneCurrentFrame(Emulator); } - if (!ignoreMovieEndAction && Movie.IsPlaying() && Movie.Emulator.Frame == Movie.FrameCount) + if (!ignoreMovieEndAction && Movie.IsPlaying() && Emulator.Frame == Movie.FrameCount) { HandlePlaybackEnd(); } @@ -154,7 +157,7 @@ public bool HandleLoadState(TextReader reader) { Movie.SwitchToRecord(); - var result = Movie.ExtractInputLog(reader, out var errorMsg); + var result = Movie.ExtractInputLog(reader, Emulator, out var errorMsg); if (!result) { Output(errorMsg); @@ -222,15 +225,16 @@ public void RunQueuedMovie(bool recordMode, IEmulator emulator) MovieController = new Bk2Controller(emulator.ControllerDefinition, _queuedMovie.LogKey); Movie = _queuedMovie; + Emulator = emulator; Movie.Attach(emulator); _queuedMovie = null; - Movie.ProcessSavestate(Movie.Emulator); - Movie.ProcessSram(Movie.Emulator); + Movie.ProcessSavestate(emulator); + Movie.ProcessSram(emulator); if (recordMode) { - Movie.StartNewRecording(); + Movie.StartNewRecording(Emulator); ReadOnly = false; // If we are starting a movie recording while another one is playing, we need to switch back to user input LatchInputToUser(); @@ -262,7 +266,7 @@ public void StopMovie(bool saveChanges = true) if (saveChanges && Movie.Changes) { - Movie.Save(); + Movie.Save(Emulator); Output($"{Path.GetFileName(Movie.Filename)} written to disk."); } Movie.Stop(); @@ -289,7 +293,7 @@ public IMovie Get(string path, bool loadMovie) : new Bk2Movie(this, path); if (loadMovie) - movie.Load(); + movie.Load(Emulator); return movie; } @@ -307,7 +311,7 @@ private void LatchInputToUser() // Latch input from the input log, if available private void LatchInputToLog() { - var input = Movie.GetInputState(Movie.Emulator.Frame); + var input = Movie.GetInputState(Emulator.Frame); MovieController.SetFrom(input ?? StickySource); MovieOut.Source = MovieController; @@ -317,10 +321,10 @@ private void HandlePlaybackEnd() { #if false // invariants given by single call-site Debug.Assert(Movie.IsPlaying()); - Debug.Assert(Movie.Emulator.Frame >= Movie.InputLogLength); + Debug.Assert(Emulator.Frame >= Movie.InputLogLength); #endif #if false // code below doesn't actually do anything as the cycle count is indiscriminately overwritten (or removed) on save anyway. - if (Movie.IsAtEnd() && Movie.Emulator.HasCycleTiming()) + if (Movie.IsAtEnd() && Emulator.HasCycleTiming()) { const string WINDOW_TITLE_MISMATCH = "Cycle count mismatch"; const string WINDOW_TITLE_MISSING = "Cycle count not yet saved"; @@ -330,7 +334,7 @@ private void HandlePlaybackEnd() const string PFX_MISMATCH = "The cycle count (running time) saved into this movie ({0}) doesn't match the measured count ({1}) here at the end.\n"; const string ERR_FMT_STR_MISMATCH_READONLY = PFX_MISMATCH + "The movie was loaded in read-only mode. To correct the cycle count, load it in read-write mode and play to the end again."; const string ERR_FMT_STR_MISMATCH_CONFIRM = PFX_MISMATCH + "Correct it now?"; - var coreValue = Movie.Emulator.AsCycleTiming().CycleCount; + var coreValue = Emulator.AsCycleTiming().CycleCount; if (!Movie.HeaderEntries.TryGetValue(HeaderKeys.CycleCount, out var movieValueStr) || !long.TryParse(movieValueStr, out var movieValue)) { diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 6e8ca063afc..f0a51e03973 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -12,12 +12,10 @@ namespace BizHawk.Client.Common { public partial class Bk2Movie { - public void Save() - { - Write(Filename); - } + public void Save(IEmulator emulator) + => Write(Filename, emulator); - public void SaveBackup() + public void SaveBackup(IEmulator emulator) { if (string.IsNullOrWhiteSpace(Filename)) { @@ -27,12 +25,12 @@ public void SaveBackup() var backupName = Filename.InsertBeforeLast('.', insert: $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}", out _); backupName = Path.Combine(Session.BackupDirectory, Path.GetFileName(backupName)); - Write(backupName, isBackup: true); + Write(backupName, emulator, isBackup: true); } - protected virtual void Write(string fn, bool isBackup = false) + private void Write(string fn, IEmulator emulator, bool isBackup = false) { - SetCycleValues(); + SetCycleValues(emulator); // EmulatorVersion used to store the unchanging original emulator version. if (!Header.ContainsKey(HeaderKeys.OriginalEmulatorVersion)) { @@ -50,10 +48,10 @@ protected virtual void Write(string fn, bool isBackup = false) } } - public void SetCycleValues() //TODO IEmulator should not be an instance prop of movies, it should be passed in to every call (i.e. from MovieService) --yoshi + public void SetCycleValues(IEmulator emulator) { // The saved cycle value will only be valid if the end of the movie has been emulated. - if (this.IsAtEnd() && Emulator.AsCycleTiming() is { } cycleCore) + if (this.IsAtEnd(emulator) && emulator.AsCycleTiming() is { } cycleCore) { // legacy movies may incorrectly have no ClockRate header value set Header[HeaderKeys.ClockRate] = cycleCore.ClockRate.ToString(NumberFormatInfo.InvariantInfo); @@ -117,18 +115,18 @@ private void ClearBk2Fields() BinarySavestate = null; } - protected override void LoadFields(ZipStateLoader bl) + protected override void LoadFields(ZipStateLoader bl, IEmulator emulator) { - base.LoadFields(bl); - LoadBk2Fields(bl); + base.LoadFields(bl, emulator); + LoadBk2Fields(bl, emulator); } - private void LoadBk2Fields(ZipStateLoader bl) + private void LoadBk2Fields(ZipStateLoader bl, IEmulator emulator) { bl.GetLump(BinaryStateLump.Input, abort: true, tr => { IsCountingRerecords = false; - ExtractInputLog(tr, out _); + ExtractInputLog(tr, emulator, out _); IsCountingRerecords = true; }); diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs index 90c26b2992e..db17f7c24ff 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs @@ -2,6 +2,7 @@ using System.IO; using BizHawk.Common; using BizHawk.Common.StringExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -31,7 +32,7 @@ public string GetInputLogEntry(int frame) : ""; } - public virtual bool ExtractInputLog(TextReader reader, out string errorMessage) + public virtual bool ExtractInputLog(TextReader reader, IEmulator emulator, out string errorMessage) { errorMessage = ""; int? stateFrame = null; @@ -39,7 +40,7 @@ public virtual bool ExtractInputLog(TextReader reader, out string errorMessage) // We are in record mode so replace the movie log with the one from the savestate if (Session.Settings.EnableBackupMovies && MakeBackup && Log.Count != 0) { - SaveBackup(); + SaveBackup(emulator); MakeBackup = false; } diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.ModeApi.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.ModeApi.cs index 4cc54e3e642..b09175d4b8c 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.ModeApi.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.ModeApi.cs @@ -1,15 +1,17 @@ -namespace BizHawk.Client.Common +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common { public partial class Bk2Movie { public MovieMode Mode { get; protected set; } = MovieMode.Inactive; - public virtual void StartNewRecording() + public virtual void StartNewRecording(IEmulator emulator) { Mode = MovieMode.Record; if (MakeBackup && Session.Settings.EnableBackupMovies && Log.Count is not 0) { - SaveBackup(); + SaveBackup(emulator); MakeBackup = false; } diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs index 3c14d94c7ef..ccf941fd222 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common @@ -13,14 +14,19 @@ public Bk2Movie(IMovieSession session, string filename) : base(filename) Header[HeaderKeys.MovieVersion] = "BizHawk v2.0.0"; } +#pragma warning disable CS0618 // this is the sanctioned call-site public virtual void Attach(IEmulator emulator) { Emulator = emulator; } - protected bool IsAttached() => Emulator != null; + public void CheckAttachedMatches(IEmulator/*?*/ passed) + => Debug.Assert(object.ReferenceEquals(passed, Emulator), $"Core instance doesn't match the object cached on the movie! (Missed call to {nameof(Attach)}?)"); +#pragma warning restore CS0618 + + [Obsolete("do not use")] + protected IEmulator Emulator { get; private set; } - public IEmulator Emulator { get; private set; } public IMovieSession Session { get; } protected bool MakeBackup { get; set; } = true; @@ -52,17 +58,17 @@ public void AppendFrame(IController source) Changes = true; } - public virtual void RecordFrame(int frame, IController source) + public virtual void RecordFrame(int targetFrame, int currentFrame, IController source) { if (Session.Settings.VBAStyleMovieLoadState) { - if (Emulator.Frame < Log.Count) + if (currentFrame < Log.Count) { - Truncate(Emulator.Frame); + Truncate(currentFrame); } } - SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); + SetFrameAt(targetFrame, Bk2LogEntryGenerator.GenerateLogEntry(source)); Changes = true; } diff --git a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs index 91a30f734fa..8311bfe20c4 100644 --- a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs +++ b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs @@ -4,6 +4,7 @@ using BizHawk.Common; using BizHawk.Common.StringExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -11,6 +12,7 @@ public interface IMovieImport { ImportResult Import( IDialogParent dialogParent, + IEmulator emulator, IMovieSession session, string path, Config config); @@ -26,6 +28,7 @@ internal abstract class MovieImporter : IMovieImport public ImportResult Import( IDialogParent dialogParent, + IEmulator emulator, IMovieSession session, string path, Config config) @@ -67,7 +70,7 @@ public ImportResult Import( Result.Movie.Hash = hash; } - Result.Movie.Save(); + Result.Movie.Save(emulator); } return Result; diff --git a/src/BizHawk.Client.Common/movie/import/MovieImport.cs b/src/BizHawk.Client.Common/movie/import/MovieImport.cs index 05076528e0a..40d7864c414 100644 --- a/src/BizHawk.Client.Common/movie/import/MovieImport.cs +++ b/src/BizHawk.Client.Common/movie/import/MovieImport.cs @@ -5,6 +5,7 @@ using BizHawk.Common.CollectionExtensions; using BizHawk.Common.StringExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -30,6 +31,7 @@ public static bool IsValidMovieExtension(string extension) // Attempt to import another type of movie file into a movie object. public static ImportResult ImportFile( IDialogParent dialogParent, + IEmulator emulator, IMovieSession session, string path, Config config) @@ -39,7 +41,7 @@ public static ImportResult ImportFile( // Create a new instance of the importer class using the no-argument constructor return result is { Key: var importerType } && importerType.GetConstructor(Type.EmptyTypes)?.Invoke(Array.Empty()) is IMovieImport importer - ? importer.Import(dialogParent, session, path, config) + ? importer.Import(dialogParent, emulator, session, path, config) : ImportResult.Error($"No importer found for file type {ext}"); } } diff --git a/src/BizHawk.Client.Common/movie/interfaces/IBasicMovieInfo.cs b/src/BizHawk.Client.Common/movie/interfaces/IBasicMovieInfo.cs index f02ed54a452..7a755ac13a8 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IBasicMovieInfo.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IBasicMovieInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -53,6 +54,6 @@ public interface IBasicMovieInfo /// Tells the movie to load the contents of Filename /// /// Return whether or not the file was successfully loaded - bool Load(); + bool Load(IEmulator emulator); } } diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs index c6b9d68c80a..c37e971ffc7 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs @@ -74,15 +74,15 @@ public interface IMovie : IBasicMovieInfo /// /// Forces the creation of a backup file of the current movie state /// - void SaveBackup(); + void SaveBackup(IEmulator emulator); /// /// Instructs the movie to save the current contents to Filename /// - void Save(); + void Save(IEmulator emulator); /// updates the and headers from the currently loaded core - void SetCycleValues(); + void SetCycleValues(IEmulator emulator); /// /// Writes the input log directly to the stream, bypassing the need to load it all into ram as a string @@ -109,12 +109,12 @@ public interface IMovie : IBasicMovieInfo /// /// The reader containing the contents of the input log /// Returns an error message, if any - bool ExtractInputLog(TextReader reader, out string errorMessage); + bool ExtractInputLog(TextReader reader, IEmulator emulator, out string errorMessage); /// /// Tells the movie to start recording from the beginning. /// - void StartNewRecording(); + void StartNewRecording(IEmulator emulator); /// /// Tells the movie to start playback from the beginning @@ -157,7 +157,7 @@ public interface IMovie : IBasicMovieInfo /// Records the given input into the given frame, /// This is subject to normal movie recording logic /// - void RecordFrame(int frame, IController source); + void RecordFrame(int targetFrame, int currentFrame, IController source); /// /// Instructs the movie to remove all input from its input log starting with the input at frame. @@ -183,11 +183,6 @@ public interface IMovie : IBasicMovieInfo /// void Attach(IEmulator emulator); - /// - /// The currently attached core or null if not yet attached - /// - IEmulator Emulator { get; } - /// /// The current movie session /// @@ -195,6 +190,8 @@ public interface IMovie : IBasicMovieInfo IStringLog GetLogEntries(); void CopyLog(IEnumerable log); + + void CheckAttachedMatches(IEmulator/*?*/ passed); } public static class MovieExtensions @@ -218,8 +215,12 @@ public static bool IsPlayingOrRecording(this IMovie movie) /// Emulation is currently right after the movie's last input frame, /// but no further frames have been emulated. /// - public static bool IsAtEnd(this IMovie movie) => movie != null && movie.Emulator?.Frame == movie.InputLogLength; - + public static bool IsAtEnd(this IMovie movie, IEmulator emulator) + { + if (movie is null) return false; + movie.CheckAttachedMatches(emulator); + return emulator?.Frame == movie.InputLogLength; + } /// /// If the given movie contains a savestate it will be loaded if diff --git a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs index 55ed40cb824..8fc68332a8f 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/ITasMovie.cs @@ -37,7 +37,7 @@ public interface ITasMovie : IMovie, INotifyPropertyChanged, IDisposable /// void ClearFrame(int frame); - void GreenzoneCurrentFrame(); + void GreenzoneCurrentFrame(IEmulator emulator); void ToggleBoolState(int frame, string buttonName); void SetAxisState(int frame, string buttonName, int val); void SetAxisStates(int frame, int count, string buttonName, int val); diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index e5bbfeecb4b..16570e2e152 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -18,16 +18,16 @@ internal partial class TasMovie // InvalidateAfter being last ensures that the GreenzoneInvalidated callback sees all edits. - public override void RecordFrame(int frame, IController source) + public override void RecordFrame(int targetFrame, int currentFrame, IController source) { - ChangeLog.AddGeneralUndo(frame, frame, $"Record Frame: {frame}"); - SetFrameAt(frame, Bk2LogEntryGenerator.GenerateLogEntry(source)); + ChangeLog.AddGeneralUndo(targetFrame, targetFrame, $"Record Frame: {targetFrame}"); + SetFrameAt(targetFrame, Bk2LogEntryGenerator.GenerateLogEntry(source)); ChangeLog.SetGeneralRedo(); - LagLog[frame] = _inputPollable.IsLagFrame; + LagLog[targetFrame] = _inputPollable.IsLagFrame; LastEditWasRecording = true; - InvalidateAfter(frame); + InvalidateAfter(targetFrame); } public override void Truncate(int frame) diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 0650635f553..5af112372be 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -1,5 +1,5 @@ using System.IO; - +using BizHawk.Emulation.Common; using Newtonsoft.Json; namespace BizHawk.Client.Common @@ -62,9 +62,9 @@ private void ClearTasprojExtras() ChangeLog.Clear(); } - protected override void LoadFields(ZipStateLoader bl) + protected override void LoadFields(ZipStateLoader bl, IEmulator emulator) { - base.LoadFields(bl); + base.LoadFields(bl, emulator); if (MovieService.IsCurrentTasVersion(Header[HeaderKeys.MovieVersion])) { diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index b6434c2abc2..94dde7f705c 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -93,7 +93,10 @@ public ITasMovieRecord this[int index] var lagged = LagLog[lagIndex]; if (lagged == null) { - if (IsAttached() && Emulator.Frame == lagIndex) +#pragma warning disable CS0618 //TODO pass in emulator... that is supported on indexers, but maybe there's a better way? + var currentFrame = Emulator?.Frame ?? -1; +#pragma warning restore CS0618 + if (lagIndex == currentFrame) { lagged = _inputPollable.IsLagFrame; } @@ -109,13 +112,13 @@ public ITasMovieRecord this[int index] } } - public override void StartNewRecording() + public override void StartNewRecording(IEmulator emulator) { ClearTasprojExtras(); Markers.Add(new TasMovieMarker(0, StartsFromSavestate ? "Savestate" : "Power on"), skipHistory: true); ClearChanges(); - base.StartNewRecording(); + base.StartNewRecording(emulator); } // Removes lag log and greenzone after this frame @@ -181,12 +184,12 @@ private static string CreateDisplayValueForButton(IController adapter, string bu return "!"; } - public void GreenzoneCurrentFrame() + public void GreenzoneCurrentFrame(IEmulator emulator) { - LagLog[Emulator.Frame] = _inputPollable.IsLagFrame; + LagLog[emulator.Frame] = _inputPollable.IsLagFrame; // We will forcibly capture a state for the last edited frame (requested by https://github.com/TASEmulators/BizHawk/issues/916 for case of "platforms with analog stick") - TasStateManager.Capture(Emulator.Frame, Emulator.AsStatable(), Emulator.Frame == LastEditedFrame - 1); + TasStateManager.Capture(emulator.Frame, emulator.AsStatable(), emulator.Frame == LastEditedFrame - 1); } @@ -199,7 +202,7 @@ public void CopyVerificationLog(IEnumerable log) } // TODO: this is 99% copy pasting of bad code - public override bool ExtractInputLog(TextReader reader, out string errorMessage) + public override bool ExtractInputLog(TextReader reader, IEmulator emulator, out string errorMessage) { errorMessage = ""; int? stateFrame = null; @@ -210,7 +213,7 @@ public override bool ExtractInputLog(TextReader reader, out string errorMessage) // We are in record mode so replace the movie log with the one from the savestate if (Session.Settings.EnableBackupMovies && MakeBackup && Log.Count != 0) { - SaveBackup(); + SaveBackup(emulator); MakeBackup = false; } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs index 6a2a7c35e4d..a05dd4bb14c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -410,7 +410,7 @@ private void StopMovieWithoutSavingMenuItem_Click(object sender, EventArgs e) { if (Config.Movies.EnableBackupMovies) { - MovieSession.Movie.SaveBackup(); + MovieSession.Movie.SaveBackup(Emulator); } StopMovie(saveChanges: false); @@ -1331,14 +1331,19 @@ private void LoadLastMovieContextMenuItem_Click(object sender, EventArgs e) private void BackupMovieContextMenuItem_Click(object sender, EventArgs e) { - MovieSession.Movie.SaveBackup(); + MovieSession.Movie.SaveBackup(Emulator); AddOnScreenMessage("Backup movie saved."); } private void ViewSubtitlesContextMenuItem_Click(object sender, EventArgs e) { if (MovieSession.Movie.NotActive()) return; - using EditSubtitlesForm form = new(this, MovieSession.Movie, Config.PathEntries, readOnly: MovieSession.ReadOnly); + using EditSubtitlesForm form = new( + this, + Emulator, + MovieSession.Movie, + Config.PathEntries, + readOnly: MovieSession.ReadOnly); this.ShowDialogWithTempMute(form); } @@ -1375,7 +1380,7 @@ private void AddSubtitleContextMenuItem_Click(object sender, EventArgs e) private void ViewCommentsContextMenuItem_Click(object sender, EventArgs e) { if (MovieSession.Movie.NotActive()) return; - using EditCommentsForm form = new(MovieSession.Movie, MovieSession.ReadOnly); + using EditCommentsForm form = new(MovieSession.Movie, Emulator, MovieSession.ReadOnly); this.ShowDialogWithTempMute(form); } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index b09ded06131..e2ef4170555 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -2665,7 +2665,7 @@ private void SaveMovie() { if (MovieSession.Movie.IsActive()) { - MovieSession.Movie.Save(); + MovieSession.Movie.Save(Emulator); AddOnScreenMessage($"{MovieSession.Movie.Filename} saved."); } } @@ -3981,7 +3981,7 @@ public void CloseRom(bool clearSram = false) private void ProcessMovieImport(string fn, bool start) { - var result = MovieImport.ImportFile(this, MovieSession, fn, Config); + var result = MovieImport.ImportFile(this, Emulator, MovieSession, fn, Config); if (result.Errors.Count is not 0) { diff --git a/src/BizHawk.Client.EmuHawk/Program.cs b/src/BizHawk.Client.EmuHawk/Program.cs index 5b0e4023546..6a122cee064 100644 --- a/src/BizHawk.Client.EmuHawk/Program.cs +++ b/src/BizHawk.Client.EmuHawk/Program.cs @@ -388,7 +388,7 @@ IGL CheckRenderer(IGL gl) ); if (result == DialogResult.Yes) { - movieSession.Movie.Save(); + movieSession.Movie.Save(mf.Emulator); } } #endif diff --git a/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs b/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs index 0ddb2e07fa3..eb876a70d5d 100644 --- a/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs +++ b/src/BizHawk.Client.EmuHawk/movie/EditCommentsForm.cs @@ -1,19 +1,23 @@ using System.ComponentModel; using System.Windows.Forms; using BizHawk.Client.Common; +using BizHawk.Emulation.Common; namespace BizHawk.Client.EmuHawk { public partial class EditCommentsForm : Form { + private readonly IEmulator _emulator; + private readonly IMovie _movie; private readonly bool _readOnly; private string _lastHeaderClicked; private bool _sortReverse; private readonly bool _dispose; - public EditCommentsForm(IMovie movie, bool readOnly, bool disposeOnClose = false) + public EditCommentsForm(IMovie movie, IEmulator emulator, bool readOnly, bool disposeOnClose = false) { + _emulator = emulator; _movie = movie; _readOnly = readOnly; _lastHeaderClicked = ""; @@ -55,7 +59,7 @@ private void Save() _movie.Comments.Add(c.Value.ToString()); } - _movie.Save(); + _movie.Save(_emulator); } private void Cancel_Click(object sender, EventArgs e) diff --git a/src/BizHawk.Client.EmuHawk/movie/EditSubtitlesForm.cs b/src/BizHawk.Client.EmuHawk/movie/EditSubtitlesForm.cs index 9ac8dc3f28d..d1d213af5ad 100644 --- a/src/BizHawk.Client.EmuHawk/movie/EditSubtitlesForm.cs +++ b/src/BizHawk.Client.EmuHawk/movie/EditSubtitlesForm.cs @@ -5,6 +5,7 @@ using BizHawk.Client.Common; using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Client.EmuHawk { @@ -14,14 +15,23 @@ public partial class EditSubtitlesForm : Form, IDialogParent private readonly PathEntryCollection _pathEntries; + private readonly IEmulator _emulator; + private readonly IMovie _selectedMovie; private readonly bool _readOnly; private readonly bool _dispose; public IDialogController DialogController { get; } - public EditSubtitlesForm(IDialogController dialogController, IMovie movie, PathEntryCollection pathEntries, bool readOnly, bool disposeOnClose = false) + public EditSubtitlesForm( + IDialogController dialogController, + IEmulator emulator, + IMovie movie, + PathEntryCollection pathEntries, + bool readOnly, + bool disposeOnClose = false) { + _emulator = emulator; _pathEntries = pathEntries; _selectedMovie = movie; _readOnly = readOnly; @@ -115,7 +125,7 @@ private void Ok_Click(object sender, EventArgs e) sub.Message = c.Value?.ToString(); _selectedMovie.Subtitles.Add(sub); } - _selectedMovie.Save(); + _selectedMovie.Save(_emulator); } Close(); diff --git a/src/BizHawk.Client.EmuHawk/movie/PlayMovie.cs b/src/BizHawk.Client.EmuHawk/movie/PlayMovie.cs index 7c5caa593da..75f42843270 100644 --- a/src/BizHawk.Client.EmuHawk/movie/PlayMovie.cs +++ b/src/BizHawk.Client.EmuHawk/movie/PlayMovie.cs @@ -159,7 +159,7 @@ private IBasicMovieInfo LoadMovieInfo(HawkFile hf, bool force) try { - movie.Load(); + movie.Load(_emulator); // Don't do this from browse if (movie.Hash == _game.Hash @@ -518,7 +518,7 @@ private void CommentsBtn_Click(object sender, EventArgs e) { // TODO this will allocate unnecessary memory when this movie is a TasMovie due to TasStateManager var movie = _movieSession.Get(_movieList[MovieView.SelectedIndices[0]].Filename, true); - var form = new EditCommentsForm(movie, readOnly: false, disposeOnClose: true); + using EditCommentsForm form = new(movie, _emulator, readOnly: false, disposeOnClose: true); form.Show(); } } @@ -530,7 +530,13 @@ private void SubtitlesBtn_Click(object sender, EventArgs e) { // TODO this will allocate unnecessary memory when this movie is a TasMovie due to TasStateManager var movie = _movieSession.Get(_movieList[MovieView.SelectedIndices[0]].Filename, true); - var form = new EditSubtitlesForm(DialogController, movie, _config.PathEntries, readOnly: false, disposeOnClose: true); + using EditSubtitlesForm form = new( + DialogController, + _emulator, + movie, + _config.PathEntries, + readOnly: false, + disposeOnClose: true); form.Show(); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs index 062edf5707e..c6b22c041fc 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.IToolForm.cs @@ -56,9 +56,10 @@ private void UpdateProgressBar() protected override void UpdateBefore() { - if (CurrentTasMovie.IsAtEnd() && !CurrentTasMovie.IsRecording()) + if (CurrentTasMovie.IsAtEnd(Emulator) && !CurrentTasMovie.IsRecording()) { - CurrentTasMovie.RecordFrame(CurrentTasMovie.Emulator.Frame, MovieSession.StickySource); + var frame = Emulator.Frame; + CurrentTasMovie.RecordFrame(frame, frame, MovieSession.StickySource); } } @@ -94,8 +95,8 @@ protected override void UpdateAfter() if (Settings.AutoPause && _seekingTo == -1) { - if (_doPause && CurrentTasMovie.IsAtEnd()) MainForm.PauseEmulator(); - _doPause = !CurrentTasMovie.IsAtEnd(); + if (_doPause && CurrentTasMovie.IsAtEnd(Emulator)) MainForm.PauseEmulator(); + _doPause = !CurrentTasMovie.IsAtEnd(Emulator); } if (!_seekingByEdit) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 50683cebb3f..559118a77df 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -44,7 +44,9 @@ private void StartNewProjectFromNowMenuItem_Click(object sender, EventArgs e) if (AskSaveChanges()) { var newProject = CurrentTasMovie.ConvertToSavestateAnchoredMovie( - Emulator.Frame, StatableEmulator.CloneSavestate()); + Emulator.Frame, + StatableEmulator.CloneSavestate(), + Emulator); MainForm.PauseEmulator(); LoadMovie(newProject, true); @@ -57,7 +59,7 @@ private void StartANewProjectFromSaveRamMenuItem_Click(object sender, EventArgs { var saveRam = SaveRamEmulator?.CloneSaveRam(clearDirty: false) ?? throw new Exception("No SaveRam"); GoToFrame(TasView.AnyRowsSelected ? TasView.FirstSelectedRowIndex : 0); - var newProject = CurrentTasMovie.ConvertToSaveRamAnchoredMovie(saveRam); + var newProject = CurrentTasMovie.ConvertToSaveRamAnchoredMovie(saveRam, Emulator); MainForm.PauseEmulator(); LoadMovie(newProject, true); } @@ -202,7 +204,7 @@ private void ToBk2MenuItem_Click(object sender, EventArgs e) { _autosaveTimer.Stop(); - if (Emulator.HasCycleTiming() && !CurrentTasMovie.IsAtEnd()) + if (Emulator.HasCycleTiming() && !CurrentTasMovie.IsAtEnd(Emulator)) { DialogController.ShowMessageBox("This core requires emulation to be on the last frame when writing the movie, otherwise movie length will appear incorrect.", "Warning", EMsgBoxIcon.Warning); } @@ -224,7 +226,7 @@ private void ToBk2MenuItem_Click(object sender, EventArgs e) var bk2 = CurrentTasMovie.ToBk2(); bk2.Filename = fileInfo.FullName; bk2.Attach(Emulator); // required to be able to save the cycle count for ICycleTiming emulators - bk2.Save(); + bk2.Save(Emulator); MessageStatusLabel.Text = $"{bk2.Name} exported."; Cursor = Cursors.Default; } @@ -943,7 +945,7 @@ private void UseOldSavestateManagerMenuItem_Click(object sender, EventArgs e) private void CommentsMenuItem_Click(object sender, EventArgs e) { - using EditCommentsForm form = new(CurrentTasMovie, false) + using EditCommentsForm form = new(CurrentTasMovie, Emulator, false) { Owner = this, StartPosition = FormStartPosition.Manual, @@ -956,6 +958,7 @@ private void SubtitlesMenuItem_Click(object sender, EventArgs e) { using EditSubtitlesForm form = new( DialogController, + Emulator, CurrentTasMovie, Config!.PathEntries, readOnly: false) diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index ff76d9f5fa6..a8387d1b75b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -278,7 +278,7 @@ private bool Engage() EMsgBoxIcon.Question); if (result == true) { - MovieSession.Movie.Save(); + MovieSession.Movie.Save(Emulator); } else if (result == null) { @@ -553,7 +553,7 @@ private int? FirstNonEmptySelectedFrame private ITasMovie ConvertCurrentMovieToTasproj() { var tasMovie = MovieSession.Movie.ToTasMovie(); - tasMovie.Save(); // should this be done? + tasMovie.Save(Emulator); // should this be done? return tasMovie; } @@ -801,9 +801,9 @@ private void SaveTas(bool saveAsBk2 = false, bool saveBackup = false) } if (saveBackup) - movieToSave.SaveBackup(); + movieToSave.SaveBackup(Emulator); else - movieToSave.Save(); + movieToSave.Save(Emulator); MessageStatusLabel.Text = saveBackup ? $"Backup .{(saveAsBk2 ? MovieService.StandardMovieExtension : MovieService.TasMovieExtension)} saved to \"Movie backups\" path." @@ -837,7 +837,7 @@ private void SaveAsTas() MessageStatusLabel.Owner.Update(); Cursor = Cursors.WaitCursor; CurrentTasMovie.Filename = fileInfo.FullName; - CurrentTasMovie.Save(); + CurrentTasMovie.Save(Emulator); Settings.RecentTas.Add(CurrentTasMovie.Filename); MessageStatusLabel.Text = "File saved."; Cursor = Cursors.Default; diff --git a/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs b/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs index bd142829c13..7b784ce3831 100644 --- a/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs +++ b/src/BizHawk.Tests.Client.Common/Movie/MovieUndoTests.cs @@ -184,39 +184,39 @@ public void BatchedEdit() [TestMethod] public void RecordFrameAtEnd() { - ITasMovie movie = TasMovieTests.MakeMovie(5); + ITasMovie movie = TasMovieTests.MakeMovie(5, out var emulator); ValidateActionCanUndoAndRedo(movie, () => { - Bk2Controller controller = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controller = new Bk2Controller(emulator.ControllerDefinition); controller.SetBool("A", true); - movie.RecordFrame(5, controller); + movie.RecordFrame(targetFrame: 5, currentFrame: emulator.Frame, controller); }); } [TestMethod] public void RecordFrameInMiddle() { - ITasMovie movie = TasMovieTests.MakeMovie(5); + ITasMovie movie = TasMovieTests.MakeMovie(5, out var emulator); ValidateActionCanUndoAndRedo(movie, () => { - Bk2Controller controller = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controller = new Bk2Controller(emulator.ControllerDefinition); controller.SetBool("A", true); - movie.RecordFrame(2, controller); + movie.RecordFrame(targetFrame: 2, currentFrame: emulator.Frame, controller); }); } [TestMethod] public void RecordFrameZero() { - ITasMovie movie = TasMovieTests.MakeMovie(5); + ITasMovie movie = TasMovieTests.MakeMovie(5, out var emulator); ValidateActionCanUndoAndRedo(movie, () => { - Bk2Controller controller = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controller = new Bk2Controller(emulator.ControllerDefinition); controller.SetBool("A", true); - movie.RecordFrame(0, controller); + movie.RecordFrame(targetFrame: 0, currentFrame: emulator.Frame, controller); }); } @@ -277,18 +277,19 @@ public void MarkersUnaffectedByMovieExtension() [TestMethod] public void AllOperationsRespectBatching() { - ITasMovie movie = TasMovieTests.MakeMovie(10); + ITasMovie movie = TasMovieTests.MakeMovie(10, out var emulator); // Some actions can move markers. movie.Markers.Add(9, ""); movie.BindMarkersToInput = true; - Bk2Controller controllerA = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controllerA = new Bk2Controller(emulator.ControllerDefinition); controllerA.SetBool("A", true); string entryA = Bk2LogEntryGenerator.GenerateLogEntry(controllerA); int beginIndex = 0; TasMovieTests.TestAllOperations(movie, + emulator, () => { beginIndex = movie.ChangeLog.UndoIndex; @@ -308,7 +309,7 @@ public void AllOperationsRespectBatching() [TestMethod] public void AllOperationsGiveOneUndo() { - ITasMovie movie = TasMovieTests.MakeMovie(10); + ITasMovie movie = TasMovieTests.MakeMovie(10, out var emulator); // Some actions can move markers. movie.Markers.Add(9, ""); @@ -316,6 +317,7 @@ public void AllOperationsGiveOneUndo() int beginIndex = 0; TasMovieTests.TestAllOperations(movie, + emulator, () => beginIndex = movie.ChangeLog.UndoIndex, #pragma warning disable BHI1600 //TODO disambiguate assert calls () => Assert.AreEqual(1, movie.ChangeLog.UndoIndex - beginIndex) @@ -424,10 +426,10 @@ public void DeleteRespectsMarkerBinding() public void GeneralRespectsMarkerBinding() { // This was just a silly bug. - ITasMovie movie = TasMovieTests.MakeMovie(5); + ITasMovie movie = TasMovieTests.MakeMovie(5, out var emulator); movie.Markers.Add(3, "a"); - Bk2Controller controllerA = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controllerA = new Bk2Controller(emulator.ControllerDefinition); controllerA.SetBool("A", true); movie.BindMarkersToInput = true; diff --git a/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs b/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs index 94c95e9d62e..06962a68c24 100644 --- a/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs +++ b/src/BizHawk.Tests.Client.Common/Movie/TasMovieTests.cs @@ -1,4 +1,5 @@ using BizHawk.Client.Common; +using BizHawk.Emulation.Common; namespace BizHawk.Tests.Client.Common.Movie { @@ -6,8 +7,11 @@ namespace BizHawk.Tests.Client.Common.Movie public class TasMovieTests { internal static TasMovie MakeMovie(int numberOfFrames) + => MakeMovie(numberOfFrames, out _); + + internal static TasMovie MakeMovie(int numberOfFrames, out IEmulator emu) { - FakeEmulator emu = new FakeEmulator(); + emu = new FakeEmulator(); FakeMovieSession session = new(emu); TasMovie movie = new(session, "/fake/path"); session.Movie = movie; @@ -18,11 +22,11 @@ internal static TasMovie MakeMovie(int numberOfFrames) return movie; } - public static void TestAllOperations(ITasMovie movie, Action PreOperation, Action PostOperation) + public static void TestAllOperations(ITasMovie movie, IEmulator emulator, Action PreOperation, Action PostOperation) { - Bk2Controller controllerEmpty = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controllerEmpty = new Bk2Controller(emulator.ControllerDefinition); string entryEmpty = Bk2LogEntryGenerator.GenerateLogEntry(controllerEmpty); - Bk2Controller controllerA = new Bk2Controller(movie.Emulator.ControllerDefinition); + Bk2Controller controllerA = new Bk2Controller(emulator.ControllerDefinition); controllerA.SetBool("A", true); string entryA = Bk2LogEntryGenerator.GenerateLogEntry(controllerA); @@ -37,7 +41,7 @@ void TestForSingleOperation(Action a) movie.ChangeLog.Undo(); } - TestForSingleOperation(() => movie.RecordFrame(1, controllerA)); + TestForSingleOperation(() => movie.RecordFrame(targetFrame: 1, currentFrame: emulator.Frame, controllerA)); TestForSingleOperation(() => movie.Truncate(3)); TestForSingleOperation(() => movie.PokeFrame(1, controllerA)); TestForSingleOperation(() => movie.SetFrame(1, entryA)); @@ -74,9 +78,10 @@ void TestForSingleOperation(Action a) [TestMethod] public void AllOperationsFlagChanges() { - ITasMovie movie = MakeMovie(10); + ITasMovie movie = MakeMovie(10, out var emulator); TestAllOperations(movie, + emulator, movie.ClearChanges, () => Assert.IsTrue(movie.Changes) ); @@ -85,11 +90,12 @@ public void AllOperationsFlagChanges() [TestMethod] public void AllOperationsProduceSingleInvalidation() { - ITasMovie movie = MakeMovie(10); + ITasMovie movie = MakeMovie(10, out var emulator); int invalidations = 0; movie.GreenzoneInvalidated = (_) => invalidations++; TestAllOperations(movie, + emulator, () => invalidations = 0, () => Assert.AreEqual(1, invalidations) );