refactor(datatypes): generified types allowing a parameter to be specified for enum type and instance type. Also made deserialization fallible

This commit is contained in:
maelstrom 2025-06-03 00:49:29 +02:00
parent 5149e34723
commit 10d69ce7ac
28 changed files with 232 additions and 124 deletions

View file

@ -174,6 +174,25 @@ static bool hasMethod(CXCursor cur, std::string methodName) {
return found; return found;
} }
static bool hasGenericMethod(CXCursor cur, std::string methodName) {
bool found = false;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_CXXMethod) return CXChildVisit_Continue;
if (x_clang_toString(clang_getCursorSpelling(cur)) != methodName) return CXChildVisit_Continue;
int numArgs = clang_Cursor_getNumArguments(cur);
CXCursor lastParam = clang_Cursor_getArgument(cur, numArgs - 1);
std::string lastParamType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(lastParam)));
if (lastParamType != "const TypeMeta") return CXChildVisit_Continue;
found = true;
return CXChildVisit_Break;
});
return found;
}
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) { static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly; ClassAnalysis anly;
@ -184,6 +203,8 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
anly.serializedName = result["name"]; anly.serializedName = result["name"];
anly.hasFromString = hasMethod(cur, "FromString"); anly.hasFromString = hasMethod(cur, "FromString");
anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize"); anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize");
anly.hasGenericDeserializer = hasGenericMethod(cur, "Deserialize");
anly.hasGenericFromString = hasGenericMethod(cur, "FromString");
if (anly.serializedName == "") if (anly.serializedName == "")
anly.serializedName = className; anly.serializedName = className;

View file

