Files
lunch-games/source/luad/conversions/classes.d
2025-04-29 01:59:15 +02:00

289 lines
5.6 KiB
D

/**
Internal module for pushing and getting class types.
This feature is still a work in progress, currently, only the simplest of _classes are supported.
See the source code for details.
*/
module luad.conversions.classes;
import luad.conversions.functions;
import luad.c.all;
import luad.stack;
import luad.base;
import core.memory;
import std.traits;
import std.string : toStringz;
extern(C) private int classCleaner(lua_State* L)
{
GC.removeRoot(lua_touserdata(L, 1));
return 0;
}
private void pushMeta(T)(lua_State* L, T obj)
{
if(luaL_newmetatable(L, T.mangleof.ptr) == 0)
return;
pushValue(L, T.stringof);
lua_setfield(L, -2, "__dclass");
pushValue(L, T.mangleof);
lua_setfield(L, -2, "__dmangle");
lua_newtable(L); //__index fallback table
foreach(member; __traits(derivedMembers, T))
{
static if(__traits(getProtection, __traits(getMember, T, member)) == "public" && //ignore non-public fields
member != "this" && member != "__ctor" && //do not handle
member != "Monitor" && member != "toHash" && //do not handle
member != "toString" && member != "opEquals" && //handle below
member != "opCmp") //handle below
{
static if(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member)))
{
pushMethod!(T, member)(L);
lua_setfield(L, -2, toStringz(member));
}
}
}
lua_setfield(L, -2, "__index");
pushMethod!(T, "toString")(L);
lua_setfield(L, -2, "__tostring");
pushMethod!(T, "opEquals")(L);
lua_setfield(L, -2, "__eq");
//TODO: handle opCmp here
lua_pushcfunction(L, &classCleaner);
lua_setfield(L, -2, "__gc");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__metatable");
}
void pushClassInstance(T)(lua_State* L, T obj) if (is(T == class))
{
T* ud = cast(T*)lua_newuserdata(L, obj.sizeof);
*ud = obj;
pushMeta(L, obj);
lua_setmetatable(L, -2);
GC.addRoot(ud);
}
//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
Object obj = *cast(Object*)lua_touserdata(L, idx);
return cast(T)obj;
}
template hasCtor(T)
{
enum hasCtor = __traits(compiles, __traits(getOverloads, T.init, "__ctor"));
}
// TODO: exclude private members (I smell DMD bugs...)
template isStaticMember(T, string member)
{
static if(__traits(compiles, mixin("&T." ~ member)))
{
static if(is(typeof(mixin("&T.init." ~ member)) == delegate))
enum isStaticMember = __traits(isStaticFunction, mixin("T." ~ member));
else
enum isStaticMember = true;
}
else
enum isStaticMember = false;
}
// For use as __call
void pushCallMetaConstructor(T)(lua_State* L)
{
alias typeof(__traits(getOverloads, T.init, "__ctor")) Ctor;
static T ctor(LuaObject self, ParameterTypeTuple!Ctor args)
{
return new T(args);
}
pushFunction(L, &ctor);
}
// TODO: Private static fields are mysteriously pushed without error...
// TODO: __index should be a function querying the static fields directly
void pushStaticTypeInterface(T)(lua_State* L)
{
lua_newtable(L);
enum metaName = T.mangleof ~ "_static";
if(luaL_newmetatable(L, metaName.ptr) == 0)
{
lua_setmetatable(L, -2);
return;
}
static if(hasCtor!T)
{
pushCallMetaConstructor!T(L);
lua_setfield(L, -2, "__call");
}
lua_newtable(L);
foreach(member; __traits(derivedMembers, T))
{
static if(isStaticMember!(T, member))
{
enum isFunction = is(typeof(mixin("T." ~ member)) == function);
static if(isFunction)
pushValue(L, mixin("&T." ~ member));
else
pushValue(L, mixin("T." ~ member));
lua_setfield(L, -2, member.ptr);
}
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
version(unittest)
{
import luad.testing;
private lua_State* L;
}
unittest
{
L = luaL_newstate();
static class A
{
private:
string s;
public:
int n;
this(int n, string s)
{
this.n = n;
this.s = s;
}
string foo(){ return s; }
int bar(int i)
{
return n += i;
}
void verifyN(int n)
{
assert(this.n == n);
}
}
static class B : A
{
this(int a, string s)
{
super(a, s);
}
override string foo() { return "B"; }
override string toString() { return "B"; }
}
void addA(in char* name, A a)
{
pushValue(L, a);
lua_setglobal(L, name);
}
auto a = new A(2, "foo");
addA("a", a);
pushValue(L, a.toString());
lua_setglobal(L, "a_toString");
auto b = new B(2, "foo");
addA("b", b);
addA("otherb", b);
pushValue(L, (A a)
{
assert(a);
a.bar(2);
});
lua_setglobal(L, "func");
luaL_openlibs(L);
unittest_lua(L, `
--assert(a.n == 2)
assert(a:bar(2) == 4)
--assert(a.n == 4)
func(a)
assert(a:bar(2) == 8)
--a.n = 42
--a:verifyN(42)
--assert(a.n == 42)
assert(a:foo() == "foo")
assert(tostring(a) == a_toString)
assert(b:bar(2) == 4)
func(b)
assert(b:bar(2) == 8)
assert(b:foo() == "B")
assert(tostring(b) == "B")
assert(a ~= b)
assert(b == otherb)
`);
pushValue(L, cast(B)null);
lua_setglobal(L, "c");
unittest_lua(L, `assert(c == nil)`);
pushValue(L, (B b) => assert(b is null));
lua_setglobal(L, "checkNull");
unittest_lua(L, `checkNull(nil)`);
}