Merge pull request #1592 from TASVideos/tsm-interface

Tsm interface
This commit is contained in:
feos 2019-06-23 18:04:56 +03:00 committed by GitHub
commit 2edf5c08f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 271 additions and 698 deletions

View File

@ -186,8 +186,8 @@
</Compile>
<Compile Include="movie\bk2\StringLogs.cs" />
<Compile Include="movie\import\PXMImport.cs" />
<Compile Include="movie\tasproj\IStateManager.cs" />
<Compile Include="movie\tasproj\StateManagerDecay.cs" />
<Compile Include="movie\tasproj\StateManagerState.cs" />
<Compile Include="movie\tasproj\TasBranch.cs" />
<Compile Include="movie\tasproj\TasMovie.History.cs" />
<Compile Include="movie\bk2\Bk2Movie.InputLog.cs">

View File

@ -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();

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace BizHawk.Client.Common
{
public interface IStateManager
{
/// <summary>
/// Retrieves the savestate for the given frame,
/// If this frame does not have a state currently, will return an empty array
/// </summary>
/// <returns>A savestate for the given frame or an empty array if there isn't one</returns>
byte[] this[int frame] { get; }
TasStateManagerSettings Settings { get; set; }
Action<int> InvalidateCallback { set; }
/// <summary>
/// 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
/// </summary>
void Capture(bool force = false);
bool HasState(int frame);
/// <summary>
/// Clears out all savestates after the given frame number
/// </summary>
bool Invalidate(int frame);
void Clear();
void Save(BinaryWriter bw);
void Load(BinaryReader br);
KeyValuePair<int, byte[]> GetStateClosestToFrame(int frame);
bool Any();
int Count { get; }
int Last { get; }
void UpdateStateFrequency();
/// <summary>
/// Returns index of the state right above the given frame
/// </summary>
int GetStateIndexByFrame(int frame);
/// <summary>
/// Returns frame of the state at the given index
/// </summary>
int GetStateFrameByIndex(int index);
/// <summary>
/// Directly remove a state from the given frame, if it exists
/// Should only be called by pruning operations
/// </summary>
bool Remove(int frame);
}
}

View File

@ -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<int> _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<int>();
_zeros.Add(_bits);
_zeros = new List<int> { _bits };
for (int i = 1; i < (1 << _bits); i++)
{

View File

@ -1,109 +0,0 @@
using System;
using System.IO;
namespace BizHawk.Client.Common
{
/// <summary>
/// Represents a savestate in the <seealso cref="TasStateManager"/>
/// </summary>
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());
}
}
}

View File

@ -272,7 +272,7 @@ namespace BizHawk.Client.Common
private void ClearTasprojExtras()
{
ClearLagLog();
_stateManager.ClearStateHistory();
_stateManager.Clear();
Markers.Clear();
ChangeLog.ClearLog();
}

View File

@ -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<int, IController> _inputStateCache = new Dictionary<int, IController>();
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<string> 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)

View File

