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 ;
2018-02-20 16:28:54 +00:00
using BizHawk.Common.NumberExtensions ;
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
2017-05-10 19:19:46 +00:00
private IStatable Core = > Global . Emulator . AsStatable ( ) ;
2014-12-21 23:53:40 +00:00
2015-07-02 18:51:42 +00:00
public Action < int > InvalidateCallback { get ; set ; }
private void CallInvalidateCallback ( int index )
{
2017-05-10 19:19:46 +00:00
InvalidateCallback ? . Invoke ( index ) ;
2015-07-02 18:51:42 +00:00
}
2018-03-08 09:24:35 +00:00
2017-05-19 18:17:07 +00:00
internal NDBDatabase NdbDatabase { get ; set ; }
private Guid _guid = Guid . NewGuid ( ) ;
private SortedList < int , StateManagerState > _states = new SortedList < int , StateManagerState > ( ) ;
2015-08-13 18:03:22 +00:00
2017-05-19 18:17:07 +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 ) ;
2017-05-19 18:17:07 +00:00
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 ;
2014-10-15 15:47:04 +00:00
2018-03-08 09:24:35 +00:00
private StateManagerDecay _decay ;
2018-02-20 16:28:54 +00:00
private ulong _expectedStateSize ;
2018-02-26 22:28:24 +00:00
private int _stateFrequency ;
2017-11-05 19:34:58 +00:00
private readonly int _minFrequency = 1 ;
2018-02-20 16:28:54 +00:00
private readonly int _maxFrequency = 16 ;
private int _maxStates = > ( int ) ( Settings . Cap / _expectedStateSize ) +
( int ) ( ( ulong ) Settings . DiskCapacitymb * 1024 * 1024 / _expectedStateSize ) ;
private int _fileStateGap = > 1 < < Settings . FileStateGap ;
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-10-17 22:47:30 +00:00
Settings = new TasStateManagerSettings ( Global . Config . DefaultTasProjSettings ) ;
2014-08-24 22:50:21 +00:00
2015-08-17 16:32:46 +00:00
if ( _movie . StartsFromSavestate )
2017-05-17 18:18:26 +00:00
{
2015-08-17 16:32:46 +00:00
SetState ( 0 , _movie . BinarySavestate ) ;
2017-05-17 18:18:26 +00:00
}
2018-02-20 16:28:54 +00:00
2018-03-08 09:24:35 +00:00
_decay = new StateManagerDecay ( this ) ;
2015-08-05 21:36:44 +00:00
}
2015-08-14 02:51:51 +00:00
public void Dispose ( )
{
2017-05-09 18:19:55 +00:00
// States and BranchStates don't need cleaning because they would only contain an ndbdatabase entry which was demolished by the below
2017-05-19 18:17:07 +00:00
NdbDatabase ? . Dispose ( ) ;
2015-08-14 02:51:51 +00:00
}
2018-02-26 22:28:24 +00:00
public void UpdateStateFrequency ( )
{
_stateFrequency = NumberExtensions . Clamp (
( ( int ) _expectedStateSize / Settings . MemStateGapDivider / 1024 ) ,
_minFrequency , _maxFrequency ) ;
2018-03-08 09:24:35 +00:00
_decay . UpdateSettings ( _maxStates , _stateFrequency , 4 ) ;
2018-02-26 22:28:24 +00:00
}
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 )
2017-05-10 19:19:46 +00:00
{
2015-08-05 22:00:39 +00:00
return ;
2017-05-10 19:19:46 +00:00
}
2015-08-05 22:00:39 +00:00
int limit = 0 ;
2018-02-20 16:28:54 +00:00
_isMountedForWrite = true ;
2015-08-05 22:00:39 +00:00
_expectedStateSize = ( ulong ) Core . SaveStateBinary ( ) . Length ;
2018-02-26 22:28:24 +00:00
UpdateStateFrequency ( ) ;
2015-08-05 22:00:39 +00:00
if ( _expectedStateSize > 0 )
{
2018-02-20 16:28:54 +00:00
limit = _maxStates ;
2015-08-05 22:00:39 +00:00
}
2017-05-19 18:17:07 +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 )
2017-05-10 19:19:46 +00:00
{
2015-08-14 02:51:51 +00:00
throw new InvalidOperationException ( ) ;
2017-05-10 19:19:46 +00:00
}
2017-05-19 18:17:07 +00:00
NdbDatabase = new NDBDatabase ( StatePath , Settings . DiskCapacitymb * 1024 * 1024 , ( int ) _expectedStateSize ) ;
2014-07-10 22:44:44 +00:00
}
2018-03-08 09:24:35 +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
{
2016-02-20 19:43:24 +00:00
if ( frame = = 0 )
{
2015-12-08 20:38:00 +00:00
return new KeyValuePair < int , byte [ ] > ( 0 , InitialState ) ;
}
2017-05-19 18:17:07 +00:00
if ( _states . ContainsKey ( frame ) )
2014-07-07 18:40:42 +00:00
{
2017-05-19 18:17:07 +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
2014-10-05 12:59:12 +00:00
public byte [ ] InitialState
{
get
{
if ( _movie . StartsFromSavestate )
{
return _movie . BinarySavestate ;
}
2017-05-19 18:17:07 +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
{
2017-05-19 18:17:07 +00:00
bool shouldCapture ;
2015-03-03 06:56:45 +00:00
int frame = Global . Emulator . Frame ;
2018-03-08 09:24:35 +00:00
2015-03-03 06:56:45 +00:00
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 ;
}
2018-03-08 09:24:35 +00:00
else if ( StateIsMarker ( frame ) )
2014-08-30 00:40:53 +00:00
{
shouldCapture = true ; // Markers shoudl always get priority
}
2014-07-07 18:40:42 +00:00
else
{
2018-02-26 22:28:24 +00:00
shouldCapture = frame % _stateFrequency = = 0 ;
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-03-15 06:26:57 +00:00
private void MoveStateToDisk ( int index )
{
2017-05-19 18:17:07 +00:00
Used - = ( ulong ) _states [ index ] . Length ;
_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 )
{
2017-05-19 18:17:07 +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
2017-05-10 19:19:46 +00:00
{
2018-02-20 16:28:54 +00:00
LimitStateCount ( ) ; // Remove before adding so this state won't be removed.
2017-05-10 19:19:46 +00:00
}
2015-09-15 18:47:59 +00:00
2017-05-19 18:17:07 +00:00
if ( _states . ContainsKey ( frame ) )
2015-03-15 06:26:57 +00:00
{
2017-05-19 18:17:07 +00:00
_states [ frame ] . State = state ;
2015-03-15 06:26:57 +00:00
}
else
{
Used + = ( ulong ) state . Length ;
2017-05-19 18:17:07 +00:00
_states . Add ( frame , new StateManagerState ( this , state , frame ) ) ;
2015-03-15 06:26:57 +00:00
}
}
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
2017-05-19 18:17:07 +00:00
return _states . ContainsKey ( frame ) ;
2014-07-10 20:48:43 +00:00
}
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
{
2018-02-20 16:28:54 +00:00
if ( frame = = 0 ) // Never invalidate frame 0
2014-10-25 19:33:28 +00:00
{
frame = 1 ;
}
2018-02-20 16:28:54 +00:00
List < KeyValuePair < int , StateManagerState > > statesToRemove = _states . Where ( s = > s . Key > = frame ) . ToList ( ) ;
2015-08-05 00:24:49 +00:00
anyInvalidated = statesToRemove . Any ( ) ;
2017-05-10 19:19:46 +00:00
foreach ( var state in statesToRemove )
{
2015-08-16 16:27:26 +00:00
RemoveState ( state . Key ) ;
2017-05-10 19:19:46 +00:00
}
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
2018-03-08 09:24:35 +00:00
public bool StateIsMarker ( int frame )
2014-10-02 23:50:50 +00:00
{
2018-02-20 16:28:54 +00:00
if ( frame = = - 1 )
{
return false ;
}
return _movie . Markers . IsMarker ( frame + 1 ) ;
2014-10-02 23:50:50 +00:00
}
2015-09-16 00:03:50 +00:00
2018-03-08 09:24:35 +00:00
public void RemoveState ( int frame )
{
2018-02-20 16:28:54 +00:00
int index = _states . IndexOfKey ( frame ) ;
2015-07-30 20:14:14 +00:00
2018-02-20 16:28:54 +00:00
if ( frame < 1 | | index < 1 )
{
return ;
}
2014-10-02 23:10:36 +00:00
2018-03-27 16:00:59 +00:00
StateManagerState state = _states . Values [ index ] ;
2015-07-30 20:14:14 +00:00
2018-02-20 16:28:54 +00:00
if ( state . IsOnDisk )
{
state . Dispose ( ) ;
}
else
{
Used - = ( ulong ) state . Length ;
2015-07-30 20:14:14 +00:00
}
2015-09-16 00:03:50 +00:00
2018-02-20 16:28:54 +00:00
_states . RemoveAt ( index ) ;
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>
2018-02-26 22:28:24 +00:00
/// Deletes states to follow the state storage size limits.
2018-02-20 16:28:54 +00:00
/// Used after changing the settings too.
2015-08-15 15:07:56 +00:00
/// </summary>
public void LimitStateCount ( )
{
2018-03-08 09:24:35 +00:00
if ( StateCount + 1 > _maxStates | | DiskUsed > ( ulong ) Settings . DiskCapacitymb * 1024 * 1024 )
{
_decay . Trigger ( StateCount + 1 - _maxStates ) ;
2017-05-10 19:19:46 +00:00
}
2015-08-15 15:07:56 +00:00
}
2015-03-17 01:02:38 +00:00
private List < int > ExcludeStates ( )
{
List < int > ret = new List < int > ( ) ;
ulong saveUsed = Used + DiskUsed ;
2016-04-25 15:04:14 +00:00
// respect state gap no matter how small the resulting size will be
// still leave marker states
2017-05-19 18:17:07 +00:00
for ( int i = 1 ; i < _states . Count ; i + + )
2016-04-25 15:04:14 +00:00
{
2018-03-08 09:24:35 +00:00
int frame = GetStateFrameByIndex ( i ) ;
if ( StateIsMarker ( frame ) | | frame % _fileStateGap < _stateFrequency )
2017-05-17 18:18:26 +00:00
{
2016-04-25 15:04:14 +00:00
continue ;
2017-05-17 18:18:26 +00:00
}
2016-04-25 15:04:14 +00:00
ret . Add ( i ) ;
2018-03-08 09:24:35 +00:00
if ( _states . Values [ i ] . IsOnDisk )
2017-05-17 18:18:26 +00:00
{
2016-04-25 15:04:14 +00:00
saveUsed - = _expectedStateSize ;
2017-05-17 18:18:26 +00:00
}
2016-04-25 15:04:14 +00:00
else
2017-05-17 18:18:26 +00:00
{
2018-03-08 09:24:35 +00:00
saveUsed - = ( ulong ) _states . Values [ i ] . Length ;
2017-05-17 18:18:26 +00:00
}
2016-04-25 15:04:14 +00:00
}
// if the size is still too big, exclude states form the beginning
// still leave marker states
int index = 0 ;
2015-03-17 01:02:38 +00:00
while ( saveUsed > ( ulong ) Settings . DiskSaveCapacitymb * 1024 * 1024 )
{
do
{
2018-03-08 09:24:35 +00:00
if ( + + index > = _states . Count )
2017-05-17 18:18:26 +00:00
{
2016-02-20 18:00:05 +00:00
break ;
2017-05-17 18:18:26 +00:00
}
2016-04-24 20:17:17 +00:00
}
2018-03-08 09:24:35 +00:00
while ( StateIsMarker ( GetStateFrameByIndex ( index ) ) ) ;
2016-04-24 20:17:17 +00:00
2017-05-19 18:17:07 +00:00
if ( index > = _states . Count )
2017-05-17 18:18:26 +00:00
{
2016-04-24 20:17:17 +00:00
break ;
2017-05-17 18:18:26 +00:00
}
2016-02-20 18:00:05 +00:00
2015-03-17 01:02:38 +00:00
ret . Add ( index ) ;
2016-04-24 20:17:17 +00:00
2018-03-08 09:24:35 +00:00
if ( _states . Values [ index ] . IsOnDisk )
2017-05-17 18:18:26 +00:00
{
2015-03-17 01:02:38 +00:00
saveUsed - = _expectedStateSize ;
2017-05-17 18:18:26 +00:00
}
2015-03-17 01:02:38 +00:00
else
2017-05-17 18:18:26 +00:00
{
2018-03-08 09:24:35 +00:00
saveUsed - = ( ulong ) _states . Values [ index ] . Length ;
2017-05-17 18:18:26 +00:00
}
2015-03-17 01:02:38 +00:00
}
2016-04-25 15:04:14 +00:00
// if there are enough markers to still be over the limit, remove marker frames
index = 0 ;
2015-03-17 01:02:38 +00:00
while ( saveUsed > ( ulong ) Settings . DiskSaveCapacitymb * 1024 * 1024 )
{
2018-03-08 09:24:35 +00:00
if ( ! ret . Contains ( + + index ) )
2017-05-17 18:18:26 +00:00
{
2016-02-20 18:00:05 +00:00
ret . Add ( index ) ;
2017-05-17 18:18:26 +00:00
}
2018-03-08 09:24:35 +00:00
if ( _states . Values [ index ] . IsOnDisk )
2017-05-17 18:18:26 +00:00
{
2015-03-17 01:02:38 +00:00
saveUsed - = _expectedStateSize ;
2017-05-17 18:18:26 +00:00
}
2015-03-17 01:02:38 +00:00
else
2017-05-17 18:18:26 +00:00
{
2018-03-08 09:24:35 +00:00
saveUsed - = ( ulong ) _states . Values [ index ] . Length ;
2017-05-17 18:18:26 +00:00
}
2015-03-17 01:02:38 +00:00
}
return ret ;
}
2014-07-10 22:07:50 +00:00
2018-02-20 16:28:54 +00:00
public void ClearStateHistory ( )
{
if ( _states . Any ( ) )
{
StateManagerState power = _states . Values . First ( s = > s . Frame = = 0 ) ;
_states . Clear ( ) ;
SetState ( 0 , power . State ) ;
Used = ( ulong ) power . State . Length ;
NdbDatabase ? . Clear ( ) ;
}
}
2018-03-08 09:24:35 +00:00
// Map:
// 4 bytes - total savestate count
// [Foreach state]
// 4 bytes - frame
// 4 bytes - length of savestate
// 0 - n savestate
2015-10-05 19:11:45 +00:00
public void Save ( BinaryWriter bw )
{
List < int > noSave = ExcludeStates ( ) ;
2017-05-19 18:17:07 +00:00
bw . Write ( _states . Count - noSave . Count ) ;
2018-03-08 09:24:35 +00:00
2017-05-19 18:17:07 +00:00
for ( int i = 0 ; i < _states . Count ; i + + )
2015-10-05 19:11:45 +00:00
{
2016-04-25 15:04:14 +00:00
if ( noSave . Contains ( i ) )
2017-05-17 18:18:26 +00:00
{
2015-10-05 19:11:45 +00:00
continue ;
2017-05-17 18:18:26 +00:00
}
2018-03-27 16:00:59 +00:00
bw . Write ( _states . Keys [ i ] ) ;
bw . Write ( _states . Values [ i ] . Length ) ;
bw . Write ( _states . Values [ i ] . State ) ;
2015-10-05 19:11:45 +00:00
}
}
2014-08-24 21:53:48 +00:00
public void Load ( BinaryReader br )
{
2017-05-19 18:17:07 +00:00
_states . Clear ( ) ;
2018-03-08 09:24:35 +00:00
2016-04-23 17:53:50 +00:00
try
{
int nstates = br . ReadInt32 ( ) ;
2018-03-08 09:24:35 +00:00
2016-04-23 17:53:50 +00:00
for ( int i = 0 ; i < nstates ; i + + )
{
int frame = br . ReadInt32 ( ) ;
int len = br . ReadInt32 ( ) ;
byte [ ] data = br . ReadBytes ( len ) ;
2017-05-09 18:19:55 +00:00
2016-04-23 17:53: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
SetState ( frame , data ) ;
}
}
2017-05-17 16:16:55 +00:00
catch ( EndOfStreamException )
{
}
2015-10-24 09:42:22 +00:00
}
2015-10-03 23:20:18 +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
{
2017-05-19 18:17:07 +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
2018-02-25 22:45:26 +00:00
/// <summary>
/// Returns index of the state right above the given frame
/// </summary>
/// <param name="frame"></param>
/// <returns></returns>
public int GetStateIndexByFrame ( int frame )
{
return _states . IndexOfKey ( GetStateClosestToFrame ( frame ) . Key ) ;
}
/// <summary>
/// Returns frame of the state at the given index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public int GetStateFrameByIndex ( int index )
{
2018-03-08 09:24:35 +00:00
// feos: this is called super often by decay
// this method is hundred times faster than _states.ElementAt(index).Key
return _states . Keys [ index ] ;
2018-02-25 22:45:26 +00:00
}
2016-04-17 13:01:09 +00:00
private ulong _used ;
private ulong Used
{
2016-04-17 15:54:55 +00:00
get
{
return _used ;
}
2017-05-10 19:19:46 +00:00
2016-04-17 13:01:09 +00:00
set
{
2017-05-10 19:19:46 +00:00
// TODO: Shouldn't we throw an exception? Debug.Fail only runs in debug mode?
2016-04-17 13:01:09 +00:00
if ( value > 0xf000000000000000 )
2017-05-10 19:19:46 +00:00
{
2016-04-17 13:01:09 +00:00
System . Diagnostics . Debug . Fail ( "ulong Used underfow!" ) ;
2017-05-10 19:19:46 +00:00
}
2016-04-17 13:01:09 +00:00
else
2017-05-10 19:19:46 +00:00
{
2016-04-17 13:01:09 +00:00
_used = value ;
2017-05-10 19:19:46 +00:00
}
2016-04-17 13:01:09 +00:00
}
}
2015-09-16 00:03:50 +00:00
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
{
2017-05-19 18:17:07 +00:00
if ( NdbDatabase = = null )
2017-05-10 19:19:46 +00:00
{
return 0 ;
}
2014-07-10 22:44:44 +00:00
2017-05-19 18:17:07 +00:00
return ( ulong ) NdbDatabase . Consumed ;
2014-07-16 23:04:56 +00:00
}
}
2017-05-19 18:17:07 +00:00
public int StateCount = > _states . Count ;
2017-05-10 19:19:46 +00:00
2014-10-02 22:58:36 +00:00
public bool Any ( )
{
2014-10-25 16:05:11 +00:00
if ( _movie . StartsFromSavestate )
{
2017-05-19 18:17:07 +00:00
return _states . Count > 0 ;
2014-10-25 16:05:11 +00:00
}
2017-05-19 18:17:07 +00:00
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
{
2017-05-19 18:17:07 +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
}
2017-05-19 18:17:07 +00:00
return _states . Last ( ) . Key ;
2014-08-24 21:29:51 +00:00
}
2014-07-13 15:26:50 +00:00
}
2018-03-08 09:24:35 +00:00
public int LastStatedFrame
2014-10-14 13:31:14 +00:00
{
get
{
if ( StateCount > 0 )
{
return LastKey ;
}
return 0 ;
}
}
2015-08-13 13:30:43 +00:00
2018-02-20 16:28:54 +00:00
private int FindState ( StateManagerState s )
2015-08-16 16:27:26 +00:00
{
2017-05-19 18:17:07 +00:00
if ( ! _states . ContainsValue ( s ) )
2015-08-17 16:32:46 +00:00
{
2018-02-20 16:28:54 +00:00
return - 1 ;
2017-05-10 19:19:46 +00:00
}
2017-03-14 19:49:55 +00:00
2018-02-20 16:28:54 +00:00
return s . Frame ;
2015-08-13 13:30:43 +00:00
}
2014-07-07 18:40:42 +00:00
}
}