|
1 | 1 | using System; |
2 | | -using System.Collections.Concurrent; |
3 | 2 | using System.Reflection; |
| 3 | +using System.Runtime.InteropServices; |
4 | 4 | using Laylua.Moon; |
5 | 5 |
|
6 | 6 | namespace Laylua.Marshaling; |
7 | 7 |
|
8 | 8 | public unsafe partial class DefaultLuaMarshaler |
9 | 9 | { |
10 | | - private const int MaxSupportedDelegateParameterCount = 16; |
11 | | - private const int MaxSupportedDelegateTypeArgumentCount = MaxSupportedDelegateParameterCount + 1; |
12 | | - |
13 | | - private static readonly ConcurrentDictionary<Type, DelegateWrapperMethodCacheEntry> _delegateWrapperMethods = new(); |
14 | | - private static readonly MethodInfo?[] _actionWrapperMethodDefinitions = BuildDelegateWrapperMethodDefinitions(nameof(LuaFunctionDelegateWrapper.InvokeAction)); |
15 | | - private static readonly MethodInfo?[] _functionWrapperMethodDefinitions = BuildDelegateWrapperMethodDefinitions(nameof(LuaFunctionDelegateWrapper.InvokeFunc)); |
16 | | - |
17 | | - /// <summary> |
18 | | - /// Wraps a <see cref="LuaFunction"/> so it can be called as a .NET delegate. |
19 | | - /// </summary> |
20 | | - private sealed partial class LuaFunctionDelegateWrapper(LuaFunction function); |
21 | | - |
22 | | - private readonly struct DelegateWrapperMethodCacheEntry(MethodInfo? method, int parameterCount, bool hasTooManyParameters) |
23 | | - { |
24 | | - public readonly MethodInfo? Method = method; |
25 | | - |
26 | | - public readonly int ParameterCount = parameterCount; |
27 | | - |
28 | | - public readonly bool HasTooManyParameters = hasTooManyParameters; |
29 | | - } |
30 | | - |
31 | | - internal static Delegate? CreateDelegateFromFunction(LuaFunction function, Type delegateType) |
| 10 | + internal static bool CanConvertFunctionToDelegate(lua_State* L, int stackIndex, Type delegateType) |
32 | 11 | { |
33 | | - var cacheEntry = _delegateWrapperMethods.GetOrAdd(delegateType, static currentDelegateType => CreateDelegateWrapperMethodCacheEntry(currentDelegateType)); |
34 | | - if (cacheEntry.HasTooManyParameters) |
| 12 | + if (delegateType.BaseType != typeof(MulticastDelegate)) |
35 | 13 | { |
36 | | - throw new NotSupportedException( |
37 | | - $"Cannot marshal a Lua function to delegate type '{delegateType.Name}' because it has {cacheEntry.ParameterCount} parameters. " + |
38 | | - $"Marshaling Lua functions to delegates supports at most {MaxSupportedDelegateParameterCount} parameters. " + |
39 | | - $"Use {nameof(LuaFunction)} directly for higher-arity calls."); |
| 14 | + return false; |
40 | 15 | } |
41 | 16 |
|
42 | | - if (cacheEntry.Method == null) |
43 | | - { |
44 | | - return null; |
45 | | - } |
| 17 | + return TryGetCallableTargetFromFunction(L, stackIndex, delegateType, out _); |
| 18 | + } |
46 | 19 |
|
47 | | - var wrapper = new LuaFunctionDelegateWrapper(function); |
48 | | - return cacheEntry.Method.CreateDelegate(delegateType, wrapper); |
| 20 | + internal static bool CanConvertFunctionToMethodInfo(lua_State* L, int stackIndex) |
| 21 | + { |
| 22 | + return TryGetMethodInfoFromFunction(L, stackIndex, typeof(MethodInfo), out _); |
49 | 23 | } |
50 | 24 |
|
51 | | - internal static bool CanConvertFunctionToDelegate(lua_State* L, int stackIndex, Type delegateType) |
| 25 | + internal static bool TryGetCallableTargetFromFunction(lua_State* L, int stackIndex, Type targetType, out object? result) |
52 | 26 | { |
53 | | - if (delegateType.BaseType != typeof(MulticastDelegate)) |
| 27 | + if (TryGetMethodInfoFromFunction(L, stackIndex, targetType, out result)) |
54 | 28 | { |
55 | | - return false; |
| 29 | + return true; |
56 | 30 | } |
57 | 31 |
|
58 | | - if (TryGetMethodInfoFromFunction(L, stackIndex, delegateType, out _)) |
| 32 | + if (targetType.BaseType == typeof(MulticastDelegate) && TryGetManagedDelegateFromFunction(L, stackIndex, targetType, out var @delegate)) |
59 | 33 | { |
| 34 | + result = @delegate; |
60 | 35 | return true; |
61 | 36 | } |
62 | 37 |
|
63 | | - var cacheEntry = _delegateWrapperMethods.GetOrAdd(delegateType, static currentDelegateType => CreateDelegateWrapperMethodCacheEntry(currentDelegateType)); |
64 | | - return !cacheEntry.HasTooManyParameters && cacheEntry.Method != null; |
65 | | - } |
66 | | - |
67 | | - internal static bool CanConvertFunctionToMethodInfo(lua_State* L, int stackIndex) |
68 | | - { |
69 | | - return TryGetMethodInfoFromFunction(L, stackIndex, typeof(MethodInfo), out _); |
| 38 | + result = null; |
| 39 | + return false; |
70 | 40 | } |
71 | 41 |
|
72 | 42 | /// <summary> |
@@ -118,89 +88,100 @@ private static bool TryGetMethodInfoFromFunction(lua_State* L, int stackIndex, T |
118 | 88 | return false; |
119 | 89 | } |
120 | 90 |
|
121 | | - private static DelegateWrapperMethodCacheEntry CreateDelegateWrapperMethodCacheEntry(Type delegateType) |
| 91 | + private static bool TryGetManagedDelegateFromFunction(lua_State* L, int stackIndex, Type delegateType, out Delegate? result) |
122 | 92 | { |
123 | | - var invokeMethod = delegateType.GetMethod("Invoke"); |
124 | | - if (invokeMethod == null) |
| 93 | + stackIndex = lua_absindex(L, stackIndex); |
| 94 | + var top = lua_gettop(L); |
| 95 | + try |
125 | 96 | { |
126 | | - return default; |
127 | | - } |
| 97 | + if (!IsManagedDelegateClosure(L, stackIndex)) |
| 98 | + { |
| 99 | + result = null; |
| 100 | + return false; |
| 101 | + } |
128 | 102 |
|
129 | | - var parameters = invokeMethod.GetParameters(); |
130 | | - var parameterCount = parameters.Length; |
131 | | - if (parameterCount > MaxSupportedDelegateParameterCount) |
132 | | - { |
133 | | - return new DelegateWrapperMethodCacheEntry(method: null, parameterCount, hasTooManyParameters: true); |
134 | | - } |
| 103 | + if (lua_getupvalue(L, stackIndex, 1).Pointer == null) |
| 104 | + { |
| 105 | + result = null; |
| 106 | + return false; |
| 107 | + } |
135 | 108 |
|
136 | | - var returnType = invokeMethod.ReturnType; |
137 | | - var isVoid = returnType == typeof(void); |
138 | | - var typeArgumentCount = isVoid |
139 | | - ? parameterCount |
140 | | - : parameterCount + 1; |
| 109 | + if (lua_type(L, -1) != LuaType.UserData || !IsDelegateHandleUserData(L, -1)) |
| 110 | + { |
| 111 | + result = null; |
| 112 | + return false; |
| 113 | + } |
141 | 114 |
|
142 | | - var typeArguments = new Type[typeArgumentCount]; |
143 | | - for (var i = 0; i < parameterCount; i++) |
144 | | - { |
145 | | - var parameterType = parameters[i].ParameterType; |
146 | | - if (IsUnsupportedDelegateType(parameterType)) |
| 115 | + var ptr = (nint*) lua_touserdata(L, -1); |
| 116 | + if (ptr == null || *ptr == 0) |
147 | 117 | { |
148 | | - return default; |
| 118 | + result = null; |
| 119 | + return false; |
149 | 120 | } |
150 | 121 |
|
151 | | - typeArguments[i] = parameterType; |
152 | | - } |
| 122 | + var handlePtr = *ptr; |
| 123 | + if (!IsDelegateHandleTracked(Lua.FromExtraSpace(L), handlePtr)) |
| 124 | + { |
| 125 | + result = null; |
| 126 | + return false; |
| 127 | + } |
153 | 128 |
|
154 | | - if (!isVoid) |
155 | | - { |
156 | | - if (IsUnsupportedDelegateType(returnType)) |
| 129 | + var handle = GCHandle.FromIntPtr(handlePtr); |
| 130 | + if (handle.Target is Delegate @delegate && delegateType.IsInstanceOfType(@delegate)) |
157 | 131 | { |
158 | | - return default; |
| 132 | + result = @delegate; |
| 133 | + return true; |
159 | 134 | } |
160 | 135 |
|
161 | | - typeArguments[parameterCount] = returnType; |
| 136 | + result = null; |
| 137 | + return false; |
162 | 138 | } |
| 139 | + finally |
| 140 | + { |
| 141 | + lua_settop(L, top); |
| 142 | + } |
| 143 | + } |
163 | 144 |
|
164 | | - var methodDefinition = isVoid |
165 | | - ? _actionWrapperMethodDefinitions[typeArgumentCount] |
166 | | - : _functionWrapperMethodDefinitions[typeArgumentCount]; |
| 145 | + private static bool IsManagedDelegateClosure(lua_State* L, int stackIndex) |
| 146 | + { |
| 147 | + if (!lua_iscfunction(L, stackIndex)) |
| 148 | + { |
| 149 | + return false; |
| 150 | + } |
167 | 151 |
|
168 | | - if (methodDefinition == null) |
| 152 | + if (lua_getupvalue(L, stackIndex, 2).Pointer == null) |
169 | 153 | { |
170 | | - return default; |
| 154 | + return false; |
171 | 155 | } |
172 | 156 |
|
173 | | - var method = typeArgumentCount == 0 |
174 | | - ? methodDefinition |
175 | | - : methodDefinition.MakeGenericMethod(typeArguments); |
| 157 | + if (lua_type(L, -1) != LuaType.LightUserData |
| 158 | + || (nint) lua_touserdata(L, -1) != (nint) LayluaNative.getPotentialPanicPtr) |
| 159 | + { |
| 160 | + lua_pop(L, 1); |
| 161 | + return false; |
| 162 | + } |
176 | 163 |
|
177 | | - return new DelegateWrapperMethodCacheEntry(method, parameterCount, hasTooManyParameters: false); |
| 164 | + lua_pop(L, 1); |
| 165 | + return true; |
178 | 166 | } |
179 | 167 |
|
180 | | - private static MethodInfo?[] BuildDelegateWrapperMethodDefinitions(string methodName) |
| 168 | + private static bool IsDelegateHandleUserData(lua_State* L, int stackIndex) |
181 | 169 | { |
182 | | - var methodDefinitions = new MethodInfo?[MaxSupportedDelegateTypeArgumentCount + 1]; |
183 | | - var methods = typeof(LuaFunctionDelegateWrapper).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); |
184 | | - for (var i = 0; i < methods.Length; i++) |
| 170 | + stackIndex = lua_absindex(L, stackIndex); |
| 171 | + if (!lua_getmetatable(L, stackIndex)) |
185 | 172 | { |
186 | | - var method = methods[i]; |
187 | | - if (method.Name != methodName) |
188 | | - { |
189 | | - continue; |
190 | | - } |
| 173 | + return false; |
| 174 | + } |
191 | 175 |
|
192 | | - var genericArgumentCount = method.GetGenericArguments().Length; |
193 | | - if (genericArgumentCount <= MaxSupportedDelegateTypeArgumentCount) |
194 | | - { |
195 | | - methodDefinitions[genericArgumentCount] = method; |
196 | | - } |
| 176 | + if (luaL_getmetatable(L, DelegateHandleMetatableName).IsNoneOrNil()) |
| 177 | + { |
| 178 | + lua_pop(L, 2); |
| 179 | + return false; |
197 | 180 | } |
198 | 181 |
|
199 | | - return methodDefinitions; |
| 182 | + var isDelegateHandle = lua_rawequal(L, -1, -2); |
| 183 | + lua_pop(L, 2); |
| 184 | + return isDelegateHandle; |
200 | 185 | } |
201 | 186 |
|
202 | | - private static bool IsUnsupportedDelegateType(Type type) |
203 | | - { |
204 | | - return type.IsByRef || type.IsByRefLike || type.IsPointer; |
205 | | - } |
206 | 187 | } |
0 commit comments