@ -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
/// </summary>
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<int> InvalidateCallback { get; set; }
private void CallInvalidateCallback(int index)
{
InvalidateCallback?.Invoke(index);
}
internal NDBDatabase NdbDatabase { get; set; }
private Guid _guid = Guid.NewGuid();
private SortedList<int, StateManagerState> _states = new SortedList<int, StateManagerState>();
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<int, byte[]> _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);
}
/// <summary>
/// Mounts this instance for write access. Prior to that it's read-only
/// </summary>
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<int, byte[]>(MaxStates);
UpdateStateFrequency();
if (_expectedStateSize > 0)
{
limit = _maxStates;
}
_states = new SortedList<int, StateManagerState>(limit);
if (_expectedStateSize > int.MaxValue)
{
throw new InvalidOperationException();
}
NdbDatabase = new NDBDatabase(StatePath, Settings.DiskCapacitymb * 1024 * 1024, (int)_expectedStateSize);
}
public Action<int> InvalidateCallback { get; set; }
public TasStateManagerSettings Settings { get; set; }
/// <summary>
/// Retrieves the savestate for the given frame,
/// If this frame does not have a state currently, will return an empty array
/// </summary>
/// <returns>A savestate for the given frame or an empty array if there isn't one</returns>
public KeyValuePair<int, byte[]> this[int frame]
public byte[] this[int frame]
{
get
{
if (frame == 0)
{
return new KeyValuePair<int, byte[]>(0, InitialState);
return InitialState;
}
if (_states.ContainsKey(frame))
{
return new KeyValuePair<int, byte[]>(frame, _states[frame].State);
return _states[frame];
}
return new KeyValuePair<int, byte[]>(-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];
}
}
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// Clears out all savestates after the given frame number
/// </summary>
public bool Invalidate(int frame)
{
bool anyInvalidated = false;
@ -238,31 +190,21 @@ namespace BizHawk.Client.Common
frame = 1;
}
List<KeyValuePair<int, StateManagerState>> statesToRemove = _states.Where(s => s.Key >= frame).ToList();
List<KeyValuePair<int, byte[]>> 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;
}
/// <summary>
/// Deletes states to follow the state storage size limits.
/// Used after changing the settings too.
/// </summary>
public void LimitStateCount()
{
if (StateCount + 1 > _maxStates || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024)
{
_decay.Trigger(StateCount + 1 - _maxStates);
}
}
private List<int> ExcludeStates()
{
List<int> ret = new List<int>();
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<int, byte[]> GetStateClosestToFrame(int frame)
{
var s = _states.LastOrDefault(state => state.Key < frame);
if (s.Key > 0)
{
return s;
}
return this[s.Key];
return new KeyValuePair<int, byte[]>(0, InitialState);
}
/// <summary>
/// Returns index of the state right above the given frame
/// </summary>
public int GetStateIndexByFrame(int frame)
{
return _states.IndexOfKey(GetStateClosestToFrame(frame).Key);
}
/// <summary>
/// Returns frame of the state at the given index
/// </summary>
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<int> ExcludeStates()
{
List<int> ret = new List<int>();
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;
}
}
}

View File

@ -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)

View File

@ -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();
}

View File

@ -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);

View File

@ -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<int, byte[]> 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;
}

View File

@ -84,7 +84,6 @@
<Compile Include="Log.cs" />
<Compile Include="MruStack.cs" />
<Compile Include="MutableIntRange.cs" />
<Compile Include="NDBDatabase.cs" />
<Compile Include="OSTailoredCode.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="QuickCollections.cs" />

View File

@ -1,200 +0,0 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
namespace BizHawk.Common
{
/// <summary>
/// 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?
/// </summary>
public class NDBDatabase : IDisposable
{
readonly int BlockSize;
readonly long BlockCount;
Dictionary<string, Item> Items = new Dictionary<string, Item>();
LinkedList<Block> FreeList = new LinkedList<Block>();
long FreeWatermark;
FileStream Stream;
class Block
{
public long Number;
}
class Item
{
public LinkedList<Block> Blocks = new LinkedList<Block>();
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;
}
/// <summary>
/// Creates a new instance around a DeleteOnClose file of the provided path
/// </summary>
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);
}
/// <summary>
/// Clears the state of the datastructure to its original condition
/// </summary>
public void Clear()
{
Consumed = 0;
Items.Clear();
FreeList.Clear();
FreeWatermark = 0;
}
public void Dispose()
{
Stream.Dispose();
}
/// <summary>
/// Total reserved storage capacity. You may nto be able to fit that much data in here though (due to blockiness)
/// </summary>
public readonly long Capacity;
/// <summary>
/// The amount of bytes of storage consumed. Not necessarily equal to the total amount of data stored (due to blockiness)
/// </summary>
public long Consumed { get; private set; }
/// <summary>
/// The amount of bytes of storage available. Store operations &lt;= Remain will always succeed
/// </summary>
public long Remain { get { return Capacity - Consumed; } }
/// <summary>
/// Stores an item with the given key
/// </summary>
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;
}
}
/// <summary>
/// Fetches an item with the given key
/// </summary>
public byte[] FetchAll(string name)
{
var buf = new byte[GetSize(name)];
Fetch(name, buf, 0);
return buf;
}
/// <summary>
/// Fetches an item with the given key
/// </summary>
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);
}
/// <summary>
/// Releases the item with the given key.
/// Removing a non-existent item is benign, I guess
/// </summary>
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;
}
/// <summary>
/// Gets the size of the item with the given key
/// </summary>
public long GetSize(string name)
{
return Items[name].Size;
}
}
}

View File

@ -173,5 +173,10 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=addr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=addr/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autosave/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=greenzone/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=savestate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=savestates/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=tasproj/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>