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 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 pauseStateChangesRequested = new Queue(); public event EventHandler SendLogMessage { add { emulator.SendLogMessage += value; } remove { emulator.SendLogMessage -= value; } } public event EventHandler EmulationReset { add { emulator.EmulationReset += value; } remove { emulator.EmulationReset -= value; } } public event EventHandler RenderScreen { add { emulator.RenderScreen += value; } remove { emulator.RenderScreen -= value; } } public event EventHandler SizeScreen { add { emulator.SizeScreen += value; } remove { emulator.SizeScreen -= value; } } public event EventHandler ChangeViewport { add { emulator.ChangeViewport += value; } remove { emulator.ChangeViewport -= value; } } public event EventHandler PollInput { add { emulator.PollInput += value; } remove { emulator.PollInput -= value; } } public event EventHandler EnqueueSamples { add { emulator.EnqueueSamples += value; } remove { emulator.EnqueueSamples -= value; } } public event EventHandler SaveExtraData { add { emulator.SaveExtraData += value; } remove { emulator.SaveExtraData -= value; } } public event EventHandler EnableRumble { add { emulator.EnableRumble += value; } remove { emulator.EnableRumble -= value; } } public event EventHandler PauseChanged; GameMetadata currentGameMetadata; public int AxiEmuRunFrame { get; private set; } 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 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; } 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 GetDebugInformation() { 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++; 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) { 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()); } } public void LoadSnapShotFromFile(int stateNumber) { var statePath = GetSaveStateFilename(stateNumber); if (File.Exists(statePath)) { using (var stream = new FileStream(statePath, FileMode.Open)) { //emulator.SetState(SaveStateHandler.Load(stream, emulator.GetType().Name)); emulator.LoadAxiStatus(SaveStateHandler.LoadAxiStatus(stream, emulator.GetType().Name)); } } } public byte[] GetStateData() { return emulator.SaveAxiStatus().ToByteArray(); } public void SetStateData(byte[] data) { emulator.LoadAxiStatus(data.ToAxiEssgssStatusData()); AxiEmuRunFrame = 0; } } }