From 8c7b0b5eba4850c3acd610ddf05b75148aebda6b Mon Sep 17 00:00:00 2001 From: SuuperW Date: Tue, 19 Sep 2023 22:29:41 -0500 Subject: [PATCH] Pass TestScriptsDoNotShareGlobals. Fixes issue #3036. --- .../lua/ILuaLibraries.cs | 4 +- .../lua/LuaLibrariesBase.cs | 140 ++++++++++++------ .../lua/NLuaTableHelper.cs | 14 +- .../lua/NamedLuaFunction.cs | 8 +- .../tools/Lua/LuaConsole.cs | 2 +- 5 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/ILuaLibraries.cs b/src/BizHawk.Client.Common/lua/ILuaLibraries.cs index 06d04c7466..f33aa9c37b 100644 --- a/src/BizHawk.Client.Common/lua/ILuaLibraries.cs +++ b/src/BizHawk.Client.Common/lua/ILuaLibraries.cs @@ -48,7 +48,7 @@ namespace BizHawk.Client.Common bool RemoveNamedFunctionMatching(Func predicate); - void SpawnAndSetFileThread(string pathToLoad, LuaFile lf); + void SpawnAndSetFileThread(LuaFile lf); void ExecuteString(string command); @@ -57,5 +57,7 @@ namespace BizHawk.Client.Common void EnableLuaFile(LuaFile item); void DisableLuaScript(LuaFile file); + + Lua GetCurrentLua(); } } \ No newline at end of file diff --git a/src/BizHawk.Client.Common/lua/LuaLibrariesBase.cs b/src/BizHawk.Client.Common/lua/LuaLibrariesBase.cs index 4c7f609840..3fb7f99d2b 100644 --- a/src/BizHawk.Client.Common/lua/LuaLibrariesBase.cs +++ b/src/BizHawk.Client.Common/lua/LuaLibrariesBase.cs @@ -25,7 +25,7 @@ namespace BizHawk.Client.Common Config config, IGameInfo game) { - _th = new NLuaTableHelper(_lua, LogToLuaConsole); + _th = new NLuaTableHelper(this, LogToLuaConsole); _displayManager = displayManager; _inputManager = inputManager; _mainFormApi = mainFormApi; @@ -36,38 +36,31 @@ namespace BizHawk.Client.Common 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() @@ -109,6 +102,35 @@ namespace BizHawk.Client.Common } } + 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 _activeLuas = new(); + + private Lua _luaWithoutFile = new(); + + private List _tablesForFunctions = new(); + private Dictionary _functionsToRegister = new(); + private ApiContainer _apiContainer; private readonly DisplayManagerBase _displayManager; @@ -119,9 +141,10 @@ namespace BizHawk.Client.Common private readonly IMainFormForApi _mainFormApi; - private Lua _lua = new(); private LuaThread _currThread; + private Lua _currLua; + private readonly NLuaTableHelper _th; protected Action _logToLuaConsoleCallback = a => Console.WriteLine("a Lua lib is logging during init and the console lib hasn't been initialised yet"); @@ -167,13 +190,18 @@ namespace BizHawk.Client.Common 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) @@ -190,7 +218,7 @@ namespace BizHawk.Client.Common { foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList()) { - lf.Call(name); + CallFunction(lf, name); } } catch (Exception e) @@ -211,7 +239,7 @@ namespace BizHawk.Client.Common { foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList()) { - lf.Call(); + CallFunction(lf); } } catch (Exception e) @@ -228,7 +256,7 @@ namespace BizHawk.Client.Common { foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList()) { - lf.Call(); + CallFunction(lf); } } catch (Exception e) @@ -246,7 +274,7 @@ namespace BizHawk.Client.Common && (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread))) .ToList()) { - exitCallback.Call(); + CallFunction(exitCallback); } } @@ -256,7 +284,7 @@ namespace BizHawk.Client.Common .Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE) .ToList()) { - closeCallback.Call(); + CallFunction(closeCallback); } RegisteredFunctions.Clear(_mainFormApi.Emulator); @@ -268,8 +296,10 @@ namespace BizHawk.Client.Common disposable.Dispose(); } - _lua.Dispose(); - _lua = null; + _luaWithoutFile.Dispose(); + _luaWithoutFile = null; + foreach (Lua lua in _activeLuas.Values) + lua.Dispose(); } public INamedLuaFunction CreateAndRegisterNamedFunction( @@ -280,7 +310,7 @@ namespace BizHawk.Client.Common 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; } @@ -293,23 +323,48 @@ namespace BizHawk.Client.Common 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) + { + if (_activeLuas.ContainsKey(lf)) + _activeLuas[lf].Dispose(); + + 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) - => _lua.DoString(command); + => _luaWithoutFile.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 { @@ -317,7 +372,6 @@ namespace BizHawk.Client.Common var execResult = _currThread.Resume(); - _currThread = null; var result = execResult switch { LuaStatus.OK => (WaitForFrame: false, Terminated: true), @@ -331,6 +385,8 @@ namespace BizHawk.Client.Common finally { LuaLibraryBase.ClearCurrentThread(); + _currThread = null; + _currLua = null; } } @@ -368,7 +424,7 @@ namespace BizHawk.Client.Common { LuaSandbox.Sandbox(null, () => { - SpawnAndSetFileThread(item.Path, item); + SpawnAndSetFileThread(item); LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(item.Path)); }, () => { diff --git a/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs b/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs index 0ef2ddab79..713d0d0145 100644 --- a/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs +++ b/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs @@ -14,6 +14,8 @@ namespace BizHawk.Client.Common { private readonly Action _logCallback; + private readonly ILuaLibraries _luaLibraries; + private readonly Lua _lua; public NLuaTableHelper(Lua lua, Action logCallback) @@ -22,7 +24,15 @@ namespace BizHawk.Client.Common _lua = lua; } - public LuaTable CreateTable() => _lua.NewTable(); + public NLuaTableHelper(ILuaLibraries luaLibraries, Action logCallback) + { + _logCallback = logCallback; + _luaLibraries = luaLibraries; + } + + private Lua GetLua() => _luaLibraries?.GetCurrentLua() ?? _lua; + + public LuaTable CreateTable() => GetLua().NewTable(); public LuaTable DictToTable(IReadOnlyDictionary dictionary) { @@ -60,7 +70,7 @@ namespace BizHawk.Client.Common { 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 diff --git a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs index 324bed0036..522f882ae9 100644 --- a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs +++ b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs @@ -73,12 +73,8 @@ namespace BizHawk.Client.Common 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; } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index 9b2ef3505a..64f32d6804 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -228,7 +228,7 @@ namespace BizHawk.Client.EmuHawk { LuaSandbox.Sandbox(file.Thread, () => { - LuaImp.SpawnAndSetFileThread(file.Path, file); + LuaImp.SpawnAndSetFileThread(file); LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(file.Path)); file.State = LuaFile.RunState.Running; }, () =>