diff --git a/src/BizHawk.Client.Common/movie/MovieSession.cs b/src/BizHawk.Client.Common/movie/MovieSession.cs index bd21a6816c..53beb1d1ab 100644 --- a/src/BizHawk.Client.Common/movie/MovieSession.cs +++ b/src/BizHawk.Client.Common/movie/MovieSession.cs @@ -18,7 +18,6 @@ namespace BizHawk.Client.Common private readonly Action _popupCallback; private IMovie _queuedMovie; - private IEmulator _emulator = new NullEmulator(); // Previous saved core preferences. Stored here so that when a movie // overrides the values, they can be restored to user preferences @@ -51,9 +50,9 @@ namespace BizHawk.Client.Common { get { - if (Movie.IsPlayingOrRecording() && _emulator.Frame > 0) + if (Movie.IsPlayingOrRecording() && Movie.Emulator.Frame > 0) { - return Movie.GetInputState(_emulator.Frame - 1); + return Movie.GetInputState(Movie.Emulator.Frame - 1); } return null; @@ -64,9 +63,9 @@ namespace BizHawk.Client.Common { get { - if (Movie.IsPlayingOrRecording() && _emulator.Frame > 1) + if (Movie.IsPlayingOrRecording() && Movie.Emulator.Frame > 1) { - return Movie.GetInputState(_emulator.Frame - 2); + return Movie.GetInputState(Movie.Emulator.Frame - 2); } return null; @@ -92,7 +91,7 @@ namespace BizHawk.Client.Common } else if (Movie.IsFinished()) { - 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 + 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 { Movie.SwitchToPlay(); LatchInputToLog(); @@ -127,7 +126,7 @@ namespace BizHawk.Client.Common if (!lg.IsEmpty) { LatchInputToUser(); - Movie.PokeFrame(_emulator.Frame, Global.InputManager.MovieOutputHardpoint); + Movie.PokeFrame(Movie.Emulator.Frame, Global.InputManager.MovieOutputHardpoint); } else { @@ -149,12 +148,12 @@ namespace BizHawk.Client.Common if (Movie is ITasMovie tasMovie) { tasMovie.GreenzoneCurrentFrame(); - if (tasMovie.IsPlayingOrFinished() && _emulator.Frame >= tasMovie.InputLogLength) + if (tasMovie.IsPlayingOrFinished() && Movie.Emulator.Frame >= tasMovie.InputLogLength) { HandleFrameLoopForRecordMode(); } } - else if (Movie.IsPlaying() && _emulator.Frame >= Movie.InputLogLength) + else if (Movie.IsPlaying() && Movie.Emulator.Frame >= Movie.InputLogLength) { HandlePlaybackEnd(); } @@ -290,7 +289,7 @@ namespace BizHawk.Client.Common public void RunQueuedMovie(bool recordMode, IEmulator emulator) { - _emulator = emulator; + _queuedMovie.Attach(emulator); foreach (var previousPref in _preferredCores) { Global.Config.PreferredCores[previousPref.Key] = previousPref.Value; @@ -298,10 +297,10 @@ namespace BizHawk.Client.Common Movie = _queuedMovie; _queuedMovie = null; - MultiTrack.Restart(_emulator.ControllerDefinition.PlayerCount); + MultiTrack.Restart(Movie.Emulator.ControllerDefinition.PlayerCount); - Movie.ProcessSavestate(_emulator); - Movie.ProcessSram(_emulator); + Movie.ProcessSavestate(Movie.Emulator); + Movie.ProcessSram(Movie.Emulator); if (recordMode) { @@ -359,14 +358,14 @@ namespace BizHawk.Client.Common Output(message); ReadOnly = true; - } - MultiTrack.Restart(_emulator.ControllerDefinition.PlayerCount); - _modeChangedCallback(); + MultiTrack.Restart(Movie.Emulator.ControllerDefinition.PlayerCount); + _modeChangedCallback(); + } // TODO: we aren't ready for this line, keeping the old movie hanging around masks a lot of Tastudio problems // Uncommenting this can cause drawing crashes in tastudio since it depends on a ITasMovie and doesn't have one between closing and opening a rom - //Movie = MovieService.Create(); + //Movie = null; } public void ConvertToTasProj() @@ -381,8 +380,8 @@ namespace BizHawk.Client.Common { if (Movie.IsPlayingOrFinished()) { - Movie.ClearFrame(_emulator.Frame); - Output($"Scrubbed input at frame {_emulator.Frame}"); + Movie.ClearFrame(Movie.Emulator.Frame); + Output($"Scrubbed input at frame {Movie.Emulator.Frame}"); } } @@ -408,9 +407,9 @@ namespace BizHawk.Client.Common rewiredSource.PlayerTargetMask = unchecked((int)0xFFFFFFFF); } - if (Movie.InputLogLength > _emulator.Frame) + if (Movie.InputLogLength > Movie.Emulator.Frame) { - var input = Movie.GetInputState(_emulator.Frame); + var input = Movie.GetInputState(Movie.Emulator.Frame); MovieController.SetFrom(input); } @@ -426,10 +425,10 @@ namespace BizHawk.Client.Common // Latch input from the input log, if available private void LatchInputToLog() { - var input = Movie.GetInputState(_emulator.Frame); + var input = Movie.GetInputState(Movie.Emulator.Frame); // adelikat: TODO: this is likely the source of frame 0 TAStudio bugs, I think the intent is to check if the movie is 0 length? - if (_emulator.Frame == 0) // Hacky + if (Movie.Emulator.Frame == 0) // Hacky { HandleFrameAfter(); // Frame 0 needs to be handled. } @@ -452,7 +451,7 @@ namespace BizHawk.Client.Common if (Movie.Core == CoreNames.Gambatte) { var movieCycles = Convert.ToUInt64(Movie.HeaderEntries[HeaderKeys.CycleCount]); - var coreCycles = ((Gameboy)_emulator).CycleCount; + var coreCycles = ((Gameboy)Movie.Emulator).CycleCount; if (movieCycles != (ulong)coreCycles) { PopupMessage($"Cycle count in the movie ({movieCycles}) doesn't match the emulated value ({coreCycles})."); @@ -502,7 +501,7 @@ namespace BizHawk.Client.Common // the movie session makes sure that the correct input has been read and merged to its MovieControllerAdapter; // this has been wired to Global.MovieOutputHardpoint in RewireInputChain - Movie.RecordFrame(_emulator.Frame, Global.InputManager.MovieOutputHardpoint); + Movie.RecordFrame(Movie.Emulator.Frame, Global.InputManager.MovieOutputHardpoint); } } } \ No newline at end of file diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 602b7ca45d..3263154778 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -165,15 +165,15 @@ namespace BizHawk.Client.Common protected virtual void Write(string fn, bool backup = false) { - if (Global.Emulator is Emulation.Cores.Nintendo.SubNESHawk.SubNESHawk subNes) + if (Emulator is Emulation.Cores.Nintendo.SubNESHawk.SubNESHawk subNes) { Header[HeaderKeys.VBlankCount] = subNes.VBL_CNT.ToString(); } - else if (Global.Emulator is Emulation.Cores.Nintendo.Gameboy.Gameboy gameboy) + else if (Emulator is Emulation.Cores.Nintendo.Gameboy.Gameboy gameboy) { Header[HeaderKeys.CycleCount] = gameboy.CycleCount.ToString(); } - else if (Global.Emulator is Emulation.Cores.Nintendo.SubGBHawk.SubGBHawk subGb) + else if (Emulator is Emulation.Cores.Nintendo.SubGBHawk.SubGBHawk subGb) { Header[HeaderKeys.CycleCount] = subGb.Cycle_CNT.ToString(); } diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs index 5012537470..9bbaf05a1c 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs @@ -19,6 +19,18 @@ namespace BizHawk.Client.Common Header[HeaderKeys.MovieVersion] = "BizHawk v2.0.0"; } + public virtual void Attach(IEmulator emulator) + { + if (!Emulator.IsNull()) + { + throw new InvalidOperationException("A core has already been attached!"); + } + + Emulator = emulator; + } + + public IEmulator Emulator { get; private set; } + protected bool MakeBackup { get; set; } = true; private string _filename; @@ -45,8 +57,7 @@ namespace BizHawk.Client.Common public ILogEntryGenerator LogGeneratorInstance(IController source) { - // TODO: when Bk2 movies have an instance of the core, use that - return new Bk2LogEntryGenerator(Global.Emulator.SystemId, source); + return new Bk2LogEntryGenerator(Emulator.SystemId, source); } public int FrameCount => Log.Count; @@ -86,9 +97,9 @@ namespace BizHawk.Client.Common { if (Global.Config.VBAStyleMovieLoadState) { - if (Global.Emulator.Frame < Log.Count) + if (Emulator.Frame < Log.Count) { - Truncate(Global.Emulator.Frame); + Truncate(Emulator.Frame); } } diff --git a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs index 2fbf3039a6..db789e40ea 100644 --- a/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs +++ b/src/BizHawk.Client.Common/movie/interfaces/IMovie.cs @@ -224,6 +224,22 @@ namespace BizHawk.Client.Common /// The frame of input to be retrieved /// A controller state representing the specified frame of input, if frame is out of range, will return null IMovieController GetInputState(int frame); + + /// + /// Attaches a core to the given movie instance, this must be done and + /// it must be done only once, a movie can not and should not exist for more + /// than the lifetime of the core + /// + /// + /// Thrown if attempting to attach a core when one is already attached + /// or if the given core does not meet all required dependencies + /// + void Attach(IEmulator emulator); + + /// + /// The currently attached core or null if not yet attached + /// + IEmulator Emulator { get; } } public static class MovieExtensions diff --git a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs index 95a678ad6a..e3c0146715 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/IStateManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -13,6 +14,17 @@ namespace BizHawk.Client.Common /// A savestate for the given frame or an empty array if there isn't one byte[] this[int frame] { get; } + /// + /// Attaches a core to the given state manager instance, this must be done and + /// it must be done only once, a state manager can not and should not exist for more + /// than the lifetime of the core + /// + /// + /// Thrown if attempting to attach a core when one is already attached + /// or if the given core does not meet all required dependencies + /// + void Attach(IEmulator emulator); + TasStateManagerSettings Settings { get; set; } Action InvalidateCallback { set; } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs index 8bcd007b77..c41cbe16c3 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.Editing.cs @@ -19,7 +19,7 @@ namespace BizHawk.Client.Common base.RecordFrame(frame, source); LagLog.RemoveFrom(frame); - LagLog[frame] = Global.Emulator.AsInputPollable().IsLagFrame; + LagLog[frame] = _inputPollable.IsLagFrame; if (this.IsRecording()) { diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 8d38c05680..9d9540371e 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -270,12 +270,6 @@ namespace BizHawk.Client.Common TasStateManager.Load(br); }); } - - // Movie should always have a state at frame 0. - if (!StartsFromSavestate && Global.Emulator.Frame == 0) - { - TasStateManager.Capture(); - } } } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 2c2383beda..873e626591 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -11,15 +11,11 @@ namespace BizHawk.Client.Common internal sealed partial class TasMovie : Bk2Movie, ITasMovie { public new const string Extension = "tasproj"; + private IInputPollable _inputPollable; /// loaded core does not implement internal TasMovie(string path, bool startsFromSavestate) : base(path) { - if (!Global.Emulator.HasSavestates()) - { - throw new InvalidOperationException($"Cannot create a {nameof(TasMovie)} against a core that does not implement {nameof(IStatable)}"); - } - Branches = new TasBranchCollection(this); ChangeLog = new TasMovieChangeLog(this); TasStateManager = new TasStateManager(this, Global.Config.DefaultTasStateManagerSettings); @@ -29,6 +25,23 @@ namespace BizHawk.Client.Common Markers.Add(0, startsFromSavestate ? "Savestate" : "Power on"); } + public override void Attach(IEmulator emulator) + { + if (!emulator.HasSavestates()) + { + throw new InvalidOperationException($"A core must be able to provide an {nameof(IStatable)} service"); + } + + if (!emulator.CanPollInput()) + { + throw new InvalidOperationException($"A core must be able to provide an {nameof(IInputPollable)} service"); + } + + _inputPollable = emulator.AsInputPollable(); + TasStateManager.Attach(emulator); + base.Attach(emulator); + } + public IStringLog VerificationLog { get; } = StringLogUtil.MakeStringLog(); // For movies that do not begin with power-on, this is the input required to get into the initial state public ITasBranchCollection Branches { get; } public ITasSession Session { get; private set; } = new TasSession(); @@ -49,9 +62,9 @@ namespace BizHawk.Client.Common { var lagIndex = index + 1; var lagged = LagLog[lagIndex]; - if (lagged == null && Global.Emulator.Frame == lagIndex) + if (lagged == null && Emulator.Frame == lagIndex) { - lagged = Global.Emulator.AsInputPollable().IsLagFrame; + lagged = _inputPollable.IsLagFrame; } return new TasMovieRecord @@ -104,7 +117,7 @@ namespace BizHawk.Client.Common _displayCache = (frame, GetInputState(frame)); } - return CreateDisplayValueForButton(_displayCache.Controller, Global.Emulator.SystemId, buttonName); + return CreateDisplayValueForButton(_displayCache.Controller, Emulator.SystemId, buttonName); } private static string CreateDisplayValueForButton(IController adapter, string systemId, string buttonName) @@ -128,17 +141,17 @@ namespace BizHawk.Client.Common { // 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 (Global.Emulator.Frame > LastEditedFrame) + if (Emulator.Frame > LastEditedFrame) { // emulated a new frame, current editing segment may change now. taseditor logic LastPositionStable = false; } - LagLog[Global.Emulator.Frame] = Global.Emulator.AsInputPollable().IsLagFrame; + LagLog[Emulator.Frame] = _inputPollable.IsLagFrame; - if (!TasStateManager.HasState(Global.Emulator.Frame)) + if (!TasStateManager.HasState(Emulator.Frame)) { - TasStateManager.Capture(Global.Emulator.Frame == LastEditedFrame - 1); + TasStateManager.Capture(Emulator.Frame == LastEditedFrame - 1); } } diff --git a/src/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/src/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 8b29445dc8..be8c67c975 100644 --- a/src/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/src/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -17,15 +17,14 @@ namespace BizHawk.Client.Common private const int MinFrequency = 1; private const int MaxFrequency = 16; - // TODO: pass this in, and find a solution to a stale reference (this is instantiated BEFORE a new core instance is made, making this one stale if it is simply set in the constructor - private IStatable Core => Global.Emulator.AsStatable(); - private IEmulator Emulator => Global.Emulator; + private IStatable _core; + private IEmulator _emulator; - private readonly StateManagerDecay _decay; + private StateManagerDecay _decay; private readonly ITasMovie _movie; - private readonly SortedList _states; - private readonly double _expectedStateSizeInMb; + private SortedList _states = new SortedList(); + private double _expectedStateSizeInMb; private ulong _used; private int _stateFrequency; @@ -43,10 +42,26 @@ namespace BizHawk.Client.Common { SetState(0, _movie.BinarySavestate); } + } + + public void Attach(IEmulator emulator) + { + if (!_emulator.IsNull()) + { + throw new InvalidOperationException("A core has already been attached!"); + } + + if (!emulator.HasSavestates()) + { + throw new InvalidOperationException($"A core must be able to provide an {nameof(IStatable)} service"); + } + + _emulator = emulator; + _core = emulator.AsStatable(); _decay = new StateManagerDecay(_movie, this); - _expectedStateSizeInMb = Core.CloneSavestate().Length / (double)(1024 * 1024); + _expectedStateSizeInMb = _core.CloneSavestate().Length / (double)(1024 * 1024); if (_expectedStateSizeInMb.HawkFloatEquality(0)) { throw new InvalidOperationException("Savestate size can not be zero!"); @@ -112,7 +127,7 @@ namespace BizHawk.Client.Common public void Capture(bool force = false) { bool shouldCapture; - int frame = Emulator.Frame; + int frame = _emulator.Frame; if (_movie.StartsFromSavestate && frame == 0) // Never capture frame 0 on savestate anchored movies since we have it anyway { @@ -137,7 +152,7 @@ namespace BizHawk.Client.Common if (shouldCapture) { - SetState(frame, (byte[])Core.SaveStateBinary().Clone(), skipRemoval: false); + SetState(frame, (byte[])_core.SaveStateBinary().Clone(), skipRemoval: false); } } @@ -301,7 +316,7 @@ namespace BizHawk.Client.Common { if (Count + 1 > MaxStates) { - _decay.Trigger(Emulator.Frame, Count + 1 - MaxStates); + _decay.Trigger(_emulator.Frame, Count + 1 - MaxStates); } }