@ -37,6 +37,8 @@ struct ClassAnalysis {
std::string headerPath; std::string headerPath;
bool hasFromString; bool hasFromString;
bool isSerializable; bool isSerializable;
bool hasGenericDeserializer;
bool hasGenericFromString;
std::vector<PropertyAnalysis> properties; std::vector<PropertyAnalysis> properties;
std::vector<MethodAnalysis> methods; std::vector<MethodAnalysis> methods;
std::vector<PropertyAnalysis> staticProperties; std::vector<PropertyAnalysis> staticProperties;

View file

@ -238,13 +238,13 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) { static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = state.name; std::string fqn = state.name;
out << "static int data_gc(lua_State*);\n" out << "static int data_" << state.name << "_gc(lua_State*);\n"
"static int data_index(lua_State*);\n" "static int data_" << state.name << "_index(lua_State*);\n"
"static int data_tostring(lua_State*);\n" "static int data_" << state.name << "_tostring(lua_State*);\n"
"static const struct luaL_Reg metatable [] = {\n" "static const struct luaL_Reg " << state.name << "_metatable [] = {\n"
" {\"__gc\", data_gc},\n" " {\"__gc\", data_" << state.name << "_gc},\n"
" {\"__index\", data_index},\n" " {\"__index\", data_" << state.name << "_index},\n"
" {\"__tostring\", data_tostring},\n" " {\"__tostring\", data_" << state.name << "_tostring},\n"
" {NULL, NULL} /* end of array */\n" " {NULL, NULL} /* end of array */\n"
"};\n\n"; "};\n\n";
@ -256,7 +256,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
" // Create the library's metatable\n" " // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_" << state.name << "\");\n" " luaL_newmetatable(L, \"__mt_" << state.name << "\");\n"
" luaL_register(L, NULL, metatable);\n" " luaL_register(L, NULL, " << state.name << "_metatable);\n"
" lua_setmetatable(L, n+1);\n" " lua_setmetatable(L, n+1);\n"
"}\n\n"; "}\n\n";
@ -271,7 +271,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// Indexing methods and properties // Indexing methods and properties
out << "static int data_index(lua_State* L) {\n" out << "static int data_" << state.name << "_index(lua_State* L) {\n"
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
"\n" "\n"
" std::string key(lua_tostring(L, 2));\n" " std::string key(lua_tostring(L, 2));\n"
@ -323,7 +323,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// ToString // ToString
out << "\nint data_tostring(lua_State* L) {\n" out << "\nint data_" << state.name << "_tostring(lua_State* L) {\n"
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
" lua_pushstring(L, std::string(this_->ToString()).c_str());\n" " lua_pushstring(L, std::string(this_->ToString()).c_str());\n"
" return 1;\n" " return 1;\n"
@ -331,7 +331,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// Destructor // Destructor
out << "\nint data_gc(lua_State* L) {\n" out << "\nint data_" << state.name << "_gc(lua_State* L) {\n"
" " << fqn << "** userdata = (" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "** userdata = (" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
" delete *userdata;\n" " delete *userdata;\n"
" return 0;\n" " return 0;\n"
@ -341,9 +341,12 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) { static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = state.name; std::string fqn = state.name;
out << "static int lib_index(lua_State*);\n" // If there are no static methods or properties, no need to create a library
"static const struct luaL_Reg lib_metatable [] = {\n" if (state.staticMethods.size() == 0 && state.staticProperties.size() == 0) return;
" {\"__index\", lib_index},\n"
out << "static int lib_" << state.name << "_index(lua_State*);\n"
"static const struct luaL_Reg lib_" << state.name << "_metatable [] = {\n"
" {\"__index\", lib_" << state.name << "_index},\n"
" {NULL, NULL} /* end of array */\n" " {NULL, NULL} /* end of array */\n"
"};\n\n"; "};\n\n";
@ -355,7 +358,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
"\n" "\n"
" // Create the library's metatable\n" " // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n" " luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n"
" luaL_register(L, NULL, lib_metatable);\n" " luaL_register(L, NULL, lib_" << state.name << "_metatable);\n"
" lua_setmetatable(L, -2);\n" " lua_setmetatable(L, -2);\n"
"\n" "\n"
" lua_rawset(L, -3);\n" " lua_rawset(L, -3);\n"
@ -364,7 +367,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
// Indexing methods and properties // Indexing methods and properties
out << "static int lib_index(lua_State* L) {\n" out << "static int lib_" << state.name << "_index(lua_State* L) {\n"
" std::string key(lua_tostring(L, 2));\n" " std::string key(lua_tostring(L, 2));\n"
" lua_pop(L, 2);\n" " lua_pop(L, 2);\n"
"\n"; "\n";
@ -418,14 +421,24 @@ void data::writeCodeForClass(std::ofstream& out, std::string headerPath, ClassAn
out << "#include \"datatypes/variant.h\"\n"; out << "#include \"datatypes/variant.h\"\n";
out << "#include <pugixml.hpp>\n"; out << "#include <pugixml.hpp>\n";
out << "#include \"lua.h\"\n\n"; out << "#include \"lua.h\"\n\n";
out << "const TypeDescriptor " << state.name << "::TYPE = {\n" out << "const TypeDesc " << state.name << "::TYPE = {\n"
<< " .name = \"" << state.serializedName << "\",\n" << " .name = \"" << state.serializedName << "\",\n";
<< " .serializer = toVariantFunction(&" << state.name << "::Serialize)," if (state.isSerializable) {
<< " .deserializer = toVariantGenerator(&" << state.name << "::Deserialize),\n" out << " .serialize = toVariantFunction(&" << state.name << "::Serialize),";
<< " .toString = toVariantFunction(&" << state.name << "::ToString),"; if (state.hasGenericDeserializer)
if (state.hasFromString) out << " .fromString = &" << state.name << "::FromString,\n"; out << " .deserialize = toVariantGenerator(&" << state.name << "::Deserialize),\n";
else
out << " .deserialize = toVariantGeneratorNoMeta(&" << state.name << "::Deserialize),\n";
}
out << " .toString = toVariantFunction(&" << state.name << "::ToString),";
if (state.hasFromString) {
if (state.hasGenericFromString)
out << " .fromString = toVariantGenerator(&" << state.name << "::FromString),\n";
else
out << " .fromString = toVariantGeneratorNoMeta(&" << state.name << "::FromString),\n";
}
out << " .pushLuaValue = toVariantFunction(&" << state.name << "::PushLuaValue)," out << " .pushLuaValue = toVariantFunction(&" << state.name << "::PushLuaValue),"
<< " .fromLuaValue = &" << state.name << "::FromLuaValue,\n" << " .fromLuaValue = toVariantGenerator(&" << state.name << "::FromLuaValue),\n"
<< "};\n\n"; << "};\n\n";
writeLuaMethodImpls(out, state); writeLuaMethodImpls(out, state);

View file

@ -26,7 +26,7 @@
#define AUTOGEN_PREAMBLE_DATA \ #define AUTOGEN_PREAMBLE_DATA \
public: \ public: \
static const TypeDescriptor TYPE; \ static const TypeDesc TYPE; \
virtual void PushLuaValue(lua_State*) const; \ virtual void PushLuaValue(lua_State*) const; \
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \ static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
private: private:

View file

@ -11,17 +11,20 @@ extern "C" { typedef struct lua_State lua_State; }
namespace pugi { class xml_node; }; namespace pugi { class xml_node; };
class Variant; class Variant;
typedef std::function<void(Variant, pugi::xml_node)> Serializer; struct TypeMeta;
typedef std::function<Variant(pugi::xml_node)> Deserializer;
typedef std::function<void(Variant, pugi::xml_node)> Serialize;
typedef std::function<result<Variant, DataParseError>(pugi::xml_node, const TypeMeta)> Deserialize;
typedef std::function<std::string(Variant)> ToString; typedef std::function<std::string(Variant)> ToString;
typedef std::function<std::optional<Variant>(std::string)> FromString; typedef std::function<result<Variant, DataParseError>(std::string, const TypeMeta)> FromString;
typedef std::function<result<Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue; typedef std::function<result<Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
typedef std::function<void(Variant self, lua_State*)> PushLuaValue; typedef std::function<void(Variant self, lua_State*)> PushLuaValue;
struct TypeDescriptor { // Describes a concrete type
struct TypeDesc {
std::string name; std::string name;
Serializer serializer; Serialize serialize;
Deserializer deserializer; Deserialize deserialize;
ToString toString; ToString toString;
FromString fromString; FromString fromString;
PushLuaValue pushLuaValue; PushLuaValue pushLuaValue;
@ -29,11 +32,17 @@ struct TypeDescriptor {
}; };
class Enum; class Enum;
struct InstanceType;
struct TypeInfo { // Describes a meta-type, which consists of a concrete type, and some generic argument.
const TypeDescriptor* descriptor; struct TypeMeta {
Enum* enum_; const TypeDesc* descriptor;
union {
Enum* enum_; // Applicable for EnumItem
InstanceType* instType; // Applicable for InstanceRef
};
inline TypeInfo(const TypeDescriptor* descriptor) : descriptor(descriptor) {} inline TypeMeta(const TypeDesc* descriptor) : descriptor(descriptor) {}
TypeInfo(Enum*); TypeMeta(Enum*);
TypeMeta(InstanceType*);
}; };

View file

@ -1,5 +1,6 @@
#include "cframe.h" #include "cframe.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "error/data.h"
#include "physics/util.h" #include "physics/util.h"
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp> #include <glm/gtc/matrix_inverse.hpp>
@ -134,7 +135,7 @@ void CFrame::Serialize(pugi::xml_node node) const {
} }
CFrame CFrame::Deserialize(pugi::xml_node node) { result<CFrame, DataParseError> CFrame::Deserialize(pugi::xml_node node) {
return CFrame( return CFrame(
node.child("X").text().as_float(), node.child("X").text().as_float(),
node.child("Y").text().as_float(), node.child("Y").text().as_float(),

View file

@ -3,6 +3,7 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "error/data.h"
#include <glm/ext/quaternion_float.hpp> #include <glm/ext/quaternion_float.hpp>
#include <glm/gtc/matrix_access.hpp> #include <glm/gtc/matrix_access.hpp>
#include <glm/matrix.hpp> #include <glm/matrix.hpp>
@ -40,7 +41,7 @@ public:
virtual const std::string ToString() const; virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node parent) const; virtual void Serialize(pugi::xml_node parent) const;
static CFrame Deserialize(pugi::xml_node node); static result<CFrame, DataParseError> Deserialize(pugi::xml_node node);
static void PushLuaLibrary(lua_State*); static void PushLuaLibrary(lua_State*);

View file

@ -1,5 +1,6 @@
#include "color3.h" #include "color3.h"
#include "datatypes/variant.h" #include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
@ -39,6 +40,6 @@ void Color3::Serialize(pugi::xml_node node) const {
node.text().set(this->ToHex()); node.text().set(this->ToHex());
} }
Color3 Color3::Deserialize(pugi::xml_node node) { result<Color3, DataParseError> Color3::Deserialize(pugi::xml_node node) {
return Color3::FromHex(node.text().get()); return Color3::FromHex(node.text().get());
} }

View file

@ -2,6 +2,7 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp> #include <glm/ext/vector_float3.hpp>
class DEF_DATA Color3 { class DEF_DATA Color3 {
@ -21,7 +22,7 @@ public:
virtual const std::string ToString() const; virtual const std::string ToString() const;
DEF_DATA_METHOD std::string ToHex() const; DEF_DATA_METHOD std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const; virtual void Serialize(pugi::xml_node node) const;
static Color3 Deserialize(pugi::xml_node node); static result<Color3, DataParseError> Deserialize(pugi::xml_node node);
static void PushLuaLibrary(lua_State*); static void PushLuaLibrary(lua_State*);

View file

@ -1,8 +1,10 @@
#include "enum.h" #include "enum.h"
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/variant.h" #include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp>
TypeInfo::TypeInfo(Enum* enum_) : enum_(enum_), descriptor(&EnumItem::TYPE) {} TypeMeta::TypeMeta(Enum* enum_) : enum_(enum_), descriptor(&EnumItem::TYPE) {}
Enum::Enum(_EnumData* data) : data(data) {} Enum::Enum(_EnumData* data) : data(data) {}
@ -36,24 +38,29 @@ EnumItem::EnumItem(_EnumData* parentData, std::string name, int value) : parentD
// //
std::string Enum::ToString() { std::string Enum::ToString() const {
return "Enum." + this->data->name; return "Enum." + this->data->name;
} }
void Enum::PushLuaValue(lua_State*) { //
std::string EnumItem::ToString() const {
return "Enum." + parentData->name + "." + name;
} }
Variant Enum::FromLuaValue(lua_State*, int) { void EnumItem::Serialize(pugi::xml_node node) const {
node.set_name("token");
node.text().set(value);
} }
const TypeDescriptor Enum::TYPE { result<EnumItem, DataParseError> EnumItem::Deserialize(pugi::xml_node node, const TypeMeta info) {
.name = "Enum", auto result = info.enum_->FromValue(node.text().as_int());
.toString = toVariantFunction(&Enum::ToString), if (result.has_value()) return result.value();
// .fromString = Enum_FromString, return DataParseError(node.text().as_string(), "EnumItem");
// .pushLuaValue = &Enum_PushLuaValue, }
.fromLuaValue = toVariantGenerator(Enum::FromLuaValue),
// .serializer = Enum_Serialize, result<EnumItem, DataParseError> EnumItem::FromString(std::string string, const TypeMeta info) {
// .deserializer = Enum_Deserialize, auto result = info.enum_->FromName(string);
}; if (result.has_value()) return result.value();
return DataParseError(string, "EnumItem");
}

View file

@ -4,6 +4,8 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include "datatypes/annotation.h"
#include "error/data.h"
#include "lua.h" #include "lua.h"
struct _EnumData { struct _EnumData {
@ -14,33 +16,40 @@ struct _EnumData {
class EnumItem; class EnumItem;
class Enum { class DEF_DATA Enum {
_EnumData* data; _EnumData* data;
public: public:
Enum(_EnumData*); Enum(_EnumData*);
static const TypeDescriptor TYPE; static const TypeDesc TYPE;
inline _EnumData* InternalType() const { return this->data; }; inline _EnumData* InternalType() const { return this->data; };
std::vector<EnumItem> GetEnumItems() const; std::vector<EnumItem> GetEnumItems() const;
std::optional<EnumItem> FromName(std::string) const; std::optional<EnumItem> FromName(std::string) const;
std::optional<EnumItem> FromValue(int) const; std::optional<EnumItem> FromValue(int) const;
std::string ToString(); std::string ToString() const;
void PushLuaValue(lua_State*); void PushLuaValue(lua_State*) const;
static Variant FromLuaValue(lua_State*, int); static result<Variant, LuaCastError> FromLuaValue(lua_State*, int);
}; };
class EnumItem { class DEF_DATA EnumItem {
_EnumData* parentData; _EnumData* parentData;
std::string name; std::string name;
int value; int value;
public: public:
EnumItem(_EnumData*, std::string, int); EnumItem(_EnumData*, std::string, int);
static const TypeDescriptor TYPE; static const TypeDesc TYPE;
inline std::string Name() const { return this->name; } inline std::string Name() const { return this->name; }
inline int Value() const { return this->value; } inline int Value() const { return this->value; }
inline Enum EnumType() const { return Enum(this->parentData); } inline Enum EnumType() const { return Enum(this->parentData); }
static result<EnumItem, DataParseError> FromString(std::string, const TypeMeta);
std::string ToString() const;
void Serialize(pugi::xml_node) const;
static result<EnumItem, DataParseError> Deserialize(pugi::xml_node, const TypeMeta);
void PushLuaValue(lua_State*) const;
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int);
}; };

View file

@ -1,4 +1,5 @@
#include "primitives.h" #include "primitives.h"
#include "error/data.h"
#include "variant.h" #include "variant.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include "lua.h" #include "lua.h"
@ -10,7 +11,7 @@ void Null_Serialize(Variant self, pugi::xml_node node) {
node.text().set("null"); node.text().set("null");
} }
Variant Null_Deserialize(pugi::xml_node node) { result<Variant, DataParseError> Null_Deserialize(pugi::xml_node node, const TypeMeta) {
return std::monostate(); return std::monostate();
} }
@ -18,7 +19,7 @@ const std::string Null_ToString(Variant self) {
return "null"; return "null";
} }
const std::optional<Variant> Null_FromString(std::string str) { result<Variant, DataParseError> Null_FromString(std::string string, const TypeMeta) {
return std::monostate(); return std::monostate();
} }
@ -30,7 +31,7 @@ result<Variant, LuaCastError> Null_FromLuaValue(lua_State* L, int idx) {
return Variant(std::monostate()); return Variant(std::monostate());
} }
const TypeDescriptor NULL_TYPE { const TypeDesc NULL_TYPE {
"null", "null",
Null_Serialize, Null_Serialize,
Null_Deserialize, Null_Deserialize,
@ -48,7 +49,7 @@ void Bool_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<bool>() ? "true" : "false"); node.text().set(self.get<bool>() ? "true" : "false");
} }
Variant Bool_Deserialize(pugi::xml_node node) { result<Variant, DataParseError> Bool_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_bool(); return node.text().as_bool();
} }
@ -56,7 +57,7 @@ const std::string Bool_ToString(Variant self) {
return self.get<bool>() ? "true" : "false"; return self.get<bool>() ? "true" : "false";
} }
const std::optional<Variant> Bool_FromString(std::string string) { result<Variant, DataParseError> Bool_FromString(std::string string, const TypeMeta) {
return string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y'; return string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y';
} }
@ -70,7 +71,7 @@ result<Variant, LuaCastError> Bool_FromLuaValue(lua_State* L, int idx) {
return Variant(lua_toboolean(L, idx)); return Variant(lua_toboolean(L, idx));
} }
const TypeDescriptor BOOL_TYPE { const TypeDesc BOOL_TYPE {
"bool", "bool",
Bool_Serialize, Bool_Serialize,
Bool_Deserialize, Bool_Deserialize,
@ -88,7 +89,7 @@ void Int_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<int>()); node.text().set(self.get<int>());
} }
Variant Int_Deserialize(pugi::xml_node node) { result<Variant, DataParseError> Int_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_int(); return node.text().as_int();
} }
@ -96,10 +97,10 @@ const std::string Int_ToString(Variant self) {
return std::to_string(self.get<int>()); return std::to_string(self.get<int>());
} }
const std::optional<Variant> Int_FromString(std::string string) { result<Variant, DataParseError> Int_FromString(std::string string, const TypeMeta) {
char* endPos; char* endPos;
int value = (int)std::strtol(string.c_str(), &endPos, 10); int value = (int)std::strtol(string.c_str(), &endPos, 10);
if (endPos == string.c_str()) return std::nullopt; if (endPos == string.c_str()) return DataParseError(string, "int");
return value; return value;
} }
@ -113,7 +114,7 @@ result<Variant, LuaCastError> Int_FromLuaValue(lua_State* L, int idx) {
return Variant((int)lua_tonumber(L, idx)); return Variant((int)lua_tonumber(L, idx));
} }
const TypeDescriptor INT_TYPE { const TypeDesc INT_TYPE {
"int", "int",
Int_Serialize, Int_Serialize,
Int_Deserialize, Int_Deserialize,
@ -131,7 +132,7 @@ void Float_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<float>()); node.text().set(self.get<float>());
} }
Variant Float_Deserialize(pugi::xml_node node) { result<Variant, DataParseError> Float_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_float(); return node.text().as_float();
} }
@ -141,10 +142,10 @@ const std::string Float_ToString(Variant self) {
return stream.str(); return stream.str();
} }
const std::optional<Variant> Float_FromString(std::string string) { result<Variant, DataParseError> Float_FromString(std::string string, const TypeMeta) {
char* endPos; char* endPos;
float value = std::strtof(string.c_str(), &endPos); float value = std::strtof(string.c_str(), &endPos);
if (endPos == string.c_str()) return std::nullopt; if (endPos == string.c_str()) return DataParseError(string, "float");
return value; return value;
} }
@ -158,7 +159,7 @@ result<Variant, LuaCastError> Float_FromLuaValue(lua_State* L, int idx) {
return Variant((float)lua_tonumber(L, idx)); return Variant((float)lua_tonumber(L, idx));
} }
const TypeDescriptor FLOAT_TYPE { const TypeDesc FLOAT_TYPE {
"float", "float",
Float_Serialize, Float_Serialize,
Float_Deserialize, Float_Deserialize,
@ -176,7 +177,7 @@ void String_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<std::string>()); node.text().set(self.get<std::string>());
} }
Variant String_Deserialize(pugi::xml_node node) { result<Variant, DataParseError> String_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_string(); return node.text().as_string();
} }
@ -184,7 +185,7 @@ const std::string String_ToString(Variant self) {
return self.get<std::string>(); return self.get<std::string>();
} }
const std::optional<Variant> String_FromString(std::string string) { result<Variant, DataParseError> String_FromString(std::string string, const TypeMeta) {
return string; return string;
} }
@ -198,7 +199,7 @@ result<Variant, LuaCastError> String_FromLuaValue(lua_State* L, int idx) {
return Variant(lua_tostring(L, idx)); return Variant(lua_tostring(L, idx));
} }
const TypeDescriptor STRING_TYPE { const TypeDesc STRING_TYPE {
"string", "string",
String_Serialize, String_Serialize,
String_Deserialize, String_Deserialize,

View file

@ -2,8 +2,8 @@
#include "base.h" #include "base.h"
extern const TypeDescriptor NULL_TYPE; extern const TypeDesc NULL_TYPE;
extern const TypeDescriptor BOOL_TYPE; extern const TypeDesc BOOL_TYPE;
extern const TypeDescriptor INT_TYPE; extern const TypeDesc INT_TYPE;
extern const TypeDescriptor FLOAT_TYPE; extern const TypeDesc FLOAT_TYPE;
extern const TypeDescriptor STRING_TYPE; extern const TypeDesc STRING_TYPE;

View file

@ -10,14 +10,16 @@
#include "objects/base/member.h" #include "objects/base/member.h"
#include <pugixml.hpp> #include <pugixml.hpp>
TypeMeta::TypeMeta(InstanceType* instType) : instType(instType), descriptor(&InstanceRef::TYPE) {}
InstanceRef::InstanceRef() {}; InstanceRef::InstanceRef() {};
InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {}; InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
InstanceRef::~InstanceRef() = default; InstanceRef::~InstanceRef() = default;
const TypeDescriptor InstanceRef::TYPE = { const TypeDesc InstanceRef::TYPE = {
.name = "Ref", .name = "Ref",
.serializer = toVariantFunction(&InstanceRef::Serialize), .serialize = toVariantFunction(&InstanceRef::Serialize),
.deserializer = &InstanceRef::Deserialize, .deserialize = toVariantGeneratorNoMeta(&InstanceRef::Deserialize),
.toString = toVariantFunction(&InstanceRef::ToString), .toString = toVariantFunction(&InstanceRef::ToString),
.fromString = nullptr, .fromString = nullptr,
.pushLuaValue = toVariantFunction(&InstanceRef::PushLuaValue), .pushLuaValue = toVariantFunction(&InstanceRef::PushLuaValue),
@ -39,7 +41,7 @@ void InstanceRef::Serialize(pugi::xml_node node) const {
panic(); panic();
} }
InstanceRef InstanceRef::Deserialize(pugi::xml_node node) { result<InstanceRef, DataParseError> InstanceRef::Deserialize(pugi::xml_node node) {
// Handled by Instance // Handled by Instance
panic(); panic();
} }

View file

@ -13,13 +13,13 @@ public:
InstanceRef(std::weak_ptr<Instance>); InstanceRef(std::weak_ptr<Instance>);
~InstanceRef(); ~InstanceRef();
static const TypeDescriptor TYPE; static const TypeDesc TYPE;
operator std::weak_ptr<Instance>(); operator std::weak_ptr<Instance>();
virtual const std::string ToString() const; virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const; virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const; virtual void PushLuaValue(lua_State*) const;
static InstanceRef Deserialize(pugi::xml_node node); static result<InstanceRef, DataParseError> Deserialize(pugi::xml_node node);
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx); static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
}; };

