diff --git a/.gitignore b/.gitignore index 16b1efb7bb..24244cd849 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,14 @@ _ReSharper.*/ # binaries mods/*/*.dll +mods/*/*.mdb /*.dll /*.dll.config -*.pdb -*.mdb -*.exe +/*.so +/*.dylib +/*.pdb +/*.mdb +/*.exe # backup files by various editors *~ @@ -47,10 +50,6 @@ OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.perspectivev3 OpenRA.Launcher.Mac/OpenRA.xcodeproj/*.mode1v3 *.resources -# KDE -*.kate-swp -*.directory - # auto-generated documentation DOCUMENTATION.md *.html diff --git a/AUTHORS b/AUTHORS index 960bb57077..19d7cd2132 100644 --- a/AUTHORS +++ b/AUTHORS @@ -69,6 +69,9 @@ Also thanks to: Using GeoLite data created by MaxMind and distributed under the CC BY-SA 3.0 license. +Using MonoBoxedLua created by Sparklin Labs +and distributed under the MIT license. + Finally, special thanks goes to the original teams at Westwood Studios and EA for creating the classic games which OpenRA aims to reimagine. diff --git a/CHANGELOG b/CHANGELOG index cf0f4d2d48..7c658c3056 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,12 +77,14 @@ NEW: Fixed a desync related to projectile contrails. Fixed corrupted replays (which would immediately desync). Removed runtime mod merging. + Added support for map scripting with Lua. Build system and packages: Added GeoIP to Makefile so it is installed properly. Added desktop shortcut creation support to the Makefile and Windows installer. COPYING and CHANGELOG are now shipped on all platforms. Fixed 'make docs' crashing when the game assets are not installed. Renamed Game.Mods launch argument to Game.Mod. + Linux packages now install to /usr/lib/openra for consistency with other Mono applications. Mod / Custom map compatibility: Mods can now include traits from TD and D2K in RA. New sections MapFolders and Translations added to mod.yaml. diff --git a/LuaInterface/AssemblyInfo.cs b/LuaInterface/AssemblyInfo.cs new file mode 100644 index 0000000000..70db5f2c9b --- /dev/null +++ b/LuaInterface/AssemblyInfo.cs @@ -0,0 +1,56 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security.Permissions; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("LuaInterface")] +[assembly: AssemblyDescription("Bridge between the Lua runtime and the CLR")] +[assembly: AssemblyCopyright("Copyright 2003-2008 Fabio Mascarenhas, Kevin Hester")] +[assembly: CLSCompliant(false)] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("2.0.4.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// + +// We call native DLLs (lua50) and we don't want to be fucked with or validated +// [assembly: SecurityPermission(RequestMinimum, UnmanagedCode = true)] diff --git a/LuaInterface/CheckType.cs b/LuaInterface/CheckType.cs new file mode 100644 index 0000000000..6fda337278 --- /dev/null +++ b/LuaInterface/CheckType.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace LuaInterface +{ + /* + * Type checking and conversion functions. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class CheckType + { + private ObjectTranslator translator; + + ExtractValue extractNetObject; + Dictionary extractValues = new Dictionary(); + + public CheckType(ObjectTranslator translator) + { + this.translator = translator; + + extractValues.Add(typeof(object).TypeHandle.Value.ToInt64(), new ExtractValue(getAsObject)); + extractValues.Add(typeof(sbyte).TypeHandle.Value.ToInt64(), new ExtractValue(getAsSbyte)); + extractValues.Add(typeof(byte).TypeHandle.Value.ToInt64(), new ExtractValue(getAsByte)); + extractValues.Add(typeof(short).TypeHandle.Value.ToInt64(), new ExtractValue(getAsShort)); + extractValues.Add(typeof(ushort).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUshort)); + extractValues.Add(typeof(int).TypeHandle.Value.ToInt64(), new ExtractValue(getAsInt)); + extractValues.Add(typeof(uint).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUint)); + extractValues.Add(typeof(long).TypeHandle.Value.ToInt64(), new ExtractValue(getAsLong)); + extractValues.Add(typeof(ulong).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUlong)); + extractValues.Add(typeof(double).TypeHandle.Value.ToInt64(), new ExtractValue(getAsDouble)); + extractValues.Add(typeof(char).TypeHandle.Value.ToInt64(), new ExtractValue(getAsChar)); + extractValues.Add(typeof(float).TypeHandle.Value.ToInt64(), new ExtractValue(getAsFloat)); + extractValues.Add(typeof(decimal).TypeHandle.Value.ToInt64(), new ExtractValue(getAsDecimal)); + extractValues.Add(typeof(bool).TypeHandle.Value.ToInt64(), new ExtractValue(getAsBoolean)); + extractValues.Add(typeof(string).TypeHandle.Value.ToInt64(), new ExtractValue(getAsString)); + extractValues.Add(typeof(LuaFunction).TypeHandle.Value.ToInt64(), new ExtractValue(getAsFunction)); + extractValues.Add(typeof(LuaTable).TypeHandle.Value.ToInt64(), new ExtractValue(getAsTable)); + extractValues.Add(typeof(LuaUserData).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUserdata)); + + extractNetObject = new ExtractValue(getAsNetObject); + } + + /* + * Checks if the value at Lua stack index stackPos matches paramType, + * returning a conversion function if it does and null otherwise. + */ + internal ExtractValue getExtractor(IReflect paramType) + { + return getExtractor(paramType.UnderlyingSystemType); + } + internal ExtractValue getExtractor(Type paramType) + { + if(paramType.IsByRef) paramType=paramType.GetElementType(); + + long runtimeHandleValue = paramType.TypeHandle.Value.ToInt64(); + + if(extractValues.ContainsKey(runtimeHandleValue)) + return extractValues[runtimeHandleValue]; + else + return extractNetObject; + } + + internal ExtractValue checkType(IntPtr luaState,int stackPos,Type paramType) + { + LuaTypes luatype = LuaDLL.lua_type(luaState, stackPos); + + if(paramType.IsByRef) paramType=paramType.GetElementType(); + + Type underlyingType = Nullable.GetUnderlyingType(paramType); + if (underlyingType != null) + { + paramType = underlyingType; // Silently convert nullable types to their non null requics + } + + long runtimeHandleValue = paramType.TypeHandle.Value.ToInt64(); + + if (paramType.Equals(typeof(object))) + return extractValues[runtimeHandleValue]; + + /* + //CP: Added support for generic parameters + if (paramType.IsGenericParameter) + { + if (luatype == LuaTypes.LUA_TBOOLEAN) + return extractValues[typeof(bool).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TSTRING) + return extractValues[typeof(string).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TTABLE) + return extractValues[typeof(LuaTable).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TUSERDATA) + return extractValues[typeof(object).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TFUNCTION) + return extractValues[typeof(LuaFunction).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TNUMBER) + return extractValues[typeof(double).TypeHandle.Value.ToInt64()]; + //else // suppress CS0642 + ;//an unsupported type was encountered + } + */ + + if (LuaDLL.lua_isnumber(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + + if (paramType == typeof(bool)) + { + if (LuaDLL.lua_isboolean(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + } + else if (paramType == typeof(string)) + { + if (LuaDLL.lua_isstring(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // kevinh - silently convert nil to a null string pointer + } + else if (paramType == typeof(LuaTable)) + { + if (luatype == LuaTypes.LUA_TTABLE) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // tkopal - silently convert nil to a null table + } + else if (paramType == typeof(LuaUserData)) + { + if (luatype == LuaTypes.LUA_TUSERDATA) + return extractValues[runtimeHandleValue]; + } + else if (paramType == typeof(LuaFunction)) + { + if (luatype == LuaTypes.LUA_TFUNCTION) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // elisee - silently convert nil to a null string pointer + } + else if (typeof(Delegate).IsAssignableFrom(paramType) && luatype == LuaTypes.LUA_TFUNCTION) + { + return new ExtractValue(new DelegateGenerator(translator, paramType).extractGenerated); + } + else if (paramType.IsInterface && luatype == LuaTypes.LUA_TTABLE) + { + return new ExtractValue(new ClassGenerator(translator, paramType).extractGenerated); + } + else if ((paramType.IsInterface || paramType.IsClass) && luatype == LuaTypes.LUA_TNIL) + { + // kevinh - allow nil to be silently converted to null - extractNetObject will return null when the item ain't found + return extractNetObject; + } + else if (LuaDLL.lua_type(luaState, stackPos) == LuaTypes.LUA_TTABLE) + { + if (LuaDLL.luaL_getmetafield(luaState, stackPos, "__index")) + { + object obj = translator.getNetObject(luaState, -1); + LuaDLL.lua_settop(luaState, -2); + if (obj != null && paramType.IsAssignableFrom(obj.GetType())) + return extractNetObject; + } + else + return null; + } + else + { + object obj = translator.getNetObject(luaState, stackPos); + if (obj != null && paramType.IsAssignableFrom(obj.GetType())) + return extractNetObject; + } + + return null; + } + + /* + * The following functions return the value in the Lua stack + * index stackPos as the desired type if it can, or null + * otherwise. + */ + private object getAsSbyte(IntPtr luaState,int stackPos) + { + sbyte retVal=(sbyte)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsByte(IntPtr luaState,int stackPos) + { + byte retVal=(byte)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsShort(IntPtr luaState,int stackPos) + { + short retVal=(short)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUshort(IntPtr luaState,int stackPos) + { + ushort retVal=(ushort)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsInt(IntPtr luaState,int stackPos) + { + int retVal=(int)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUint(IntPtr luaState,int stackPos) + { + uint retVal=(uint)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsLong(IntPtr luaState,int stackPos) + { + long retVal=(long)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUlong(IntPtr luaState,int stackPos) + { + ulong retVal=(ulong)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsDouble(IntPtr luaState,int stackPos) + { + double retVal=LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsChar(IntPtr luaState,int stackPos) + { + char retVal=(char)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsFloat(IntPtr luaState,int stackPos) + { + float retVal=(float)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsDecimal(IntPtr luaState,int stackPos) + { + decimal retVal=(decimal)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsBoolean(IntPtr luaState,int stackPos) + { + return LuaDLL.lua_toboolean(luaState,stackPos); + } + private object getAsString(IntPtr luaState,int stackPos) + { + string retVal=LuaDLL.lua_tostring(luaState,stackPos); + if(retVal=="" && !LuaDLL.lua_isstring(luaState,stackPos)) return null; + return retVal; + } + private object getAsTable(IntPtr luaState,int stackPos) + { + return translator.getTable(luaState,stackPos); + } + private object getAsFunction(IntPtr luaState,int stackPos) + { + return translator.getFunction(luaState,stackPos); + } + private object getAsUserdata(IntPtr luaState,int stackPos) + { + return translator.getUserData(luaState,stackPos); + } + public object getAsObject(IntPtr luaState,int stackPos) + { + if(LuaDLL.lua_type(luaState,stackPos)==LuaTypes.LUA_TTABLE) + { + if(LuaDLL.luaL_getmetafield(luaState,stackPos,"__index")) + { + if(LuaDLL.luaL_checkmetatable(luaState,-1)) + { + LuaDLL.lua_insert(luaState,stackPos); + LuaDLL.lua_remove(luaState,stackPos+1); + } + else + { + LuaDLL.lua_settop(luaState,-2); + } + } + } + object obj=translator.getObject(luaState,stackPos); + return obj; + } + public object getAsNetObject(IntPtr luaState,int stackPos) + { + object obj=translator.getNetObject(luaState,stackPos); + if(obj==null && LuaDLL.lua_type(luaState,stackPos)==LuaTypes.LUA_TTABLE) + { + if(LuaDLL.luaL_getmetafield(luaState,stackPos,"__index")) + { + if(LuaDLL.luaL_checkmetatable(luaState,-1)) + { + LuaDLL.lua_insert(luaState,stackPos); + LuaDLL.lua_remove(luaState,stackPos+1); + obj=translator.getNetObject(luaState,stackPos); + } + else + { + LuaDLL.lua_settop(luaState,-2); + } + } + } + return obj; + } + } +} diff --git a/LuaInterface/GenerateEventAssembly.cs b/LuaInterface/GenerateEventAssembly.cs new file mode 100644 index 0000000000..775b62a2cb --- /dev/null +++ b/LuaInterface/GenerateEventAssembly.cs @@ -0,0 +1,683 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace LuaInterface +{ + /* + * Structure to store a type and the return types of + * its methods (the type of the returned value and out/ref + * parameters). + */ + struct LuaClassType + { + public Type klass; + public Type[][] returnTypes; + } + + /* + * Common interface for types generated from tables. The method + * returns the table that overrides some or all of the type's methods. + */ + public interface ILuaGeneratedType + { + LuaTable __luaInterface_getLuaTable(); + } + + /* + * Class used for generating delegates that get a function from the Lua + * stack as a delegate of a specific type. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class DelegateGenerator + { + private ObjectTranslator translator; + private Type delegateType; + + public DelegateGenerator(ObjectTranslator translator,Type delegateType) + { + this.translator=translator; + this.delegateType=delegateType; + } + public object extractGenerated(IntPtr luaState,int stackPos) + { + return CodeGeneration.Instance.GetDelegate(delegateType,translator.getFunction(luaState,stackPos)); + } + } + + /* + * Class used for generating delegates that get a table from the Lua + * stack as a an object of a specific type. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class ClassGenerator + { + private ObjectTranslator translator; + private Type klass; + + public ClassGenerator(ObjectTranslator translator,Type klass) + { + this.translator=translator; + this.klass=klass; + } + public object extractGenerated(IntPtr luaState,int stackPos) + { + return CodeGeneration.Instance.GetClassInstance(klass,translator.getTable(luaState,stackPos)); + } + } + + /* + * Dynamically generates new types from existing types and + * Lua function and table values. Generated types are event handlers, + * delegates, interface implementations and subclasses. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class CodeGeneration + { + private Type eventHandlerParent=typeof(LuaEventHandler); + private Dictionary eventHandlerCollection=new Dictionary(); + + private Type delegateParent=typeof(LuaDelegate); + private Dictionary delegateCollection=new Dictionary(); + + private Type classHelper=typeof(LuaClassHelper); + private Dictionary classCollection=new Dictionary(); + + private AssemblyName assemblyName; + private AssemblyBuilder newAssembly; + private ModuleBuilder newModule; + private int luaClassNumber=1; + private static readonly CodeGeneration instance = new CodeGeneration(); + + static CodeGeneration() + { + } + + private CodeGeneration() + { + // Create an assembly name + assemblyName=new AssemblyName( ); + assemblyName.Name="LuaInterface_generatedcode"; + // Create a new assembly with one module. + newAssembly=Thread.GetDomain().DefineDynamicAssembly( + assemblyName, AssemblyBuilderAccess.Run); + newModule=newAssembly.DefineDynamicModule("LuaInterface_generatedcode"); + } + + /* + * Singleton instance of the class + */ + public static CodeGeneration Instance + { + get + { + return instance; + } + } + + /* + * Generates an event handler that calls a Lua function + */ + private Type GenerateEvent(Type eventHandlerType) + { + string typeName; + lock(this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + // Define a public class in the assembly, called typeName + TypeBuilder myType=newModule.DefineType(typeName,TypeAttributes.Public,eventHandlerParent); + + // Defines the handler method. Its signature is void(object,) + Type[] paramTypes = new Type[2]; + paramTypes[0]=typeof(object); + paramTypes[1]=eventHandlerType; + Type returnType=typeof(void); + MethodBuilder handleMethod=myType.DefineMethod("HandleEvent", + MethodAttributes.Public|MethodAttributes.HideBySig, + returnType,paramTypes); + + // Emits the IL for the method. It loads the arguments + // and calls the handleEvent method of the base class + ILGenerator generator=handleMethod.GetILGenerator( ); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + MethodInfo miGenericEventHandler; + miGenericEventHandler=eventHandlerParent.GetMethod("handleEvent"); + generator.Emit(OpCodes.Call,miGenericEventHandler); + // returns + generator.Emit(OpCodes.Ret); + + // creates the new type + return myType.CreateType(); + } + + /* + * Generates a type that can be used for instantiating a delegate + * of the provided type, given a Lua function. + */ + private Type GenerateDelegate(Type delegateType) + { + string typeName; + lock(this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + // Define a public class in the assembly, called typeName + TypeBuilder myType=newModule.DefineType(typeName,TypeAttributes.Public,delegateParent); + + // Defines the delegate method with the same signature as the + // Invoke method of delegateType + MethodInfo invokeMethod=delegateType.GetMethod("Invoke"); + ParameterInfo[] paramInfo=invokeMethod.GetParameters(); + Type[] paramTypes=new Type[paramInfo.Length]; + Type returnType=invokeMethod.ReturnType; + + // Counts out and ref params, for use later + int nOutParams=0; int nOutAndRefParams=0; + for(int i=0;i returnTypesList=new List(); + + // Counts out and ref parameters, for later use, + // and creates the list of return types + int nOutParams=0; int nOutAndRefParams=0; + Type returnType=method.ReturnType; + returnTypesList.Add(returnType); + for(int i=0;i returnTypes=new List(); + Type luaDelegateType; + if (delegateCollection.ContainsKey(delegateType)) + { + luaDelegateType=delegateCollection[delegateType]; + } + else + { + luaDelegateType=GenerateDelegate(delegateType); + delegateCollection[delegateType] = luaDelegateType; + } + MethodInfo methodInfo=delegateType.GetMethod("Invoke"); + returnTypes.Add(methodInfo.ReturnType); + foreach(ParameterInfo paramInfo in methodInfo.GetParameters()) + if(paramInfo.ParameterType.IsByRef) + returnTypes.Add(paramInfo.ParameterType); + LuaDelegate luaDelegate=(LuaDelegate)Activator.CreateInstance(luaDelegateType); + luaDelegate.function=luaFunc; + luaDelegate.returnTypes=returnTypes.ToArray(); + return Delegate.CreateDelegate(delegateType,luaDelegate,"CallFunction"); + } + + /* + * Gets an instance of an implementation of the klass interface or + * subclass of klass that delegates public virtual methods to the + * luaTable table. + * Caches the generated type. + */ + public object GetClassInstance(Type klass, LuaTable luaTable) + { + LuaClassType luaClassType; + if (classCollection.ContainsKey(klass)) + { + luaClassType=classCollection[klass]; + } + else + { + luaClassType=new LuaClassType(); + GenerateClass(klass,out luaClassType.klass,out luaClassType.returnTypes,luaTable); + classCollection[klass] = luaClassType; + } + return Activator.CreateInstance(luaClassType.klass,new object[] {luaTable,luaClassType.returnTypes}); + } + } +} diff --git a/LuaInterface/Lua.cs b/LuaInterface/Lua.cs new file mode 100644 index 0000000000..cc5ba42077 --- /dev/null +++ b/LuaInterface/Lua.cs @@ -0,0 +1,761 @@ + +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Reflection; + using System.Threading; + using OpenRA; + using System.Text; + + /* + * Main class of LuaInterface + * Object-oriented wrapper to Lua API + * + * Author: Fabio Mascarenhas + * Version: 1.0 + * + * // steffenj: important changes in Lua class: + * - removed all Open*Lib() functions + * - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs) + * */ + public class Lua : IDisposable + { + /*readonly */ IntPtr luaState; + ObjectTranslator translator; + + LuaFunctionCallback panicCallback; + LuaCSFunction tracebackFunction; + // lockCallback, unlockCallback; used by debug code commented out for now + + public Lua() + { + luaState = LuaDLL.luaL_newstate(); // steffenj: Lua 5.1.1 API change (lua_open is gone) + + // Load libraries + LuaDLL.luaL_openlibs(luaState); + + // Add LuaInterface marker + LuaDLL.lua_pushstring(luaState, "LUAINTERFACE LOADED"); + LuaDLL.lua_pushboolean(luaState, true); + LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + + translator=new ObjectTranslator(this,luaState); + + tracebackFunction = new LuaCSFunction(traceback); + + // We need to keep this in a managed reference so the delegate doesn't get garbage collected + panicCallback = new LuaFunctionCallback(PanicCallback); + LuaDLL.lua_atpanic(luaState, panicCallback); + } + + private bool _StatePassed; + + /* + * CAUTION: LuaInterface.Lua instances can't share the same lua state! + */ + public Lua( Int64 luaState ) + { + // Check for existing LuaInterface marker + IntPtr lState = new IntPtr(luaState); + LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED"); + LuaDLL.lua_gettable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX); + + if(LuaDLL.lua_toboolean(lState,-1)) + { + LuaDLL.lua_settop(lState,-2); + throw new LuaException("There is already a LuaInterface.Lua instance associated with this Lua state"); + } + else + { + // Add LuaInterface marker + LuaDLL.lua_settop(lState,-2); + LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED"); + LuaDLL.lua_pushboolean(lState, true); + LuaDLL.lua_settable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX); + + this.luaState = lState; + LuaDLL.lua_pushvalue(lState, (int)LuaIndexes.LUA_GLOBALSINDEX); + + translator = new ObjectTranslator(this, this.luaState); + } + + _StatePassed = true; + } + + public void Close() + { + if (_StatePassed) + return; + + if (luaState != IntPtr.Zero) + LuaDLL.lua_close(luaState); + //luaState = IntPtr.Zero; <- suggested by Christopher Cebulski http://luaforge.net/forum/forum.php?thread_id=44593&forum_id=146 + } + + static int PanicCallback(IntPtr luaState) + { + // string desc = LuaDLL.lua_tostring(luaState, 1); + string reason = String.Format("unprotected error in call to Lua API ({0})", LuaDLL.lua_tostring(luaState, -1)); + + // lua_tostring(L, -1); + + throw new LuaException(reason); + } + + + + /// + /// Assuming we have a Lua error string sitting on the stack, throw a C# exception out to the user's app + /// + /// Thrown if the script caused an exception + void ThrowExceptionFromError(int oldTop) + { + object err = translator.getObject(luaState, -1); + LuaDLL.lua_settop(luaState, oldTop); + + // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved) + LuaScriptException luaEx = err as LuaScriptException; + if (luaEx != null) throw luaEx; + + // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it + if (err == null) err = "Unknown Lua Error"; + throw new LuaScriptException(err.ToString(), ""); + } + + + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + internal int SetPendingException(Exception e) + { + Exception caughtExcept = e; + + if (caughtExcept != null) + { + translator.throwError(luaState, caughtExcept); + LuaDLL.lua_pushnil(luaState); + + return 1; + } + else + return 0; + } + + private bool executing; + + /// + /// True while a script is being executed + /// + public bool IsExecuting { get { return executing; } } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(string chunk, string name) + { + int oldTop = LuaDLL.lua_gettop(luaState); + + executing = true; + + // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length + // Adapted for OpenRA + var chunkLength = Platform.CurrentPlatform != PlatformType.Windows + ? Encoding.UTF8.GetByteCount(chunk) + : chunk.Length; + + try + { + if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, name) != 0) + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + + LuaFunction result = translator.getFunction(luaState, -1); + translator.popValues(luaState, oldTop); + + return result; + } + + /// + /// + /// + /// + /// + public LuaFunction LoadFile(string fileName) + { + int oldTop = LuaDLL.lua_gettop(luaState); + if (LuaDLL.luaL_loadfile(luaState, fileName) != 0) + ThrowExceptionFromError(oldTop); + + LuaFunction result = translator.getFunction(luaState, -1); + translator.popValues(luaState, oldTop); + + return result; + } + + + /* + * Excutes a Lua chunk and returns all the chunk's return + * values in an array + */ + public object[] DoString(string chunk) + { + return DoString(chunk,"chunk"); + } + + /// + /// Executes a Lua chnk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk + /// + public object[] DoString(string chunk, string chunkName) + { + int oldTop = LuaDLL.lua_gettop(luaState); + executing = true; + + // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length + // Adapted for OpenRA + var chunkLength = Platform.CurrentPlatform != PlatformType.Windows + ? Encoding.UTF8.GetByteCount(chunk) + : chunk.Length; + + if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, chunkName) == 0) + { + try + { + if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0) + return translator.popValues(luaState, oldTop); + else + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + } + else + ThrowExceptionFromError(oldTop); + + return null; // Never reached - keeps compiler happy + } + + private int traceback(IntPtr luaState) + { + LuaDLL.lua_getglobal(luaState,"debug"); + LuaDLL.lua_getfield(luaState,-1,"traceback"); + LuaDLL.lua_pushvalue(luaState,1); + LuaDLL.lua_pushnumber(luaState,2); + LuaDLL.lua_call (luaState,2,1); + return 1; + } + + /* + * Excutes a Lua file and returns all the chunk's return + * values in an array + */ + public object[] DoFile(string fileName) + { + LuaDLL.lua_pushstdcallcfunction(luaState,tracebackFunction); + int oldTop=LuaDLL.lua_gettop(luaState); + if(LuaDLL.luaL_loadfile(luaState,fileName)==0) + { + executing = true; + try + { + if (LuaDLL.lua_pcall(luaState, 0, -1, -2) == 0) + return translator.popValues(luaState, oldTop); + else + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + } + else + ThrowExceptionFromError(oldTop); + + return null; // Never reached - keeps compiler happy + } + + + public void CollectGarbage() + { + LuaDLL.lua_gc( luaState, LuaGCOptions.LUA_GCCOLLECT, 0 ); + } + + /* + * Indexer for global variables from the LuaInterpreter + * Supports navigation of tables by using . operator + */ + public object this[string fullPath] + { + get + { + object returnValue=null; + int oldTop=LuaDLL.lua_gettop(luaState); + string[] path=fullPath.Split(new char[] { '.' }); + LuaDLL.lua_getglobal(luaState,path[0]); + returnValue=translator.getObject(luaState,-1); + if(path.Length>1) + { + string[] remainingPath=new string[path.Length-1]; + Array.Copy(path,1,remainingPath,0,path.Length-1); + returnValue=getObject(remainingPath); + } + LuaDLL.lua_settop(luaState,oldTop); + return returnValue; + } + set + { + int oldTop=LuaDLL.lua_gettop(luaState); + string[] path=fullPath.Split(new char[] { '.' }); + if(path.Length==1) + { + translator.push(luaState,value); + LuaDLL.lua_setglobal(luaState,fullPath); + } + else + { + LuaDLL.lua_getglobal(luaState,path[0]); + string[] remainingPath=new string[path.Length-1]; + Array.Copy(path,1,remainingPath,0,path.Length-1); + setObject(remainingPath,value); + } + LuaDLL.lua_settop(luaState,oldTop); + + // Globals auto-complete + if (value == null) + { + // Remove now obsolete entries + globals.Remove(fullPath); + } + else + { + // Add new entries + if (!globals.Contains(fullPath)) + registerGlobal(fullPath, value.GetType(), 0); + } + } + } + + #region Globals auto-complete + private readonly List globals = new List(); + private bool globalsSorted; + + /// + /// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance + /// + /// Members of globals are also listed. The formatting is optimized for text input auto-completion. + public IEnumerable Globals + { + get + { + // Only sort list when necessary + if (!globalsSorted) + { + globals.Sort(); + globalsSorted = true; + } + + return globals; + } + } + + /// + /// Adds an entry to (recursivley handles 2 levels of members) + /// + /// The index accessor path ot the entry + /// The type of the entry + /// How deep have we gone with recursion? + private void registerGlobal(string path, Type type, int recursionCounter) + { + // If the type is a global method, list it directly + if (type == typeof(LuaCSFunction)) + { + // Format for easy method invocation + globals.Add(path + "("); + } + // If the type is a class or an interface and recursion hasn't been running too long, list the members + else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < 2) + { + #region Methods + foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (method.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) && + // Exclude some generic .NET methods that wouldn't be very usefull in Lua + method.Name != "GetType" && method.Name != "GetHashCode" && method.Name != "Equals" && + method.Name != "ToString" && method.Name != "Clone" && method.Name != "Dispose" && + method.Name != "GetEnumerator" && method.Name != "CopyTo" && + !method.Name.StartsWith("get_", StringComparison.Ordinal) && + !method.Name.StartsWith("set_", StringComparison.Ordinal) && + !method.Name.StartsWith("add_", StringComparison.Ordinal) && + !method.Name.StartsWith("remove_", StringComparison.Ordinal)) + { + // Format for easy method invocation + string command = path + ":" + method.Name + "("; + if (method.GetParameters().Length == 0) command += ")"; + globals.Add(command); + } + } + #endregion + + #region Fields + foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (field.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0)) + { + // Go into recursion for members + registerGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1); + } + } + #endregion + + #region Properties + foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (property.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) + // Exclude some generic .NET properties that wouldn't be very usefull in Lua + && property.Name != "Item") + { + // Go into recursion for members + registerGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1); + } + } + #endregion + } + // Otherwise simply add the element to the list + else globals.Add(path); + + // List will need to be sorted on next access + globalsSorted = false; + } + #endregion + + /* + * Navigates a table in the top of the stack, returning + * the value of the specified field + */ + internal object getObject(string[] remainingPath) + { + object returnValue=null; + for(int i=0;i + /// Base class to provide consistent disposal flow across lua objects. Uses code provided by Yves Duhoux and suggestions by Hans Schmeidenbacher and Qingrui Li + /// + public abstract class LuaBase : IDisposable + { + private bool _Disposed; + protected int _Reference; + protected Lua _Interpreter; + + ~LuaBase() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public virtual void Dispose(bool disposeManagedResources) + { + if (!_Disposed) + { + if (disposeManagedResources) + { + if (_Reference != 0) + _Interpreter.dispose(_Reference); + } + _Interpreter = null; + _Disposed = true; + } + } + + public override bool Equals(object o) + { + if (o is LuaBase) + { + LuaBase l = (LuaBase)o; + return _Interpreter.compareRef(l._Reference, _Reference); + } + else return false; + } + + public override int GetHashCode() + { + return _Reference; + } + } +} diff --git a/LuaInterface/LuaDLL.cs b/LuaInterface/LuaDLL.cs new file mode 100644 index 0000000000..3156a67dc9 --- /dev/null +++ b/LuaInterface/LuaDLL.cs @@ -0,0 +1,456 @@ +namespace LuaInterface +{ + + using System; + using System.Runtime.InteropServices; + using System.Reflection; + using System.Collections; + using System.Text; + using System.Security; + + /* + * Lua types for the API, returned by lua_type function + */ + public enum LuaTypes + { + LUA_TNONE=-1, + LUA_TNIL=0, + LUA_TNUMBER=3, + LUA_TSTRING=4, + LUA_TBOOLEAN=1, + LUA_TTABLE=5, + LUA_TFUNCTION=6, + LUA_TUSERDATA=7, + LUA_TLIGHTUSERDATA=2 + } + + // steffenj: BEGIN lua garbage collector options + /* + * Lua Garbage Collector options (param "what") + */ + public enum LuaGCOptions + { + LUA_GCSTOP = 0, + LUA_GCRESTART = 1, + LUA_GCCOLLECT = 2, + LUA_GCCOUNT = 3, + LUA_GCCOUNTB = 4, + LUA_GCSTEP = 5, + LUA_GCSETPAUSE = 6, + LUA_GCSETSTEPMUL = 7, + } + /* + sealed class LuaGCOptions + { + public static int LUA_GCSTOP = 0; + public static int LUA_GCRESTART = 1; + public static int LUA_GCCOLLECT = 2; + public static int LUA_GCCOUNT = 3; + public static int LUA_GCCOUNTB = 4; + public static int LUA_GCSTEP = 5; + public static int LUA_GCSETPAUSE = 6; + public static int LUA_GCSETSTEPMUL = 7; + }; + */ + // steffenj: END lua garbage collector options + + /* + * Special stack indexes + */ + sealed class LuaIndexes + { + public static int LUA_REGISTRYINDEX=-10000; + public static int LUA_ENVIRONINDEX=-10001; // steffenj: added environindex + public static int LUA_GLOBALSINDEX=-10002; // steffenj: globalsindex previously was -10001 + } + + /* + * Structure used by the chunk reader + */ + [ StructLayout( LayoutKind.Sequential )] + public struct ReaderInfo + { + public String chunkData; + public bool finished; + } + + /* + * Delegate for functions passed to Lua as function pointers + */ + public delegate int LuaCSFunction(IntPtr luaState); + + /* + * Delegate for chunk readers used with lua_load + */ + public delegate string LuaChunkReader(IntPtr luaState,ref ReaderInfo data,ref uint size); + + + /// + /// Used to handle Lua panics + /// + /// + /// + public delegate int LuaFunctionCallback(IntPtr luaState); + + /* + * P/Invoke wrapper of the Lua API + * + * Author: Fabio Mascarenhas + * Version: 1.0 + * + * // steffenj: noteable changes in the LuaDLL API: + * - luaopen_* functions are gone + * (however Lua class constructor already calls luaL_openlibs now, so just remove these calls) + * - deprecated functions: lua_open, lua_strlen, lua_dostring + * (they still work but may be removed with next Lua version) + * + * list of functions of the Lua 5.1.1 C API that are not in LuaDLL + * i thought this may come in handy for the next Lua version upgrade and for anyone to see + * what the differences are in the APIs (C API vs LuaDLL API) + lua_concat (use System.String concatenation or similar) + lua_cpcall (no point in calling C functions) + lua_dump (would write to unmanaged memory via lua_Writer) + lua_getallocf (no C functions/pointers) + lua_isthread (no threads) + lua_newstate (use luaL_newstate) + lua_newthread (no threads) + lua_pushcclosure (no C functions/pointers) + lua_pushcfunction (no C functions/pointers) + lua_pushfstring (use lua_pushstring) + lua_pushthread (no threads) + lua_pushvfstring (use lua_pushstring) + lua_register (all libs already opened, use require in scripts for external libs) + lua_resume (no threads) + lua_setallocf (no C functions/pointers) + lua_status (no threads) + lua_tointeger (use System.Convert) + lua_tolstring (use lua_tostring) + lua_topointer (no C functions/pointers) + lua_tothread (no threads) + lua_xmove (no threads) + lua_yield (no threads) + + luaL_add* (use System.String concatenation or similar) + luaL_argcheck (function argument checking unnecessary) + luaL_argerror (function argument checking unnecessary) + luaL_buffinit (use System.String concatenation or similar) + luaL_checkany (function argument checking unnecessary) + luaL_checkint (function argument checking unnecessary) + luaL_checkinteger (function argument checking unnecessary) + luaL_checklong (function argument checking unnecessary) + luaL_checklstring (function argument checking unnecessary) + luaL_checknumber (function argument checking unnecessary) + luaL_checkoption (function argument checking unnecessary) + luaL_checkstring (function argument checking unnecessary) + luaL_checktype (function argument checking unnecessary) + luaL_prepbuffer (use System.String concatenation or similar) + luaL_pushresult (use System.String concatenation or similar) + luaL_register (all libs already opened, use require in scripts for external libs) + luaL_typerror (function argument checking unnecessary) + + (complete lua_Debug interface omitted) + lua_gethook*** + lua_getinfo + lua_getlocal + lua_getstack + lua_getupvalue + lua_sethook + lua_setlocal + lua_setupvalue + */ + public class LuaDLL + { + // for debugging + // const string BASEPATH = @"C:\development\software\dotnet\tools\PulseRecognizer\PulseRecognizer\bin\Debug\"; + // const string BASEPATH = @"C:\development\software\ThirdParty\lua\Built\"; + const string LUADLL = "lua51.dll"; + const string LUALIBDLL = LUADLL; + const string STUBDLL = LUADLL; + + // steffenj: BEGIN additional Lua API functions new in Lua 5.1 + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_gc(IntPtr luaState, LuaGCOptions what, int data); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern string lua_typename(IntPtr luaState, LuaTypes type); + public static string luaL_typename(IntPtr luaState, int stackPos) + { + return LuaDLL.lua_typename(luaState, LuaDLL.lua_type(luaState, stackPos)); + } + + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaL_error(IntPtr luaState, string message); + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern string luaL_gsub(IntPtr luaState, string str, string pattern, string replacement); + + // the functions below are still untested + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_getfenv(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_isfunction(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_islightuserdata(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_istable(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_isuserdata(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_lessthan(IntPtr luaState, int stackPos1, int stackPos2); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_rawequal(IntPtr luaState, int stackPos1, int stackPos2); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_setfenv(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_setfield(IntPtr luaState, int stackPos, string name); + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int luaL_callmeta(IntPtr luaState, int stackPos, string name); + // steffenj: END additional Lua API functions new in Lua 5.1 + + // steffenj: BEGIN Lua 5.1.1 API change (lua_open replaced by luaL_newstate) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr luaL_newstate(); + /// DEPRECATED - use luaL_newstate() instead! + public static IntPtr lua_open() + { + return LuaDLL.luaL_newstate(); + } + // steffenj: END Lua 5.1.1 API change (lua_open replaced by luaL_newstate) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_close(IntPtr luaState); + // steffenj: BEGIN Lua 5.1.1 API change (new function luaL_openlibs) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaL_openlibs(IntPtr luaState); + /* + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaopen_base(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_io(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_table(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_string(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_math(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_debug(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_loadlib(IntPtr luaState); + */ + // steffenj: END Lua 5.1.1 API change (new function luaL_openlibs) + // steffenj: BEGIN Lua 5.1.1 API change (lua_strlen is now lua_objlen) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_objlen(IntPtr luaState, int stackPos); + /// DEPRECATED - use lua_objlen(IntPtr luaState, int stackPos) instead! + public static int lua_strlen(IntPtr luaState, int stackPos) + { + return lua_objlen(luaState, stackPos); + } + // steffenj: END Lua 5.1.1 API change (lua_strlen is now lua_objlen) + // steffenj: BEGIN Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int luaL_loadstring(IntPtr luaState, string chunk); + public static int luaL_dostring(IntPtr luaState, string chunk) + { + int result = LuaDLL.luaL_loadstring(luaState, chunk); + if (result != 0) + return result; + + return LuaDLL.lua_pcall(luaState, 0, -1, 0); + } + /// DEPRECATED - use luaL_dostring(IntPtr luaState, string chunk) instead! + public static int lua_dostring(IntPtr luaState, string chunk) + { + return LuaDLL.luaL_dostring(luaState, chunk); + } + // steffenj: END Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring) + // steffenj: BEGIN Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_createtable(IntPtr luaState, int narr, int nrec); + public static void lua_newtable(IntPtr luaState) + { + LuaDLL.lua_createtable(luaState, 0, 0); + } + // steffenj: END Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new) + // steffenj: BEGIN Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile macro) + //[DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static int luaL_dofile(IntPtr luaState, string fileName) + { + int result = LuaDLL.luaL_loadfile(luaState, fileName); + if (result != 0) + return result; + + return LuaDLL.lua_pcall(luaState, 0, -1, 0); + } + // steffenj: END Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile) + public static void lua_getglobal(IntPtr luaState, string name) + { + LuaDLL.lua_pushstring(luaState,name); + LuaDLL.lua_gettable(luaState,LuaIndexes.LUA_GLOBALSINDEX); + } + public static void lua_setglobal(IntPtr luaState, string name) + { + LuaDLL.lua_pushstring(luaState,name); + LuaDLL.lua_insert(luaState,-2); + LuaDLL.lua_settable(luaState,LuaIndexes.LUA_GLOBALSINDEX); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_settop(IntPtr luaState, int newTop); + // steffenj: BEGIN added lua_pop "macro" + public static void lua_pop(IntPtr luaState, int amount) + { + LuaDLL.lua_settop(luaState, -(amount) - 1); + } + // steffenj: END added lua_pop "macro" + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_insert(IntPtr luaState, int newTop); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_remove(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_gettable(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawget(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_settable(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawset(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_setmetatable(IntPtr luaState, int objIndex); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_getmetatable(IntPtr luaState, int objIndex); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_equal(IntPtr luaState, int index1, int index2); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushvalue(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_replace(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_gettop(IntPtr luaState); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern LuaTypes lua_type(IntPtr luaState, int index); + public static bool lua_isnil(IntPtr luaState, int index) + { + return (LuaDLL.lua_type(luaState,index)==LuaTypes.LUA_TNIL); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_isnumber(IntPtr luaState, int index); + public static bool lua_isboolean(IntPtr luaState, int index) + { + return LuaDLL.lua_type(luaState,index)==LuaTypes.LUA_TBOOLEAN; + } + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_ref(IntPtr luaState, int registryIndex); + public static int lua_ref(IntPtr luaState, int lockRef) + { + if(lockRef!=0) + { + return LuaDLL.luaL_ref(luaState,LuaIndexes.LUA_REGISTRYINDEX); + } + else return 0; + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawgeti(IntPtr luaState, int tableIndex, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawseti(IntPtr luaState, int tableIndex, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_newuserdata(IntPtr luaState, int size); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_touserdata(IntPtr luaState, int index); + public static void lua_getref(IntPtr luaState, int reference) + { + LuaDLL.lua_rawgeti(luaState,LuaIndexes.LUA_REGISTRYINDEX,reference); + } + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaL_unref(IntPtr luaState, int registryIndex, int reference); + public static void lua_unref(IntPtr luaState, int reference) + { + LuaDLL.luaL_unref(luaState,LuaIndexes.LUA_REGISTRYINDEX,reference); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_isstring(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_iscfunction(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushnil(IntPtr luaState); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushstdcallcfunction(IntPtr luaState, [MarshalAs(UnmanagedType.FunctionPtr)]LuaCSFunction function); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_call(IntPtr luaState, int nArgs, int nResults); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_pcall(IntPtr luaState, int nArgs, int nResults, int errfunc); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_rawcall(IntPtr luaState, int nArgs, int nResults); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_tocfunction(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern double lua_tonumber(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_toboolean(IntPtr luaState, int index); + + [DllImport(LUADLL,CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr lua_tolstring(IntPtr luaState, int index, out int strLen); + + public static string lua_tostring(IntPtr luaState, int index) + { + int strlen; + + IntPtr str = lua_tolstring(luaState, index, out strlen); + if (str != IntPtr.Zero) + return Marshal.PtrToStringAnsi(str, strlen); + else + return null; // treat lua nulls to as C# nulls + } + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_atpanic(IntPtr luaState, LuaFunctionCallback panicf); + + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushnumber(IntPtr luaState, double number); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushboolean(IntPtr luaState, bool value); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushlstring(IntPtr luaState, string str, int size); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushstring(IntPtr luaState, string str); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_newmetatable(IntPtr luaState, string meta); + // steffenj: BEGIN Lua 5.1.1 API change (luaL_getmetatable is now a macro using lua_getfield) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_getfield(IntPtr luaState, int stackPos, string meta); + public static void luaL_getmetatable(IntPtr luaState, string meta) + { + LuaDLL.lua_getfield(luaState, LuaIndexes.LUA_REGISTRYINDEX, meta); + } + // steffenj: END Lua 5.1.1 API change (luaL_getmetatable is now a macro using lua_getfield) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr luaL_checkudata(IntPtr luaState, int stackPos, string meta); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool luaL_getmetafield(IntPtr luaState, int stackPos, string field); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_load(IntPtr luaState, LuaChunkReader chunkReader, ref ReaderInfo data, string chunkName); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_loadbuffer(IntPtr luaState, string buff, int size, string name); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_loadfile(IntPtr luaState, string filename); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool luaL_checkmetatable(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_tonetobject(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_newudata(IntPtr luaState,int val); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_rawnetobj(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_checkudata(IntPtr luaState,int obj,string meta); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_error(IntPtr luaState); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern bool lua_checkstack(IntPtr luaState,int extra); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_next(IntPtr luaState,int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushlightuserdata(IntPtr luaState, IntPtr udata); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr luanet_gettag(); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaL_where (IntPtr luaState, int level); + } +} diff --git a/LuaInterface/LuaException.cs b/LuaInterface/LuaException.cs new file mode 100644 index 0000000000..1393e6a989 --- /dev/null +++ b/LuaInterface/LuaException.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.Serialization; + +namespace LuaInterface +{ + /// + /// Exceptions thrown by the Lua runtime + /// + [Serializable] + public class LuaException : Exception + { + public LuaException() + {} + + public LuaException(string message) : base(message) + {} + + public LuaException(string message, Exception innerException) : base(message, innerException) + {} + + protected LuaException(SerializationInfo info, StreamingContext context) : base(info, context) + {} + + public override string ToString() + { + return Message; + } + } +} diff --git a/LuaInterface/LuaFunction.cs b/LuaInterface/LuaFunction.cs new file mode 100644 index 0000000000..2ef89a1ab4 --- /dev/null +++ b/LuaInterface/LuaFunction.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LuaInterface +{ + public class LuaFunction : LuaBase + { + //private Lua interpreter; + internal LuaCSFunction function; + //internal int reference; + + public LuaFunction(int reference, Lua interpreter) + { + _Reference = reference; + this.function = null; + _Interpreter = interpreter; + } + + public LuaFunction(LuaCSFunction function, Lua interpreter) + { + _Reference = 0; + this.function = function; + _Interpreter = interpreter; + } + + //~LuaFunction() + //{ + // if (reference != 0) + // interpreter.dispose(reference); + //} + + //bool disposed = false; + //~LuaFunction() + //{ + // Dispose(false); + //} + + //public void Dispose() + //{ + // Dispose(true); + // GC.SuppressFinalize(this); + //} + + //public virtual void Dispose(bool disposeManagedResources) + //{ + // if (!this.disposed) + // { + // if (disposeManagedResources) + // { + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + // } + + // disposed = true; + // } + //} + + + /* + * Calls the function casting return values to the types + * in returnTypes + */ + internal object[] call(object[] args, Type[] returnTypes) + { + return _Interpreter.callFunction(this, args, returnTypes); + } + /* + * Calls the function and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + return _Interpreter.callFunction(this, args); + } + /* + * Pushes the function into the Lua stack + */ + internal void push(IntPtr luaState) + { + if (_Reference != 0) + LuaDLL.lua_getref(luaState, _Reference); + else + _Interpreter.pushCSFunction(function); + } + public override string ToString() + { + return "function"; + } + public override bool Equals(object o) + { + if (o is LuaFunction) + { + LuaFunction l = (LuaFunction)o; + if (this._Reference != 0 && l._Reference != 0) + return _Interpreter.compareRef(l._Reference, this._Reference); + else + return this.function == l.function; + } + else return false; + } + + public override int GetHashCode() + { + if (_Reference != 0) + // elisee: Used to return _Reference + // which doesn't make sense as you can have different refs + // to the same function + return 0; + else + return function.GetHashCode(); + } + } + +} diff --git a/LuaInterface/LuaGlobalAttribute.cs b/LuaInterface/LuaGlobalAttribute.cs new file mode 100644 index 0000000000..f444f10ec1 --- /dev/null +++ b/LuaInterface/LuaGlobalAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace LuaInterface +{ + /// + /// Marks a method for global usage in Lua scripts + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LuaGlobalAttribute : Attribute + { + /// + /// An alternative name to use for calling the function in Lua - leave empty for CLR name + /// + public string Name { get; set; } + + /// + /// A description of the function + /// + public string Description { get; set; } + } +} diff --git a/LuaInterface/LuaHideAttribute.cs b/LuaInterface/LuaHideAttribute.cs new file mode 100644 index 0000000000..854f5de78b --- /dev/null +++ b/LuaInterface/LuaHideAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace LuaInterface +{ + /// + /// Marks a method, field or property to be hidden from Lua auto-completion + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class LuaHideAttribute : Attribute + {} +} diff --git a/LuaInterface/LuaInterface.csproj b/LuaInterface/LuaInterface.csproj new file mode 100644 index 0000000000..9098fd212a --- /dev/null +++ b/LuaInterface/LuaInterface.csproj @@ -0,0 +1,47 @@ + + + + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1} + v3.5 + Client + + + ..\ + + + Library + + + + + + + + + + + + + + + + + + + + + + + + + + + + {bdaeab25-991e-46a7-af1e-4f0e03358daa} + OpenRA.FileFormats + + + + + + \ No newline at end of file diff --git a/LuaInterface/LuaRegistrationHelper.cs b/LuaInterface/LuaRegistrationHelper.cs new file mode 100644 index 0000000000..9e3fc0fa5a --- /dev/null +++ b/LuaInterface/LuaRegistrationHelper.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace LuaInterface +{ + public static class LuaRegistrationHelper + { + #region Tagged instance methods + /// + /// Registers all public instance methods in an object tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The object to get the methods from + public static void TaggedInstanceMethods(Lua lua, object o) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + if (o == null) throw new ArgumentNullException("o"); + #endregion + + foreach (MethodInfo method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, o, method); // CLR name + else + lua.RegisterFunction(attribute.Name, o, method); // Custom name + } + } + } + #endregion + + #region Tagged static methods + /// + /// Registers all public static methods in a class tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The class type to get the methods from + public static void TaggedStaticMethods(Lua lua, Type type) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + if (type == null) throw new ArgumentNullException("type"); + if (!type.IsClass) throw new ArgumentException("The type must be a class!", "type"); + #endregion + + foreach (MethodInfo method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, null, method); // CLR name + else + lua.RegisterFunction(attribute.Name, null, method); // Custom name + } + } + } + #endregion + + #region Enumeration + /// + /// Registers an enumeration's values for usage as a Lua variable table + /// + /// The enum type to register + /// The Lua VM to add the enum to + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type parameter is used to select an enum type")] + public static void Enumeration(Lua lua) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + #endregion + + Type type = typeof(T); + if (!type.IsEnum) throw new ArgumentException("The type must be an enumeration!"); + + string[] names = Enum.GetNames(type); + T[] values = (T[])Enum.GetValues(type); + + lua.NewTable(type.Name); + for (int i = 0; i < names.Length; i++) + { + string path = type.Name + "." + names[i]; + lua[path] = values[i]; + } + } + #endregion + } +} diff --git a/LuaInterface/LuaScriptException.cs b/LuaInterface/LuaScriptException.cs new file mode 100644 index 0000000000..6b45a0ca7e --- /dev/null +++ b/LuaInterface/LuaScriptException.cs @@ -0,0 +1,50 @@ +using System; + +namespace LuaInterface +{ + /// + /// Exceptions thrown by the Lua runtime because of errors in the script + /// + public class LuaScriptException : LuaException + { + /// + /// Returns true if the exception has occured as the result of a .NET exception in user code + /// + public bool IsNetException { get; private set; } + + private readonly string source; + + /// + /// The position in the script where the exception was triggered. + /// + public override string Source { get { return source; } } + + /// + /// Creates a new Lua-only exception. + /// + /// The message that describes the error. + /// The position in the script where the exception was triggered. + public LuaScriptException(string message, string source) : base(message) + { + this.source = source; + } + + /// + /// Creates a new .NET wrapping exception. + /// + /// The .NET exception triggered by user-code. + /// The position in the script where the exception was triggered. + public LuaScriptException(Exception innerException, string source) + : base(innerException.Message, innerException) + { + this.source = source; + this.IsNetException = true; + } + + public override string ToString() + { + // Prepend the error source + return GetType().FullName + ": " + source + Message; + } + } +} diff --git a/LuaInterface/LuaTable.cs b/LuaInterface/LuaTable.cs new file mode 100644 index 0000000000..d9fb67c47d --- /dev/null +++ b/LuaInterface/LuaTable.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections; + +namespace LuaInterface +{ + /* + * Wrapper class for Lua tables + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaTable : LuaBase + { + public bool IsOrphaned; + + //internal int _Reference; + //private Lua _Interpreter; + public LuaTable(int reference, Lua interpreter) + { + _Reference = reference; + _Interpreter = interpreter; + } + + //bool disposed = false; + //~LuaTable() + //{ + // Dispose(false); + //} + + //public void Dispose() + //{ + // Dispose(true); + // GC.SuppressFinalize(this); + //} + + //public virtual void Dispose(bool disposeManagedResources) + //{ + // if (!this.disposed) + // { + // if (disposeManagedResources) + // { + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + // } + + // disposed = true; + // } + //} + //~LuaTable() + //{ + // _Interpreter.dispose(_Reference); + //} + /* + * Indexer for string fields of the table + */ + public object this[string field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Indexer for numeric fields of the table + */ + public object this[object field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + + + public System.Collections.IDictionaryEnumerator GetEnumerator() + { + return _Interpreter.GetTableDict(this).GetEnumerator(); + } + + public ICollection Keys + { + get { return _Interpreter.GetTableDict(this).Keys; } + } + + public ICollection Values + { + get { return _Interpreter.GetTableDict(this).Values; } + } + + /* + * Gets an string fields of a table ignoring its metatable, + * if it exists + */ + internal object rawget(string field) + { + return _Interpreter.rawGetObject(_Reference, field); + } + + internal object rawgetFunction(string field) + { + object obj = _Interpreter.rawGetObject(_Reference, field); + + if (obj is LuaCSFunction) + return new LuaFunction((LuaCSFunction)obj, _Interpreter); + else + return obj; + } + + /* + * Pushes this table into the Lua stack + */ + internal void push(IntPtr luaState) + { + LuaDLL.lua_getref(luaState, _Reference); + } + public override string ToString() + { + return "table"; + } + + //public override bool Equals(object o) + //{ + // if (o is LuaTable) + // { + // LuaTable l = (LuaTable)o; + // return _Interpreter.compareRef(l._Reference, _Reference); + // } + // else return false; + //} + //public override int GetHashCode() + //{ + // return _Reference; + //} + } +} diff --git a/LuaInterface/LuaUserData.cs b/LuaInterface/LuaUserData.cs new file mode 100644 index 0000000000..17b3e75505 --- /dev/null +++ b/LuaInterface/LuaUserData.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LuaInterface +{ + public class LuaUserData : LuaBase + { + //internal int _Reference; + //private Lua _Interpreter; + public LuaUserData(int reference, Lua interpreter) + { + _Reference = reference; + _Interpreter = interpreter; + } + //~LuaUserData() + //{ + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + //} + /* + * Indexer for string fields of the userdata + */ + public object this[string field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Indexer for numeric fields of the userdata + */ + public object this[object field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Calls the userdata and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + return _Interpreter.callFunction(this, args); + } + /* + * Pushes the userdata into the Lua stack + */ + internal void push(IntPtr luaState) + { + LuaDLL.lua_getref(luaState, _Reference); + } + public override string ToString() + { + return "userdata"; + } + //public override bool Equals(object o) + //{ + // if (o is LuaUserData) + // { + // LuaUserData l = (LuaUserData)o; + // return _Interpreter.compareRef(l._Reference, _Reference); + // } + // else return false; + //} + //public override int GetHashCode() + //{ + // return _Reference; + //} + } +} diff --git a/LuaInterface/Metatables.cs b/LuaInterface/Metatables.cs new file mode 100644 index 0000000000..630ea6791e --- /dev/null +++ b/LuaInterface/Metatables.cs @@ -0,0 +1,935 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Diagnostics; + using System.Collections.Generic; + using System.Runtime.InteropServices; + + /* + * Functions used in the metatables of userdata representing + * CLR objects + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class MetaFunctions + { + /* + * __index metafunction for CLR objects. Implemented in Lua. + */ + internal static string luaIndexFunction = + @" + local function index(obj,name) + local meta=getmetatable(obj) + local cached=meta.cache[name] + if cached then + return cached + else + local value,isFunc = get_object_member(obj,name) + if value==nil and type(isFunc)=='string' then error(isFunc,2) end + if isFunc then + meta.cache[name]=value + end + return value + end + end + return index"; + + private ObjectTranslator translator; + private Hashtable memberCache = new Hashtable(); + internal LuaCSFunction gcFunction, indexFunction, newindexFunction, + baseIndexFunction, classIndexFunction, classNewindexFunction, + execDelegateFunction, callConstructorFunction, toStringFunction; + + public MetaFunctions(ObjectTranslator translator) + { + this.translator = translator; + gcFunction = new LuaCSFunction(this.collectObject); + toStringFunction = new LuaCSFunction(this.toString); + indexFunction = new LuaCSFunction(this.getMethod); + newindexFunction = new LuaCSFunction(this.setFieldOrProperty); + baseIndexFunction = new LuaCSFunction(this.getBaseMethod); + callConstructorFunction = new LuaCSFunction(this.callConstructor); + classIndexFunction = new LuaCSFunction(this.getClassMethod); + classNewindexFunction = new LuaCSFunction(this.setClassFieldOrProperty); + execDelegateFunction = new LuaCSFunction(this.runFunctionDelegate); + } + + /* + * __call metafunction of CLR delegates, retrieves and calls the delegate. + */ + private int runFunctionDelegate(IntPtr luaState) + { + LuaCSFunction func = (LuaCSFunction)translator.getRawNetObject(luaState, 1); + LuaDLL.lua_remove(luaState, 1); + return func(luaState); + } + /* + * __gc metafunction of CLR objects. + */ + private int collectObject(IntPtr luaState) + { + int udata = LuaDLL.luanet_rawnetobj(luaState, 1); + if (udata != -1) + { + translator.collectObject(udata); + } + else + { + // Debug.WriteLine("not found: " + udata); + } + return 0; + } + /* + * __tostring metafunction of CLR objects. + */ + private int toString(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj != null) + { + translator.push(luaState, obj.ToString() + ": " + obj.GetHashCode()); + } + else LuaDLL.lua_pushnil(luaState); + return 1; + } + + + /// + /// Debug tool to dump the lua stack + /// + /// FIXME, move somewhere else + public static void dumpStack(ObjectTranslator translator, IntPtr luaState) + { + int depth = LuaDLL.lua_gettop(luaState); + + Debug.WriteLine("lua stack depth: " + depth); + for (int i = 1; i <= depth; i++) + { + LuaTypes type = LuaDLL.lua_type(luaState, i); + // we dump stacks when deep in calls, calling typename while the stack is in flux can fail sometimes, so manually check for key types + string typestr = (type == LuaTypes.LUA_TTABLE) ? "table" : LuaDLL.lua_typename(luaState, type); + + string strrep = LuaDLL.lua_tostring(luaState, i); + if (type == LuaTypes.LUA_TUSERDATA) + { + object obj = translator.getRawNetObject(luaState, i); + strrep = obj.ToString(); + } + + Debug.Print("{0}: ({1}) {2}", i, typestr, strrep); + } + } + + /* + * Called by the __index metafunction of CLR objects in case the + * method is not cached or it is a field/property/event. + * Receives the object and the member name as arguments and returns + * either the value of the member or a delegate to call it. + * If the member does not exist returns nil. + */ + private int getMethod(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null) + { + translator.throwError(luaState, "trying to index an invalid object reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + object index = translator.getObject(luaState, 2); + + string methodName = index as string; // will be null if not a string arg + Type objType = obj.GetType(); + + // Handle the most common case, looking up the method by name. + + // CP: This will fail when using indexers and attempting to get a value with the same name as a property of the object, + // ie: xmlelement['item'] <- item is a property of xmlelement + try + { + if (methodName != null && isMemberPresent(objType, methodName)) + return getMember(luaState, objType, obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + } + catch { } + bool failed = true; + + // Try to access by array if the type is right and index is an int (lua numbers always come across as double) + if (objType.IsArray && index is double) + { + int intIndex = (int)((double)index); + Array aa = obj as Array; + if (intIndex >= aa.Length) { + return translator.pushError(luaState,"array index out of bounds: "+intIndex + " " + aa.Length); + } + object val = aa.GetValue(intIndex); + translator.push (luaState,val); + failed = false; + } + else + { + // Try to use get_Item to index into this .net object + //MethodInfo getter = objType.GetMethod("get_Item"); + // issue here is that there may be multiple indexers.. + MethodInfo[] methods = objType.GetMethods(); + + foreach (MethodInfo mInfo in methods) + { + if (mInfo.Name == "get_Item") + { + //check if the signature matches the input + if (mInfo.GetParameters().Length == 1) + { + MethodInfo getter = mInfo; + ParameterInfo[] actualParms = (getter != null) ? getter.GetParameters() : null; + if (actualParms == null || actualParms.Length != 1) + { + return translator.pushError(luaState, "method not found (or no indexer): " + index); + } + else + { + // Get the index in a form acceptable to the getter + index = translator.getAsType(luaState, 2, actualParms[0].ParameterType); + // Just call the indexer - if out of bounds an exception will happen + try + { + object result = getter.Invoke(obj, new object[]{index}); + translator.push(luaState, result); + failed = false; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + return translator.pushError(luaState, "key '" + index + "' not found "); + else + return translator.pushError(luaState, "exception indexing '" + index + "' " + e.Message); + + + } + } + } + } + } + + + } + if (failed) { + return translator.pushError(luaState,"cannot find " + index); + } + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + + + /* + * __index metafunction of base classes (the base field of Lua tables). + * Adds a prefix to the method name to call the base version of the method. + */ + private int getBaseMethod(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null) + { + translator.throwError(luaState, "trying to index an invalid object reference"); + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + string methodName = LuaDLL.lua_tostring(luaState, 2); + if (methodName == null) + { + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + getMember(luaState, obj.GetType(), obj, "__luaInterface_base_" + methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + LuaDLL.lua_settop(luaState, -2); + if (LuaDLL.lua_type(luaState, -1) == LuaTypes.LUA_TNIL) + { + LuaDLL.lua_settop(luaState, -2); + return getMember(luaState, obj.GetType(), obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + } + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + + + /// + /// Does this method exist as either an instance or static? + /// + /// + /// + /// + bool isMemberPresent(IReflect objType, string methodName) + { + object cachedMember = checkMemberCache(memberCache, objType, methodName); + + if (cachedMember != null) + return true; + + //CP: Removed NonPublic binding search + MemberInfo[] members = objType.GetMember(methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase/* | BindingFlags.NonPublic*/); + return (members.Length > 0); + } + + /* + * Pushes the value of a member or a delegate to call it, depending on the type of + * the member. Works with static or instance members. + * Uses reflection to find members, and stores the reflected MemberInfo object in + * a cache (indexed by the type of the object and the name of the member). + */ + private int getMember(IntPtr luaState, IReflect objType, object obj, string methodName, BindingFlags bindingType) + { + bool implicitStatic = false; + MemberInfo member = null; + object cachedMember = checkMemberCache(memberCache, objType, methodName); + //object cachedMember=null; + if (cachedMember is LuaCSFunction) + { + translator.pushFunction(luaState, (LuaCSFunction)cachedMember); + translator.push(luaState, true); + return 2; + } + else if (cachedMember != null) + { + member = (MemberInfo)cachedMember; + } + else + { + //CP: Removed NonPublic binding search + MemberInfo[] members = objType.GetMember(methodName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + if (members.Length > 0) + member = members[0]; + else + { + // If we can't find any suitable instance members, try to find them as statics - but we only want to allow implicit static + // lookups for fields/properties/events -kevinh + //CP: Removed NonPublic binding search and made case insensitive + members = objType.GetMember(methodName, bindingType | BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + + if (members.Length > 0) + { + member = members[0]; + implicitStatic = true; + } + } + } + if (member != null) + { + if (member.MemberType == MemberTypes.Field) + { + FieldInfo field = (FieldInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + try + { + translator.push(luaState, field.GetValue(obj)); + } + catch + { + LuaDLL.lua_pushnil(luaState); + } + } + else if (member.MemberType == MemberTypes.Property) + { + PropertyInfo property = (PropertyInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + try + { + object val = property.GetValue(obj, null); + + translator.push(luaState, val); + } + catch (ArgumentException) + { + // If we can't find the getter in our class, recurse up to the base class and see + // if they can help. + + if (objType is Type && !(((Type)objType) == typeof(object))) + return getMember(luaState, ((Type)objType).BaseType, obj, methodName, bindingType); + else + LuaDLL.lua_pushnil(luaState); + } + catch (TargetInvocationException e) // Convert this exception into a Lua error + { + ThrowError(luaState, e); + LuaDLL.lua_pushnil(luaState); + } + } + else if (member.MemberType == MemberTypes.Event) + { + EventInfo eventInfo = (EventInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + translator.push(luaState, new RegisterEventHandler(translator.pendingEvents, obj, eventInfo)); + } + else if (!implicitStatic) + { + if (member.MemberType == MemberTypes.NestedType) + { + // kevinh - added support for finding nested types + + // cache us + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + + // Find the name of our class + string name = member.Name; + Type dectype = member.DeclaringType; + + // Build a new long name and try to find the type by name + string longname = dectype.FullName + "+" + name; + Type nestedType = translator.FindType(longname); + + translator.pushType(luaState, nestedType); + } + else + { + // Member type must be 'method' + LuaCSFunction wrapper = new LuaCSFunction((new LuaMethodWrapper(translator, objType, methodName, bindingType)).call); + + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, wrapper); + translator.pushFunction(luaState, wrapper); + translator.push(luaState, true); + return 2; + } + } + else + { + // If we reach this point we found a static method, but can't use it in this context because the user passed in an instance + translator.throwError(luaState, "can't pass instance to static method " + methodName); + + LuaDLL.lua_pushnil(luaState); + } + } + else + { + // kevinh - we want to throw an exception because meerly returning 'nil' in this case + // is not sufficient. valid data members may return nil and therefore there must be some + // way to know the member just doesn't exist. + + translator.throwError(luaState, "unknown member name " + methodName); + + LuaDLL.lua_pushnil(luaState); + } + + // push false because we are NOT returning a function (see luaIndexFunction) + translator.push(luaState, false); + return 2; + } + /* + * Checks if a MemberInfo object is cached, returning it or null. + */ + private object checkMemberCache(Hashtable memberCache, IReflect objType, string memberName) + { + Hashtable members = (Hashtable)memberCache[objType]; + if (members != null) + return members[memberName]; + else + return null; + } + /* + * Stores a MemberInfo object in the member cache. + */ + private void setMemberCache(Hashtable memberCache, IReflect objType, string memberName, object member) + { + Hashtable members = (Hashtable)memberCache[objType]; + if (members == null) + { + members = new Hashtable(); + memberCache[objType] = members; + } + members[memberName] = member; + } + /* + * __newindex metafunction of CLR objects. Receives the object, + * the member name and the value to be stored as arguments. Throws + * and error if the assignment is invalid. + */ + private int setFieldOrProperty(IntPtr luaState) + { + object target = translator.getRawNetObject(luaState, 1); + if (target == null) + { + translator.throwError(luaState, "trying to index and invalid object reference"); + return 0; + } + Type type = target.GetType(); + + // First try to look up the parameter as a property name + string detailMessage; + bool didMember = trySetMember(luaState, type, target, BindingFlags.Instance | BindingFlags.IgnoreCase, out detailMessage); + + if (didMember) + return 0; // Must have found the property name + + // We didn't find a property name, now see if we can use a [] style this accessor to set array contents + try + { + if (type.IsArray && LuaDLL.lua_isnumber(luaState, 2)) + { + int index = (int)LuaDLL.lua_tonumber(luaState, 2); + + Array arr = (Array)target; + object val = translator.getAsType(luaState, 3, arr.GetType().GetElementType()); + arr.SetValue(val, index); + } + else + { + // Try to see if we have a this[] accessor + MethodInfo setter = type.GetMethod("set_Item"); + if (setter != null) + { + ParameterInfo[] args = setter.GetParameters(); + Type valueType = args[1].ParameterType; + + // The new val ue the user specified + object val = translator.getAsType(luaState, 3, valueType); + + Type indexType = args[0].ParameterType; + object index = translator.getAsType(luaState, 2, indexType); + + object[] methodArgs = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + methodArgs[0] = index; + methodArgs[1] = val; + + setter.Invoke(target, methodArgs); + } + else + { + translator.throwError(luaState, detailMessage); // Pass the original message from trySetMember because it is probably best + } + } + } + catch (SEHException) + { + // If we are seeing a C++ exception - this must actually be for Lua's private use. Let it handle it + throw; + } + catch (Exception e) + { + ThrowError(luaState, e); + } + return 0; + } + + /// + /// Tries to set a named property or field + /// + /// + /// + /// + /// + /// false if unable to find the named member, true for success + private bool trySetMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType, out string detailMessage) + { + detailMessage = null; // No error yet + + // If not already a string just return - we don't want to call tostring - which has the side effect of + // changing the lua typecode to string + // Note: We don't use isstring because the standard lua C isstring considers either strings or numbers to + // be true for isstring. + if (LuaDLL.lua_type(luaState, 2) != LuaTypes.LUA_TSTRING) + { + detailMessage = "property names must be strings"; + return false; + } + + // We only look up property names by string + string fieldName = LuaDLL.lua_tostring(luaState, 2); + if (fieldName == null || fieldName.Length < 1 || !(char.IsLetter(fieldName[0]) || fieldName[0] == '_')) + { + detailMessage = "invalid property name"; + return false; + } + + // Find our member via reflection or the cache + MemberInfo member = (MemberInfo)checkMemberCache(memberCache, targetType, fieldName); + if (member == null) + { + //CP: Removed NonPublic binding search and made case insensitive + MemberInfo[] members = targetType.GetMember(fieldName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + if (members.Length > 0) + { + member = members[0]; + setMemberCache(memberCache, targetType, fieldName, member); + } + else + { + detailMessage = "field or property '" + fieldName + "' does not exist"; + return false; + } + } + + if (member.MemberType == MemberTypes.Field) + { + FieldInfo field = (FieldInfo)member; + object val = translator.getAsType(luaState, 3, field.FieldType); + try + { + field.SetValue(target, val); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + // We did a call + return true; + } + else if (member.MemberType == MemberTypes.Property) + { + PropertyInfo property = (PropertyInfo)member; + object val = translator.getAsType(luaState, 3, property.PropertyType); + try + { + property.SetValue(target, val, null); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + // We did a call + return true; + } + + detailMessage = "'" + fieldName + "' is not a .net field or property"; + return false; + } + + + /* + * Writes to fields or properties, either static or instance. Throws an error + * if the operation is invalid. + */ + private int setMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType) + { + string detail; + bool success = trySetMember(luaState, targetType, target, bindingType, out detail); + + if (!success) + translator.throwError(luaState, detail); + + return 0; + } + + /// + /// Convert a C# exception into a Lua error + /// + /// + /// We try to look into the exception to give the most meaningful description + void ThrowError(IntPtr luaState, Exception e) + { + // If we got inside a reflection show what really happened + TargetInvocationException te = e as TargetInvocationException; + + if (te != null) + e = te.InnerException; + + translator.throwError(luaState, e); + } + + /* + * __index metafunction of type references, works on static members. + */ + private int getClassMethod(IntPtr luaState) + { + IReflect klass; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to index an invalid type reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + else klass = (IReflect)obj; + if (LuaDLL.lua_isnumber(luaState, 2)) + { + int size = (int)LuaDLL.lua_tonumber(luaState, 2); + translator.push(luaState, Array.CreateInstance(klass.UnderlyingSystemType, size)); + return 1; + } + else + { + string methodName = LuaDLL.lua_tostring(luaState, 2); + if (methodName == null) + { + LuaDLL.lua_pushnil(luaState); + return 1; + } //CP: Ignore case + else return getMember(luaState, klass, null, methodName, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase); + } + } + /* + * __newindex function of type references, works on static members. + */ + private int setClassFieldOrProperty(IntPtr luaState) + { + IReflect target; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to index an invalid type reference"); + return 0; + } + else target = (IReflect)obj; + return setMember(luaState, target, null, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase); + } + /* + * __call metafunction of type references. Searches for and calls + * a constructor for the type. Returns nil if the constructor is not + * found or if the arguments are invalid. Throws an error if the constructor + * generates an exception. + */ + private int callConstructor(IntPtr luaState) + { + MethodCache validConstructor = new MethodCache(); + IReflect klass; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to call constructor on an invalid type reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + else klass = (IReflect)obj; + LuaDLL.lua_remove(luaState, 1); + ConstructorInfo[] constructors = klass.UnderlyingSystemType.GetConstructors(); + foreach (ConstructorInfo constructor in constructors) + { + bool isConstructor = matchParameters(luaState, constructor, ref validConstructor); + if (isConstructor) + { + try + { + translator.push(luaState, constructor.Invoke(validConstructor.args)); + } + catch (TargetInvocationException e) + { + ThrowError(luaState, e); + LuaDLL.lua_pushnil(luaState); + } + catch + { + LuaDLL.lua_pushnil(luaState); + } + return 1; + } + } + + string constructorName = (constructors.Length == 0) ? "unknown" : constructors[0].Name; + + translator.throwError(luaState, String.Format("{0} does not contain constructor({1}) argument match", + klass.UnderlyingSystemType, + constructorName)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + private static bool IsInteger(double x) { + return Math.Ceiling(x) == x; + } + + + internal Array TableToArray(object luaParamValue, Type paramArrayType) { + Array paramArray; + + if (luaParamValue is LuaTable) { + LuaTable table = (LuaTable)luaParamValue; + IDictionaryEnumerator tableEnumerator = table.GetEnumerator(); + tableEnumerator.Reset(); + paramArray = Array.CreateInstance(paramArrayType, table.Values.Count); + + int paramArrayIndex = 0; + + while(tableEnumerator.MoveNext()) { + object o = tableEnumerator.Value; + if (paramArrayType == typeof(object)) { + if (o != null && o.GetType() == typeof(double) && IsInteger((double)o)) + o = Convert.ToInt32((double)o); + } + paramArray.SetValue(Convert.ChangeType(o, paramArrayType), paramArrayIndex); + paramArrayIndex++; + } + } else { + paramArray = Array.CreateInstance(paramArrayType, 1); + paramArray.SetValue(luaParamValue, 0); + } + + return paramArray; + + } + + /* + * Matches a method against its arguments in the Lua stack. Returns + * if the match was succesful. It it was also returns the information + * necessary to invoke the method. + */ + internal bool matchParameters(IntPtr luaState, MethodBase method, ref MethodCache methodCache) + { + ExtractValue extractValue; + bool isMethod = true; + ParameterInfo[] paramInfo = method.GetParameters(); + int currentLuaParam = 1; + int nLuaParams = LuaDLL.lua_gettop(luaState); + ArrayList paramList = new ArrayList(); + List outList = new List(); + List argTypes = new List(); + foreach (ParameterInfo currentNetParam in paramInfo) + { + if (!currentNetParam.IsIn && currentNetParam.IsOut) // Skips out params + { + outList.Add(paramList.Add(null)); + } + else if (currentLuaParam > nLuaParams) // Adds optional parameters + { + if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + } + else + { + isMethod = false; + break; + } + } + else if (_IsTypeCorrect(luaState, currentLuaParam, currentNetParam, out extractValue)) // Type checking + { + int index = paramList.Add(extractValue(luaState, currentLuaParam)); + + MethodArgs methodArg = new MethodArgs(); + methodArg.index = index; + methodArg.extractValue = extractValue; + argTypes.Add(methodArg); + + if (currentNetParam.ParameterType.IsByRef) + outList.Add(index); + currentLuaParam++; + } // Type does not match, ignore if the parameter is optional + else if (_IsParamsArray(luaState, currentLuaParam, currentNetParam, out extractValue)) + { + object luaParamValue = extractValue(luaState, currentLuaParam); + Type paramArrayType = currentNetParam.ParameterType.GetElementType(); + + Array paramArray = TableToArray(luaParamValue, paramArrayType); + int index = paramList.Add(paramArray); + + MethodArgs methodArg = new MethodArgs(); + methodArg.index = index; + methodArg.extractValue = extractValue; + methodArg.isParamsArray = true; + methodArg.paramsArrayType = paramArrayType; + argTypes.Add(methodArg); + + currentLuaParam++; + } + else if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + } + else // No match + { + isMethod = false; + break; + } + } + if (currentLuaParam != nLuaParams + 1) // Number of parameters does not match + isMethod = false; + if (isMethod) + { + methodCache.args = paramList.ToArray(); + methodCache.cachedMethod = method; + methodCache.outList = outList.ToArray(); + methodCache.argTypes = argTypes.ToArray(); + } + return isMethod; + } + + /// + /// CP: Fix for operator overloading failure + /// Returns true if the type is set and assigns the extract value + /// + /// + /// + /// + /// + /// + private bool _IsTypeCorrect(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + try + { + return (extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, currentNetParam.ParameterType)) != null; + } + catch + { + extractValue = null; + Debug.WriteLine("Type wasn't correct"); + return false; + } + } + + private bool _IsParamsArray(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = null; + + if (currentNetParam.GetCustomAttributes(typeof(ParamArrayAttribute), false).Length > 0) + { + LuaTypes luaType; + + try + { + luaType = LuaDLL.lua_type(luaState, currentLuaParam); + } + catch (Exception ex) + { + Debug.WriteLine("Could not retrieve lua type while attempting to determine params Array Status."+ ex.ToString()); + Debug.WriteLine(ex.Message); + extractValue = null; + return false; + } + + if (luaType == LuaTypes.LUA_TTABLE) + { + try + { + extractValue = translator.typeChecker.getExtractor(typeof(LuaTable)); + } + catch (Exception ex) + { + Debug.WriteLine("An error occurred during an attempt to retrieve a LuaTable extractor while checking for params array status." + ex.ToString()); + } + + if (extractValue != null) + { + return true; + } + } + else + { + Type paramElementType = currentNetParam.ParameterType.GetElementType(); + + try + { + extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, paramElementType); + } + catch (Exception ex) + { + Debug.WriteLine(string.Format("An error occurred during an attempt to retrieve an extractor ({0}) while checking for params array status:{1}", paramElementType.FullName,ex.ToString())); + } + + if (extractValue != null) + { + return true; + } + } + } + + Debug.WriteLine("Type wasn't Params object."); + + return false; + } + } +} diff --git a/LuaInterface/MethodWrapper.cs b/LuaInterface/MethodWrapper.cs new file mode 100644 index 0000000000..f572420729 --- /dev/null +++ b/LuaInterface/MethodWrapper.cs @@ -0,0 +1,595 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Collections.Generic; + using System.Diagnostics; + + /* + * Cached method + */ + struct MethodCache + { + private MethodBase _cachedMethod; + + public MethodBase cachedMethod + { + get + { + return _cachedMethod; + } + set + { + _cachedMethod = value; + MethodInfo mi = value as MethodInfo; + if (mi != null) + { + //SJD this is guaranteed to be correct irrespective of actual name used for type.. + IsReturnVoid = mi.ReturnType == typeof(void); + } + } + } + + public bool IsReturnVoid; + + // List or arguments + public object[] args; + // Positions of out parameters + public int[] outList; + // Types of parameters + public MethodArgs[] argTypes; + } + + /* + * Parameter information + */ + struct MethodArgs + { + // Position of parameter + public int index; + // Type-conversion function + public ExtractValue extractValue; + + public bool isParamsArray; + + public Type paramsArrayType; + } + + /* + * Argument extraction with type-conversion function + */ + delegate object ExtractValue(IntPtr luaState, int stackPos); + + /* + * Wrapper class for methods/constructors accessed from Lua. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class LuaMethodWrapper + { + private ObjectTranslator _Translator; + private MethodBase _Method; + private MethodCache _LastCalledMethod = new MethodCache(); + private string _MethodName; + private MemberInfo[] _Members; + private ExtractValue _ExtractTarget; + private object _Target; + private BindingFlags _BindingType; + + /* + * Constructs the wrapper for a known MethodBase instance + */ + public LuaMethodWrapper(ObjectTranslator translator, object target, IReflect targetType, MethodBase method) + { + _Translator = translator; + _Target = target; + if (targetType != null) + _ExtractTarget = translator.typeChecker.getExtractor(targetType); + _Method = method; + _MethodName = method.Name; + + if (method.IsStatic) + { _BindingType = BindingFlags.Static; } + else + { _BindingType = BindingFlags.Instance; } + } + /* + * Constructs the wrapper for a known method name + */ + public LuaMethodWrapper(ObjectTranslator translator, IReflect targetType, string methodName, BindingFlags bindingType) + { + _Translator = translator; + _MethodName = methodName; + + if (targetType != null) + _ExtractTarget = translator.typeChecker.getExtractor(targetType); + + _BindingType = bindingType; + + //CP: Removed NonPublic binding search and added IgnoreCase + _Members = targetType.UnderlyingSystemType.GetMember(methodName, MemberTypes.Method, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*|BindingFlags.NonPublic*/); + } + + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + int SetPendingException(Exception e) + { + return _Translator.interpreter.SetPendingException(e); + } + + private static bool IsInteger(double x) { + return Math.Ceiling(x) == x; + } + + + /* + * Calls the method. Receives the arguments from the Lua stack + * and returns values in it. + */ + public int call(IntPtr luaState) + { + MethodBase methodToCall = _Method; + object targetObject = _Target; + bool failedCall = true; + int nReturnValues = 0; + + if (!LuaDLL.lua_checkstack(luaState, 5)) + throw new LuaException("Lua stack overflow"); + + bool isStatic = (_BindingType & BindingFlags.Static) == BindingFlags.Static; + + SetPendingException(null); + + if (methodToCall == null) // Method from name + { + if (isStatic) + targetObject = null; + else + targetObject = _ExtractTarget(luaState, 1); + + //LuaDLL.lua_remove(luaState,1); // Pops the receiver + if (_LastCalledMethod.cachedMethod != null) // Cached? + { + int numStackToSkip = isStatic ? 0 : 1; // If this is an instance invoe we will have an extra arg on the stack for the targetObject + int numArgsPassed = LuaDLL.lua_gettop(luaState) - numStackToSkip; + MethodBase method = _LastCalledMethod.cachedMethod; + + if (numArgsPassed == _LastCalledMethod.argTypes.Length) // No. of args match? + { + if (!LuaDLL.lua_checkstack(luaState, _LastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + object[] args = _LastCalledMethod.args; + + try + { + for (int i = 0; i < _LastCalledMethod.argTypes.Length; i++) + { + MethodArgs type = _LastCalledMethod.argTypes[i]; + object luaParamValue = type.extractValue(luaState, i + 1 + numStackToSkip); + if (_LastCalledMethod.argTypes[i].isParamsArray) + { + args[type.index] = _Translator.tableToArray(luaParamValue,type.paramsArrayType); + } + else + { + args[type.index] = luaParamValue; + } + + if (args[type.index] == null && + !LuaDLL.lua_isnil(luaState, i + 1 + numStackToSkip)) + { + throw new LuaException("argument number " + (i + 1) + " is invalid"); + } + } + if ((_BindingType & BindingFlags.Static) == BindingFlags.Static) + { + _Translator.push(luaState, method.Invoke(null, args)); + } + else + { + if (_LastCalledMethod.cachedMethod.IsConstructor) + _Translator.push(luaState, ((ConstructorInfo)method).Invoke(args)); + else + _Translator.push(luaState, method.Invoke(targetObject,args)); + } + failedCall = false; + } + catch (TargetInvocationException e) + { + // Failure of method invocation + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + if (_Members.Length == 1) // Is the method overloaded? + // No, throw error + return SetPendingException(e); + } + } + } + + // Cache miss + if (failedCall) + { + // System.Diagnostics.Debug.WriteLine("cache miss on " + methodName); + + // If we are running an instance variable, we can now pop the targetObject from the stack + if (!isStatic) + { + if (targetObject == null) + { + _Translator.throwError(luaState, String.Format("instance method '{0}' requires a non null target object", _MethodName)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + LuaDLL.lua_remove(luaState, 1); // Pops the receiver + } + + bool hasMatch = false; + string candidateName = null; + + foreach (MemberInfo member in _Members) + { + candidateName = member.ReflectedType.Name + "." + member.Name; + + MethodBase m = (MethodInfo)member; + + bool isMethod = _Translator.matchParameters(luaState, m, ref _LastCalledMethod); + if (isMethod) + { + hasMatch = true; + break; + } + } + if (!hasMatch) + { + string msg = (candidateName == null) + ? "invalid arguments to method call: " + _MethodName + : ("invalid arguments to method: " + candidateName); + + _Translator.throwError(luaState, msg); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + } + else // Method from MethodBase instance + { + if (methodToCall.ContainsGenericParameters) + { + // bool isMethod = //* not used + _Translator.matchParameters(luaState, methodToCall, ref _LastCalledMethod); + + if (methodToCall.IsGenericMethodDefinition) + { + //need to make a concrete type of the generic method definition + List typeArgs = new List(); + + foreach (object arg in _LastCalledMethod.args) + typeArgs.Add(arg.GetType()); + + MethodInfo concreteMethod = (methodToCall as MethodInfo).MakeGenericMethod(typeArgs.ToArray()); + + _Translator.push(luaState, concreteMethod.Invoke(targetObject, _LastCalledMethod.args)); + failedCall = false; + } + else if (methodToCall.ContainsGenericParameters) + { + _Translator.throwError(luaState, "unable to invoke method on generic class as the current method is an open generic method"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + else + { + if (!methodToCall.IsStatic && !methodToCall.IsConstructor && targetObject == null) + { + targetObject = _ExtractTarget(luaState, 1); + LuaDLL.lua_remove(luaState, 1); // Pops the receiver + } + + if (!_Translator.matchParameters(luaState, methodToCall, ref _LastCalledMethod)) + { + _Translator.throwError(luaState, string.Format("invalid arguments to method call {0} of type {1}", methodToCall, methodToCall.ReflectedType)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + } + + if (failedCall) + { + if (!LuaDLL.lua_checkstack(luaState, _LastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + try + { + if (isStatic) + { + _Translator.push(luaState, _LastCalledMethod.cachedMethod.Invoke(null, _LastCalledMethod.args)); + } + else + { + if (_LastCalledMethod.cachedMethod.IsConstructor) + _Translator.push(luaState, ((ConstructorInfo)_LastCalledMethod.cachedMethod).Invoke(_LastCalledMethod.args)); + else + { + object returnValue = _LastCalledMethod.cachedMethod.Invoke( targetObject, _LastCalledMethod.args ); + _Translator.push(luaState, returnValue ); + + LuaTable returnValueLuaBase = returnValue as LuaTable; + if( returnValueLuaBase != null && returnValueLuaBase.IsOrphaned ) + { + returnValueLuaBase.Dispose(); + } + } + } + } + catch (TargetInvocationException e) + { + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + return SetPendingException(e); + } + } + + // Pushes out and ref return values + for (int index = 0; index < _LastCalledMethod.outList.Length; index++) + { + nReturnValues++; + + object outArg = _LastCalledMethod.args[_LastCalledMethod.outList[index]]; + + _Translator.push(luaState, outArg ); + + LuaTable outArgLuaBase = outArg as LuaTable; + if( outArgLuaBase != null && outArgLuaBase.IsOrphaned ) + { + outArgLuaBase.Dispose(); + } + } + + //by isSingle 2010-09-10 11:26:31 + //Desc: + // if not return void,we need add 1, + // or we will lost the function's return value + // when call dotnet function like "int foo(arg1,out arg2,out arg3)" in lua code + if (!_LastCalledMethod.IsReturnVoid && nReturnValues > 0) + { + nReturnValues++; + } + + return nReturnValues < 1 ? 1 : nReturnValues; + } + } + + + + + /// + /// We keep track of what delegates we have auto attached to an event - to allow us to cleanly exit a LuaInterface session + /// + class EventHandlerContainer : IDisposable + { + Dictionary dict = new Dictionary(); + + public void Add(Delegate handler, RegisterEventHandler eventInfo) + { + dict.Add(handler, eventInfo); + } + + public void Remove(Delegate handler) + { + bool found = dict.Remove(handler); + Debug.Assert(found); + } + + /// + /// Remove any still registered handlers + /// + public void Dispose() + { + foreach (KeyValuePair pair in dict) + { + pair.Value.RemovePending(pair.Key); + } + + dict.Clear(); + } + } + + + /* + * Wrapper class for events that does registration/deregistration + * of event handlers. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class RegisterEventHandler + { + object target; + EventInfo eventInfo; + EventHandlerContainer pendingEvents; + + public RegisterEventHandler(EventHandlerContainer pendingEvents, object target, EventInfo eventInfo) + { + this.target = target; + this.eventInfo = eventInfo; + this.pendingEvents = pendingEvents; + } + + + /* + * Adds a new event handler + */ + public Delegate Add(LuaFunction function) + { + //CP: Fix by Ben Bryant for event handling with one parameter + //link: http://luaforge.net/forum/message.php?msg_id=9266 + Delegate handlerDelegate = CodeGeneration.Instance.GetDelegate(eventInfo.EventHandlerType, function); + eventInfo.AddEventHandler(target, handlerDelegate); + pendingEvents.Add(handlerDelegate, this); + + return handlerDelegate; + + + //MethodInfo mi = eventInfo.EventHandlerType.GetMethod("Invoke"); + //ParameterInfo[] pi = mi.GetParameters(); + //LuaEventHandler handler=CodeGeneration.Instance.GetEvent(pi[1].ParameterType,function); + + //Delegate handlerDelegate=Delegate.CreateDelegate(eventInfo.EventHandlerType,handler,"HandleEvent"); + //eventInfo.AddEventHandler(target,handlerDelegate); + //pendingEvents.Add(handlerDelegate, this); + + //return handlerDelegate; + } + + /* + * Removes an existing event handler + */ + public void Remove(Delegate handlerDelegate) + { + RemovePending(handlerDelegate); + pendingEvents.Remove(handlerDelegate); + } + + /* + * Removes an existing event handler (without updating the pending handlers list) + */ + internal void RemovePending(Delegate handlerDelegate) + { + eventInfo.RemoveEventHandler(target, handlerDelegate); + } + } + + /* + * Base wrapper class for Lua function event handlers. + * Subclasses that do actual event handling are created + * at runtime. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaEventHandler + { + public LuaFunction handler = null; + + // CP: Fix provided by Ben Bryant for delegates with one param + // link: http://luaforge.net/forum/message.php?msg_id=9318 + public void handleEvent(object[] args) + { + handler.Call(args); + } + //public void handleEvent(object sender,object data) + //{ + // handler.call(new object[] { sender,data },new Type[0]); + //} + } + + /* + * Wrapper class for Lua functions as delegates + * Subclasses with correct signatures are created + * at runtime. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaDelegate + { + public Type[] returnTypes; + public LuaFunction function; + public LuaDelegate() + { + function = null; + returnTypes = null; + } + public object callFunction(object[] args, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.call(inArgs, returnTypes); + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + // Sets the value of out and ref parameters (from + // the values returned by the Lua function). + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + return returnValue; + } + } + + /* + * Static helper methods for Lua tables acting as CLR objects. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaClassHelper + { + /* + * Gets the function called name from the provided table, + * returning null if it does not exist + */ + public static LuaFunction getTableFunction(LuaTable luaTable, string name) + { + object funcObj = luaTable.rawget(name); + if (funcObj is LuaFunction) + return (LuaFunction)funcObj; + else + return null; + } + /* + * Calls the provided function with the provided parameters + */ + public static object callFunction(LuaFunction function, object[] args, Type[] returnTypes, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.call(inArgs, returnTypes); + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + return returnValue; + } + } +} diff --git a/LuaInterface/ObjectTranslator.cs b/LuaInterface/ObjectTranslator.cs new file mode 100644 index 0000000000..a28c73e236 --- /dev/null +++ b/LuaInterface/ObjectTranslator.cs @@ -0,0 +1,870 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Collections.Generic; + using System.Diagnostics; + + /* + * Passes objects from the CLR to Lua and vice-versa + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class ObjectTranslator + { + internal CheckType typeChecker; + + // object # to object (FIXME - it should be possible to get object address as an object #) + public readonly Dictionary objects = new Dictionary(); + // object to object # + public readonly Dictionary objectsBackMap = new Dictionary(); + internal Lua interpreter; + private MetaFunctions metaFunctions; + private List assemblies; + private LuaCSFunction getMethodSigFunction, getConstructorSigFunction, ctypeFunction, enumFromIntFunction; + + internal EventHandlerContainer pendingEvents = new EventHandlerContainer(); + + public ObjectTranslator(Lua interpreter,IntPtr luaState) + { + this.interpreter = interpreter; + typeChecker = new CheckType(this); + metaFunctions = new MetaFunctions(this); + assemblies = new List(); + + getMethodSigFunction= getMethodSignature; + getConstructorSigFunction= getConstructorSignature; + + ctypeFunction = ctype; + enumFromIntFunction = enumFromInt; + + createLuaObjectList(luaState); + createIndexingMetaFunction(luaState); + createBaseClassMetatable(luaState); + createClassMetatable(luaState); + createFunctionMetatable(luaState); + setGlobalFunctions(luaState); + } + + /* + * Sets up the list of objects in the Lua side + */ + private void createLuaObjectList(IntPtr luaState) + { + LuaDLL.lua_pushstring(luaState,"luaNet_objects"); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_pushstring(luaState,"__mode"); + LuaDLL.lua_pushstring(luaState,"v"); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_setmetatable(luaState,-2); + LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + } + /* + * Registers the indexing function of CLR objects + * passed to Lua + */ + private void createIndexingMetaFunction(IntPtr luaState) + { + LuaDLL.lua_pushstring(luaState,"luaNet_indexfunction"); + LuaDLL.luaL_dostring(luaState,MetaFunctions.luaIndexFunction); // steffenj: lua_dostring renamed to luaL_dostring + //LuaDLL.lua_pushstdcallcfunction(luaState,indexFunction); + LuaDLL.lua_rawset(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + } + /* + * Creates the metatable for superclasses (the base + * field of registered tables) + */ + private void createBaseClassMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_searchbase"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__tostring"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.baseIndexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__newindex"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.newindexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Creates the metatable for type references + */ + private void createClassMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_class"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__tostring"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.classIndexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__newindex"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.classNewindexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__call"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.callConstructorFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Registers the global functions used by LuaInterface + */ + private void setGlobalFunctions(IntPtr luaState) + { + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.indexFunction); + LuaDLL.lua_setglobal(luaState,"get_object_member"); + /*LuaDLL.lua_pushstdcallcfunction(luaState,importTypeFunction); + LuaDLL.lua_setglobal(luaState,"import_type"); + LuaDLL.lua_pushstdcallcfunction(luaState,loadAssemblyFunction); + LuaDLL.lua_setglobal(luaState,"load_assembly"); + LuaDLL.lua_pushstdcallcfunction(luaState,registerTableFunction); + LuaDLL.lua_setglobal(luaState,"make_object"); + LuaDLL.lua_pushstdcallcfunction(luaState,unregisterTableFunction); + LuaDLL.lua_setglobal(luaState,"free_object");*/ + LuaDLL.lua_pushstdcallcfunction(luaState,getMethodSigFunction); + LuaDLL.lua_setglobal(luaState,"get_method_bysig"); + LuaDLL.lua_pushstdcallcfunction(luaState,getConstructorSigFunction); + LuaDLL.lua_setglobal(luaState,"get_constructor_bysig"); + LuaDLL.lua_pushstdcallcfunction(luaState,ctypeFunction); + LuaDLL.lua_setglobal(luaState,"ctype"); + LuaDLL.lua_pushstdcallcfunction(luaState,enumFromIntFunction); + LuaDLL.lua_setglobal(luaState,"enum"); + + } + + /* + * Creates the metatable for delegates + */ + private void createFunctionMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_function"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__call"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.execDelegateFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Passes errors (argument e) to the Lua interpreter + */ + internal void throwError(IntPtr luaState, object e) + { + // We use this to remove anything pushed by luaL_where + int oldTop = LuaDLL.lua_gettop(luaState); + + // Stack frame #1 is our C# wrapper, so not very interesting to the user + // Stack frame #2 must be the lua code that called us, so that's what we want to use + LuaDLL.luaL_where(luaState, 1); + object[] curlev = popValues(luaState, oldTop); + + // Determine the position in the script where the exception was triggered + string errLocation = ""; + if (curlev.Length > 0) + errLocation = curlev[0].ToString(); + + string message = e as string; + if (message != null) + { + // Wrap Lua error (just a string) and store the error location + e = new LuaScriptException(message, errLocation); + } + else + { + Exception ex = e as Exception; + if (ex != null) + { + // Wrap generic .NET exception as an InnerException and store the error location + e = new LuaScriptException(ex, errLocation); + } + } + + push(luaState, e); + LuaDLL.lua_error(luaState); + } + /* + * Implementation of load_assembly. Throws an error + * if the assembly is not found. + */ + private int loadAssembly(IntPtr luaState) + { + try + { + string assemblyName = LuaDLL.lua_tostring(luaState,1); + + Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName)); + + if (assembly != null && !assemblies.Contains(assembly)) + { + assemblies.Add(assembly); + } + } + catch(Exception e) + { + throwError(luaState,e); + } + + return 0; + } + + internal Type FindType(string className) + { + foreach(Assembly assembly in assemblies) + { + Type klass=assembly.GetType(className); + if(klass!=null) + { + return klass; + } + } + return null; + } + + /* + * Implementation of import_type. Returns nil if the + * type is not found. + */ + private int importType(IntPtr luaState) + { + string className=LuaDLL.lua_tostring(luaState,1); + Type klass=FindType(className); + if(klass!=null) + pushType(luaState,klass); + else + LuaDLL.lua_pushnil(luaState); + return 1; + } + /* + * Implementation of make_object. Registers a table (first + * argument in the stack) as an object subclassing the + * type passed as second argument in the stack. + */ + private int registerTable(IntPtr luaState) + { + if(LuaDLL.lua_type(luaState,1)==LuaTypes.LUA_TTABLE) + { + LuaTable luaTable=getTable(luaState,1); + string superclassName = LuaDLL.lua_tostring(luaState, 2); + if (superclassName != null) + { + Type klass = FindType(superclassName); + if (klass != null) + { + // Creates and pushes the object in the stack, setting + // it as the metatable of the first argument + object obj = CodeGeneration.Instance.GetClassInstance(klass, luaTable); + pushObject(luaState, obj, "luaNet_metatable"); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_pushstring(luaState, "__index"); + LuaDLL.lua_pushvalue(luaState, -3); + LuaDLL.lua_settable(luaState, -3); + LuaDLL.lua_pushstring(luaState, "__newindex"); + LuaDLL.lua_pushvalue(luaState, -3); + LuaDLL.lua_settable(luaState, -3); + LuaDLL.lua_setmetatable(luaState, 1); + // Pushes the object again, this time as the base field + // of the table and with the luaNet_searchbase metatable + LuaDLL.lua_pushstring(luaState, "base"); + int index = addObject(obj); + pushNewObject(luaState, obj, index, "luaNet_searchbase"); + LuaDLL.lua_rawset(luaState, 1); + } + else + throwError(luaState, "register_table: can not find superclass '" + superclassName + "'"); + } + else + throwError(luaState, "register_table: superclass name can not be null"); + } + else throwError(luaState,"register_table: first arg is not a table"); + return 0; + } + /* + * Implementation of free_object. Clears the metatable and the + * base field, freeing the created object for garbage-collection + */ + private int unregisterTable(IntPtr luaState) + { + try + { + if(LuaDLL.lua_getmetatable(luaState,1)!=0) + { + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.lua_gettable(luaState,-2); + object obj=getRawNetObject(luaState,-1); + if(obj==null) throwError(luaState,"unregister_table: arg is not valid table"); + FieldInfo luaTableField=obj.GetType().GetField("__luaInterface_luaTable"); + if(luaTableField==null) throwError(luaState,"unregister_table: arg is not valid table"); + luaTableField.SetValue(obj,null); + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_setmetatable(luaState,1); + LuaDLL.lua_pushstring(luaState,"base"); + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_settable(luaState,1); + } + else throwError(luaState,"unregister_table: arg is not valid table"); + } + catch(Exception e) + { + throwError(luaState,e.Message); + } + return 0; + } + /* + * Implementation of get_method_bysig. Returns nil + * if no matching method is not found. + */ + private int getMethodSignature(IntPtr luaState) + { + IReflect klass; object target; + int udata=LuaDLL.luanet_checkudata(luaState,1,"luaNet_class"); + if(udata!=-1) + { + klass=(IReflect)objects[udata]; + target=null; + } + else + { + target=getRawNetObject(luaState,1); + if(target==null) + { + throwError(luaState,"get_method_bysig: first arg is not type or object reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + klass=target.GetType(); + } + string methodName=LuaDLL.lua_tostring(luaState,2); + Type[] signature=new Type[LuaDLL.lua_gettop(luaState)-2]; + for(int i=0;i + /// Given the Lua int ID for an object remove it from our maps + /// + /// + internal void collectObject(int udata) + { + object o; + bool found = objects.TryGetValue(udata, out o); + + // The other variant of collectObject might have gotten here first, in that case we will silently ignore the missing entry + if (found) + { + // Debug.WriteLine("Removing " + o.ToString() + " @ " + udata); + + objects.Remove(udata); + objectsBackMap.Remove(o); + } + } + + + /// + /// Given an object reference, remove it from our maps + /// + /// + void collectObject(object o, int udata) + { + // Debug.WriteLine("Removing " + o.ToString() + " @ " + udata); + + objects.Remove(udata); + objectsBackMap.Remove(o); + } + + + /// + /// We want to ensure that objects always have a unique ID + /// + int nextObj = 0; + + int addObject(object obj) + { + // New object: inserts it in the list + int index = nextObj++; + + // Debug.WriteLine("Adding " + obj.ToString() + " @ " + index); + + objects[index] = obj; + objectsBackMap[obj] = index; + + return index; + } + + + + /* + * Gets an object from the Lua stack according to its Lua type. + */ + internal object getObject(IntPtr luaState,int index) + { + LuaTypes type=LuaDLL.lua_type(luaState,index); + switch(type) + { + case LuaTypes.LUA_TNUMBER: + { + return LuaDLL.lua_tonumber(luaState,index); + } + case LuaTypes.LUA_TSTRING: + { + return LuaDLL.lua_tostring(luaState,index); + } + case LuaTypes.LUA_TBOOLEAN: + { + return LuaDLL.lua_toboolean(luaState,index); + } + case LuaTypes.LUA_TTABLE: + { + return getTable(luaState,index); + } + case LuaTypes.LUA_TFUNCTION: + { + return getFunction(luaState,index); + } + case LuaTypes.LUA_TUSERDATA: + { + int udata=LuaDLL.luanet_tonetobject(luaState,index); + if(udata!=-1) + return objects[udata]; + else + return getUserData(luaState,index); + } + default: + return null; + } + } + /* + * Gets the table in the index positon of the Lua stack. + */ + internal LuaTable getTable(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaTable(LuaDLL.lua_ref(luaState,1),interpreter); + } + /* + * Gets the userdata in the index positon of the Lua stack. + */ + internal LuaUserData getUserData(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaUserData(LuaDLL.lua_ref(luaState,1),interpreter); + } + /* + * Gets the function in the index positon of the Lua stack. + */ + internal LuaFunction getFunction(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaFunction(LuaDLL.lua_ref(luaState,1),interpreter); + } + /* + * Gets the CLR object in the index positon of the Lua stack. Returns + * delegates as Lua functions. + */ + internal object getNetObject(IntPtr luaState,int index) + { + int idx=LuaDLL.luanet_tonetobject(luaState,index); + if(idx!=-1) + return objects[idx]; + else + return null; + } + /* + * Gets the CLR object in the index positon of the Lua stack. Returns + * delegates as is. + */ + internal object getRawNetObject(IntPtr luaState,int index) + { + int udata=LuaDLL.luanet_rawnetobj(luaState,index); + if(udata!=-1) + { + return objects[udata]; + } + return null; + } + /* + * Pushes the entire array into the Lua stack and returns the number + * of elements pushed. + */ + internal int returnValues(IntPtr luaState, object[] returnValues) + { + if(LuaDLL.lua_checkstack(luaState,returnValues.Length+5)) + { + for(int i=0;i + /// Summary description for ProxyType. + /// + public class ProxyType : IReflect + { + + Type proxy; + + public ProxyType(Type proxy) + { + this.proxy = proxy; + } + + /// + /// Provide human readable short hand for this proxy object + /// + /// + public override string ToString() + { + return "ProxyType(" + UnderlyingSystemType + ")"; + } + + + public Type UnderlyingSystemType + { + get + { + return proxy; + } + } + + public FieldInfo GetField(string name, BindingFlags bindingAttr) + { + return proxy.GetField(name, bindingAttr); + } + + public FieldInfo[] GetFields(BindingFlags bindingAttr) + { + return proxy.GetFields(bindingAttr); + } + + public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) + { + return proxy.GetMember(name, bindingAttr); + } + + public MemberInfo[] GetMembers(BindingFlags bindingAttr) + { + return proxy.GetMembers(bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr) + { + return proxy.GetMethod(name, bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) + { + return proxy.GetMethod(name, bindingAttr, binder, types, modifiers); + } + + public MethodInfo[] GetMethods(BindingFlags bindingAttr) + { + return proxy.GetMethods(bindingAttr); + } + + public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) + { + return proxy.GetProperty(name, bindingAttr); + } + + public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + { + return proxy.GetProperty(name, bindingAttr, binder, returnType, types, modifiers); + } + + public PropertyInfo[] GetProperties(BindingFlags bindingAttr) + { + return proxy.GetProperties(bindingAttr); + } + + public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + return proxy.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); + } + + } +} diff --git a/Makefile b/Makefile index 60685d8690..eb5c4a81fa 100644 --- a/Makefile +++ b/Makefile @@ -48,8 +48,10 @@ prefix ?= /usr/local datarootdir ?= $(prefix)/share datadir ?= $(datarootdir) bindir ?= $(prefix)/bin +libexecdir ?= $(prefix)/lib BIN_INSTALL_DIR = $(DESTDIR)$(bindir) -DATA_INSTALL_DIR = $(DESTDIR)$(datadir)/openra +# TODO: separate data and binaries properly +DATA_INSTALL_DIR = $(DESTDIR)$(libexecdir)/openra # install tools RM = rm @@ -63,7 +65,7 @@ INSTALL_PROGRAM = $(INSTALL) -m755 INSTALL_DATA = $(INSTALL) -m644 # program targets -CORE = fileformats rcg rgl rsdl rnull game utility geoip irc +CORE = fileformats rcg rgl rsdl rnull game utility geoip irc lua TOOLS = editor tsbuild ralint VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`) @@ -77,7 +79,7 @@ fileformats_SRCS := $(shell find OpenRA.FileFormats/ -iname '*.cs') fileformats_TARGET = OpenRA.FileFormats.dll fileformats_KIND = library fileformats_LIBS = $(COMMON_LIBS) thirdparty/Tao/Tao.Sdl.dll System.Windows.Forms.dll -PROGRAMS = fileformats +PROGRAMS = fileformats fileformats: $(fileformats_TARGET) geoip_SRCS := $(shell find GeoIP/ -iname '*.cs') @@ -105,6 +107,14 @@ irc_LIBS = $(COMMON_LIBS) $(irc_DEPS) PROGRAMS += irc irc: $(irc_TARGET) +lua_SRCS := $(shell find LuaInterface/ -name '*.cs') +lua_TARGET = LuaInterface.dll +lua_KIND = library +lua_DEPS = $(fileformats_TARGET) +lua_LIBS = $(COMMON_LIBS) $(lua_DEPS) +PROGRAMS += lua +lua: $(lua_TARGET) + # Renderer dlls rsdl_SRCS := $(shell find OpenRA.Renderer.SdlCommon/ -iname '*.cs') rsdl_TARGET = OpenRA.Renderer.SdlCommon.dll @@ -126,13 +136,13 @@ rgl_DEPS = $(fileformats_TARGET) $(game_TARGET) $(rsdl_TARGET) rgl_LIBS = $(COMMON_LIBS) thirdparty/Tao/Tao.OpenGl.dll $(rgl_DEPS) rsdl2_SRCS := $(shell find OpenRA.Renderer.Sdl2/ -iname '*.cs') -rsdl2_TARGET = OpenRA.Renderer.Sdl2.dll +rsdl2_TARGET = OpenRA.Renderer.Sdl2.dll rsdl2_KIND = library rsdl2_DEPS = $(fileformats_TARGET) $(game_TARGET) $(rsdl_TARGET) $(rgl_TARGET) rsdl2_LIBS = $(COMMON_LIBS) thirdparty/Tao/Tao.OpenGl.dll thirdparty/SDL2\#.dll $(rsdl2_DEPS) rnull_SRCS := $(shell find OpenRA.Renderer.Null/ -iname '*.cs') -rnull_TARGET = OpenRA.Renderer.Null.dll +rnull_TARGET = OpenRA.Renderer.Null.dll rnull_KIND = library rnull_DEPS = $(fileformats_TARGET) $(game_TARGET) rnull_LIBS = $(COMMON_LIBS) $(rnull_DEPS) @@ -149,8 +159,8 @@ STD_MOD_DEPS = $(STD_MOD_LIBS) $(ralint_TARGET) mod_ra_SRCS := $(shell find OpenRA.Mods.RA/ -iname '*.cs') mod_ra_TARGET = mods/ra/OpenRA.Mods.RA.dll mod_ra_KIND = library -mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) -mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) +mod_ra_DEPS = $(STD_MOD_DEPS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET) +mod_ra_LIBS = $(COMMON_LIBS) $(STD_MOD_LIBS) $(utility_TARGET) $(geoip_TARGET) $(irc_TARGET) $(lua_TARGET) PROGRAMS += mod_ra mod_ra: $(mod_ra_TARGET) @@ -189,7 +199,7 @@ editor_TARGET = OpenRA.Editor.exe editor_KIND = winexe editor_DEPS = $(fileformats_TARGET) $(game_TARGET) editor_LIBS = $(COMMON_LIBS) System.Windows.Forms.dll System.Data.dll $(editor_DEPS) -editor_EXTRA = -resource:OpenRA.Editor.Form1.resources -resource:OpenRA.Editor.MapSelect.resources +editor_EXTRA = -resource:OpenRA.Editor.Form1.resources -resource:OpenRA.Editor.MapSelect.resources editor_FLAGS = -win32icon:OpenRA.Editor/OpenRA.Editor.Icon.ico PROGRAMS += editor @@ -225,7 +235,7 @@ tsbuild_KIND = winexe tsbuild_DEPS = $(fileformats_TARGET) $(game_TARGET) tsbuild_LIBS = $(COMMON_LIBS) $(tsbuild_DEPS) System.Windows.Forms.dll tsbuild_EXTRA = -resource:OpenRA.TilesetBuilder.FormBuilder.resources -resource:OpenRA.TilesetBuilder.FormNew.resources -resource:OpenRA.TilesetBuilder.Surface.resources -PROGRAMS += tsbuild +PROGRAMS += tsbuild OpenRA.TilesetBuilder.FormBuilder.resources: resgen2 OpenRA.TilesetBuilder/FormBuilder.resx OpenRA.TilesetBuilder.FormBuilder.resources 1> /dev/null OpenRA.TilesetBuilder.FormNew.resources: @@ -243,7 +253,7 @@ utility_TARGET = OpenRA.Utility.exe utility_KIND = exe utility_DEPS = $(fileformats_TARGET) $(game_TARGET) utility_LIBS = $(COMMON_LIBS) $(utility_DEPS) thirdparty/ICSharpCode.SharpZipLib.dll System.Windows.Forms.dll -PROGRAMS += utility +PROGRAMS += utility utility: $(utility_TARGET) @@ -293,6 +303,8 @@ distclean: clean dependencies: @ $(CP_R) thirdparty/*.dl* . + @ $(CP_R) thirdparty/*.dylib . + @ $(CP_R) thirdparty/*.so . @ $(CP_R) thirdparty/Tao/* . version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml @@ -336,12 +348,16 @@ install-core: default @$(INSTALL_PROGRAM) thirdparty/SharpFont.dll "$(DATA_INSTALL_DIR)" @$(CP) thirdparty/SharpFont.dll.config "$(DATA_INSTALL_DIR)" @$(INSTALL_PROGRAM) thirdparty/Mono.Nat.dll "$(DATA_INSTALL_DIR)" + @$(CP) thirdparty/LuaInterface.dll.config "$(DATA_INSTALL_DIR)" + @$(INSTALL_PROGRAM) thirdparty/liblua-linux32.so "$(DATA_INSTALL_DIR)" + @$(INSTALL_PROGRAM) thirdparty/liblua-linux64.so "$(DATA_INSTALL_DIR)" + @$(INSTALL_PROGRAM) thirdparty/liblua-osx.dylib "$(DATA_INSTALL_DIR)" @echo "#!/bin/sh" > openra @echo 'BINDIR=$$(dirname $$(readlink -f $$0))' >> openra @echo 'ROOTDIR="$${BINDIR%'"$(bindir)"'}"' >> openra - @echo 'DATADIR="$${ROOTDIR}'"$(datadir)"'"' >> openra - @echo 'cd "$${DATADIR}/openra"' >> openra + @echo 'EXECDIR="$${ROOTDIR}'"$(libexecdir)"'"' >> openra + @echo 'cd "$${EXECDIR}/openra"' >> openra @echo 'exec mono OpenRA.Game.exe "$$@"' >> openra @$(INSTALL_DIR) "$(BIN_INSTALL_DIR)" @$(INSTALL_PROGRAM) -m +rx openra "$(BIN_INSTALL_DIR)" @@ -355,8 +371,8 @@ install-tools: tools @echo "#!/bin/sh" > openra-editor @echo 'BINDIR=$$(dirname $$(readlink -f $$0))' >> openra-editor @echo 'ROOTDIR="$${BINDIR%'"$(bindir)"'}"' >> openra-editor - @echo 'DATADIR="$${ROOTDIR}/'"$(datadir)"'"' >> openra-editor - @echo 'cd "$${DATADIR}/openra"' >> openra-editor + @echo 'EXECDIR="$${ROOTDIR}'"$(libexecdir)"'"' >> openra-editor + @echo 'cd "$${EXECDIR}/openra"' >> openra-editor @echo 'exec mono OpenRA.Editor.exe "$$@"' >> openra-editor @$(INSTALL_DIR) "$(BIN_INSTALL_DIR)" @$(INSTALL_PROGRAM) -m +rx openra-editor "$(BIN_INSTALL_DIR)" diff --git a/OpenRA.FileFormats/Manifest.cs b/OpenRA.FileFormats/Manifest.cs index fbc8c0c9d1..22a6fdcf90 100644 --- a/OpenRA.FileFormats/Manifest.cs +++ b/OpenRA.FileFormats/Manifest.cs @@ -22,7 +22,7 @@ namespace OpenRA.FileFormats Folders, MapFolders, Rules, ServerTraits, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, - ChromeMetrics, PackageContents; + ChromeMetrics, PackageContents, LuaScripts; public readonly Dictionary Packages; public readonly MiniYaml LoadScreen; @@ -59,6 +59,7 @@ namespace OpenRA.FileFormats TileSets = YamlList(yaml, "TileSets"); ChromeMetrics = YamlList(yaml, "ChromeMetrics"); PackageContents = YamlList(yaml, "PackageContents"); + LuaScripts = YamlList(yaml, "LuaScripts"); LoadScreen = yaml["LoadScreen"]; LobbyDefaults = yaml["LobbyDefaults"]; diff --git a/OpenRA.Game/Map.cs b/OpenRA.Game/Map.cs index fd28aec8c0..8399e50a34 100644 --- a/OpenRA.Game/Map.cs +++ b/OpenRA.Game/Map.cs @@ -54,7 +54,7 @@ namespace OpenRA public class Map { - [FieldLoader.Ignore] IFolder container; + [FieldLoader.Ignore] public IFolder Container; public string Path { get; private set; } // Yaml map data @@ -132,7 +132,7 @@ namespace OpenRA void AssertExists(string filename) { - using (var s = container.GetContent(filename)) + using (var s = Container.GetContent(filename)) if (s == null) throw new InvalidOperationException("Required file {0} not present in this map".F(filename)); } @@ -142,12 +142,12 @@ namespace OpenRA public Map(string path) { Path = path; - container = FileSystem.OpenPackage(path, null, int.MaxValue); + Container = FileSystem.OpenPackage(path, null, int.MaxValue); AssertExists("map.yaml"); AssertExists("map.bin"); - var yaml = new MiniYaml(null, MiniYaml.FromStream(container.GetContent("map.yaml"))); + var yaml = new MiniYaml(null, MiniYaml.FromStream(Container.GetContent("map.yaml"))); FieldLoader.Load(this, yaml); Uid = ComputeHash(); @@ -263,17 +263,17 @@ namespace OpenRA // Create a new map package // TODO: Add other files (custom assets) to the entries list - container = FileSystem.CreatePackage(Path, int.MaxValue, entries); + Container = FileSystem.CreatePackage(Path, int.MaxValue, entries); } // Update existing package - container.Write(entries); + Container.Write(entries); } public TileReference[,] LoadMapTiles() { var tiles = new TileReference[MapSize.X, MapSize.Y]; - using (var dataStream = container.GetContent("map.bin")) + using (var dataStream = Container.GetContent("map.bin")) { if (dataStream.ReadUInt8() != 1) throw new InvalidDataException("Unknown binary map format"); @@ -305,7 +305,7 @@ namespace OpenRA { var resources = new TileReference[MapSize.X, MapSize.Y]; - using (var dataStream = container.GetContent("map.bin")) + using (var dataStream = Container.GetContent("map.bin")) { if (dataStream.ReadUInt8() != 1) throw new InvalidDataException("Unknown binary map format"); @@ -391,8 +391,8 @@ namespace OpenRA { // UID is calculated by taking an SHA1 of the yaml and binary data // Read the relevant data into a buffer - var data = container.GetContent("map.yaml").ReadAllBytes() - .Concat(container.GetContent("map.bin").ReadAllBytes()).ToArray(); + var data = Container.GetContent("map.yaml").ReadAllBytes() + .Concat(Container.GetContent("map.bin").ReadAllBytes()).ToArray(); // Take the SHA1 using (var csp = SHA1.Create()) diff --git a/OpenRA.Game/ObjectCreator.cs b/OpenRA.Game/ObjectCreator.cs index e7a54ab96a..cdf01b0a21 100755 --- a/OpenRA.Game/ObjectCreator.cs +++ b/OpenRA.Game/ObjectCreator.cs @@ -19,13 +19,15 @@ namespace OpenRA { public class ObjectCreator { - Pair[] modAssemblies; + Pair[] assemblies; public ObjectCreator(Manifest manifest) { // All the core namespaces - var asms = typeof(Game).Assembly.GetNamespaces() + var asms = typeof(Game).Assembly.GetNamespaces() // Game .Select(c => Pair.New(typeof(Game).Assembly, c)) + .Concat(typeof(Mod).Assembly.GetNamespaces() // FileFormats + .Select(c => Pair.New(typeof(Mod).Assembly, c))) .ToList(); // Namespaces from each mod assembly @@ -35,7 +37,7 @@ namespace OpenRA asms.AddRange(asm.GetNamespaces().Select(ns => Pair.New(asm, ns))); } - modAssemblies = asms.ToArray(); + assemblies = asms.ToArray(); } public static Action MissingTypeAction = @@ -48,24 +50,30 @@ namespace OpenRA public T CreateObject(string className, Dictionary args) { - foreach (var mod in modAssemblies) + var type = FindType(className); + if (type == null) { - var type = mod.First.GetType(mod.Second + "." + className, false); - if (type == null) continue; - var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; - var ctors = type.GetConstructors(flags) - .Where(x => x.HasAttribute()).ToList(); - - if (ctors.Count == 0) - return (T)CreateBasic(type); - else if (ctors.Count == 1) - return (T)CreateUsingArgs(ctors[0], args); - else - throw new InvalidOperationException("ObjectCreator: UseCtor on multiple constructors; invalid."); + MissingTypeAction(className); + return default(T); } - MissingTypeAction(className); - return default(T); + var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; + var ctors = type.GetConstructors(flags) + .Where(x => x.HasAttribute()).ToList(); + + if (ctors.Count == 0) + return (T)CreateBasic(type); + else if (ctors.Count == 1) + return (T)CreateUsingArgs(ctors[0], args); + else + throw new InvalidOperationException("ObjectCreator: UseCtor on multiple constructors; invalid."); + } + + public Type FindType(string className) + { + return assemblies + .Select(pair => pair.First.GetType(pair.Second + "." + className, false)) + .FirstOrDefault(t => t != null); } public object CreateBasic(Type type) @@ -90,7 +98,7 @@ namespace OpenRA public IEnumerable GetTypesImplementing() { var it = typeof(T); - return modAssemblies.Select(ma => ma.First).Distinct() + return assemblies.Select(ma => ma.First).Distinct() .SelectMany(ma => ma.GetTypes() .Where(t => t != it && it.IsAssignableFrom(t))); } diff --git a/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs b/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs index e457896675..8fe61fd133 100644 --- a/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs +++ b/OpenRA.Mods.RA/Missions/DesertShellmapScript.cs @@ -16,7 +16,7 @@ using OpenRA.Mods.RA.Activities; using OpenRA.Mods.RA.Air; using OpenRA.Mods.RA.Buildings; using OpenRA.Mods.RA.Move; -using OpenRA.Scripting; +using OpenRA.Mods.RA.Scripting; using OpenRA.Traits; namespace OpenRA.Mods.RA.Missions diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 50fdbc7c7e..0ae49ce679 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -338,8 +338,10 @@ + + @@ -476,6 +478,10 @@ + + {e915a0a4-2641-4f7e-8a88-8f123fa88bf1} + LuaInterface + {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA} OpenRA.FileFormats diff --git a/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs b/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs new file mode 100644 index 0000000000..97fca80e64 --- /dev/null +++ b/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs @@ -0,0 +1,134 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LuaInterface; + +namespace OpenRA.Mods.RA.Scripting +{ + public class LuaScriptContext : IDisposable + { + public Lua Lua { get; private set; } + + public LuaScriptContext() + { + Log.Write("debug", "Creating Lua script context"); + Lua = new Lua(); + } + + public void RegisterObject(object target, string tableName, bool exposeAllMethods) + { + Log.Write("debug", "Registering object {0}", target); + + if (tableName != null && Lua.GetTable(tableName) == null) + Lua.NewTable(tableName); + + var type = target.GetType(); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); + RegisterMethods(tableName, target, methods, exposeAllMethods); + } + + public void RegisterType(Type type, string tableName, bool exposeAllMethods) + { + Log.Write("debug", "Registering type {0}", type); + + if (tableName != null && Lua.GetTable(tableName) == null) + Lua.NewTable(tableName); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static); + RegisterMethods(tableName, null, methods, exposeAllMethods); + } + + void RegisterMethods(string tableName, object target, IEnumerable methods, bool allMethods) + { + foreach (var method in methods) + { + string methodName; + + var attr = method.GetCustomAttributes(true).FirstOrDefault(); + if (attr == null) + { + if (allMethods) + methodName = method.Name; + else + continue; + } + else + methodName = attr.Name ?? method.Name; + + var methodTarget = method.IsStatic ? null : target; + + if (tableName != null) + Lua.RegisterFunction(tableName + "." + methodName, methodTarget, method); + else + Lua.RegisterFunction(methodName, methodTarget, method); + } + } + + void LogException(Exception e) + { + Game.Debug("{0}", e.Message); + Game.Debug("See debug.log for details"); + Log.Write("debug", "{0}", e); + } + + public void LoadLuaScripts(Func getFileContents, params string[] files) + { + foreach (var file in files) + { + try + { + Log.Write("debug", "Loading Lua script {0}", file); + var content = getFileContents(file); + Lua.DoString(content, file); + } + catch (Exception e) + { + LogException(e); + } + } + } + + public object[] InvokeLuaFunction(string name, params object[] args) + { + try + { + var function = Lua.GetFunction(name); + if (function == null) + return null; + return function.Call(args); + } + catch (Exception e) + { + LogException(e); + return null; + } + } + + public void Dispose() + { + if (Lua == null) + return; + + GC.SuppressFinalize(this); + Lua.Dispose(); + Lua = null; + } + + ~LuaScriptContext() + { + Dispose(); + } + } +} diff --git a/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs new file mode 100644 index 0000000000..43f449bb9d --- /dev/null +++ b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs @@ -0,0 +1,165 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using LuaInterface; +using OpenRA.Effects; +using OpenRA.FileFormats; +using OpenRA.Traits; +using WorldRenderer = OpenRA.Graphics.WorldRenderer; + +namespace OpenRA.Mods.RA.Scripting +{ + public class LuaScriptInterfaceInfo : ITraitInfo, Requires + { + public readonly string[] LuaScripts = { }; + + public object Create(ActorInitializer init) { return new LuaScriptInterface(this); } + } + + public class LuaScriptInterface : IWorldLoaded, ITick + { + World world; + readonly LuaScriptContext context = new LuaScriptContext(); + readonly LuaScriptInterfaceInfo info; + + public LuaScriptInterface(LuaScriptInterfaceInfo info) + { + this.info = info; + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + world = w; + AddMapActorGlobals(); + context.Lua["World"] = w; + context.Lua["WorldRenderer"] = wr; + context.RegisterObject(this, "_OpenRA", false); + context.RegisterType(typeof(WVec), "WVec", true); + context.RegisterType(typeof(WPos), "WPos", true); + context.RegisterType(typeof(CPos), "CPos", true); + context.RegisterType(typeof(WRot), "WRot", true); + context.RegisterType(typeof(WAngle), "WAngle", true); + context.RegisterType(typeof(WRange), "WRange", true); + context.RegisterType(typeof(int2), "int2", true); + context.RegisterType(typeof(float2), "float2", true); + var sharedScripts = Game.modData.Manifest.LuaScripts ?? new string[0]; + if (sharedScripts.Any()) + context.LoadLuaScripts(f => FileSystem.Open(f).ReadAllText(), sharedScripts); + context.LoadLuaScripts(f => w.Map.Container.GetContent(f).ReadAllText(), info.LuaScripts); + context.InvokeLuaFunction("WorldLoaded"); + } + + void AddMapActorGlobals() + { + foreach (var kv in world.WorldActor.Trait().Actors) + context.Lua[kv.Key] = kv.Value; + } + + public void Tick(Actor self) + { + context.InvokeLuaFunction("Tick"); + } + + [LuaGlobal] + public object New(string typeName, LuaTable args) + { + var type = Game.modData.ObjectCreator.FindType(typeName); + if (type == null) + throw new InvalidOperationException("Cannot locate type: {0}".F(typeName)); + if (args == null) + return Activator.CreateInstance(type); + var argsArray = ConvertArgs(args); + return Activator.CreateInstance(type, argsArray); + } + + object[] ConvertArgs(LuaTable args) + { + var argsArray = new object[args.Keys.Count]; + for (var i = 1; i <= args.Keys.Count; i++) + { + var arg = args[i] as LuaTable; + if (arg != null && arg[1] != null && arg[2] != null) + argsArray[i - 1] = Convert.ChangeType(arg[1], Enum.Parse(arg[2].ToString())); + else + argsArray[i - 1] = args[i]; + } + return argsArray; + } + + [LuaGlobal] + public void Debug(object obj) + { + if (obj != null) + Game.Debug(obj.ToString()); + } + + [LuaGlobal] + public object TraitOrDefault(Actor actor, string className) + { + var type = Game.modData.ObjectCreator.FindType(className); + if (type == null) + return null; + + var method = typeof(Actor).GetMethod("TraitOrDefault"); + var genericMethod = method.MakeGenericMethod(type); + return genericMethod.Invoke(actor, null); + } + + [LuaGlobal] + public object Trait(Actor actor, string className) + { + var ret = TraitOrDefault(actor, className); + if (ret == null) + throw new InvalidOperationException("Actor {0} does not have trait of type {1}".F(actor, className)); + return ret; + } + + [LuaGlobal] + public bool HasTrait(Actor actor, string className) + { + var ret = TraitOrDefault(actor, className); + return ret != null; + } + + [LuaGlobal] + public object TraitInfoOrDefault(string actorType, string className) + { + var type = Game.modData.ObjectCreator.FindType(className); + if (type == null || !Rules.Info.ContainsKey(actorType)) + return null; + + return Rules.Info[actorType].Traits.GetOrDefault(type); + } + + [LuaGlobal] + public object TraitInfo(string actorType, string className) + { + var ret = TraitInfoOrDefault(actorType, className); + if (ret == null) + throw new InvalidOperationException("Actor type {0} does not have trait info of type {1}".F(actorType, className)); + return ret; + } + + [LuaGlobal] + public bool HasTraitInfo(string actorType, string className) + { + var ret = TraitInfoOrDefault(actorType, className); + return ret != null; + } + + [LuaGlobal] + public void RunAfterDelay(double delay, Action func) + { + world.AddFrameEndTask(w => w.Add(new DelayedAction((int)delay, func))); + } + } +} diff --git a/OpenRA.Mods.RA/Scripting/RASpecialPowers.cs b/OpenRA.Mods.RA/Scripting/RASpecialPowers.cs index 304e7f8176..bd044cf7d8 100644 --- a/OpenRA.Mods.RA/Scripting/RASpecialPowers.cs +++ b/OpenRA.Mods.RA/Scripting/RASpecialPowers.cs @@ -10,9 +10,8 @@ using System.Collections.Generic; using OpenRA.FileFormats; -using OpenRA.Mods.RA; -namespace OpenRA.Scripting +namespace OpenRA.Mods.RA.Scripting { public class RASpecialPowers { diff --git a/OpenRA.sln b/OpenRA.sln index 2e9cbc466c..b343c4d9ec 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Irc", "OpenRA.Irc\Op EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Renderer.Sdl2", "OpenRA.Renderer.Sdl2\OpenRA.Renderer.Sdl2.csproj", "{33D03738-C154-4028-8EA8-63A3C488A651}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaInterface", "LuaInterface\LuaInterface.csproj", "{E915A0A4-2641-4F7E-8A88-8F123FA88BF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -184,6 +186,14 @@ Global {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.Build.0 = Release|Any CPU {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Any CPU.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Mixed Platforms.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = OpenRA.Game\OpenRA.Game.csproj diff --git a/packaging/linux/buildpackage.sh b/packaging/linux/buildpackage.sh index d7d5827940..3aa9fe6afe 100644 --- a/packaging/linux/buildpackage.sh +++ b/packaging/linux/buildpackage.sh @@ -20,11 +20,14 @@ cd ../.. make install-all prefix="/usr" DESTDIR="$PWD/packaging/linux/$ROOTDIR" # Launch scripts (executed by Desura) -cp *.sh "$PWD/packaging/linux/$ROOTDIR/usr/share/openra/" || exit 3 +cp *.sh "$PWD/packaging/linux/$ROOTDIR/usr/lib/openra/" || exit 3 # Icons and .desktop files make install-shortcuts prefix="/usr" DESTDIR="$PWD/packaging/linux/$ROOTDIR" +# Remove Mac OS X libaries +rm -rf "$PWD/packaging/linux/$ROOTDIR/usr/lib/openra/liblua-osx.dylib" || exit 3 + cd packaging/linux ( diff --git a/packaging/linux/rpm/buildpackage.sh b/packaging/linux/rpm/buildpackage.sh index b640d3fdde..b5e88ee430 100755 --- a/packaging/linux/rpm/buildpackage.sh +++ b/packaging/linux/rpm/buildpackage.sh @@ -13,7 +13,7 @@ sed -i "s/{VERSION_FIELD}/$PKGVERSION/" openra.spec rootdir=`readlink -f $2` sed -i "s|{ROOT_DIR}|$rootdir|" openra.spec -for x in `find $rootdir/usr/share/openra -type d` +for x in `find $rootdir/usr/lib/openra -type d` do y="${x#$rootdir}" sed -i "/%files/ a ${y}" openra.spec diff --git a/packaging/linux/rpm/openra.spec b/packaging/linux/rpm/openra.spec index 4d5bcfcef3..bb6083c567 100644 --- a/packaging/linux/rpm/openra.spec +++ b/packaging/linux/rpm/openra.spec @@ -1,6 +1,10 @@ %define name openra %define version {VERSION_FIELD} %define root {ROOT_DIR} +%define _binaries_in_noarch_packages_terminate_build 0 +%define _use_internal_dependency_generator 0 +%define __find_provides "" +%define __find_requires "" Name: %{name} Version: %{version} Release: 1 diff --git a/packaging/osx/buildpackage.sh b/packaging/osx/buildpackage.sh index ded6a6f695..c511a3d8fe 100755 --- a/packaging/osx/buildpackage.sh +++ b/packaging/osx/buildpackage.sh @@ -21,6 +21,10 @@ cp -rv $2/* "OpenRA.app/Contents/Resources/" || exit 3 rm OpenRA.app/Contents/Resources/OpenRA.ico rm OpenRA.app/Contents/Resources/OpenRA.Editor.exe +# Install the stripped down Lua library +cp ../../liblua-osx.dylib OpenRA.app/Contents/Resources/ +cp ../../LuaInterface.dll.config OpenRA.app/Contents/Resources/ + # SDL2 is the only supported renderer rm -rf OpenRA.app/Contents/Resources/cg rm OpenRA.app/Contents/Resources/OpenRA.Renderer.Cg.dll diff --git a/packaging/package-all.sh b/packaging/package-all.sh index cd6df0f1fe..20d6d8ffeb 100755 --- a/packaging/package-all.sh +++ b/packaging/package-all.sh @@ -26,15 +26,13 @@ markdown CONTRIBUTING.md > CONTRIBUTING.html markdown DOCUMENTATION.md > DOCUMENTATION.html # List of files that are packaged on all platforms -# Note that the Tao dlls are shipped on all platforms except osx and that -# they are now installed to the game directory instead of placed in the gac FILES=('OpenRA.Game.exe' 'OpenRA.Editor.exe' 'OpenRA.Utility.exe' \ 'OpenRA.FileFormats.dll' 'OpenRA.Renderer.SdlCommon.dll' 'OpenRA.Renderer.Sdl2.dll' 'OpenRA.Renderer.Cg.dll' 'OpenRA.Renderer.Gl.dll' 'OpenRA.Renderer.Null.dll' 'OpenRA.Irc.dll' \ 'FreeSans.ttf' 'FreeSansBold.ttf' \ 'cg' 'glsl' 'mods/ra' 'mods/cnc' 'mods/d2k' \ 'AUTHORS' 'CHANGELOG' 'COPYING' \ 'README.html' 'CONTRIBUTING.html' 'DOCUMENTATION.html' \ -'global mix database.dat' 'GeoIP.dll' 'GeoIP.dat') +'global mix database.dat' 'GeoIP.dll' 'GeoIP.dat' 'LuaInterface.dll') echo "Copying files..." for i in "${FILES[@]}"; do diff --git a/packaging/windows/OpenRA.nsi b/packaging/windows/OpenRA.nsi index b87371864f..2c5d64629a 100644 --- a/packaging/windows/OpenRA.nsi +++ b/packaging/windows/OpenRA.nsi @@ -83,6 +83,8 @@ Section "Game" GAME File "${SRCDIR}\global mix database.dat" File "${SRCDIR}\GeoIP.dll" File "${SRCDIR}\GeoIP.dat" + File "${SRCDIR}\LuaInterface.dll" + File lua51.dll File OpenAL32.dll File SDL.dll File freetype6.dll @@ -201,6 +203,8 @@ Function ${UN}Clean Delete "$INSTDIR\global mix database.dat" Delete $INSTDIR\GeoIP.dat Delete $INSTDIR\GeoIP.dll + Delete $INSTDIR\LuaInterface.dll + Delete $INSTDIR\lua51.dll Delete $INSTDIR\OpenAL32.dll Delete $INSTDIR\SDL.dll Delete $INSTDIR\freetype6.dll diff --git a/packaging/windows/lua51.dll b/packaging/windows/lua51.dll new file mode 100644 index 0000000000..07f311f703 Binary files /dev/null and b/packaging/windows/lua51.dll differ diff --git a/thirdparty/LuaInterface.dll.config b/thirdparty/LuaInterface.dll.config new file mode 100644 index 0000000000..97931b2a89 --- /dev/null +++ b/thirdparty/LuaInterface.dll.config @@ -0,0 +1,5 @@ + + + + + diff --git a/thirdparty/liblua-linux32.so b/thirdparty/liblua-linux32.so new file mode 100644 index 0000000000..fec8643995 Binary files /dev/null and b/thirdparty/liblua-linux32.so differ diff --git a/thirdparty/liblua-linux64.so b/thirdparty/liblua-linux64.so new file mode 100644 index 0000000000..dfe43c6115 Binary files /dev/null and b/thirdparty/liblua-linux64.so differ diff --git a/thirdparty/liblua-osx.dylib b/thirdparty/liblua-osx.dylib new file mode 100644 index 0000000000..01731e5f56 Binary files /dev/null and b/thirdparty/liblua-osx.dylib differ