From 7d54e06b06f0cb61c1c389f83c50b158c96d38c5 Mon Sep 17 00:00:00 2001 From: maelstrom Date: Sun, 11 May 2025 10:42:06 +0200 Subject: [PATCH] feat(lua): signals implemented --- autogen/src/object/analysis.cpp | 22 +++++ autogen/src/object/analysis.h | 6 ++ autogen/src/object/codegen.cpp | 29 ++++++ core/src/datatypes/meta.h | 5 +- core/src/datatypes/signal.cpp | 165 +++++++++++++++++++++++++++++++- core/src/datatypes/signal.h | 49 ++++++++-- core/src/objects/annotation.h | 4 + core/src/objects/part.h | 2 +- 8 files changed, 270 insertions(+), 12 deletions(-) diff --git a/autogen/src/object/analysis.cpp b/autogen/src/object/analysis.cpp index d730312..a4e7909 100644 --- a/autogen/src/object/analysis.cpp +++ b/autogen/src/object/analysis.cpp @@ -119,6 +119,27 @@ static void processField(CXCursor cur, ClassAnalysis* state) { }; } +static void processSignal(CXCursor cur, ClassAnalysis* state) { + std::optional signalDef = findAnnotation(cur, "OB::def_signal"); + if (!signalDef) return; + + SignalAnalysis anly; + + auto result = parseAnnotationString(signalDef.value()); + std::string fieldName = x_clang_toString(clang_getCursorDisplayName(cur)); + + anly.name = result["name"]; + anly.sourceFieldName = fieldName; + + // if name field is not provided, use fieldName instead, but capitalize the first character + if (anly.name == "") { + anly.name = fieldName; + anly.name[0] = std::toupper(anly.name[0]); + } + + state->signals.push_back(anly); +} + static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) { ClassAnalysis anly; @@ -159,6 +180,7 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa if (kind != CXCursor_FieldDecl) return CXChildVisit_Continue; processField(cur, &anly); + processSignal(cur, &anly); return CXChildVisit_Continue; }); diff --git a/autogen/src/object/analysis.h b/autogen/src/object/analysis.h index 0fbfea0..c486dc6 100644 --- a/autogen/src/object/analysis.h +++ b/autogen/src/object/analysis.h @@ -36,6 +36,11 @@ struct PropertyAnalysis { PropertyFlags flags = (PropertyFlags)0; }; +struct SignalAnalysis { + std::string name; + std::string sourceFieldName; +}; + // https://stackoverflow.com/a/1448478/16255372 inline ClassFlags operator|(ClassFlags a, ClassFlags b) { return static_cast(static_cast(a) | static_cast(b)); @@ -51,6 +56,7 @@ struct ClassAnalysis { std::string headerPath; bool abstract = false; std::vector properties; + std::vector signals; ClassFlags flags = (ClassFlags)0; std::string explorerIcon; }; diff --git a/autogen/src/object/codegen.cpp b/autogen/src/object/codegen.cpp index 3961b6a..eb0c62f 100644 --- a/autogen/src/object/codegen.cpp +++ b/autogen/src/object/codegen.cpp @@ -146,6 +146,18 @@ static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) { out << "\n }"; first = false; } + + // Handle signals + out << "\n "; + first = true; + for (auto& signal : state.signals) { + out << (first ? "" : " else ") << "if (name == \"" << signal.name << "\") {"; + + out << "\n return Data::Variant(Data::SignalRef(" << signal.sourceFieldName << "));"; + + out << "\n }"; + first = false; + } out << "\n return " << state.baseClass << "::InternalGetPropertyValue(name);"; // out << "\n return MemberNotFound(\"" << state.name << "\", name);"; @@ -199,6 +211,23 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) { out << "\n }"; first = false; } + + // Handle signals + out << "\n "; + first = true; + for (auto& signal : state.signals) { + out << (first ? "" : " else ") << "if (name == \"" << signal.name << "\") {"; + + std::string strFlags; + strFlags += "PROP_READONLY"; + strFlags += "| PROP_HIDDEN"; + strFlags += "| PROP_NOSAVE"; + + out << "\n return PropertyMeta { &SignalRef::TYPE, " << strFlags << " };"; + + out << "\n }"; + first = false; + } out << "\n return " << state.baseClass << "::InternalGetPropertyMeta(name);"; // out << "\n return MemberNotFound(\"" << state.name << "\", name);"; diff --git a/core/src/datatypes/meta.h b/core/src/datatypes/meta.h index dea11b0..f897000 100644 --- a/core/src/datatypes/meta.h +++ b/core/src/datatypes/meta.h @@ -5,6 +5,7 @@ #include "base.h" #include "datatypes/color3.h" #include "datatypes/ref.h" +#include "datatypes/signal.h" #include "vector.h" #include "cframe.h" @@ -26,7 +27,9 @@ namespace Data { Vector3, CFrame, Color3, - InstanceRef + InstanceRef, + SignalRef, + SignalConnectionRef > __VARIANT_TYPE; class Variant { diff --git a/core/src/datatypes/signal.cpp b/core/src/datatypes/signal.cpp index d0e503d..82ee617 100644 --- a/core/src/datatypes/signal.cpp +++ b/core/src/datatypes/signal.cpp @@ -1,7 +1,10 @@ #include "signal.h" +#include "datatypes/base.h" #include "meta.h" #include "lua.h" +#include #include +#include #include SignalSource::SignalSource() {} @@ -67,11 +70,11 @@ void CSignalConnection::Call(std::vector args) { // void Signal::Connect(std::function)> callback) { - connections.push_back(std::dynamic_pointer_cast(std::make_shared(callback))); + connections.push_back(std::dynamic_pointer_cast(std::make_shared(CSignalConnection(callback)))); } void Signal::Connect(lua_State* state) { - connections.push_back(std::dynamic_pointer_cast(std::make_shared(state))); + connections.push_back(std::dynamic_pointer_cast(std::make_shared(LuaSignalConnection(state)))); } void Signal::Fire(std::vector args) { @@ -97,4 +100,162 @@ void SignalConnection::Disconnect() { it++; } parentSignal = {}; +} + +// + +static int signal_gc(lua_State*); +static int signal_index(lua_State*); +static int signal_tostring(lua_State*); +static const struct luaL_Reg signal_metatable [] = { + {"__gc", signal_gc}, + {"__index", signal_index}, + {"__tostring", signal_tostring}, + {NULL, NULL} /* end of array */ +}; + + +static int signal_gc(lua_State* L) { + // Destroy the contained shared_ptr + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signal"); + delete userdata; + lua_pop(L, 1); + + return 0; +} + +// __index(t,k) +static int signal_index(lua_State* L) { + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signal"); + std::weak_ptr signal = *userdata; + std::string key(lua_tostring(L, 2)); + lua_pop(L, 2); + + return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), "Signal"); +} + +Data::SignalRef::SignalRef(std::weak_ptr ref) : signal(ref) {} +Data::SignalRef::~SignalRef() = default; + +const Data::TypeInfo Data::SignalRef::TYPE = { + .name = "Signal", + .fromLuaValue = &Data::SignalRef::FromLuaValue, +}; + +const Data::TypeInfo& Data::SignalRef::GetType() const { return Data::SignalRef::TYPE; }; + +const Data::String Data::SignalRef::ToString() const { + return Data::String("Signal"); +} + +Data::SignalRef::operator std::weak_ptr() { + return signal; +} + +void Data::SignalRef::Serialize(pugi::xml_node node) const { + // Not serializable +} + +void Data::SignalRef::PushLuaValue(lua_State* L) const { + int n = lua_gettop(L); + + auto userdata = (std::weak_ptr*)lua_newuserdata(L, sizeof(void*)); + new(userdata) std::weak_ptr(signal); + + // Create the instance's metatable + luaL_newmetatable(L, "__mt_signal"); + luaL_register(L, NULL, signal_metatable); + + lua_setmetatable(L, n+1); +} + +result Data::SignalRef::FromLuaValue(lua_State* L, int idx) { + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signal"); + lua_pop(L, 1); + return Data::Variant(Data::SignalRef(*userdata)); +} + +static int signal_tostring(lua_State* L) { + lua_pop(L, 1); + lua_pushstring(L, "Signal"); + return 1; +} + +// + +static int signalconnection_gc(lua_State*); +static int signalconnection_index(lua_State*); +static int signalconnection_tostring(lua_State*); +static const struct luaL_Reg signalconnection_metatable [] = { + {"__gc", signalconnection_gc}, + {"__index", signalconnection_index}, + {"__tostring", signalconnection_tostring}, + {NULL, NULL} /* end of array */ +}; + + +static int signalconnection_gc(lua_State* L) { + // Destroy the contained shared_ptr + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signalconnection"); + delete userdata; + lua_pop(L, 1); + + return 0; +} + +// __index(t,k) +static int signalconnection_index(lua_State* L) { + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signalconnection"); + std::weak_ptr signalConnection = *userdata; + std::string key(lua_tostring(L, 2)); + lua_pop(L, 2); + + return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), "SignalConnection"); +} + +Data::SignalConnectionRef::SignalConnectionRef(std::weak_ptr ref) : signalConnection(ref) {} +Data::SignalConnectionRef::~SignalConnectionRef() = default; + +const Data::TypeInfo Data::SignalConnectionRef::TYPE = { + .name = "Signal", + .fromLuaValue = &Data::SignalConnectionRef::FromLuaValue, +}; + +const Data::TypeInfo& Data::SignalConnectionRef::GetType() const { return Data::SignalConnectionRef::TYPE; }; + +const Data::String Data::SignalConnectionRef::ToString() const { + return Data::String("Connection"); +} + +Data::SignalConnectionRef::operator std::weak_ptr() { + return signalConnection; +} + +void Data::SignalConnectionRef::Serialize(pugi::xml_node node) const { + // Not serializable +} + +void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const { + int n = lua_gettop(L); + + auto userdata = (std::weak_ptr*)lua_newuserdata(L, sizeof(void*)); + new(userdata) std::weak_ptr(signalConnection); + + // Create the instance's metatable + luaL_newmetatable(L, "__mt_signalconnection"); + luaL_register(L, NULL, signalconnection_metatable); + + lua_setmetatable(L, n+1); +} + +result Data::SignalConnectionRef::FromLuaValue(lua_State* L, int idx) { + auto userdata = (std::weak_ptr*)luaL_checkudata(L, 1, "__mt_signalconnection"); + lua_pop(L, 1); + return Data::Variant(Data::SignalConnectionRef(*userdata)); +} + +static int signalconnection_tostring(lua_State* L) { + lua_pop(L, 1); + lua_pushstring(L, "SignalConnection"); + return 1; } \ No newline at end of file diff --git a/core/src/datatypes/signal.h b/core/src/datatypes/signal.h index 18f3192..80222bc 100644 --- a/core/src/datatypes/signal.h +++ b/core/src/datatypes/signal.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "datatypes/annotation.h" #include #include #include @@ -18,16 +19,16 @@ class SignalConnection : public std::enable_shared_from_this { protected: std::weak_ptr parentSignal; - virtual ~SignalConnection(); - - virtual void Call(std::vector); + virtual void Call(std::vector) = 0; friend Signal; public: inline bool Connected() { return !parentSignal.expired(); }; void Disconnect(); + + virtual ~SignalConnection(); }; -class CSignalConnection : protected SignalConnection { +class CSignalConnection : public SignalConnection { std::function)> function; CSignalConnection(std::function)>); @@ -37,15 +38,16 @@ protected: void Call(std::vector) override; }; -class LuaSignalConnection : protected SignalConnection { +class LuaSignalConnection : public SignalConnection { lua_State* thread; LuaSignalConnection(lua_State*); - ~LuaSignalConnection(); friend Signal; protected: void Call(std::vector) override; +public: + ~LuaSignalConnection(); }; class Signal { @@ -71,9 +73,40 @@ public: namespace Data { class SignalRef : public Data::Base { std::weak_ptr signal; + + public: + SignalRef(std::weak_ptr); + ~SignalRef(); + + virtual const TypeInfo& GetType() const override; + static const TypeInfo TYPE; + + operator std::weak_ptr(); + + virtual const Data::String ToString() const override; + virtual void Serialize(pugi::xml_node node) const override; + virtual void PushLuaValue(lua_State*) const override; + static result FromLuaValue(lua_State*, int idx); }; class SignalConnectionRef : public Data::Base { - std::weak_ptr signal; + std::weak_ptr signalConnection; + + public: + SignalConnectionRef(std::weak_ptr); + ~SignalConnectionRef(); + + virtual const TypeInfo& GetType() const override; + static const TypeInfo TYPE; + + operator std::weak_ptr(); + + virtual const Data::String ToString() const override; + virtual void Serialize(pugi::xml_node node) const override; + virtual void PushLuaValue(lua_State*) const override; + static result FromLuaValue(lua_State*, int idx); }; -} \ No newline at end of file +} + +using Data::SignalRef; +using Data::SignalConnectionRef; \ No newline at end of file diff --git a/core/src/objects/annotation.h b/core/src/objects/annotation.h index 39f5f29..5d16d4a 100644 --- a/core/src/objects/annotation.h +++ b/core/src/objects/annotation.h @@ -6,12 +6,14 @@ #ifdef __AUTOGEN__ #define def_inst(...) clang::annotate("OB::def_inst", #__VA_ARGS__) #define def_prop(...) clang::annotate("OB::def_prop", #__VA_ARGS__) +#define def_signal(...) clang::annotate("OB::def_signal", #__VA_ARGS__) #define def_prop_category(...) clang::annotate("OB::def_prop_category", #__VA_ARGS__) #define cframe_position_prop(...) clang::annotate("OB::cframe_position_prop", #__VA_ARGS__) #define cframe_rotation_prop(...) clang::annotate("OB::cframe_rotation_prop", #__VA_ARGS__) #else #define def_inst(...) #define def_prop(...) +#define def_signal(...) #define def_prop_category(...) #define cframe_position_prop(...) #define cframe_rotation_prop(...) @@ -26,6 +28,8 @@ #define DEF_INST_SERVICE_(...) [[ def_inst(__VA_ARGS__, service) ]] #define DEF_PROP [[ def_prop() ]] #define DEF_PROP_(...) [[ def_prop(__VA_ARGS__) ]] +#define DEF_SIGNAL [[ def_signal() ]] +#define DEF_SIGNAL_(...) [[ def_signal(__VA_ARGS__) ]] // Categories #define DEF_PROP_CATEGORY(CATEGORY) [[ def_prop_category(category=CATEGORY) ]] diff --git a/core/src/objects/part.h b/core/src/objects/part.h index 89a7c60..95a7d3c 100644 --- a/core/src/objects/part.h +++ b/core/src/objects/part.h @@ -92,7 +92,7 @@ public: DEF_PROP float frontParamB = 0.5; DEF_PROP float backParamB = 0.5; - SignalSource OnParentUpdated; + DEF_SIGNAL SignalSource OnParentUpdated; rp::RigidBody* rigidBody = nullptr;