2014-07-10 22:07:50 +00:00
using System ;
using System.Collections.Generic ;
2014-07-10 22:44:44 +00:00
using System.ComponentModel ;
2014-07-10 22:07:50 +00:00
using System.IO ;
2014-07-07 18:40:42 +00:00
using System.Linq ;
2014-07-10 22:44:44 +00:00
using System.Text ;
2014-07-07 18:40:42 +00:00
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 [ ] > ( ) ;
2014-07-07 18:40:42 +00:00
2014-07-17 18:21:12 +00:00
private readonly TasMovie _movie ;
2014-10-15 15:47:04 +00:00
private int _expectedStateSize = 0 ;
private const int _minFrequency = 2 ;
private const int _maxFrequency = 16 ;
private int StateFrequency
{
get
{
var freq = _expectedStateSize / 65536 ;
if ( freq < _minFrequency )
{
return _minFrequency ;
}
if ( freq > _maxFrequency )
{
return _maxFrequency ;
}
return freq ;
}
}
2014-07-17 18:21:12 +00:00
public TasStateManager ( TasMovie movie )
2014-07-10 22:44:44 +00:00
{
2014-07-17 18:21:12 +00:00
_movie = movie ;
2014-07-10 22:44:44 +00:00
Settings = new ManagerSettings ( ) ;
2014-08-24 22:50:21 +00:00
var cap = Settings . Cap ;
int limit = 0 ;
if ( Global . Emulator ! = null )
{
2014-10-15 15:47:04 +00:00
_expectedStateSize = Global . Emulator . SaveStateBinary ( ) . Length ;
2014-08-24 22:50:21 +00:00
2014-10-15 15:47:04 +00:00
if ( _expectedStateSize > 0 )
2014-08-24 22:50:21 +00:00
{
2014-10-15 15:47:04 +00:00
limit = cap / _expectedStateSize ;
2014-08-24 22:50:21 +00:00
}
}
States = new SortedList < int , byte [ ] > ( limit ) ;
2014-07-10 22:44:44 +00:00
}
public ManagerSettings Settings { get ; set ; }
2014-07-07 18:40:42 +00:00
/// <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
{
2014-07-17 18:21:12 +00:00
if ( frame = = 0 & & _movie . StartsFromSavestate )
{
return _movie . BinarySavestate ;
}
2014-07-07 18:40:42 +00:00
if ( States . ContainsKey ( frame ) )
{
return States [ frame ] ;
}
return new byte [ 0 ] ;
}
}
2014-10-05 12:59:12 +00:00
public byte [ ] InitialState
{
get
{
if ( _movie . StartsFromSavestate )
{
return _movie . BinarySavestate ;
}
return States [ 0 ] ;
}
}
2014-07-07 18:40:42 +00:00
/// <summary>
/// Requests that the current emulator state be captured
2014-08-27 20:43:45 +00:00
/// Unless force is true, the state may or may not be captured depending on the logic employed by "greenzone" management
2014-07-07 18:40:42 +00:00
/// </summary>
2014-08-27 20:43:45 +00:00
public void Capture ( bool force = false )
2014-07-07 18:40:42 +00:00
{
2014-08-27 20:43:45 +00:00
bool shouldCapture = false ;
2014-10-15 15:47:04 +00:00
if ( _movie . StartsFromSavestate & & Global . Emulator . Frame = = 0 ) // Never capture frame 0 on savestate anchored movies since we have it anyway
{
shouldCapture = false ;
}
else if ( force )
2014-07-07 18:40:42 +00:00
{
2014-08-27 20:43:45 +00:00
shouldCapture = force ;
2014-07-07 18:40:42 +00:00
}
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
}
2014-07-07 18:40:42 +00:00
else
{
2014-10-15 15:47:04 +00:00
shouldCapture = Global . Emulator . Frame % StateFrequency = = 0 ;
2014-08-27 20:43:45 +00:00
}
if ( shouldCapture )
{
var frame = Global . Emulator . Frame ;
var state = ( byte [ ] ) Global . Emulator . SaveStateBinary ( ) . Clone ( ) ;
if ( States . ContainsKey ( frame ) )
2014-07-10 22:44:44 +00:00
{
2014-08-27 20:43:45 +00:00
States [ frame ] = state ;
}
else
{
if ( Used + state . Length > = Settings . Cap )
{
2014-10-15 15:23:48 +00:00
var first = _movie . StartsFromSavestate ? 0 : 1 ;
Used - = States . ElementAt ( first ) . Value . Length ;
States . RemoveAt ( first ) ;
2014-08-27 20:43:45 +00:00
}
States . Add ( frame , state ) ;
Used + = state . Length ;
2014-07-10 22:44:44 +00:00
}
2014-07-07 18:40:42 +00:00
}
}
2014-07-10 20:48:43 +00:00
public bool HasState ( int frame )
{
return States . ContainsKey ( frame ) ;
}
2014-07-07 18:40:42 +00:00
/// <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
2014-07-07 18:40:42 +00:00
{
2014-09-22 22:52:34 +00:00
var statesToRemove = States
. Where ( x = > x . Key > = frame )
. ToList ( ) ;
foreach ( var state in statesToRemove )
2014-09-22 14:44:32 +00:00
{
2014-09-22 22:52:34 +00:00
Used - = state . Value . Length ;
States . Remove ( state . Key ) ;
2014-09-22 14:44:32 +00:00
}
2014-07-07 18:40:42 +00:00
}
}
2014-07-07 19:32:37 +00:00
/// <summary>
/// Clears all state information
/// </summary>
2014-10-02 23:50:50 +00:00
///
2014-07-07 19:32:37 +00:00
public void Clear ( )
2014-10-02 23:50:50 +00:00
{
States . Clear ( ) ;
Used = 0 ;
}
public void ClearGreenzone ( )
2014-07-07 19:32:37 +00:00
{
2014-10-02 23:19:37 +00:00
if ( States . Any ( ) )
2014-10-02 23:10:36 +00:00
{
2014-10-02 23:19:37 +00:00
var power = States . FirstOrDefault ( s = > s . Key = = 0 ) ;
States . Clear ( ) ;
2014-10-02 23:10:36 +00:00
2014-10-02 23:19:37 +00:00
if ( power . Value . Length > 0 )
{
States . Add ( 0 , power . Value ) ;
Used = power . Value . Length ;
}
else
{
Used = 0 ;
}
}
2014-07-07 19:32:37 +00:00
}
2014-07-10 22:07:50 +00:00
2014-08-24 21:53:48 +00:00
public void Save ( BinaryWriter bw )
2014-07-10 22:07:50 +00:00
{
2014-08-24 21:53:48 +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
{
2014-08-24 21:53:48 +00:00
bw . Write ( kvp . Key ) ;
bw . Write ( kvp . Value . Length ) ;
bw . Write ( kvp . Value ) ;
2014-07-10 22:07:50 +00:00
}
2014-08-24 21:53:48 +00:00
}
2014-07-10 22:07:50 +00:00
2014-08-24 21:53:48 +00:00
public void Load ( BinaryReader br )
{
2014-08-25 22:04:05 +00:00
States . Clear ( ) ;
2014-08-24 21:53:48 +00:00
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-08-24 21:53:48 +00:00
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
2014-07-10 22:44:44 +00:00
private int Used
{
2014-08-24 21:29:51 +00:00
get ;
set ;
2014-07-10 22:44:44 +00:00
}
2014-07-16 23:04:56 +00:00
public int StateCount
{
get
{
return States . Count ;
}
}
2014-10-02 22:58:36 +00:00
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 )
2014-10-14 13:31:14 +00:00
{
2014-08-24 21:29:51 +00:00
return 0 ;
2014-10-14 13:31:14 +00:00
}
2014-08-24 21:29:51 +00:00
return kk [ index - 1 ] ;
}
2014-07-13 15:26:50 +00:00
}
2014-10-14 13:31:14 +00:00
public int LastEmulatedFrame
{
get
{
if ( StateCount > 0 )
{
return LastKey ;
}
return 0 ;
}
}
2014-07-10 22:44:44 +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 ] ) ;
}
}
2014-07-07 18:40:42 +00:00
}
}