Rename file. (Next commit will rename class and create new file/class with the old filename.)

This commit is contained in:
SuuperW 2023-09-19 02:31:10 -05:00
parent 258289426e
commit 53b2d1d7bc
1 changed files with 373 additions and 373 deletions

View File

@ -1,373 +1,373 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using NLua;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk
{
public class LuaLibraries : ILuaLibraries
{
public LuaLibraries(
LuaFileList scriptList,
LuaFunctionList registeredFuncList,
IEmulatorServiceProvider serviceProvider,
MainForm mainForm,
DisplayManagerBase displayManager,
InputManager inputManager,
Config config,
IEmulator emulator,
IGameInfo game)
{
void EnumerateLuaFunctions(string name, Type type, LuaLibraryBase instance)
{
if (instance != null) _lua.NewTable(name);
foreach (var method in type.GetMethods())
{
var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false);
if (foundAttrs.Length == 0) continue;
if (instance != null) _lua.RegisterFunction($"{name}.{((LuaMethodAttribute)foundAttrs[0]).Name}", instance, method);
LibraryFunction libFunc = new(
name,
type.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>()
.Select(descAttr => descAttr.Description).FirstOrDefault() ?? string.Empty,
method
);
Docs.Add(libFunc);
}
}
_th = new NLuaTableHelper(_lua, LogToLuaConsole);
_displayManager = displayManager;
_inputManager = inputManager;
_mainForm = mainForm;
LuaWait = new AutoResetEvent(false);
PathEntries = config.PathEntries;
RegisteredFunctions = registeredFuncList;
ScriptList = scriptList;
Docs.Clear();
_apiContainer = ApiManager.RestartLua(serviceProvider, LogToLuaConsole, _mainForm, _displayManager, _inputManager, _mainForm.MovieSession, _mainForm.Tools, config, emulator, game);
// Register lua libraries
foreach (var lib in Client.Common.ReflectionCache.Types.Concat(EmuHawk.ReflectionCache.Types)
.Where(t => typeof(LuaLibraryBase).IsAssignableFrom(t) && t.IsSealed && ServiceInjector.IsAvailable(serviceProvider, t)))
{
if (VersionInfo.DeveloperBuild
|| lib.GetCustomAttribute<LuaLibraryAttribute>(inherit: false)?.Released is not false)
{
var instance = (LuaLibraryBase)Activator.CreateInstance(lib, this, _apiContainer, (Action<string>)LogToLuaConsole);
if (!ServiceInjector.UpdateServices(serviceProvider, instance, mayCache: true)) throw new Exception("Lua lib has required service(s) that can't be fulfilled");
// TODO: make EmuHawk libraries have a base class with common properties such as this
// and inject them here
if (instance is ClientLuaLibrary clientLib)
{
clientLib.MainForm = _mainForm;
}
else if (instance is ConsoleLuaLibrary consoleLib)
{
consoleLib.Tools = _mainForm.Tools;
_logToLuaConsoleCallback = consoleLib.Log;
}
else if (instance is FormsLuaLibrary formsLib)
{
formsLib.MainForm = _mainForm;
}
else if (instance is GuiLuaLibrary guiLib)
{
// emu lib may be null now, depending on order of ReflectionCache.Types, but definitely won't be null when this is called
guiLib.CreateLuaCanvasCallback = (width, height, x, y) =>
{
var canvas = new LuaCanvas(EmulationLuaLibrary, width, height, x, y, _th, LogToLuaConsole);
canvas.Show();
return _th.ObjectToTable(canvas);
};
}
else if (instance is TAStudioLuaLibrary tastudioLib)
{
tastudioLib.Tools = _mainForm.Tools;
}
EnumerateLuaFunctions(instance.Name, lib, instance);
Libraries.Add(lib, instance);
}
}
_lua.RegisterFunction("print", this, typeof(LuaLibraries).GetMethod(nameof(Print)));
var packageTable = (LuaTable) _lua["package"];
var luaPath = PathEntries.LuaAbsolutePath();
if (OSTailoredCode.IsUnixHost)
{
// add %exe%/Lua to library resolution pathset (LUA_PATH)
// this is done already on windows, but not on linux it seems?
packageTable["path"] = $"{luaPath}/?.lua;{luaPath}?/init.lua;{packageTable["path"]}";
// we need to modifiy the cpath so it looks at our lua dir too, and remove the relative pathing
// we do this on Windows too, but keep in mind Linux uses .so and Windows use .dll
// TODO: Does the relative pathing issue Windows has also affect Linux? I'd assume so...
packageTable["cpath"] = $"{luaPath}/?.so;{luaPath}/loadall.so;{packageTable["cpath"]}";
packageTable["cpath"] = ((string)packageTable["cpath"]).Replace(";./?.so", "");
}
else
{
packageTable["cpath"] = $"{luaPath}\\?.dll;{luaPath}\\loadall.dll;{packageTable["cpath"]}";
packageTable["cpath"] = ((string)packageTable["cpath"]).Replace(";.\\?.dll", "");
}
EmulationLuaLibrary.FrameAdvanceCallback = FrameAdvance;
EmulationLuaLibrary.YieldCallback = EmuYield;
EnumerateLuaFunctions(nameof(LuaCanvas), typeof(LuaCanvas), null); // add LuaCanvas to Lua function reference table
}
private ApiContainer _apiContainer;
private readonly DisplayManagerBase _displayManager;
private GuiApi GuiAPI => (GuiApi)_apiContainer.Gui;
private readonly InputManager _inputManager;
private readonly MainForm _mainForm;
private Lua _lua = new();
private LuaThread _currThread;
private readonly NLuaTableHelper _th;
private static Action<object[]> _logToLuaConsoleCallback = a => Console.WriteLine("a Lua lib is logging during init and the console lib hasn't been initialised yet");
private FormsLuaLibrary FormsLibrary => (FormsLuaLibrary)Libraries[typeof(FormsLuaLibrary)];
public LuaDocumentation Docs { get; } = new LuaDocumentation();
private EmulationLuaLibrary EmulationLuaLibrary => (EmulationLuaLibrary)Libraries[typeof(EmulationLuaLibrary)];
public string EngineName => "NLua+Lua";
public bool IsRebootingCore { get; set; }
public bool IsUpdateSupressed { get; set; }
private readonly IDictionary<Type, LuaLibraryBase> Libraries = new Dictionary<Type, LuaLibraryBase>();
private EventWaitHandle LuaWait;
public PathEntryCollection PathEntries { get; private set; }
public LuaFileList ScriptList { get; }
private static void LogToLuaConsole(object outputs) => _logToLuaConsoleCallback(new[] { outputs });
public NLuaTableHelper GetTableHelper() => _th;
public void Restart(
IEmulatorServiceProvider newServiceProvider,
Config config,
IEmulator emulator,
IGameInfo game)
{
_apiContainer = ApiManager.RestartLua(newServiceProvider, LogToLuaConsole, _mainForm, _displayManager, _inputManager, _mainForm.MovieSession, _mainForm.Tools, config, emulator, game);
PathEntries = config.PathEntries;
foreach (var lib in Libraries.Values)
{
lib.APIs = _apiContainer;
Debug.Assert(ServiceInjector.UpdateServices(newServiceProvider, lib, mayCache: true));
lib.Restarted();
}
}
public bool FrameAdvanceRequested { get; private set; }
public LuaFunctionList RegisteredFunctions { get; }
public void CallSaveStateEvent(string name)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
{
lf.Call(name);
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onsavestate\nError message: {e.Message}");
}
}
public void CallLoadStateEvent(string name)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList())
{
lf.Call(name);
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onloadstate\nError message: {e.Message}");
}
}
public void CallFrameBeforeEvent()
{
if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList())
{
lf.Call();
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onframestart\nError message: {e.Message}");
}
}
public void CallFrameAfterEvent()
{
if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
{
lf.Call();
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onframeend\nError message: {e.Message}");
}
}
public void CallExitEvent(LuaFile lf)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
foreach (var exitCallback in RegisteredFunctions
.Where(l => l.Event == NamedLuaFunction.EVENT_TYPE_ENGINESTOP
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
.ToList())
{
exitCallback.Call();
}
}
public void Close()
{
foreach (var closeCallback in RegisteredFunctions
.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE)
.ToList())
{
closeCallback.Call();
}
RegisteredFunctions.Clear(_mainForm.Emulator);
ScriptList.Clear();
FormsLibrary.DestroyAll();
_lua.Dispose();
_lua = null;
}
public INamedLuaFunction CreateAndRegisterNamedFunction(
LuaFunction function,
string theEvent,
Action<string> logCallback,
LuaFile luaFile,
string name = null)
{
var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile,
() => { _lua.NewThread(out var thread); return thread; }, name);
RegisteredFunctions.Add(nlf);
return nlf;
}
public bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate)
{
var nlf = (NamedLuaFunction)RegisteredFunctions.FirstOrDefault(predicate);
if (nlf == null) return false;
RegisteredFunctions.Remove(nlf, _mainForm.Emulator);
return true;
}
public LuaThread SpawnCoroutine(string file)
{
var content = File.ReadAllText(file);
var main = _lua.LoadString(content, "main");
_lua.NewThread(main, out var ret);
return ret;
}
public void SpawnAndSetFileThread(string pathToLoad, LuaFile lf)
=> lf.Thread = SpawnCoroutine(pathToLoad);
public void ExecuteString(string command)
=> _lua.DoString(command);
public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
{
_currThread = lf.Thread;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
LuaLibraryBase.SetCurrentThread(lf);
var execResult = _currThread.Resume();
_currThread = null;
var result = execResult switch
{
LuaStatus.OK => (WaitForFrame: false, Terminated: true),
LuaStatus.Yield => (WaitForFrame: FrameAdvanceRequested, Terminated: false),
_ => throw new InvalidOperationException($"{nameof(_currThread.Resume)}() returned {execResult}?")
};
FrameAdvanceRequested = false;
return result;
}
finally
{
LuaLibraryBase.ClearCurrentThread();
}
}
public static void Print(params object[] outputs)
{
_logToLuaConsoleCallback(outputs);
}
private void FrameAdvance()
{
FrameAdvanceRequested = true;
_currThread.Yield();
}
private void EmuYield()
{
_currThread.Yield();
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using NLua;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk
{
public class LuaLibraries : ILuaLibraries
{
public LuaLibraries(
LuaFileList scriptList,
LuaFunctionList registeredFuncList,
IEmulatorServiceProvider serviceProvider,
MainForm mainForm,
DisplayManagerBase displayManager,
InputManager inputManager,
Config config,
IEmulator emulator,
IGameInfo game)
{
void EnumerateLuaFunctions(string name, Type type, LuaLibraryBase instance)
{
if (instance != null) _lua.NewTable(name);
foreach (var method in type.GetMethods())
{
var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false);
if (foundAttrs.Length == 0) continue;
if (instance != null) _lua.RegisterFunction($"{name}.{((LuaMethodAttribute)foundAttrs[0]).Name}", instance, method);
LibraryFunction libFunc = new(
name,
type.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>()
.Select(descAttr => descAttr.Description).FirstOrDefault() ?? string.Empty,
method
);
Docs.Add(libFunc);
}
}
_th = new NLuaTableHelper(_lua, LogToLuaConsole);
_displayManager = displayManager;
_inputManager = inputManager;
_mainForm = mainForm;
LuaWait = new AutoResetEvent(false);
PathEntries = config.PathEntries;
RegisteredFunctions = registeredFuncList;
ScriptList = scriptList;
Docs.Clear();
_apiContainer = ApiManager.RestartLua(serviceProvider, LogToLuaConsole, _mainForm, _displayManager, _inputManager, _mainForm.MovieSession, _mainForm.Tools, config, emulator, game);
// Register lua libraries
foreach (var lib in Client.Common.ReflectionCache.Types.Concat(EmuHawk.ReflectionCache.Types)
.Where(t => typeof(LuaLibraryBase).IsAssignableFrom(t) && t.IsSealed && ServiceInjector.IsAvailable(serviceProvider, t)))
{
if (VersionInfo.DeveloperBuild
|| lib.GetCustomAttribute<LuaLibraryAttribute>(inherit: false)?.Released is not false)
{
var instance = (LuaLibraryBase)Activator.CreateInstance(lib, this, _apiContainer, (Action<string>)LogToLuaConsole);
if (!ServiceInjector.UpdateServices(serviceProvider, instance, mayCache: true)) throw new Exception("Lua lib has required service(s) that can't be fulfilled");
// TODO: make EmuHawk libraries have a base class with common properties such as this
// and inject them here
if (instance is ClientLuaLibrary clientLib)
{
clientLib.MainForm = _mainForm;
}
else if (instance is ConsoleLuaLibrary consoleLib)
{
consoleLib.Tools = _mainForm.Tools;
_logToLuaConsoleCallback = consoleLib.Log;
}
else if (instance is FormsLuaLibrary formsLib)
{
formsLib.MainForm = _mainForm;
}
else if (instance is GuiLuaLibrary guiLib)
{
// emu lib may be null now, depending on order of ReflectionCache.Types, but definitely won't be null when this is called
guiLib.CreateLuaCanvasCallback = (width, height, x, y) =>
{
var canvas = new LuaCanvas(EmulationLuaLibrary, width, height, x, y, _th, LogToLuaConsole);
canvas.Show();
return _th.ObjectToTable(canvas);
};
}
else if (instance is TAStudioLuaLibrary tastudioLib)
{
tastudioLib.Tools = _mainForm.Tools;
}
EnumerateLuaFunctions(instance.Name, lib, instance);
Libraries.Add(lib, instance);
}
}
_lua.RegisterFunction("print", this, typeof(LuaLibraries).GetMethod(nameof(Print)));
var packageTable = (LuaTable) _lua["package"];
var luaPath = PathEntries.LuaAbsolutePath();
if (OSTailoredCode.IsUnixHost)
{
// add %exe%/Lua to library resolution pathset (LUA_PATH)
// this is done already on windows, but not on linux it seems?
packageTable["path"] = $"{luaPath}/?.lua;{luaPath}?/init.lua;{packageTable["path"]}";
// we need to modifiy the cpath so it looks at our lua dir too, and remove the relative pathing
// we do this on Windows too, but keep in mind Linux uses .so and Windows use .dll
// TODO: Does the relative pathing issue Windows has also affect Linux? I'd assume so...
packageTable["cpath"] = $"{luaPath}/?.so;{luaPath}/loadall.so;{packageTable["cpath"]}";
packageTable["cpath"] = ((string)packageTable["cpath"]).Replace(";./?.so", "");
}
else
{
packageTable["cpath"] = $"{luaPath}\\?.dll;{luaPath}\\loadall.dll;{packageTable["cpath"]}";
packageTable["cpath"] = ((string)packageTable["cpath"]).Replace(";.\\?.dll", "");
}
EmulationLuaLibrary.FrameAdvanceCallback = FrameAdvance;
EmulationLuaLibrary.YieldCallback = EmuYield;
EnumerateLuaFunctions(nameof(LuaCanvas), typeof(LuaCanvas), null); // add LuaCanvas to Lua function reference table
}
private ApiContainer _apiContainer;
private readonly DisplayManagerBase _displayManager;
private GuiApi GuiAPI => (GuiApi)_apiContainer.Gui;
private readonly InputManager _inputManager;
private readonly MainForm _mainForm;
private Lua _lua = new();
private LuaThread _currThread;
private readonly NLuaTableHelper _th;
private static Action<object[]> _logToLuaConsoleCallback = a => Console.WriteLine("a Lua lib is logging during init and the console lib hasn't been initialised yet");
private FormsLuaLibrary FormsLibrary => (FormsLuaLibrary)Libraries[typeof(FormsLuaLibrary)];
public LuaDocumentation Docs { get; } = new LuaDocumentation();
private EmulationLuaLibrary EmulationLuaLibrary => (EmulationLuaLibrary)Libraries[typeof(EmulationLuaLibrary)];
public string EngineName => "NLua+Lua";
public bool IsRebootingCore { get; set; }
public bool IsUpdateSupressed { get; set; }
private readonly IDictionary<Type, LuaLibraryBase> Libraries = new Dictionary<Type, LuaLibraryBase>();
private EventWaitHandle LuaWait;
public PathEntryCollection PathEntries { get; private set; }
public LuaFileList ScriptList { get; }
private static void LogToLuaConsole(object outputs) => _logToLuaConsoleCallback(new[] { outputs });
public NLuaTableHelper GetTableHelper() => _th;
public void Restart(
IEmulatorServiceProvider newServiceProvider,
Config config,
IEmulator emulator,
IGameInfo game)
{
_apiContainer = ApiManager.RestartLua(newServiceProvider, LogToLuaConsole, _mainForm, _displayManager, _inputManager, _mainForm.MovieSession, _mainForm.Tools, config, emulator, game);
PathEntries = config.PathEntries;
foreach (var lib in Libraries.Values)
{
lib.APIs = _apiContainer;
Debug.Assert(ServiceInjector.UpdateServices(newServiceProvider, lib, mayCache: true));
lib.Restarted();
}
}
public bool FrameAdvanceRequested { get; private set; }
public LuaFunctionList RegisteredFunctions { get; }
public void CallSaveStateEvent(string name)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
{
lf.Call(name);
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onsavestate\nError message: {e.Message}");
}
}
public void CallLoadStateEvent(string name)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList())
{
lf.Call(name);
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onloadstate\nError message: {e.Message}");
}
}
public void CallFrameBeforeEvent()
{
if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList())
{
lf.Call();
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onframestart\nError message: {e.Message}");
}
}
public void CallFrameAfterEvent()
{
if (IsUpdateSupressed) return;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
{
lf.Call();
}
}
catch (Exception e)
{
LogToLuaConsole($"error running function attached by lua function event.onframeend\nError message: {e.Message}");
}
}
public void CallExitEvent(LuaFile lf)
{
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
foreach (var exitCallback in RegisteredFunctions
.Where(l => l.Event == NamedLuaFunction.EVENT_TYPE_ENGINESTOP
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
.ToList())
{
exitCallback.Call();
}
}
public void Close()
{
foreach (var closeCallback in RegisteredFunctions
.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE)
.ToList())
{
closeCallback.Call();
}
RegisteredFunctions.Clear(_mainForm.Emulator);
ScriptList.Clear();
FormsLibrary.DestroyAll();
_lua.Dispose();
_lua = null;
}
public INamedLuaFunction CreateAndRegisterNamedFunction(
LuaFunction function,
string theEvent,
Action<string> logCallback,
LuaFile luaFile,
string name = null)
{
var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile,
() => { _lua.NewThread(out var thread); return thread; }, name);
RegisteredFunctions.Add(nlf);
return nlf;
}
public bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate)
{
var nlf = (NamedLuaFunction)RegisteredFunctions.FirstOrDefault(predicate);
if (nlf == null) return false;
RegisteredFunctions.Remove(nlf, _mainForm.Emulator);
return true;
}
public LuaThread SpawnCoroutine(string file)
{
var content = File.ReadAllText(file);
var main = _lua.LoadString(content, "main");
_lua.NewThread(main, out var ret);
return ret;
}
public void SpawnAndSetFileThread(string pathToLoad, LuaFile lf)
=> lf.Thread = SpawnCoroutine(pathToLoad);
public void ExecuteString(string command)
=> _lua.DoString(command);
public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
{
_currThread = lf.Thread;
using var luaAutoUnlockHack = GuiAPI.ThisIsTheLuaAutoUnlockHack();
try
{
LuaLibraryBase.SetCurrentThread(lf);
var execResult = _currThread.Resume();
_currThread = null;
var result = execResult switch
{
LuaStatus.OK => (WaitForFrame: false, Terminated: true),
LuaStatus.Yield => (WaitForFrame: FrameAdvanceRequested, Terminated: false),
_ => throw new InvalidOperationException($"{nameof(_currThread.Resume)}() returned {execResult}?")
};
FrameAdvanceRequested = false;
return result;
}
finally
{
LuaLibraryBase.ClearCurrentThread();
}
}
public static void Print(params object[] outputs)
{
_logToLuaConsoleCallback(outputs);
}
private void FrameAdvance()
{
FrameAdvanceRequested = true;
_currThread.Yield();
}
private void EmuYield()
{
_currThread.Yield();
}
}
}