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)!
This commit is contained in:
Lily Tsuru 2023-07-27 07:16:39 -04:00
parent 40c539bf22
commit 1b347eecc8
4 changed files with 273 additions and 53 deletions

View File

@ -10,6 +10,22 @@ if SERVER then
return return
end 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 -- rapid iteration requires rapid solutions
--[[ --[[
device = LCPUNative.CreateDevice(0x100000f0, 0x10) device = LCPUNative.CreateDevice(0x100000f0, 0x10)

View File

@ -1,13 +1,46 @@
#include "LcpuGlobals.hpp" #include "LcpuGlobals.hpp"
#include "GarrysMod/Lua/Interface.h" #include "GarrysMod/Lua/Interface.h"
#include "GarrysMod/Lua/LuaBase.h"
#include "LuaCpu.hpp" #include "LuaCpu.hpp"
#include "LuaDevice.hpp" #include "LuaDevice.hpp"
#include "LuaHelpers.hpp" #include "LuaHelpers.hpp"
/// test for the "new" lua object system
struct TestLuaObject : public lcpu::lua::LuaObject<TestLuaObject> {
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_FUNCTION(LCPUNative_CreateCPU) {
LUA->CheckType(1, GarrysMod::Lua::Type::Number); LUA->CheckType(1, GarrysMod::Lua::Type::Number);
auto memorySize = static_cast<u32>(LUA->GetNumber(1)); auto memorySize = static_cast<u32>(LUA->GetNumber(1));
// TODO: There's probably a way to like, ensure a per-player max. // TODO: There's probably a way to like, ensure a per-player max.
if(memorySize > (64 * 1024 * 1024)) if(memorySize > (64 * 1024 * 1024))
@ -20,15 +53,23 @@ LUA_FUNCTION(LCPUNative_CreateCPU) {
LUA_FUNCTION(LCPUNative_CreateDevice) { LUA_FUNCTION(LCPUNative_CreateDevice) {
auto base = LUA->CheckNumber(1); auto base = LUA->CheckNumber(1);
auto size = LUA->CheckNumber(2); auto size = LUA->CheckNumber(2);
lucore::LogInfo("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast<riscv::Address>(base), static_cast<riscv::Address>(size)); lucore::LogInfo("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast<riscv::Address>(base),
static_cast<riscv::Address>(size));
LuaDevice::Create(LUA, static_cast<riscv::Address>(base), static_cast<riscv::Address>(size)); LuaDevice::Create(LUA, static_cast<riscv::Address>(base), static_cast<riscv::Address>(size));
return 1; return 1;
} }
LUA_FUNCTION(LCPUNative_CreateTest) {
TestLuaObject::Create(LUA);
return 1;
}
void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) { void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) {
LuaCpu::Bind(LUA); LuaCpu::Bind(LUA);
LuaDevice::Bind(LUA); LuaDevice::Bind(LUA);
TestLuaObject::RegisterClass(LUA);
// clang-format off // clang-format off
LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB);
LUA->CreateTable(); 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_CreateCPU, "CreateCPU");
LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateDevice, "CreateDevice"); LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateDevice, "CreateDevice");
LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateTest, "CreateTest");
LUA->SetField(-2, "LCPUNative"); LUA->SetField(-2, "LCPUNative");
LUA->Pop(); LUA->Pop();
// clang-format on // clang-format on

View File

@ -9,6 +9,7 @@ bool LuaDevice::Clocked() const {
} }
void LuaDevice::Clock() { void LuaDevice::Clock() {
// clang-format off
LuaState->ReferencePush(tableReference); LuaState->ReferencePush(tableReference);
LuaState->GetField(-1,"Clock"); LuaState->GetField(-1,"Clock");
if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { 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 Clock function off the stack
} }
LuaState->Pop(); // pop the reference LuaState->Pop(); // pop the reference
// clang-format off
} }
riscv::Address LuaDevice::Base() const { riscv::Address LuaDevice::Base() const {
@ -29,15 +31,7 @@ riscv::Address LuaDevice::Size() const {
} }
u32 LuaDevice::Peek(riscv::Address address) { u32 LuaDevice::Peek(riscv::Address address) {
/*if(peekHandlerReference != -1) { // clang-format off
LuaState->ReferencePush(resetHandlerReference);
LuaState->PushNumber(static_cast<double>(address));
LuaState->Call(1, 1);
auto result = LuaState->GetNumber(-1);
LuaState->Pop();
return static_cast<u32>(result);
}*/
LuaState->ReferencePush(tableReference); LuaState->ReferencePush(tableReference);
LuaState->GetField(-1,"Peek"); LuaState->GetField(-1,"Peek");
if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { 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 whatever Peek is off the stack
} }
LuaState->Pop(); // pop the table reference LuaState->Pop(); // pop the table reference
// clang-format on
return 0xffffffff; return 0xffffffff;
} }
void LuaDevice::Poke(riscv::Address address, u32 value) { void LuaDevice::Poke(riscv::Address address, u32 value) {
/*if(pokeHandlerReference != -1) { // clang-format off
LuaState->ReferencePush(pokeHandlerReference);
LuaState->PushNumber(address);
LuaState->PushNumber(value);
LuaState->Call(2, 0);
}*/
LuaState->ReferencePush(tableReference); LuaState->ReferencePush(tableReference);
LuaState->GetField(-1,"Poke"); LuaState->GetField(-1,"Poke");
if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { 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 whatever Peek is
} }
LuaState->Pop(); // pop the table reference LuaState->Pop(); // pop the table reference
// clang-format on
} }
void LuaDevice::Reset() { void LuaDevice::Reset() {
/*if(resetHandlerReference != -1) { // clang-format off
LuaState->ReferencePush(resetHandlerReference);
LuaState->Call(0, 0);
}*/
LuaState->ReferencePush(tableReference); LuaState->ReferencePush(tableReference);
LuaState->GetField(-1,"Reset"); LuaState->GetField(-1,"Reset");
if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) { if(LuaState->GetType(-1) == GarrysMod::Lua::Type::Function) {
@ -91,6 +77,7 @@ void LuaDevice::Reset() {
LuaState->Pop(); // pop whatever reset is LuaState->Pop(); // pop whatever reset is
} }
LuaState->Pop(); // pop the reference LuaState->Pop(); // pop the reference
// clang-format on
} }
LuaDevice::LuaDevice(riscv::Address base, riscv::Address size) : base(base), size(size) { LuaDevice::LuaDevice(riscv::Address base, riscv::Address size) : base(base), size(size) {
@ -104,8 +91,8 @@ LuaDevice::~LuaDevice() {
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __index) { LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __index) {
auto self = LUA_CLASS_GET(LuaDevice)(1); 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 // TODO: before moving this to a shared lua object class thing
// and moving the CPU class to use this way of doing things // and moving the CPU class to use this way of doing things
// I should probably try and like, add stuff to ensure native // 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) { LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, __newindex) {
auto self = LUA_CLASS_GET(LuaDevice)(1); 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 // TODO: This function
// should error on attempt to __newindex any native methods // should error on attempt to __newindex any native methods
// (when moved to a shared place) // (when moved to a shared place)
@ -146,8 +133,6 @@ void LuaDevice::Bind(GarrysMod::Lua::ILuaBase* LUA) {
LUA_SET_C_FUNCTION(__newindex) LUA_SET_C_FUNCTION(__newindex)
LUA_CLASS_BIND_END(); LUA_CLASS_BIND_END();
// clang-format on // clang-format on
} }
void LuaDevice::Create(GarrysMod::Lua::ILuaBase* LUA, riscv::Address base, riscv::Address size) { 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; device->LuaState = LUA;
LUA->CreateTable(); LUA->CreateTable();
device->tableReference = LUA->ReferenceCreate(); device->tableReference = LUA->ReferenceCreate();
LUA->Pop(); LUA->Pop();
// push base/size properties for lua to have a looksee at ! // push base/size properties for lua to have a looksee at !
// ideally these should be handled as metamethods in __index, // ideally these should be handled as metamethods in __index,
// but i don't quite feel like making gmod sol2 yet /shrug // but i don't quite feel like making gmod sol2 yet /shrug
LUA->ReferencePush(device->tableReference); LUA->ReferencePush(device->tableReference);
LUA->PushNumber(static_cast<double>(base)); LUA->PushNumber(static_cast<double>(base));
LUA->SetField(-2, "Base"); LUA->SetField(-2, "Base");
LUA->PushNumber(static_cast<double>(base)); LUA->PushNumber(static_cast<double>(base));
LUA->SetField(-2, "Size"); LUA->SetField(-2, "Size");
LUA->Pop(); LUA->Pop();
LUA->PushUserType(device, __lua_typeid); LUA->PushUserType(device, __lua_typeid);

View File

@ -4,6 +4,9 @@
#include <GarrysMod/Lua/Interface.h> #include <GarrysMod/Lua/Interface.h>
#include <lucore/Logger.hpp> #include <lucore/Logger.hpp>
#include <unordered_map>
#include "GarrysMod/Lua/LuaBase.h"
// These are like the official GMOD header LUA_FUNCTION but allow forward declaration // 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 // and implementation inside of classes, making writing class bindings that much less
@ -34,7 +37,7 @@
public: \ public: \
static int __lua_typeid; \ static int __lua_typeid; \
ACCESS_LEVEL: \ ACCESS_LEVEL: \
LUA_MEMBER_FUNCTION(__gc); LUA_MEMBER_FUNCTION(__gc);
// Implement required binding variables (typically in a .cpp file). // Implement required binding variables (typically in a .cpp file).
#define LUA_CLASS_BIND_VARIABLES_IMPLEMENT(T) \ #define LUA_CLASS_BIND_VARIABLES_IMPLEMENT(T) \
@ -47,19 +50,18 @@
delete self; \ delete self; \
} \ } \
return 0; \ return 0; \
} }
// Begin the Bind() method of a class. This just sets up boilerplate // Begin the Bind() method of a class. This just sets up boilerplate
// and required things to setup a class. // and required things to setup a class.
#define LUA_CLASS_BIND_BEGIN(T) \ #define LUA_CLASS_BIND_BEGIN(T) \
T::__lua_typeid = LUA->CreateMetaTable(#T); \ T::__lua_typeid = LUA->CreateMetaTable(#T); \
LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \ LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \
LUA->PushNumber(T::__lua_typeid); \ LUA->PushNumber(T::__lua_typeid); \
LUA->SetField(-2, #T "__typeid"); \ LUA->SetField(-2, #T "__typeid"); \
LUA->Pop(); /* pop registry */ \ LUA->Pop(); /* pop registry */ \
LUA->Push(-1); \ LUA->Push(-1); \
LUA->SetField(-2, "__index"); \ LUA->SetField(-2, "__index"); \
LUA_SET_C_FUNCTION(__gc) LUA_SET_C_FUNCTION(__gc)
// End the Bind() method. // End the Bind() method.
@ -75,13 +77,187 @@
LUA->PushCFunction(name); \ LUA->PushCFunction(name); \
LUA->SetField(-2, altName); LUA->SetField(-2, altName);
namespace lcpu::lua {
inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) {
unsigned len{}; unsigned len {};
auto ptr = LUA->GetString(stackPos, &len); auto ptr = LUA->GetString(stackPos, &len);
if(ptr) { if(ptr) {
return std::string(ptr, len); return std::string(ptr, len);
} else { } else {
return {}; return {};
}
} }
}
template <class TImpl>
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 <class... Args>
static void Create(GarrysMod::Lua::ILuaBase* LUA, Args&&... args) {
auto ptr = new TImpl(static_cast<Args&&>(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<TImpl>(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<std::string, GarrysMod::Lua::CFunc> methods__;
return methods__;
}
static auto& getters() {
static std::unordered_map<std::string, ILuaFunc> getters__;
return getters__;
}
static auto& setters() {
static std::unordered_map<std::string, ILuaFunc> setters__;
return setters__;
}
// instance stuff
int tableReference { -1 };
GarrysMod::Lua::ILuaBase* lua;
};
template <class TImpl>
int LuaObject<TImpl>::__lua_typeid = 0;
template <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __gc) {
auto self = FromLua(LUA, 1);
if(self != nullptr) {
lucore::LogInfo("GCing LuaObject-based object @ {:p}", static_cast<void*>(self));
delete self;
}
return 0;
}
template <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __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 <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __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