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
2014-11-30 16:42:58 +00:00
using BizHawk.Emulation.Common ;
using BizHawk.Emulation.Common.IEmulatorExtensions ;
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-12-21 23:53:40 +00:00
// 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
{
get
{
return Global . Emulator . AsStatable ( ) ;
}
}
2015-07-02 18:51:42 +00:00
public Action < int > InvalidateCallback { get ; set ; }
private void CallInvalidateCallback ( int index )
{
if ( InvalidateCallback ! = null )
{
InvalidateCallback ( index ) ;
}
}
2014-08-24 21:29:51 +00:00
private readonly SortedList < int , byte [ ] > States = new SortedList < int , byte [ ] > ( ) ;
2015-03-15 06:26:57 +00:00
private string statePath
{
get
{
return PathManager . MakeAbsolutePath ( Global . Config . PathEntries [ "Global" , "TAStudio states" ] . Path , null ) ;
}
}
2014-07-07 18:40:42 +00:00
2014-07-17 18:21:12 +00:00
private readonly TasMovie _movie ;
2015-03-15 06:26:57 +00:00
private ulong _expectedStateSize = 0 ;
2014-10-15 15:47:04 +00:00
2014-11-19 16:22:03 +00:00
private int _minFrequency = VersionInfo . DeveloperBuild ? 2 : 1 ;
2015-03-03 06:56:45 +00:00
private const int _maxFrequency = 16 ;
2014-10-15 15:47:04 +00:00
private int StateFrequency
{
get
{
2015-03-15 06:26:57 +00:00
int freq = ( int ) ( _expectedStateSize / 65536 ) ;
2014-10-15 15:47:04 +00:00
if ( freq < _minFrequency )
{
return _minFrequency ;
}
if ( freq > _maxFrequency )
{
return _maxFrequency ;
}
return freq ;
}
}
2014-07-17 18:21:12 +00:00
2015-03-14 16:38:07 +00:00
private int maxStates
2015-03-15 06:26:57 +00:00
{ get { return ( int ) ( Settings . Cap / _expectedStateSize ) ; } }
2015-03-14 16:38:07 +00:00
2014-12-21 23:53:40 +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-11-30 16:42:58 +00:00
2014-10-17 22:47:30 +00:00
Settings = new TasStateManagerSettings ( Global . Config . DefaultTasProjSettings ) ;
2014-08-24 22:50:21 +00:00
int limit = 0 ;
2015-03-03 06:56:45 +00:00
2015-03-15 06:26:57 +00:00
_expectedStateSize = ( ulong ) Core . SaveStateBinary ( ) . Length ;
2014-08-24 22:50:21 +00:00
2014-11-30 17:54:21 +00:00
if ( _expectedStateSize > 0 )
{
2015-03-15 06:26:57 +00:00
limit = maxStates ;
2014-08-24 22:50:21 +00:00
}
States = new SortedList < int , byte [ ] > ( limit ) ;
2015-03-15 06:26:57 +00:00
if ( Directory . Exists ( statePath ) )
2015-03-19 19:55:38 +00:00
{
2015-03-15 06:26:57 +00:00
Directory . Delete ( statePath , true ) ; // To delete old files that may still exist.
2015-03-19 19:55:38 +00:00
}
2015-03-15 06:26:57 +00:00
Directory . CreateDirectory ( statePath ) ;
accessed = new List < int > ( ) ;
2014-07-10 22:44:44 +00:00
}
2014-10-17 22:39:40 +00:00
public TasStateManagerSettings Settings { get ; set ; }
2014-07-10 22:44:44 +00:00
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>
2014-10-26 23:37:42 +00:00
public KeyValuePair < int , byte [ ] > this [ int frame ]
2014-07-07 18:40:42 +00:00
{
get
{
2014-07-17 18:21:12 +00:00
if ( frame = = 0 & & _movie . StartsFromSavestate )
{
2015-03-03 06:56:45 +00:00
return new KeyValuePair < int , byte [ ] > ( 0 , _movie . BinarySavestate ) ;
2014-07-17 18:21:12 +00:00
}
2014-07-07 18:40:42 +00:00
if ( States . ContainsKey ( frame ) )
{
2015-03-15 06:26:57 +00:00
// if (States[frame] == null) // Get from file
StateAccessed ( frame ) ;
2015-03-03 06:56:45 +00:00
return new KeyValuePair < int , byte [ ] > ( frame , States [ frame ] ) ;
2014-07-07 18:40:42 +00:00
}
2015-03-03 06:56:45 +00:00
return new KeyValuePair < int , byte [ ] > ( - 1 , new byte [ 0 ] ) ;
2014-07-07 18:40:42 +00:00
}
}
2015-03-15 06:26:57 +00:00
private List < int > accessed ;
2014-07-07 18:40:42 +00:00
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
2015-03-03 06:56:45 +00:00
int frame = Global . Emulator . Frame ;
if ( _movie . StartsFromSavestate & & frame = = 0 ) // Never capture frame 0 on savestate anchored movies since we have it anyway
2014-10-15 15:47:04 +00:00
{
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
}
2015-03-03 06:56:45 +00:00
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 ;
}
2015-03-14 16:38:07 +00:00
else if ( _movie . Markers . IsMarker ( frame + 1 ) )
2014-08-30 00:40:53 +00:00
{
shouldCapture = true ; // Markers shoudl always get priority
}
2014-07-07 18:40:42 +00:00
else
{
2015-03-17 01:02:38 +00:00
shouldCapture = frame - States . Keys . LastOrDefault ( k = > k < frame ) > = StateFrequency ;
2014-08-27 20:43:45 +00:00
}
if ( shouldCapture )
{
2015-03-15 06:26:57 +00:00
SetState ( frame , ( byte [ ] ) Core . SaveStateBinary ( ) . Clone ( ) ) ;
2014-07-07 18:40:42 +00:00
}
}
2015-03-03 06:56:45 +00:00
private void MaybeRemoveState ( )
{
int shouldRemove = - 1 ;
2015-03-15 06:26:57 +00:00
if ( Used + DiskUsed > Settings . CapTotal )
shouldRemove = StateToRemove ( ) ;
if ( shouldRemove ! = - 1 )
2015-03-14 16:38:07 +00:00
{
2015-03-15 06:26:57 +00:00
RemoveState ( States . ElementAt ( shouldRemove ) . Key ) ;
}
2015-03-03 06:56:45 +00:00
2015-03-15 06:26:57 +00:00
if ( Used > Settings . Cap )
{
2015-03-16 16:36:00 +00:00
int lastMemState = - 1 ;
do { lastMemState + + ; } while ( States [ accessed [ lastMemState ] ] = = null ) ;
MoveStateToDisk ( accessed [ lastMemState ] ) ;
2015-03-15 06:26:57 +00:00
}
}
private int StateToRemove ( )
{
int markerSkips = maxStates / 3 ;
2015-03-14 16:38:07 +00:00
2015-03-15 06:26:57 +00:00
int shouldRemove = _movie . StartsFromSavestate ? - 1 : 0 ;
do
{
shouldRemove + + ;
// No need to have two savestates with only lag frames between them.
2015-03-16 16:36:00 +00:00
for ( int i = shouldRemove ; i < States . Count - 1 ; i + + )
2015-03-15 06:26:57 +00:00
{
if ( AllLag ( States . ElementAt ( i ) . Key , States . ElementAt ( i + 1 ) . Key ) )
2015-03-14 16:38:07 +00:00
{
2015-03-15 06:26:57 +00:00
shouldRemove = i ;
break ;
2015-03-14 16:38:07 +00:00
}
2015-03-15 06:26:57 +00:00
}
2015-03-14 16:38:07 +00:00
2015-03-15 06:26:57 +00:00
// Keep marker states
markerSkips - - ;
if ( markerSkips < 0 )
shouldRemove = _movie . StartsFromSavestate ? 0 : 1 ;
} while ( _movie . Markers . IsMarker ( States . ElementAt ( shouldRemove ) . Key + 1 ) & & markerSkips > - 1 ) ;
2015-03-12 18:31:28 +00:00
2015-03-15 06:26:57 +00:00
return shouldRemove ;
2015-03-03 06:56:45 +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 . Value )
return false ;
}
return true ;
}
2015-03-15 06:26:57 +00:00
private void MoveStateToDisk ( int index )
{
// Save
string path = Path . Combine ( statePath , index . ToString ( ) ) ;
File . WriteAllBytes ( path , States [ index ] ) ;
DiskUsed + = _expectedStateSize ;
// Remove from RAM
Used - = ( ulong ) States [ index ] . Length ;
States [ index ] = null ;
}
private void MoveStateToMemory ( int index )
{
// Load
string path = Path . Combine ( statePath , index . ToString ( ) ) ;
byte [ ] loadData = File . ReadAllBytes ( path ) ;
DiskUsed - = _expectedStateSize ;
// States list
Used + = ( ulong ) loadData . Length ;
States [ index ] = loadData ;
File . Delete ( path ) ;
}
2015-07-19 14:37:53 +00:00
internal void SetState ( int frame , byte [ ] state )
2015-03-15 06:26:57 +00:00
{
if ( States . ContainsKey ( frame ) )
{
States [ frame ] = state ;
MaybeRemoveState ( ) ; // Also does moving to disk
}
else
{
Used + = ( ulong ) state . Length ;
MaybeRemoveState ( ) ; // Remove before adding so this state won't be removed.
States . Add ( frame , state ) ;
}
StateAccessed ( frame ) ;
}
private void RemoveState ( int index )
{
if ( States [ index ] = = null )
{
DiskUsed - = _expectedStateSize ; // Disk length?
string path = Path . Combine ( statePath , index . ToString ( ) ) ;
File . Delete ( path ) ;
}
else
Used - = ( ulong ) States [ index ] . Length ;
States . RemoveAt ( States . IndexOfKey ( index ) ) ;
accessed . Remove ( index ) ;
}
private void StateAccessed ( int index )
{
bool removed = accessed . Remove ( index ) ;
accessed . Add ( index ) ;
if ( States [ index ] = = null )
{
2015-03-16 16:36:00 +00:00
if ( States [ accessed [ 0 ] ] ! = null )
MoveStateToDisk ( accessed [ 0 ] ) ;
2015-03-15 06:26:57 +00:00
MoveStateToMemory ( index ) ;
}
if ( ! removed & & accessed . Count > ( int ) ( Used / _expectedStateSize ) )
accessed . RemoveAt ( 0 ) ;
}
2014-07-10 20:48:43 +00:00
public bool HasState ( int frame )
{
2014-10-25 16:11:40 +00:00
if ( _movie . StartsFromSavestate & & frame = = 0 )
{
return true ;
}
2015-03-03 06:56:45 +00:00
2014-07-10 20:48:43 +00:00
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-10-25 19:33:28 +00:00
if ( Any ( ) )
2014-07-07 18:40:42 +00:00
{
2014-10-25 19:33:28 +00:00
if ( ! _movie . StartsFromSavestate & & frame = = 0 ) // Never invalidate frame 0 on a non-savestate-anchored movie
{
frame = 1 ;
}
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
{
2015-03-15 06:26:57 +00:00
if ( state . Value = = null )
DiskUsed - = _expectedStateSize ; // Length??
else
Used - = ( ulong ) state . Value . Length ;
2015-03-16 16:36:00 +00:00
accessed . Remove ( state . Key ) ;
2014-09-22 22:52:34 +00:00
States . Remove ( state . Key ) ;
2014-09-22 14:44:32 +00:00
}
2015-07-02 18:51:42 +00:00
CallInvalidateCallback ( frame ) ;
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 ( ) ;
2015-03-16 16:36:00 +00:00
accessed . Clear ( ) ;
2014-10-02 23:50:50 +00:00
Used = 0 ;
2015-03-16 16:36:00 +00:00
DiskUsed = 0 ;
2014-10-02 23:50:50 +00:00
}
2015-03-02 23:43:52 +00:00
public void ClearStateHistory ( )
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
{
2015-03-16 16:36:00 +00:00
KeyValuePair < int , byte [ ] > power = States . FirstOrDefault ( s = > s . Key = = 0 ) ;
if ( power . Value = = null )
{
StateAccessed ( power . Key ) ;
power = States . FirstOrDefault ( s = > s . Key = = 0 ) ;
}
2014-10-02 23:19:37 +00:00
States . Clear ( ) ;
2015-03-16 16:36:00 +00:00
accessed . Clear ( ) ;
2014-10-02 23:10:36 +00:00
2014-10-02 23:19:37 +00:00
if ( power . Value . Length > 0 )
{
2015-03-16 16:36:00 +00:00
SetState ( 0 , power . Value ) ;
2015-03-15 06:26:57 +00:00
Used = ( ulong ) power . Value . Length ;
2014-10-02 23:19:37 +00:00
}
else
{
Used = 0 ;
2015-03-16 16:36:00 +00:00
DiskUsed = 0 ;
2014-10-02 23:19:37 +00:00
}
}
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
{
2015-03-17 01:02:38 +00:00
List < int > noSave = ExcludeStates ( ) ;
2015-03-15 06:26:57 +00:00
2015-03-16 16:36:00 +00:00
bw . Write ( States . Count - noSave . Count ) ;
for ( int i = 0 ; i < States . Count ; i + + )
2014-07-10 22:07:50 +00:00
{
2015-03-17 01:02:38 +00:00
if ( noSave . Contains ( i ) )
2015-03-15 06:26:57 +00:00
continue ;
2015-03-16 16:36:00 +00:00
StateAccessed ( States . ElementAt ( i ) . Key ) ;
KeyValuePair < int , byte [ ] > kvp = States . ElementAt ( i ) ;
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
}
2015-03-17 01:02:38 +00:00
private List < int > ExcludeStates ( )
{
List < int > ret = new List < int > ( ) ;
ulong saveUsed = Used + DiskUsed ;
int index = - 1 ;
while ( saveUsed > ( ulong ) Settings . DiskSaveCapacitymb * 1024 * 1024 )
{
do
{
index + + ;
} while ( _movie . Markers . IsMarker ( States . ElementAt ( index ) . Key + 1 ) ) ;
ret . Add ( index ) ;
if ( States . ElementAt ( index ) . Value = = null )
saveUsed - = _expectedStateSize ;
else
saveUsed - = ( ulong ) States . ElementAt ( index ) . Value . Length ;
}
// If there are enough markers to still be over the limit, remove marker frames
index = - 1 ;
while ( saveUsed > ( ulong ) Settings . DiskSaveCapacitymb * 1024 * 1024 )
{
index + + ;
ret . Add ( index ) ;
if ( States . ElementAt ( index ) . Value = = null )
saveUsed - = _expectedStateSize ;
else
saveUsed - = ( ulong ) States . ElementAt ( index ) . Value . Length ;
}
return ret ;
}
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 ( ) ;
2015-03-16 16:36:00 +00:00
//if (br.BaseStream.Length > 0)
//{ BaseStream.Length does not return the expected value.
int nstates = br . ReadInt32 ( ) ;
for ( int i = 0 ; i < nstates ; i + + )
2014-08-24 21:53:48 +00:00
{
2015-03-16 16:36:00 +00:00
int frame = br . ReadInt32 ( ) ;
int len = br . ReadInt32 ( ) ;
byte [ ] data = br . ReadBytes ( len ) ;
SetState ( frame , data ) ;
//States.Add(frame, data);
//Used += len;
2014-08-24 21:53:48 +00:00
}
2015-03-16 16:36:00 +00:00
//}
2014-07-10 22:07:50 +00:00
}
2014-10-26 23:26:43 +00:00
public KeyValuePair < int , byte [ ] > GetStateClosestToFrame ( int frame )
2014-08-30 00:40:53 +00:00
{
2014-10-26 23:26:43 +00:00
var s = States . LastOrDefault ( state = > state . Key < frame ) ;
2014-10-26 23:17:20 +00:00
2015-03-15 06:26:57 +00:00
return this [ s . Key ] ;
2014-08-30 00:40:53 +00:00
}
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
2015-03-15 06:26:57 +00:00
private ulong Used
{
get ;
set ;
}
private ulong DiskUsed
2014-07-10 22:44:44 +00:00
{
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 ( )
{
2014-10-25 16:05:11 +00:00
if ( _movie . StartsFromSavestate )
{
return States . Count > 0 ;
}
return States . Count > 1 ;
2014-10-02 22:58:36 +00:00
}
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
{
2015-03-03 06:56:45 +00:00
if ( States . Count = = 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
}
2015-03-03 06:56:45 +00:00
return States . Last ( ) . Key ;
2014-08-24 21:29:51 +00:00
}
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-07 18:40:42 +00:00
}
}