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
{
2015-08-13 18:03:22 +00:00
private class tsmState
{
static int state_id = 0 ;
byte [ ] _state ;
int frame ;
int ID ;
public tsmState ( byte [ ] state )
{
_state = state ;
ID = state_id ;
state_id + + ;
}
public byte [ ] State
{
get
{
if ( _state ! = null )
return _state ;
string path = Path . Combine ( TasStateManager . statePath , ID . ToString ( ) ) ;
return File . ReadAllBytes ( path ) ;
}
set
{
if ( _state ! = null )
{
_state = value ;
return ;
}
string path = Path . Combine ( TasStateManager . statePath , ID . ToString ( ) ) ;
File . WriteAllBytes ( path , value ) ;
}
}
public int Length { get { return State . Length ; } }
public bool IsOnDisk { get { return _state = = null ; } }
public void MoveToDisk ( )
{
if ( IsOnDisk )
return ;
string path = Path . Combine ( TasStateManager . statePath , ID . ToString ( ) ) ;
File . WriteAllBytes ( path , _state ) ;
_state = null ;
}
public void MoveToRAM ( )
{
if ( ! IsOnDisk )
return ;
string path = Path . Combine ( TasStateManager . statePath , ID . ToString ( ) ) ;
_state = File . ReadAllBytes ( path ) ;
File . Delete ( path ) ;
}
public void DeleteFile ( )
{
if ( ! IsOnDisk )
return ;
string path = Path . Combine ( TasStateManager . statePath , ID . ToString ( ) ) ;
File . Delete ( path ) ;
}
}
2014-07-07 18:40:42 +00:00
/// <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 ) ;
}
}
2015-08-13 18:03:22 +00:00
static private Guid guid = Guid . NewGuid ( ) ;
private SortedList < int , tsmState > States = new SortedList < int , tsmState > ( ) ;
private SortedList < int , SortedList < int , tsmState > > BranchStates = new SortedList < int , SortedList < int , tsmState > > ( ) ;
2015-08-13 13:30:43 +00:00
private int branches = 0 ;
2015-08-13 18:03:22 +00:00
/// <summary>
/// Checks if the state at frame in the given branch (-1 for current) has any duplicates.
/// </summary>
/// <returns>Returns the ID of the branch (-1 for current) of the first match. If no match, returns -2.</returns>
private int stateHasDuplicate ( int frame , int branch )
{
tsmState stateToMatch ;
if ( branch = = - 1 )
stateToMatch = States [ frame ] ;
else
{
stateToMatch = BranchStates [ frame ] [ branch ] ;
if ( States . ContainsKey ( frame ) & & States [ frame ] = = stateToMatch )
return - 1 ;
}
for ( int i = 0 ; i < branches ; i + + )
{
if ( i = = branch )
continue ;
if ( BranchStates . ContainsKey ( frame ) )
{
SortedList < int , tsmState > stateList = BranchStates [ frame ] ;
if ( stateList ! = null & & stateList . ContainsKey ( i ) & & stateList [ i ] = = stateToMatch )
return i ;
}
}
return - 2 ;
}
public static string statePath
2015-03-15 06:26:57 +00:00
{
get
{
2015-08-05 21:36:44 +00:00
var basePath = PathManager . MakeAbsolutePath ( Global . Config . PathEntries [ "Global" , "TAStudio states" ] . Path , null ) ;
return Path . Combine ( basePath , guid . ToString ( ) ) ;
2015-03-15 06:26:57 +00:00
}
}
2014-07-07 18:40:42 +00:00
2015-08-05 22:00:39 +00:00
private bool _isMountedForWrite ;
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
2015-08-05 21:36:44 +00:00
accessed = new List < int > ( ) ;
}
/// <summary>
/// Mounts this instance for write access. Prior to that it's read-only
/// </summary>
public void MountWriteAccess ( )
{
2015-08-05 22:00:39 +00:00
if ( _isMountedForWrite )
return ;
_isMountedForWrite = true ;
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 ) ;
2015-08-05 22:00:39 +00:00
int limit = 0 ;
_expectedStateSize = ( ulong ) Core . SaveStateBinary ( ) . Length ;
if ( _expectedStateSize > 0 )
{
limit = maxStates ;
}
2015-08-13 18:03:22 +00:00
States = new SortedList < int , tsmState > ( limit ) ;
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
StateAccessed ( frame ) ;
2015-08-13 18:03:22 +00:00
return new KeyValuePair < int , byte [ ] > ( frame , States [ frame ] . State ) ;
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 ;
}
2015-08-13 18:03:22 +00:00
return States [ 0 ] . State ;
2014-10-05 12:59:12 +00:00
}
}
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 + + ;
2015-08-06 00:42:01 +00:00
// No need to have two savestates with only lag frames between them:
// zero 05-aug-2015 - changed algorithm to iterate through States (a SortedList) instead of repeatedly call ElementAt (which is slow)
// previously : for (int i = shouldRemove; i < States.Count - 1; i++) if (AllLag(States.ElementAt(i).Key, States.ElementAt(i + 1).Key)) { shouldRemove = i; break; } }
int ctr = 0 ;
2015-08-13 18:03:22 +00:00
KeyValuePair < int , tsmState > ? prior = null ;
2015-08-06 00:42:01 +00:00
foreach ( var kvp in States )
2015-03-15 06:26:57 +00:00
{
2015-08-06 00:42:01 +00:00
ctr + + ;
if ( ctr < shouldRemove )
2015-03-14 16:38:07 +00:00
{
2015-08-06 00:42:01 +00:00
prior = kvp ;
continue ;
2015-03-14 16:38:07 +00:00
}
2015-08-06 00:42:01 +00:00
if ( prior . HasValue )
{
if ( AllLag ( prior . Value . Key , kvp . Key ) )
{
2015-08-13 18:03:22 +00:00
shouldRemove = ctr - 1 ;
2015-08-06 00:42:01 +00:00
break ;
}
}
prior = kvp ;
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 )
{
DiskUsed + = _expectedStateSize ;
Used - = ( ulong ) States [ index ] . Length ;
2015-08-13 18:03:22 +00:00
States [ index ] . MoveToDisk ( ) ;
2015-03-15 06:26:57 +00:00
}
private void MoveStateToMemory ( int index )
{
2015-08-13 18:03:22 +00:00
States [ index ] . MoveToRAM ( ) ;
2015-03-15 06:26:57 +00:00
DiskUsed - = _expectedStateSize ;
2015-08-13 18:03:22 +00:00
Used + = ( ulong ) States [ index ] . Length ;
2015-03-15 06:26:57 +00:00
}
2015-07-19 14:37:53 +00:00
internal void SetState ( int frame , byte [ ] state )
2015-03-15 06:26:57 +00:00
{
2015-08-13 18:03:22 +00:00
MaybeRemoveState ( ) ; // Remove before adding so this state won't be removed.
2015-03-15 06:26:57 +00:00
if ( States . ContainsKey ( frame ) )
{
2015-08-13 18:03:22 +00:00
if ( stateHasDuplicate ( frame , - 1 ) ! = - 2 )
Used + = ( ulong ) state . Length ;
States [ frame ] . State = state ;
2015-03-15 06:26:57 +00:00
}
else
{
Used + = ( ulong ) state . Length ;
2015-08-13 18:03:22 +00:00
States . Add ( frame , new tsmState ( state ) ) ;
2015-03-15 06:26:57 +00:00
}
2015-08-13 18:03:22 +00:00
2015-03-15 06:26:57 +00:00
StateAccessed ( frame ) ;
}
2015-07-23 18:30:25 +00:00
private void RemoveState ( int frame )
2015-03-15 06:26:57 +00:00
{
2015-08-13 18:03:22 +00:00
if ( States [ frame ] . IsOnDisk )
2015-03-15 06:26:57 +00:00
{
2015-08-13 18:03:22 +00:00
DiskUsed - = _expectedStateSize ;
States [ frame ] . DeleteFile ( ) ;
2015-03-15 06:26:57 +00:00
}
else
2015-07-23 18:30:25 +00:00
Used - = ( ulong ) States [ frame ] . Length ;
2015-03-15 06:26:57 +00:00
2015-08-13 18:03:22 +00:00
States . RemoveAt ( States . IndexOfKey ( frame ) ) ;
2015-07-23 18:30:25 +00:00
accessed . Remove ( frame ) ;
2015-03-15 06:26:57 +00:00
}
private void StateAccessed ( int index )
{
2015-07-30 20:14:14 +00:00
if ( index = = 0 & & _movie . StartsFromSavestate )
return ;
2015-03-15 06:26:57 +00:00
bool removed = accessed . Remove ( index ) ;
accessed . Add ( index ) ;
2015-08-13 18:03:22 +00:00
if ( States [ index ] . IsOnDisk )
2015-03-15 06:26:57 +00:00
{
2015-08-13 18:03:22 +00:00
if ( ! States [ accessed [ 0 ] ] . IsOnDisk )
2015-03-16 16:36:00 +00:00
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>
2015-08-05 00:24:49 +00:00
public bool Invalidate ( int frame )
2014-07-07 18:40:42 +00:00
{
2015-08-05 00:24:49 +00:00
bool anyInvalidated = false ;
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 ( ) ;
2015-08-05 00:24:49 +00:00
anyInvalidated = statesToRemove . Any ( ) ;
2014-09-22 22:52:34 +00:00
foreach ( var state in statesToRemove )
2014-09-22 14:44:32 +00:00
{
2015-08-13 18:03:22 +00:00
if ( state . Value . IsOnDisk )
{
DiskUsed - = _expectedStateSize ;
state . Value . DeleteFile ( ) ;
}
2015-03-15 06:26:57 +00:00
else
Used - = ( ulong ) state . Value . Length ;
2015-08-13 18:03:22 +00:00
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
}
2015-08-05 00:24:49 +00:00
return anyInvalidated ;
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 ;
2015-07-30 20:14:14 +00:00
clearDiskStates ( ) ;
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-08-13 18:03:22 +00:00
KeyValuePair < int , tsmState > power = States . FirstOrDefault ( s = > s . Key = = 0 ) ;
2015-07-30 20:14:14 +00:00
StateAccessed ( power . Key ) ;
2015-08-13 18:03:22 +00:00
if ( power . Value . IsOnDisk ) // TODO: Is this needed?
2015-03-16 16:36:00 +00:00
power = States . FirstOrDefault ( s = > s . Key = = 0 ) ;
2015-07-30 20:14:14 +00:00
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
2015-07-30 20:14:14 +00:00
if ( power . Value ! = null ) // savestate-anchored movie?
2014-10-02 23:19:37 +00:00
{
2015-08-13 18:03:22 +00:00
SetState ( 0 , power . Value . State ) ;
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-07-30 20:14:14 +00:00
DiskUsed = 0 ;
clearDiskStates ( ) ;
}
}
private void clearDiskStates ( )
{
2015-08-13 18:03:22 +00:00
if ( Directory . Exists ( statePath ) )
2015-07-30 20:14:14 +00:00
{
2015-08-13 18:03:22 +00:00
Directory . Delete ( statePath , true ) ;
Directory . CreateDirectory ( statePath ) ;
2014-10-02 23:19:37 +00:00
}
2014-07-07 19:32:37 +00:00
}
2014-07-10 22:07:50 +00:00
2015-08-13 18:03:22 +00:00
// TODO: save/load BranchStates
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 ) ;
2015-08-13 18:03:22 +00:00
KeyValuePair < int , tsmState > kvp = States . ElementAt ( i ) ;
2014-08-24 21:53:48 +00:00
bw . Write ( kvp . Key ) ;
bw . Write ( kvp . Value . Length ) ;
2015-08-13 18:03:22 +00:00
bw . Write ( kvp . Value . State ) ;
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 ) ;
2015-08-13 18:03:22 +00:00
if ( States . ElementAt ( index ) . Value . IsOnDisk )
2015-03-17 01:02:38 +00:00
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 ) ;
2015-08-13 18:03:22 +00:00
if ( States . ElementAt ( index ) . Value . IsOnDisk )
2015-03-17 01:02:38 +00:00
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 ;
}
}
2015-08-13 13:30:43 +00:00
#region "Branches"
public void AddBranch ( )
{
2015-08-13 18:03:22 +00:00
foreach ( KeyValuePair < int , tsmState > kvp in States )
2015-08-13 13:30:43 +00:00
{
if ( ! BranchStates . ContainsKey ( kvp . Key ) )
2015-08-13 18:03:22 +00:00
BranchStates . Add ( kvp . Key , new SortedList < int , tsmState > ( ) ) ;
SortedList < int , tsmState > stateList = BranchStates [ kvp . Key ] ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
{
2015-08-13 18:03:22 +00:00
stateList = new SortedList < int , tsmState > ( ) ;
2015-08-13 13:30:43 +00:00
BranchStates [ kvp . Key ] = stateList ;
}
stateList . Add ( branches , kvp . Value ) ;
}
branches + + ;
}
public void RemoveBranch ( int index )
{
2015-08-13 18:03:22 +00:00
foreach ( KeyValuePair < int , SortedList < int , tsmState > > kvp in BranchStates )
2015-08-13 13:30:43 +00:00
{
2015-08-13 18:03:22 +00:00
SortedList < int , tsmState > stateList = kvp . Value ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
continue ;
2015-08-13 18:03:22 +00:00
if ( stateHasDuplicate ( kvp . Key , index ) = = - 2 )
{
if ( stateList [ index ] . IsOnDisk )
DiskUsed - = _expectedStateSize ;
else
Used - = ( ulong ) stateList [ index ] . Length ;
}
2015-08-13 13:30:43 +00:00
stateList . Remove ( index ) ;
if ( stateList . Count = = 0 )
BranchStates [ kvp . Key ] = null ;
}
branches - - ;
}
public void UpdateBranch ( int index )
{
// RemoveBranch
2015-08-13 18:03:22 +00:00
foreach ( KeyValuePair < int , SortedList < int , tsmState > > kvp in BranchStates )
2015-08-13 13:30:43 +00:00
{
2015-08-13 18:03:22 +00:00
SortedList < int , tsmState > stateList = kvp . Value ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
continue ;
2015-08-13 18:03:22 +00:00
if ( stateHasDuplicate ( kvp . Key , index ) = = - 2 )
{
if ( stateList [ index ] . IsOnDisk )
DiskUsed - = _expectedStateSize ;
else
Used - = ( ulong ) stateList [ index ] . Length ;
}
2015-08-13 13:30:43 +00:00
stateList . Remove ( index ) ;
if ( stateList . Count = = 0 )
BranchStates [ kvp . Key ] = null ;
}
// AddBranch
2015-08-13 18:03:22 +00:00
foreach ( KeyValuePair < int , tsmState > kvp in States )
2015-08-13 13:30:43 +00:00
{
2015-08-13 18:03:22 +00:00
if ( ! BranchStates . ContainsKey ( kvp . Key ) )
BranchStates . Add ( kvp . Key , new SortedList < int , tsmState > ( ) ) ;
SortedList < int , tsmState > stateList = BranchStates [ kvp . Key ] ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
{
2015-08-13 18:03:22 +00:00
stateList = new SortedList < int , tsmState > ( ) ;
2015-08-13 13:30:43 +00:00
BranchStates [ kvp . Key ] = stateList ;
}
stateList . Add ( index , kvp . Value ) ;
}
}
public void LoadBranch ( int index )
{
Invalidate ( 0 ) ; // Not a good way of doing it?
2015-08-13 18:03:22 +00:00
foreach ( KeyValuePair < int , SortedList < int , tsmState > > kvp in BranchStates )
2015-08-13 13:30:43 +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 ( index ) )
2015-08-13 18:03:22 +00:00
SetState ( kvp . Key , kvp . Value [ index ] . State ) ;
2015-08-13 13:30:43 +00:00
}
}
#endregion
2014-07-07 18:40:42 +00:00
}
}