#region Copyright & License Information /* * Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Eluant; using OpenRA.Traits; namespace OpenRA.Scripting { public class ScriptMemberWrapper { readonly ScriptContext context; public readonly object Target; public readonly MemberInfo Member; public readonly bool IsMethod; public readonly bool IsGetProperty; public readonly bool IsSetProperty; public ScriptMemberWrapper(ScriptContext context, object target, MemberInfo mi) { this.context = context; Target = target; Member = mi; var property = mi as PropertyInfo; if (property != null) { IsGetProperty = property.GetGetMethod() != null; IsSetProperty = property.GetSetMethod() != null; } else IsMethod = true; } LuaValue Invoke(LuaVararg args) { object[] clrArgs = null; try { if (!IsMethod) throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!"); var mi = (MethodInfo)Member; var pi = mi.GetParameters(); clrArgs = new object[pi.Length]; var argCount = args.Count; for (var i = 0; i < pi.Length; i++) { if (i >= argCount) { if (!pi[i].IsOptional) throw new LuaException($"Argument '{pi[i].LuaDocString()}' of '{Member.LuaDocString()}' is not optional."); clrArgs[i] = pi[i].DefaultValue; continue; } if (!args[i].TryGetClrValue(pi[i].ParameterType, out clrArgs[i])) throw new LuaException($"Unable to convert parameter {i} to {pi[i].ParameterType.Name}"); } return mi.Invoke(Target, clrArgs).ToLuaValue(context); } finally { // Clean up all the Lua arguments that were given to us. foreach (var arg in args) arg.Dispose(); args.Dispose(); // If we created any arrays of LuaValues to pass around, we need to dispose those too. if (clrArgs != null) { foreach (var arg in clrArgs) { if (!(arg is LuaValue[] table)) continue; foreach (var value in table) value.Dispose(); } } } } public LuaValue Get(LuaRuntime runtime) { if (IsMethod) return runtime.CreateFunctionFromDelegate((Func)Invoke); if (IsGetProperty) return ((PropertyInfo)Member).GetValue(Target, null).ToLuaValue(context); throw new LuaException($"The property '{Member.Name}' is write-only"); } public void Set(LuaValue value) { if (IsSetProperty) { var pi = (PropertyInfo)Member; if (!value.TryGetClrValue(pi.PropertyType, out var clrValue)) throw new LuaException($"Unable to convert '{value.WrappedClrType().Name}' to Clr type '{pi.PropertyType}'"); pi.SetValue(Target, clrValue, null); } else throw new LuaException($"The property '{Member.Name}' is read-only"); } public static IEnumerable WrappableMembers(Type t) { // Only expose defined public non-static methods that were explicitly declared by the author var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; foreach (var mi in t.GetMembers(flags)) { // Properties are always wrappable if (mi is PropertyInfo) yield return mi; // Methods are allowed if they aren't generic, and aren't generated by the compiler var method = mi as MethodInfo; if (method != null && !method.IsGenericMethodDefinition && !method.IsSpecialName) yield return mi; // Fields aren't allowed } } public static string[] RequiredTraitNames(Type t) { // Returns the inner types of all the Requires interfaces on this type var types = t.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Requires<>)); // Remove the namespace and the trailing "Info" return types.SelectMany(i => i.GetGenericArguments()) .Select(g => g.Name.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault()) .Select(s => s.EndsWith("Info") ? s.Remove(s.Length - 4, 4) : s) .ToArray(); } } }