Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lua no global sharing #3780

Draft
wants to merge 5 commits into
base: MultiScriptDrawing
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/BizHawk.Client.Common/lua/ILuaLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ INamedLuaFunction CreateAndRegisterNamedFunction(

bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate);

void SpawnAndSetFileThread(string pathToLoad, LuaFile lf);
void SpawnAndSetFileThread(LuaFile lf, bool shareGlobals);

void ExecuteString(string command);
void ExecuteString(string command, LuaFile lf = null);

(bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf);

void EnableLuaFile(LuaFile item);
void EnableLuaFile(LuaFile item, bool shareGlobals);

void DisableLuaScript(LuaFile file);

Lua GetCurrentLua();
}
}
2 changes: 1 addition & 1 deletion src/BizHawk.Client.Common/lua/LuaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void Toggle()
FrameWaiting = false;
break;
default:
State = RunState.Disabled;
Stop();
break;
}
}
Expand Down
157 changes: 113 additions & 44 deletions src/BizHawk.Client.Common/lua/LuaLibrariesBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public LuaLibrariesBase(
Config config,
IGameInfo game)
{
_th = new NLuaTableHelper(_lua, LogToLuaConsole);
_th = new NLuaTableHelper(this, LogToLuaConsole);
_displayManager = displayManager;
_inputManager = inputManager;
_mainFormApi = mainFormApi;
Expand All @@ -36,38 +36,31 @@ public LuaLibrariesBase(
Docs.Clear();
_apiContainer = ApiManager.RestartLua(_mainFormApi.Emulator.ServiceProvider, LogToLuaConsole, _mainFormApi, _displayManager, _inputManager, _mainFormApi.MovieSession, _mainFormApi.Tools, config, _mainFormApi.Emulator, game);

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", "");
}
UpdatePackageTable(_luaWithoutFile);

_lua.RegisterFunction("print", this, typeof(LuaLibrariesBase).GetMethod(nameof(Print)));
_luaWithoutFile.RegisterFunction("print", this, typeof(LuaLibrariesBase).GetMethod(nameof(Print)));

RegisterLuaLibraries(Common.ReflectionCache.Types);
RegisterLuaLibraries(ReflectionCache.Types);
}

protected void EnumerateLuaFunctions(string name, Type type, LuaLibraryBase instance)
{
if (instance != null) _lua.NewTable(name);
if (instance != null)
{
_luaWithoutFile.NewTable(name);
_tablesForFunctions.Add(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);
if (instance != null)
{
string path = $"{name}.{((LuaMethodAttribute)foundAttrs[0]).Name}";
_luaWithoutFile.RegisterFunction(path, instance, method);
_functionsToRegister[path] = (instance, method);

}
LibraryFunction libFunc = new(
name,
type.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>()
Expand Down Expand Up @@ -109,6 +102,35 @@ protected virtual void HandleSpecialLuaLibraryProperties(LuaLibraryBase library)
}
}

private void UpdatePackageTable(Lua lua)
{
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", "");
}
}

private Dictionary<LuaFile, Lua> _activeLuas = new();

private Lua _luaWithoutFile = new();

private List<string> _tablesForFunctions = new();
private Dictionary<string, (LuaLibraryBase Lib, MethodInfo Func)> _functionsToRegister = new();

private ApiContainer _apiContainer;

private readonly DisplayManagerBase _displayManager;
Expand All @@ -119,9 +141,10 @@ protected virtual void HandleSpecialLuaLibraryProperties(LuaLibraryBase library)

private readonly IMainFormForApi _mainFormApi;

private Lua _lua = new();
private LuaThread _currThread;

private Lua _currLua;

private readonly NLuaTableHelper _th;

protected Action<object[]> _logToLuaConsoleCallback = a => Console.WriteLine("a Lua lib is logging during init and the console lib hasn't been initialised yet");
Expand Down Expand Up @@ -167,13 +190,18 @@ public void Restart(Config config, IGameInfo game)

public LuaFunctionList RegisteredFunctions { get; }

public Lua GetCurrentLua()
{
return _currLua ?? _luaWithoutFile;
}

public void CallSaveStateEvent(string name)
{
try
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
{
lf.Call(name);
CallFunction(lf, name);
}
}
catch (Exception e)
Expand All @@ -190,7 +218,7 @@ public void CallLoadStateEvent(string name)
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList())
{
lf.Call(name);
CallFunction(lf, name);
}
}
catch (Exception e)
Expand All @@ -211,7 +239,7 @@ public void CallFrameBeforeEvent()
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList())
{
lf.Call();
CallFunction(lf);
}
}
catch (Exception e)
Expand All @@ -228,7 +256,7 @@ public void CallFrameAfterEvent()
{
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
{
lf.Call();
CallFunction(lf);
}
}
catch (Exception e)
Expand All @@ -246,7 +274,7 @@ public void CallExitEvent(LuaFile lf)
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
.ToList())
{
exitCallback.Call();
CallFunction(exitCallback);
}
}

Expand All @@ -256,7 +284,7 @@ public void Close()
.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE)
.ToList())
{
closeCallback.Call();
CallFunction(closeCallback);
}

RegisteredFunctions.Clear(_mainFormApi.Emulator);
Expand All @@ -268,8 +296,10 @@ public void Close()
disposable.Dispose();
}

_lua.Dispose();
_lua = null;
_luaWithoutFile.Dispose();
_luaWithoutFile = null;
foreach (Lua lua in _activeLuas.Values)
lua.Dispose();
}

