2025-02-14 16:09:33 +08:00
using Essgee.Emulation.Configuration ;
using Essgee.Emulation.Machines ;
using Essgee.EventArguments ;
using Essgee.Metadata ;
using Essgee.Utilities ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Threading ;
namespace Essgee.Emulation
{
public class EmulatorHandler
{
readonly Action < Exception > exceptionHandler ;
public IMachine emulator { get ; private set ; }
Thread emulationThread ;
volatile bool emulationThreadRunning ;
volatile bool limitFps ;
volatile bool emulationThreadPaused ;
volatile bool configChangeRequested = false ;
volatile IConfiguration newConfiguration = null ;
volatile bool stateLoadRequested = false , stateSaveRequested = false ;
volatile int stateNumber = - 1 ;
volatile Queue < bool > pauseStateChangesRequested = new Queue < bool > ( ) ;
public event EventHandler < SendLogMessageEventArgs > SendLogMessage
{
add { emulator . SendLogMessage + = value ; }
remove { emulator . SendLogMessage - = value ; }
}
public event EventHandler < EventArgs > EmulationReset
{
add { emulator . EmulationReset + = value ; }
remove { emulator . EmulationReset - = value ; }
}
public event EventHandler < RenderScreenEventArgs > RenderScreen
{
add { emulator . RenderScreen + = value ; }
remove { emulator . RenderScreen - = value ; }
}
public event EventHandler < SizeScreenEventArgs > SizeScreen
{
add { emulator . SizeScreen + = value ; }
remove { emulator . SizeScreen - = value ; }
}
public event EventHandler < ChangeViewportEventArgs > ChangeViewport
{
add { emulator . ChangeViewport + = value ; }
remove { emulator . ChangeViewport - = value ; }
}
public event EventHandler < PollInputEventArgs > PollInput
{
add { emulator . PollInput + = value ; }
remove { emulator . PollInput - = value ; }
}
public event EventHandler < EnqueueSamplesEventArgs > EnqueueSamples
{
add { emulator . EnqueueSamples + = value ; }
remove { emulator . EnqueueSamples - = value ; }
}
public event EventHandler < SaveExtraDataEventArgs > SaveExtraData
{
add { emulator . SaveExtraData + = value ; }
remove { emulator . SaveExtraData - = value ; }
}
public event EventHandler < EventArgs > EnableRumble
{
add { emulator . EnableRumble + = value ; }
remove { emulator . EnableRumble - = value ; }
}
public event EventHandler < EventArgs > PauseChanged ;
GameMetadata currentGameMetadata ;
2025-02-20 18:29:10 +08:00
public int AxiEmuRunFrame { get ; private set ; }
2025-02-14 16:09:33 +08:00
public bool IsCartridgeLoaded { get ; private set ; }
public bool IsRunning = > emulationThreadRunning ;
public bool IsPaused = > emulationThreadPaused ;
public bool IsHandlingSaveState = > ( stateLoadRequested | | stateSaveRequested ) ;
public ( string Manufacturer , string Model , string DatFileName , double RefreshRate , double PixelAspectRatio , ( string Name , string Description ) [ ] RuntimeOptions ) Information = >
( emulator . ManufacturerName , emulator . ModelName , emulator . DatFilename , emulator . RefreshRate , emulator . PixelAspectRatio , emulator . RuntimeOptions ) ;
public EmulatorHandler ( Type type , Action < Exception > exceptionHandler = null )
{
this . exceptionHandler = exceptionHandler ;
emulator = ( IMachine ) Activator . CreateInstance ( type ) ;
}
public void SetConfiguration ( IConfiguration config )
{
if ( emulationThreadRunning )
{
configChangeRequested = true ;
newConfiguration = config ;
}
else
emulator . SetConfiguration ( config ) ;
}
public void Initialize ( )
{
emulator . Initialize ( ) ;
}
public void Startup ( )
{
emulationThreadRunning = true ;
emulationThreadPaused = false ;
emulator . Startup ( ) ;
emulator . Reset ( ) ;
//不再使用进程推帧
//emulationThread = new Thread(ThreadMainLoop) { Name = "EssgeeEmulation", Priority = ThreadPriority.Normal };
//emulationThread.Start();
}
public void Reset ( )
{
emulator . Reset ( ) ;
}
public void Shutdown ( )
{
emulationThreadRunning = false ;
emulationThread ? . Join ( ) ;
emulator . Shutdown ( ) ;
}
public void Pause ( bool pauseState )
{
pauseStateChangesRequested . Enqueue ( pauseState ) ;
}
public string GetSaveStateFilename ( int number )
{
return Path . Combine ( EmuStandInfo . SaveStatePath , $"{Path.GetFileNameWithoutExtension(currentGameMetadata.FileName)} (State {number:D2}).est" ) ;
}
public void LoadState ( int number )
{
stateLoadRequested = true ;
stateNumber = number ;
}
public void SaveState ( int number )
{
stateSaveRequested = true ;
stateNumber = number ;
}
public void LoadCartridge ( byte [ ] romData , GameMetadata gameMetadata )
{
//初始化AxiMem
AxiMemoryEx . Init ( ) ;
currentGameMetadata = gameMetadata ;
byte [ ] ramData = new byte [ currentGameMetadata . RamSize ] ;
var savePath = Path . Combine ( EmuStandInfo . SaveDataPath , Path . ChangeExtension ( currentGameMetadata . FileName , "sav" ) ) ;
if ( File . Exists ( savePath ) )
ramData = File . ReadAllBytes ( savePath ) ;
emulator . Load ( romData , ramData , currentGameMetadata . MapperType ) ;
IsCartridgeLoaded = true ;
2025-02-20 18:29:10 +08:00
AxiEmuRunFrame = 0 ;
2025-02-14 16:09:33 +08:00
}
public void SaveCartridge ( )
{
if ( currentGameMetadata = = null ) return ;
var cartRamSaveNeeded = emulator . IsCartridgeRamSaveNeeded ( ) ;
if ( ( cartRamSaveNeeded & & currentGameMetadata . MapperType ! = null & & currentGameMetadata . HasNonVolatileRam ) | |
cartRamSaveNeeded )
{
var ramData = emulator . GetCartridgeRam ( ) ;
var savePath = Path . Combine ( EmuStandInfo . SaveDataPath , Path . ChangeExtension ( currentGameMetadata . FileName , "sav" ) ) ;
File . WriteAllBytes ( savePath , ramData ) ;
}
}
2025-02-17 17:48:48 +08:00
public Dictionary < string , object > GetDebugInformation ( )
2025-02-14 16:09:33 +08:00
{
return emulator . GetDebugInformation ( ) ;
}
public Type GetMachineType ( )
{
return emulator . GetType ( ) ;
}
public void SetFpsLimiting ( bool value )
{
limitFps = value ;
}
public object GetRuntimeOption ( string name )
{
return emulator . GetRuntimeOption ( name ) ;
}
public void SetRuntimeOption ( string name , object value )
{
emulator . SetRuntimeOption ( name , value ) ;
}
public int FramesPerSecond { get ; private set ; }
public void Update_Frame ( )
{
if ( ! emulationThreadRunning )
return ;
while ( pauseStateChangesRequested . Count > 0 )
{
var newPauseState = pauseStateChangesRequested . Dequeue ( ) ;
emulationThreadPaused = newPauseState ;
PauseChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
emulator . RunFrame ( ) ;
2025-02-20 18:29:10 +08:00
AxiEmuRunFrame + + ;
2025-02-14 16:09:33 +08:00
if ( configChangeRequested )
{
emulator . SetConfiguration ( newConfiguration ) ;
configChangeRequested = false ;
}
}
2025-02-17 17:48:48 +08:00
//private void ThreadMainLoop()
//{
// // TODO: rework fps limiter/counter - AGAIN - because the counter is inaccurate at sampleTimespan=0.25 and the limiter CAN cause sound crackling at sampleTimespan>0.25
// // try this maybe? https://stackoverflow.com/a/34839411
// var stopWatch = Stopwatch.StartNew();
// TimeSpan accumulatedTime = TimeSpan.Zero, lastStartTime = TimeSpan.Zero, lastEndTime = TimeSpan.Zero;
// var frameCounter = 0;
// var sampleTimespan = TimeSpan.FromSeconds(0.5);
// try
// {
// while (true)
// {
// if (!emulationThreadRunning)
// break;
// if (stateLoadRequested && stateNumber != -1)
// {
// var statePath = GetSaveStateFilename(stateNumber);
// if (File.Exists(statePath))
// {
// using (var stream = new FileStream(statePath, FileMode.Open))
// {
// emulator.SetState(SaveStateHandler.Load(stream, emulator.GetType().Name));
// }
// }
// stateLoadRequested = false;
// stateNumber = -1;
// }
// var refreshRate = emulator.RefreshRate;
// var targetElapsedTime = TimeSpan.FromTicks((long)Math.Round(TimeSpan.TicksPerSecond / refreshRate));
// var startTime = stopWatch.Elapsed;
// while (pauseStateChangesRequested.Count > 0)
// {
// var newPauseState = pauseStateChangesRequested.Dequeue();
// emulationThreadPaused = newPauseState;
// PauseChanged?.Invoke(this, EventArgs.Empty);
// }
// if (!emulationThreadPaused)
// {
// if (limitFps)
// {
// var elapsedTime = (startTime - lastStartTime);
// lastStartTime = startTime;
// if (elapsedTime < targetElapsedTime)
// {
// accumulatedTime += elapsedTime;
// while (accumulatedTime >= targetElapsedTime)
// {
// emulator.RunFrame();
// frameCounter++;
// accumulatedTime -= targetElapsedTime;
// }
// }
// }
// else
// {
// emulator.RunFrame();
// frameCounter++;
// }
// if ((stopWatch.Elapsed - lastEndTime) >= sampleTimespan)
// {
// FramesPerSecond = (int)((frameCounter * 1000.0) / sampleTimespan.TotalMilliseconds);
// frameCounter = 0;
// lastEndTime = stopWatch.Elapsed;
// }
// }
// else
// {
// lastEndTime = stopWatch.Elapsed;
// }
// if (configChangeRequested)
// {
// emulator.SetConfiguration(newConfiguration);
// configChangeRequested = false;
// }
// if (stateSaveRequested && stateNumber != -1)
// {
// var statePath = GetSaveStateFilename(stateNumber);
// using (var stream = new FileStream(statePath, FileMode.OpenOrCreate))
// {
// SaveStateHandler.Save(stream, emulator.GetType().Name, emulator.GetState());
// }
// stateSaveRequested = false;
// stateNumber = -1;
// }
// }
// }
// catch (Exception ex) when (!AppEnvironment.DebugMode)
// {
// ex.Data.Add("Thread", Thread.CurrentThread.Name);
// exceptionHandler(ex);
// }
//}
public void SaveSnapShotToFile ( int stateNumber )
2025-02-14 16:09:33 +08:00
{
2025-02-17 17:48:48 +08:00
var statePath = GetSaveStateFilename ( stateNumber ) ;
using ( var stream = new FileStream ( statePath , FileMode . OpenOrCreate ) )
{
//SaveStateHandler.Save(stream, emulator.GetType().Name, emulator.GetState());
SaveStateHandler . Save ( stream , emulator . GetType ( ) . Name , emulator . SaveAxiStatus ( ) ) ;
}
}
2025-02-14 16:09:33 +08:00
2025-02-17 17:48:48 +08:00
public void LoadSnapShotFromFile ( int stateNumber )
{
var statePath = GetSaveStateFilename ( stateNumber ) ;
if ( File . Exists ( statePath ) )
2025-02-14 16:09:33 +08:00
{
2025-02-17 17:48:48 +08:00
using ( var stream = new FileStream ( statePath , FileMode . Open ) )
2025-02-14 16:09:33 +08:00
{
2025-02-17 17:48:48 +08:00
//emulator.SetState(SaveStateHandler.Load(stream, emulator.GetType().Name));
emulator . LoadAxiStatus ( SaveStateHandler . LoadAxiStatus ( stream , emulator . GetType ( ) . Name ) ) ;
2025-02-14 16:09:33 +08:00
}
}
2025-02-17 17:48:48 +08:00
}
public byte [ ] GetStateData ( )
{
return emulator . SaveAxiStatus ( ) . ToByteArray ( ) ;
}
public void SetStateData ( byte [ ] data )
{
emulator . LoadAxiStatus ( data . ToAxiEssgssStatusData ( ) ) ;
2025-02-20 18:29:10 +08:00
AxiEmuRunFrame = 0 ;
2025-02-14 16:09:33 +08:00
}
}
}