diff --git a/luad/conversions/classes.d b/luad/conversions/classes.d index ffd5fde..745643b 100644 --- a/luad/conversions/classes.d +++ b/luad/conversions/classes.d @@ -17,50 +17,32 @@ import core.memory; import std.traits; import std.typetuple; -extern(C) private int classCleaner(lua_State* L) -{ - GC.removeRoot(lua_touserdata(L, 1)); - return 0; -} - -private extern(C) int indexClass(lua_State* L) -{ - auto field = lua_tostring(L, 2); - - // check the getter table - lua_getfield(L, lua_upvalueindex(1), field); - if(!lua_isnil(L, -1)) - { - lua_pushvalue(L, 1); - lua_call(L, 1, LUA_MULTRET); - return lua_gettop(L) - 2; - } - else - lua_pop(L, 1); - - // return method - lua_getfield(L, lua_upvalueindex(2), field); - return 1; -} -private extern(C) int newIndexClass(lua_State* L) +void pushGetter(T, string member)(lua_State* L) { - auto field = lua_tostring(L, 2); - - // call setter - lua_getfield(L, lua_upvalueindex(1), field); - if(!lua_isnil(L, -1)) + alias RT = typeof(mixin("T."~member)); + final class C { - lua_pushvalue(L, 1); - lua_pushvalue(L, 3); - lua_call(L, 2, LUA_MULTRET); - } - else - { - // TODO: error? + static if((!isMemberFunction!(T, member) || returnsRef!(AliasMember!(T, member))) && isUserStruct!RT) + { + ref RT get() + { + T _this = *cast(T*)&this; + return mixin("_this."~member); + } + } + else + { + RT get() + { + T _this = *cast(T*)&this; + return mixin("_this."~member); + } + } } - return 0; + lua_pushlightuserdata(L, (&C.init.get).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&C.init.get), T, false), 1); } private void pushGetters(T)(lua_State* L) @@ -71,9 +53,9 @@ private void pushGetters(T)(lua_State* L) // populate getters foreach(member; __traits(derivedMembers, T)) { - static if(!skipField!(T, member) && - !isStaticMember!(T, member) && - member != "Monitor") + static if(!skipMember!(T, member) && + !isStaticMember!(T, member) && + member != "Monitor") { static if(isMemberFunction!(T, member) && !isProperty!(T, member)) { @@ -88,7 +70,36 @@ private void pushGetters(T)(lua_State* L) } } - lua_pushcclosure(L, &indexClass, 2); + lua_pushcclosure(L, &index, 2); +} + +void pushSetter(T, string member)(lua_State* L) +{ + // TODO: This is broken if setter argument is different from the getter's return type... + alias ArgType = typeof(mixin("T."~member)); + + final class C + { + static if(isUserStruct!ArgType) + { + final void set(ref ArgType value) + { + T _this = *cast(T*)&this; + mixin("_this."~member) = value; + } + } + else + { + final void set(ArgType value) + { + T _this = *cast(T*)&this; + mixin("_this."~member) = value; + } + } + } + + lua_pushlightuserdata(L, (&C.init.set).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&C.init.set), T, false), 1); } private void pushSetters(T)(lua_State* L) @@ -98,10 +109,10 @@ private void pushSetters(T)(lua_State* L) // populate setters foreach(member; __traits(derivedMembers, T)) { - static if(!skipField!(T, member) && - !isStaticMember!(T, member) && - canWrite!(T, member) && - member != "Monitor") + static if(!skipMember!(T, member) && + !isStaticMember!(T, member) && + canWrite!(T, member) && // TODO: move into the setter for readonly fields + member != "Monitor") { static if(!isMemberFunction!(T, member) || isProperty!(T, member)) { @@ -111,7 +122,7 @@ private void pushSetters(T)(lua_State* L) } } - lua_pushcclosure(L, &newIndexClass, 1); + lua_pushcclosure(L, &newIndex, 1); } private void pushMeta(T)(lua_State* L) @@ -120,11 +131,13 @@ private void pushMeta(T)(lua_State* L) return; pushValue(L, T.stringof); - lua_setfield(L, -2, "__dclass"); + lua_setfield(L, -2, "__dtype"); + + // TODO: mangled names can get REALLY long in D, it might be nicer to store a hash instead? pushValue(L, T.mangleof); lua_setfield(L, -2, "__dmangle"); - lua_pushcfunction(L, &classCleaner); + lua_pushcfunction(L, &userdataCleaner); lua_setfield(L, -2, "__gc"); pushGetters!T(L); @@ -157,28 +170,10 @@ void pushClassInstance(T)(lua_State* L, T obj) if (is(T == class)) lua_setmetatable(L, -2); } -//TODO: handle foreign userdata properly (i.e. raise errors) T getClassInstance(T)(lua_State* L, int idx) if (is(T == class)) { - if(lua_getmetatable(L, idx) == 0) - { - luaL_error(L, "attempt to get 'userdata: %p' as a D object", lua_topointer(L, idx)); - } - - lua_getfield(L, -1, "__dmangle"); //must be a D object - - static if(!is(T == Object)) //must be the right object - { - size_t manglelen; - auto cmangle = lua_tolstring(L, -1, &manglelen); - if(cmangle[0 .. manglelen] != T.mangleof) - { - lua_getfield(L, -2, "__dclass"); - auto cname = lua_tostring(L, -1); - luaL_error(L, `attempt to get instance %s as type "%s"`, cname, toStringz(T.stringof)); - } - } - lua_pop(L, 2); //metatable and metatable.__dmangle + //TODO: handle foreign userdata properly (i.e. raise errors) + verifyType!T(L, idx); Object obj = *cast(Object*)lua_touserdata(L, idx); return cast(T)obj; diff --git a/luad/conversions/functions.d b/luad/conversions/functions.d index 93cdf83..be5f5c2 100644 --- a/luad/conversions/functions.d +++ b/luad/conversions/functions.d @@ -14,6 +14,9 @@ Typesafe varargs is supported when pushing _functions to Lua, but as of DMD 2.05 */ module luad.conversions.functions; +import luad.conversions.helpers; +import luad.all; + import core.memory; import std.range; import std.string : toStringz; @@ -35,79 +38,83 @@ private void argsError(lua_State* L, int nargs, ptrdiff_t expected) template StripHeadQual(T : const(T*)) { - alias const(T)* StripHeadQual; + alias StripHeadQual = const(T)*; } template StripHeadQual(T : const(T[])) { - alias const(T)[] StripHeadQual; + alias StripHeadQual = const(T)[]; } template StripHeadQual(T : immutable(T*)) { - alias immutable(T)* StripHeadQual; + alias StripHeadQual = immutable(T)*; } template StripHeadQual(T : immutable(T[])) { - alias immutable(T)[] StripHeadQual; + alias StripHeadQual = immutable(T)[]; } template StripHeadQual(T : T[]) { - alias T[] StripHeadQual; + alias StripHeadQual = T[]; } template StripHeadQual(T : T*) { - alias T* StripHeadQual; + alias StripHeadQual = T*; } template StripHeadQual(T : T[N], size_t N) { - alias T[N] StripHeadQual; + alias StripHeadQual = T[N]; } template StripHeadQual(T) { - alias T StripHeadQual; + alias StripHeadQual = T; } template FillableParameterTypeTuple(T) { - alias staticMap!(StripHeadQual, ParameterTypeTuple!T) FillableParameterTypeTuple; + alias FillableParameterTypeTuple = staticMap!(StripHeadQual, ParameterTypeTuple!T); } template BindableReturnType(T) { - alias StripHeadQual!(ReturnType!T) BindableReturnType; + alias BindableReturnType = StripHeadQual!(ReturnType!T); } -template UnqualTuple(T...) +template TreatArgs(T...) { static if(T.length == 0) - alias UnqualTuple = TypeTuple!(); + alias TreatArgs = TypeTuple!(); + else static if(isUserStruct!(T[0])) // TODO: we might do this for static arrays too in future...? + // we need to convert struct's into Ref's because 'ref' isn't part of the type in D, and it gets lots in the function calling logic + alias TreatArgs = TypeTuple!(Ref!(T[0]), TreatArgs!(T[1..$])); else - alias UnqualTuple = TypeTuple!(Unqual!(T[0]), UnqualTuple!(T[1..$])); + alias TreatArgs = TypeTuple!(T[0], TreatArgs!(T[1..$])); } //Call with or without return value, propagating Exceptions as Lua errors. //This should rather be throwing a userdata with __tostring and a reference to //the thrown exception, as it is now, everything but the error type and message is lost. -int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) - if(!is(BindableReturnType!T == const) && - !is(BindableReturnType!T == immutable)) +int callFunction(T, RT = BindableReturnType!T)(lua_State* L, T func, ParameterTypeTuple!T args) + if((returnsRef!T && isUserStruct!RT) || + (!is(RT == const) && !is(RT == immutable))) { - alias BindableReturnType!T RetType; - enum hasReturnValue = !is(RetType == void); - - static if(hasReturnValue) - RetType ret; - try { - static if(hasReturnValue) - ret = func(args); + static if(!is(RT == void)) + { + // TODO: should we support references for all types? + static if(returnsRef!T && isUserStruct!RT) + auto ret = Ref!RT(func(args)); + else + RT ret = func(args); + return pushReturnValues(L, ret); + } else func(args); } @@ -116,18 +123,17 @@ int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) luaL_error(L, "%s", toStringz(e.toString())); } - static if(hasReturnValue) - return pushReturnValues(L, ret); - else - return 0; + return 0; } // Ditto, but wrap the try-catch in a nested function because the return value's // declaration and initialization cannot be separated. -int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) - if(is(BindableReturnType!T == const) || - is(BindableReturnType!T == immutable)) +int callFunction(T, RT = BindableReturnType!T)(lua_State* L, T func, ParameterTypeTuple!T args) + if((!returnsRef!T || !isUserStruct!RT) && + (is(RT == const) || is(RT == immutable))) { + // TODO: reconsider if this is necessary? + // surely it would be easier just to wrap the return statement in the try? auto ref call() { try @@ -142,17 +148,17 @@ int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) package: // TODO: right now, virtual functions on specialized classes can be called with base classes as 'self', not safe! -extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L) +extern(C) int methodWrapper(M, T, bool virtual)(lua_State* L) { - alias ParameterTypeTuple!T Args; + alias ParameterTypeTuple!M Args; - static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c), + static assert ((variadicFunctionStyle!M != Variadic.d && variadicFunctionStyle!M != Variadic.c), "Non-typesafe variadic functions are not supported."); //Check arguments int top = lua_gettop(L); - static if (variadicFunctionStyle!T == Variadic.typesafe) + static if (variadicFunctionStyle!M == Variadic.typesafe) enum requiredArgs = Args.length; else enum requiredArgs = Args.length + 1; @@ -160,38 +166,47 @@ extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L) if(top < requiredArgs) argsError(L, top, requiredArgs); - Class* self = cast(Class*)luaL_checkudata(L, 1, toStringz(Class.mangleof)); + static if(is(T == struct)) + Ref!T self = *cast(Ref!T*)luaL_checkudata(L, 1, toStringz(T.mangleof)); + else + T self = *cast(T*)luaL_checkudata(L, 1, toStringz(T.mangleof)); static if(virtual) { - alias ReturnType!T function(Class, Args) VirtualWrapper; + alias RT = ReturnType!M; + static if(returnsRef!M && isUserStruct!RT) + alias ref RT function(T, Args) VirtualWrapper; + else + alias RT function(T, Args) VirtualWrapper; VirtualWrapper func = cast(VirtualWrapper)lua_touserdata(L, lua_upvalueindex(1)); } else { - T func; - static if(is(Class == struct)) - func.ptr = cast(void*)self; + M func; + static if(is(T == struct)) + func.ptr = cast(void*)&self.__instance(); else - func.ptr = cast(void*)*self; + func.ptr = cast(void*)self; func.funcptr = cast(typeof(func.funcptr))lua_touserdata(L, lua_upvalueindex(1)); } //Assemble arguments static if(virtual) { - UnqualTuple!(ParameterTypeTuple!VirtualWrapper) allArgs; - allArgs[0] = *self; + TreatArgs!(ParameterTypeTuple!VirtualWrapper) allArgs; + allArgs[0] = self; alias allArgs[1..$] args; } else { - UnqualTuple!(Args) allArgs; + // TODO: maybe we should build a tuple of 'ReturnType!(getArgument!(T, i))' for each arg? + // then we could get rid of this TreatArgs! rubbish... + TreatArgs!Args allArgs; alias allArgs args; } foreach(i, Arg; Args) - args[i] = getArgument!(T, i)(L, i + 2); + args[i] = getArgument!(M, i)(L, i + 2); return callFunction!(typeof(func))(L, func, allArgs); } @@ -221,19 +236,13 @@ extern(C) int functionWrapper(T)(lua_State* L) T func = *cast(T*)lua_touserdata(L, lua_upvalueindex(1)); //Assemble arguments - Args args; + TreatArgs!Args args; foreach(i, Arg; Args) args[i] = getArgument!(T, i)(L, i + 1); return callFunction!T(L, func, args); } -extern(C) int functionCleaner(lua_State* L) -{ - GC.removeRoot(lua_touserdata(L, 1)); - return 0; -} - public: void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) @@ -249,7 +258,7 @@ void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) if(luaL_newmetatable(L, "__dcall") == 1) { - lua_pushcfunction(L, &functionCleaner); + lua_pushcfunction(L, &userdataCleaner); lua_setfield(L, -2, "__gc"); } @@ -264,16 +273,27 @@ void pushMethod(T, string member)(lua_State* L) if (isSomeFunction!(__traits(get { alias typeof(mixin("&T.init." ~ member)) M; - enum isVirtual = is(T == struct) ? false : true; + enum isVirtual = !is(T == struct); // TODO: final methods should also be handled... static if(isVirtual) { + alias RT = ReturnType!M; + // Delay vtable lookup until the right time - static ReturnType!M virtualWrapper(T self, ParameterTypeTuple!M args) + static if(returnsRef!M && isUserStruct!RT) { - return mixin("self." ~ member)(args); + static ref RT virtualWrapper(T self, ParameterTypeTuple!M args) + { + return mixin("self." ~ member)(args); + } + } + else + { + static RT virtualWrapper(T self, ParameterTypeTuple!M args) + { + return mixin("self." ~ member)(args); + } } - lua_pushlightuserdata(L, &virtualWrapper); } else diff --git a/luad/conversions/helpers.d b/luad/conversions/helpers.d index 4a17010..6761492 100644 --- a/luad/conversions/helpers.d +++ b/luad/conversions/helpers.d @@ -1,5 +1,5 @@ /** -Helpers for various conversions. +Various helper functions, templates, and common code used by the conversion routines. */ module luad.conversions.helpers; @@ -7,17 +7,38 @@ import luad.conversions.functions; import luad.c.all; import luad.base; +import luad.all; + +import core.memory; import std.traits; import std.typetuple; package: -template isInternal(string field) +struct Ref(T) { - enum isInternal = field.length >= 2 && field[0..2] == "__"; + alias __instance this; + + this(ref T s) { ptr = &s; } + + @property ref T __instance() { return *ptr; } + +private: + T* ptr; } +alias AliasMember(T, string member) = Alias!(__traits(getMember, T, member)); + +enum isInternal(string field) = field.length >= 2 && field[0..2] == "__"; +enum isMemberFunction(T, string member) = mixin("is(typeof(&T.init." ~ member ~ ") == delegate)"); +enum isUserStruct(T) = is(T == struct) && !is(T == LuaObject) && !is(T == LuaTable) && !is(T == LuaDynamic) && !is(T == LuaFunction) && !is(T == Ref!S, S); +enum isValueType(T) = isUserStruct!T || isStaticArray!T; + +// TODO: in the presence of a setter property with no getter, '= typeof(T.member).init' doesn't work +// we need to use the setter's argument type instead... +enum canWrite(T, string member) = mixin("__traits(compiles, (cast(T*)null)."~member~" = typeof(T."~member~").init)"); + template isOperator(string field) { enum isOperator = field == "toString" || @@ -33,24 +54,6 @@ template isOperator(string field) field == "opDispatch"; } -template skipField(T, string field) -{ - static if(mixin("is(T."~field~")")) - enum skipField = true; - else static if(__traits(getProtection, __traits(getMember, T, field)) != "public") - enum skipField = true; - else - enum skipField = isInternal!field || - isOperator!field || - hasAttribute!(__traits(getMember, T, field), noscript) || - field == "this"; -} - -template isMemberFunction(T, string member) -{ - enum isMemberFunction = mixin("is(typeof(&T.init." ~ member ~ ") == delegate)"); -} - template isProperty(T, string member) { static if(isMemberFunction!(T, member)) @@ -59,11 +62,24 @@ template isProperty(T, string member) enum isProperty = false; } -template canWrite(T, string member) +template skipMember(T, string member) { - // TODO: in the presence of a setter property with no getter, '= typeof(T.member).init' doesn't work - // we need to use the setter's argument type instead... - enum canWrite = mixin("__traits(compiles, (cast(T*)null)."~member~" = typeof(T."~member~").init)"); + static if(isInternal!member || + isOperator!member || + member == "this" || + mixin("is(T."~member~")") || + __traits(getProtection, __traits(getMember, T, member)) != "public") + enum skipMember = true; + else + enum skipMember = hasAttribute!(__traits(getMember, T, member), noscript) >= 0; +} + +template returnsRef(F...) +{ + static if(isSomeFunction!(F[0])) + enum returnsRef = !!(functionAttributes!(F[0]) & FunctionAttribute.ref_); + else + enum returnsRef = false; } template hasAttribute(alias x, alias attr) @@ -116,64 +132,70 @@ template isStaticMember(T, string member) enum isStaticMember = false; } -void pushGetter(T, string member)(lua_State* L) +void verifyType(T)(lua_State* L, int idx) { - static if(is(T == class)) + if(lua_getmetatable(L, idx) == 0) + luaL_error(L, "attempt to get 'userdata: %p' as a D object", lua_topointer(L, idx)); + + lua_getfield(L, -1, "__dmangle"); //must be a D object + + // TODO: support pointers... + + // TODO: if is(T == const), then we need to check __dmangle == T, const(T) or immutable(T) + size_t manglelen; + auto cmangle = lua_tolstring(L, -1, &manglelen); + if(cmangle[0 .. manglelen] != T.mangleof) { - class S - { - // TODO: should struct and static array return by ref? - final auto get() - { - T _this = *cast(T*)&this; - return mixin("_this."~member); - } - } + lua_getfield(L, -2, "__dtype"); + auto cname = lua_tostring(L, -1); + luaL_error(L, `attempt to get instance %s as type "%s"`, cname, toStringz(T.stringof)); } - else + lua_pop(L, 2); //metatable and metatable.__dmangle +} + + +extern(C) int userdataCleaner(lua_State* L) +{ + GC.removeRoot(lua_touserdata(L, 1)); + return 0; +} + +extern(C) int index(lua_State* L) +{ + auto field = lua_tostring(L, 2); + + // check the getter table + lua_getfield(L, lua_upvalueindex(1), field); + if(!lua_isnil(L, -1)) { - struct S - { - // TODO: should struct and static array return by ref? - auto get() - { - T* _this = cast(T*)&this; - return mixin("_this."~member); - } - } + lua_pushvalue(L, 1); + lua_call(L, 1, LUA_MULTRET); + return lua_gettop(L) - 2; } + else + lua_pop(L, 1); - lua_pushlightuserdata(L, (&S.init.get).funcptr); - lua_pushcclosure(L, &methodWrapper!(typeof(&S.init.get), T, false), 1); + // return method + lua_getfield(L, lua_upvalueindex(2), field); + return 1; } -void pushSetter(T, string member)(lua_State* L) +extern(C) int newIndex(lua_State* L) { - static if(is(T == class)) + auto field = lua_tostring(L, 2); + + // call setter + lua_getfield(L, lua_upvalueindex(1), field); + if(!lua_isnil(L, -1)) { - class S - { - // TODO: where 'value' is struct or static array, we should receive by ref for performance - final void set(typeof(mixin("T."~member)) value) - { - T _this = *cast(T*)&this; - mixin("_this."~member) = value; - } - } + lua_pushvalue(L, 1); + lua_pushvalue(L, 3); + lua_call(L, 2, LUA_MULTRET); } else { - struct S - { - // TODO: where 'value' is struct or static array, we should receive by ref for performance - void set(typeof(mixin("T."~member)) value) - { - T* _this = cast(T*)&this; - mixin("_this."~member) = value; - } - } + // TODO: error? } - lua_pushlightuserdata(L, (&S.init.set).funcptr); - lua_pushcclosure(L, &methodWrapper!(typeof(&S.init.set), T, false), 1); + return 0; } diff --git a/luad/conversions/structs.d b/luad/conversions/structs.d index bf4cb87..200ba55 100644 --- a/luad/conversions/structs.d +++ b/luad/conversions/structs.d @@ -1,9 +1,8 @@ /** -Internal module for pushing and getting _structs. - -A struct is treated as a userdata block. -Members and properties are handled via a thin shim implemented in __index/__newindex. -Methods are registered directly. +Internal module for pushing and getting structs. +Structs are handled by-value across the LuaD API boundary, but internally managed by reference, with semantics equivalent to tables. +Fields and properties are handled via a thin shim implemented in __index/__newindex. Methods are registered directly. +mutable, const and immutable are all supported as expected. immutable structs will capture a direct reference to the D instance, and not be duplicated by LuaD. For an example, see the "Configuration File" example on the $(LINK2 $(REFERENCETOP),front page). */ module luad.conversions.structs; @@ -19,77 +18,88 @@ import core.memory; import std.traits; import std.conv; -private extern(C) int structCleaner(lua_State* L) -{ - GC.removeRoot(lua_touserdata(L, 1)); - return 0; -} -private extern(C) int indexStruct(lua_State* L) +void pushGetter(T, string member)(lua_State* L) { - auto field = lua_tostring(L, 2); - - // check the getter table - lua_getfield(L, lua_upvalueindex(1), field); - if(!lua_isnil(L, -1)) + alias RT = typeof(mixin("T."~member)); + struct S { - lua_pushvalue(L, 1); - lua_call(L, 1, LUA_MULTRET); - return lua_gettop(L) - 2; + static if((!isMemberFunction!(T, member) || returnsRef!(AliasMember!(T, member))) && isUserStruct!RT) + { + ref RT get() + { + T* _this = cast(T*)&this; + return mixin("_this."~member); + } + } + else + { + RT get() + { + T* _this = cast(T*)&this; + return mixin("_this."~member); + } + } } - else - lua_pop(L, 1); - // return method - lua_getfield(L, lua_upvalueindex(2), field); - return 1; + lua_pushlightuserdata(L, (&S.init.get).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&S.init.get), T, false), 1); } -private extern(C) int newIndexStruct(lua_State* L) +private void pushGetters(T)(lua_State* L) { - auto field = lua_tostring(L, 2); + lua_newtable(L); // -2 is getters + lua_newtable(L); // -1 is methods - // call setter - lua_getfield(L, lua_upvalueindex(1), field); - if(!lua_isnil(L, -1)) - { - lua_pushvalue(L, 1); - lua_pushvalue(L, 3); - lua_call(L, 2, LUA_MULTRET); - } - else + // populate getters + foreach(member; __traits(allMembers, T)) { - // TODO: error? + static if(!skipMember!(T, member) && + !isStaticMember!(T, member)) + { + static if(isMemberFunction!(T, member) && !isProperty!(T, member)) + { + pushMethod!(T, member)(L); + lua_setfield(L, -2, member.ptr); + } + else + { + pushGetter!(T, member)(L); + lua_setfield(L, -3, member.ptr); + } + } } - return 0; + lua_pushcclosure(L, &index, 2); } -private void pushGetters(T)(lua_State* L) +void pushSetter(T, string member)(lua_State* L) { - lua_newtable(L); // -2 is getters - lua_newtable(L); // -1 is methods + // TODO: This is broken if setter argument is different from the getter's return type... + alias ArgType = typeof(mixin("T."~member)); - // populate getters - foreach(field; __traits(allMembers, T)) + struct S { - static if(!skipField!(T, field) && - !isStaticMember!(T, field)) + static if(isUserStruct!ArgType) { - static if(isMemberFunction!(T, field) && !isProperty!(T, field)) + void set(ref ArgType value) { - pushMethod!(T, field)(L); - lua_setfield(L, -2, field.ptr); + T* _this = cast(T*)&this; + mixin("_this."~member) = value; } - else + } + else + { + void set(ArgType value) { - pushGetter!(T, field)(L); - lua_setfield(L, -3, field.ptr); + T* _this = cast(T*)&this; + mixin("_this."~member) = value; } } } - lua_pushcclosure(L, &indexStruct, 2); + lua_pushlightuserdata(L, (&S.init.set).funcptr); + lua_pushcclosure(L, &methodWrapper!(typeof(&S.init.set), T, false), 1); } private void pushSetters(T)(lua_State* L) @@ -97,21 +107,21 @@ private void pushSetters(T)(lua_State* L) lua_newtable(L); // populate setters - foreach(field; __traits(allMembers, T)) + foreach(member; __traits(allMembers, T)) { - static if(!skipField!(T, field) && - !isStaticMember!(T, field) && - canWrite!(T, field)) + static if(!skipMember!(T, member) && + !isStaticMember!(T, member) && + canWrite!(T, member)) // TODO: move into the setter for readonly fields { - static if(!isMemberFunction!(T, field) || isProperty!(T, field)) + static if(!isMemberFunction!(T, member) || isProperty!(T, member)) { - pushSetter!(T, field)(L); - lua_setfield(L, -2, field.ptr); + pushSetter!(T, member)(L); + lua_setfield(L, -2, member.ptr); } } } - lua_pushcclosure(L, &newIndexStruct, 1); + lua_pushcclosure(L, &newIndex, 1); } private void pushMeta(T)(lua_State* L) @@ -120,11 +130,13 @@ private void pushMeta(T)(lua_State* L) return; pushValue(L, T.stringof); - lua_setfield(L, -2, "__dstruct"); + lua_setfield(L, -2, "__dtype"); + + // TODO: mangled names can get REALLY long in D, it might be nicer to store a hash instead? pushValue(L, T.mangleof); lua_setfield(L, -2, "__dmangle"); - lua_pushcfunction(L, &structCleaner); + lua_pushcfunction(L, &userdataCleaner); lua_setfield(L, -2, "__gc"); pushGetters!T(L); @@ -151,9 +163,34 @@ private void pushMeta(T)(lua_State* L) lua_setfield(L, -2, "__metatable"); } -void pushStruct(T)(lua_State* L, ref inout(T) value) if (is(T == struct)) +void pushStruct(T)(lua_State* L, ref T value) if (is(T == struct)) { - auto udata = emplace(cast(T*)lua_newuserdata(L, T.sizeof), value); + // if T is immutable, we can capture a reference, otherwise we need to take a copy + static if(is(T == immutable)) // TODO: verify that this is actually okay? + { + auto udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + *udata = Ref!T(value); + } + else + { + Ref!T* udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + // TODO: we should try and call the postblit here maybe...? +// T* copy = new T(value); + T* copy = cast(T*)GC.malloc(T.sizeof); + *copy = value; + *udata = Ref!T(*copy); + } + + GC.addRoot(udata); + + pushMeta!T(L); + lua_setmetatable(L, -2); +} + +void pushStruct(R : Ref!T, T)(lua_State* L, R value) if (is(T == struct)) +{ + auto udata = cast(Ref!T*)lua_newuserdata(L, Ref!T.sizeof); + *udata = Ref!T(value); GC.addRoot(udata); @@ -163,7 +200,9 @@ void pushStruct(T)(lua_State* L, ref inout(T) value) if (is(T == struct)) ref T getStruct(T)(lua_State* L, int idx) if(is(T == struct)) { - T* udata = cast(T*)lua_touserdata(L, idx); + verifyType!T(L, idx); + + Ref!T* udata = cast(Ref!T*)lua_touserdata(L, idx); return *udata; } diff --git a/luad/stack.d b/luad/stack.d index ae0978b..9d1886d 100644 --- a/luad/stack.d +++ b/luad/stack.d @@ -73,6 +73,7 @@ import luad.conversions.assocarrays; import luad.conversions.classes; import luad.conversions.enums; import luad.conversions.variant; +import luad.conversions.helpers; /** * Push a value of any type to the stack. @@ -80,7 +81,7 @@ import luad.conversions.variant; * L = stack to push to * value = value to push */ -void pushValue(T)(lua_State* L, T value) +void pushValue(T)(lua_State* L, T value) if(!isUserStruct!T) { static if(is(T : LuaObject)) value.push(); @@ -124,7 +125,7 @@ void pushValue(T)(lua_State* L, T value) else static if(isArray!T) pushArray(L, value); - else static if(is(T == struct)) + else static if(is(T == Ref!S, S) && isUserStruct!S) pushStruct(L, value); // luaCFunction's are directly pushed @@ -146,6 +147,18 @@ void pushValue(T)(lua_State* L, T value) static assert(false, "Unsupported type `" ~ T.stringof ~ "` in stack push operation"); } +void pushValue(T)(lua_State* L, ref T value) if(isUserStruct!T) +{ + static if(isArray!T) + pushArray(L, value); + else static if(is(T == struct)) + pushStruct(L, value); + else + { + static assert(false, "Shouldn't be here! `" ~ T.stringof ~ "` should be handled by the other overload."); + } +} + template isVoidArray(T) { enum isVoidArray = is(T == void[]) || @@ -209,7 +222,7 @@ private void argumentTypeMismatch(lua_State* L, int idx, int expectedType) * L = stack to get from * idx = value stack index */ -T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) +T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) if(!isUserStruct!T) { debug //ensure unchanged stack { @@ -298,8 +311,6 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int return getVariant!T(L, idx); } - else static if(is(T == struct)) - return getStruct!T(L, idx); else static if(isSomeFunction!T) return getFunction!T(L, idx); @@ -313,11 +324,49 @@ T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int } } +// we need an overload that handles struct and static arrays (which need to return by ref) +ref T getValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L, int idx) if(isUserStruct!T) +{ + debug //ensure unchanged stack + { + int _top = lua_gettop(L); + scope(success) assert(lua_gettop(L) == _top); + } + + // TODO: confirm that we need this in this overload...? + static if(!is(T == LuaObject) && !is(T == LuaDynamic) && !isVariant!T) + { + int type = lua_type(L, idx); + enum expectedType = luaTypeOf!T; + + //if a class reference, return null for nil values + static if(is(T : Object)) + { + if(type == LuaType.Nil) + return null; + } + + if(type != expectedType) + typeMismatchHandler(L, idx, expectedType); + } + + static if(isArray!T) + return getArray!T(L, idx); + + else static if(is(T == struct)) + return getStruct!T(L, idx); + + else + { + static assert(false, "Shouldn't be here! `" ~ T.stringof ~ "` should be handled by the other overload."); + } +} + /** * Same as calling getValue!(T, typeMismatchHandler)(L, -1), then popping one value from the stack. * See_Also: $(MREF getValue) */ -T popValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L) +auto ref popValue(T, alias typeMismatchHandler = defaultTypeMismatch)(lua_State* L) { scope(success) lua_pop(L, 1); return getValue!(T, typeMismatchHandler)(L, -1); @@ -384,7 +433,13 @@ auto getArgument(T, int narg)(lua_State* L, int idx) return cstr[0 .. len]; } else - return getValue!(Arg, argumentTypeMismatch)(L, idx); + { + // TODO: make an overload to handle struct and static array, and remove this Ref! hack? + static if(isUserStruct!Arg) // user struct's need to return wrapped in a Ref + return Ref!Arg(getValue!(Arg, argumentTypeMismatch)(L, idx)); + else + return getValue!(Arg, argumentTypeMismatch)(L, idx); + } } template isVariableReturnType(T : LuaVariableReturn!U, U) @@ -487,7 +542,7 @@ int pushReturnValues(T)(lua_State* L, T value) pushTuple(L, value); return cast(int)T.Types.length; } - else static if(isStaticArray!T) + else static if(isStaticArray!T) // TODO: remove this special case when we fix pushValue for static arrays { pushStaticArray(L, value); return cast(int)value.length;