2014-07-10 22:07:50 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2014-07-07 18:40:42 +00:00
using System.Linq ;
2015-08-17 16:32:46 +00:00
using System.Drawing ;
2014-07-07 18:40:42 +00:00
2015-08-14 02:54:38 +00:00
using BizHawk.Common ;
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>
2015-08-14 02:51:51 +00:00
public class TasStateManager : IDisposable
2014-07-07 18:40:42 +00:00
{
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-09-16 00:03:50 +00:00
private List < StateManagerState > lowPriorityStates = new List < StateManagerState > ( ) ;
2015-08-14 02:51:51 +00:00
internal NDBDatabase ndbdatabase ;
private Guid guid = Guid . NewGuid ( ) ;
2015-09-16 00:03:50 +00:00
private SortedList < int , StateManagerState > States = new SortedList < int , StateManagerState > ( ) ;
2015-08-13 18:03:22 +00:00
2015-08-15 15:07:56 +00:00
private 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 ;
2015-09-16 00:03:50 +00:00
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-09-16 00:03:50 +00:00
{
get { return ( int ) ( Settings . Cap / _expectedStateSize ) + ( int ) ( ( ulong ) Settings . DiskCapacitymb * 1024 * 1024 / _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-09-16 00:03:50 +00:00
accessed = new List < StateManagerState > ( ) ;
2015-08-17 16:32:46 +00:00
if ( _movie . StartsFromSavestate )
SetState ( 0 , _movie . BinarySavestate ) ;
2015-08-05 21:36:44 +00:00
}
2015-08-14 02:51:51 +00:00
public void Dispose ( )
{
if ( ndbdatabase ! = null )
ndbdatabase . Dispose ( ) ;
//States and BranchStates don't need cleaning because they would only contain an ndbdatabase entry which was demolished by the above
}
2015-08-05 21:36:44 +00:00
/// <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 ;
int limit = 0 ;
_expectedStateSize = ( ulong ) Core . SaveStateBinary ( ) . Length ;
if ( _expectedStateSize > 0 )
{
limit = maxStates ;
}
2015-09-16 00:03:50 +00:00
States = new SortedList < int , StateManagerState > ( limit ) ;
2015-08-14 02:51:51 +00:00
2015-08-16 16:27:26 +00:00
if ( _expectedStateSize > int . MaxValue )
2015-08-14 02:51:51 +00:00
throw new InvalidOperationException ( ) ;
ndbdatabase = new NDBDatabase ( statePath , Settings . DiskCapacitymb * 1024 * 1024 , ( int ) _expectedStateSize ) ;
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
{
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-09-16 00:03:50 +00:00
private List < StateManagerState > 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-09-16 16:40:50 +00:00
SetState ( frame , ( byte [ ] ) Core . SaveStateBinary ( ) . Clone ( ) , skipRemoval : false ) ;
2014-07-07 18:40:42 +00:00
}
}
2015-08-17 16:32:46 +00:00
private void MaybeRemoveStates ( )
2015-03-03 06:56:45 +00:00
{
2015-08-17 16:32:46 +00:00
// Loop, because removing a state that has a duplicate won't save any space
while ( Used > Settings . Cap | | DiskUsed > ( ulong ) Settings . DiskCapacitymb * 1024 * 1024 )
2015-03-14 16:38:07 +00:00
{
2015-08-17 16:32:46 +00:00
Point shouldRemove = StateToRemove ( ) ;
RemoveState ( shouldRemove . X , shouldRemove . Y ) ;
2015-03-15 06:26:57 +00:00
}
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 ;
2015-08-16 16:27:26 +00:00
do { lastMemState + + ; } while ( States [ accessed [ lastMemState ] . Frame ] = = null ) ;
MoveStateToDisk ( accessed [ lastMemState ] . Frame ) ;
2015-03-15 06:26:57 +00:00
}
}
2015-09-16 00:03:50 +00:00
2015-08-17 16:32:46 +00:00
/// <summary>
/// X is the frame of the state, Y is the branch (-1 for current).
/// </summary>
private Point StateToRemove ( )
2015-03-15 06:26:57 +00:00
{
2015-08-17 16:32:46 +00:00
int markerSkips = maxStates / 2 ;
2015-03-14 16:38:07 +00:00
2015-08-17 16:32:46 +00:00
// X is frame, Y is branch
Point shouldRemove = new Point ( - 1 , - 1 ) ;
int i = 0 ;
// lowPrioritySates (e.g. states with only lag frames between them)
2015-03-15 06:26:57 +00:00
do
{
2015-08-17 16:32:46 +00:00
if ( lowPriorityStates . Count > i )
shouldRemove = findState ( lowPriorityStates [ i ] ) ;
else
break ;
2015-03-14 16:38:07 +00:00
2015-03-15 06:26:57 +00:00
// Keep marker states
markerSkips - - ;
if ( markerSkips < 0 )
2015-08-17 16:32:46 +00:00
shouldRemove . X = - 1 ;
i + + ;
} while ( StateIsMarker ( shouldRemove . X , shouldRemove . Y ) & & markerSkips > - 1 | | shouldRemove . X = = 0 ) ;
// by last accessed
markerSkips = maxStates / 2 ;
if ( shouldRemove . X < 1 )
{
i = 0 ;
do
{
2015-09-15 18:47:59 +00:00
if ( accessed . Count > i )
shouldRemove = findState ( accessed [ i ] ) ;
else
break ;
2015-08-17 16:32:46 +00:00
// Keep marker states
markerSkips - - ;
if ( markerSkips < 0 )
shouldRemove . X = - 1 ;
i + + ;
} while ( StateIsMarker ( shouldRemove . X , shouldRemove . Y ) & & markerSkips > - 1 | | shouldRemove . X = = 0 ) ;
}
if ( shouldRemove . X < 1 ) // only found marker states above
{
if ( BranchStates . Any ( ) )
{
var kvp = BranchStates . ElementAt ( 1 ) ;
shouldRemove . X = kvp . Key ;
shouldRemove . Y = kvp . Value . Keys [ 0 ] ;
}
else
{
2015-09-16 00:03:50 +00:00
StateManagerState s = States . Values [ 1 ] ;
2015-08-17 16:32:46 +00:00
shouldRemove . X = s . Frame ;
shouldRemove . Y = - 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
}
2015-09-16 00:03:50 +00:00
2015-08-17 16:32:46 +00:00
private bool StateIsMarker ( int frame , int branch )
{
if ( frame = = - 1 )
return false ;
if ( branch = = - 1 )
return _movie . Markers . IsMarker ( States [ frame ] . Frame + 1 ) ;
else
{
if ( _movie . GetBranch ( branch ) . Markers = = null )
return _movie . Markers . IsMarker ( States [ frame ] . Frame + 1 ) ;
else
return _movie . GetBranch ( branch ) . Markers . Any ( m = > m . Frame + 1 = = frame ) ;
}
}
2015-09-16 00:03:50 +00:00
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 )
{
Used - = ( ulong ) States [ index ] . Length ;
2015-08-13 18:03:22 +00:00
States [ index ] . MoveToDisk ( ) ;
2015-03-15 06:26:57 +00:00
}
2015-09-16 00:03:50 +00:00
2015-03-15 06:26:57 +00:00
private void MoveStateToMemory ( int index )
{
2015-08-13 18:03:22 +00:00
States [ index ] . MoveToRAM ( ) ;
Used + = ( ulong ) States [ index ] . Length ;
2015-03-15 06:26:57 +00:00
}
2015-07-19 14:37:53 +00:00
2015-09-16 16:40:50 +00:00
internal void SetState ( int frame , byte [ ] state , bool skipRemoval = true )
2015-03-15 06:26:57 +00:00
{
2015-09-16 16:40:50 +00:00
if ( ! skipRemoval ) // skipRemoval: false only when capturing new states
2015-09-15 18:47:59 +00:00
MaybeRemoveStates ( ) ; // 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-09-16 00:03:50 +00:00
States . Add ( frame , new StateManagerState ( this , state , frame ) ) ;
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-08-17 14:13:44 +00:00
int i = States . IndexOfKey ( frame ) ;
if ( i > 0 & & AllLag ( States . Keys [ i - 1 ] , States . Keys [ i ] ) )
{
lowPriorityStates . Add ( States [ frame ] ) ;
}
2015-03-15 06:26:57 +00:00
}
2015-09-16 00:03:50 +00:00
2015-08-16 16:27:26 +00:00
private void RemoveState ( int frame , int branch = - 1 )
2015-03-15 06:26:57 +00:00
{
2015-08-16 16:27:26 +00:00
if ( branch = = - 1 )
accessed . Remove ( States [ frame ] ) ;
else
accessed . Remove ( BranchStates [ frame ] [ branch ] ) ;
2015-09-16 00:03:50 +00:00
StateManagerState state ;
2015-08-17 16:32:46 +00:00
bool hasDuplicate = stateHasDuplicate ( frame , branch ) ! = - 2 ;
2015-08-16 16:27:26 +00:00
if ( branch = = - 1 )
2015-03-15 06:26:57 +00:00
{
2015-08-17 14:13:44 +00:00
state = States [ frame ] ;
2015-08-16 16:27:26 +00:00
if ( States [ frame ] . IsOnDisk )
States [ frame ] . Dispose ( ) ;
else
Used - = ( ulong ) States [ frame ] . Length ;
States . RemoveAt ( States . IndexOfKey ( frame ) ) ;
2015-03-15 06:26:57 +00:00
}
else
2015-08-16 16:27:26 +00:00
{
2015-08-17 14:13:44 +00:00
state = BranchStates [ frame ] [ branch ] ;
2015-08-16 16:27:26 +00:00
if ( BranchStates [ frame ] [ branch ] . IsOnDisk )
BranchStates [ frame ] [ branch ] . Dispose ( ) ;
2015-08-17 14:13:44 +00:00
else
Used - = ( ulong ) BranchStates [ frame ] [ branch ] . Length ;
2015-08-16 16:27:26 +00:00
BranchStates [ frame ] . RemoveAt ( BranchStates [ frame ] . IndexOfKey ( branch ) ) ;
2015-09-16 20:18:44 +00:00
if ( BranchStates [ frame ] . Count = = 0 )
BranchStates . Remove ( frame ) ;
2015-08-16 16:27:26 +00:00
}
2015-08-17 16:32:46 +00:00
if ( ! hasDuplicate )
lowPriorityStates . Remove ( state ) ;
2015-03-15 06:26:57 +00:00
}
2015-09-16 00:03:50 +00:00
2015-08-16 16:27:26 +00:00
private void StateAccessed ( int frame )
2015-03-15 06:26:57 +00:00
{
2015-08-16 16:27:26 +00:00
if ( frame = = 0 & & _movie . StartsFromSavestate )
2015-07-30 20:14:14 +00:00
return ;
2015-09-16 00:03:50 +00:00
StateManagerState state = States [ frame ] ;
2015-08-16 16:27:26 +00:00
bool removed = accessed . Remove ( state ) ;
accessed . Add ( state ) ;
2015-03-15 06:26:57 +00:00
2015-08-16 16:27:26 +00:00
if ( States [ frame ] . IsOnDisk )
2015-03-15 06:26:57 +00:00
{
2015-08-16 16:27:26 +00:00
if ( ! States [ accessed [ 0 ] . Frame ] . IsOnDisk )
MoveStateToDisk ( accessed [ 0 ] . Frame ) ;
MoveStateToMemory ( frame ) ;
2015-03-15 06:26:57 +00:00
}
2015-08-16 16:27:26 +00:00
if ( ! removed & & accessed . Count > maxStates )
2015-03-15 06:26:57 +00:00
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 ;
}
2015-09-16 00:03:50 +00:00
List < KeyValuePair < int , StateManagerState > > statesToRemove =
2015-08-16 16:27:26 +00:00
States . Where ( x = > x . Key > = frame ) . ToList ( ) ;
2015-08-05 00:24:49 +00:00
anyInvalidated = statesToRemove . Any ( ) ;
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , StateManagerState > state in statesToRemove )
2015-08-16 16:27:26 +00:00
RemoveState ( state . Key ) ;
2015-08-13 18:03:22 +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-07-30 20:14:14 +00:00
clearDiskStates ( ) ;
2014-10-02 23:50:50 +00:00
}
2015-09-16 00:03: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-09-16 00:03:50 +00:00
StateManagerState power = States . Values . FirstOrDefault ( s = > s . Frame = = 0 ) ;
2015-08-17 16:32:46 +00:00
StateAccessed ( power . Frame ) ;
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-08-17 16:32:46 +00:00
SetState ( 0 , power . State ) ;
Used = ( ulong ) power . State . Length ;
2015-07-30 20:14:14 +00:00
clearDiskStates ( ) ;
}
}
2015-09-16 00:03:50 +00:00
2015-07-30 20:14:14 +00:00
private void clearDiskStates ( )
{
2015-08-14 02:51:51 +00:00
if ( ndbdatabase ! = null )
ndbdatabase . Clear ( ) ;
2014-07-07 19:32:37 +00:00
}
2014-07-10 22:07:50 +00:00
2015-08-15 15:07:56 +00:00
/// <summary>
/// Deletes/moves states to follow the state storage size limits.
/// Used after changing the settings.
/// </summary>
public void LimitStateCount ( )
{
2015-08-17 16:32:46 +00:00
while ( Used + DiskUsed > Settings . CapTotal )
{
Point s = StateToRemove ( ) ;
RemoveState ( s . X , s . Y ) ;
}
2015-08-16 16:27:26 +00:00
2015-08-17 16:32:46 +00:00
int index = - 1 ;
while ( DiskUsed > ( ulong ) Settings . DiskCapacitymb * 1024 uL * 1024 uL )
{
do { index + + ; } while ( ! accessed [ index ] . IsOnDisk ) ;
accessed [ index ] . MoveToRAM ( ) ;
}
2015-08-16 16:27:26 +00:00
2015-08-17 16:32:46 +00:00
if ( Used > Settings . Cap )
MaybeRemoveStates ( ) ;
2015-08-15 15:07:56 +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-09-16 00:03:50 +00:00
KeyValuePair < int , StateManagerState > 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
}
2015-10-03 23:20:18 +00:00
bw . Write ( currentBranch ) ;
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 ) ;
}
}
2014-08-24 21:53:48 +00:00
}
2015-09-16 00:03:50 +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 ) ;
2015-09-16 16:40:50 +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
2015-03-16 16:36:00 +00:00
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
//}
2015-10-03 23:20:18 +00:00
currentBranch = br . ReadInt32 ( ) ;
int c = br . ReadInt32 ( ) ;
BranchStates = new SortedList < int , SortedList < int , StateManagerState > > ( c ) ;
while ( c > 0 )
{
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 - - ;
}
BranchStates . Add ( key , list ) ;
c - - ;
}
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-09-16 00:03:50 +00:00
private ulong Used { get ; set ; }
2015-03-15 06:26:57 +00:00
private ulong DiskUsed
2014-07-10 22:44:44 +00:00
{
2015-08-14 02:51:51 +00:00
get
{
if ( ndbdatabase = = null ) return 0 ;
else return ( ulong ) ndbdatabase . Consumed ;
}
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"
2015-09-16 00:03:50 +00:00
private SortedList < int , SortedList < int , StateManagerState > > BranchStates = new SortedList < int , SortedList < int , StateManagerState > > ( ) ;
2015-08-16 16:27:26 +00:00
private int currentBranch = - 1 ;
/// <summary>
/// Checks if the state at frame in the given branch (-1 for current) has any duplicates.
/// </summary>
2015-09-16 20:18:44 +00:00
/// <returns>Index of the branch (-1 for current) of the first match. If no match, returns -2.</returns>
private int stateHasDuplicate ( int frame , int branchHash )
2015-08-16 16:27:26 +00:00
{
2015-09-16 00:03:50 +00:00
StateManagerState stateToMatch ;
2015-09-16 20:18:44 +00:00
// figure out what state we're checking
if ( branchHash = = - 1 )
2015-08-16 16:27:26 +00:00
stateToMatch = States [ frame ] ;
else
{
2015-09-16 20:18:44 +00:00
if ( ! BranchStates [ frame ] . ContainsKey ( branchHash ) )
2015-09-06 17:23:11 +00:00
return - 2 ;
2015-09-16 20:18:44 +00:00
stateToMatch = BranchStates [ frame ] [ branchHash ] ;
2015-08-16 16:27:26 +00:00
if ( States . ContainsKey ( frame ) & & States [ frame ] = = stateToMatch )
return - 1 ;
}
2015-09-16 20:18:44 +00:00
// there's no state for that frame at all
2015-08-17 16:32:46 +00:00
if ( ! BranchStates . ContainsKey ( frame ) )
return - 2 ;
2015-09-16 20:18:44 +00:00
// find the branches whose state for that frame is the same
SortedList < int , StateManagerState > stateList = BranchStates [ frame ] ;
2015-09-13 20:36:10 +00:00
for ( int i = 0 ; i < _movie . BranchCount ; i + + )
2015-08-16 16:27:26 +00:00
{
2015-10-04 14:43:36 +00:00
if ( i = = _movie . BranchIndexByHash ( branchHash ) )
2015-08-16 16:27:26 +00:00
continue ;
2015-08-17 16:32:46 +00:00
if ( stateList ! = null & & stateList . ContainsKey ( i ) & & stateList [ i ] = = stateToMatch )
return i ;
2015-08-16 16:27:26 +00:00
}
return - 2 ;
}
2015-09-16 00:03:50 +00:00
private Point findState ( StateManagerState s )
2015-08-17 16:32:46 +00:00
{
Point ret = new Point ( 0 , - 1 ) ;
ret . X = s . Frame ;
if ( ! States . ContainsValue ( s ) )
{
if ( BranchStates . ContainsKey ( s . Frame ) )
ret . Y = BranchStates [ s . Frame ] . Values . IndexOf ( s ) ;
if ( ret . Y = = - 1 )
return new Point ( - 1 , - 2 ) ;
}
return ret ;
}
2015-08-16 16:27:26 +00:00
2015-08-13 13:30:43 +00:00
public void AddBranch ( )
{
2015-09-16 16:40:50 +00:00
int branchHash = _movie . BranchHashByIndex ( _movie . BranchCount - 1 ) ;
2015-09-13 20:36:10 +00:00
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , StateManagerState > kvp in States )
2015-08-13 13:30:43 +00:00
{
if ( ! BranchStates . ContainsKey ( kvp . Key ) )
2015-09-16 00:03:50 +00:00
BranchStates . Add ( kvp . Key , new SortedList < int , StateManagerState > ( ) ) ;
2015-09-16 16:40:50 +00:00
2015-09-16 00:03:50 +00:00
SortedList < int , StateManagerState > stateList = BranchStates [ kvp . Key ] ;
2015-09-16 16:40:50 +00:00
2015-09-13 20:36:10 +00:00
if ( stateList = = null ) // when does this happen?
2015-08-13 13:30:43 +00:00
{
2015-09-16 00:03:50 +00:00
stateList = new SortedList < int , StateManagerState > ( ) ;
2015-08-13 13:30:43 +00:00
BranchStates [ kvp . Key ] = stateList ;
}
2015-09-16 16:40:50 +00:00
stateList . Add ( branchHash , kvp . Value ) ;
2015-09-16 18:44:42 +00:00
Used + = ( ulong ) stateList [ branchHash ] . Length ;
2015-08-13 13:30:43 +00:00
}
2015-09-13 20:36:10 +00:00
currentBranch = _movie . BranchCount ;
2015-08-13 13:30:43 +00:00
}
public void RemoveBranch ( int index )
{
2015-09-16 16:40:50 +00:00
int branchHash = _movie . BranchHashByIndex ( index ) ;
2015-09-13 20:36:10 +00:00
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , SortedList < int , StateManagerState > > kvp in BranchStates . ToList ( ) )
2015-08-13 13:30:43 +00:00
{
2015-09-16 00:03:50 +00:00
SortedList < int , StateManagerState > stateList = kvp . Value ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
continue ;
2015-08-13 18:03:22 +00:00
2015-09-16 16:40:50 +00:00
if ( stateHasDuplicate ( kvp . Key , branchHash ) = = - 2 )
2015-08-13 18:03:22 +00:00
{
2015-09-16 16:40:50 +00:00
if ( stateList . ContainsKey ( branchHash ) )
2015-09-06 17:23:11 +00:00
{
2015-09-16 16:40:50 +00:00
if ( stateList [ branchHash ] . IsOnDisk )
2015-09-06 17:23:11 +00:00
{ }
else
2015-09-16 16:40:50 +00:00
Used - = ( ulong ) stateList [ branchHash ] . Length ;
2015-09-06 17:23:11 +00:00
}
2015-08-13 18:03:22 +00:00
}
2015-09-16 16:40:50 +00:00
stateList . Remove ( branchHash ) ;
2015-08-13 13:30:43 +00:00
if ( stateList . Count = = 0 )
2015-09-14 17:45:27 +00:00
BranchStates . Remove ( kvp . Key ) ;
2015-08-13 13:30:43 +00:00
}
2015-09-13 20:36:10 +00:00
if ( currentBranch > index )
currentBranch - - ;
else if ( currentBranch = = index )
2015-08-16 16:27:26 +00:00
currentBranch = - 1 ;
2015-08-13 13:30:43 +00:00
}
public void UpdateBranch ( int index )
{
2015-09-16 16:40:50 +00:00
int branchHash = _movie . BranchHashByIndex ( index ) ;
2015-09-13 20:36:10 +00:00
2015-08-13 13:30:43 +00:00
// RemoveBranch
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , SortedList < int , StateManagerState > > kvp in BranchStates . ToList ( ) )
2015-08-13 13:30:43 +00:00
{
2015-09-16 00:03:50 +00:00
SortedList < int , StateManagerState > stateList = kvp . Value ;
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
continue ;
2015-08-13 18:03:22 +00:00
2015-09-16 16:40:50 +00:00
if ( stateHasDuplicate ( kvp . Key , branchHash ) = = - 2 )
2015-08-13 18:03:22 +00:00
{
2015-09-16 16:40:50 +00:00
if ( stateList . ContainsKey ( branchHash ) )
2015-09-06 17:23:11 +00:00
{
2015-09-16 16:40:50 +00:00
if ( stateList [ branchHash ] . IsOnDisk )
2015-09-06 17:23:11 +00:00
{ }
else
2015-09-16 16:40:50 +00:00
Used - = ( ulong ) stateList [ branchHash ] . Length ;
2015-09-06 17:23:11 +00:00
}
2015-08-13 18:03:22 +00:00
}
2015-09-16 16:40:50 +00:00
stateList . Remove ( branchHash ) ;
2015-08-13 13:30:43 +00:00
if ( stateList . Count = = 0 )
2015-09-16 18:44:42 +00:00
BranchStates . Remove ( kvp . Key ) ;
2015-08-13 13:30:43 +00:00
}
// AddBranch
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , StateManagerState > kvp in States )
2015-08-13 13:30:43 +00:00
{
2015-08-13 18:03:22 +00:00
if ( ! BranchStates . ContainsKey ( kvp . Key ) )
2015-09-16 00:03:50 +00:00
BranchStates . Add ( kvp . Key , new SortedList < int , StateManagerState > ( ) ) ;
2015-09-16 16:40:50 +00:00
2015-09-16 00:03:50 +00:00
SortedList < int , StateManagerState > stateList = BranchStates [ kvp . Key ] ;
2015-09-16 16:40:50 +00:00
2015-08-13 13:30:43 +00:00
if ( stateList = = null )
{
2015-09-16 00:03:50 +00:00
stateList = new SortedList < int , StateManagerState > ( ) ;
2015-08-13 13:30:43 +00:00
BranchStates [ kvp . Key ] = stateList ;
}
2015-09-16 16:40:50 +00:00
stateList . Add ( branchHash , kvp . Value ) ;
2015-09-16 18:44:42 +00:00
Used + = ( ulong ) stateList [ branchHash ] . Length ;
2015-08-13 13:30:43 +00:00
}
2015-08-16 16:27:26 +00:00
currentBranch = index ;
2015-08-13 13:30:43 +00:00
}
public void LoadBranch ( int index )
{
2015-09-16 16:40:50 +00:00
int branchHash = _movie . BranchHashByIndex ( index ) ;
2015-09-23 16:42:41 +00:00
//Invalidate(0); // Not a good way of doing it?
2015-09-16 16:40:50 +00:00
2015-09-16 00:03:50 +00:00
foreach ( KeyValuePair < int , SortedList < int , StateManagerState > > 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.
2015-09-16 16:40:50 +00:00
if ( kvp . Value . ContainsKey ( branchHash ) )
SetState ( kvp . Key , kvp . Value [ branchHash ] . State ) ;
2015-08-13 13:30:43 +00:00
}
2015-08-16 16:27:26 +00:00
currentBranch = index ;
2015-08-13 13:30:43 +00:00
}
#endregion
2014-07-07 18:40:42 +00:00
}
}