AxibugEmuOnline/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs

407 lines
13 KiB
C#
Raw Normal View History

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;
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;
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);
}
}
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();
AxiEmuRunFrame++;
2025-02-14 16:09:33 +08:00
if (configChangeRequested)
{
emulator.SetConfiguration(newConfiguration);
configChangeRequested = false;
}
}
//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
{
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
public void LoadSnapShotFromFile(int stateNumber)
{
var statePath = GetSaveStateFilename(stateNumber);
if (File.Exists(statePath))
2025-02-14 16:09:33 +08:00
{
using (var stream = new FileStream(statePath, FileMode.Open))
2025-02-14 16:09:33 +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
}
}
}
public byte[] GetStateData()
{
return emulator.SaveAxiStatus().ToByteArray();
}
public void SetStateData(byte[] data)
{
emulator.LoadAxiStatus(data.ToAxiEssgssStatusData());
AxiEmuRunFrame = 0;
2025-02-14 16:09:33 +08:00
}
}
}