diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj index ec805f3bfe..c866a9a85c 100644 --- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -186,8 +186,8 @@ + - diff --git a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs index ac88cba786..a1450bf56a 100644 --- a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs +++ b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs @@ -37,7 +37,6 @@ namespace BizHawk.Client.Common.MovieConversionExtensions } var tas = new TasMovie(newFilename, old.StartsFromSavestate); - tas.TasStateManager.MountWriteAccess(); for (var i = 0; i < old.InputLogLength; i++) { @@ -167,8 +166,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions // States can't be easily moved over, because they contain the frame number. // TODO? I'm not sure how this would be done. - tas.TasStateManager.MountWriteAccess(); - old.TasStateManager.ClearStateHistory(); + old.TasStateManager.Clear(); // Lag Log tas.TasLagLog.FromLagLog(old.TasLagLog); @@ -242,7 +240,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions } var tas = new TasMovie(newFilename, true) { SaveRam = saveRam }; - tas.TasStateManager.ClearStateHistory(); + tas.TasStateManager.Clear(); tas.ClearLagLog(); var entries = old.GetLogEntries(); diff --git a/BizHawk.Client.Common/movie/tasproj/IStateManager.cs b/BizHawk.Client.Common/movie/tasproj/IStateManager.cs new file mode 100644 index 0000000000..95a678ad6a --- /dev/null +++ b/BizHawk.Client.Common/movie/tasproj/IStateManager.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace BizHawk.Client.Common +{ + public interface IStateManager + { + /// + /// Retrieves the savestate for the given frame, + /// If this frame does not have a state currently, will return an empty array + /// + /// A savestate for the given frame or an empty array if there isn't one + byte[] this[int frame] { get; } + + TasStateManagerSettings Settings { get; set; } + + Action InvalidateCallback { set; } + + /// + /// Requests that the current emulator state be captured + /// Unless force is true, the state may or may not be captured depending on the logic employed by "green-zone" management + /// + void Capture(bool force = false); + + bool HasState(int frame); + + /// + /// Clears out all savestates after the given frame number + /// + bool Invalidate(int frame); + + void Clear(); + + void Save(BinaryWriter bw); + + void Load(BinaryReader br); + + KeyValuePair GetStateClosestToFrame(int frame); + + bool Any(); + + int Count { get; } + + int Last { get; } + + void UpdateStateFrequency(); + + /// + /// Returns index of the state right above the given frame + /// + int GetStateIndexByFrame(int frame); + + /// + /// Returns frame of the state at the given index + /// + int GetStateFrameByIndex(int index); + + /// + /// Directly remove a state from the given frame, if it exists + /// Should only be called by pruning operations + /// + bool Remove(int frame); + } +} diff --git a/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs b/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs index f7b3130881..28fa07e735 100644 --- a/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs +++ b/BizHawk.Client.Common/movie/tasproj/StateManagerDecay.cs @@ -8,7 +8,7 @@ First element is always assumed to be 16, which has all 4 bits set to 0. Each right zero means that we lower the priority of a state that goes at that index. Priority changes depending on current frame and amount of states. States with biggest priority get erased - first. With a 4-bit battern and no initial gap between states, total frame coverage is + first. With a 4-bit pattern and no initial gap between states, total frame coverage is about 5 times state count. Initial state gap can screw up our patterns, so do all the calculations like the gap @@ -18,7 +18,7 @@ don't drop it) or appears inside the state gap (in which case we forcibly drop it). This step doesn't involve numbers reduction. - _zeros values are essentialy the values of rshiftby here: + _zeros values are essentially the values of rshiftby here: bitwise view frame rshiftby priority 00010000 0 4 1 00000001 1 0 15 @@ -42,9 +42,12 @@ using System.Collections.Generic; namespace BizHawk.Client.Common { + // TODO: interface me internal class StateManagerDecay { - private TasStateManager _tsm; // access tsm methods to make life easier + private readonly TasMovie _movie; + private readonly IStateManager _tsm; + private List _zeros; // amount of least significant zeros in bitwise view (also max pattern step) private int _bits; // size of _zeros is 2 raised to the power of _bits private int _mask; // for remainder calculation using bitwise instead of division @@ -53,8 +56,9 @@ namespace BizHawk.Client.Common private int _step; // initial memory state gap private bool _align; // extra care about fine alignment. TODO: do we want it? - public StateManagerDecay(TasStateManager tsm) + public StateManagerDecay(TasMovie movie, IStateManager tsm) { + _movie = movie; _tsm = tsm; _align = false; } @@ -62,7 +66,7 @@ namespace BizHawk.Client.Common // todo: go through all states once, remove as many as we need. refactor to not need goto public void Trigger(int decayStates) { - for (; decayStates > 0 && _tsm.StateCount > 1;) + for (; decayStates > 0 && _tsm.Count > 1;) { int baseStateIndex = _tsm.GetStateIndexByFrame(Global.Emulator.Frame); int baseStateFrame = _tsm.GetStateFrameByIndex(baseStateIndex) / _step; // reduce right away @@ -75,18 +79,20 @@ namespace BizHawk.Client.Common { int currentFrame = _tsm.GetStateFrameByIndex(currentStateIndex); - if (_tsm.StateIsMarker(currentFrame)) + if (_movie.Markers.IsMarker(currentFrame + 1)) { continue; } - else if (currentFrame + 1 == _tsm.LastEditedFrame) + + if (currentFrame + 1 == _movie.LastEditedFrame) { continue; } - else if (currentFrame % _step > 0) + + if (currentFrame % _step > 0) { // ignore the pattern if the state doesn't belong already, drop it blindly and skip everything - if (_tsm.RemoveState(currentFrame)) + if (_tsm.Remove(currentFrame)) { // decrementing this if no state was removed is BAD decayStates--; @@ -102,11 +108,11 @@ namespace BizHawk.Client.Common } int zeroCount = _zeros[currentFrame & _mask]; - int priority = ((baseStateFrame - currentFrame) >> zeroCount); + int priority = (baseStateFrame - currentFrame) >> zeroCount; if (_align) { - priority -= ((_base * ((1 << zeroCount) * 2 - 1)) >> zeroCount); + priority -= (_base * ((1 << zeroCount) * 2 - 1)) >> zeroCount; } if (priority > forwardPriority) @@ -116,18 +122,19 @@ namespace BizHawk.Client.Common } } - for (int currentStateIndex = _tsm.StateCount - 1; currentStateIndex > baseStateIndex; currentStateIndex--) + for (int currentStateIndex = _tsm.Count - 1; currentStateIndex > baseStateIndex; currentStateIndex--) { int currentFrame = _tsm.GetStateFrameByIndex(currentStateIndex); - if (_tsm.StateIsMarker(currentFrame)) + if (_movie.Markers.IsMarker(currentFrame + 1)) { continue; } - else if ((currentFrame % _step > 0) && (currentFrame + 1 != _tsm.LastEditedFrame)) + + if ((currentFrame % _step > 0) && (currentFrame + 1 != _movie.LastEditedFrame)) { // ignore the pattern if the state doesn't belong already, drop it blindly and skip everything - if (_tsm.RemoveState(currentFrame)) + if (_tsm.Remove(currentFrame)) { // decrementing this if no state was removed is BAD decayStates--; @@ -143,7 +150,7 @@ namespace BizHawk.Client.Common } int zeroCount = _zeros[currentFrame & _mask]; - int priority = ((currentFrame - baseStateFrame) >> zeroCount); + int priority = (currentFrame - baseStateFrame) >> zeroCount; if (_align) { @@ -163,7 +170,7 @@ namespace BizHawk.Client.Common { if (baseStateFrame - forwardFrame > backwardFrame - baseStateFrame) { - if (_tsm.RemoveState(forwardFrame * _step)) + if (_tsm.Remove(forwardFrame * _step)) { // decrementing this if no state was removed is BAD decayStates--; @@ -171,7 +178,7 @@ namespace BizHawk.Client.Common } else { - if (_tsm.RemoveState(backwardFrame * _step)) + if (_tsm.Remove(backwardFrame * _step)) { // decrementing this if no state was removed is BAD decayStates--; @@ -180,7 +187,7 @@ namespace BizHawk.Client.Common } else if (forwardFrame > -1) { - if (_tsm.RemoveState(forwardFrame * _step)) + if (_tsm.Remove(forwardFrame * _step)) { // decrementing this if no state was removed is BAD decayStates--; @@ -188,7 +195,7 @@ namespace BizHawk.Client.Common } else if (backwardFrame > -1) { - if (_tsm.RemoveState(backwardFrame * _step)) + if (_tsm.Remove(backwardFrame * _step)) { // decrementing this if no state was removed is BAD decayStates--; @@ -199,7 +206,7 @@ namespace BizHawk.Client.Common // this shouldn't happen, but if we don't do it here, nothing good will happen either if (decayStatesLast == decayStates) { - if (_tsm.RemoveState(_tsm.GetStateFrameByIndex(1))) + if (_tsm.Remove(_tsm.GetStateFrameByIndex(1))) { // decrementing this if no state was removed is BAD decayStates--; @@ -207,7 +214,7 @@ namespace BizHawk.Client.Common } // this is the kind of highly complex loops that might justify goto - next_state:; + next_state: ; } } @@ -218,8 +225,7 @@ namespace BizHawk.Client.Common _bits = bits; _mask = (1 << _bits) - 1; _base = (_capacity + _bits / 2) / (_bits + 1); - _zeros = new List(); - _zeros.Add(_bits); + _zeros = new List { _bits }; for (int i = 1; i < (1 << _bits); i++) { diff --git a/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs b/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs deleted file mode 100644 index ae5255c80c..0000000000 --- a/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.IO; - -namespace BizHawk.Client.Common -{ - /// - /// Represents a savestate in the - /// - internal class StateManagerState : IDisposable - { - private static long _stateId; - private readonly TasStateManager _manager; - private readonly long _id; - - private byte[] _state; - - public int Frame { get; } - - public static StateManagerState Read(BinaryReader r, TasStateManager m) - { - int frame = r.ReadInt32(); - byte[] data = r.ReadBytes(r.ReadInt32()); - return new StateManagerState(m, data, frame); - } - - public void Write(BinaryWriter w) - { - w.Write(Frame); - w.Write(_state.Length); - w.Write(_state); - } - - public byte[] State - { - get - { - if (_state != null) - { - return _state; - } - - return _manager.NdbDatabase.FetchAll(_id.ToString()); - } - - set - { - if (_state != null) - { - _state = value; - } - else - { - throw new Exception("Attempted to set a state to null."); - } - } - } - - public int Length => State.Length; - - public bool IsOnDisk => _state == null; - - public StateManagerState(TasStateManager manager, byte[] state, int frame) - { - _manager = manager; - _state = state; - Frame = frame; - - if (_stateId > long.MaxValue - 100) - { - throw new InvalidOperationException(); - } - - _id = System.Threading.Interlocked.Increment(ref _stateId); - } - - public void MoveToDisk() - { - if (IsOnDisk) - { - return; - } - - _manager.NdbDatabase.Store(_id.ToString(), _state, 0, _state.Length); - _state = null; - } - - public void MoveToRAM() - { - if (!IsOnDisk) - { - return; - } - - string key = _id.ToString(); - _state = _manager.NdbDatabase.FetchAll(key); - _manager.NdbDatabase.Release(key); - } - - public void Dispose() - { - if (!IsOnDisk) - { - return; - } - - _manager.NdbDatabase.Release(_id.ToString()); - } - } -} diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 221d235b89..cd99ea0766 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -272,7 +272,7 @@ namespace BizHawk.Client.Common private void ClearTasprojExtras() { ClearLagLog(); - _stateManager.ClearStateHistory(); + _stateManager.Clear(); Markers.Clear(); ChangeLog.ClearLog(); } diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 3814fcdb11..ee482e958f 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -13,7 +13,7 @@ namespace BizHawk.Client.Common public sealed partial class TasMovie : Bk2Movie, INotifyPropertyChanged { private readonly Bk2MnemonicConstants _mnemonics = new Bk2MnemonicConstants(); - private readonly TasStateManager _stateManager; + private readonly IStateManager _stateManager; private readonly TasLagLog _lagLog = new TasLagLog(); private readonly Dictionary _inputStateCache = new Dictionary(); private BackgroundWorker _progressReportWorker; @@ -35,13 +35,12 @@ namespace BizHawk.Client.Common public TasLagLog TasLagLog => _lagLog; public IStringLog InputLog => Log; public int BranchCount => Branches.Count; - public int LastStatedFrame => _stateManager.LastStatedFrame; + public int LastStatedFrame => _stateManager.Last; public override string PreferredExtension => Extension; - public TasStateManager TasStateManager => _stateManager; + public IStateManager TasStateManager => _stateManager; public TasMovieRecord this[int index] => new TasMovieRecord { - // State = StateManager[index], HasState = _stateManager.HasState(index), LogEntry = GetInputLogEntry(index), Lagged = _lagLog[index + 1], @@ -218,7 +217,7 @@ namespace BizHawk.Client.Common { if (_stateManager.Any()) { - _stateManager.ClearStateHistory(); + _stateManager.Clear(); Changes = true; } } @@ -246,14 +245,6 @@ namespace BizHawk.Client.Common _lagLog.Clear(); } - public void DeleteLogBefore(int frame) - { - if (frame < Log.Count) - { - Log.RemoveRange(0, frame); - } - } - public void CopyLog(IEnumerable log) { Log.Clear(); @@ -276,7 +267,7 @@ namespace BizHawk.Client.Common return Log; } - private int? _timelineBranchFrame = null; + private int? _timelineBranchFrame; // TODO: this is 99% copy pasting of bad code public override bool ExtractInputLog(TextReader reader, out string errorMessage) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 38b961c6c1..08891bb478 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Drawing; -using BizHawk.Common; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Common.IEmulatorExtensions; @@ -15,42 +13,25 @@ namespace BizHawk.Client.Common /// Captures savestates and manages the logic of adding, retrieving, /// invalidating/clearing of states. Also does memory management and limiting of states /// - public class TasStateManager : IDisposable + public class TasStateManager : IStateManager { + 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(); - - public Action InvalidateCallback { get; set; } - - private void CallInvalidateCallback(int index) - { - InvalidateCallback?.Invoke(index); - } - - internal NDBDatabase NdbDatabase { get; set; } - private Guid _guid = Guid.NewGuid(); - private SortedList _states = new SortedList(); - - private string StatePath - { - get - { - var basePath = PathManager.MakeAbsolutePath(Global.Config.PathEntries["Global", "TAStudio states"].Path, null); - return Path.Combine(basePath, _guid.ToString()); - } - } - - private bool _isMountedForWrite; + private readonly StateManagerDecay _decay; private readonly TasMovie _movie; - private StateManagerDecay _decay; - private ulong _expectedStateSize; + private readonly SortedList _states; + private readonly ulong _expectedStateSize; + + private ulong _used; private int _stateFrequency; - private readonly int _minFrequency = 1; - private readonly int _maxFrequency = 16; - private int _maxStates => (int)(Settings.Cap / _expectedStateSize) + + + private int MaxStates => (int)(Settings.Cap / _expectedStateSize) + (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize); - private int _fileStateGap => 1 << Settings.FileStateGap; + private int FileStateGap => 1 << Settings.FileStateGap; public TasStateManager(TasMovie movie) { @@ -62,80 +43,48 @@ namespace BizHawk.Client.Common SetState(0, _movie.BinarySavestate); } - _decay = new StateManagerDecay(this); - } + _decay = new StateManagerDecay(_movie, this); - public void Dispose() - { - // States and BranchStates don't need cleaning because they would only contain an ndbdatabase entry which was demolished by the below - NdbDatabase?.Dispose(); - } - - public void UpdateStateFrequency() - { - _stateFrequency = NumberExtensions.Clamp( - ((int)_expectedStateSize / Settings.MemStateGapDivider / 1024), - _minFrequency, _maxFrequency); - - _decay.UpdateSettings(_maxStates, _stateFrequency, 4); - } - - /// - /// Mounts this instance for write access. Prior to that it's read-only - /// - public void MountWriteAccess() - { - if (_isMountedForWrite) - { - return; - } - - int limit = 0; - _isMountedForWrite = true; _expectedStateSize = (ulong)Core.SaveStateBinary().Length; + if (_expectedStateSize == 0) + { + throw new InvalidOperationException("Savestate size can not be zero!"); + } + + _states = new SortedList(MaxStates); + UpdateStateFrequency(); - - if (_expectedStateSize > 0) - { - limit = _maxStates; - } - - _states = new SortedList(limit); - - if (_expectedStateSize > int.MaxValue) - { - throw new InvalidOperationException(); - } - - NdbDatabase = new NDBDatabase(StatePath, Settings.DiskCapacitymb * 1024 * 1024, (int)_expectedStateSize); } - + + public Action InvalidateCallback { get; set; } + public TasStateManagerSettings Settings { get; set; } - /// - /// Retrieves the savestate for the given frame, - /// If this frame does not have a state currently, will return an empty array - /// - /// A savestate for the given frame or an empty array if there isn't one - public KeyValuePair this[int frame] + public byte[] this[int frame] { get { if (frame == 0) { - return new KeyValuePair(0, InitialState); + return InitialState; } if (_states.ContainsKey(frame)) { - return new KeyValuePair(frame, _states[frame].State); + return _states[frame]; } - return new KeyValuePair(-1, new byte[0]); + return new byte[0]; } } - public byte[] InitialState + public int Count => _states.Count; + + public int Last => _states.Count > 0 + ? _states.Last().Key + : 0; + + private byte[] InitialState { get { @@ -144,14 +93,29 @@ namespace BizHawk.Client.Common return _movie.BinarySavestate; } - return _states[0].State; + return _states[0]; } } - /// - /// Requests that the current emulator state be captured - /// Unless force is true, the state may or may not be captured depending on the logic employed by "greenzone" management - /// + public bool Any() + { + if (_movie.StartsFromSavestate) + { + return _states.Count > 0; + } + + return _states.Count > 1; + } + + public void UpdateStateFrequency() + { + _stateFrequency = ((int)_expectedStateSize / Settings.MemStateGapDivider / 1024) + .Clamp(MinFrequency, MaxFrequency); + + _decay.UpdateSettings(MaxStates, _stateFrequency, 4); + LimitStateCount(); + } + public void Capture(bool force = false) { bool shouldCapture; @@ -163,15 +127,15 @@ namespace BizHawk.Client.Common } else if (force) { - shouldCapture = force; + shouldCapture = true; } - else if (frame == 0) // For now, long term, TasMovie should have a .StartState property, and a tasproj file for the start state in non-savestate anchored movies + else if (frame == 0) // For now, long term, TasMovie should have a .StartState property, and a .tasproj file for the start state in non-savestate anchored movies { shouldCapture = true; } - else if (StateIsMarker(frame)) + else if (IsMarkerState(frame)) { - shouldCapture = true; // Markers shoudl always get priority + shouldCapture = true; // Markers should always get priority } else { @@ -184,33 +148,24 @@ namespace BizHawk.Client.Common } } - private void MoveStateToDisk(int index) + public void Clear() { - Used -= (ulong)_states[index].Length; - _states[index].MoveToDisk(); - } - - private void MoveStateToMemory(int index) - { - _states[index].MoveToRAM(); - Used += (ulong)_states[index].Length; - } - - internal void SetState(int frame, byte[] state, bool skipRemoval = true) - { - if (!skipRemoval) // skipRemoval: false only when capturing new states + if (_states.Any()) { - LimitStateCount(); // Remove before adding so this state won't be removed. - } + // For power-on movies, we can't lose frame 0; + byte[] power = null; + if (!_movie.StartsFromSavestate) + { + power = _states[0]; + } - if (_states.ContainsKey(frame)) - { - _states[frame].State = state; - } - else - { - Used += (ulong)state.Length; - _states.Add(frame, new StateManagerState(this, state, frame)); + _states.Clear(); + + if (power != null) + { + SetState(0, power); + _used = (ulong)power.Length; + } } } @@ -224,9 +179,6 @@ namespace BizHawk.Client.Common return _states.ContainsKey(frame); } - /// - /// Clears out all savestates after the given frame number - /// public bool Invalidate(int frame) { bool anyInvalidated = false; @@ -238,31 +190,21 @@ namespace BizHawk.Client.Common frame = 1; } - List> statesToRemove = _states.Where(s => s.Key >= frame).ToList(); + List> statesToRemove = _states.Where(s => s.Key >= frame).ToList(); anyInvalidated = statesToRemove.Any(); foreach (var state in statesToRemove) { - RemoveState(state.Key); + Remove(state.Key); } - CallInvalidateCallback(frame); + InvalidateCallback?.Invoke(frame); } return anyInvalidated; } - public bool StateIsMarker(int frame) - { - if (frame == -1) - { - return false; - } - - return _movie.Markers.IsMarker(frame + 1); - } - - public bool RemoveState(int frame) + public bool Remove(int frame) { int index = _states.IndexOfKey(frame); @@ -271,137 +213,15 @@ namespace BizHawk.Client.Common return false; } - StateManagerState state = _states.Values[index]; + var state = _states[frame]; - if (state.IsOnDisk) - { - state.Dispose(); - } - else - { - Used -= (ulong)state.Length; - } + _used -= (ulong)state.Length; _states.RemoveAt(index); return true; } - /// - /// Deletes states to follow the state storage size limits. - /// Used after changing the settings too. - /// - public void LimitStateCount() - { - if (StateCount + 1 > _maxStates || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024) - { - _decay.Trigger(StateCount + 1 - _maxStates); - } - } - - private List ExcludeStates() - { - List ret = new List(); - ulong saveUsed = Used + DiskUsed; - - // respect state gap no matter how small the resulting size will be - // still leave marker states - for (int i = 1; i < _states.Count; i++) - { - int frame = GetStateFrameByIndex(i); - - if (StateIsMarker(frame) || frame % _fileStateGap < _stateFrequency) - { - continue; - } - - ret.Add(i); - - if (_states.Values[i].IsOnDisk) - { - saveUsed -= _expectedStateSize; - } - else - { - saveUsed -= (ulong)_states.Values[i].Length; - } - } - - // if the size is still too big, exclude states form the beginning - // still leave marker states - int index = 0; - while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024) - { - do - { - if (++index >= _states.Count) - { - break; - } - } - while (StateIsMarker(GetStateFrameByIndex(index))); - - if (index >= _states.Count) - { - break; - } - - ret.Add(index); - - if (_states.Values[index].IsOnDisk) - { - saveUsed -= _expectedStateSize; - } - else - { - saveUsed -= (ulong)_states.Values[index].Length; - } - } - - // if there are enough markers to still be over the limit, remove marker frames - index = 0; - while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024) - { - if (!ret.Contains(++index)) - { - ret.Add(index); - } - - if (_states.Values[index].IsOnDisk) - { - saveUsed -= _expectedStateSize; - } - else - { - saveUsed -= (ulong)_states.Values[index].Length; - } - } - - return ret; - } - - public void ClearStateHistory() - { - if (_states.Any()) - { - var temp_state = _states.Values; - StateManagerState power = null; - if (temp_state[0].Frame==0) - { - power = _states.Values.First(s => s.Frame == 0); - } - else - { - power = _states.Values[0]; - } - - _states.Clear(); - SetState(0, power.State); - Used = (ulong)power.State.Length; - NdbDatabase?.Clear(); - } - } - // Map: // 4 bytes - total savestate count // [Foreach state] @@ -422,7 +242,7 @@ namespace BizHawk.Client.Common bw.Write(_states.Keys[i]); bw.Write(_states.Values[i].Length); - bw.Write(_states.Values[i].State); + bw.Write(_states.Values[i]); } } @@ -453,110 +273,114 @@ namespace BizHawk.Client.Common public KeyValuePair GetStateClosestToFrame(int frame) { var s = _states.LastOrDefault(state => state.Key < frame); + if (s.Key > 0) + { + return s; + } - return this[s.Key]; + return new KeyValuePair(0, InitialState); } - /// - /// Returns index of the state right above the given frame - /// public int GetStateIndexByFrame(int frame) { return _states.IndexOfKey(GetStateClosestToFrame(frame).Key); } - /// - /// Returns frame of the state at the given index - /// public int GetStateFrameByIndex(int index) { - // feos: this is called super often by decay - // this method is hundred times faster than _states.ElementAt(index).Key return _states.Keys[index]; } - private ulong _used; - private ulong Used + private bool IsMarkerState(int frame) { - get + return _movie.Markers.IsMarker(frame + 1); + } + + private void SetState(int frame, byte[] state, bool skipRemoval = true) + { + if (!skipRemoval) // skipRemoval: false only when capturing new states { - return _used; + LimitStateCount(); // Remove before adding so this state won't be removed. } - set + if (_states.ContainsKey(frame)) { - // TODO: Shouldn't we throw an exception? Debug.Fail only runs in debug mode? - if (value > 0xf000000000000000) - { - System.Diagnostics.Debug.Fail("ulong Used underfow!"); - } - else - { - _used = value; - } + _states[frame] = state; + } + else + { + _used += (ulong)state.Length; + _states.Add(frame, state); } } - private ulong DiskUsed + // Deletes states to follow the state storage size limits. + // Used after changing the settings too. + private void LimitStateCount() { - get + if (Count + 1 > MaxStates) { - if (NdbDatabase == null) + _decay.Trigger(Count + 1 - MaxStates); + } + } + + private List ExcludeStates() + { + List ret = new List(); + ulong saveUsed = _used; + + // respect state gap no matter how small the resulting size will be + // still leave marker states + for (int i = 1; i < _states.Count; i++) + { + int frame = GetStateFrameByIndex(i); + + if (IsMarkerState(frame) || frame % FileStateGap < _stateFrequency) { - return 0; + continue; } - return (ulong)NdbDatabase.Consumed; - } - } + ret.Add(i); - public int StateCount => _states.Count; - public int LastEditedFrame => _movie.LastEditedFrame; - - public bool Any() - { - if (_movie.StartsFromSavestate) - { - return _states.Count > 0; + saveUsed -= (ulong)_states.Values[i].Length; } - return _states.Count > 1; - } - - public int LastKey - { - get + // if the size is still too big, exclude states form the beginning + // still leave marker states + int index = 0; + while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024) { - if (_states.Count == 0) + do { - return 0; + if (++index >= _states.Count) + { + break; + } + } + while (IsMarkerState(GetStateFrameByIndex(index))); + + if (index >= _states.Count) + { + break; } - return _states.Last().Key; + ret.Add(index); + saveUsed -= (ulong)_states.Values[index].Length; } - } - public int LastStatedFrame - { - get + // if there are enough markers to still be over the limit, remove marker frames + index = 0; + while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024) { - if (StateCount > 0) + if (!ret.Contains(++index)) { - return LastKey; + ret.Add(index); } - return 0; - } - } - - private int FindState(StateManagerState s) - { - if (!_states.ContainsValue(s)) - { - return -1; + saveUsed -= (ulong)_states.Values[index].Length; } - return s.Frame; + return ret; } } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Movie.cs b/BizHawk.Client.EmuHawk/MainForm.Movie.cs index b9bc4db335..d7094c8893 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Movie.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Movie.cs @@ -5,10 +5,6 @@ using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Common.IEmulatorExtensions; -using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES; -using BizHawk.Emulation.Cores.Nintendo.NES; -using BizHawk.Emulation.Cores.Nintendo.SNES9X; -using BizHawk.Emulation.Cores.Nintendo.SNES; namespace BizHawk.Client.EmuHawk { @@ -30,9 +26,6 @@ namespace BizHawk.Client.EmuHawk try { - var tasmovie = (movie as TasMovie); - if (tasmovie != null) - tasmovie.TasStateManager.MountWriteAccess(); Global.MovieSession.QueueNewMovie(movie, record, Emulator); } catch (MoviePlatformMismatchException ex) diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs index 3695e8d996..3aa06f75b6 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.MenuItems.cs @@ -814,10 +814,10 @@ namespace BizHawk.Client.EmuHawk if (CurrentTasMovie.TasStateManager.HasState(Emulator.Frame)) { - byte[] state = (byte[])StatableEmulator.SaveStateBinary().Clone(); // Why is this cloning it? - byte[] greenzone = CurrentTasMovie.TasStateManager[Emulator.Frame].Value; + byte[] state = StatableEmulator.SaveStateBinary(); + byte[] greenZone = CurrentTasMovie.TasStateManager[Emulator.Frame]; - if (!state.SequenceEqual(greenzone)) + if (!state.SequenceEqual(greenZone)) { MessageBox.Show($"Bad data between frames {lastState} and {Emulator.Frame}"); return; @@ -1080,7 +1080,6 @@ namespace BizHawk.Client.EmuHawk Statable = this.StatableEmulator }.ShowDialog(); CurrentTasMovie.TasStateManager.UpdateStateFrequency(); - CurrentTasMovie.TasStateManager.LimitStateCount(); UpdateChangesIndicator(); } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index e6ec6e7615..4ea57477ed 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -57,10 +57,12 @@ namespace BizHawk.Client.EmuHawk { TastudioPlayMode(); - int lastState = CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame).Key; // Simply getting the last state doesn't work if that state is the frame. [dispaly isn't saved in the state, need to emulate to frame] - if (lastState > Emulator.Frame) + // Simply getting the last state doesn't work if that state is the frame. + // display isn't saved in the state, need to emulate to frame + var lastState = CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame); + if (lastState.Key > Emulator.Frame) { - LoadState(CurrentTasMovie.TasStateManager[lastState]); // STATE ACCESS + LoadState(lastState); } StartSeeking(frame); diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index c9a866b822..e7afee0d36 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -642,7 +642,6 @@ namespace BizHawk.Client.EmuHawk Global.MovieSession.Movie = new TasMovie(false, _seekBackgroundWorker); var stateManager = ((TasMovie)Global.MovieSession.Movie).TasStateManager; - stateManager.MountWriteAccess(); stateManager.InvalidateCallback = GreenzoneInvalidated; BookMarkControl.LoadedCallback = BranchLoaded; @@ -740,7 +739,7 @@ namespace BizHawk.Client.EmuHawk return; } - MovieZone loadZone = new MovieZone(path) + var loadZone = new MovieZone(path) { Start = TasView.FirstSelectedIndex.Value }; @@ -865,7 +864,6 @@ namespace BizHawk.Client.EmuHawk TasView.Refresh(); - //SetSplicer(); CurrentTasMovie.FlushInputCache(); CurrentTasMovie.UseInputCache = false; @@ -896,12 +894,14 @@ namespace BizHawk.Client.EmuHawk private void StartAtNearestFrameAndEmulate(int frame, bool fromLua, bool fromRewinding) { if (frame == Emulator.Frame) + { return; + } _unpauseAfterSeeking = (fromRewinding || WasRecording) && !Mainform.EmulatorPaused; TastudioPlayMode(); - KeyValuePair closestState = CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame); - if (closestState.Value != null && (frame < Emulator.Frame || closestState.Key > Emulator.Frame)) + var closestState = CurrentTasMovie.TasStateManager.GetStateClosestToFrame(frame); + if (closestState.Value.Length > 0 && (frame < Emulator.Frame || closestState.Key > Emulator.Frame)) { LoadState(closestState); } @@ -995,7 +995,7 @@ namespace BizHawk.Client.EmuHawk private void SetSplicer() { // TODO: columns selected? - var temp = $"Selected: {TasView.SelectedRows.Count()} {(TasView.SelectedRows.Count() == 1 ? "frame" : "frames")}, States: {CurrentTasMovie.TasStateManager.StateCount}"; + var temp = $"Selected: {TasView.SelectedRows.Count()} {(TasView.SelectedRows.Count() == 1 ? "frame" : "frames")}, States: {CurrentTasMovie.TasStateManager.Count}"; if (_tasClipboard.Any()) temp += $", Clipboard: {_tasClipboard.Count} {(_tasClipboard.Count == 1 ? "frame" : "frames")}"; SplicerStatusLabel.Text = temp; } diff --git a/BizHawk.Common/BizHawk.Common.csproj b/BizHawk.Common/BizHawk.Common.csproj index 57821a4430..2a93d8a56f 100644 --- a/BizHawk.Common/BizHawk.Common.csproj +++ b/BizHawk.Common/BizHawk.Common.csproj @@ -84,7 +84,6 @@ - diff --git a/BizHawk.Common/NDBDatabase.cs b/BizHawk.Common/NDBDatabase.cs deleted file mode 100644 index cefeffccc6..0000000000 --- a/BizHawk.Common/NDBDatabase.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; - -namespace BizHawk.Common -{ - /// - /// Non-consecutive Disk Block Database - /// Opens a file and stores blocks in it. - /// Blocks can be differently sized than the basic block size. Wastage will occur. - /// TODO: Mount on memory as well? - /// - public class NDBDatabase : IDisposable - { - readonly int BlockSize; - readonly long BlockCount; - - Dictionary Items = new Dictionary(); - LinkedList FreeList = new LinkedList(); - long FreeWatermark; - FileStream Stream; - - class Block - { - public long Number; - } - - class Item - { - public LinkedList Blocks = new LinkedList(); - public long Size; - } - - Block AllocBlock() - { - if (FreeList.Count != 0) - { - var blocknode = FreeList.First; - FreeList.RemoveFirst(); - Consumed += BlockSize; - return blocknode.Value; - } - - if (FreeWatermark == BlockCount) - throw new OutOfMemoryException($"{nameof(NDBDatabase)} out of reserved space"); - - var b = new Block() { Number = FreeWatermark }; - FreeWatermark++; - Consumed += BlockSize; - - return b; - } - - long GetOffsetForBlock(Block b) - { - return b.Number * BlockSize; - } - - /// - /// Creates a new instance around a DeleteOnClose file of the provided path - /// - public NDBDatabase(string path, long size, int blocksize) - { - Capacity = size; - Consumed = 0; - BlockSize = blocksize; - BlockCount = size / BlockSize; - Directory.CreateDirectory(Path.GetDirectoryName(path)); - Stream = new FileStream(path, FileMode.Create, System.Security.AccessControl.FileSystemRights.FullControl, FileShare.None, 4 * 1024, FileOptions.DeleteOnClose); - } - - /// - /// Clears the state of the datastructure to its original condition - /// - public void Clear() - { - Consumed = 0; - Items.Clear(); - FreeList.Clear(); - FreeWatermark = 0; - } - - public void Dispose() - { - Stream.Dispose(); - } - - /// - /// Total reserved storage capacity. You may nto be able to fit that much data in here though (due to blockiness) - /// - public readonly long Capacity; - - /// - /// The amount of bytes of storage consumed. Not necessarily equal to the total amount of data stored (due to blockiness) - /// - public long Consumed { get; private set; } - - /// - /// The amount of bytes of storage available. Store operations <= Remain will always succeed - /// - public long Remain { get { return Capacity - Consumed; } } - - /// - /// Stores an item with the given key - /// - public void Store(string name, byte[] buf, int offset, int length) - { - if (Items.ContainsKey(name)) - throw new InvalidOperationException($"Can't add already existing key of name {name}"); - - if (length > Remain) - throw new OutOfMemoryException($"Insufficient storage reserved for {length} bytes"); - - long todo = length; - int src = offset; - Item item = new Item { Size = length }; - Items[name] = item; - while (todo > 0) - { - var b = AllocBlock(); - item.Blocks.AddLast(b); - - long tocopy = todo; - if (tocopy > BlockSize) - tocopy = BlockSize; - - Stream.Position = GetOffsetForBlock(b); - Stream.Write(buf, src, (int)tocopy); - - todo -= tocopy; - src += (int)tocopy; - } - } - - /// - /// Fetches an item with the given key - /// - public byte[] FetchAll(string name) - { - var buf = new byte[GetSize(name)]; - Fetch(name, buf, 0); - return buf; - } - - /// - /// Fetches an item with the given key - /// - public void Fetch(string name, byte[] buf, int offset) - { - Item item; - if (!Items.TryGetValue(name, out item)) - throw new KeyNotFoundException(); - - long todo = item.Size; - var curr = item.Blocks.First; - while (todo > 0) - { - long tocopy = todo; - if (tocopy > BlockSize) - tocopy = BlockSize; - Stream.Position = GetOffsetForBlock(curr.Value); - Stream.Read(buf, offset, (int)tocopy); - - todo -= tocopy; - offset += (int)tocopy; - - curr = curr.Next; - } - - System.Diagnostics.Debug.Assert(curr == null); - } - - /// - /// Releases the item with the given key. - /// Removing a non-existent item is benign, I guess - /// - public void Release(string name) - { - Item item; - if (!Items.TryGetValue(name, out item)) - return; - Items.Remove(name); - var blocks = item.Blocks.ToArray(); - item.Blocks.Clear(); - foreach (var block in blocks) - FreeList.AddLast(block); - Consumed -= blocks.Length * BlockSize; - } - - /// - /// Gets the size of the item with the given key - /// - public long GetSize(string name) - { - return Items[name].Size; - } - } - -} \ No newline at end of file diff --git a/BizHawk.sln.DotSettings b/BizHawk.sln.DotSettings index a365a0b643..f62ceeecdc 100644 --- a/BizHawk.sln.DotSettings +++ b/BizHawk.sln.DotSettings @@ -173,5 +173,10 @@ True True True - True + True + True + True + True + True + True \ No newline at end of file