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