public INamedLuaFunction CreateAndRegisterNamedFunction(
Expand All @@ -280,7 +310,7 @@ public INamedLuaFunction CreateAndRegisterNamedFunction(
string name = null)
{
var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile,
() => { _lua.NewThread(out var thread); return thread; }, name);
() => { _activeLuas[luaFile].NewThread(out var thread); return thread; }, name);
RegisteredFunctions.Add(nlf);
return nlf;
}
Expand All @@ -293,31 +323,68 @@ public bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate)
return true;
}

public LuaThread SpawnCoroutine(string file)
public LuaThread SpawnCoroutine(LuaFile file)
{
var content = File.ReadAllText(file);
var main = _lua.LoadString(content, "main");
_lua.NewThread(main, out var ret);
var content = File.ReadAllText(file.Path);
var main = _activeLuas[file].LoadString(content, "main");
_activeLuas[file].NewThread(main, out var ret);
return ret;
}

public void SpawnAndSetFileThread(string pathToLoad, LuaFile lf)
=> lf.Thread = SpawnCoroutine(pathToLoad);
public void SpawnAndSetFileThread(LuaFile lf, bool shareGlobals)
{
if (_activeLuas.TryGetValue(lf, out Lua al) && al != _luaWithoutFile)
_activeLuas[lf].Dispose();

if (shareGlobals)
_activeLuas[lf] = _luaWithoutFile;
else
{
Lua lua = new();
UpdatePackageTable(lua);
lua.RegisterFunction("print", this, typeof(LuaLibrariesBase).GetMethod(nameof(Print)));
// We cannot copy tables from one Lua to another, unfortunately. Directly assigning them doesn't work at all.
// Transferring each individual value to new tables mostly works, but not always.
// For example event.onframeend would receive bad LuaFunction references.
foreach (string name in _tablesForFunctions)
lua.NewTable(name);
foreach (var item in _functionsToRegister)
lua.RegisterFunction(item.Key, item.Value.Lib, item.Value.Func);
_activeLuas[lf] = lua;
}

lf.Thread = SpawnCoroutine(lf);
}

public void ExecuteString(string command, LuaFile lf = null)
{
Lua lua = _luaWithoutFile;
if (lf != null)
{
if (_activeLuas.TryGetValue(lf, out Lua val))
lua = val;
}
lua.DoString(command);
}

public void ExecuteString(string command)
=> _lua.DoString(command);
private void CallFunction(NamedLuaFunction func, string name = null)
{
_currLua = _activeLuas[func.LuaFile];
func.Call(name);
_currLua = null;
}

public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
{
_currThread = lf.Thread;
_currLua = _activeLuas[lf];

try
{
LuaLibraryBase.SetCurrentThread(lf);

var execResult = _currThread.Resume();

_currThread = null;
var result = execResult switch
{
LuaStatus.OK => (WaitForFrame: false, Terminated: true),
Expand All @@ -331,6 +398,8 @@ public void ExecuteString(string command)
finally
{
LuaLibraryBase.ClearCurrentThread();
_currThread = null;
_currLua = null;
}
}

Expand Down Expand Up @@ -364,11 +433,11 @@ public void DisableLuaScript(LuaFile file)
}
}

public void EnableLuaFile(LuaFile item)
public void EnableLuaFile(LuaFile item, bool shareGlobals)
{
LuaSandbox.Sandbox(null, () =>
{
SpawnAndSetFileThread(item.Path, item);
SpawnAndSetFileThread(item, shareGlobals);
LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(item.Path));
}, () =>
{
Expand Down
14 changes: 12 additions & 2 deletions src/BizHawk.Client.Common/lua/NLuaTableHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public sealed class NLuaTableHelper
{
private readonly Action<string> _logCallback;

private readonly ILuaLibraries _luaLibraries;

private readonly Lua _lua;

public NLuaTableHelper(Lua lua, Action<string> logCallback)
Expand All @@ -22,7 +24,15 @@ public NLuaTableHelper(Lua lua, Action<string> logCallback)
_lua = lua;
}

public LuaTable CreateTable() => _lua.NewTable();
public NLuaTableHelper(ILuaLibraries luaLibraries, Action<string> logCallback)
{
_logCallback = logCallback;
_luaLibraries = luaLibraries;
}

private Lua GetLua() => _luaLibraries?.GetCurrentLua() ?? _lua;

public LuaTable CreateTable() => GetLua().NewTable();

public LuaTable DictToTable<T>(IReadOnlyDictionary<string, T> dictionary)
{
Expand Down Expand Up @@ -60,7 +70,7 @@ public LuaTable ObjectToTable(object obj)
{
if (!method.IsPublic) continue;
var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false);
table[method.Name] = _lua.RegisterFunction(
table[method.Name] = GetLua().RegisterFunction(
foundAttrs.Length == 0 ? string.Empty : ((LuaMethodAttribute) foundAttrs[0]).Name, // empty string will default to the actual method name
obj,
method
Expand Down
8 changes: 2 additions & 6 deletions src/BizHawk.Client.Common/lua/NamedLuaFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,8 @@ public NamedLuaFunction(LuaFunction function, string theEvent, Action<string> lo
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 };
LuaSandbox.CreateSandbox(thread, LuaFile.CurrentDirectory);
LuaFile.Thread = thread;
}

public Guid Guid { get; }
Expand Down
Loading