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

262 lines
5.7 KiB
C#
Raw Normal View History

2014-07-10 22:07:50 +00:00
using System;
using System.Collections.Generic;
using System.ComponentModel;
2014-07-10 22:07:50 +00:00
using System.IO;
using System.Linq;
using System.Text;
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
{
2014-08-24 21:29:51 +00:00
private readonly SortedList<int, byte[]> States = new SortedList<int, byte[]>();
private readonly TasMovie _movie;
public TasStateManager(TasMovie movie)
{
_movie = movie;
Settings = new ManagerSettings();
var cap = Settings.Cap;
int limit = 0;
if (Global.Emulator != null)
{
var stateSize = Global.Emulator.SaveStateBinary().Length;
if (stateSize > 0)
{
limit = cap / stateSize;
}
}
States = new SortedList<int, byte[]>(limit);
}
public ManagerSettings 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 byte[] this[int frame]
{
get
{
if (frame == 0 && _movie.StartsFromSavestate)
{
return _movie.BinarySavestate;
}
if (States.ContainsKey(frame))
{
return States[frame];
}
return new byte[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 void Capture(bool force = false)
{
bool shouldCapture = false;
if (force)
{
shouldCapture = force;
}
2014-08-30 00:40:53 +00:00
else if (Global.Emulator.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 (_movie.Markers.IsMarker(Global.Emulator.Frame))
{
shouldCapture = true; // Markers shoudl always get priority
}
else
{
shouldCapture = Global.Emulator.Frame % 2 > 0;
}
if (shouldCapture)
{
var frame = Global.Emulator.Frame;
var state = (byte[])Global.Emulator.SaveStateBinary().Clone();
if (States.ContainsKey(frame))
{
States[frame] = state;
}
else
{
if (Used + state.Length >= Settings.Cap)
{
Used -= States.ElementAt(0).Value.Length;
States.RemoveAt(0);
}
States.Add(frame, state);
Used += state.Length;
}
}
}
public bool HasState(int frame)
{
return States.ContainsKey(frame);
}
/// <summary>
/// Clears out all savestates after the given frame number
/// </summary>
public void Invalidate(int frame)
{
2014-09-22 14:44:32 +00:00
if (States.Count > 0 && frame > 0) // Never invalidate frame 0, TODO: Only if movie is a power-on movie should we keep frame 0, check this
{
var statesToRemove = States
.Where(x => x.Key >= frame)
.ToList();
foreach (var state in statesToRemove)
2014-09-22 14:44:32 +00:00
{
Used -= state.Value.Length;
States.Remove(state.Key);
2014-09-22 14:44:32 +00:00
}
}
}
/// <summary>
/// Clears all state information
/// </summary>
public void Clear()
{
var power = States.FirstOrDefault(s => s.Key == 0);
States.Clear();
if (power.Value.Length > 0)
{
States.Add(0, power.Value);
}
Used = power.Value.Length;
}
2014-07-10 22:07:50 +00:00
public void Save(BinaryWriter bw)
2014-07-10 22:07:50 +00:00
{
bw.Write(States.Count);
2014-08-24 21:29:51 +00:00
foreach (var kvp in States)
2014-07-10 22:07:50 +00:00
{
bw.Write(kvp.Key);
bw.Write(kvp.Value.Length);
bw.Write(kvp.Value);
2014-07-10 22:07:50 +00:00
}
}
2014-07-10 22:07:50 +00:00
public void Load(BinaryReader br)
{
2014-08-25 22:04:05 +00:00
States.Clear();
int nstates = br.ReadInt32();
for (int i = 0; i < nstates; i++)
{
int frame = br.ReadInt32();
int len = br.ReadInt32();
byte[] data = br.ReadBytes(len);
States.Add(frame, data);
Used += len;
}
2014-07-10 22:07:50 +00:00
}
2014-08-30 00:40:53 +00:00
public byte[] GetStateClosestToFrame(int frame)
{
return States.LastOrDefault(state => state.Key < frame).Value;
}
2014-07-10 22:07:50 +00:00
// Map:
// 4 bytes - total savestate count
//[Foreach state]
// 4 bytes - frame
// 4 bytes - length of savestate
// 0 - n savestate
private int Used
{
2014-08-24 21:29:51 +00:00
get;
set;
}
public int StateCount
{
get
{
return States.Count;
}
}
public bool Any()
{
return States.Count > 1; // TODO: power-on MUST have a state, savestate-anchored movies do not, take this into account
}
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
{
var kk = States.Keys;
int index = kk.Count;
if (index == 0)
return 0;
return kk[index - 1];
}
2014-07-13 15:26:50 +00:00
}
public class ManagerSettings
{
public ManagerSettings()
{
SaveGreenzone = true;
Capacitymb = 512;
}
/// <summary>
/// Whether or not to save greenzone information to disk
/// </summary>
public bool SaveGreenzone { get; set; }
/// <summary>
/// The total amount of memory to devote to greenzone in megabytes
/// </summary>
public int Capacitymb { get; set; }
public int Cap
{
get { return Capacitymb * 1024 * 1024; }
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(SaveGreenzone.ToString());
sb.AppendLine(Capacitymb.ToString());
return sb.ToString();
}
public void PopulateFromString(string settings)
{
var lines = settings.Split(new [] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
SaveGreenzone = bool.Parse(lines[0]);
Capacitymb = int.Parse(lines[1]);
}
}
}
}