commit
2edf5c08f8
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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++)
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -272,7 +272,7 @@ namespace BizHawk.Client.Common
|
|||
private void ClearTasprojExtras()
|
||||
{
|
||||
ClearLagLog();
|
||||
_stateManager.ClearStateHistory();
|
||||
_stateManager.Clear();
|
||||
Markers.Clear();
|
||||
ChangeLog.ClearLog();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 <= 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
Loading…
Reference in New Issue