BizHawk/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs

138 lines
3.5 KiB
C#

using NLua;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
public sealed class NamedLuaFunction : INamedLuaFunction
{
public const string EVENT_TYPE_CONSOLECLOSE = "OnConsoleClose";
public const string EVENT_TYPE_ENGINESTOP = "OnExit";
public const string EVENT_TYPE_INPUTPOLL = "OnInputPoll";
public const string EVENT_TYPE_LOADSTATE = "OnSavestateLoad";
public const string EVENT_TYPE_MEMEXEC = "OnMemoryExecute";
public const string EVENT_TYPE_MEMEXECANY = "OnMemoryExecuteAny";
public const string EVENT_TYPE_MEMREAD = "OnMemoryRead";
public const string EVENT_TYPE_MEMWRITE = "OnMemoryWrite";
public const string EVENT_TYPE_POSTFRAME = "OnFrameEnd";
public const string EVENT_TYPE_PREFRAME = "OnFrameStart";
public const string EVENT_TYPE_SAVESTATE = "OnSavestateSave";
private readonly LuaFunction _function;
public NamedLuaFunction(LuaFunction function, string theEvent, Action<string> logCallback, LuaFile luaFile,
Func<LuaThread> createThreadCallback, ILuaLibraries luaLibraries, string name = null)
{
_function = function;
Name = name ?? "Anonymous";
Event = theEvent;
CreateThreadCallback = createThreadCallback;
LuaLibraries = luaLibraries;
// When would a file be null?
// When a script is loaded with a callback, but no infinite loop so it closes
// Then that callback proceeds to register more callbacks
// In these situations, we will generate a thread for this new callback on the fly here
// Scenarios like this suggest that a thread being managed by a LuaFile is a bad idea,
// and we should refactor
if (luaFile == null)
{
DetachFromScript();
}
else
{
LuaFile = luaFile;
}
Guid = Guid.NewGuid();
Callback = args =>
{
try
{
return _function.Call(args);
}
catch (Exception ex)
{
logCallback($"error running function attached by the event {Event}\nError message: {ex.Message}");
}
return null;
};
InputCallback = () =>
{
LuaLibraries.IsInInputOrMemoryCallback = true;
try
{
Callback(Array.Empty<object>());
}
finally
{
LuaLibraries.IsInInputOrMemoryCallback = false;
}
};
MemCallback = (addr, val, flags) =>
{
LuaLibraries.IsInInputOrMemoryCallback = true;
try
{
return Callback([ addr, val, flags ]) is [ long n ] ? unchecked((uint) n) : null;
}
finally
{
LuaLibraries.IsInInputOrMemoryCallback = false;
}
};
}
public void DetachFromScript()
{
var thread = CreateThreadCallback();
// Current dir will have to do for now, but this will inevitably not be desired
// Users will expect it to be the same directly as the thread that spawned this callback
// But how do we know what that directory was?
LuaSandbox.CreateSandbox(thread, ".");
LuaFile = new LuaFile(".") { Thread = thread };
}
public Guid Guid { get; }
public string Name { get; }
public LuaFile LuaFile { get; private set; }
/// <summary>
/// HACK
/// </summary>
private ILuaLibraries LuaLibraries { get; }
private Func<LuaThread> CreateThreadCallback { get; }
public string Event { get; }
private Func<object[], object[]> Callback { get; }
public Action InputCallback { get; }
public MemoryCallbackDelegate MemCallback { get; }
public void Call(string name = null)
{
LuaSandbox.Sandbox(LuaFile.Thread, () =>
{
_function.Call(name);
});
}
}
}