Make LuaCsSetup easier to integrate into unit tests

This commit is contained in:
peelz
2022-08-03 21:34:41 -04:00
parent d98daf008e
commit d9dc84425d
3 changed files with 82 additions and 62 deletions
@@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
@@ -8,42 +6,47 @@ using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
namespace Barotrauma namespace Barotrauma
{ {
public static class LuaCustomConverters public delegate DynValue CallLuaFunctionFunc(object function, params object[] args);
{
public static void RegisterAll() internal static class LuaCustomConverters
{ {
private static CallLuaFunctionFunc CallLuaFunction;
public static void Initialize(CallLuaFunctionFunc callLuaFunction)
{
CallLuaFunction = callLuaFunction;
RegisterAction<Item>(); RegisterAction<Item>();
RegisterAction<Character>(); RegisterAction<Character>();
RegisterAction<Entity>(); RegisterAction<Entity>();
RegisterAction(); RegisterAction();
RegisterFunc<Fixture, Vector2, Vector2, float, float>(); RegisterFunc<Fixture, Vector2, Vector2, float, float>();
RegisterFunc<AIObjective, bool>(); RegisterFunc<AIObjective, bool>();
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
DataType.Function, DataType.Function,
typeof(LuaCsAction), typeof(LuaCsAction),
v => (LuaCsAction)(args => GameMain.LuaCs.CallLuaFunction(v.Function, args))); v => (LuaCsAction)(args => CallLuaFunction(v.Function, args)));
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
DataType.Function, DataType.Function,
typeof(LuaCsFunc), typeof(LuaCsFunc),
v => (LuaCsFunc)(args => GameMain.LuaCs.CallLuaFunction(v.Function, args))); v => (LuaCsFunc)(args => CallLuaFunction(v.Function, args)));
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion( Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
DataType.Function, DataType.Function,
typeof(LuaCsCompatPatchFunc), 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( Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
DataType.Function, DataType.Function,
typeof(LuaCsPatchFunc), typeof(LuaCsPatchFunc),
v => (LuaCsPatchFunc)((self, args) => GameMain.LuaCs.CallLuaFunction(v.Function, self, args))); v => (LuaCsPatchFunc)((self, args) => CallLuaFunction(v.Function, self, args)));
#if CLIENT #if CLIENT
RegisterAction<float>(); RegisterAction<float>();
RegisterAction<Microsoft.Xna.Framework.Graphics.SpriteBatch, float>(); RegisterAction<Microsoft.Xna.Framework.Graphics.SpriteBatch, float>();
{ {
DynValue Call(object function, params object[] arguments) => GameMain.LuaCs.CallLuaFunction(function, arguments); DynValue Call(object function, params object[] arguments) => CallLuaFunction(function, arguments);
void RegisterHandler<T>(Func<Closure, T> converter) void RegisterHandler<T>(Func<Closure, T> converter)
=> Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(T), v => converter(v.Function)); => 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())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIButton.OnClickedHandler)( RegisterHandler(f => (GUIButton.OnClickedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIButton.OnButtonDownHandler)( RegisterHandler(f => (GUIButton.OnButtonDownHandler)(
() => Call(f).CastToBool())); () => Call(f).CastToBool()));
RegisterHandler(f => (GUIButton.OnPressedHandler)( RegisterHandler(f => (GUIButton.OnPressedHandler)(
() => Call(f).CastToBool())); () => Call(f).CastToBool()));
RegisterHandler(f => (GUIColorPicker.OnColorSelectedHandler)( RegisterHandler(f => (GUIColorPicker.OnColorSelectedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIDropDown.OnSelectedHandler)( RegisterHandler(f => (GUIDropDown.OnSelectedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIListBox.OnSelectedHandler)( RegisterHandler(f => (GUIListBox.OnSelectedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIListBox.OnRearrangedHandler)( RegisterHandler(f => (GUIListBox.OnRearrangedHandler)(
(a1, a2) => Call(f, a1, a2))); (a1, a2) => Call(f, a1, a2)));
RegisterHandler(f => (GUIListBox.CheckSelectedHandler)( RegisterHandler(f => (GUIListBox.CheckSelectedHandler)(
() => Call(f).ToObject())); () => Call(f).ToObject()));
RegisterHandler(f => (GUINumberInput.OnValueEnteredHandler)( RegisterHandler(f => (GUINumberInput.OnValueEnteredHandler)(
(a1) => Call(f, a1))); (a1) => Call(f, a1)));
RegisterHandler(f => (GUINumberInput.OnValueChangedHandler)( RegisterHandler(f => (GUINumberInput.OnValueChangedHandler)(
(a1) => Call(f, a1))); (a1) => Call(f, a1)));
RegisterHandler(f => (GUIProgressBar.ProgressGetterHandler)( RegisterHandler(f => (GUIProgressBar.ProgressGetterHandler)(
() => (float)(Call(f).CastToNumber() ?? 0))); () => (float)(Call(f).CastToNumber() ?? 0)));
RegisterHandler(f => (GUIRadioButtonGroup.RadioButtonGroupDelegate)( RegisterHandler(f => (GUIRadioButtonGroup.RadioButtonGroupDelegate)(
(a1, a2) => Call(f, a1, a2))); (a1, a2) => Call(f, a1, a2)));
RegisterHandler(f => (GUIScrollBar.OnMovedHandler)( RegisterHandler(f => (GUIScrollBar.OnMovedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUIScrollBar.ScrollConversion)( 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)( RegisterHandler(f => (GUITextBlock.TextGetterHandler)(
() => Call(f, new object[0]).CastToString())); () => Call(f, new object[0]).CastToString()));
RegisterHandler(f => (GUITextBox.OnEnterHandler)( RegisterHandler(f => (GUITextBox.OnEnterHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (GUITextBox.OnTextChangedHandler)( RegisterHandler(f => (GUITextBox.OnTextChangedHandler)(
(a1, a2) => Call(f, a1, a2).CastToBool())); (a1, a2) => Call(f, a1, a2).CastToBool()));
RegisterHandler(f => (TextBoxEvent)( RegisterHandler(f => (TextBoxEvent)(
(a1, a2) => Call(f, a1, a2))); (a1, a2) => Call(f, a1, a2)));
RegisterHandler(f => (GUITickBox.OnSelectedHandler)( RegisterHandler(f => (GUITickBox.OnSelectedHandler)(
(a1) => Call(f, a1).CastToBool())); (a1) => Call(f, a1).CastToBool()));
} }
#endif #endif
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Pair<JobPrefab, int>), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Pair<JobPrefab, int>), v =>
{ {
return new Pair<JobPrefab, int>((JobPrefab)v.Table.Get(1).ToObject(), (int)v.Table.Get(2).CastToNumber()); return new Pair<JobPrefab, int>((JobPrefab)v.Table.Get(1).ToObject(), (int)v.Table.Get(2).CastToNumber());
}); });
Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion<UInt64>((Script script, UInt64 v) => Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion<ulong>((Script script, ulong v) =>
{ {
return DynValue.NewString(v.ToString()); 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( Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
@@ -185,13 +187,13 @@ namespace Barotrauma
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T>), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T>), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action<T>)(p => GameMain.LuaCs.CallLuaFunction(function, p)); return (Action<T>)(p => CallLuaFunction(function, p));
}); });
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T>), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T>), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action<T>)(p => GameMain.LuaCs.CallLuaFunction(function, p)); return (Action<T>)(p => CallLuaFunction(function, p));
}); });
} }
@@ -200,13 +202,13 @@ namespace Barotrauma
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T1, T2>), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T1, T2>), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action<T1, T2>)((a1, a2) => GameMain.LuaCs.CallLuaFunction(function, a1, a2)); return (Action<T1, T2>)((a1, a2) => CallLuaFunction(function, a1, a2));
}); });
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T1, T2>), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T1, T2>), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action<T1, T2>)((a1, a2) => GameMain.LuaCs.CallLuaFunction(function, a1, a2)); return (Action<T1, T2>)((a1, a2) => CallLuaFunction(function, a1, a2));
}); });
} }
@@ -215,13 +217,13 @@ namespace Barotrauma
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action)(() => GameMain.LuaCs.CallLuaFunction(function)); return (Action)(() => CallLuaFunction(function));
}); });
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v =>
{ {
var function = v.Function; var function = v.Function;
return (Action)(() => GameMain.LuaCs.CallLuaFunction(function)); return (Action)(() => CallLuaFunction(function));
}); });
} }
@@ -269,7 +271,5 @@ namespace Barotrauma
return (Func<T1, T2, T3, T4, T5>)((T1 a, T2 b, T3 c, T4 d) => function.Call(a, b, c, d).ToObject<T5>()); return (Func<T1, T2, T3, T4, T5>)((T1 a, T2 b, T3 c, T4 d) => function.Call(a, b, c, d).ToObject<T5>());
}); });
} }
} }
} }
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -541,6 +541,8 @@ namespace Barotrauma
private readonly Dictionary<MethodKey, PatchedMethod> registeredPatches = new Dictionary<MethodKey, PatchedMethod>(); private readonly Dictionary<MethodKey, PatchedMethod> registeredPatches = new Dictionary<MethodKey, PatchedMethod>();
private LuaCsSetup luaCs;
private static LuaCsHook instance; private static LuaCsHook instance;
private struct MethodKey : IEquatable<MethodKey> private struct MethodKey : IEquatable<MethodKey>
@@ -581,9 +583,10 @@ namespace Barotrauma
}; };
} }
public LuaCsHook() internal LuaCsHook(LuaCsSetup luaCs)
{ {
instance = this; instance = this;
this.luaCs = luaCs;
} }
public void Initialize() public void Initialize()
@@ -722,6 +725,18 @@ namespace Barotrauma
{ {
harmony?.UnpatchAll(); 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(); hookFunctions.Clear();
registeredPatches.Clear(); registeredPatches.Clear();
patchModuleBuilder = null; patchModuleBuilder = null;
@@ -737,7 +752,6 @@ namespace Barotrauma
[MoonSharpHidden] [MoonSharpHidden]
public T Call<T>(string name, params object[] args) public T Call<T>(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 (name == null) throw new ArgumentNullException(name);
if (args == null) args = new object[0]; if (args == null) args = new object[0];
@@ -757,7 +771,7 @@ namespace Barotrauma
try try
{ {
if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) if (luaCs.PerformanceCounter.EnablePerformanceCounter)
{ {
performanceMeasurement.Start(); performanceMeasurement.Start();
} }
@@ -769,10 +783,10 @@ namespace Barotrauma
lastResult = result.ToObject<T>(); lastResult = result.ToObject<T>();
} }
if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) if (luaCs.PerformanceCounter.EnablePerformanceCounter)
{ {
performanceMeasurement.Stop(); performanceMeasurement.Stop();
GameMain.LuaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks);
performanceMeasurement.Reset(); performanceMeasurement.Reset();
} }
} }
@@ -840,6 +854,8 @@ namespace Barotrauma
private static readonly Regex InvalidIdentifierCharsRegex = new Regex(@"[^\w\d]", RegexOptions.Compiled); private static readonly Regex InvalidIdentifierCharsRegex = new Regex(@"[^\w\d]", RegexOptions.Compiled);
private const string FIELD_LUACS = "LuaCs";
// If you need to debug this: // If you need to debug this:
// - use https://sharplab.io ; it's a very useful for resource for writing IL by hand. // - 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. // - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime.
@@ -876,6 +892,8 @@ namespace Barotrauma
: MangleName(original); : MangleName(original);
var typeBuilder = moduleBuilder.DefineType($"Patch_{identifier}_{Guid.NewGuid():N}_{mangledName}", TypeAttributes.Public); 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 methodName = hookType == HookMethodType.Before ? "HarmonyPrefix" : "HarmonyPostfix";
var il = Emit.BuildMethod( var il = Emit.BuildMethod(
returnType: hookType == HookMethodType.Before ? typeof(bool) : typeof(void), returnType: hookType == HookMethodType.Before ? typeof(bool) : typeof(void),
@@ -1094,17 +1112,18 @@ namespace Barotrauma
var exception = il.DeclareLocal<Exception>("exception"); var exception = il.DeclareLocal<Exception>("exception");
il.StoreLocal(exception); il.StoreLocal(exception);
// IL: var luaCsSetup = GameMain.LuaCs; // IL: if (LuaCs != null)
var luaCsSetup = il.DeclareLocal<LuaCsSetup>("luaCsSetup"); il.LoadField(luaCsField);
il.LoadField(typeof(GameMain).GetField(nameof(GameMain.LuaCs), BindingFlags.Public | BindingFlags.Static)); il.If((il) =>
il.StoreLocal(luaCsSetup); {
// 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.EndCatchBlock(catchBlock);
il.EndExceptionBlock(exceptionBlock); il.EndExceptionBlock(exceptionBlock);
@@ -1123,6 +1142,7 @@ namespace Barotrauma
} }
var type = typeBuilder.CreateType(); var type = typeBuilder.CreateType();
type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, luaCs);
return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
} }
@@ -77,7 +77,7 @@ namespace Barotrauma
public LuaCsSetup() public LuaCsSetup()
{ {
Hook = new LuaCsHook(); Hook = new LuaCsHook(this);
ModStore = new LuaCsModStore(); ModStore = new LuaCsModStore();
Game = new LuaGame(); Game = new LuaGame();
@@ -285,13 +285,13 @@ namespace Barotrauma
return lua.LoadFile(file, globalContext, codeStringFriendly); return lua.LoadFile(file, globalContext, codeStringFriendly);
} }
public DynValue CallLuaFunction(object function, params object[] arguments) public DynValue CallLuaFunction(object function, params object[] args)
{ {
lock (lua) lock (lua)
{ {
try try
{ {
return lua.Call(function, arguments); return lua.Call(function, args);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -376,7 +376,7 @@ namespace Barotrauma
LuaScriptLoader = new LuaScriptLoader(); LuaScriptLoader = new LuaScriptLoader();
LuaScriptLoader.ModulePaths = new string[] { }; LuaScriptLoader.ModulePaths = new string[] { };
LuaCustomConverters.RegisterAll(); LuaCustomConverters.Initialize(CallLuaFunction);
lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug); lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug);
lua.Options.DebugPrint = PrintMessage; lua.Options.DebugPrint = PrintMessage;