Skip to content

Commit

Permalink
Pass TestScriptsDoNotShareGlobals. Fixes issue #3036.
Browse files Browse the repository at this point in the history
  • Loading branch information
SuuperW committed Sep 25, 2023
1 parent a959aa8 commit 3fe5549
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 46 deletions.
4 changes: 3 additions & 1 deletion src/BizHawk.Client.Common/lua/ILuaLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ INamedLuaFunction CreateAndRegisterNamedFunction(

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

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

void ExecuteString(string command);

Expand All @@ -57,5 +57,7 @@ INamedLuaFunction CreateAndRegisterNamedFunction(
void EnableLuaFile(LuaFile item);

void DisableLuaScript(LuaFile file);

Lua GetCurrentLua();
}
}
140 changes: 98 additions & 42 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,55 @@ 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)
{
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
{
LuaLibraryBase.SetCurrentThread(lf);

var execResult = _currThread.Resume();

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

Expand Down Expand Up @@ -368,7 +424,7 @@ public void EnableLuaFile(LuaFile item)
{
LuaSandbox.Sandbox(null, () =>
{
SpawnAndSetFileThread(item.Path, item);
SpawnAndSetFileThread(item);
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
2 changes: 1 addition & 1 deletion src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public override void Restart()
{
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;
}, () =>
Expand Down

0 comments on commit 3fe5549

Please sign in to comment.