2013-07-27 21:53:47 +00:00
using System ;
using System.IO ;
2013-07-28 22:47:37 +00:00
using System.Threading ;
2013-07-27 21:53:47 +00:00
using System.Collections.Generic ;
2013-07-28 22:47:37 +00:00
using System.Collections.Concurrent ;
2011-01-11 02:55:51 +00:00
2013-10-25 00:57:23 +00:00
using BizHawk.Client.Common ;
2013-11-03 03:54:37 +00:00
namespace BizHawk.Client.EmuHawk
2011-01-11 02:55:51 +00:00
{
2011-06-12 00:14:19 +00:00
public partial class MainForm
{
2013-08-24 21:40:42 +00:00
public StreamBlobDatabase RewindBuf ; // = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, Global.Config.Rewind_BufferSize * (long)1024 * (long)1024);
2013-07-28 22:47:37 +00:00
private RewindThreader RewindThread ;
2013-07-27 23:13:27 +00:00
2013-04-16 00:19:31 +00:00
private byte [ ] LastState ;
private bool RewindImpossible ;
2013-07-20 14:38:09 +00:00
private int RewindFrequency = 1 ;
2013-07-27 23:13:27 +00:00
private bool RewindDeltaEnable = false ;
2013-07-20 14:38:09 +00:00
2013-07-30 01:59:28 +00:00
public float Rewind_FullnessRatio { get { return RewindBuf . FullnessRatio ; } }
2013-08-24 21:40:42 +00:00
public int Rewind_Count { get { return RewindBuf ! = null ? RewindBuf . Count : 0 ; } }
public long Rewind_Size { get { return RewindBuf ! = null ? RewindBuf . Size : 0 ; } }
2013-07-27 21:53:47 +00:00
/// <summary>
/// Manages a ring buffer of storage which can continually chow its own tail to keep growing forward.
/// Probably only useful for the rewind buffer, so I didnt put it in another file
/// </summary>
2013-08-24 21:40:42 +00:00
public class StreamBlobDatabase : IDisposable
2011-06-12 00:14:19 +00:00
{
2013-07-27 21:53:47 +00:00
public void Dispose ( )
{
mStream . Dispose ( ) ;
mStream = null ;
2013-08-24 23:00:33 +00:00
if ( mAllocatedBuffer ! = null )
mBufferManage ( mAllocatedBuffer , 0 , false ) ;
2013-07-27 21:53:47 +00:00
}
2013-08-24 23:00:33 +00:00
Func < byte [ ] , long , bool , byte [ ] > mBufferManage ;
byte [ ] mAllocatedBuffer ;
2013-07-27 21:53:47 +00:00
2013-08-24 23:00:33 +00:00
public StreamBlobDatabase ( bool onDisk , long capacity , Func < byte [ ] , long , bool , byte [ ] > BufferManage )
2013-07-27 21:53:47 +00:00
{
2013-08-24 23:00:33 +00:00
this . mBufferManage = BufferManage ;
2013-07-27 21:53:47 +00:00
mCapacity = capacity ;
if ( onDisk )
{
2013-07-27 22:02:43 +00:00
var path = Path . Combine ( System . IO . Path . GetTempPath ( ) , "bizhawk.rewindbuf-pid" + System . Diagnostics . Process . GetCurrentProcess ( ) . Id + "-" + Guid . NewGuid ( ) . ToString ( ) ) ;
2013-07-27 21:53:47 +00:00
//I checked the DeleteOnClose operation to make sure it cleans up when the process is aborted, and it seems to.
//Otherwise we would have a more complex tempfile management problem here.
2013-07-27 22:39:12 +00:00
//4KB buffer chosen due to similarity to .net defaults, and fear of anything larger making hiccups for small systems (we could try asyncing this stuff though...)
mStream = new FileStream ( path , FileMode . Create , System . Security . AccessControl . FileSystemRights . FullControl , FileShare . None , 4 * 1024 , FileOptions . DeleteOnClose ) ;
2013-07-27 21:53:47 +00:00
}
else
{
2013-08-24 23:00:33 +00:00
mAllocatedBuffer = mBufferManage ( null , capacity , true ) ;
mStream = new MemoryStream ( mAllocatedBuffer ) ;
2013-07-27 21:53:47 +00:00
}
}
2011-09-19 00:39:28 +00:00
2013-07-27 21:53:47 +00:00
public class ListItem
2011-06-12 00:14:19 +00:00
{
2013-07-27 21:53:47 +00:00
public ListItem ( int _timestamp , long _index , int _length ) { this . timestamp = _timestamp ; this . index = _index ; this . length = _length ; }
public int timestamp ;
public long index ;
public int length ;
public long endExclusive { get { return index + length ; } }
2011-06-12 00:14:19 +00:00
}
2011-01-11 02:55:51 +00:00
2013-07-27 21:53:47 +00:00
Stream mStream ;
LinkedList < ListItem > mBookmarks = new LinkedList < ListItem > ( ) ;
LinkedListNode < ListItem > mHead , mTail ;
2013-07-27 22:39:12 +00:00
long mCapacity , mSize ;
2013-07-27 21:53:47 +00:00
2013-07-27 22:39:12 +00:00
/// <summary>
/// Returns the amount of the buffer that's used
/// </summary>
public long Size { get { return mSize ; } }
/// <summary>
/// Gets the current fullness ratio (Size/Capacity). Note that this wont reach 100% due to the buffer size not being a multiple of a fixed savestate size.
/// </summary>
public float FullnessRatio { get { return ( float ) ( ( double ) Size / ( double ) mCapacity ) ; } }
/// <summary>
/// the number of frames stored here
/// </summary>
2013-07-27 21:53:47 +00:00
public int Count { get { return mBookmarks . Count ; } }
2013-07-27 22:39:12 +00:00
/// <summary>
/// The underlying stream to
/// </summary>
2013-07-27 21:53:47 +00:00
public Stream Stream { get { return mStream ; } }
public void Clear ( )
2013-07-20 14:38:09 +00:00
{
2013-07-27 21:53:47 +00:00
mHead = mTail = null ;
2013-07-27 22:39:12 +00:00
mSize = 0 ;
2013-07-27 21:53:47 +00:00
mBookmarks . Clear ( ) ;
2013-07-20 14:38:09 +00:00
}
2011-01-11 02:55:51 +00:00
2013-07-27 21:53:47 +00:00
/// <summary>
2013-07-27 23:02:26 +00:00
/// The push and pop semantics are for historical reasons and not resemblence to normal definitions
2013-07-27 21:53:47 +00:00
/// </summary>
2013-07-27 23:02:26 +00:00
public void Push ( ArraySegment < byte > seg )
2013-07-27 21:53:47 +00:00
{
2013-07-27 23:02:26 +00:00
var buf = seg . Array ;
int len = seg . Count ;
2013-07-27 22:43:08 +00:00
long offset = Enqueue ( 0 , len ) ;
2013-07-27 21:53:47 +00:00
mStream . Position = offset ;
2013-07-27 23:02:26 +00:00
mStream . Write ( buf , seg . Offset , len ) ;
2013-07-27 21:53:47 +00:00
}
2013-07-21 20:39:11 +00:00
2013-07-27 23:02:26 +00:00
/// <summary>
/// The push and pop semantics are for historical reasons and not resemblence to normal definitions
/// </summary>
2013-07-27 21:53:47 +00:00
public MemoryStream PopMemoryStream ( )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
var item = Pop ( ) ;
var buf = new byte [ item . length ] ;
mStream . Position = item . index ;
mStream . Read ( buf , 0 , item . length ) ;
var ret = new MemoryStream ( buf , 0 , item . length , false , true ) ;
return ret ;
}
public long Enqueue ( int timestamp , int amount )
{
2013-07-27 22:39:12 +00:00
mSize + = amount ;
2013-07-27 21:53:47 +00:00
if ( mHead = = null )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
mTail = mHead = mBookmarks . AddFirst ( new ListItem ( timestamp , 0 , amount ) ) ;
return 0 ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
long target = mHead . Value . endExclusive + amount ;
if ( mTail ! = null & & target < = mTail . Value . index )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
//theres room to add a new head before the tail
mHead = mBookmarks . AddAfter ( mHead , new ListItem ( timestamp , mHead . Value . endExclusive , amount ) ) ;
goto CLEANUP ;
}
2013-07-21 20:39:11 +00:00
2013-07-27 21:53:47 +00:00
//maybe the tail is earlier than the head
if ( mTail . Value . index < mHead . Value . index )
{
if ( target < = mCapacity )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
//theres room to add a new head before the end of capacity
mHead = mBookmarks . AddAfter ( mHead , new ListItem ( timestamp , mHead . Value . endExclusive , amount ) ) ;
goto CLEANUP ;
2013-07-21 20:39:11 +00:00
}
}
else
{
2013-07-27 21:53:47 +00:00
//nope, tail is after head. we'll have to clobber from the tail
mHead = mBookmarks . AddAfter ( mHead , new ListItem ( timestamp , mHead . Value . endExclusive , amount ) ) ;
goto CLEANUP ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
//no room before the tail, or before capacity. head needs to wrap around.
mHead = mBookmarks . AddAfter ( mHead , new ListItem ( timestamp , 0 , amount ) ) ;
CLEANUP :
//while the head impinges on tail items, discard them
for ( ; ; )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
if ( mTail = = null ) break ;
if ( mHead . Value . endExclusive > mTail . Value . index & & mHead . Value . index < = mTail . Value . index )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
LinkedListNode < ListItem > nextTail = mTail . Next ;
2013-07-27 22:39:12 +00:00
mSize - = mTail . Value . length ;
2013-07-27 21:53:47 +00:00
mBookmarks . Remove ( mTail ) ;
mTail = nextTail ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
else break ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
return mHead . Value . index ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
public ListItem Pop ( )
{
if ( mHead = = null ) throw new InvalidOperationException ( "Attempted to pop from an empty data structure" ) ;
var ret = mHead . Value ;
2013-07-27 22:39:12 +00:00
mSize - = ret . length ;
2013-07-27 21:53:47 +00:00
LinkedListNode < ListItem > nextHead = mHead . Previous ;
mBookmarks . Remove ( mHead ) ;
if ( mHead = = mTail )
mTail = null ;
mHead = nextHead ;
if ( mHead = = null )
mHead = mBookmarks . Last ;
return ret ;
}
public ListItem Dequeue ( )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
if ( mTail = = null ) throw new InvalidOperationException ( "Attempted to dequeue from an empty data structure" ) ;
var ret = mTail . Value ;
2013-07-27 22:39:12 +00:00
mSize - = ret . length ;
2013-07-27 21:53:47 +00:00
LinkedListNode < ListItem > nextTail = mTail . Next ;
mBookmarks . Remove ( mTail ) ;
if ( mTail = = mHead )
mHead = null ;
mTail = nextTail ;
if ( mTail = = null )
mTail = mBookmarks . First ;
return ret ;
}
//-------- tests ---------
public void AssertMonotonic ( )
{
if ( mTail = = null ) return ;
int ts = mTail . Value . timestamp ;
LinkedListNode < ListItem > curr = mTail ;
for ( ; ; )
2013-07-21 20:39:11 +00:00
{
2013-07-27 21:53:47 +00:00
if ( curr = = null )
curr = mBookmarks . First ;
if ( curr = = null ) break ;
System . Diagnostics . Debug . Assert ( curr . Value . timestamp > = ts ) ;
if ( curr = = mHead ) return ;
ts = curr . Value . timestamp ;
curr = curr . Next ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
}
2013-07-21 20:39:11 +00:00
2013-08-24 23:00:33 +00:00
//void Test()
//{
// var sbb = new StreamBlobDatabase(false, Global.Config.Rewind_BufferSize * 1024 * 1024);
// var rand = new Random(0);
// int timestamp = 0;
// for (; ; )
// {
// long test = sbb.Enqueue(timestamp, rand.Next(100 * 1024));
// if (rand.Next(10) == 0)
// if (sbb.Count != 0) sbb.Dequeue();
// if (rand.Next(10) == 0)
// if (sbb.Count != 0) sbb.Pop();
// if (rand.Next(50) == 1)
// {
// while (sbb.Count != 0)
// {
// Console.WriteLine("ZAM!!!");
// sbb.Dequeue();
// }
// }
// sbb.AssertMonotonic();
// timestamp++;
// Console.WriteLine("{0}, {1}", test, sbb.Count);
// }
//}
2013-07-28 22:47:37 +00:00
} //class StreamBlobDatabase
class RewindThreader : IDisposable
{
//adelikat: tweak this to test performance with threading or not with threading
public static bool IsThreaded = true ;
MainForm mf ;
2013-07-30 01:30:59 +00:00
public RewindThreader ( MainForm mf , bool isThreaded )
2013-07-28 22:47:37 +00:00
{
2013-07-30 01:30:59 +00:00
IsThreaded = isThreaded ;
2013-07-28 22:47:37 +00:00
this . mf = mf ;
if ( IsThreaded )
{
ewh = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
ewh2 = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
thread = new Thread ( ThreadProc ) ;
thread . IsBackground = true ;
thread . Start ( ) ;
}
}
public void Dispose ( )
{
if ( ! IsThreaded )
return ;
var job = new Job ( ) ;
job . Type = JobType . Abort ;
Jobs . Enqueue ( job ) ;
ewh . Set ( ) ;
thread . Join ( ) ;
ewh . Dispose ( ) ;
ewh2 . Dispose ( ) ;
}
void ThreadProc ( )
{
for ( ; ; )
{
ewh . WaitOne ( ) ;
while ( Jobs . Count ! = 0 )
{
Job job = null ;
if ( Jobs . TryDequeue ( out job ) )
{
if ( job . Type = = JobType . Abort )
return ;
if ( job . Type = = JobType . Capture )
{
mf . _RunCapture ( job . CoreState ) ;
}
if ( job . Type = = JobType . Rewind )
{
mf . _RunRewind ( job . Frames ) ;
ewh2 . Set ( ) ;
}
}
}
}
}
EventWaitHandle ewh , ewh2 ;
Thread thread ;
public void Rewind ( int frames )
{
if ( ! IsThreaded )
{
mf . _RunRewind ( frames ) ;
return ;
}
var job = new Job ( ) ;
job . Type = JobType . Rewind ;
job . Frames = frames ;
Jobs . Enqueue ( job ) ;
ewh . Set ( ) ;
ewh2 . WaitOne ( ) ;
}
void DoSafeEnqueue ( Job job )
{
Jobs . Enqueue ( job ) ;
ewh . Set ( ) ;
//just in case... we're getting really behind.. slow it down here
//if this gets backed up too much, then the rewind will seem to malfunction since it requires all the captures in the queue to complete first
while ( Jobs . Count > 15 )
{
Thread . Sleep ( 0 ) ;
}
}
public void Capture ( byte [ ] coreSavestate )
{
if ( ! IsThreaded )
{
mf . _RunCapture ( coreSavestate ) ;
return ;
}
var job = new Job ( ) ;
job . Type = JobType . Capture ;
job . CoreState = coreSavestate ;
DoSafeEnqueue ( job ) ;
}
enum JobType
{
Capture , Rewind , Abort
}
class Job
{
public JobType Type ;
public byte [ ] CoreState ;
public int Frames ;
}
ConcurrentQueue < Job > Jobs = new ConcurrentQueue < Job > ( ) ;
2013-07-21 20:39:11 +00:00
}
2013-07-27 21:53:47 +00:00
private void CaptureRewindState ( )
2011-06-12 00:14:19 +00:00
{
2013-07-27 21:53:47 +00:00
if ( RewindImpossible )
return ;
if ( LastState = = null )
2012-10-10 15:04:13 +00:00
{
2013-07-27 21:53:47 +00:00
DoRewindSettings ( ) ;
2012-10-10 15:04:13 +00:00
}
2013-07-27 21:53:47 +00:00
2013-07-27 23:13:27 +00:00
//log a frame
2013-10-20 18:02:43 +00:00
if ( LastState ! = null & & Global . Emulator . Frame % RewindFrequency = = 0 )
2011-06-12 00:14:19 +00:00
{
2013-10-20 18:02:43 +00:00
byte [ ] CurrentState = Global . Emulator . SaveStateBinary ( ) ;
2013-07-28 22:47:37 +00:00
RewindThread . Capture ( CurrentState ) ;
2013-07-27 21:53:47 +00:00
}
}
2012-10-10 15:04:13 +00:00
2013-07-27 21:53:47 +00:00
void SetRewindParams ( bool enabled , int frequency )
{
2013-08-24 15:01:50 +00:00
if ( RewindActive ! = enabled )
{
2013-11-03 16:07:58 +00:00
GlobalWin . OSD . AddMessage ( "Rewind " + ( enabled ? "Enabled" : "Disabled" ) ) ;
2013-08-24 15:01:50 +00:00
}
2013-08-02 20:24:03 +00:00
2013-08-24 15:01:50 +00:00
if ( RewindFrequency ! = frequency & & enabled )
{
2013-11-03 16:07:58 +00:00
GlobalWin . OSD . AddMessage ( "Rewind frequency set to " + frequency ) ;
2013-08-24 15:01:50 +00:00
}
2012-10-10 15:04:13 +00:00
2013-07-27 21:53:47 +00:00
RewindActive = enabled ;
RewindFrequency = frequency ;
2011-01-11 02:55:51 +00:00
2013-07-27 21:53:47 +00:00
if ( ! RewindActive )
LastState = null ;
}
public void DoRewindSettings ( )
{
// This is the first frame. Capture the state, and put it in LastState for future deltas to be compared against.
2013-10-20 18:02:43 +00:00
LastState = Global . Emulator . SaveStateBinary ( ) ;
2013-07-27 21:53:47 +00:00
2013-08-24 15:01:50 +00:00
int state_size = 0 ;
2013-07-28 19:09:52 +00:00
if ( LastState . Length > = Global . Config . Rewind_LargeStateSize )
{
SetRewindParams ( Global . Config . RewindEnabledLarge , Global . Config . RewindFrequencyLarge ) ;
2013-08-24 15:01:50 +00:00
state_size = 3 ;
2013-07-28 19:09:52 +00:00
}
2013-07-28 22:46:54 +00:00
else if ( LastState . Length > = Global . Config . Rewind_MediumStateSize )
2013-07-28 19:09:52 +00:00
{
SetRewindParams ( Global . Config . RewindEnabledMedium , Global . Config . RewindFrequencyMedium ) ;
2013-08-24 15:01:50 +00:00
state_size = 2 ;
2013-07-28 19:09:52 +00:00
}
2013-07-27 21:53:47 +00:00
else
2013-07-28 19:09:52 +00:00
{
2013-07-27 21:53:47 +00:00
SetRewindParams ( Global . Config . RewindEnabledSmall , Global . Config . RewindFrequencySmall ) ;
2013-08-24 15:01:50 +00:00
state_size = 1 ;
2013-07-28 19:09:52 +00:00
}
2013-07-27 23:13:27 +00:00
2013-08-24 15:01:50 +00:00
bool rewind_enabled = false ;
if ( state_size = = 1 ) rewind_enabled = Global . Config . RewindEnabledSmall ;
if ( state_size = = 2 ) rewind_enabled = Global . Config . RewindEnabledMedium ;
if ( state_size = = 3 ) rewind_enabled = Global . Config . RewindEnabledLarge ;
2013-07-28 19:09:52 +00:00
RewindDeltaEnable = Global . Config . Rewind_UseDelta ;
2013-08-24 15:01:50 +00:00
if ( rewind_enabled )
{
long cap = Global . Config . Rewind_BufferSize * ( long ) 1024 * ( long ) 1024 ;
2013-08-24 23:00:33 +00:00
if ( RewindBuf ! = null )
RewindBuf . Dispose ( ) ;
RewindBuf = new StreamBlobDatabase ( Global . Config . Rewind_OnDisk , cap , BufferManage ) ;
2013-08-24 15:01:50 +00:00
if ( RewindThread ! = null )
RewindThread . Dispose ( ) ;
RewindThread = new RewindThreader ( this , Global . Config . Rewind_IsThreaded ) ;
}
2011-06-12 00:14:19 +00:00
}
2011-01-11 02:55:51 +00:00
2013-08-24 23:00:33 +00:00
byte [ ] RewindFellationBuf ;
byte [ ] BufferManage ( byte [ ] inbuf , long size , bool allocate )
{
if ( allocate )
{
//if we have an appropriate buffer free, return it
if ( RewindFellationBuf ! = null & & RewindFellationBuf . LongLength = = size )
{
byte [ ] ret = RewindFellationBuf ;
RewindFellationBuf = null ;
return ret ;
}
//otherwise, allocate it
return new byte [ size ] ;
}
else
{
RewindFellationBuf = inbuf ;
return null ;
}
}
2013-07-28 22:47:37 +00:00
void CaptureRewindStateNonDelta ( byte [ ] CurrentState )
2013-07-27 23:13:27 +00:00
{
long offset = RewindBuf . Enqueue ( 0 , CurrentState . Length + 1 ) ;
Stream stream = RewindBuf . Stream ;
stream . Position = offset ;
//write the header for a non-delta frame
stream . WriteByte ( 1 ) ; //i.e. true
stream . Write ( CurrentState , 0 , CurrentState . Length ) ;
}
2013-07-27 21:53:47 +00:00
2013-07-27 23:02:26 +00:00
byte [ ] TempBuf = new byte [ 0 ] ;
2013-07-28 22:47:37 +00:00
void CaptureRewindStateDelta ( byte [ ] CurrentState , bool isSmall )
2011-06-12 00:14:19 +00:00
{
2013-07-27 23:13:27 +00:00
//in case the state sizes mismatch, capture a full state rather than trying to do anything clever
if ( CurrentState . Length ! = LastState . Length )
{
2013-07-28 22:47:37 +00:00
CaptureRewindStateNonDelta ( CurrentState ) ;
2013-07-27 23:13:27 +00:00
return ;
}
2011-06-12 00:14:19 +00:00
int beginChangeSequence = - 1 ;
bool inChangeSequence = false ;
2013-07-27 23:02:26 +00:00
MemoryStream ms ;
//try to set up the buffer in advance so we dont ever have exceptions in here
if ( TempBuf . Length < CurrentState . Length )
TempBuf = new byte [ CurrentState . Length * 2 ] ;
ms = new MemoryStream ( TempBuf , 0 , TempBuf . Length , true , true ) ;
RETRY :
try
2011-06-12 00:14:19 +00:00
{
2013-07-27 23:02:26 +00:00
var writer = new BinaryWriter ( ms ) ;
2013-07-27 23:13:27 +00:00
writer . Write ( false ) ; // delta state
for ( int i = 0 ; i < CurrentState . Length ; i + + )
2013-07-27 23:02:26 +00:00
{
2013-07-27 23:13:27 +00:00
if ( inChangeSequence = = false )
2012-10-10 15:04:13 +00:00
{
2013-07-27 23:13:27 +00:00
if ( i > = LastState . Length )
2013-07-27 23:02:26 +00:00
continue ;
2013-07-27 23:13:27 +00:00
if ( CurrentState [ i ] = = LastState [ i ] )
2013-07-27 23:02:26 +00:00
continue ;
2011-01-11 02:55:51 +00:00
2013-07-27 23:13:27 +00:00
inChangeSequence = true ;
beginChangeSequence = i ;
continue ;
}
if ( i - beginChangeSequence = = 254 | | i = = CurrentState . Length - 1 )
{
writer . Write ( ( byte ) ( i - beginChangeSequence + 1 ) ) ;
if ( isSmall ) writer . Write ( ( ushort ) beginChangeSequence ) ;
else writer . Write ( beginChangeSequence ) ;
writer . Write ( LastState , beginChangeSequence , i - beginChangeSequence + 1 ) ;
inChangeSequence = false ;
continue ;
}
if ( CurrentState [ i ] = = LastState [ i ] )
{
writer . Write ( ( byte ) ( i - beginChangeSequence ) ) ;
if ( isSmall ) writer . Write ( ( ushort ) beginChangeSequence ) ;
else writer . Write ( beginChangeSequence ) ;
writer . Write ( LastState , beginChangeSequence , i - beginChangeSequence ) ;
inChangeSequence = false ;
2012-10-10 15:04:13 +00:00
}
2011-06-12 00:14:19 +00:00
}
}
2013-07-27 23:02:26 +00:00
catch ( NotSupportedException )
{
//ok... we had an exception after all
//if we did actually run out of room in the memorystream, then try it again with a bigger buffer
TempBuf = new byte [ TempBuf . Length * 2 ] ;
goto RETRY ;
}
2011-06-12 00:14:19 +00:00
LastState = CurrentState ;
2013-07-27 23:02:26 +00:00
var seg = new ArraySegment < byte > ( TempBuf , 0 , ( int ) ms . Position ) ;
RewindBuf . Push ( seg ) ;
2011-06-12 00:14:19 +00:00
}
2011-01-11 02:55:51 +00:00
2013-07-27 21:53:47 +00:00
void RewindLarge ( ) { RewindDelta ( false ) ; }
void Rewind64K ( ) { RewindDelta ( true ) ; }
void RewindDelta ( bool isSmall )
2011-06-12 00:14:19 +00:00
{
2013-07-27 21:53:47 +00:00
var ms = RewindBuf . PopMemoryStream ( ) ;
2011-06-12 00:14:19 +00:00
var reader = new BinaryReader ( ms ) ;
2012-10-10 15:04:13 +00:00
bool fullstate = reader . ReadBoolean ( ) ;
if ( fullstate )
{
2013-10-20 18:02:43 +00:00
Global . Emulator . LoadStateBinary ( reader ) ;
2012-10-10 15:04:13 +00:00
}
else
2011-06-12 00:14:19 +00:00
{
2012-10-10 15:04:13 +00:00
var output = new MemoryStream ( LastState ) ;
while ( ms . Position < ms . Length - 1 )
{
byte len = reader . ReadByte ( ) ;
2013-07-27 21:53:47 +00:00
int offset ;
if ( isSmall )
offset = reader . ReadUInt16 ( ) ;
else offset = reader . ReadInt32 ( ) ;
2012-10-10 15:04:13 +00:00
output . Position = offset ;
output . Write ( ms . GetBuffer ( ) , ( int ) ms . Position , len ) ;
ms . Position + = len ;
}
reader . Close ( ) ;
output . Position = 0 ;
2013-10-20 18:02:43 +00:00
Global . Emulator . LoadStateBinary ( new BinaryReader ( output ) ) ;
2011-06-12 00:14:19 +00:00
}
}
2011-01-11 02:55:51 +00:00
2011-06-12 00:14:19 +00:00
public void Rewind ( int frames )
2013-07-28 22:47:37 +00:00
{
RewindThread . Rewind ( frames ) ;
}
void _RunRewind ( int frames )
2011-06-12 00:14:19 +00:00
{
for ( int i = 0 ; i < frames ; i + + )
{
2013-11-30 03:10:05 +00:00
if ( RewindBuf . Count = = 0 | | ( Global . MovieSession . Movie . Loaded & & Global . MovieSession . Movie . FrameCount = = 0 ) )
2011-06-12 00:14:19 +00:00
return ;
2013-07-28 22:47:37 +00:00
2011-06-12 00:14:19 +00:00
if ( LastState . Length < 0x10000 )
Rewind64K ( ) ;
else
RewindLarge ( ) ;
}
}
2011-01-11 02:55:51 +00:00
2013-07-28 22:47:37 +00:00
void _RunCapture ( byte [ ] coreSavestate )
{
if ( RewindDeltaEnable )
{
if ( LastState . Length < = 0x10000 )
CaptureRewindStateDelta ( coreSavestate , true ) ;
else
CaptureRewindStateDelta ( coreSavestate , false ) ;
}
else CaptureRewindStateNonDelta ( coreSavestate ) ;
}
2011-06-12 00:14:19 +00:00
public void ResetRewindBuffer ( )
{
2013-08-24 15:01:50 +00:00
if ( RewindBuf ! = null ) { RewindBuf . Clear ( ) ; }
2013-04-16 00:19:31 +00:00
RewindImpossible = false ;
2011-06-12 00:14:19 +00:00
LastState = null ;
}
2012-05-28 00:44:27 +00:00
2013-04-16 00:19:31 +00:00
public int RewindBufferCount ( )
{
return RewindBuf . Count ;
}
2011-06-12 00:14:19 +00:00
}
2011-01-11 02:55:51 +00:00
}