BizHawk/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs

1051 lines
23 KiB
C#
Raw Normal View History

2014-07-10 22:07:50 +00:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Drawing;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Common.IEmulatorExtensions;
namespace BizHawk.Client.Common
{
/// <summary>
/// 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
{
// 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);
}
2017-05-19 18:17:07 +00:00
private readonly List<StateManagerState> _lowPriorityStates = new List<StateManagerState>();
internal NDBDatabase NdbDatabase { get; set; }
private Guid _guid = Guid.NewGuid();
private SortedList<int, StateManagerState> _states = new SortedList<int, StateManagerState>();
2017-05-19 18:17:07 +00:00
private string StatePath
{
get
{
var basePath = PathManager.MakeAbsolutePath(Global.Config.PathEntries["Global", "TAStudio states"].Path, null);
2017-05-19 18:17:07 +00:00
return Path.Combine(basePath, _guid.ToString());
}
}
private bool _isMountedForWrite;
private readonly TasMovie _movie;
2017-05-19 18:17:07 +00:00
private ulong _expectedStateSize;
private readonly int _minFrequency = VersionInfo.DeveloperBuild ? 2 : 1;
2017-05-19 18:17:07 +00:00
private const int MaxFrequency = 16;
2015-09-16 00:03:50 +00:00
private int StateFrequency
{
get
{
int freq = (int)(_expectedStateSize / 65536);
if (freq < _minFrequency)
{
return _minFrequency;
}
2017-05-19 18:17:07 +00:00
if (freq > MaxFrequency)
{
2017-05-19 18:17:07 +00:00
return MaxFrequency;
}
return freq;
}
}
2017-05-19 18:17:07 +00:00
private int MaxStates => (int)(Settings.Cap / _expectedStateSize) + (int)((ulong)Settings.DiskCapacitymb * 1024 * 1024 / _expectedStateSize);
2017-05-19 18:17:07 +00:00
private int StateGap => 1 << Settings.StateGap;
public TasStateManager(TasMovie movie)
{
_movie = movie;
Settings = new TasStateManagerSettings(Global.Config.DefaultTasProjSettings);
2017-05-19 18:17:07 +00:00
_accessed = new List<StateManagerState>();
if (_movie.StartsFromSavestate)
2017-05-17 18:18:26 +00:00
{
SetState(0, _movie.BinarySavestate);
2017-05-17 18:18:26 +00:00
}
}
public void Dispose()
{
2017-05-09 18:19:55 +00:00
// States and BranchStates don't need cleaning because they would only contain an ndbdatabase entry which was demolished by the below
2017-05-19 18:17:07 +00:00
NdbDatabase?.Dispose();
}
/// <summary>
/// Mounts this instance for write access. Prior to that it's read-only
/// </summary>
public void MountWriteAccess()
{
if (_isMountedForWrite)
{
return;
}
_isMountedForWrite = true;
int limit = 0;
_expectedStateSize = (ulong)Core.SaveStateBinary().Length;
if (_expectedStateSize > 0)
{
2017-05-19 18:17:07 +00:00
limit = MaxStates;
}
2017-05-19 18:17:07 +00:00
_states = new SortedList<int, StateManagerState>(limit);
if (_expectedStateSize > int.MaxValue)
{
throw new InvalidOperationException();
}
2017-05-19 18:17:07 +00:00
NdbDatabase = new NDBDatabase(StatePath, Settings.DiskCapacitymb * 1024 * 1024, (int)_expectedStateSize);
}
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]
{
get
{
if (frame == 0)
{
return new KeyValuePair<int, byte[]>(0, InitialState);
}
2017-05-19 18:17:07 +00:00
if (_states.ContainsKey(frame))
{
StateAccessed(frame);
2017-05-19 18:17:07 +00:00
return new KeyValuePair<int, byte[]>(frame, _states[frame].State);
}
return new KeyValuePair<int, byte[]>(-1, new byte[0]);
}
}
2015-09-16 00:03:50 +00:00
2017-05-19 18:17:07 +00:00
private readonly List<StateManagerState> _accessed;
public byte[] InitialState
{
get
{
if (_movie.StartsFromSavestate)
{
return _movie.BinarySavestate;
}
2017-05-19 18:17:07 +00:00
return _states[0].State;
}
}
/// <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 void Capture(bool force = false)
{
2017-05-19 18:17:07 +00:00
bool shouldCapture;
int frame = Global.Emulator.Frame;
if (_movie.StartsFromSavestate && frame == 0) // Never capture frame 0 on savestate anchored movies since we have it anyway
{
shouldCapture = false;
}
else if (force)
{
shouldCapture = force;
}
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
2014-08-30 00:40:53 +00:00
{
shouldCapture = true;
}
else if (_movie.Markers.IsMarker(frame + 1))
2014-08-30 00:40:53 +00:00
{
shouldCapture = true; // Markers shoudl always get priority
}
else
{
shouldCapture = frame % StateFrequency == 0;
}
if (shouldCapture)
{
SetState(frame, (byte[])Core.SaveStateBinary().Clone(), skipRemoval: false);
}
}
private void MaybeRemoveStates()
{
// Loop, because removing a state that has a duplicate won't save any space
while (Used + _expectedStateSize > Settings.Cap || DiskUsed > (ulong)Settings.DiskCapacitymb * 1024 * 1024)
{
Point shouldRemove = StateToRemove();
RemoveState(shouldRemove.X, shouldRemove.Y);
}
if (Used > Settings.Cap)
{
int lastMemState = -1;
2017-05-17 18:18:26 +00:00
do
{
lastMemState++;
}
2017-05-19 18:17:07 +00:00
while (_states[_accessed[lastMemState].Frame] == null);
MoveStateToDisk(_accessed[lastMemState].Frame);
}
}
2015-09-16 00:03:50 +00:00
/// <summary>
/// X is the frame of the state, Y is the branch (-1 for current).
/// </summary>
private Point StateToRemove()
{
// X is frame, Y is branch
Point shouldRemove = new Point(-1, -1);
2017-05-19 18:17:07 +00:00
if (_branchStates.Any() && Settings.EraseBranchStatesFirst)
{
2017-05-19 18:17:07 +00:00
var kvp = _branchStates.Count > 1 ? _branchStates.ElementAt(1) : _branchStates.ElementAt(0);
shouldRemove.X = kvp.Key;
shouldRemove.Y = kvp.Value.Keys[0];
return shouldRemove;
}
int i = 0;
2017-05-19 18:17:07 +00:00
int markerSkips = MaxStates / 2;
// lowPrioritySates (e.g. states with only lag frames between them)
do
{
2017-05-19 18:17:07 +00:00
if (_lowPriorityStates.Count > i)
{
2017-05-19 18:17:07 +00:00
shouldRemove = FindState(_lowPriorityStates[i]);
}
else
{
break;
}
// Keep marker states
markerSkips--;
if (markerSkips < 0)
{
shouldRemove.X = -1;
}
i++;
2017-05-17 18:18:26 +00:00
}
2017-05-19 18:17:07 +00:00
while ((StateIsMarker(shouldRemove.X, shouldRemove.Y) && markerSkips > -1) || shouldRemove.X == 0);
// by last accessed
2017-05-19 18:17:07 +00:00
markerSkips = MaxStates / 2;
if (shouldRemove.X < 1)
{
i = 0;
do
{
2017-05-19 18:17:07 +00:00
if (_accessed.Count > i)
{
2017-05-19 18:17:07 +00:00
shouldRemove = FindState(_accessed[i]);
}
else
{
break;
}
// Keep marker states
markerSkips--;
if (markerSkips < 0)
{
shouldRemove.X = -1;
}
i++;
2017-05-17 18:18:26 +00:00
}
2017-05-19 18:17:07 +00:00
while ((StateIsMarker(shouldRemove.X, shouldRemove.Y) && markerSkips > -1) || shouldRemove.X == 0);
}
if (shouldRemove.X < 1) // only found marker states above
{
2017-05-19 18:17:07 +00:00
if (_branchStates.Any() && !Settings.EraseBranchStatesFirst)
{
2017-05-19 18:17:07 +00:00
var kvp = _branchStates.Count > 1 ? _branchStates.ElementAt(1) : _branchStates.ElementAt(0);
shouldRemove.X = kvp.Key;
shouldRemove.Y = kvp.Value.Keys[0];
}
else
{
2017-05-19 18:17:07 +00:00
StateManagerState s = _states.Values[1];
shouldRemove.X = s.Frame;
shouldRemove.Y = -1;
}
}
return shouldRemove;
}
2015-09-16 00:03:50 +00:00
private bool StateIsMarker(int frame, int branch)
{
if (frame == -1)
{
return false;
}
if (branch == -1)
{
2017-05-19 18:17:07 +00:00
return _movie.Markers.IsMarker(_states[frame].Frame + 1);
}
if (_movie.GetBranch(_movie.BranchIndexByHash(branch)).Markers == null)
{
2017-05-19 18:17:07 +00:00
return _movie.Markers.IsMarker(_states[frame].Frame + 1);
}
return _movie.GetBranch(branch).Markers.Any(m => m.Frame + 1 == frame);
}
2015-09-16 00:03:50 +00:00
private bool AllLag(int from, int upTo)
{
if (upTo >= Global.Emulator.Frame)
{
upTo = Global.Emulator.Frame - 1;
if (!Global.Emulator.AsInputPollable().IsLagFrame)
{
return false;
}
}
for (int i = from; i < upTo; i++)
{
if (_movie[i].Lagged == false)
{
return false;
}
}
return true;
}
private void MoveStateToDisk(int index)
{
2017-05-19 18:17:07 +00:00
Used -= (ulong)_states[index].Length;
_states[index].MoveToDisk();
}
2015-09-16 00:03:50 +00:00
private void MoveStateToMemory(int index)
{
2017-05-19 18:17:07 +00:00
_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
{
MaybeRemoveStates(); // Remove before adding so this state won't be removed.
}
2017-05-19 18:17:07 +00:00
if (_states.ContainsKey(frame))
{
2017-05-19 18:17:07 +00:00
if (StateHasDuplicate(frame, -1) != -2)
{
Used += (ulong)state.Length;
}
2017-05-19 18:17:07 +00:00
_states[frame].State = state;
}
else
{
Used += (ulong)state.Length;
2017-05-19 18:17:07 +00:00
_states.Add(frame, new StateManagerState(this, state, frame));
}
StateAccessed(frame);
2017-05-19 18:17:07 +00:00
int i = _states.IndexOfKey(frame);
if (i > 0 && AllLag(_states.Keys[i - 1], _states.Keys[i]))
{
2017-05-19 18:17:07 +00:00
_lowPriorityStates.Add(_states[frame]);
}
}
2015-09-16 00:03:50 +00:00
private void RemoveState(int frame, int branch = -1)
{
if (branch == -1)
{
2017-05-19 18:17:07 +00:00
_accessed.Remove(_states[frame]);
}
2017-05-19 18:17:07 +00:00
else if (_accessed.Contains(_branchStates[frame][branch]) && !Settings.EraseBranchStatesFirst)
{
2017-05-19 18:17:07 +00:00
_accessed.Remove(_branchStates[frame][branch]);
}
2015-09-16 00:03:50 +00:00
StateManagerState state;
2017-05-19 18:17:07 +00:00
bool hasDuplicate = StateHasDuplicate(frame, branch) != -2;
if (branch == -1)
{
2017-05-19 18:17:07 +00:00
state = _states[frame];
if (_states[frame].IsOnDisk)
{
2017-05-19 18:17:07 +00:00
_states[frame].Dispose();
}
else
{
2017-05-19 18:17:07 +00:00
Used -= (ulong)_states[frame].Length;
}
2017-05-19 18:17:07 +00:00
_states.RemoveAt(_states.IndexOfKey(frame));
}
else
{
2017-05-19 18:17:07 +00:00
state = _branchStates[frame][branch];
2017-05-19 18:17:07 +00:00
if (_branchStates[frame][branch].IsOnDisk)
{
2017-05-19 18:17:07 +00:00
_branchStates[frame][branch].Dispose();
}
2017-05-19 18:17:07 +00:00
_branchStates[frame].RemoveAt(_branchStates[frame].IndexOfKey(branch));
2017-05-19 18:17:07 +00:00
if (_branchStates[frame].Count == 0)
{
2017-05-19 18:17:07 +00:00
_branchStates.Remove(frame);
}
}
if (!hasDuplicate)
{
2017-05-19 18:17:07 +00:00
_lowPriorityStates.Remove(state);
}
}
2015-09-16 00:03:50 +00:00
private void StateAccessed(int frame)
{
if (frame == 0 && _movie.StartsFromSavestate)
{
return;
}
2017-05-19 18:17:07 +00:00
StateManagerState state = _states[frame];
bool removed = _accessed.Remove(state);
_accessed.Add(state);
2017-05-19 18:17:07 +00:00
if (_states[frame].IsOnDisk)
{
2017-05-19 18:17:07 +00:00
if (!_states[_accessed[0].Frame].IsOnDisk)
{
2017-05-19 18:17:07 +00:00
MoveStateToDisk(_accessed[0].Frame);
}
MoveStateToMemory(frame);
}
2017-05-19 18:17:07 +00:00
if (!removed && _accessed.Count > MaxStates)
{
2017-05-19 18:17:07 +00:00
_accessed.RemoveAt(0);
}
}
public bool HasState(int frame)
{
if (_movie.StartsFromSavestate && frame == 0)
{
return true;
}
2017-05-19 18:17:07 +00:00
return _states.ContainsKey(frame);
}
/// <summary>
/// Clears out all savestates after the given frame number
/// </summary>
public bool Invalidate(int frame)
{
bool anyInvalidated = false;
if (Any())
{
if (!_movie.StartsFromSavestate && frame == 0) // Never invalidate frame 0 on a non-savestate-anchored movie
{
frame = 1;
}
2015-09-16 00:03:50 +00:00
List<KeyValuePair<int, StateManagerState>> statesToRemove =
2017-05-19 18:17:07 +00:00
_states.Where(s => s.Key >= frame).ToList();
anyInvalidated = statesToRemove.Any();
foreach (var state in statesToRemove)
{
RemoveState(state.Key);
}
CallInvalidateCallback(frame);
}
return anyInvalidated;
}
/// <summary>
/// Clears all state information
/// </summary>
public void Clear()
2014-10-02 23:50:50 +00:00
{
2017-05-19 18:17:07 +00:00
_states.Clear();
_accessed.Clear();
2014-10-02 23:50:50 +00:00
Used = 0;
ClearDiskStates();
2014-10-02 23:50:50 +00:00
}
2015-09-16 00:03:50 +00:00
public void ClearStateHistory()
{
2017-05-19 18:17:07 +00:00
if (_states.Any())
{
2017-05-19 18:17:07 +00:00
StateManagerState power = _states.Values.First(s => s.Frame == 0);
StateAccessed(power.Frame);
2017-05-19 18:17:07 +00:00
_states.Clear();
_accessed.Clear();
SetState(0, power.State);
Used = (ulong)power.State.Length;
ClearDiskStates();
}
}
2015-09-16 00:03:50 +00:00
private void ClearDiskStates()
{
2017-05-19 18:17:07 +00:00
NdbDatabase?.Clear();
}
2014-07-10 22:07:50 +00:00
/// <summary>
/// Deletes/moves states to follow the state storage size limits.
/// Used after changing the settings.
/// </summary>
public void LimitStateCount()
{
while (Used + DiskUsed > Settings.CapTotal)
{
Point s = StateToRemove();
RemoveState(s.X, s.Y);
}
int index = -1;
while (DiskUsed > (ulong)Settings.DiskCapacitymb * 1024uL * 1024uL)
{
2017-05-17 18:18:26 +00:00
do
{
index++;
}
2017-05-19 18:17:07 +00:00
while (!_accessed[index].IsOnDisk);
2017-05-17 18:18:26 +00:00
2017-05-19 18:17:07 +00:00
_accessed[index].MoveToRAM();
}
if (Used > Settings.Cap)
{
MaybeRemoveStates();
}
}
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
2017-05-19 18:17:07 +00:00
for (int i = 1; i < _states.Count; i++)
{
2017-05-19 18:17:07 +00:00
if (_movie.Markers.IsMarker(_states.ElementAt(i).Key + 1)
|| _states.ElementAt(i).Key % StateGap == 0)
2017-05-17 18:18:26 +00:00
{
continue;
2017-05-17 18:18:26 +00:00
}
ret.Add(i);
2017-05-19 18:17:07 +00:00
if (_states.ElementAt(i).Value.IsOnDisk)
2017-05-17 18:18:26 +00:00
{
saveUsed -= _expectedStateSize;
2017-05-17 18:18:26 +00:00
}
else
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
saveUsed -= (ulong)_states.ElementAt(i).Value.Length;
2017-05-17 18:18:26 +00:00
}
}
// 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
{
index++;
2017-05-19 18:17:07 +00:00
if (index >= _states.Count)
2017-05-17 18:18:26 +00:00
{
2016-02-20 18:00:05 +00:00
break;
2017-05-17 18:18:26 +00:00
}
}
2017-05-19 18:17:07 +00:00
while (_movie.Markers.IsMarker(_states.ElementAt(index).Key + 1));
2017-05-19 18:17:07 +00:00
if (index >= _states.Count)
2017-05-17 18:18:26 +00:00
{
break;
2017-05-17 18:18:26 +00:00
}
2016-02-20 18:00:05 +00:00
ret.Add(index);
2017-05-19 18:17:07 +00:00
if (_states.ElementAt(index).Value.IsOnDisk)
2017-05-17 18:18:26 +00:00
{
saveUsed -= _expectedStateSize;
2017-05-17 18:18:26 +00:00
}
else
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
saveUsed -= (ulong)_states.ElementAt(index).Value.Length;
2017-05-17 18:18:26 +00:00
}
}
// if there are enough markers to still be over the limit, remove marker frames
index = 0;
while (saveUsed > (ulong)Settings.DiskSaveCapacitymb * 1024 * 1024)
{
index++;
2016-02-20 18:00:05 +00:00
if (!ret.Contains(index))
2017-05-17 18:18:26 +00:00
{
2016-02-20 18:00:05 +00:00
ret.Add(index);
2017-05-17 18:18:26 +00:00
}
2017-05-19 18:17:07 +00:00
if (_states.ElementAt(index).Value.IsOnDisk)
2017-05-17 18:18:26 +00:00
{
saveUsed -= _expectedStateSize;
2017-05-17 18:18:26 +00:00
}
else
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
saveUsed -= (ulong)_states.ElementAt(index).Value.Length;
2017-05-17 18:18:26 +00:00
}
}
return ret;
}
2014-07-10 22:07:50 +00:00
public void Save(BinaryWriter bw)
{
List<int> noSave = ExcludeStates();
2017-05-19 18:17:07 +00:00
bw.Write(_states.Count - noSave.Count);
for (int i = 0; i < _states.Count; i++)
{
if (noSave.Contains(i))
2017-05-17 18:18:26 +00:00
{
continue;
2017-05-17 18:18:26 +00:00
}
2017-05-19 18:17:07 +00:00
StateAccessed(_states.ElementAt(i).Key);
KeyValuePair<int, StateManagerState> kvp = _states.ElementAt(i);
bw.Write(kvp.Key);
bw.Write(kvp.Value.Length);
bw.Write(kvp.Value.State);
2017-05-09 18:19:55 +00:00
////_movie.ReportProgress(100d / States.Count * i);
}
}
public void Load(BinaryReader br)
{
2017-05-19 18:17:07 +00:00
_states.Clear();
try
{
int nstates = br.ReadInt32();
for (int i = 0; i < nstates; i++)
{
int frame = br.ReadInt32();
int len = br.ReadInt32();
byte[] data = br.ReadBytes(len);
2017-05-09 18:19:55 +00:00
// whether we should allow state removal check here is an interesting question
// nothing was edited yet, so it might make sense to show the project untouched first
SetState(frame, data);
2017-05-09 18:19:55 +00:00
////States.Add(frame, data);
////Used += len;
}
}
catch (EndOfStreamException)
{
}
}
2015-10-03 23:20:18 +00:00
public void SaveBranchStates(BinaryWriter bw)
{
2017-05-19 18:17:07 +00:00
bw.Write(_branchStates.Count);
foreach (var s in _branchStates)
{
bw.Write(s.Key);
bw.Write(s.Value.Count);
foreach (var t in s.Value)
{
bw.Write(t.Key);
t.Value.Write(bw);
}
}
}
public void LoadBranchStates(BinaryReader br)
{
try
{
int c = br.ReadInt32();
2017-05-19 18:17:07 +00:00
_branchStates = new SortedList<int, SortedList<int, StateManagerState>>(c);
while (c > 0)
2015-10-03 23:20:18 +00:00
{
int key = br.ReadInt32();
int c2 = br.ReadInt32();
var list = new SortedList<int, StateManagerState>(c2);
while (c2 > 0)
{
int key2 = br.ReadInt32();
var state = StateManagerState.Read(br, this);
list.Add(key2, state);
c2--;
}
2017-05-19 18:17:07 +00:00
_branchStates.Add(key, list);
c--;
2015-10-03 23:20:18 +00:00
}
}
catch (EndOfStreamException)
{
// Bad!
}
2014-07-10 22:07:50 +00:00
}
public KeyValuePair<int, byte[]> GetStateClosestToFrame(int frame)
2014-08-30 00:40:53 +00:00
{
2017-05-19 18:17:07 +00:00
var s = _states.LastOrDefault(state => state.Key < frame);
return this[s.Key];
2014-08-30 00:40:53 +00:00
}
2014-07-10 22:07:50 +00:00
// Map:
// 4 bytes - total savestate count
2017-05-09 18:19:55 +00:00
// [Foreach state]
2014-07-10 22:07:50 +00:00
// 4 bytes - frame
// 4 bytes - length of savestate
// 0 - n savestate
2016-04-17 13:01:09 +00:00
private ulong _used;
private ulong Used
{
get
{
return _used;
}
2016-04-17 13:01:09 +00:00
set
{
// TODO: Shouldn't we throw an exception? Debug.Fail only runs in debug mode?
2016-04-17 13:01:09 +00:00
if (value > 0xf000000000000000)
{
2016-04-17 13:01:09 +00:00
System.Diagnostics.Debug.Fail("ulong Used underfow!");
}
2016-04-17 13:01:09 +00:00
else
{
2016-04-17 13:01:09 +00:00
_used = value;
}
2016-04-17 13:01:09 +00:00
}
}
2015-09-16 00:03:50 +00:00
private ulong DiskUsed
{
get
{
2017-05-19 18:17:07 +00:00
if (NdbDatabase == null)
{
return 0;
}
2017-05-19 18:17:07 +00:00
return (ulong)NdbDatabase.Consumed;
}
}
2017-05-19 18:17:07 +00:00
public int StateCount => _states.Count;
public bool Any()
{
if (_movie.StartsFromSavestate)
{
2017-05-19 18:17:07 +00:00
return _states.Count > 0;
}
2017-05-19 18:17:07 +00:00
return _states.Count > 1;
}
2014-08-24 21:29:51 +00:00
public int LastKey
2014-07-13 15:26:50 +00:00
{
2014-08-24 21:29:51 +00:00
get
{
2017-05-19 18:17:07 +00:00
if (_states.Count == 0)
{
2014-08-24 21:29:51 +00:00
return 0;
}
2017-05-19 18:17:07 +00:00
return _states.Last().Key;
2014-08-24 21:29:51 +00:00
}
2014-07-13 15:26:50 +00:00
}
public int LastEmulatedFrame
{
get
{
if (StateCount > 0)
{
return LastKey;
}
return 0;
}
}
#region Branches
2017-05-19 18:17:07 +00:00
private SortedList<int, SortedList<int, StateManagerState>> _branchStates = new SortedList<int, SortedList<int, StateManagerState>>();
/// <summary>
/// Checks if the state at frame in the given branch (-1 for current) has any duplicates.
/// </summary>
/// <returns>Index of the branch (-1 for current) of the first match. If no match, returns -2.</returns>
2017-05-19 18:17:07 +00:00
private int StateHasDuplicate(int frame, int branchHash)
{
2015-09-16 00:03:50 +00:00
StateManagerState stateToMatch;
// Get the state instance
if (branchHash == -1)
2017-05-17 18:18:26 +00:00
{
2017-05-19 18:17:07 +00:00
stateToMatch = _states[frame];
2017-05-17 18:18:26 +00:00
}
else
{
2017-05-19 18:17:07 +00:00
if (!_branchStates[frame].ContainsKey(branchHash))
{
return -2;
}
2017-05-19 18:17:07 +00:00
stateToMatch = _branchStates[frame][branchHash];
// Check the current branch for duplicate.
2017-05-19 18:17:07 +00:00
if (_states.ContainsKey(frame) && _states[frame] == stateToMatch)
{
return -1;
}
}
// Check if there are any branch states for the given frame.
2017-05-19 18:17:07 +00:00
if (!_branchStates.ContainsKey(frame) || _branchStates[frame] == null || branchHash == -1)
{
return -2;
}
// Loop through branch states for the given frame.
2017-05-19 18:17:07 +00:00
SortedList<int, StateManagerState> stateList = _branchStates[frame];
2017-05-18 16:36:38 +00:00
for (int i = 0; i < stateList.Count; i++)
{
// Don't check the branch containing the state to match.
if (i == _movie.BranchIndexByHash(branchHash))
{
continue;
}
if (stateList.Values[i] == stateToMatch)
{
return i;
}
}
return -2; // No match.
}
2015-09-16 00:03:50 +00:00
2017-05-19 18:17:07 +00:00
private Point FindState(StateManagerState s)
{
Point ret = new Point(0, -1);
ret.X = s.Frame;
2017-05-19 18:17:07 +00:00
if (!_states.ContainsValue(s))
{
2017-05-19 18:17:07 +00:00
if (_branchStates.ContainsKey(s.Frame))
{
2017-05-19 18:17:07 +00:00
int index = _branchStates[s.Frame].Values.IndexOf(s);
ret.Y = _branchStates[s.Frame].Keys.ElementAt(index);
}
if (ret.Y == -1)
{
return new Point(-1, -2);
}
}
return ret;
}
public void AddBranch()
{
int branchHash = _movie.BranchHashByIndex(_movie.BranchCount - 1);
2017-05-19 18:17:07 +00:00
foreach (KeyValuePair<int, StateManagerState> kvp in _states)
{
2017-05-19 18:17:07 +00:00
if (!_branchStates.ContainsKey(kvp.Key))
{
2017-05-19 18:17:07 +00:00
_branchStates.Add(kvp.Key, new SortedList<int, StateManagerState>());
}
2017-05-19 18:17:07 +00:00
SortedList<int, StateManagerState> stateList = _branchStates[kvp.Key];
if (stateList == null) // when does this happen?
{
2015-09-16 00:03:50 +00:00
stateList = new SortedList<int, StateManagerState>();
2017-05-19 18:17:07 +00:00
_branchStates[kvp.Key] = stateList;
}
2016-02-20 18:00:05 +00:00
// We aren't creating any new states, just adding a reference to an already-existing one; so Used doesn't need to be updated.
2017-05-18 16:36:38 +00:00
stateList.Add(branchHash, kvp.Value);
}
}
public void RemoveBranch(int index)
{
int branchHash = _movie.BranchHashByIndex(index);
2017-05-19 18:17:07 +00:00
foreach (KeyValuePair<int, SortedList<int, StateManagerState>> kvp in _branchStates.ToList())
{
2015-09-16 00:03:50 +00:00
SortedList<int, StateManagerState> stateList = kvp.Value;
if (stateList == null)
{
continue;
}
/*
if (stateList.ContainsKey(branchHash))
{
if (stateHasDuplicate(kvp.Key, branchHash) == -2)
{
if (!stateList[branchHash].IsOnDisk)
Used -= (ulong)stateList[branchHash].Length;
}
}
*/
stateList.Remove(branchHash);
if (stateList.Count == 0)
{
2017-05-19 18:17:07 +00:00
_branchStates.Remove(kvp.Key);
}
}
}
public void UpdateBranch(int index)
{
if (index == -1) // backup branch is outsider
{
return;
}
int branchHash = _movie.BranchHashByIndex(index);
// RemoveBranch
2017-05-19 18:17:07 +00:00
foreach (KeyValuePair<int, SortedList<int, StateManagerState>> kvp in _branchStates.ToList())
{
2015-09-16 00:03:50 +00:00
SortedList<int, StateManagerState> stateList = kvp.Value;
if (stateList == null)
{
continue;
}
/*
if (stateList.ContainsKey(branchHash))
{
if (stateHasDuplicate(kvp.Key, branchHash) == -2)
{
if (!stateList[branchHash].IsOnDisk)
Used -= (ulong)stateList[branchHash].Length;
}
}
*/
stateList.Remove(branchHash);
if (stateList.Count == 0)
{
2017-05-19 18:17:07 +00:00
_branchStates.Remove(kvp.Key);
}
}
// AddBranch
2017-05-19 18:17:07 +00:00
foreach (KeyValuePair<int, StateManagerState> kvp in _states)
{
2017-05-19 18:17:07 +00:00
if (!_branchStates.ContainsKey(kvp.Key))
{
2017-05-19 18:17:07 +00:00
_branchStates.Add(kvp.Key, new SortedList<int, StateManagerState>());
}
2017-05-19 18:17:07 +00:00
SortedList<int, StateManagerState> stateList = _branchStates[kvp.Key];
if (stateList == null)
{
2015-09-16 00:03:50 +00:00
stateList = new SortedList<int, StateManagerState>();
2017-05-19 18:17:07 +00:00
_branchStates[kvp.Key] = stateList;
}
stateList.Add(branchHash, kvp.Value);
}
}
public void LoadBranch(int index)
{
if (index == -1) // backup branch is outsider
{
return;
}
int branchHash = _movie.BranchHashByIndex(index);
2017-05-09 18:19:55 +00:00
////Invalidate(0); // Not a good way of doing it?
2017-05-19 18:17:07 +00:00
foreach (KeyValuePair<int, SortedList<int, StateManagerState>> kvp in _branchStates)
{
2017-05-19 18:17:07 +00:00
if (kvp.Key == 0 && _states.ContainsKey(0))
{
continue; // TODO: It might be a better idea to just not put state 0 in BranchStates.
}
if (kvp.Value.ContainsKey(branchHash))
{
SetState(kvp.Key, kvp.Value[branchHash].State);
}
}
}
#endregion
}
}