From d9dc84425d809af1e020672078af4331a9a120a3 Mon Sep 17 00:00:00 2001 From: peelz Date: Wed, 3 Aug 2022 21:34:41 -0400 Subject: [PATCH] Make LuaCsSetup easier to integrate into unit tests --- .../LuaCs/Lua/LuaCustomConverters.cs | 84 +++++++++---------- .../SharedSource/LuaCs/LuaCsHook.cs | 52 ++++++++---- .../SharedSource/LuaCs/LuaCsSetup.cs | 8 +- 3 files changed, 82 insertions(+), 62 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaCustomConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaCustomConverters.cs index d3366e7d7..55e6e514d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaCustomConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaCustomConverters.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using MoonSharp.Interpreter; using Microsoft.Xna.Framework; using FarseerPhysics.Dynamics; @@ -8,42 +6,47 @@ using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; namespace Barotrauma { - public static class LuaCustomConverters - { - public static void RegisterAll() - { + public delegate DynValue CallLuaFunctionFunc(object function, params object[] args); + + internal static class LuaCustomConverters + { + private static CallLuaFunctionFunc CallLuaFunction; + + public static void Initialize(CallLuaFunctionFunc callLuaFunction) + { + CallLuaFunction = callLuaFunction; + RegisterAction(); RegisterAction(); RegisterAction(); RegisterAction(); - RegisterFunc(); RegisterFunc(); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( DataType.Function, typeof(LuaCsAction), - v => (LuaCsAction)(args => GameMain.LuaCs.CallLuaFunction(v.Function, args))); + v => (LuaCsAction)(args => CallLuaFunction(v.Function, args))); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( DataType.Function, typeof(LuaCsFunc), - v => (LuaCsFunc)(args => GameMain.LuaCs.CallLuaFunction(v.Function, args))); + v => (LuaCsFunc)(args => CallLuaFunction(v.Function, args))); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( DataType.Function, typeof(LuaCsCompatPatchFunc), - v => (LuaCsCompatPatchFunc)((self, args) => GameMain.LuaCs.CallLuaFunction(v.Function, self, args))); + v => (LuaCsCompatPatchFunc)((self, args) => CallLuaFunction(v.Function, self, args))); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( DataType.Function, typeof(LuaCsPatchFunc), - v => (LuaCsPatchFunc)((self, args) => GameMain.LuaCs.CallLuaFunction(v.Function, self, args))); + v => (LuaCsPatchFunc)((self, args) => CallLuaFunction(v.Function, self, args))); #if CLIENT RegisterAction(); RegisterAction(); { - DynValue Call(object function, params object[] arguments) => GameMain.LuaCs.CallLuaFunction(function, arguments); + DynValue Call(object function, params object[] arguments) => CallLuaFunction(function, arguments); void RegisterHandler(Func converter) => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(T), v => converter(v.Function)); @@ -51,71 +54,70 @@ namespace Barotrauma (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIButton.OnClickedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIButton.OnButtonDownHandler)( - () => Call(f).CastToBool())); + () => Call(f).CastToBool())); RegisterHandler(f => (GUIButton.OnPressedHandler)( - () => Call(f).CastToBool())); + () => Call(f).CastToBool())); RegisterHandler(f => (GUIColorPicker.OnColorSelectedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIDropDown.OnSelectedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIListBox.OnSelectedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIListBox.OnRearrangedHandler)( - (a1, a2) => Call(f, a1, a2))); + (a1, a2) => Call(f, a1, a2))); RegisterHandler(f => (GUIListBox.CheckSelectedHandler)( - () => Call(f).ToObject())); + () => Call(f).ToObject())); RegisterHandler(f => (GUINumberInput.OnValueEnteredHandler)( (a1) => Call(f, a1))); RegisterHandler(f => (GUINumberInput.OnValueChangedHandler)( - (a1) => Call(f, a1))); + (a1) => Call(f, a1))); RegisterHandler(f => (GUIProgressBar.ProgressGetterHandler)( - () => (float)(Call(f).CastToNumber() ?? 0))); + () => (float)(Call(f).CastToNumber() ?? 0))); RegisterHandler(f => (GUIRadioButtonGroup.RadioButtonGroupDelegate)( - (a1, a2) => Call(f, a1, a2))); + (a1, a2) => Call(f, a1, a2))); RegisterHandler(f => (GUIScrollBar.OnMovedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUIScrollBar.ScrollConversion)( - (a1, a2) => (float)(Call(f, a1, a2).CastToNumber() ?? 0))); + (a1, a2) => (float)(Call(f, a1, a2).CastToNumber() ?? 0))); RegisterHandler(f => (GUITextBlock.TextGetterHandler)( - () => Call(f, new object[0]).CastToString())); + () => Call(f, new object[0]).CastToString())); RegisterHandler(f => (GUITextBox.OnEnterHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (GUITextBox.OnTextChangedHandler)( - (a1, a2) => Call(f, a1, a2).CastToBool())); + (a1, a2) => Call(f, a1, a2).CastToBool())); RegisterHandler(f => (TextBoxEvent)( - (a1, a2) => Call(f, a1, a2))); + (a1, a2) => Call(f, a1, a2))); RegisterHandler(f => (GUITickBox.OnSelectedHandler)( - (a1) => Call(f, a1).CastToBool())); + (a1) => Call(f, a1).CastToBool())); } #endif - Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Pair), v => { return new Pair((JobPrefab)v.Table.Get(1).ToObject(), (int)v.Table.Get(2).CastToNumber()); }); - Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion((Script script, UInt64 v) => + Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion((Script script, ulong v) => { return DynValue.NewString(v.ToString()); }); - Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.String, typeof(UInt64), v => + Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.String, typeof(ulong), v => { - return UInt64.Parse(v.String); + return ulong.Parse(v.String); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( @@ -185,13 +187,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)(p => GameMain.LuaCs.CallLuaFunction(function, p)); + return (Action)(p => CallLuaFunction(function, p)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)(p => GameMain.LuaCs.CallLuaFunction(function, p)); + return (Action)(p => CallLuaFunction(function, p)); }); } @@ -200,13 +202,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)((a1, a2) => GameMain.LuaCs.CallLuaFunction(function, a1, a2)); + return (Action)((a1, a2) => CallLuaFunction(function, a1, a2)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)((a1, a2) => GameMain.LuaCs.CallLuaFunction(function, a1, a2)); + return (Action)((a1, a2) => CallLuaFunction(function, a1, a2)); }); } @@ -215,13 +217,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)(() => GameMain.LuaCs.CallLuaFunction(function)); + return (Action)(() => CallLuaFunction(function)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)(() => GameMain.LuaCs.CallLuaFunction(function)); + return (Action)(() => CallLuaFunction(function)); }); } @@ -269,7 +271,5 @@ namespace Barotrauma return (Func)((T1 a, T2 b, T3 c, T4 d) => function.Call(a, b, c, d).ToObject()); }); } - } - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs index 1d6f53405..31bf20425 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -541,6 +541,8 @@ namespace Barotrauma private readonly Dictionary registeredPatches = new Dictionary(); + private LuaCsSetup luaCs; + private static LuaCsHook instance; private struct MethodKey : IEquatable @@ -581,9 +583,10 @@ namespace Barotrauma }; } - public LuaCsHook() + internal LuaCsHook(LuaCsSetup luaCs) { instance = this; + this.luaCs = luaCs; } public void Initialize() @@ -722,6 +725,18 @@ namespace Barotrauma { harmony?.UnpatchAll(); + foreach (var (_, patch) in registeredPatches) + { + // Remove references stored in our dynamic types so the generated + // assembly can be garbage-collected. + patch.HarmonyPrefixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + patch.HarmonyPostfixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + } + hookFunctions.Clear(); registeredPatches.Clear(); patchModuleBuilder = null; @@ -737,7 +752,6 @@ namespace Barotrauma [MoonSharpHidden] public T Call(string name, params object[] args) { - if (GameMain.LuaCs == null) throw new InvalidOperationException("Can't call hooks before LuaCsHook is initialized."); if (name == null) throw new ArgumentNullException(name); if (args == null) args = new object[0]; @@ -757,7 +771,7 @@ namespace Barotrauma try { - if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) + if (luaCs.PerformanceCounter.EnablePerformanceCounter) { performanceMeasurement.Start(); } @@ -769,10 +783,10 @@ namespace Barotrauma lastResult = result.ToObject(); } - if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) + if (luaCs.PerformanceCounter.EnablePerformanceCounter) { performanceMeasurement.Stop(); - GameMain.LuaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); + luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); performanceMeasurement.Reset(); } } @@ -840,6 +854,8 @@ namespace Barotrauma private static readonly Regex InvalidIdentifierCharsRegex = new Regex(@"[^\w\d]", RegexOptions.Compiled); + private const string FIELD_LUACS = "LuaCs"; + // If you need to debug this: // - use https://sharplab.io ; it's a very useful for resource for writing IL by hand. // - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime. @@ -876,6 +892,8 @@ namespace Barotrauma : MangleName(original); var typeBuilder = moduleBuilder.DefineType($"Patch_{identifier}_{Guid.NewGuid():N}_{mangledName}", TypeAttributes.Public); + var luaCsField = typeBuilder.DefineField(FIELD_LUACS, typeof(LuaCsSetup), FieldAttributes.Public | FieldAttributes.Static); + var methodName = hookType == HookMethodType.Before ? "HarmonyPrefix" : "HarmonyPostfix"; var il = Emit.BuildMethod( returnType: hookType == HookMethodType.Before ? typeof(bool) : typeof(void), @@ -1094,17 +1112,18 @@ namespace Barotrauma var exception = il.DeclareLocal("exception"); il.StoreLocal(exception); - // IL: var luaCsSetup = GameMain.LuaCs; - var luaCsSetup = il.DeclareLocal("luaCsSetup"); - il.LoadField(typeof(GameMain).GetField(nameof(GameMain.LuaCs), BindingFlags.Public | BindingFlags.Static)); - il.StoreLocal(luaCsSetup); + // IL: if (LuaCs != null) + il.LoadField(luaCsField); + il.If((il) => + { + // IL: LuaCs.HandleException(exception, "", ExceptionType.Lua); + il.LoadField(luaCsField); + il.LoadLocal(exception); + il.LoadConstant(""); + il.LoadConstant((int)ExceptionType.Lua); // underlying enum type is int + il.Call(typeof(LuaCsSetup).GetMethod(nameof(LuaCsSetup.HandleException))); + }); - // IL: luaCsSetup.HandleException(exception, "", ExceptionType.Lua); - il.LoadLocal(luaCsSetup); - il.LoadLocal(exception); - il.LoadConstant(""); - il.LoadConstant((int)ExceptionType.Lua); // underlying enum type is int - il.Call(typeof(LuaCsSetup).GetMethod(nameof(LuaCsSetup.HandleException))); il.EndCatchBlock(catchBlock); il.EndExceptionBlock(exceptionBlock); @@ -1123,6 +1142,7 @@ namespace Barotrauma } var type = typeBuilder.CreateType(); + type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, luaCs); return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index e5ca284cd..daf0e669b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -77,7 +77,7 @@ namespace Barotrauma public LuaCsSetup() { - Hook = new LuaCsHook(); + Hook = new LuaCsHook(this); ModStore = new LuaCsModStore(); Game = new LuaGame(); @@ -285,13 +285,13 @@ namespace Barotrauma return lua.LoadFile(file, globalContext, codeStringFriendly); } - public DynValue CallLuaFunction(object function, params object[] arguments) + public DynValue CallLuaFunction(object function, params object[] args) { lock (lua) { try { - return lua.Call(function, arguments); + return lua.Call(function, args); } catch (Exception e) { @@ -376,7 +376,7 @@ namespace Barotrauma LuaScriptLoader = new LuaScriptLoader(); LuaScriptLoader.ModulePaths = new string[] { }; - LuaCustomConverters.RegisterAll(); + LuaCustomConverters.Initialize(CallLuaFunction); lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug); lua.Options.DebugPrint = PrintMessage;