diff --git a/native/projects/lcpu/src/LcpuGlobals.cpp b/native/projects/lcpu/src/LcpuGlobals.cpp index bdf53ef..a720ac6 100644 --- a/native/projects/lcpu/src/LcpuGlobals.cpp +++ b/native/projects/lcpu/src/LcpuGlobals.cpp @@ -4,32 +4,32 @@ #include "GarrysMod/Lua/LuaBase.h" #include "LuaCpu.hpp" #include "LuaDevice.hpp" + #include "LuaHelpers.hpp" +#include "LuaObject.hpp" /// test for the "new" lua object system struct TestLuaObject : public lcpu::lua::LuaObject { - static const char* Name() { return "TestLuaObject"; } + constexpr static const char* Name() { return "TestLuaObject"; } static void RegisterClass(GarrysMod::Lua::ILuaBase* LUA) { RegisterClassStart(LUA); - // Metamethods can be registered here; in this case, our test object doesn't need any - RegisterClassEnd(LUA); // Register methods. Maybe later I'll do some crazy template stuff; for now this is pretty barebones. - RegisterMethod("Test", &Test); + RegisterMethod("Test", Test); RegisterGetter("Variable", [](GarrysMod::Lua::ILuaBase* LUA) { LUA->PushNumber(32.6); }); RegisterGetter("MemberVariable", [](GarrysMod::Lua::ILuaBase* LUA) { auto self = TestLuaObject::FromLua(LUA, 1); LUA->PushNumber(self->n); }); RegisterSetter("MemberVariable", [](GarrysMod::Lua::ILuaBase* LUA) { - // 3 will be the assignment stack position + // The value of a setter is placed at the top of the stack by LuaObject auto self = TestLuaObject::FromLua(LUA, 1); - self->n = LUA->GetNumber(3); + self->n = LUA->GetNumber(-1); }); } - LUA_MEMBER_FUNCTION(Test) + LUA_MEMBER_FUNCTION(Test); double n; }; diff --git a/native/projects/lcpu/src/LuaHelpers.hpp b/native/projects/lcpu/src/LuaHelpers.hpp index a8229fb..5629e08 100644 --- a/native/projects/lcpu/src/LuaHelpers.hpp +++ b/native/projects/lcpu/src/LuaHelpers.hpp @@ -88,185 +88,5 @@ namespace lcpu::lua { return {}; } } - - /// A CRTP-based class which allows binding C++ to Lua, in a - /// fairly sensible manner. - template - struct LuaObject { - using CFunc = GarrysMod::Lua::CFunc; - using ILuaFunc = void (*)(GarrysMod::Lua::ILuaBase*); - - /// Register a C++ method. - static void RegisterMethod(const std::string& name, CFunc func) { methods()[name] = func; } - - /// Register a getter for a value to be read. - static void RegisterGetter(const std::string& name, ILuaFunc func) { getters()[name] = func; } - - /// Register a setter. This can be used to make a - /// C++ registered value read-write. - static void RegisterSetter(const std::string& name, ILuaFunc func) { setters()[name] = func; } - - // addl. arguments are forwarded to the C++ constructor - template - static void Create(GarrysMod::Lua::ILuaBase* LUA, Args&&... args) { - auto ptr = new TImpl(static_cast(args)...); - ptr->InitLuaStuff(LUA); - LUA->PushUserType(ptr, __lua_typeid); - } - - static TImpl* FromLua(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { - LUA->CheckType(stackPos, __lua_typeid); - return LUA->GetUserType(stackPos, __lua_typeid); - } - - protected: - /// This should be called first in your RegisterClass static method. - /// This doesn't pop the metatable off so you can keep adding things to it - static void RegisterClassStart(GarrysMod::Lua::ILuaBase* LUA) { - auto typeid_name = std::format("{}__typeid", TImpl::Name()); - - // clang-format off - __lua_typeid = LUA->CreateMetaTable(TImpl::Name()); - LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); - LUA->PushNumber(__lua_typeid); - LUA->SetField(-2, typeid_name.c_str()); - LUA->Pop(); // pop registry - // add in required metamethods - LUA_SET_C_FUNCTION(__gc) - LUA_SET_C_FUNCTION(__index) - LUA_SET_C_FUNCTION(__newindex) - // clang-format on - } - - /// Call in your RegisterClass() method when done registering metamethods. - static void RegisterClassEnd(GarrysMod::Lua::ILuaBase* LUA) { LUA->Pop(); } - - /// A detail function used to setup some stuff in the Create() method. - void InitLuaStuff(GarrysMod::Lua::ILuaBase* LUA) { - lua = LUA; - - // create the table used to store user properties - // from Lua - LUA->CreateTable(); - tableReference = LUA->ReferenceCreate(); - - // register some convinence things - RegisterGetter("Name", [](GarrysMod::Lua::ILuaBase* LUA) { LUA->PushString(TImpl::Name()); }); - } - - LuaObject() = default; - ~LuaObject() { - // free the table reference - if(tableReference != -1) - lua->ReferenceFree(tableReference); - } - - - /// The LUA interface used to create this class. - GarrysMod::Lua::ILuaBase* lua; - - private: - // base metamethods - LUA_MEMBER_FUNCTION(__gc) - LUA_MEMBER_FUNCTION(__index) - LUA_MEMBER_FUNCTION(__newindex) - - // static stuff - static int __lua_typeid; - - static auto& methods() { - static std::unordered_map methods__; - return methods__; - } - - static auto& getters() { - static std::unordered_map getters__; - return getters__; - } - - static auto& setters() { - static std::unordered_map setters__; - return setters__; - } - - // instance stuff - int tableReference { -1 }; - }; - - template - int LuaObject::__lua_typeid = 0; - - template - LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __gc) { - auto self = FromLua(LUA, 1); - if(self != nullptr) { - lucore::LogInfo("GCing LuaObject-based object @ {:p}", static_cast(self)); - delete self; - } - return 0; - } - - template - LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __index) { - auto self = FromLua(LUA, 1); - - if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { - auto key = GetLuaString(LUA, 2); - - if(methods().find(key) != methods().end()) { - LUA->PushCFunction(methods()[key]); - return 1; - } - - if(getters().find(key) != getters().end()) { - // getters explicitly push their return onto the stack - getters()[key](LUA); - return 1; - } - } - - // look up from the table - // clang-format off - LUA->ReferencePush(self->tableReference); - LUA->Push(2); - LUA->GetTable(-2); - // clang-format on - return 1; - } - - template - LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __newindex) { - auto self = FromLua(LUA, 1); - - if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { - auto key = GetLuaString(LUA, 2); - - // don't allow overwriting methods - if(methods().find(key) != methods().end()) - return 0; - - // or read-only values - if(getters().find(key) != getters().end() && setters().find(key) == setters().end()) - return 0; - - if(setters().find(key) != setters().end()) { - // for ergonomic sake only I kind of want to make this like - // LUA->Push(3) so that -1 (top of stack) is the value - // a bit cleaner. idk - setters()[key](LUA); - return 0; - } - } - - // push onto the table - // clang-format off - LUA->ReferencePush(self->tableReference); - LUA->Push(2); - LUA->Push(3); - LUA->SetTable(-3); - LUA->Pop(); - // clang-format on - return 0; - } - + } // namespace lcpu::lua diff --git a/native/projects/lcpu/src/LuaObject.hpp b/native/projects/lcpu/src/LuaObject.hpp new file mode 100644 index 0000000..1d16811 --- /dev/null +++ b/native/projects/lcpu/src/LuaObject.hpp @@ -0,0 +1,217 @@ +//#include + +#include "GarrysMod/Lua/Interface.h" +#include "GarrysMod/Lua/LuaBase.h" +#include "LuaHelpers.hpp" + +namespace lcpu::lua { + + /// A CRTP-based class which allows binding C++ to Lua, in a + /// fairly sensible manner. + template + struct LuaObject { + using CFunc = GarrysMod::Lua::CFunc; + using ILuaFunc = int (*)(GarrysMod::Lua::ILuaBase*); + using ILuaVoidFunc = void (*)(GarrysMod::Lua::ILuaBase*); + + //using CFuncStd = std::function; + //using ILuaFuncStd = std::function; + //using ILuaVoidFuncStd = std::function; + + /// Register a C++ method. + static void RegisterMethod(const std::string& name, CFunc func) { + methods()[name] = func; + } + + /// Register a getter for a value to be read. + static void RegisterGetter(const std::string& name, ILuaVoidFunc func) { getters()[name] = func; } + + /// Register a setter. This can be used to make a + /// C++ registered value read-write. + static void RegisterSetter(const std::string& name, ILuaVoidFunc func) { setters()[name] = func; } + + /// Create an instance of this type to give to Lua. + /// addl. arguments are forwarded to the C++ constructor + template + static void Create(GarrysMod::Lua::ILuaBase* LUA, Args&&... args) { + auto ptr = new TImpl(static_cast(args)...); + ptr->InitLuaStuff(LUA); + LUA->PushUserType(ptr, __lua_typeid); + } + + static TImpl* FromLua(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { + LUA->CheckType(stackPos, __lua_typeid); + return LUA->GetUserType(stackPos, __lua_typeid); + } + + protected: + /// This should be called first in your RegisterClass static method. + static void RegisterClassStart(GarrysMod::Lua::ILuaBase* LUA) { + auto typeid_name = std::format("{}__typeid", TImpl::Name()); + + // clang-format off + __lua_typeid = LUA->CreateMetaTable(TImpl::Name()); + LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); + LUA->PushNumber(__lua_typeid); + LUA->SetField(-2, typeid_name.c_str()); + LUA->Pop(); // pop registry + // add in required metamethods + LUA_SET_C_FUNCTION(__gc) + LUA_SET_C_FUNCTION(__index) + LUA_SET_C_FUNCTION(__newindex) + LUA->Pop(); // pop metatable + // clang-format on + } + + /// Register a metafunction. + /// Note that the following metafunctions are reserved by the implementation + /// of this object, and should not be overwritten: + /// + /// - __gc + /// - __index + /// - __newindex + /// + static void RegisterMetaFunction(GarrysMod::Lua::ILuaBase* LUA, const std::string& name, CFunc func) { + // clang-format off + LUA->PushMetaTable(__lua_typeid); + LUA->PushCFunction(func); + LUA->SetField(-2, name.c_str()); + LUA->Pop(); + // clang-format on + } + + /// A detail function used to setup some stuff in the Create() method. + void InitLuaStuff(GarrysMod::Lua::ILuaBase* LUA) { + lua = LUA; + + // create the table used to store user properties + // from Lua + LUA->CreateTable(); + tableReference = LUA->ReferenceCreate(); + + // register some convinence things + RegisterGetter("Name", [](GarrysMod::Lua::ILuaBase* LUA) { LUA->PushString(TImpl::Name()); }); + } + + LuaObject() = default; + ~LuaObject() { + // free the table reference + if(tableReference != -1) + lua->ReferenceFree(tableReference); + } + + /// The LUA interface used to create this class. + GarrysMod::Lua::ILuaBase* lua; + + private: + // base metamethods + LUA_MEMBER_FUNCTION(__gc) + LUA_MEMBER_FUNCTION(__index) + LUA_MEMBER_FUNCTION(__newindex) + + // static stuff + static int __lua_typeid; + + static auto& methods() { + static std::unordered_map methods__; + return methods__; + } + + static auto& getters() { + static std::unordered_map getters__; + return getters__; + } + + static auto& setters() { + static std::unordered_map setters__; + return setters__; + } + + // instance stuff + int tableReference { -1 }; + }; + + template + int LuaObject::__lua_typeid = 0; + + template + LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __gc) { + auto self = FromLua(LUA, 1); + if(self != nullptr) { + lucore::LogInfo("GCing LuaObject {} @ {:p}", TImpl::Name(), static_cast(self)); + delete self; + } + return 0; + } + + template + LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __index) { + auto self = FromLua(LUA, 1); + + if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { + auto& methods = LuaObject::methods(); + auto& getters = LuaObject::getters(); + auto key = GetLuaString(LUA, 2); + + if(methods.find(key) != methods.end()) { + LUA->PushCFunction(methods[key]); + return 1; + } + + if(getters.find(key) != getters.end()) { + // getters explicitly push their return onto the stack + getters[key](LUA); + return 1; + } + } + + // look up from the table + // clang-format off + LUA->ReferencePush(self->tableReference); + LUA->Push(2); + LUA->GetTable(-2); + // clang-format on + return 1; + } + + template + LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject, __newindex) { + auto self = FromLua(LUA, 1); + + if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { + auto& methods = LuaObject::methods(); + auto& getters = LuaObject::getters(); + auto& setters = LuaObject::setters(); + auto key = GetLuaString(LUA, 2); + + // don't allow overwriting methods + if(methods.find(key) != methods.end()) + return 0; + + // or read-only C++ values + if(getters.find(key) != getters.end() && setters.find(key) == setters.end()) + return 0; + + if(setters.find(key) != setters.end()) { + // clang-format off + // Push the value to be written onto the top of the stack. + // This is mostly for ergonomic reasons. + LUA->Push(3); + setters[key](LUA); + LUA->Pop(); + return 0; + } + } + + // push onto the table + // clang-format off + LUA->ReferencePush(self->tableReference); + LUA->Push(2); + LUA->Push(3); + LUA->SetTable(-3); + LUA->Pop(); + // clang-format on + return 0; + } + +} // namespace lcpu::lua