Pass TestScriptsDoNotShareGlobals. Fixes issue #3036.

This commit is contained in:
SuuperW 2023-09-19 22:29:41 -05:00
parent a959aa887b
commit 8c7b0b5eba
5 changed files with 116 additions and 52 deletions

View File

@ -48,7 +48,7 @@ namespace BizHawk.Client.Common
bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate); bool RemoveNamedFunctionMatching(Func<INamedLuaFunction, bool> predicate);
void SpawnAndSetFileThread(string pathToLoad, LuaFile lf); void SpawnAndSetFileThread(LuaFile lf);
void ExecuteString(string command); void ExecuteString(string command);
@ -57,5 +57,7 @@ namespace BizHawk.Client.Common
void EnableLuaFile(LuaFile item); void EnableLuaFile(LuaFile item);
void DisableLuaScript(LuaFile file); void DisableLuaScript(LuaFile file);
Lua GetCurrentLua();
} }
} }

View File

@ -25,7 +25,7 @@ namespace BizHawk.Client.Common
Config config, Config config,
IGameInfo game) IGameInfo game)
{ {
_th = new NLuaTableHelper(_lua, LogToLuaConsole); _th = new NLuaTableHelper(this, LogToLuaConsole);
_displayManager = displayManager; _displayManager = displayManager;
_inputManager = inputManager; _inputManager = inputManager;
_mainFormApi = mainFormApi; _mainFormApi = mainFormApi;
@ -36,38 +36,31 @@ namespace BizHawk.Client.Common
Docs.Clear(); Docs.Clear();
_apiContainer = ApiManager.RestartLua(_mainFormApi.Emulator.ServiceProvider, LogToLuaConsole, _mainFormApi, _displayManager, _inputManager, _mainFormApi.MovieSession, _mainFormApi.Tools, config, _mainFormApi.Emulator, game); _apiContainer = ApiManager.RestartLua(_mainFormApi.Emulator.ServiceProvider, LogToLuaConsole, _mainFormApi, _displayManager, _inputManager, _mainFormApi.MovieSession, _mainFormApi.Tools, config, _mainFormApi.Emulator, game);
var packageTable = (LuaTable) _lua["package"]; UpdatePackageTable(_luaWithoutFile);
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", "");
}
_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) 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()) foreach (var method in type.GetMethods())
{ {
var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false); var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false);
if (foundAttrs.Length == 0) continue; 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( LibraryFunction libFunc = new(
name, name,
type.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>() type.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>()
@ -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<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 ApiContainer _apiContainer;
private readonly DisplayManagerBase _displayManager; private readonly DisplayManagerBase _displayManager;
@ -119,9 +141,10 @@ namespace BizHawk.Client.Common
private readonly IMainFormForApi _mainFormApi; private readonly IMainFormForApi _mainFormApi;
private Lua _lua = new();
private LuaThread _currThread; private LuaThread _currThread;
private Lua _currLua;
private readonly NLuaTableHelper _th; 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"); protected Action<object[]> _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 LuaFunctionList RegisteredFunctions { get; }
public Lua GetCurrentLua()
{
return _currLua ?? _luaWithoutFile;
}
public void CallSaveStateEvent(string name) public void CallSaveStateEvent(string name)
{ {
try try
{ {
foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_SAVESTATE).ToList())
{ {
lf.Call(name); CallFunction(lf, name);
} }
} }
catch (Exception e) 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()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_LOADSTATE).ToList())
{ {
lf.Call(name); CallFunction(lf, name);
} }
} }
catch (Exception e) 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()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_PREFRAME).ToList())
{ {
lf.Call(); CallFunction(lf);
} }
} }
catch (Exception e) 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()) foreach (var lf in RegisteredFunctions.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_POSTFRAME).ToList())
{ {
lf.Call(); CallFunction(lf);
} }
} }
catch (Exception e) catch (Exception e)
@ -246,7 +274,7 @@ namespace BizHawk.Client.Common
&& (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread))) && (l.LuaFile.Path == lf.Path || ReferenceEquals(l.LuaFile.Thread, lf.Thread)))
.ToList()) .ToList())
{ {
exitCallback.Call(); CallFunction(exitCallback);
} }
} }
@ -256,7 +284,7 @@ namespace BizHawk.Client.Common
.Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE) .Where(static l => l.Event == NamedLuaFunction.EVENT_TYPE_CONSOLECLOSE)
.ToList()) .ToList())
{ {
closeCallback.Call(); CallFunction(closeCallback);
} }
RegisteredFunctions.Clear(_mainFormApi.Emulator); RegisteredFunctions.Clear(_mainFormApi.Emulator);
@ -268,8 +296,10 @@ namespace BizHawk.Client.Common
disposable.Dispose(); disposable.Dispose();
} }
_lua.Dispose(); _luaWithoutFile.Dispose();
_lua = null; _luaWithoutFile = null;
foreach (Lua lua in _activeLuas.Values)
lua.Dispose();
} }
public INamedLuaFunction CreateAndRegisterNamedFunction( public INamedLuaFunction CreateAndRegisterNamedFunction(
@ -280,7 +310,7 @@ namespace BizHawk.Client.Common
string name = null) string name = null)
{ {
var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile, 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); RegisteredFunctions.Add(nlf);
return nlf; return nlf;
} }
@ -293,23 +323,48 @@ namespace BizHawk.Client.Common
return true; return true;
} }
public LuaThread SpawnCoroutine(string file) public LuaThread SpawnCoroutine(LuaFile file)
{ {
var content = File.ReadAllText(file); var content = File.ReadAllText(file.Path);
var main = _lua.LoadString(content, "main"); var main = _activeLuas[file].LoadString(content, "main");
_lua.NewThread(main, out var ret); _activeLuas[file].NewThread(main, out var ret);
return ret; return ret;
} }
public void SpawnAndSetFileThread(string pathToLoad, LuaFile lf) public void SpawnAndSetFileThread(LuaFile lf)
=> lf.Thread = SpawnCoroutine(pathToLoad); {
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) 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) public (bool WaitForFrame, bool Terminated) ResumeScript(LuaFile lf)
{ {
_currThread = lf.Thread; _currThread = lf.Thread;
_currLua = _activeLuas[lf];
try try
{ {
@ -317,7 +372,6 @@ namespace BizHawk.Client.Common
var execResult = _currThread.Resume(); var execResult = _currThread.Resume();
_currThread = null;
var result = execResult switch var result = execResult switch
{ {
LuaStatus.OK => (WaitForFrame: false, Terminated: true), LuaStatus.OK => (WaitForFrame: false, Terminated: true),
@ -331,6 +385,8 @@ namespace BizHawk.Client.Common
finally finally
{ {
LuaLibraryBase.ClearCurrentThread(); LuaLibraryBase.ClearCurrentThread();
_currThread = null;
_currLua = null;
} }
} }
@ -368,7 +424,7 @@ namespace BizHawk.Client.Common
{ {
LuaSandbox.Sandbox(null, () => LuaSandbox.Sandbox(null, () =>
{ {
SpawnAndSetFileThread(item.Path, item); SpawnAndSetFileThread(item);
LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(item.Path)); LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(item.Path));
}, () => }, () =>
{ {

View File

@ -14,6 +14,8 @@ namespace BizHawk.Client.Common
{ {
private readonly Action<string> _logCallback; private readonly Action<string> _logCallback;
private readonly ILuaLibraries _luaLibraries;
private readonly Lua _lua; private readonly Lua _lua;
public NLuaTableHelper(Lua lua, Action<string> logCallback) public NLuaTableHelper(Lua lua, Action<string> logCallback)
@ -22,7 +24,15 @@ namespace BizHawk.Client.Common
_lua = lua; _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) public LuaTable DictToTable<T>(IReadOnlyDictionary<string, T> dictionary)
{ {
@ -60,7 +70,7 @@ namespace BizHawk.Client.Common
{ {
if (!method.IsPublic) continue; if (!method.IsPublic) continue;
var foundAttrs = method.GetCustomAttributes(typeof(LuaMethodAttribute), false); 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 foundAttrs.Length == 0 ? string.Empty : ((LuaMethodAttribute) foundAttrs[0]).Name, // empty string will default to the actual method name
obj, obj,
method method

View File

@ -73,12 +73,8 @@ namespace BizHawk.Client.Common
public void DetachFromScript() public void DetachFromScript()
{ {
var thread = CreateThreadCallback(); var thread = CreateThreadCallback();
LuaSandbox.CreateSandbox(thread, LuaFile.CurrentDirectory);
// Current dir will have to do for now, but this will inevitably not be desired LuaFile.Thread = thread;
// 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 Guid Guid { get; }

View File

@ -228,7 +228,7 @@ namespace BizHawk.Client.EmuHawk
{ {
LuaSandbox.Sandbox(file.Thread, () => LuaSandbox.Sandbox(file.Thread, () =>
{ {
LuaImp.SpawnAndSetFileThread(file.Path, file); LuaImp.SpawnAndSetFileThread(file);
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(file.Path)); LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(file.Path));
file.State = LuaFile.RunState.Running; file.State = LuaFile.RunState.Running;
}, () => }, () =>