From 1b347eecc88ce6d63b1ae607053e686a36af3453 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Thu, 27 Jul 2023 07:16:39 -0400 Subject: [PATCH] lcpu: Cleanup, and begin the implementation of a new system for Lua binding I didn't like how I did it before, and I don't like how right now everything binds a bit differently. So let's unify it using the way I like (from the LuaDevice binding)! --- lua/autorun/lcpu_load.lua | 16 ++ native/projects/lcpu/src/LcpuGlobals.cpp | 47 ++++- native/projects/lcpu/src/LuaDevice.cpp | 49 ++---- native/projects/lcpu/src/LuaHelpers.hpp | 214 +++++++++++++++++++++-- 4 files changed, 273 insertions(+), 53 deletions(-) diff --git a/lua/autorun/lcpu_load.lua b/lua/autorun/lcpu_load.lua index 739a139..b835679 100644 --- a/lua/autorun/lcpu_load.lua +++ b/lua/autorun/lcpu_load.lua @@ -10,6 +10,22 @@ if SERVER then return end +----[[ + testobj = LCPUNative.CreateTest() + print(testobj:Test()) + print(testobj.Variable) + + testobj.MemberVariable = 32.9 + print(testobj.MemberVariable) + + testobj.Variable = 32.1 -- this should fial + testobj.Test = nil; + + print(testobj.Variable) + + print(testobj.Name) +--]] + -- rapid iteration requires rapid solutions --[[ device = LCPUNative.CreateDevice(0x100000f0, 0x10) diff --git a/native/projects/lcpu/src/LcpuGlobals.cpp b/native/projects/lcpu/src/LcpuGlobals.cpp index d91635f..bdf53ef 100644 --- a/native/projects/lcpu/src/LcpuGlobals.cpp +++ b/native/projects/lcpu/src/LcpuGlobals.cpp @@ -1,13 +1,46 @@ #include "LcpuGlobals.hpp" #include "GarrysMod/Lua/Interface.h" +#include "GarrysMod/Lua/LuaBase.h" #include "LuaCpu.hpp" #include "LuaDevice.hpp" #include "LuaHelpers.hpp" +/// test for the "new" lua object system +struct TestLuaObject : public lcpu::lua::LuaObject { + 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); + 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 + auto self = TestLuaObject::FromLua(LUA, 1); + self->n = LUA->GetNumber(3); + }); + } + + LUA_MEMBER_FUNCTION(Test) + double n; +}; + +LUA_MEMBER_FUNCTION_IMPLEMENT(TestLuaObject, Test) { + LUA->PushString("hi :)"); + return 1; +} + LUA_FUNCTION(LCPUNative_CreateCPU) { LUA->CheckType(1, GarrysMod::Lua::Type::Number); - auto memorySize = static_cast(LUA->GetNumber(1)); + auto memorySize = static_cast(LUA->GetNumber(1)); // TODO: There's probably a way to like, ensure a per-player max. if(memorySize > (64 * 1024 * 1024)) @@ -20,15 +53,23 @@ LUA_FUNCTION(LCPUNative_CreateCPU) { LUA_FUNCTION(LCPUNative_CreateDevice) { auto base = LUA->CheckNumber(1); auto size = LUA->CheckNumber(2); - lucore::LogInfo("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast(base), static_cast(size)); + lucore::LogInfo("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast(base), + static_cast(size)); LuaDevice::Create(LUA, static_cast(base), static_cast(size)); return 1; } + LUA_FUNCTION(LCPUNative_CreateTest) { + TestLuaObject::Create(LUA); + return 1; +} + void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) { LuaCpu::Bind(LUA); LuaDevice::Bind(LUA); + TestLuaObject::RegisterClass(LUA); + // clang-format off LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); LUA->CreateTable(); @@ -37,6 +78,8 @@ void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) { LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateCPU, "CreateCPU"); LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateDevice, "CreateDevice"); + + LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateTest, "CreateTest"); LUA->SetField(-2, "LCPUNative"); LUA->Pop(); // clang-format on diff --git a/native/projects/lcpu/src/LuaDevice.cpp b/native/projects/lcpu/src/LuaDevice.cpp index 8e21997..2bfb48c 100644 --- a/native/projects/lcpu/src/LuaDevice.cpp +++ b/native/projects/lcpu/src/LuaDevice.cpp @@ -9,6 +9,7 @@ bool LuaDevice::Clocked() const { } void LuaDevice::Clock() { + // clang-format off LuaState->ReferencePush(tableReference); LuaState->GetField(-1,"Clock"); if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { @@ -18,6 +19,7 @@ void LuaDevice::Clock() { LuaState->Pop(); // pop the Clock function off the stack } LuaState->Pop(); // pop the reference + // clang-format off } riscv::Address LuaDevice::Base() const { @@ -29,15 +31,7 @@ riscv::Address LuaDevice::Size() const { } u32 LuaDevice::Peek(riscv::Address address) { - /*if(peekHandlerReference != -1) { - LuaState->ReferencePush(resetHandlerReference); - LuaState->PushNumber(static_cast(address)); - LuaState->Call(1, 1); - auto result = LuaState->GetNumber(-1); - LuaState->Pop(); - return static_cast(result); - }*/ - + // clang-format off LuaState->ReferencePush(tableReference); LuaState->GetField(-1,"Peek"); if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { @@ -52,17 +46,12 @@ u32 LuaDevice::Peek(riscv::Address address) { LuaState->Pop(); // pop whatever Peek is off the stack } LuaState->Pop(); // pop the table reference + // clang-format on return 0xffffffff; } void LuaDevice::Poke(riscv::Address address, u32 value) { - /*if(pokeHandlerReference != -1) { - LuaState->ReferencePush(pokeHandlerReference); - LuaState->PushNumber(address); - LuaState->PushNumber(value); - LuaState->Call(2, 0); - }*/ - + // clang-format off LuaState->ReferencePush(tableReference); LuaState->GetField(-1,"Poke"); if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { @@ -74,14 +63,11 @@ void LuaDevice::Poke(riscv::Address address, u32 value) { LuaState->Pop(); // pop whatever Peek is } LuaState->Pop(); // pop the table reference + // clang-format on } void LuaDevice::Reset() { - /*if(resetHandlerReference != -1) { - LuaState->ReferencePush(resetHandlerReference); - LuaState->Call(0, 0); - }*/ - + // clang-format off LuaState->ReferencePush(tableReference); LuaState->GetField(-1,"Reset"); if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { @@ -91,6 +77,7 @@ void LuaDevice::Reset() { LuaState->Pop(); // pop whatever reset is } LuaState->Pop(); // pop the reference + // clang-format on } LuaDevice::LuaDevice(riscv::Address base, riscv::Address size) : base(base), size(size) { @@ -104,8 +91,8 @@ LuaDevice::~LuaDevice() { LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __index) { auto self = LUA_CLASS_GET(LuaDevice)(1); - //lucore::LogInfo("metamethod __index call"); - + // lucore::LogInfo("metamethod __index call"); + // TODO: before moving this to a shared lua object class thing // and moving the CPU class to use this way of doing things // I should probably try and like, add stuff to ensure native @@ -118,9 +105,9 @@ LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __index) { LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __newindex) { auto self = LUA_CLASS_GET(LuaDevice)(1); - //lucore::LogInfo("metamethod __newindex call"); + // lucore::LogInfo("metamethod __newindex call"); - // Always push onto the table. + // Always push onto the table. // TODO: This function // should error on attempt to __newindex any native methods // (when moved to a shared place) @@ -146,8 +133,6 @@ void LuaDevice::Bind(GarrysMod::Lua::ILuaBase* LUA) { LUA_SET_C_FUNCTION(__newindex) LUA_CLASS_BIND_END(); // clang-format on - - } void LuaDevice::Create(GarrysMod::Lua::ILuaBase* LUA, riscv::Address base, riscv::Address size) { @@ -155,17 +140,17 @@ void LuaDevice::Create(GarrysMod::Lua::ILuaBase* LUA, riscv::Address base, riscv device->LuaState = LUA; LUA->CreateTable(); - device->tableReference = LUA->ReferenceCreate(); + device->tableReference = LUA->ReferenceCreate(); LUA->Pop(); // push base/size properties for lua to have a looksee at ! // ideally these should be handled as metamethods in __index, // but i don't quite feel like making gmod sol2 yet /shrug LUA->ReferencePush(device->tableReference); - LUA->PushNumber(static_cast(base)); - LUA->SetField(-2, "Base"); - LUA->PushNumber(static_cast(base)); - LUA->SetField(-2, "Size"); + LUA->PushNumber(static_cast(base)); + LUA->SetField(-2, "Base"); + LUA->PushNumber(static_cast(base)); + LUA->SetField(-2, "Size"); LUA->Pop(); LUA->PushUserType(device, __lua_typeid); diff --git a/native/projects/lcpu/src/LuaHelpers.hpp b/native/projects/lcpu/src/LuaHelpers.hpp index 20b88ed..78f1fd8 100644 --- a/native/projects/lcpu/src/LuaHelpers.hpp +++ b/native/projects/lcpu/src/LuaHelpers.hpp @@ -4,6 +4,9 @@ #include #include +#include + +#include "GarrysMod/Lua/LuaBase.h" // These are like the official GMOD header LUA_FUNCTION but allow forward declaration // and implementation inside of classes, making writing class bindings that much less @@ -34,7 +37,7 @@ public: \ static int __lua_typeid; \ ACCESS_LEVEL: \ - LUA_MEMBER_FUNCTION(__gc); + LUA_MEMBER_FUNCTION(__gc); // Implement required binding variables (typically in a .cpp file). #define LUA_CLASS_BIND_VARIABLES_IMPLEMENT(T) \ @@ -47,19 +50,18 @@ delete self; \ } \ return 0; \ - } - + } // Begin the Bind() method of a class. This just sets up boilerplate // and required things to setup a class. -#define LUA_CLASS_BIND_BEGIN(T) \ - T::__lua_typeid = LUA->CreateMetaTable(#T); \ - LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \ - LUA->PushNumber(T::__lua_typeid); \ - LUA->SetField(-2, #T "__typeid"); \ - LUA->Pop(); /* pop registry */ \ - LUA->Push(-1); \ - LUA->SetField(-2, "__index"); \ +#define LUA_CLASS_BIND_BEGIN(T) \ + T::__lua_typeid = LUA->CreateMetaTable(#T); \ + LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \ + LUA->PushNumber(T::__lua_typeid); \ + LUA->SetField(-2, #T "__typeid"); \ + LUA->Pop(); /* pop registry */ \ + LUA->Push(-1); \ + LUA->SetField(-2, "__index"); \ LUA_SET_C_FUNCTION(__gc) // End the Bind() method. @@ -75,13 +77,187 @@ LUA->PushCFunction(name); \ LUA->SetField(-2, altName); +namespace lcpu::lua { -inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { - unsigned len{}; - auto ptr = LUA->GetString(stackPos, &len); - if(ptr) { - return std::string(ptr, len); - } else { - return {}; + inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { + unsigned len {}; + auto ptr = LUA->GetString(stackPos, &len); + if(ptr) { + return std::string(ptr, len); + } else { + return {}; + } } -} + + 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 */ + 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); + } + + 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 }; + GarrysMod::Lua::ILuaBase* lua; + }; + + 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); + + if(methods().find(key) != methods().end()) { + return 0; + } + + if(getters().find(key) != getters().end() && setters().find(key) == setters().end()) { + return 0; + } + + if(setters().find(key) != setters().end()) { + 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