View file

@ -225,7 +225,7 @@ static const struct luaL_Reg signal_metatable [] = {
SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {} SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
SignalRef::~SignalRef() = default; SignalRef::~SignalRef() = default;
const TypeDescriptor SignalRef::TYPE = { const TypeDesc SignalRef::TYPE = {
.name = "Signal", .name = "Signal",
.toString = toVariantFunction(&SignalRef::ToString), .toString = toVariantFunction(&SignalRef::ToString),
.pushLuaValue = toVariantFunction(&SignalRef::PushLuaValue), .pushLuaValue = toVariantFunction(&SignalRef::PushLuaValue),
@ -346,7 +346,7 @@ static const struct luaL_Reg signalconnection_metatable [] = {
SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {} SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
SignalConnectionRef::~SignalConnectionRef() = default; SignalConnectionRef::~SignalConnectionRef() = default;
const TypeDescriptor SignalConnectionRef::TYPE = { const TypeDesc SignalConnectionRef::TYPE = {
.name = "Signal", .name = "Signal",
.toString = toVariantFunction(&SignalConnectionRef::ToString), .toString = toVariantFunction(&SignalConnectionRef::ToString),
.pushLuaValue = toVariantFunction(&SignalConnectionRef::PushLuaValue), .pushLuaValue = toVariantFunction(&SignalConnectionRef::PushLuaValue),

View file

@ -112,7 +112,7 @@ public:
SignalRef(std::weak_ptr<Signal>); SignalRef(std::weak_ptr<Signal>);
~SignalRef(); ~SignalRef();
static const TypeDescriptor TYPE; static const TypeDesc TYPE;
operator std::weak_ptr<Signal>(); operator std::weak_ptr<Signal>();
@ -129,7 +129,7 @@ public:
SignalConnectionRef(std::weak_ptr<SignalConnection>); SignalConnectionRef(std::weak_ptr<SignalConnection>);
~SignalConnectionRef(); ~SignalConnectionRef();
static const TypeDescriptor TYPE; static const TypeDesc TYPE;
operator std::weak_ptr<SignalConnection>(); operator std::weak_ptr<SignalConnection>();

View file

@ -6,6 +6,7 @@
#include "datatypes/ref.h" #include "datatypes/ref.h"
#include "datatypes/signal.h" #include "datatypes/signal.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "error/data.h"
#include "logger.h" #include "logger.h"
#include "panic.h" #include "panic.h"
#include <pugixml.hpp> #include <pugixml.hpp>
@ -20,7 +21,7 @@
#endif #endif
} }
const TypeDescriptor* VARIANT_TYPES[] { const TypeDesc* VARIANT_TYPES[] {
&NULL_TYPE, &NULL_TYPE,
&BOOL_TYPE, &BOOL_TYPE,
&INT_TYPE, &INT_TYPE,
@ -32,15 +33,15 @@ const TypeDescriptor* VARIANT_TYPES[] {
&InstanceRef::TYPE, &InstanceRef::TYPE,
&SignalRef::TYPE, &SignalRef::TYPE,
&SignalConnectionRef::TYPE, &SignalConnectionRef::TYPE,
// &Enum::TYPE, &Enum::TYPE,
// &EnumItem::TYPE, &EnumItem::TYPE,
}; };
const TypeInfo Variant::GetTypeInfo() const { const TypeMeta Variant::GetTypeMeta() const {
return VARIANT_TYPES[wrapped.index()]; return VARIANT_TYPES[wrapped.index()];
} }
const TypeDescriptor* Variant::GetType() const { const TypeDesc* Variant::GetType() const {
return VARIANT_TYPES[wrapped.index()]; return VARIANT_TYPES[wrapped.index()];
} }
@ -55,7 +56,7 @@ void Variant::Serialize(pugi::xml_node node) const {
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) { if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
Logger::fatalErrorf("Data type %s does not implement serializer", VARIANT_TYPES[wrapped.index()]->name.c_str()); Logger::fatalErrorf("Data type %s does not implement serializer", VARIANT_TYPES[wrapped.index()]->name.c_str());
} }
VARIANT_TYPES[wrapped.index()]->serializer(*this, node); VARIANT_TYPES[wrapped.index()]->serialize(*this, node);
} }
void Variant::PushLuaValue(lua_State* state) const { void Variant::PushLuaValue(lua_State* state) const {
@ -65,14 +66,15 @@ void Variant::PushLuaValue(lua_State* state) const {
VARIANT_TYPES[wrapped.index()]->pushLuaValue(*this, state); VARIANT_TYPES[wrapped.index()]->pushLuaValue(*this, state);
} }
Variant Variant::Deserialize(pugi::xml_node node, const TypeInfo type) { result<Variant, DataParseError> Variant::Deserialize(pugi::xml_node node, const TypeMeta type) {
if (!type.descriptor->deserializer) { if (!type.descriptor->deserialize) {
Logger::fatalErrorf("Data type %s does not implement deserialize", type.descriptor->name.c_str()); Logger::fatalErrorf("Data type %s does not implement deserialize", type.descriptor->name.c_str());
return DataParseError(node.text().as_string(), type.descriptor->name);
} }
return type.descriptor->deserializer(node); return type.descriptor->deserialize(node, type);
} }
std::map<std::string, const TypeDescriptor*> TYPE_MAP = { std::map<std::string, const TypeDesc*> TYPE_MAP = {
{ "null", &NULL_TYPE }, { "null", &NULL_TYPE },
{ "bool", &BOOL_TYPE }, { "bool", &BOOL_TYPE },
{ "int", &INT_TYPE }, { "int", &INT_TYPE },
@ -82,4 +84,5 @@ std::map<std::string, const TypeDescriptor*> TYPE_MAP = {
{ "CoordinateFrame", &CFrame::TYPE }, { "CoordinateFrame", &CFrame::TYPE },
{ "Color3", &Color3::TYPE }, { "Color3", &Color3::TYPE },
{ "Ref", &InstanceRef::TYPE }, { "Ref", &InstanceRef::TYPE },
{ "token", &EnumItem::TYPE },
}; };

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <type_traits>
#include <variant> #include <variant>
#include <map> #include <map>
#include "base.h" #include "base.h"
@ -7,6 +8,7 @@
#include "datatypes/enum.h" #include "datatypes/enum.h"
#include "datatypes/ref.h" #include "datatypes/ref.h"
#include "datatypes/signal.h" #include "datatypes/signal.h"
#include "error/data.h"
#include "vector.h" #include "vector.h"
#include "cframe.h" #include "cframe.h"
@ -37,16 +39,16 @@ typedef std::variant<
class Variant { class Variant {
__VARIANT_TYPE wrapped; __VARIANT_TYPE wrapped;
public: public:
template <typename T> Variant(T obj) : wrapped(obj) {} template <typename T, std::enable_if_t<std::is_constructible_v<__VARIANT_TYPE, T>, int> = 0> Variant(T obj) : wrapped(obj) {}
template <typename T> T get() { return std::get<T>(wrapped); } template <typename T, std::enable_if_t<std::is_constructible_v<__VARIANT_TYPE, T>, int> = 0> T get() { return std::get<T>(wrapped); }
std::string ToString() const; std::string ToString() const;
const TypeInfo GetTypeInfo() const; const TypeMeta GetTypeMeta() const;
const TypeDescriptor* GetType() const; const TypeDesc* GetType() const;
void Serialize(pugi::xml_node node) const; void Serialize(pugi::xml_node node) const;
void PushLuaValue(lua_State* state) const; void PushLuaValue(lua_State* state) const;
static Variant Deserialize(pugi::xml_node node, const TypeInfo); static result<Variant, DataParseError> Deserialize(pugi::xml_node node, const TypeMeta);
}; };
template <typename T, typename R, typename ...Args> template <typename T, typename R, typename ...Args>
@ -70,5 +72,23 @@ std::function<Variant(Args...)> toVariantGenerator(T(f)(Args...)) {
}; };
} }
template <typename T, typename ...Args, typename ...E>
std::function<result<Variant, E...>(Args...)> toVariantGenerator(result<T, E...>(f)(Args...)) {
return [f](Args... args) -> result<Variant, E...> {
auto result = f(args...);
if (result.isSuccess()) return (Variant)(result.success().value());
return result.error().value();
};
}
template <typename T, typename ...Args, typename ...E>
std::function<result<Variant, E...>(Args..., const TypeMeta)> toVariantGeneratorNoMeta(result<T, E...>(f)(Args...)) {
return [f](Args... args, const TypeMeta) -> result<Variant, E...> {
auto result = f(args...);
if (result.isSuccess()) return (Variant)(result.success().value());
return result.error().value();
};
}
// Map of all data types to their type names // Map of all data types to their type names
extern std::map<std::string, const TypeDescriptor*> TYPE_MAP; extern std::map<std::string, const TypeDesc*> TYPE_MAP;

View file

@ -8,6 +8,7 @@
#include <pugixml.hpp> #include <pugixml.hpp>
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/variant.h" #include "datatypes/variant.h"
#include "error/data.h"
#include <sstream> #include <sstream>
namespace rp = reactphysics3d; namespace rp = reactphysics3d;
@ -87,15 +88,15 @@ void Vector3::Serialize(pugi::xml_node node) const {
node.append_child("Z").text().set(std::to_string(this->Z())); node.append_child("Z").text().set(std::to_string(this->Z()));
} }
Vector3 Vector3::Deserialize(pugi::xml_node node) { result<Vector3, DataParseError> Vector3::Deserialize(pugi::xml_node node) {
return Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float()); return Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float());
} }
std::optional<Variant> Vector3::FromString(std::string string) { result<Vector3, DataParseError> Vector3::FromString(std::string string) {
float components[3]; float components[3];
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (string.length() == 0) return std::nullopt; if (string.length() == 0) return DataParseError(string, "Vector3");
while (string[0] == ' ' && string.length() > 0) string.erase(0, 1); while (string[0] == ' ' && string.length() > 0) string.erase(0, 1);
size_t nextPos = string.find(","); size_t nextPos = string.find(",");
if (nextPos == -1) nextPos = string.length(); if (nextPos == -1) nextPos = string.length();
@ -104,7 +105,7 @@ std::optional<Variant> Vector3::FromString(std::string string) {
char* cpos; char* cpos;
float value = std::strtof(term.c_str(), &cpos); float value = std::strtof(term.c_str(), &cpos);
if (cpos == term.c_str()) return std::nullopt; if (cpos == term.c_str()) return DataParseError(string, "Vector3");
components[i] = value; components[i] = value;
} }

View file

@ -2,6 +2,7 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp> #include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp> #include <glm/geometric.hpp>
@ -24,8 +25,8 @@ public:
virtual const std::string ToString() const; virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const; virtual void Serialize(pugi::xml_node node) const;
static Vector3 Deserialize(pugi::xml_node node); static result<Vector3, DataParseError> Deserialize(pugi::xml_node node);
static std::optional<Variant> FromString(std::string); static result<Vector3, DataParseError> FromString(std::string);
static void PushLuaLibrary(lua_State*); static void PushLuaLibrary(lua_State*);

View file

@ -4,5 +4,10 @@
class LuaCastError : public Error { class LuaCastError : public Error {
public: public:
inline LuaCastError(std::string sourceType, std::string targetType) : Error("InstanceCastError", "Attempt to cast " + sourceType + " to " + targetType) {} inline LuaCastError(std::string sourceType, std::string targetType) : Error("LuaCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
};
class DataParseError : public Error {
public:
inline DataParseError(std::string parsedString, std::string targetType) : Error("DataParseError", "Failed to parse '" + parsedString + "' into value of type " + targetType) {}
}; };

View file

@ -24,6 +24,8 @@ class [[nodiscard]] result {
std::variant<success_state, error_state> value; std::variant<success_state, error_state> value;
public: public:
// Helper for std::variant, etc.
template <typename ...T_Args, std::enable_if_t<std::is_constructible_v<T_Result, T_Args...>, int> = 0> result(T_Args... args) : value(success_state { T_Result(args...) }) {}
result(T_Result success) : value(success_state { success }) {} result(T_Result success) : value(success_state { success }) {}
result(std::variant<T_Errors...> error) : value(error_state { error }) {} result(std::variant<T_Errors...> error) : value(error_state { error }) {}
template <typename T_Error, std::enable_if_t<std::disjunction_v<std::is_same<T_Error, T_Errors>...>, int> = 0> template <typename T_Error, std::enable_if_t<std::disjunction_v<std::is_same<T_Error, T_Errors>...>, int> = 0>
@ -61,7 +63,7 @@ public:
// Equivalent to .success // Equivalent to .success
operator std::optional<T_Result>() { return success(); } operator std::optional<T_Result>() { return success(); }
operator bool() { return isSuccess(); } explicit operator bool() const { return isSuccess(); } // Explicity is necessary to prevent auto-casting from result to Variant, for instance
bool operator !() { return isError(); } bool operator !() { return isError(); }
}; };

View file

@ -354,7 +354,12 @@ result<std::shared_ptr<Instance>, NoSuchInstance> Instance::Deserialize(pugi::xm
object->SetPropertyValue(propertyName, InstanceRef()).expect(); object->SetPropertyValue(propertyName, InstanceRef()).expect();
} }
} else { } else {
Variant value = Variant::Deserialize(propertyNode, meta.type); auto valueResult = Variant::Deserialize(propertyNode, meta.type);
if (valueResult.isError()) {
valueResult.logError();
continue;
}
auto value = valueResult.expect();
object->SetPropertyValue(propertyName, value).expect("Declared property was missing"); object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
} }
} }

