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
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)

View File

@ -1,10 +1,43 @@
#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<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->CheckType(1, GarrysMod::Lua::Type::Number);
auto memorySize = static_cast<u32>(LUA->GetNumber(1));
@ -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<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));
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

View File

@ -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<double>(address));
LuaState->Call(1, 1);
auto result = LuaState->GetNumber(-1);
LuaState->Pop();
return static_cast<u32>(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,7 +91,7 @@ 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
@ -118,7 +105,7 @@ 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.
// TODO: This function
@ -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) {

View File

@ -4,6 +4,9 @@
#include <GarrysMod/Lua/Interface.h>
#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
// and implementation inside of classes, making writing class bindings that much less
@ -49,7 +52,6 @@
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) \
@ -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{};
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 <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