View file

@ -24,7 +24,7 @@ enum PropertyCategory {
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE_INPUT; const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE_INPUT;
struct PropertyMeta { struct PropertyMeta {
const TypeInfo type; const TypeMeta type;
PropertyFlags flags; PropertyFlags flags;
PropertyCategory category = PROP_CATEGORY_DATA; PropertyCategory category = PROP_CATEGORY_DATA;
}; };

View file

@ -1,3 +1,4 @@
#include "error/data.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "logger.h" #include "logger.h"

View file

@ -3,6 +3,7 @@
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/variant.h" #include "datatypes/variant.h"
#include "datatypes/primitives.h" #include "datatypes/primitives.h"
#include "error/data.h"
#include "objects/base/member.h" #include "objects/base/member.h"
#include <QColorDialog> #include <QColorDialog>
@ -213,11 +214,12 @@ public:
} else if (meta.type.descriptor->fromString) { } else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor); QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
std::optional<Variant> parsedResult = meta.type.descriptor->fromString(lineEdit->text().toStdString()); result<Variant, DataParseError> parsedResult = meta.type.descriptor->fromString(lineEdit->text().toStdString(), meta.type);
if (!parsedResult) return; if (!parsedResult) return;
inst->SetPropertyValue(propertyName, parsedResult.value()).expect(); Variant parsedValue = parsedResult.expect();
model->setData(index, QString::fromStdString(parsedResult.value().ToString())); inst->SetPropertyValue(propertyName, parsedValue).expect();
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedResult.value()); model->setData(index, QString::fromStdString(parsedValue.ToString()));
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedValue);
} }
} }
}; };
@ -358,7 +360,7 @@ void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) {
} }
} }
void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDescriptor* type, Variant value) { void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc* type, Variant value) {
if (type == &Vector3::TYPE) { if (type == &Vector3::TYPE) {
// https://forum.qt.io/post/266837 // https://forum.qt.io/post/266837
foreach(auto i, item->takeChildren()) delete i; foreach(auto i, item->takeChildren()) delete i;

View file

@ -15,7 +15,7 @@ class PropertiesView : public QTreeWidget {
std::weak_ptr<Instance> currentInstance; std::weak_ptr<Instance> currentInstance;
void propertyChanged(QTreeWidgetItem *item, int column); void propertyChanged(QTreeWidgetItem *item, int column);
void activateProperty(QTreeWidgetItem *item, int column); void activateProperty(QTreeWidgetItem *item, int column);
void rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDescriptor*, Variant); void rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc*, Variant);
void onPropertyUpdated(std::shared_ptr<Instance> instance, std::string property, Variant newValue); void onPropertyUpdated(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
friend PropertiesItemDelegate; friend PropertiesItemDelegate;