Compare commits

...

17 commits

Author SHA1 Message Date
10d69ce7ac refactor(datatypes): generified types allowing a parameter to be specified for enum type and instance type. Also made deserialization fallible 2025-06-03 00:49:29 +02:00
5149e34723 refactor(datatypes): more refactoring to allow for enums 2025-06-02 18:39:39 +02:00
0f44012e33 refactor(datatypes): changed how typeinfo works 2025-06-02 00:36:26 +02:00
bb67a246e2 fix(datatypes): signal type descriptor error 2025-06-02 00:35:38 +02:00
f9fc8c8cae feat(datatypes): basis for enums 2025-06-02 00:06:16 +02:00
5f6ff971d2 refactor(datatypes): complete refactor of how datatypes work and removal of wrapper classes for bool, string etc. 2025-05-31 23:08:13 +02:00
c8880e0edc feat(editor): drag-and-drop open file 2025-05-31 17:32:08 +02:00
14b0667fc9 feat(editor): right-click context menu in main gl widget 2025-05-31 02:04:38 +02:00
e5c8bdd2e2 feat(editor): multi-select rotation 2025-05-31 01:53:28 +02:00
80ec1a9132 feat(editor): icon for ServerScriptService 2025-05-30 21:45:39 +02:00
1af34a21fa feat(physics): last part of model that falls off world destroys model too 2025-05-30 02:19:44 +02:00
964c733f53 feat(physics): parts fallen beyond fall height get automatically destroyed 2025-05-30 02:15:58 +02:00
19b6489476 feat(model): added model container object for parts 2025-05-30 02:15:44 +02:00
497a3f783c refactor(misc): removed InstanceRef and InstanceRefWeak type aliases due to confusion with Data::InstanceRef
Possible alternative names: Object and ObjectWeak
2025-05-30 01:27:22 +02:00
215fa141b6 refactor(selection): made selection shared_ptr rather than weak_ptr 2025-05-30 01:20:08 +02:00
f6d5ebd7c7 feat(instance): folder, AKA probably one of if not the most useless features thus far 2025-05-30 00:40:04 +02:00
8b7fef624f feat(serialization): serialize instance references 2025-05-29 22:15:05 +02:00
74 changed files with 1567 additions and 1010 deletions

BIN
assets/icons/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

BIN
assets/icons/model.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

View file

@ -174,6 +174,25 @@ static bool hasMethod(CXCursor cur, std::string methodName) {
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) {
ClassAnalysis anly;
@ -184,6 +203,8 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
anly.serializedName = result["name"];
anly.hasFromString = hasMethod(cur, "FromString");
anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize");
anly.hasGenericDeserializer = hasGenericMethod(cur, "Deserialize");
anly.hasGenericFromString = hasGenericMethod(cur, "FromString");
if (anly.serializedName == "")
anly.serializedName = className;

View file

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

View file

@ -9,10 +9,6 @@
using namespace data;
static std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" },
{ "int", "Data::Int" },
{ "float", "Data::Float" },
{ "std::string", "Data::String" },
{ "glm::vec3", "Vector3" },
};
@ -30,16 +26,36 @@ static std::map<std::string, std::string> LUA_TEST_FUNCS = {
{ "std::string", "lua_isstring" },
};
static std::map<std::string, std::string> LUA_PUSH_FUNCS = {
{ "bool", "lua_pushboolean" },
{ "int", "lua_pushinteger" },
{ "float", "lua_pushnumber" },
// Handled specially
// { "std::string", "lua_pushstring" },
};
static std::string getLuaMethodFqn(std::string className, std::string methodName) {
return "__lua_impl__" + className + "__" + methodName;
}
static std::string getMtName(std::string type) {
if (type.starts_with("Data::"))
return "__mt_" + type.substr(6);
// if (type.starts_with("Data::"))
// return "__mt_" + type.substr(6);
return "__mt_" + type;
}
static std::string pushLuaValue(std::string type, std::string expr) {
if (type == "std::string")
return "lua_pushstring(L, " + expr + ".c_str())";
std::string mappedType = MAPPED_TYPE[type];
if (mappedType != "")
return mappedType + "(" + expr + ").PushLuaValue(L)";
std::string pushFunc = LUA_PUSH_FUNCS[type];
if (pushFunc != "")
return pushFunc + "(L, " + expr + ")";
return expr + ".PushLuaValue(L)";
}
static void writeLuaGetArgument(std::ofstream& out, std::string type, int narg, bool member) {
std::string varname = "arg" + std::to_string(narg);
narg += 1; // Arguments start at 1
@ -69,7 +85,7 @@ static void writeLuaTestArgument(std::ofstream& out, std::string type, int narg,
}
static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
std::string fqn = "" + state.name;
// Collect all method names to account for overloaded functions
std::map<std::string, std::vector<MethodAnalysis>> methods;
@ -132,11 +148,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Return result
if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
}
if (methodImpl.returnType == "void")
@ -205,11 +217,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Return result
if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
}
if (methodImpl.returnType == "void")
@ -228,45 +236,42 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
}
static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
std::string fqn = state.name;
out << "static int data_gc(lua_State*);\n"
"static int data_index(lua_State*);\n"
"static int data_tostring(lua_State*);\n"
"static const struct luaL_Reg metatable [] = {\n"
" {\"__gc\", data_gc},\n"
" {\"__index\", data_index},\n"
" {\"__tostring\", data_tostring},\n"
out << "static int data_" << state.name << "_gc(lua_State*);\n"
"static int data_" << state.name << "_index(lua_State*);\n"
"static int data_" << state.name << "_tostring(lua_State*);\n"
"static const struct luaL_Reg " << state.name << "_metatable [] = {\n"
" {\"__gc\", data_" << state.name << "_gc},\n"
" {\"__index\", data_" << state.name << "_index},\n"
" {\"__tostring\", data_" << state.name << "_tostring},\n"
" {NULL, NULL} /* end of array */\n"
"};\n\n";
out << "void Data::" << state.name << "::PushLuaValue(lua_State* L) const {\n"
out << "void " << state.name << "::PushLuaValue(lua_State* L) const {\n"
" int n = lua_gettop(L);\n"
// " // I'm torn... should this be Data::Variant, or Data::Base?\n"
// " // If I ever decouple typing from Data::Base, I'll switch it to variant,\n"
// " // otherwise, it doesn't make much sense to represent it as one\n"
" " << fqn << "** userdata = (" << fqn << "**)lua_newuserdata(L, sizeof(" << fqn << "));\n"
" *userdata = new " << fqn << "(*this);\n"
" // Create the library's metatable\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"
"}\n\n";
out << "result<Data::Variant, LuaCastError> Data::" << state.name << "::FromLuaValue(lua_State* L, int idx) {\n"
out << "result<Variant, LuaCastError> " << state.name << "::FromLuaValue(lua_State* L, int idx) {\n"
" " << fqn << "** userdata = (" << fqn << "**) luaL_testudata(L, idx, \"" << getMtName(state.name) << "\");\n"
" if (userdata == nullptr)\n"
" return LuaCastError(lua_typename(L, idx), \"" << state.name << "\");\n"
" return Data::Variant(**userdata);\n"
" return Variant(**userdata);\n"
"}\n\n";
// 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"
"\n"
" std::string key(lua_tostring(L, 2));\n"
@ -292,7 +297,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
valueExpr = "this_->" + prop.backingSymbol + "()";
// This largely depends on the type
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
out << " " << pushLuaValue(type, valueExpr) << ";\n";
out << " return 1;\n";
out << " }";
@ -318,7 +323,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// 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"
" lua_pushstring(L, std::string(this_->ToString()).c_str());\n"
" return 1;\n"
@ -326,7 +331,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// 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"
" delete *userdata;\n"
" return 0;\n"
@ -334,15 +339,18 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
}
static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
std::string fqn = state.name;
out << "static int lib_index(lua_State*);\n"
"static const struct luaL_Reg lib_metatable [] = {\n"
" {\"__index\", lib_index},\n"
// If there are no static methods or properties, no need to create a library
if (state.staticMethods.size() == 0 && state.staticProperties.size() == 0) return;
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"
"};\n\n";
out << "void Data::" << state.name << "::PushLuaLibrary(lua_State* L) {\n"
out << "void " << state.name << "::PushLuaLibrary(lua_State* L) {\n"
" lua_getglobal(L, \"_G\");\n"
" lua_pushstring(L, \"" << state.name << "\");\n"
"\n"
@ -350,7 +358,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
"\n"
" // Create the library's metatable\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"
"\n"
" lua_rawset(L, -3);\n"
@ -359,7 +367,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
// 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"
" lua_pop(L, 2);\n"
"\n";
@ -382,7 +390,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
else if (prop.backingType == PropertyBackingType::Method)
valueExpr = fqn + "::" + prop.backingSymbol + "()";
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
out << " " << pushLuaValue(type, valueExpr) << ";\n";
out << " return 1;\n";
out << " }";
@ -408,22 +416,29 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
}
void data::writeCodeForClass(std::ofstream& out, std::string headerPath, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
out << "#include \"" << headerPath << "\"\n\n";
out << "#include \"datatypes/meta.h\"\n";
out << "#include \"datatypes/variant.h\"\n";
out << "#include <pugixml.hpp>\n";
out << "#include \"lua.h\"\n\n";
out << "const Data::TypeInfo " << fqn << "::TYPE = {\n"
<< " .name = \"" << state.serializedName << "\",\n"
<< " .deserializer = &" << fqn << "::Deserialize,\n";
if (state.hasFromString) out << " .fromString = &" << fqn << "::FromString,\n";
out << " .fromLuaValue = &" << fqn << "::FromLuaValue,\n"
<< "};\n\n";
out << "const Data::TypeInfo& " << fqn << "::GetType() const {\n"
<< " return TYPE;\n"
out << "const TypeDesc " << state.name << "::TYPE = {\n"
<< " .name = \"" << state.serializedName << "\",\n";
if (state.isSerializable) {
out << " .serialize = toVariantFunction(&" << state.name << "::Serialize),";
if (state.hasGenericDeserializer)
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),"
<< " .fromLuaValue = toVariantGenerator(&" << state.name << "::FromLuaValue),\n"
<< "};\n\n";
writeLuaMethodImpls(out, state);

View file

@ -54,7 +54,7 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
fs::path relpath = fs::relative(srcPath, srcRoot);
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
object::analyzeClasses(cursor, srcRoot, &objectAnlyState);
data::analyzeClasses(cursor, srcRoot, &dataAnlyState);
analyzeClasses(cursor, srcRoot, &dataAnlyState);
fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it
@ -72,7 +72,7 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
}
for (auto& [_, clazz] : dataAnlyState.classes) {
data::writeCodeForClass(outStream, relpath, clazz);
writeCodeForClass(outStream, relpath, clazz);
}
outStream.close();

View file

@ -16,13 +16,17 @@ static std::map<std::string, std::string> CATEGORY_STR = {
};
static std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" },
{ "int", "Data::Int" },
{ "float", "Data::Float" },
{ "std::string", "Data::String" },
{ "glm::vec3", "Vector3" },
};
static std::map<std::string, std::string> TYPEINFO_REFS = {
{ "glm::vec3", "Vector3::TYPE" },
{ "bool", "BOOL_TYPE" },
{ "int", "INT_TYPE" },
{ "float", "FLOAT_TYPE" },
{ "std::string", "STRING_TYPE" },
};
static std::map<std::string, std::monostate> ENUM_TYPES = {
{ "SurfaceType", std::monostate() }
};
@ -40,7 +44,7 @@ static std::string parseWeakPtr(std::string weakPtrType) {
static std::string castFromVariant(std::string valueStr, std::string fieldType) {
// Manual exception for now, enums will get their own system eventually
if (fieldType == "SurfaceType") {
return "(SurfaceType)(int)" + valueStr + ".get<Data::Int>()";
return "(SurfaceType)(int)" + valueStr + ".get<int>()";
}
std::string mappedType = MAPPED_TYPE[fieldType];
@ -50,13 +54,13 @@ static std::string castFromVariant(std::string valueStr, std::string fieldType)
static std::string castToVariant(std::string valueStr, std::string fieldType) {
// Manual exception for now, enums will get their own system eventually
if (fieldType == "SurfaceType") {
return "Data::Int((int)" + valueStr + ")";
return "(int)" + valueStr;
}
// InstanceRef
// std::shared_ptr<Instance>
std::string subtype = parseWeakPtr(fieldType);
if (!subtype.empty()) {
return "Data::Variant(" + valueStr + ".expired() ? Data::InstanceRef() : Data::InstanceRef(std::dynamic_pointer_cast<Instance>(" + valueStr + ".lock())))";
return "Variant(" + valueStr + ".expired() ? InstanceRef() : InstanceRef(std::dynamic_pointer_cast<Instance>(" + valueStr + ".lock())))";
}
std::string mappedType = MAPPED_TYPE[fieldType];
@ -67,13 +71,13 @@ static std::string castToVariant(std::string valueStr, std::string fieldType) {
}
static void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {";
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Variant value) {";
out << "\n ";
bool first = true;
for (auto& prop : state.properties) {
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
// InstanceRef
// std::shared_ptr<Instance>
std::string subtype = parseWeakPtr(prop.backingFieldType);
if (prop.flags & PropertyFlag_Readonly) {
@ -83,7 +87,7 @@ static void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
} else if (prop.cframeMember == CFrameMember_Rotation) {
out << "\n this->" << prop.fieldName << " = CFrame::FromEulerAnglesXYZ(value.get<Vector3>()) + this->" << prop.fieldName << ".Position();";
} else if (!subtype.empty()) {
out << "\n std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();"
out << "\n std::weak_ptr<Instance> ref = value.get<InstanceRef>();"
<< "\n this->" << prop.fieldName << " = ref.expired() ? std::weak_ptr<" << subtype << ">() : std::dynamic_pointer_cast<" << subtype << ">(ref.lock());";
} else {
out << "\n this->" << prop.fieldName << " = " << castFromVariant("value", prop.backingFieldType) << ";";
@ -128,7 +132,7 @@ static void writePropertyUpdateHandler(std::ofstream& out, ClassAnalysis state)
}
static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
out << "result<Data::Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
out << "result<Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
out << "\n ";
bool first = true;
@ -136,11 +140,11 @@ static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
if (prop.cframeMember == CFrameMember_Position) {
out << "\n return Data::Variant(" << prop.fieldName << ".Position());";
out << "\n return Variant(" << prop.fieldName << ".Position());";
} else if (prop.cframeMember == CFrameMember_Rotation) {
out << "\n return Data::Variant(" << prop.fieldName << ".ToEulerAnglesXYZ());";
out << "\n return Variant(" << prop.fieldName << ".ToEulerAnglesXYZ());";
} else {
out << "\n return Data::Variant(" << castToVariant(prop.fieldName, prop.backingFieldType) << ");";
out << "\n return Variant(" << castToVariant(prop.fieldName, prop.backingFieldType) << ");";
}
out << "\n }";
@ -153,7 +157,7 @@ static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
for (auto& signal : state.signals) {
out << (first ? "" : " else ") << "if (name == \"" << signal.name << "\") {";
out << "\n return Data::Variant(Data::SignalRef(" << signal.sourceFieldName << "));";
out << "\n return Variant(SignalRef(" << signal.sourceFieldName << "));";
out << "\n }";
first = false;
@ -186,10 +190,10 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
for (auto& prop : state.properties) {
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
std::string type = MAPPED_TYPE[prop.backingFieldType];
if (type.empty()) type = prop.backingFieldType;
if (type == "SurfaceType") type = "Data::Int";
if (!parseWeakPtr(prop.backingFieldType).empty()) type = "Data::InstanceRef";
std::string typeInfo = TYPEINFO_REFS[prop.backingFieldType];
if (typeInfo.empty()) typeInfo = prop.backingFieldType + "::TYPE";
if (!parseWeakPtr(prop.backingFieldType).empty()) typeInfo = "InstanceRef::TYPE";
if (prop.backingFieldType == "SurfaceType") typeInfo = "INT_TYPE";
std::string strFlags;
if (prop.flags & PropertyFlag_Readonly)
@ -206,7 +210,7 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
std::string category = CATEGORY_STR[prop.category];
if (category.empty()) category = "PROP_CATEGORY_DATA";
out << "\n return PropertyMeta { &" << type << "::TYPE, " << strFlags << ", " << category << " };";
out << "\n return PropertyMeta { &" << typeInfo << ", " << strFlags << ", " << category << " };";
out << "\n }";
first = false;
@ -220,8 +224,8 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
std::string strFlags;
strFlags += "PROP_READONLY";
strFlags += "| PROP_HIDDEN";
strFlags += "| PROP_NOSAVE";
strFlags += " | PROP_HIDDEN";
strFlags += " | PROP_NOSAVE";
out << "\n return PropertyMeta { &SignalRef::TYPE, " << strFlags << " };";
@ -252,7 +256,8 @@ void object::writeCodeForClass(std::ofstream& out, std::string headerPath, Class
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
out << "#include \"" << state.headerPath << "\"\n\n";
out << "#include \"datatypes/meta.h\"\n\n";
out << "#include \"datatypes/variant.h\"\n\n";
out << "#include \"datatypes/primitives.h\"\n\n";
out << "const InstanceType " << state.name << "::TYPE = {\n"
<< " .super = &" << state.baseClass << "::TYPE,\n"
<< " .className = \"" << state.name << "\",\n"

View file

@ -52,7 +52,7 @@ int main() {
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
gWorkspace()->SyncPartPhysics(part);

View file

@ -1,7 +1,7 @@
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
#include "objects/datamodel.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "common.h"
#include <memory>
@ -14,11 +14,11 @@ std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
Handles editorToolHandles;
std::vector<InstanceRefWeak> currentSelection;
std::vector<std::shared_ptr<Instance>> currentSelection;
std::vector<SelectionUpdateHandler> selectionUpdateListeners;
std::vector<PropertyUpdateHandler> propertyUpdatelisteners;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer) {
void setSelection(std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer) {
for (SelectionUpdateHandler handler : selectionUpdateListeners) {
handler(currentSelection, newSelection, fromExplorer);
}
@ -26,7 +26,7 @@ void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer)
currentSelection = newSelection;
}
const std::vector<InstanceRefWeak> getSelection() {
const std::vector<std::shared_ptr<Instance>> getSelection() {
return currentSelection;
}
@ -34,7 +34,7 @@ void addSelectionListener(SelectionUpdateHandler handler) {
selectionUpdateListeners.push_back(handler);
}
void sendPropertyUpdatedSignal(InstanceRef instance, std::string property, Data::Variant newValue) {
void sendPropertyUpdatedSignal(std::shared_ptr<Instance> instance, std::string property, Variant newValue) {
for (PropertyUpdateHandler handler : propertyUpdatelisteners) {
handler(instance, property, newValue);
}

View file

@ -9,10 +9,10 @@
class Instance;
// typedef std::function<void(std::shared_ptr<Instance> element, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyUpdateHandler;
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPreUpdateHandler;
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPostUpdateHandler;
typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vector<InstanceRefWeak> newSelection, bool fromExplorer)> SelectionUpdateHandler;
typedef std::function<void(InstanceRef instance, std::string property, Data::Variant newValue)> PropertyUpdateHandler;
typedef std::function<void(std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyPreUpdateHandler;
typedef std::function<void(std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyPostUpdateHandler;
typedef std::function<void(std::vector<std::shared_ptr<Instance>> oldSelection, std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer)> SelectionUpdateHandler;
typedef std::function<void(std::shared_ptr<Instance> instance, std::string property, Variant newValue)> PropertyUpdateHandler;
// TEMPORARY COMMON DATA FOR VARIOUS INTERNAL COMPONENTS
@ -24,9 +24,9 @@ extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
extern Handles editorToolHandles;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
const std::vector<InstanceRefWeak> getSelection();
void setSelection(std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer = false);
const std::vector<std::shared_ptr<Instance>> getSelection();
void addSelectionListener(SelectionUpdateHandler handler);
void sendPropertyUpdatedSignal(InstanceRef instance, std::string property, Data::Variant newValue);
void sendPropertyUpdatedSignal(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
void addPropertyUpdateListener(PropertyUpdateHandler handler);

View file

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

View file

@ -1,162 +0,0 @@
#include "base.h"
#include "error/data.h"
#include "meta.h"
#include <ios>
#include <sstream>
#include <pugixml.hpp>
#include "lua.h"
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \
Data::CLASS_NAME::~CLASS_NAME() = default; \
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { \
.name = TYPE_NAME, \
.deserializer = &Data::CLASS_NAME::Deserialize, \
.fromString = &Data::CLASS_NAME::FromString, \
.fromLuaValue = &Data::CLASS_NAME::FromLuaValue, \
}; \
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \
void Data::CLASS_NAME::Serialize(pugi::xml_node node) const { node.text().set(std::string(this->ToString())); }
Data::Base::~Base() {};
Data::Null::Null() {};
Data::Null::~Null() = default;
const Data::TypeInfo Data::Null::TYPE = {
.name = "null",
.deserializer = &Data::Null::Deserialize,
};
const Data::TypeInfo& Data::Null::GetType() const { return Data::Null::TYPE; };
const Data::String Data::Null::ToString() const {
return Data::String("null");
}
void Data::Null::Serialize(pugi::xml_node node) const {
node.text().set("null");
}
Data::Variant Data::Null::Deserialize(pugi::xml_node node) {
return Data::Null();
}
void Data::Null::PushLuaValue(lua_State* L) const {
lua_pushnil(L);
}
result<Data::Variant, LuaCastError> Data::Null::FromLuaValue(lua_State* L, int idx) {
return Data::Variant(Data::Null());
}
//
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
IMPL_WRAPPER_CLASS(Int, int, "int")
IMPL_WRAPPER_CLASS(Float, float, "float")
IMPL_WRAPPER_CLASS(String, std::string, "string")
// ToString
const Data::String Data::Bool::ToString() const {
return Data::String(value ? "true" : "false");
}
const Data::String Data::Int::ToString() const {
return Data::String(std::to_string(value));
}
const Data::String Data::Float::ToString() const {
std::stringstream stream;
stream << std::noshowpoint << value;
return Data::String(stream.str());
}
const Data::String Data::String::ToString() const {
return *this;
}
// Deserialize
Data::Variant Data::Bool::Deserialize(pugi::xml_node node) {
return Data::Bool(node.text().as_bool());
}
Data::Variant Data::Int::Deserialize(pugi::xml_node node) {
return Data::Int(node.text().as_int());
}
Data::Variant Data::Float::Deserialize(pugi::xml_node node) {
return Data::Float(node.text().as_float());
}
Data::Variant Data::String::Deserialize(pugi::xml_node node) {
return Data::String(node.text().as_string());
}
// FromString
std::optional<Data::Variant> Data::Bool::FromString(std::string string) {
return Data::Bool(string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y');
}
std::optional<Data::Variant> Data::Int::FromString(std::string string) {
char* endPos;
int value = (int)std::strtol(string.c_str(), &endPos, 10);
if (endPos == string.c_str()) return std::nullopt;
return Data::Int(value);
}
std::optional<Data::Variant> Data::Float::FromString(std::string string) {
char* endPos;
float value = std::strtof(string.c_str(), &endPos);
if (endPos == string.c_str()) return std::nullopt;
return Data::Float(value);
}
std::optional<Data::Variant> Data::String::FromString(std::string string) {
return Data::String(string);
}
// PushLuaValue
void Data::Bool::PushLuaValue(lua_State* L) const {
lua_pushboolean(L, *this);
}
void Data::Int::PushLuaValue(lua_State* L) const {
lua_pushinteger(L, *this);
}
void Data::Float::PushLuaValue(lua_State* L) const {
lua_pushnumber(L, *this);
}
void Data::String::PushLuaValue(lua_State* L) const {
lua_pushstring(L, value.c_str());
}
// FromLuaValue
result<Data::Variant, LuaCastError> Data::Bool::FromLuaValue(lua_State* L, int idx) {
if (!lua_isboolean(L, idx))
return LuaCastError(lua_typename(L, idx), "boolean");
return Data::Variant(Data::Bool(lua_toboolean(L, idx)));
}
result<Data::Variant, LuaCastError> Data::Int::FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "integer");
return Data::Variant(Data::Int((int)lua_tonumber(L, idx)));
}
result<Data::Variant, LuaCastError> Data::Float::FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "float");
return Data::Variant(Data::Float((float)lua_tonumber(L, idx)));
}
result<Data::Variant, LuaCastError> Data::String::FromLuaValue(lua_State* L, int idx) {
if (!lua_tostring(L, idx))
return LuaCastError(lua_typename(L, idx), "string");
return Data::Variant(Data::String(lua_tostring(L, idx)));
}

View file

@ -10,67 +10,39 @@ extern "C" { typedef struct lua_State lua_State; }
namespace pugi { class xml_node; };
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
WRAPPED_TYPE value; \
public: \
CLASS_NAME(WRAPPED_TYPE); \
~CLASS_NAME(); \
operator const WRAPPED_TYPE() const; \
virtual const TypeInfo& GetType() const override; \
static const TypeInfo TYPE; \
\
virtual const Data::String ToString() const override; \
virtual void Serialize(pugi::xml_node node) const override; \
virtual void PushLuaValue(lua_State*) const override; \
\
static Data::Variant Deserialize(pugi::xml_node node); \
static std::optional<Data::Variant> FromString(std::string); \
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
class Variant;
struct TypeMeta;
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<result<Variant, DataParseError>(std::string, const TypeMeta)> FromString;
typedef std::function<result<Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
typedef std::function<void(Variant self, lua_State*)> PushLuaValue;
// Describes a concrete type
struct TypeDesc {
std::string name;
Serialize serialize;
Deserialize deserialize;
ToString toString;
FromString fromString;
PushLuaValue pushLuaValue;
FromLuaValue fromLuaValue;
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node)> Deserializer;
typedef std::function<std::optional<Data::Variant>(std::string)> FromString;
typedef std::function<result<Data::Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
class Enum;
struct InstanceType;
struct TypeInfo {
std::string name;
Deserializer deserializer;
FromString fromString;
FromLuaValue fromLuaValue;
// Describes a meta-type, which consists of a concrete type, and some generic argument.
struct TypeMeta {
const TypeDesc* descriptor;
union {
Enum* enum_; // Applicable for EnumItem
InstanceType* instType; // Applicable for InstanceRef
};
class String;
class Base {
public:
virtual ~Base();
virtual const TypeInfo& GetType() const = 0;
virtual const Data::String ToString() const = 0;
virtual void Serialize(pugi::xml_node node) const = 0;
virtual void PushLuaValue(lua_State*) const = 0;
};
class Null : Base {
public:
Null();
~Null();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
DEF_WRAPPER_CLASS(Bool, bool)
DEF_WRAPPER_CLASS(Int, int)
DEF_WRAPPER_CLASS(Float, float)
DEF_WRAPPER_CLASS(String, std::string)
};
#undef DEF_WRAPPER_CLASS
inline TypeMeta(const TypeDesc* descriptor) : descriptor(descriptor) {}
TypeMeta(Enum*);
TypeMeta(InstanceType*);
};

View file

@ -1,23 +1,24 @@
#include "cframe.h"
#include "datatypes/vector.h"
#include "error/data.h"
#include "physics/util.h"
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include <pugixml.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
// #include "meta.h" // IWYU pragma: keep
// #include "variant.h" // IWYU pragma: keep
const Data::CFrame Data::CFrame::IDENTITY(glm::vec3(0, 0, 0), glm::mat3(1.f));
const Data::CFrame Data::CFrame::YToZ(glm::vec3(0, 0, 0), glm::mat3(glm::vec3(1, 0, 0), glm::vec3(0, 0, 1), glm::vec3(0, 1, 0)));
const CFrame CFrame::IDENTITY(glm::vec3(0, 0, 0), glm::mat3(1.f));
const CFrame CFrame::YToZ(glm::vec3(0, 0, 0), glm::mat3(glm::vec3(1, 0, 0), glm::vec3(0, 0, 1), glm::vec3(0, 1, 0)));
Data::CFrame::CFrame() : Data::CFrame::CFrame(glm::vec3(0, 0, 0), glm::mat3(1.f)) {}
CFrame::CFrame() : CFrame::CFrame(glm::vec3(0, 0, 0), glm::mat3(1.f)) {}
Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
: translation(x, y, z)
, rotation({
// { R00, R01, R02 },
@ -29,17 +30,17 @@ Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02,
}) {
}
Data::CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
: translation(translation)
, rotation(rotation) {
}
Data::CFrame::CFrame(Vector3 position, glm::quat quat)
CFrame::CFrame(Vector3 position, glm::quat quat)
: translation(position)
, rotation(quat) {
}
Data::CFrame::CFrame(const rp::Transform& transform) : Data::CFrame::CFrame(rpToGlm(transform.getPosition()), rpToGlm(transform.getOrientation())) {
CFrame::CFrame(const rp::Transform& transform) : CFrame::CFrame(rpToGlm(transform.getPosition()), rpToGlm(transform.getOrientation())) {
}
glm::mat3 lookAt(Vector3 position, Vector3 lookAt, Vector3 up) {
@ -52,35 +53,35 @@ glm::mat3 lookAt(Vector3 position, Vector3 lookAt, Vector3 up) {
return { s, u, -f };
}
Data::CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
: translation(position)
, rotation(::lookAt(position, lookAt, up)) {
}
Data::CFrame::~CFrame() = default;
CFrame::~CFrame() = default;
const Data::String Data::CFrame::ToString() const {
const std::string CFrame::ToString() const {
return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
}
Data::CFrame Data::CFrame::pointToward(Vector3 position, Vector3 toward) {
return Data::CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
CFrame CFrame::pointToward(Vector3 position, Vector3 toward) {
return CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
}
Data::CFrame Data::CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
return Data::CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
CFrame CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
return CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
}
Data::CFrame::operator glm::mat4() const {
CFrame::operator glm::mat4() const {
// Always make sure to translate the position first, then rotate. Matrices work backwards
return glm::translate(glm::mat4(1.0f), this->translation) * glm::mat4(this->rotation);
}
Data::CFrame::operator rp::Transform() const {
CFrame::operator rp::Transform() const {
return rp::Transform(glmToRp(translation), glmToRp(rotation));
}
Vector3 Data::CFrame::ToEulerAnglesXYZ() {
Vector3 CFrame::ToEulerAnglesXYZ() {
float x;
float y;
float z;
@ -88,37 +89,37 @@ Vector3 Data::CFrame::ToEulerAnglesXYZ() {
return Vector3(x, y, z);
}
Data::CFrame Data::CFrame::FromEulerAnglesXYZ(Vector3 vector) {
CFrame CFrame::FromEulerAnglesXYZ(Vector3 vector) {
glm::mat3 mat = glm::eulerAngleXYZ(vector.X(), vector.Y(), vector.Z());
return Data::CFrame((glm::vec3)Vector3::ZERO, mat);
return CFrame((glm::vec3)Vector3::ZERO, mat);
}
Data::CFrame Data::CFrame::Inverse() const {
CFrame CFrame::Inverse() const {
return CFrame { -translation * glm::transpose(glm::inverse(rotation)), glm::inverse(rotation) };
}
// Operators
Data::CFrame Data::CFrame::operator *(Data::CFrame otherFrame) const {
CFrame CFrame::operator *(CFrame otherFrame) const {
return CFrame { this->translation + this->rotation * otherFrame.translation, this->rotation * otherFrame.rotation };
}
Vector3 Data::CFrame::operator *(Vector3 vector) const {
Vector3 CFrame::operator *(Vector3 vector) const {
return this->translation + this->rotation * vector;
}
Data::CFrame Data::CFrame::operator +(Vector3 vector) const {
CFrame CFrame::operator +(Vector3 vector) const {
return CFrame { this->translation + glm::vec3(vector), this->rotation };
}
Data::CFrame Data::CFrame::operator -(Vector3 vector) const {
CFrame CFrame::operator -(Vector3 vector) const {
return *this + -vector;
}
// Serialization
void Data::CFrame::Serialize(pugi::xml_node node) const {
void CFrame::Serialize(pugi::xml_node node) const {
node.append_child("X").text().set(std::to_string(this->X()));
node.append_child("Y").text().set(std::to_string(this->Y()));
node.append_child("Z").text().set(std::to_string(this->Z()));
@ -134,8 +135,8 @@ void Data::CFrame::Serialize(pugi::xml_node node) const {
}
Data::Variant Data::CFrame::Deserialize(pugi::xml_node node) {
return Data::CFrame(
result<CFrame, DataParseError> CFrame::Deserialize(pugi::xml_node node) {
return CFrame(
node.child("X").text().as_float(),
node.child("Y").text().as_float(),
node.child("Z").text().as_float(),

View file

@ -3,72 +3,69 @@
#include "base.h"
#include "datatypes/annotation.h"
#include "datatypes/vector.h"
#include "error/data.h"
#include <glm/ext/quaternion_float.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/matrix.hpp>
namespace reactphysics3d { class Transform; };
namespace Data {
class DEF_DATA_(name="CoordinateFrame") CFrame : public Base {
AUTOGEN_PREAMBLE_DATA
class DEF_DATA_(name="CoordinateFrame") CFrame {
AUTOGEN_PREAMBLE_DATA
glm::vec3 translation;
glm::mat3 rotation;
CFrame(glm::vec3, glm::mat3);
public:
// CFrame(float x, float y, float z);
// CFrame(const glm::vec3&);
// CFrame(const rp::Vector3&);
DEF_DATA_CTOR CFrame();
DEF_DATA_CTOR CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
DEF_DATA_CTOR CFrame(Vector3 , Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
CFrame(const reactphysics3d::Transform&);
CFrame(Vector3 position, glm::quat quat);
~CFrame();
glm::vec3 translation;
glm::mat3 rotation;
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
static CFrame pointToward(Vector3 position, Vector3 toward);
// Creates a cframe looking at position + toward, whilst aligning its up to up.
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
// then the right is used instead
// Up and right must NOT be linearly dependent
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
CFrame(glm::vec3, glm::mat3);
public:
// CFrame(float x, float y, float z);
// CFrame(const glm::vec3&);
// CFrame(const rp::Vector3&);
DEF_DATA_CTOR CFrame();
DEF_DATA_CTOR CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
DEF_DATA_CTOR CFrame(Vector3 , Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
CFrame(const reactphysics3d::Transform&);
CFrame(Vector3 position, glm::quat quat);
~CFrame();
DEF_DATA_PROP static const CFrame IDENTITY;
static const CFrame YToZ;
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
static CFrame pointToward(Vector3 position, Vector3 toward);
// Creates a cframe looking at position + toward, whilst aligning its up to up.
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
// then the right is used instead
// Up and right must NOT be linearly dependent
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node parent) const override;
static Data::Variant Deserialize(pugi::xml_node node);
DEF_DATA_PROP static const CFrame IDENTITY;
static const CFrame YToZ;
static void PushLuaLibrary(lua_State*);
virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node parent) const;
static result<CFrame, DataParseError> Deserialize(pugi::xml_node node);
operator glm::mat4() const;
operator reactphysics3d::Transform() const;
static void PushLuaLibrary(lua_State*);
//inline static CFrame identity() { }
DEF_DATA_PROP inline Vector3 Position() const { return translation; }
DEF_DATA_PROP inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
DEF_DATA_METHOD CFrame Inverse() const;
DEF_DATA_PROP inline float X() const { return translation.x; }
DEF_DATA_PROP inline float Y() const { return translation.y; }
DEF_DATA_PROP inline float Z() const { return translation.z; }
operator glm::mat4() const;
operator reactphysics3d::Transform() const;
DEF_DATA_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); }
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); }
//inline static CFrame identity() { }
DEF_DATA_PROP inline Vector3 Position() const { return translation; }
DEF_DATA_PROP inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
DEF_DATA_METHOD CFrame Inverse() const;
DEF_DATA_PROP inline float X() const { return translation.x; }
DEF_DATA_PROP inline float Y() const { return translation.y; }
DEF_DATA_PROP inline float Z() const { return translation.z; }
DEF_DATA_METHOD Vector3 ToEulerAnglesXYZ();
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
DEF_DATA_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); }
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); }
// Operators
DEF_DATA_OP Data::CFrame operator *(Data::CFrame) const;
DEF_DATA_OP Vector3 operator *(Vector3) const;
DEF_DATA_OP Data::CFrame operator +(Vector3) const;
DEF_DATA_OP Data::CFrame operator -(Vector3) const;
};
}
DEF_DATA_METHOD Vector3 ToEulerAnglesXYZ();
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
using Data::CFrame;
// Operators
DEF_DATA_OP CFrame operator *(CFrame) const;
DEF_DATA_OP Vector3 operator *(Vector3) const;
DEF_DATA_OP CFrame operator +(Vector3) const;
DEF_DATA_OP CFrame operator -(Vector3) const;
};

View file

@ -1,21 +1,22 @@
#include "color3.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp>
#include <sstream>
#include <iomanip>
Data::Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {};
Data::Color3::Color3(const glm::vec3& vec) : r(std::clamp(vec.x, 0.f, 1.f)), g(std::clamp(vec.y, 0.f, 1.f)), b(std::clamp(vec.z, 0.f, 1.f)) {};
Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {};
Color3::Color3(const glm::vec3& vec) : r(std::clamp(vec.x, 0.f, 1.f)), g(std::clamp(vec.y, 0.f, 1.f)), b(std::clamp(vec.z, 0.f, 1.f)) {};
Data::Color3::~Color3() = default;
Color3::~Color3() = default;
const Data::String Data::Color3::ToString() const {
const std::string Color3::ToString() const {
return std::to_string(int(r*256)) + ", " + std::to_string(int(g*256)) + ", " + std::to_string(int(b*256));
}
Data::Color3::operator glm::vec3() const { return glm::vec3(r, g, b); };
Color3::operator glm::vec3() const { return glm::vec3(r, g, b); };
std::string Data::Color3::ToHex() const {
std::string Color3::ToHex() const {
std::stringstream ss;
ss << "FF" << std::hex << std::uppercase << std::setfill('0')
<< std::setw(2) << int(r*255)
@ -25,7 +26,7 @@ std::string Data::Color3::ToHex() const {
}
Data::Color3 Data::Color3::FromHex(std::string hex) {
Color3 Color3::FromHex(std::string hex) {
float r = float(std::stoi(hex.substr(2, 2), nullptr, 16)) / 255;
float g = float(std::stoi(hex.substr(4, 2), nullptr, 16)) / 255;
float b = float(std::stoi(hex.substr(6, 2), nullptr, 16)) / 255;
@ -35,10 +36,10 @@ Data::Color3 Data::Color3::FromHex(std::string hex) {
// Serialization
void Data::Color3::Serialize(pugi::xml_node node) const {
void Color3::Serialize(pugi::xml_node node) const {
node.text().set(this->ToHex());
}
Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
result<Color3, DataParseError> Color3::Deserialize(pugi::xml_node node) {
return Color3::FromHex(node.text().get());
}

View file

@ -2,36 +2,33 @@
#include "base.h"
#include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp>
namespace Data {
class DEF_DATA Color3 : public Base {
AUTOGEN_PREAMBLE_DATA
class DEF_DATA Color3 {
AUTOGEN_PREAMBLE_DATA
float r;
float g;
float b;
public:
DEF_DATA_CTOR Color3(float r, float g, float b);
Color3(const glm::vec3&);
~Color3();
float r;
float g;
float b;
DEF_DATA_METHOD static Color3 FromHex(std::string hex);
public:
DEF_DATA_CTOR Color3(float r, float g, float b);
Color3(const glm::vec3&);
~Color3();
virtual const Data::String ToString() const override;
DEF_DATA_METHOD std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const override;
static Data::Variant Deserialize(pugi::xml_node node);
DEF_DATA_METHOD static Color3 FromHex(std::string hex);
static void PushLuaLibrary(lua_State*);
virtual const std::string ToString() const;
DEF_DATA_METHOD std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const;
static result<Color3, DataParseError> Deserialize(pugi::xml_node node);
operator glm::vec3() const;
static void PushLuaLibrary(lua_State*);
DEF_DATA_PROP inline float R() const { return r; }
DEF_DATA_PROP inline float G() const { return g; }
DEF_DATA_PROP inline float B() const { return b; }
};
}
operator glm::vec3() const;
using Data::Color3;
DEF_DATA_PROP inline float R() const { return r; }
DEF_DATA_PROP inline float G() const { return g; }
DEF_DATA_PROP inline float B() const { return b; }
};

View file

@ -0,0 +1,66 @@
#include "enum.h"
#include "datatypes/base.h"
#include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp>
TypeMeta::TypeMeta(Enum* enum_) : enum_(enum_), descriptor(&EnumItem::TYPE) {}
Enum::Enum(_EnumData* data) : data(data) {}
std::vector<EnumItem> Enum::GetEnumItems() const {
std::vector<EnumItem> enumItems;
for (int i = 0; i < data->count; i++) {
enumItems.push_back(EnumItem(data, data->values[i].second, data->values[i].first));
}
return enumItems;
}
std::optional<EnumItem> Enum::FromName(std::string name) const {
for (int i = 0; i < data->count; i++) {
if (data->values[i].second == name)
return EnumItem(data, name, data->values[i].first);
}
return {};
}
std::optional<EnumItem> Enum::FromValue(int value) const {
for (int i = 0; i < data->count; i++) {
if (data->values[i].first == value)
return EnumItem(data, data->values[i].second, value);
}
return {};
}
EnumItem::EnumItem(_EnumData* parentData, std::string name, int value) : parentData(parentData), name(name), value(value) {}
//
std::string Enum::ToString() const {
return "Enum." + this->data->name;
}
//
std::string EnumItem::ToString() const {
return "Enum." + parentData->name + "." + name;
}
void EnumItem::Serialize(pugi::xml_node node) const {
node.set_name("token");
node.text().set(value);
}
result<EnumItem, DataParseError> EnumItem::Deserialize(pugi::xml_node node, const TypeMeta info) {
auto result = info.enum_->FromValue(node.text().as_int());
if (result.has_value()) return result.value();
return DataParseError(node.text().as_string(), "EnumItem");
}
result<EnumItem, DataParseError> EnumItem::FromString(std::string string, const TypeMeta info) {
auto result = info.enum_->FromName(string);
if (result.has_value()) return result.value();
return DataParseError(string, "EnumItem");
}

55
core/src/datatypes/enum.h Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include "base.h"
#include <optional>
#include <string>
#include <vector>
#include "datatypes/annotation.h"
#include "error/data.h"
#include "lua.h"
struct _EnumData {
std::pair<int, std::string>* values;
std::string name;
int count;
};
class EnumItem;
class DEF_DATA Enum {
_EnumData* data;
public:
Enum(_EnumData*);
static const TypeDesc TYPE;
inline _EnumData* InternalType() const { return this->data; };
std::vector<EnumItem> GetEnumItems() const;
std::optional<EnumItem> FromName(std::string) const;
std::optional<EnumItem> FromValue(int) const;
std::string ToString() const;
void PushLuaValue(lua_State*) const;
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int);
};
class DEF_DATA EnumItem {
_EnumData* parentData;
std::string name;
int value;
public:
EnumItem(_EnumData*, std::string, int);
static const TypeDesc TYPE;
inline std::string Name() const { return this->name; }
inline int Value() const { return this->value; }
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,48 +0,0 @@
#include "meta.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include "datatypes/ref.h"
#include "logger.h"
#include "panic.h"
#include <pugixml.hpp>
#include <variant>
Data::String Data::Variant::ToString() const {
return std::visit([](auto&& it) {
return it.ToString();
}, this->wrapped);
}
void Data::Variant::Serialize(pugi::xml_node node) const {
std::visit([&](auto&& it) {
it.Serialize(node);
}, this->wrapped);
}
void Data::Variant::PushLuaValue(lua_State* state) const {
return std::visit([&](auto&& it) {
return it.PushLuaValue(state);
}, this->wrapped);
}
Data::Variant Data::Variant::Deserialize(pugi::xml_node node) {
if (Data::TYPE_MAP.count(node.name()) == 0) {
Logger::fatalErrorf("Unknown type for property: '%s'", node.name());
panic();
}
const Data::TypeInfo* type = Data::TYPE_MAP[node.name()];
return type->deserializer(node);
}
std::map<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
{ "null", &Data::Null::TYPE },
{ "bool", &Data::Bool::TYPE },
{ "int", &Data::Int::TYPE },
{ "float", &Data::Float::TYPE },
{ "string", &Data::String::TYPE },
{ "Vector3", &Data::Vector3::TYPE },
{ "CoordinateFrame", &Data::CFrame::TYPE },
{ "Color3", &Data::Color3::TYPE },
{ "Ref", &Data::InstanceRef::TYPE },
};

View file

@ -1,49 +0,0 @@
#pragma once
#include <variant>
#include <map>
#include "base.h"
#include "datatypes/color3.h"
#include "datatypes/ref.h"
#include "datatypes/signal.h"
#include "vector.h"
#include "cframe.h"
// #define __VARIANT_TYPE std::variant< \
// Null, \
// Bool, \
// Int, \
// Float, \
// String \
// >
namespace Data {
typedef std::variant<
Null,
Bool,
Int,
Float,
String,
Vector3,
CFrame,
Color3,
InstanceRef,
SignalRef,
SignalConnectionRef
> __VARIANT_TYPE;
class Variant {
__VARIANT_TYPE wrapped;
public:
template <typename T> Variant(T obj) : wrapped(obj) {}
template <typename T> T get() { return std::get<T>(wrapped); }
Data::String ToString() const;
void Serialize(pugi::xml_node node) const;
void PushLuaValue(lua_State* state) const;
static Data::Variant Deserialize(pugi::xml_node node);
};
// Map of all data types to their type names
extern std::map<std::string, const TypeInfo*> TYPE_MAP;
}

View file

@ -0,0 +1,212 @@
#include "primitives.h"
#include "error/data.h"
#include "variant.h"
#include <pugixml.hpp>
#include "lua.h"
#include <sstream>
// null
void Null_Serialize(Variant self, pugi::xml_node node) {
node.text().set("null");
}
result<Variant, DataParseError> Null_Deserialize(pugi::xml_node node, const TypeMeta) {
return std::monostate();
}
const std::string Null_ToString(Variant self) {
return "null";
}
result<Variant, DataParseError> Null_FromString(std::string string, const TypeMeta) {
return std::monostate();
}
void Null_PushLuaValue(Variant self, lua_State* L) {
lua_pushnil(L);
}
result<Variant, LuaCastError> Null_FromLuaValue(lua_State* L, int idx) {
return Variant(std::monostate());
}
const TypeDesc NULL_TYPE {
"null",
Null_Serialize,
Null_Deserialize,
Null_ToString,
Null_FromString,
Null_PushLuaValue,
Null_FromLuaValue,
};
// /null
// bool
void Bool_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<bool>() ? "true" : "false");
}
result<Variant, DataParseError> Bool_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_bool();
}
const std::string Bool_ToString(Variant self) {
return self.get<bool>() ? "true" : "false";
}
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';
}
void Bool_PushLuaValue(Variant self, lua_State* L) {
lua_pushboolean(L, self.get<bool>());
}
result<Variant, LuaCastError> Bool_FromLuaValue(lua_State* L, int idx) {
if (!lua_isboolean(L, idx))
return LuaCastError(lua_typename(L, idx), "boolean");
return Variant(lua_toboolean(L, idx));
}
const TypeDesc BOOL_TYPE {
"bool",
Bool_Serialize,
Bool_Deserialize,
Bool_ToString,
Bool_FromString,
Bool_PushLuaValue,
Bool_FromLuaValue,
};
// /bool
// int
void Int_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<int>());
}
result<Variant, DataParseError> Int_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_int();
}
const std::string Int_ToString(Variant self) {
return std::to_string(self.get<int>());
}
result<Variant, DataParseError> Int_FromString(std::string string, const TypeMeta) {
char* endPos;
int value = (int)std::strtol(string.c_str(), &endPos, 10);
if (endPos == string.c_str()) return DataParseError(string, "int");
return value;
}
void Int_PushLuaValue(Variant self, lua_State* L) {
lua_pushinteger(L, self.get<int>());
}
result<Variant, LuaCastError> Int_FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "integer");
return Variant((int)lua_tonumber(L, idx));
}
const TypeDesc INT_TYPE {
"int",
Int_Serialize,
Int_Deserialize,
Int_ToString,
Int_FromString,
Int_PushLuaValue,
Int_FromLuaValue,
};
// /int
// float
void Float_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<float>());
}
result<Variant, DataParseError> Float_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_float();
}
const std::string Float_ToString(Variant self) {
std::stringstream stream;
stream << std::noshowpoint << self.get<float>();
return stream.str();
}
result<Variant, DataParseError> Float_FromString(std::string string, const TypeMeta) {
char* endPos;
float value = std::strtof(string.c_str(), &endPos);
if (endPos == string.c_str()) return DataParseError(string, "float");
return value;
}
void Float_PushLuaValue(Variant self, lua_State* L) {
lua_pushnumber(L, self.get<float>());
}
result<Variant, LuaCastError> Float_FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "float");
return Variant((float)lua_tonumber(L, idx));
}
const TypeDesc FLOAT_TYPE {
"float",
Float_Serialize,
Float_Deserialize,
Float_ToString,
Float_FromString,
Float_PushLuaValue,
Float_FromLuaValue,
};
// /float
// string
void String_Serialize(Variant self, pugi::xml_node node) {
node.text().set(self.get<std::string>());
}
result<Variant, DataParseError> String_Deserialize(pugi::xml_node node, const TypeMeta) {
return node.text().as_string();
}
const std::string String_ToString(Variant self) {
return self.get<std::string>();
}
result<Variant, DataParseError> String_FromString(std::string string, const TypeMeta) {
return string;
}
void String_PushLuaValue(Variant self, lua_State* L) {
lua_pushstring(L, self.get<std::string>().c_str());
}
result<Variant, LuaCastError> String_FromLuaValue(lua_State* L, int idx) {
if (!lua_tostring(L, idx))
return LuaCastError(lua_typename(L, idx), "string");
return Variant(lua_tostring(L, idx));
}
const TypeDesc STRING_TYPE {
"string",
String_Serialize,
String_Deserialize,
String_ToString,
String_FromString,
String_PushLuaValue,
String_FromLuaValue,
};
// /string

View file

@ -0,0 +1,9 @@
#pragma once
#include "base.h"
extern const TypeDesc NULL_TYPE;
extern const TypeDesc BOOL_TYPE;
extern const TypeDesc INT_TYPE;
extern const TypeDesc FLOAT_TYPE;
extern const TypeDesc STRING_TYPE;

View file

@ -2,7 +2,7 @@
#include "datatypes/base.h"
#include "error/data.h"
#include "logger.h"
#include "meta.h" // IWYU pragma: keep
#include "variant.h" // IWYU pragma: keep
#include <memory>
#include <optional>
#include "objects/base/instance.h"
@ -10,34 +10,40 @@
#include "objects/base/member.h"
#include <pugixml.hpp>
Data::InstanceRef::InstanceRef() {};
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
Data::InstanceRef::~InstanceRef() = default;
TypeMeta::TypeMeta(InstanceType* instType) : instType(instType), descriptor(&InstanceRef::TYPE) {}
const Data::TypeInfo Data::InstanceRef::TYPE = {
InstanceRef::InstanceRef() {};
InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
InstanceRef::~InstanceRef() = default;
const TypeDesc InstanceRef::TYPE = {
.name = "Ref",
.deserializer = &Data::InstanceRef::Deserialize,
.fromLuaValue = &Data::InstanceRef::FromLuaValue,
.serialize = toVariantFunction(&InstanceRef::Serialize),
.deserialize = toVariantGeneratorNoMeta(&InstanceRef::Deserialize),
.toString = toVariantFunction(&InstanceRef::ToString),
.fromString = nullptr,
.pushLuaValue = toVariantFunction(&InstanceRef::PushLuaValue),
.fromLuaValue = &InstanceRef::FromLuaValue,
};
const Data::TypeInfo& Data::InstanceRef::GetType() const { return Data::InstanceRef::TYPE; };
const Data::String Data::InstanceRef::ToString() const {
const std::string InstanceRef::ToString() const {
return ref.expired() ? "" : ref.lock()->name;
}
Data::InstanceRef::operator std::weak_ptr<Instance>() {
InstanceRef::operator std::weak_ptr<Instance>() {
return ref;
}
// Serialization
void Data::InstanceRef::Serialize(pugi::xml_node node) const {
// node.text().set(this->ToHex());
void InstanceRef::Serialize(pugi::xml_node node) const {
// Handled by Instance
panic();
}
Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) {
return Data::InstanceRef();
result<InstanceRef, DataParseError> InstanceRef::Deserialize(pugi::xml_node node) {
// Handled by Instance
panic();
}
static int inst_gc(lua_State*);
@ -50,7 +56,7 @@ static const struct luaL_Reg metatable [] = {
{NULL, NULL} /* end of array */
};
void Data::InstanceRef::PushLuaValue(lua_State* L) const {
void InstanceRef::PushLuaValue(lua_State* L) const {
if (ref.expired()) return lua_pushnil(L);
int n = lua_gettop(L);
@ -68,14 +74,14 @@ void Data::InstanceRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::InstanceRef::FromLuaValue(lua_State* L, int idx) {
result<Variant, LuaCastError> InstanceRef::FromLuaValue(lua_State* L, int idx) {
if (lua_isnil(L, idx))
return Data::Variant(Data::InstanceRef());
return Variant(InstanceRef());
if (!lua_isuserdata(L, idx))
return LuaCastError(lua_typename(L, idx), "Instance");
// TODO: overhaul this to support other types
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, idx);
return Data::Variant(Data::InstanceRef(**userdata));
return Variant(InstanceRef(**userdata));
}
static int inst_gc(lua_State* L) {
@ -97,7 +103,7 @@ static int inst_index(lua_State* L) {
// Read property
std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key);
if (meta) {
Data::Variant value = inst->GetPropertyValue(key).expect();
Variant value = inst->GetPropertyValue(key).expect();
value.PushLuaValue(L);
return 1;
}
@ -105,7 +111,7 @@ static int inst_index(lua_State* L) {
// Look for child
std::optional<std::shared_ptr<Instance>> child = inst->FindFirstChild(key);
if (child) {
Data::InstanceRef(child.value()).PushLuaValue(L);
InstanceRef(child.value()).PushLuaValue(L);
return 1;
}
@ -127,7 +133,8 @@ static int inst_newindex(lua_State* L) {
if (key == "Parent" && inst->IsParentLocked())
return luaL_error(L, "Cannot set property Parent (%s) of %s, parent is locked", inst->GetParent() ? inst->GetParent().value()->name.c_str() : "NULL", inst->GetClass()->className.c_str());
result<Data::Variant, LuaCastError> value = meta->type->fromLuaValue(L, -1);
// TODO: Make this work for enums, this is not a solution!!
result<Variant, LuaCastError> value = meta->type.descriptor->fromLuaValue(L, -1);
lua_pop(L, 3);
if (value.isError())

View file

@ -6,23 +6,20 @@
class Instance;
namespace Data {
class InstanceRef : public Base {
std::weak_ptr<Instance> ref;
public:
InstanceRef();
InstanceRef(std::weak_ptr<Instance>);
~InstanceRef();
class InstanceRef {
std::weak_ptr<Instance> ref;
public:
InstanceRef();
InstanceRef(std::weak_ptr<Instance>);
~InstanceRef();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
static const TypeDesc TYPE;
operator std::weak_ptr<Instance>();
operator std::weak_ptr<Instance>();
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
}
virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const;
static result<InstanceRef, DataParseError> Deserialize(pugi::xml_node node);
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};

View file

@ -1,6 +1,6 @@
#include "signal.h"
#include "datatypes/base.h"
#include "meta.h"
#include "variant.h"
#include "lua.h"
#include <cstdio>
#include <luajit-2.1/lauxlib.h>
@ -53,13 +53,13 @@ static void stackdump(lua_State* L) {
fflush(stdout);
}
void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
void LuaSignalConnection::Call(std::vector<Variant> args) {
lua_State* thread = lua_newthread(state);
// Push function
lua_rawgeti(thread, LUA_REGISTRYINDEX, function);
for (Data::Variant arg : args) {
for (Variant arg : args) {
arg.PushLuaValue(thread);
}
@ -74,11 +74,11 @@ void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
//
CSignalConnection::CSignalConnection(std::function<void(std::vector<Data::Variant>)> func, std::weak_ptr<Signal> parent) : SignalConnection(parent) {
CSignalConnection::CSignalConnection(std::function<void(std::vector<Variant>)> func, std::weak_ptr<Signal> parent) : SignalConnection(parent) {
this->function = func;
}
void CSignalConnection::Call(std::vector<Data::Variant> args) {
void CSignalConnection::Call(std::vector<Variant> args) {
function(args);
}
@ -86,7 +86,7 @@ void CSignalConnection::Call(std::vector<Data::Variant> args) {
SignalConnectionHolder::SignalConnectionHolder() : heldConnection() {}
SignalConnectionHolder::SignalConnectionHolder(std::shared_ptr<SignalConnection> connection) : heldConnection(connection) {}
SignalConnectionHolder::SignalConnectionHolder(Data::SignalConnectionRef other) : heldConnection(other) {}
SignalConnectionHolder::SignalConnectionHolder(SignalConnectionRef other) : heldConnection(other) {}
SignalConnectionHolder::~SignalConnectionHolder() {
// printf("Prediscon!\n");
@ -97,7 +97,7 @@ SignalConnectionHolder::~SignalConnectionHolder() {
//
SignalConnectionRef Signal::Connect(std::function<void(std::vector<Data::Variant>)> callback) {
SignalConnectionRef Signal::Connect(std::function<void(std::vector<Variant>)> callback) {
auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
connections.push_back(conn);
return SignalConnectionRef(conn);
@ -109,7 +109,7 @@ SignalConnectionRef Signal::Connect(lua_State* state) {
return SignalConnectionRef(conn);
}
SignalConnectionRef Signal::Once(std::function<void(std::vector<Data::Variant>)> callback) {
SignalConnectionRef Signal::Once(std::function<void(std::vector<Variant>)> callback) {
auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
onceConnections.push_back(conn);
return SignalConnectionRef(conn);
@ -132,7 +132,7 @@ int Signal::Wait(lua_State* thread) {
return lua_yield(thread, 0);
}
void Signal::Fire(std::vector<Data::Variant> args) {
void Signal::Fire(std::vector<Variant> args) {
for (std::shared_ptr<SignalConnection> connection : connections) {
connection->Call(args);
}
@ -148,7 +148,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
auto prevThreads = std::move(waitingThreads);
waitingThreads = std::vector<std::pair<int, lua_State*>>();
for (auto& [threadId, thread] : prevThreads) {
for (Data::Variant arg : args) {
for (Variant arg : args) {
arg.PushLuaValue(thread);
}
@ -165,7 +165,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
}
void Signal::Fire() {
return Fire(std::vector<Data::Variant> {});
return Fire(std::vector<Variant> {});
}
void Signal::DisconnectAll() {
@ -222,29 +222,29 @@ static const struct luaL_Reg signal_metatable [] = {
{NULL, NULL} /* end of array */
};
Data::SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
Data::SignalRef::~SignalRef() = default;
SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
SignalRef::~SignalRef() = default;
const Data::TypeInfo Data::SignalRef::TYPE = {
const TypeDesc SignalRef::TYPE = {
.name = "Signal",
.fromLuaValue = &Data::SignalRef::FromLuaValue,
.toString = toVariantFunction(&SignalRef::ToString),
.pushLuaValue = toVariantFunction(&SignalRef::PushLuaValue),
.fromLuaValue = &SignalRef::FromLuaValue,
};
const Data::TypeInfo& Data::SignalRef::GetType() const { return Data::SignalRef::TYPE; };
const Data::String Data::SignalRef::ToString() const {
return Data::String("Signal");
const std::string SignalRef::ToString() const {
return "Signal";
}
Data::SignalRef::operator std::weak_ptr<Signal>() {
SignalRef::operator std::weak_ptr<Signal>() {
return signal;
}
void Data::SignalRef::Serialize(pugi::xml_node node) const {
void SignalRef::Serialize(pugi::xml_node node) const {
// Not serializable
}
void Data::SignalRef::PushLuaValue(lua_State* L) const {
void SignalRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L);
auto userdata = (std::weak_ptr<Signal>**)lua_newuserdata(L, sizeof(std::weak_ptr<Signal>));
@ -257,10 +257,10 @@ void Data::SignalRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::SignalRef::FromLuaValue(lua_State* L, int idx) {
result<Variant, LuaCastError> SignalRef::FromLuaValue(lua_State* L, int idx) {
auto userdata = (std::weak_ptr<Signal>**)luaL_checkudata(L, 1, "__mt_signal");
lua_pop(L, 1);
return Data::Variant(Data::SignalRef(**userdata));
return Variant(SignalRef(**userdata));
}
static int signal_gc(lua_State* L) {
@ -343,29 +343,29 @@ static const struct luaL_Reg signalconnection_metatable [] = {
{NULL, NULL} /* end of array */
};
Data::SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
Data::SignalConnectionRef::~SignalConnectionRef() = default;
SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
SignalConnectionRef::~SignalConnectionRef() = default;
const Data::TypeInfo Data::SignalConnectionRef::TYPE = {
const TypeDesc SignalConnectionRef::TYPE = {
.name = "Signal",
.fromLuaValue = &Data::SignalConnectionRef::FromLuaValue,
.toString = toVariantFunction(&SignalConnectionRef::ToString),
.pushLuaValue = toVariantFunction(&SignalConnectionRef::PushLuaValue),
.fromLuaValue = &SignalConnectionRef::FromLuaValue,
};
const Data::TypeInfo& Data::SignalConnectionRef::GetType() const { return Data::SignalConnectionRef::TYPE; };
const Data::String Data::SignalConnectionRef::ToString() const {
return Data::String("Connection");
const std::string SignalConnectionRef::ToString() const {
return "Connection";
}
Data::SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
return signalConnection;
}
void Data::SignalConnectionRef::Serialize(pugi::xml_node node) const {
void SignalConnectionRef::Serialize(pugi::xml_node node) const {
// Not serializable
}
void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
void SignalConnectionRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L);
auto userdata = (std::weak_ptr<SignalConnection>**)lua_newuserdata(L, sizeof(std::weak_ptr<SignalConnection>));
@ -378,10 +378,10 @@ void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
result<Variant, LuaCastError> SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
auto userdata = (std::weak_ptr<SignalConnection>**)luaL_checkudata(L, 1, "__mt_signalconnection");
lua_pop(L, 1);
return Data::Variant(Data::SignalConnectionRef(**userdata));
return Variant(SignalConnectionRef(**userdata));
}
static int signalconnection_tostring(lua_State* L) {

View file

@ -15,7 +15,7 @@
class Instance;
class Signal;
namespace Data { class SignalConnectionRef; }
class SignalConnectionRef;
class SignalConnection : public std::enable_shared_from_this<SignalConnection> {
protected:
@ -23,7 +23,7 @@ protected:
SignalConnection(std::weak_ptr<Signal> parent);
virtual void Call(std::vector<Data::Variant>) = 0;
virtual void Call(std::vector<Variant>) = 0;
friend Signal;
public:
inline bool Connected() { return !parentSignal.expired(); };
@ -33,13 +33,13 @@ public:
};
class CSignalConnection : public SignalConnection {
std::function<void(std::vector<Data::Variant>)> function;
std::function<void(std::vector<Variant>)> function;
friend Signal;
protected:
void Call(std::vector<Data::Variant>) override;
void Call(std::vector<Variant>) override;
public:
CSignalConnection(std::function<void(std::vector<Data::Variant>)>, std::weak_ptr<Signal> parent);
CSignalConnection(std::function<void(std::vector<Variant>)>, std::weak_ptr<Signal> parent);
};
class LuaSignalConnection : public SignalConnection {
@ -48,7 +48,7 @@ class LuaSignalConnection : public SignalConnection {
friend Signal;
protected:
void Call(std::vector<Data::Variant>) override;
void Call(std::vector<Variant>) override;
public:
LuaSignalConnection(lua_State*, std::weak_ptr<Signal> parent);
LuaSignalConnection (const LuaSignalConnection&) = delete;
@ -63,7 +63,7 @@ class SignalConnectionHolder {
public:
SignalConnectionHolder();
SignalConnectionHolder(std::shared_ptr<SignalConnection>);
SignalConnectionHolder(Data::SignalConnectionRef other);
SignalConnectionHolder(SignalConnectionRef other);
~SignalConnectionHolder();
// Prevent SignalConnectionHolder being accidentally copied, making it useless
@ -90,12 +90,12 @@ public:
Signal& operator= (const Signal&) = delete;
void DisconnectAll();
void Fire(std::vector<Data::Variant> args);
void Fire(std::vector<Variant> args);
void Fire();
Data::SignalConnectionRef Connect(std::function<void(std::vector<Data::Variant>)> callback);
Data::SignalConnectionRef Connect(lua_State*);
Data::SignalConnectionRef Once(std::function<void(std::vector<Data::Variant>)> callback);
Data::SignalConnectionRef Once(lua_State*);
SignalConnectionRef Connect(std::function<void(std::vector<Variant>)> callback);
SignalConnectionRef Connect(lua_State*);
SignalConnectionRef Once(std::function<void(std::vector<Variant>)> callback);
SignalConnectionRef Once(lua_State*);
int Wait(lua_State*);
};
@ -105,43 +105,36 @@ public:
virtual ~SignalSource();
};
namespace Data {
class SignalRef : public Data::Base {
std::weak_ptr<Signal> signal;
class SignalRef {
std::weak_ptr<Signal> signal;
public:
SignalRef(std::weak_ptr<Signal>);
~SignalRef();
public:
SignalRef(std::weak_ptr<Signal>);
~SignalRef();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
static const TypeDesc TYPE;
operator std::weak_ptr<Signal>();
operator std::weak_ptr<Signal>();
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<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const;
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
class SignalConnectionRef : public Data::Base {
std::weak_ptr<SignalConnection> signalConnection;
class SignalConnectionRef {
std::weak_ptr<SignalConnection> signalConnection;
public:
SignalConnectionRef(std::weak_ptr<SignalConnection>);
~SignalConnectionRef();
public:
SignalConnectionRef(std::weak_ptr<SignalConnection>);
~SignalConnectionRef();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
static const TypeDesc TYPE;
operator std::weak_ptr<SignalConnection>();
operator std::weak_ptr<SignalConnection>();
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<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
}
using Data::SignalRef;
using Data::SignalConnectionRef;
virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const;
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};

View file

@ -0,0 +1,88 @@
#include "variant.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include "datatypes/enum.h"
#include "datatypes/primitives.h"
#include "datatypes/ref.h"
#include "datatypes/signal.h"
#include "datatypes/vector.h"
#include "error/data.h"
#include "logger.h"
#include "panic.h"
#include <pugixml.hpp>
#include <string>
#include <variant>
[[noreturn]] inline void unreachable() {
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
__assume(false);
#else // GCC, Clang
__builtin_unreachable();
#endif
}
const TypeDesc* VARIANT_TYPES[] {
&NULL_TYPE,
&BOOL_TYPE,
&INT_TYPE,
&FLOAT_TYPE,
&STRING_TYPE,
&Vector3::TYPE,
&CFrame::TYPE,
&Color3::TYPE,
&InstanceRef::TYPE,
&SignalRef::TYPE,
&SignalConnectionRef::TYPE,
&Enum::TYPE,
&EnumItem::TYPE,
};
const TypeMeta Variant::GetTypeMeta() const {
return VARIANT_TYPES[wrapped.index()];
}
const TypeDesc* Variant::GetType() const {
return VARIANT_TYPES[wrapped.index()];
}
std::string Variant::ToString() const {
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
Logger::fatalErrorf("Data type %s does not implement toString", VARIANT_TYPES[wrapped.index()]->name.c_str());
}
return VARIANT_TYPES[wrapped.index()]->toString(*this);
}
void Variant::Serialize(pugi::xml_node node) const {
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
Logger::fatalErrorf("Data type %s does not implement serializer", VARIANT_TYPES[wrapped.index()]->name.c_str());
}
VARIANT_TYPES[wrapped.index()]->serialize(*this, node);
}
void Variant::PushLuaValue(lua_State* state) const {
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
Logger::fatalErrorf("Data type %s does not implement pushLuaValue", VARIANT_TYPES[wrapped.index()]->name.c_str());
}
VARIANT_TYPES[wrapped.index()]->pushLuaValue(*this, state);
}
result<Variant, DataParseError> Variant::Deserialize(pugi::xml_node node, const TypeMeta type) {
if (!type.descriptor->deserialize) {
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->deserialize(node, type);
}
std::map<std::string, const TypeDesc*> TYPE_MAP = {
{ "null", &NULL_TYPE },
{ "bool", &BOOL_TYPE },
{ "int", &INT_TYPE },
{ "float", &FLOAT_TYPE },
{ "string", &STRING_TYPE },
{ "Vector3", &Vector3::TYPE },
{ "CoordinateFrame", &CFrame::TYPE },
{ "Color3", &Color3::TYPE },
{ "Ref", &InstanceRef::TYPE },
{ "token", &EnumItem::TYPE },
};

View file

@ -0,0 +1,94 @@
#pragma once
#include <type_traits>
#include <variant>
#include <map>
#include "base.h"
#include "datatypes/color3.h"
#include "datatypes/enum.h"
#include "datatypes/ref.h"
#include "datatypes/signal.h"
#include "error/data.h"
#include "vector.h"
#include "cframe.h"
// #define __VARIANT_TYPE std::variant< \
// Null, \
// Bool, \
// Int, \
// Float, \
// String \
// >
typedef std::variant<
std::monostate,
bool,
int,
float,
std::string,
Vector3,
CFrame,
Color3,
InstanceRef,
SignalRef,
SignalConnectionRef,
Enum,
EnumItem
> __VARIANT_TYPE;
class Variant {
__VARIANT_TYPE wrapped;
public:
template <typename T, std::enable_if_t<std::is_constructible_v<__VARIANT_TYPE, T>, int> = 0> Variant(T obj) : wrapped(obj) {}
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;
const TypeMeta GetTypeMeta() const;
const TypeDesc* GetType() const;
void Serialize(pugi::xml_node node) const;
void PushLuaValue(lua_State* state) const;
static result<Variant, DataParseError> Deserialize(pugi::xml_node node, const TypeMeta);
};
template <typename T, typename R, typename ...Args>
std::function<R(Variant, Args...)> toVariantFunction(R(T::*f)(Args...)) {
return [f](Variant var, Args... args) {
return (var.get<T>().*f)(args...);
};
}
template <typename T, typename R, typename ...Args>
std::function<R(Variant, Args...)> toVariantFunction(R(T::*f)(Args...) const) {
return [f](Variant var, Args... args) {
return (var.get<T>().*f)(args...);
};
}
template <typename T, typename ...Args>
std::function<Variant(Args...)> toVariantGenerator(T(f)(Args...)) {
return [f](Args... args) {
return (Variant)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
extern std::map<std::string, const TypeDesc*> TYPE_MAP;

View file

@ -7,95 +7,96 @@
#include <string>
#include <pugixml.hpp>
#include "datatypes/base.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "error/data.h"
#include <sstream>
namespace rp = reactphysics3d;
Data::Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
Data::Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
Vector3::Vector3(const glm::vec3& src) : vector(src) {};
Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
Data::Vector3::~Vector3() = default;
Vector3::~Vector3() = default;
Data::Vector3 Data::Vector3::ZERO(0, 0, 0);
Data::Vector3 Data::Vector3::ONE(1, 1, 1);
Vector3 Vector3::ZERO(0, 0, 0);
Vector3 Vector3::ONE(1, 1, 1);
const Data::String Data::Vector3::ToString() const {
const std::string Vector3::ToString() const {
// https://stackoverflow.com/a/46424921/16255372
std::stringstream stream;
stream << std::setprecision(8) << std::noshowpoint << X() << ", " << Y() << ", " << Z();
return stream.str();
}
Data::Vector3::operator glm::vec3() const { return vector; };
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
Vector3::operator glm::vec3() const { return vector; };
Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
// Operators
Data::Vector3 Data::Vector3::operator *(float scale) const {
return Data::Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale);
Vector3 Vector3::operator *(float scale) const {
return Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale);
}
Data::Vector3 Data::Vector3::operator /(float scale) const {
return Data::Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale);
Vector3 Vector3::operator /(float scale) const {
return Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale);
}
// Component-wise
Data::Vector3 Data::Vector3::operator *(Data::Vector3 other) const {
return Data::Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z());
Vector3 Vector3::operator *(Vector3 other) const {
return Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z());
}
Data::Vector3 Data::Vector3::operator +(Data::Vector3 other) const {
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
Vector3 Vector3::operator +(Vector3 other) const {
return Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
}
Data::Vector3 Data::Vector3::operator -(Data::Vector3 other) const {
return Data::Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
Vector3 Vector3::operator -(Vector3 other) const {
return Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
}
Data::Vector3 Data::Vector3::operator -() const {
return Data::Vector3(-this->X(), -this->Y(), -this->Z());
Vector3 Vector3::operator -() const {
return Vector3(-this->X(), -this->Y(), -this->Z());
}
bool Data::Vector3::operator ==(Data::Vector3 other) const {
bool Vector3::operator ==(Vector3 other) const {
return this->X() == other.X() && this->Y() == other.Y() && this->Z() == other.Z();
}
bool Data::Vector3::operator <(Data::Vector3 other) const {
bool Vector3::operator <(Vector3 other) const {
return X() < other.X() && Y() < other.Y() && Z() < other.Z();
}
bool Data::Vector3::operator >(Data::Vector3 other) const {
bool Vector3::operator >(Vector3 other) const {
return X() > other.X() && Y() > other.Y() && Z() > other.Z();
}
Data::Vector3 Data::Vector3::Cross(Data::Vector3 other) const {
Vector3 Vector3::Cross(Vector3 other) const {
return glm::cross(this->vector, other.vector);
}
float Data::Vector3::Dot(Data::Vector3 other) const {
float Vector3::Dot(Vector3 other) const {
return glm::dot(this->vector, other.vector);
}
// Serialization
void Data::Vector3::Serialize(pugi::xml_node node) const {
void Vector3::Serialize(pugi::xml_node node) const {
node.append_child("X").text().set(std::to_string(this->X()));
node.append_child("Y").text().set(std::to_string(this->Y()));
node.append_child("Z").text().set(std::to_string(this->Z()));
}
Data::Variant Data::Vector3::Deserialize(pugi::xml_node node) {
return Data::Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float());
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());
}
std::optional<Data::Variant> Data::Vector3::FromString(std::string string) {
result<Vector3, DataParseError> Vector3::FromString(std::string string) {
float components[3];
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);
size_t nextPos = string.find(",");
if (nextPos == -1) nextPos = string.length();
@ -104,10 +105,10 @@ std::optional<Data::Variant> Data::Vector3::FromString(std::string string) {
char* 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;
}
return Data::Vector3(components[0], components[1], components[2]);
return Vector3(components[0], components[1], components[2]);
}

View file

@ -2,64 +2,61 @@
#include "base.h"
#include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp>
namespace reactphysics3d { class Vector3; };
namespace Data {
class DEF_DATA Vector3 : public Base {
AUTOGEN_PREAMBLE_DATA
glm::vec3 vector;
class DEF_DATA Vector3 {
AUTOGEN_PREAMBLE_DATA
glm::vec3 vector;
public:
DEF_DATA_CTOR Vector3();
DEF_DATA_CTOR Vector3(float x, float y, float z);
Vector3(const glm::vec3&);
Vector3(const reactphysics3d::Vector3&);
~Vector3();
DEF_DATA_PROP static Vector3 ZERO;
DEF_DATA_PROP static Vector3 ONE;
virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const;
static result<Vector3, DataParseError> Deserialize(pugi::xml_node node);
static result<Vector3, DataParseError> FromString(std::string);
public:
DEF_DATA_CTOR Vector3();
DEF_DATA_CTOR Vector3(float x, float y, float z);
Vector3(const glm::vec3&);
Vector3(const reactphysics3d::Vector3&);
~Vector3();
static void PushLuaLibrary(lua_State*);
DEF_DATA_PROP static Data::Vector3 ZERO;
DEF_DATA_PROP static Data::Vector3 ONE;
operator glm::vec3() const;
operator reactphysics3d::Vector3() const;
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override;
DEF_DATA_PROP inline float X() const { return vector.x; }
DEF_DATA_PROP inline float Y() const { return vector.y; }
DEF_DATA_PROP inline float Z() const { return vector.z; }
DEF_DATA_METHOD inline float Magnitude() const { return glm::length(vector); }
DEF_DATA_METHOD inline Vector3 Unit() const { return glm::normalize(vector); }
DEF_DATA_METHOD inline Vector3 Abs() const { return glm::abs(vector); }
static Data::Variant Deserialize(pugi::xml_node node);
static std::optional<Data::Variant> FromString(std::string);
static void PushLuaLibrary(lua_State*);
DEF_DATA_METHOD Vector3 Cross(Vector3) const;
DEF_DATA_METHOD float Dot(Vector3) const;
operator glm::vec3() const;
operator reactphysics3d::Vector3() const;
// Operators
DEF_DATA_OP Vector3 operator *(float) const;
DEF_DATA_OP Vector3 operator /(float) const;
DEF_DATA_OP Vector3 operator *(Vector3) const; // Component-wise
DEF_DATA_OP Vector3 operator +(Vector3) const;
DEF_DATA_OP Vector3 operator -(Vector3) const;
DEF_DATA_OP Vector3 operator -() const;
DEF_DATA_PROP inline float X() const { return vector.x; }
DEF_DATA_PROP inline float Y() const { return vector.y; }
DEF_DATA_PROP inline float Z() const { return vector.z; }
DEF_DATA_METHOD inline float Magnitude() const { return glm::length(vector); }
DEF_DATA_METHOD inline Data::Vector3 Unit() const { return glm::normalize(vector); }
DEF_DATA_METHOD inline Data::Vector3 Abs() const { return glm::abs(vector); }
DEF_DATA_OP bool operator <(Vector3) const;
DEF_DATA_OP bool operator >(Vector3) const;
DEF_DATA_METHOD Data::Vector3 Cross(Data::Vector3) const;
DEF_DATA_METHOD float Dot(Data::Vector3) const;
// Operators
DEF_DATA_OP Data::Vector3 operator *(float) const;
DEF_DATA_OP Data::Vector3 operator /(float) const;
DEF_DATA_OP Data::Vector3 operator *(Data::Vector3) const; // Component-wise
DEF_DATA_OP Data::Vector3 operator +(Data::Vector3) const;
DEF_DATA_OP Data::Vector3 operator -(Data::Vector3) const;
DEF_DATA_OP Data::Vector3 operator -() const;
DEF_DATA_OP bool operator ==(Vector3) const;
};
DEF_DATA_OP bool operator <(Data::Vector3) const;
DEF_DATA_OP bool operator >(Data::Vector3) const;
DEF_DATA_OP bool operator ==(Data::Vector3) const;
};
}
using Data::Vector3;
inline void printVec(Data::Vector3 vec) {
inline void printVec(Vector3 vec) {
printf("(%f, %f, %f)\n", vec.X(), vec.Y(), vec.Z());
}

View file

@ -4,5 +4,10 @@
class LuaCastError : public Error {
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;
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(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>
@ -61,7 +63,7 @@ public:
// Equivalent to .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(); }
};

View file

@ -37,8 +37,8 @@
#define AUTOGEN_PREAMBLE \
protected: \
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name) override; \
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value) override; \
virtual result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name) override; \
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Variant value) override; \
virtual result<Variant, MemberNotFound> InternalGetPropertyValue(std::string name) override; \
virtual void InternalUpdateProperty(std::string name) override; \
virtual std::vector<std::string> InternalGetProperties() override; \
public: \

View file

@ -1,6 +1,7 @@
#include "instance.h"
#include "common.h"
#include "datatypes/meta.h"
#include "datatypes/primitives.h"
#include "datatypes/variant.h"
#include "datatypes/base.h"
#include "datatypes/ref.h"
#include "error/instance.h"
@ -96,7 +97,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
}
OnAncestryChanged(updatedChild, newParent);
AncestryChanged->Fire({updatedChild.has_value() ? Data::InstanceRef(updatedChild.value()) : Data::InstanceRef(), newParent.has_value() ? Data::InstanceRef(newParent.value()) : Data::InstanceRef()});
AncestryChanged->Fire({updatedChild.has_value() ? InstanceRef(updatedChild.value()) : InstanceRef(), newParent.has_value() ? InstanceRef(newParent.value()) : InstanceRef()});
// Old workspace used to exist, and workspaces differ
if (!oldWorkspace.expired() && oldWorkspace != _workspace) {
@ -109,7 +110,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
}
// Update ancestry in descendants
for (InstanceRef child : children) {
for (std::shared_ptr<Instance> child : children) {
child->updateAncestry(updatedChild, newParent);
}
}
@ -179,11 +180,11 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Properties
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
result<Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
return InternalGetPropertyValue(name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value, bool sendUpdateEvent) {
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent) {
auto result = InternalSetPropertyValue(name, value);
if (result.isSuccess() && sendUpdateEvent) {
InternalUpdateProperty(name);
@ -197,33 +198,33 @@ result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name)
}
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
result<Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
if (name == "Name") {
return Data::Variant(Data::String(this->name));
return Variant(this->name);
} else if (name == "Parent") {
return Data::Variant(Data::InstanceRef(this->parent));
return Variant(InstanceRef(this->parent));
} else if (name == "ClassName") {
return Data::Variant(Data::String(GetClass()->className));
return Variant(GetClass()->className);
}
return MemberNotFound(GetClass()->className, name);
}
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
if (name == "Name") {
return PropertyMeta { &Data::String::TYPE };
return PropertyMeta { &STRING_TYPE };
} else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, };
return PropertyMeta { &InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") {
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
return PropertyMeta { &STRING_TYPE, PROP_NOSAVE | PROP_READONLY };
}
return MemberNotFound(GetClass()->className, name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Data::Variant value) {
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Variant value) {
if (name == "Name") {
this->name = (std::string)value.get<Data::String>();
this->name = (std::string)value.get<std::string>();
} else if (name == "Parent") {
std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();
std::weak_ptr<Instance> ref = value.get<InstanceRef>();
SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
} else if (name == "ClassName") {
return AssignToReadOnlyMember(GetClass()->className, name);
@ -259,7 +260,7 @@ std::vector<std::string> Instance::GetProperties() {
// Serialization
void Instance::Serialize(pugi::xml_node parent) {
void Instance::Serialize(pugi::xml_node parent, RefStateSerialize state) {
pugi::xml_node node = parent.append_child("Item");
node.append_attribute("class").set_value(this->GetClass()->className);
@ -269,24 +270,55 @@ void Instance::Serialize(pugi::xml_node parent) {
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing");
if (meta.flags & (PROP_NOSAVE | PROP_READONLY)) continue; // This property should not be serialized. Skip...
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type.descriptor->name);
propertyNode.append_attribute("name").set_value(name);
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(name).expect("Declared property is missing").get<InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef != "") {
// If the instance has already been remapped, set the new value
propertyNode.text().set(remappedRef);
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(propertyNode);
state->refsAwaitingRemap[ref] = refs;
}
} else {
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
}
}
// Remap self
std::string remappedId = "OB" + std::to_string(state->count++);
state->remappedInstances[shared_from_this()] = remappedId;
node.append_attribute("referent").set_value(remappedId);
// Remap queued properties
for (pugi::xml_node ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.text().set(remappedId);
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Add children
for (InstanceRef child : this->children) {
child->Serialize(node);
for (std::shared_ptr<Instance> child : this->children) {
child->Serialize(node, state);
}
}
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
result<std::shared_ptr<Instance>, NoSuchInstance> Instance::Deserialize(pugi::xml_node node, RefStateDeserialize state) {
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
return NoSuchInstance(className);
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = INSTANCE_MAP[className]->constructor();
std::shared_ptr<Instance> object = INSTANCE_MAP[className]->constructor();
object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className);
@ -300,13 +332,51 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
auto meta = meta_.expect();
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
if (propertyNode.text().empty())
continue;
std::string refId = propertyNode.text().as_string();
auto remappedRef = state->remappedInstances[refId]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef) {
// If the instance has already been remapped, set the new value
object->SetPropertyValue(propertyName, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[refId];
refs.push_back(std::make_pair(object, propertyName));
state->refsAwaitingRemap[refId] = refs;
object->SetPropertyValue(propertyName, InstanceRef()).expect();
}
} else {
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");
}
}
// Remap self
std::string remappedId = node.attribute("referent").value();
state->remappedInstances[remappedId] = object;
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[remappedId]) {
ref.first->SetPropertyValue(ref.second, InstanceRef(object)).expect();
}
state->refsAwaitingRemap[remappedId].clear();
// Read children
for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
result<std::shared_ptr<Instance>, NoSuchInstance> child = Instance::Deserialize(childNode, state);
if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
@ -362,7 +432,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
return *this;
}
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) {
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
std::shared_ptr<Instance> newInstance = GetClass()->constructor();
// Copy properties
@ -371,9 +441,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update InstanceRef properties using map above
if (meta.type == &Data::InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
@ -381,17 +451,17 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (remappedRef) {
// If the instance has already been remapped, set the new value
newInstance->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
newInstance->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newInstance, property));
state->refsAwaitingRemap[ref] = refs;
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
newInstance->SetPropertyValue(property, InstanceRef(ref)).expect();
}
} else {
Data::Variant value = GetPropertyValue(property).expect();
Variant value = GetPropertyValue(property).expect();
newInstance->SetPropertyValue(property, value).expect();
}
}
@ -401,8 +471,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
ref.first->SetPropertyValue(ref.second, InstanceRef(newInstance)).expect();
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Clone children
for (std::shared_ptr<Instance> child : GetChildren()) {
@ -421,9 +492,9 @@ std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetRefe
for (std::string property : propertyNames) {
PropertyMeta meta = GetPropertyMeta(property).expect();
if (meta.type != &Data::InstanceRef::TYPE) continue;
if (meta.type.descriptor != &InstanceRef::TYPE) continue;
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<Data::InstanceRef>();
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<InstanceRef>();
if (ref.expired()) continue;
referenceProperties.push_back(std::make_pair(property, ref.lock()));
}

View file

@ -42,8 +42,6 @@ struct InstanceType {
InstanceFlags flags;
};
typedef std::pair<std::shared_ptr<Instance>, std::string> _RefStatePropertyCell;
class DescendantsIterator;
class JointInstance;
@ -71,8 +69,8 @@ protected:
Instance(const InstanceType*);
virtual ~Instance();
virtual result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value);
virtual result<Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Variant value);
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name);
virtual void InternalUpdateProperty(std::string name);
virtual std::vector<std::string> InternalGetProperties();
@ -116,8 +114,8 @@ public:
std::string GetFullName();
// Properties
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Data::Variant value, bool sendUpdateEvent = true);
result<Variant, MemberNotFound> GetPropertyValue(std::string name);
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent = true);
result<PropertyMeta, MemberNotFound> GetPropertyMeta(std::string name);
// Manually trigger the update of a property. Useful internally when setting properties directly
void UpdateProperty(std::string name);
@ -135,14 +133,11 @@ public:
}
// Serialization
void Serialize(pugi::xml_node parent);
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
std::optional<std::shared_ptr<Instance>> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>());
void Serialize(pugi::xml_node parent, RefStateSerialize state = std::make_shared<__RefStateSerialize>());
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node, RefStateDeserialize state = std::make_shared<__RefStateDeserialize>());
std::optional<std::shared_ptr<Instance>> Clone(RefStateClone state = std::make_shared<__RefStateClone>());
};
typedef std::shared_ptr<Instance> InstanceRef;
typedef std::weak_ptr<Instance> InstanceRefWeak;
// https://gist.github.com/jeetsukumaran/307264
class DescendantsIterator {
public:

View file

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

View file

@ -2,16 +2,26 @@
// Helper struct used for remapping reference when cloning/serializing
#include "datatypes/base.h"
#include <map>
#include <memory>
#include <vector>
class Instance;
template <typename T>
template <typename T, typename U, typename K>
struct __RefState {
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances;
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap;
std::map<K, U> remappedInstances;
std::map<K, std::vector<T>> refsAwaitingRemap;
int count = 0;
};
template <typename T>
using RefState = std::shared_ptr<__RefState<T>>;
template <typename T, typename U, typename K>
using RefState = std::shared_ptr<__RefState<T, U, K>>;
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::shared_ptr<Instance>> __RefStateClone;
typedef __RefState<pugi::xml_node, std::string, std::shared_ptr<Instance>> __RefStateSerialize;
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::string> __RefStateDeserialize;
typedef std::shared_ptr<__RefStateClone> RefStateClone;
typedef std::shared_ptr<__RefStateSerialize> RefStateSerialize;
typedef std::shared_ptr<__RefStateDeserialize> RefStateDeserialize;

View file

@ -5,7 +5,7 @@
#include "objects/base/service.h"
#include "objects/meta.h"
#include "objects/script/serverscriptservice.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "workspace.h"
#include "logger.h"
#include "panic.h"
@ -49,7 +49,7 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
pugi::xml_document doc;
pugi::xml_node root = doc.append_child("openblocks");
for (InstanceRef child : this->GetChildren()) {
for (std::shared_ptr<Instance> child : this->GetChildren()) {
child->Serialize(root);
}
@ -59,50 +59,6 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
Logger::info("Place saved successfully");
}
void DataModel::DeserializeService(pugi::xml_node node) {
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
return;
}
if (services.count(className) != 0) {
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
return;
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = INSTANCE_MAP[className]->constructor();
AddChild(object);
// Read properties
pugi::xml_node propertiesNode = node.child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName);
if (!meta_) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect();
}
// Add children
for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
}
// We add the service to the list
// All services get init'd at once in InitServices
this->services[className] = std::dynamic_pointer_cast<Service>(object);
}
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::ifstream inStream(path);
pugi::xml_document doc;
@ -110,9 +66,28 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
for (pugi::xml_node childNode : rootNode.children("Item")) {
newModel->DeserializeService(childNode);
// Make sure the class hasn't already been deserialized
std::string className = childNode.attribute("class").value();
// TODO: Make this push its children into the first service, or however it is actually done in the thing
// for parity
if (newModel->services.count(className) != 0) {
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
continue;
}
auto result = Instance::Deserialize(childNode, state);
if (result.isError()) {
Logger::errorf("Failed to deserialize service: %s", result.errorMessage()->c_str());
continue;
}
auto service = result.expect();
newModel->AddChild(service);
newModel->services[className] = std::dynamic_pointer_cast<Service>(service);
}
newModel->Init();
@ -146,7 +121,7 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
}
std::shared_ptr<DataModel> DataModel::CloneModel() {
RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>();
RefStateClone state = std::make_shared<__RefStateClone>();
std::shared_ptr<DataModel> newModel = DataModel::New();
// Copy properties
@ -155,9 +130,9 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update InstanceRef properties using map above
if (meta.type == &Data::InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
@ -165,17 +140,17 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
if (remappedRef) {
// If the instance has already been remapped, set the new value
newModel->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
newModel->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newModel, property));
state->refsAwaitingRemap[ref] = refs;
newModel->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
newModel->SetPropertyValue(property, InstanceRef(ref)).expect();
}
} else {
Data::Variant value = GetPropertyValue(property).expect();
Variant value = GetPropertyValue(property).expect();
newModel->SetPropertyValue(property, value).expect();
}
}
@ -185,7 +160,7 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newModel)).expect();
ref.first->SetPropertyValue(ref.second, InstanceRef(newModel)).expect();
}
// Clone services

View file

@ -16,8 +16,8 @@ class Service;
class DEF_INST_(abstract) DataModel : public Instance {
AUTOGEN_PREAMBLE
private:
void DeserializeService(pugi::xml_node node);
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>);
// void DeserializeService(pugi::xml_node node, RefStateDeserialize);
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefStateClone);
public:
std::map<std::string, std::shared_ptr<Service>> services;

View file

@ -0,0 +1,4 @@
#include "folder.h"
Folder::Folder(): Instance(&TYPE) {}
Folder::~Folder() = default;

19
core/src/objects/folder.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include <memory>
// The simplest instance
// Has no functionality of its own, used purely for organizational/grouping purposes
class DEF_INST_(explorer_icon="folder") Folder : public Instance {
AUTOGEN_PREAMBLE
public:
Folder();
~Folder();
static inline std::shared_ptr<Folder> New() { return std::make_shared<Folder>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Folder>(); };
};

View file

@ -1,9 +1,11 @@
#include "meta.h"
#include "objects/folder.h"
#include "objects/joint/jointinstance.h"
#include "objects/joint/rotate.h"
#include "objects/joint/rotatev.h"
#include "objects/joint/weld.h"
#include "objects/jointsservice.h"
#include "objects/model.h"
#include "objects/part.h"
#include "objects/joint/snap.h"
#include "objects/script.h"
@ -23,6 +25,8 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "RotateV", &RotateV::TYPE },
{ "JointInstance", &JointInstance::TYPE },
{ "Script", &Script::TYPE },
{ "Model", &Model::TYPE },
// { "Folder", &Folder::TYPE },
// Services

View file

@ -0,0 +1,4 @@
#include "model.h"
Model::Model(): Instance(&TYPE) {}
Model::~Model() = default;

18
core/src/objects/model.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include <memory>
// Group object for Parts
class DEF_INST_(explorer_icon="model") Model : public Instance {
AUTOGEN_PREAMBLE
public:
Model();
~Model();
static inline std::shared_ptr<Model> New() { return std::make_shared<Model>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Model>(); };
};

View file

@ -224,7 +224,7 @@ void Part::MakeJoints() {
// TEMPORARY
// TODO: Use more efficient algorithm to *actually* find nearby parts)
for (auto it = workspace().value()->GetDescendantsStart(); it != workspace().value()->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
std::shared_ptr<Instance> obj = *it;
if (obj == shared_from_this()) continue; // Skip ourselves
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Part> otherPart = obj->CastTo<Part>().expect();

View file

@ -107,7 +107,7 @@ public:
static inline std::shared_ptr<Part> New() { return std::make_shared<Part>(); };
static inline std::shared_ptr<Part> New(PartConstructParams params) { return std::make_shared<Part>(params); };
static inline InstanceRef Create() { return std::make_shared<Part>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Part>(); };
inline Vector3 position() { return cframe.Position(); }

View file

@ -41,10 +41,10 @@ void Script::Run() {
// Initialize script globals
lua_getglobal(Lt, "_G");
Data::InstanceRef(dataModel().value()).PushLuaValue(Lt);
InstanceRef(dataModel().value()).PushLuaValue(Lt);
lua_setfield(Lt, -2, "game");
Data::InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
lua_setfield(Lt, -2, "workspace");
lua_pushlightuserdata(Lt, scriptContext.get());

View file

@ -43,9 +43,9 @@ void ScriptContext::InitService() {
// luaopen_debug(state);
luaopen_bit(state);
Data::Vector3::PushLuaLibrary(state);
Data::CFrame::PushLuaLibrary(state);
Data::Color3::PushLuaLibrary(state);
Vector3::PushLuaLibrary(state);
CFrame::PushLuaLibrary(state);
Color3::PushLuaLibrary(state);
// TODO: custom os library

View file

@ -5,7 +5,7 @@
// Container class for server scripts
// Also handles/manages running server scripts on run
class DEF_INST_SERVICE ServerScriptService : public Service {
class DEF_INST_SERVICE_(explorer_icon="server-scripts") ServerScriptService : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;

View file

@ -1,5 +1,5 @@
#include "workspace.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "datatypes/ref.h"
#include "datatypes/vector.h"
#include "objects/base/instance.h"
@ -33,11 +33,11 @@ void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData&
auto part1 = reinterpret_cast<Part*>(pair.getBody2()->getUserData())->shared<Part>();
if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart) {
part0->Touched->Fire({ (Data::Variant)Data::InstanceRef(part1) });
part1->Touched->Fire({ (Data::Variant)Data::InstanceRef(part0) });
part0->Touched->Fire({ (Variant)InstanceRef(part1) });
part1->Touched->Fire({ (Variant)InstanceRef(part0) });
} else if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactExit) {
part0->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part1) });
part1->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part0) });
part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
}
}
}
@ -59,7 +59,7 @@ void Workspace::InitService() {
// Sync all parts
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
std::shared_ptr<Instance> obj = *it;
if (!obj->IsA<Part>()) continue;
std::shared_ptr<Part> part = obj->CastTo<Part>().expect();
this->SyncPartPhysics(part);
@ -68,7 +68,7 @@ void Workspace::InitService() {
// Activate all joints
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
std::shared_ptr<Instance> obj = *it;
if (!obj->IsA<JointInstance>()) continue;
std::shared_ptr<JointInstance> joint = obj->CastTo<JointInstance>().expect();
joint->UpdateProperty("Part0");
@ -127,17 +127,19 @@ void Workspace::PhysicsStep(float deltaTime) {
// Step the simulation a few steps
physicsWorld->update(std::min(deltaTime / 2, (1/60.f)));
// Naive implementation. Parts are only considered so if they are just under Workspace
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Instance> obj = *it;
if (!obj->IsA<Part>()) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
// Sync properties
const rp::Transform& transform = part->rigidBody->getTransform();
part->cframe = CFrame(transform);
part->velocity = part->rigidBody->getLinearVelocity();
// part->rigidBody->enableGravity(true);
// RotateV/Motor joint
for (auto& joint : part->secondaryJoints) {
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
@ -146,6 +148,16 @@ void Workspace::PhysicsStep(float deltaTime) {
// part->rigidBody->enableGravity(false);
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate);
}
// Destroy fallen parts
if (part->cframe.Position().Y() < this->fallenPartsDestroyHeight) {
auto parent = part->GetParent();
part->Destroy();
// If the parent of the part is a Model, destroy it too
if (parent.has_value() && parent.value()->IsA("Model"))
parent.value()->Destroy();
}
}
}

View file

@ -66,6 +66,8 @@ public:
Workspace();
~Workspace();
DEF_PROP float fallenPartsDestroyHeight = -500;
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };

View file

@ -1,6 +1,7 @@
#include "partassembly.h"
#include "common.h"
#include "datatypes/cframe.h"
#include "datatypes/variant.h"
#include "datatypes/vector.h"
#include "math_helper.h"
#include "objects/base/instance.h"
@ -32,7 +33,7 @@ PartAssembly::PartAssembly(std::vector<std::shared_ptr<Part>> parts, bool worldM
_bounds = size;
}
PartAssembly PartAssembly::FromSelection(std::vector<std::weak_ptr<Instance>> newSelection) {
PartAssembly PartAssembly::FromSelection(std::vector<std::shared_ptr<Instance>> newSelection) {
std::vector<std::shared_ptr<Part>> selection;
for (std::weak_ptr<Instance> obj : newSelection) {
@ -48,6 +49,7 @@ void PartAssembly::SetOrigin(CFrame newOrigin) {
for (auto part : parts) {
part->cframe = newOrigin * (_assemblyOrigin.Inverse() * part->cframe);
part->UpdateProperty("CFrame");
// sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
}
_assemblyOrigin = newOrigin;
@ -57,6 +59,7 @@ void PartAssembly::TransformBy(CFrame transform) {
for (auto part : parts) {
part->cframe = transform * part->cframe;
part->UpdateProperty("CFrame");
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
}
_assemblyOrigin = transform * _assemblyOrigin;
@ -66,6 +69,7 @@ void PartAssembly::Scale(Vector3 newSize, bool scaleUp) {
if (parts.size() == 1) {
parts[0]->size = newSize;
parts[0]->UpdateProperty("Size");
sendPropertyUpdatedSignal(parts[0], "Size", Variant((Vector3)parts[0]->size));
_bounds = newSize;
return;
}
@ -78,8 +82,10 @@ void PartAssembly::Scale(Vector3 newSize, bool scaleUp) {
localOff = localOff * factor;
part->cframe = part->cframe.Rotation() + _assemblyOrigin * localOff;
part->UpdateProperty("CFrame");
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
part->size *= factor;
part->UpdateProperty("Size");
sendPropertyUpdatedSignal(part, "Size", Variant((Vector3)part->size));
}
_bounds = _bounds * factor;

View file

@ -7,7 +7,7 @@
class Part;
class Instance;
const std::vector<std::weak_ptr<Instance>> getSelection();
const std::vector<std::shared_ptr<Instance>> getSelection();
class PartAssembly {
CFrame _assemblyOrigin;
@ -17,7 +17,7 @@ class PartAssembly {
public:
PartAssembly(std::vector<std::shared_ptr<Part>>, bool worldMode = false);
static PartAssembly FromSelection(std::vector<std::weak_ptr<Instance>> selection = getSelection());
static PartAssembly FromSelection(std::vector<std::shared_ptr<Instance>> selection = getSelection());
inline CFrame assemblyOrigin() { return _assemblyOrigin; };
inline Vector3 bounds() { return _bounds; };

View file

@ -133,7 +133,7 @@ void renderParts() {
// Sort by nearest
std::map<float, std::shared_ptr<Part>> sorted;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (part->transparency > 0.00001) {
@ -227,7 +227,7 @@ void renderSurfaceExtras() {
ghostShader->set("viewPos", camera.cameraPos);
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (!inst->IsA("Part")) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
for (int i = 0; i < 6; i++) {
@ -367,7 +367,7 @@ void renderAABB() {
ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = CFrame::IDENTITY + part->cframe.Position();
@ -407,7 +407,7 @@ void renderWireframe() {
wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = part->cframe;
@ -451,7 +451,7 @@ void renderOutlines() {
int count = 0;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (inst->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (!part->selected) continue;
@ -556,14 +556,14 @@ void renderRotationArcs() {
// Pass in the camera position
handleShader->set("viewPos", camera.cameraPos);
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(getSelection()[0].lock());
PartAssembly assembly = PartAssembly::FromSelection();
for (HandleFace face : HandleFace::Faces) {
if (glm::any(glm::lessThan(face.normal, glm::vec3(0)))) continue;
glm::mat4 model = part->cframe * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
glm::mat4 model = assembly.assemblyOrigin() * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
handleShader->set("model", model);
float radius = glm::max(part->size.x, part->size.y, part->size.z) / 2.f + 2.f;
float radius = glm::max(assembly.bounds().X(), assembly.bounds().Y(), assembly.bounds().Z()) / 2.f + 2.f;
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),

View file

@ -8,5 +8,5 @@ namespace Data { class CFrame; class Color3; };
void renderInit(GLFWwindow* window, int width, int height);
void render(GLFWwindow* window);
void setViewport(int width, int height);
void addDebugRenderCFrame(Data::CFrame);
void addDebugRenderCFrame(Data::CFrame, Data::Color3);
void addDebugRenderCFrame(CFrame);
void addDebugRenderCFrame(CFrame, Color3);

View file

@ -20,6 +20,6 @@ enum SurfaceType {
SurfaceMotor = 7,
};
namespace Data { class Vector3; } using Data::Vector3;
class Vector3;
NormalId faceFromNormal(Vector3);
Vector3 normalFromFace(NormalId);

View file

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

View file

@ -4,6 +4,7 @@
#include <qnamespace.h>
#include <qsoundeffect.h>
#include <string>
#include "./ui_mainwindow.h"
#include "mainglwidget.h"
#include "datatypes/vector.h"
#include "handles.h"
@ -16,13 +17,14 @@
#include "physics/util.h"
#include "rendering/renderer.h"
#include "rendering/shader.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#define PI 3.14159
#define M_mainWindow dynamic_cast<MainWindow*>(window())
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent), contextMenu(this) {
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setMouseTracking(true);
}
@ -115,12 +117,14 @@ CFrame snapCFrame(CFrame frame) {
return CFrame(frame.Position(), frame.Position() + closestVec1, closestVec2);
}
bool tryMouseContextMenu = false;
bool isMouseDragging = false;
std::weak_ptr<Part> draggingObject;
std::optional<HandleFace> draggingHandle;
Vector3 initialHitPos;
Vector3 initialHitNormal;
CFrame initialFrame;
PartAssembly initialAssembly({});
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging || draggingObject.expired() || mainWindow()->selectedTool >= TOOL_SMOOTH) return;
@ -271,7 +275,6 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = getHandleAdornee();
// Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
@ -303,11 +306,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
gWorkspace()->SyncPartPhysics(part);
part->UpdateProperty("Rotation");
sendPropertyUpdatedSignal(part, "Rotation", part->cframe.ToEulerAnglesXYZ());
CFrame newFrame = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
initialAssembly.SetOrigin(newFrame);
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
@ -342,6 +342,7 @@ void MainGLWidget::wheelEvent(QWheelEvent* evt) {
}
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
tryMouseContextMenu = false;
handleCameraRotate(evt);
handleObjectDrag(evt);
handleCursorChange(evt);
@ -360,6 +361,7 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
}
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
tryMouseContextMenu = evt->button() == Qt::RightButton;
switch(evt->button()) {
// Camera drag
case Qt::RightButton: {
@ -375,7 +377,8 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
initialFrame = getHandleAdornee()->cframe;
initialAssembly = PartAssembly::FromSelection();
initialFrame = initialAssembly.assemblyOrigin();
isMouseDragging = true;
draggingHandle = handle;
startLinearTransform(evt);
@ -417,10 +420,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
isMouseDragging = true;
draggingObject = part;
if (evt->modifiers() & Qt::ControlModifier) {
std::vector<InstanceRefWeak> currentSelection = getSelection();
std::vector<std::shared_ptr<Instance>> currentSelection = getSelection();
for (int i = 0; i < currentSelection.size(); i++) {
InstanceRefWeak inst = currentSelection[i];
if (!inst.expired() && inst.lock() == part) {
std::shared_ptr<Instance> inst = currentSelection[i];
if (inst == part) {
currentSelection.erase(currentSelection.begin() + i);
goto skipAddPart;
}
@ -428,8 +431,8 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
currentSelection.push_back(part);
skipAddPart:
setSelection(currentSelection);
}else
setSelection(std::vector<InstanceRefWeak> { part });
} else
setSelection({ part });
// Disable bit so that we can ignore the part while raycasting
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
@ -445,6 +448,23 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
isMouseDragging = false;
draggingObject = {};
draggingHandle = std::nullopt;
// Open context menu
if (tryMouseContextMenu)
contextMenu.exec(QCursor::pos());
tryMouseContextMenu = false;
}
void MainGLWidget::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionCopy);
contextMenu.addAction(M_mainWindow->ui->actionCut);
contextMenu.addAction(M_mainWindow->ui->actionPaste);
contextMenu.addAction(M_mainWindow->ui->actionPasteInto);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionSaveModel);
contextMenu.addAction(M_mainWindow->ui->actionInsertModel);
}
static int moveZ = 0;
@ -488,11 +508,11 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
if (evt->key() == Qt::Key_O)
Logger::error("error message");
if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired())
getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace());
if (evt->key() == Qt::Key_C && getSelection().size() > 0)
getSelection()[0]->Clone().value()->SetParent(gWorkspace());
if (evt->key() == Qt::Key_H && getSelection().size() > 0 && !getSelection()[0].expired())
Logger::infof("Object at: 0x%x\n", getSelection()[0].lock().get());
if (evt->key() == Qt::Key_H && getSelection().size() > 0)
Logger::infof("Object at: 0x%x\n", getSelection()[0].get());
}
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {

View file

@ -6,6 +6,7 @@
#include <QOpenGLWidget>
#include <QWidget>
#include <memory>
#include <qmenu.h>
class HandleFace;
class MainWindow;
@ -16,6 +17,7 @@ public:
void updateCycle();
std::shared_ptr<Part> lastPart;
void buildContextMenu();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
@ -36,6 +38,8 @@ protected:
void keyPressEvent(QKeyEvent* evt) override;
void keyReleaseEvent(QKeyEvent* evt) override;
QMenu contextMenu;
MainWindow* mainWindow();
float snappingFactor();
};

View file

@ -3,6 +3,7 @@
#include "common.h"
#include "logger.h"
#include "objects/datamodel.h"
#include "objects/model.h"
#include "placedocument.h"
#include "script/scriptdocument.h"
#include <cstdio>
@ -19,6 +20,7 @@
#include <pugixml.hpp>
#include <qtextcursor.h>
#include <qtextedit.h>
#include <vector>
#ifdef _NDEBUG
#define NDEBUG
@ -83,6 +85,8 @@ MainWindow::MainWindow(QWidget *parent)
this->close();
});
ui->explorerView->buildContextMenu();
connectActionHandlers();
// Update properties
@ -90,17 +94,17 @@ MainWindow::MainWindow(QWidget *parent)
if (newSelection.size() == 0) return;
if (newSelection.size() > 1)
ui->propertiesView->setSelected(std::nullopt);
ui->propertiesView->setSelected(newSelection[0].lock());
ui->propertiesView->setSelected(newSelection[0]);
});
addSelectionListener([&](auto oldSelection, auto newSelection, bool __) {
for (InstanceRefWeak inst : oldSelection) {
for (std::weak_ptr<Instance> inst : oldSelection) {
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = false;
}
for (InstanceRefWeak inst : newSelection) {
for (std::weak_ptr<Instance> inst : newSelection) {
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = true;
@ -141,10 +145,6 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
}
void MainWindow::connectActionHandlers() {
// Explorer View
ui->explorerView->buildContextMenu();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_SCALE : TOOL_SELECT; updateToolbars(); });
@ -291,16 +291,16 @@ void MainWindow::connectActionHandlers() {
});
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
for (InstanceRefWeak inst : getSelection()) {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->SetParent(std::nullopt);
}
setSelection(std::vector<InstanceRefWeak> {});
setSelection({});
});
connect(ui->actionCopy, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(rootDoc);
}
@ -314,7 +314,7 @@ void MainWindow::connectActionHandlers() {
});
connect(ui->actionCut, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(rootDoc);
inst.lock()->SetParent(std::nullopt);
@ -338,16 +338,16 @@ void MainWindow::connectActionHandlers() {
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
gWorkspace()->AddChild(inst.expect());
}
});
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
if (getSelection().size() != 1) return;
InstanceRef selectedParent = getSelection()[0].lock();
std::shared_ptr<Instance> selectedParent = getSelection()[0];
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
@ -358,12 +358,50 @@ void MainWindow::connectActionHandlers() {
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
}
});
connect(ui->actionGroupObjects, &QAction::triggered, this, [&]() {
auto model = Model::New();
std::shared_ptr<Instance> firstParent;
for (auto object : getSelection()) {
if (firstParent == nullptr && object->GetParent().has_value()) firstParent = object->GetParent().value();
object->SetParent(model);
}
if (model->GetChildren().size() == 0)
return;
// Technically not how it works in the actual studio, but it's not an API-breaking change
// and I think this implementation is more useful so I'm sticking with it
if (firstParent == nullptr) firstParent = gWorkspace();
model->SetParent(firstParent);
setSelection({ model });
});
connect(ui->actionUngroupObjects, &QAction::triggered, this, [&]() {
std::vector<std::shared_ptr<Instance>> newSelection;
for (auto model : getSelection()) {
// Not a model, skip
if (!model->IsA<Model>()) { newSelection.push_back(model); continue; }
for (auto object : model->GetChildren()) {
object->SetParent(model->GetParent());
newSelection.push_back(object);
}
model->Destroy();
}
setSelection(newSelection);
});
connect(ui->actionSaveModel, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave);
if (!path) return;
@ -373,7 +411,7 @@ void MainWindow::connectActionHandlers() {
pugi::xml_document modelDoc;
pugi::xml_node modelRoot = modelDoc.append_child("openblocks");
for (InstanceRefWeak inst : getSelection()) {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(modelRoot);
}
@ -382,8 +420,8 @@ void MainWindow::connectActionHandlers() {
});
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
InstanceRef selectedParent = getSelection()[0].lock();
if (getSelection().size() != 1) return;
std::shared_ptr<Instance> selectedParent = getSelection()[0];
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen);
if (!path) return;
@ -393,13 +431,46 @@ void MainWindow::connectActionHandlers() {
modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
}
});
}
void MainWindow::openFile(std::string path) {
// Don't ask for confirmation if running a debug build (makes development easier)
#ifdef NDEBUG
// Ask if the user wants to save their changes
// https://stackoverflow.com/a/33890731
QMessageBox msgBox;
msgBox.setText("Save changes before creating new document?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int result = msgBox.exec();
if (result == QMessageBox::Cancel) return;
if (result == QMessageBox::Save) {
std::optional<std::string> path;
if (!gDataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
if (!path || path == "") return;
gDataModel->SaveToFile(path);
}
#endif
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
editModeDataModel = newModel;
gDataModel = newModel;
newModel->Init();
ui->explorerView->updateRoot(newModel);
// Reset running state
placeDocument->setRunState(RUN_STOPPED);
updateToolbars();
}
void MainWindow::updateToolbars() {
ui->actionToolSelect->setChecked(selectedTool == TOOL_SELECT);
ui->actionToolMove->setChecked(selectedTool == TOOL_MOVE);

View file

@ -57,6 +57,8 @@ public:
void openScriptDocument(std::shared_ptr<Script>);
void closeScriptDocument(std::shared_ptr<Script>);
void openFile(std::string path);
Ui::MainWindow *ui;
private:
PlaceDocument* placeDocument;

View file

@ -176,6 +176,8 @@
<addaction name="actionCut"/>
<addaction name="actionPaste"/>
<addaction name="actionPasteInto"/>
<addaction name="actionGroupObjects"/>
<addaction name="actionUngroupObjects"/>
</widget>
<widget class="QToolBar" name="snappingOptions">
<property name="windowTitle">
@ -768,6 +770,40 @@
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionGroupObjects">
<property name="icon">
<iconset theme="object-group"/>
</property>
<property name="text">
<string>Group Objects</string>
</property>
<property name="toolTip">
<string>Group objects under a Model</string>
</property>
<property name="shortcut">
<string>Ctrl+G</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionUngroupObjects">
<property name="icon">
<iconset theme="object-ungroup"/>
</property>
<property name="text">
<string>Ungroup Objects</string>
</property>
<property name="toolTip">
<string>Ungroup objects inside selected Model</string>
</property>
<property name="shortcut">
<string>Ctrl+U</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View file

@ -10,11 +10,11 @@
std::map<std::string, QIcon> instanceIconCache;
ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
ExplorerModel::ExplorerModel(std::shared_ptr<Instance> dataRoot, QWidget *parent)
: QAbstractItemModel(parent)
, rootItem(dataRoot) {
// TODO: Don't use lambdas and handlers like that
hierarchyPreUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
hierarchyPreUpdateHandler = [&](std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (oldParent.has_value()) {
auto children = oldParent.value()->GetChildren();
size_t idx = std::find(children.begin(), children.end(), object) - children.begin();
@ -29,7 +29,7 @@ ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
}
};
hierarchyPostUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
hierarchyPostUpdateHandler = [&](std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (newParent.has_value()) endInsertRows();
if (oldParent.has_value()) endRemoveRows();
};
@ -50,11 +50,11 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
return {};
}
QModelIndex ExplorerModel::toIndex(InstanceRef item) {
QModelIndex ExplorerModel::toIndex(std::shared_ptr<Instance> item) {
if (item == rootItem || !item->GetParent().has_value())
return {};
InstanceRef parentItem = item->GetParent().value();
std::shared_ptr<Instance> parentItem = item->GetParent().value();
// Check above ensures this item is not root, so value() must be valid
for (int i = 0; i < parentItem->GetChildren().size(); i++)
if (parentItem->GetChildren()[i] == item)
@ -62,7 +62,7 @@ QModelIndex ExplorerModel::toIndex(InstanceRef item) {
return QModelIndex{};
}
QModelIndex ExplorerModel::ObjectToIndex(InstanceRef item) {
QModelIndex ExplorerModel::ObjectToIndex(std::shared_ptr<Instance> item) {
return toIndex(item);
}
@ -72,13 +72,13 @@ QModelIndex ExplorerModel::parent(const QModelIndex &index) const {
Instance* childItem = static_cast<Instance*>(index.internalPointer());
// NORISK: The parent must exist if the child was obtained from it during this frame
InstanceRef parentItem = childItem->GetParent().value();
std::shared_ptr<Instance> parentItem = childItem->GetParent().value();
if (parentItem == rootItem)
return {};
// Check above ensures this item is not root, so value() must be valid
InstanceRef parentParent = parentItem->GetParent().value();
std::shared_ptr<Instance> parentParent = parentItem->GetParent().value();
for (int i = 0; i < parentParent->GetChildren().size(); i++)
if (parentParent->GetChildren()[i] == parentItem)
return createIndex(i, 0, parentItem.get());
@ -196,13 +196,13 @@ Qt::DropActions ExplorerModel::supportedDropActions() const {
}
InstanceRef ExplorerModel::fromIndex(const QModelIndex index) const {
std::shared_ptr<Instance> ExplorerModel::fromIndex(const QModelIndex index) const {
if (!index.isValid()) return rootItem;
return static_cast<Instance*>(index.internalPointer())->shared_from_this();
}
struct DragDropSlot {
std::vector<InstanceRef> instances;
std::vector<std::shared_ptr<Instance>> instances;
};
bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
@ -216,8 +216,8 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
return true;
}
InstanceRef parentInst = fromIndex(parent);
for (InstanceRef instance : slot->instances) {
std::shared_ptr<Instance> parentInst = fromIndex(parent);
for (std::shared_ptr<Instance> instance : slot->instances) {
instance->SetParent(parentInst);
}
@ -225,7 +225,7 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
return true;
}
void ExplorerModel::updateRoot(InstanceRef newRoot) {
void ExplorerModel::updateRoot(std::shared_ptr<Instance> newRoot) {
beginResetModel();
rootItem = newRoot;
endResetModel();

View file

@ -8,7 +8,7 @@ class ExplorerModel : public QAbstractItemModel {
public:
Q_DISABLE_COPY_MOVE(ExplorerModel)
explicit ExplorerModel(InstanceRef dataRoot, QWidget *parent = nullptr);
explicit ExplorerModel(std::shared_ptr<Instance> dataRoot, QWidget *parent = nullptr);
~ExplorerModel() override;
QVariant data(const QModelIndex &index, int role) const override;
@ -29,15 +29,15 @@ public:
QStringList mimeTypes() const override;
Qt::DropActions supportedDragActions() const override;
Qt::DropActions supportedDropActions() const override;
InstanceRef fromIndex(const QModelIndex index) const;
QModelIndex ObjectToIndex(InstanceRef item);
std::shared_ptr<Instance> fromIndex(const QModelIndex index) const;
QModelIndex ObjectToIndex(std::shared_ptr<Instance> item);
QIcon iconOf(const InstanceType* type) const;
void updateRoot(InstanceRef newRoot);
void updateRoot(std::shared_ptr<Instance> newRoot);
private:
InstanceRef rootItem;
QModelIndex toIndex(InstanceRef item);
std::shared_ptr<Instance> rootItem;
QModelIndex toIndex(std::shared_ptr<Instance> item);
};
// #endif

View file

@ -38,11 +38,11 @@ ExplorerView::ExplorerView(QWidget* parent):
});
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
std::vector<InstanceRefWeak> selectedInstances;
std::vector<std::shared_ptr<Instance>> selectedInstances;
selectedInstances.reserve(selectedIndexes().count()); // This doesn't reserve everything, but should enhance things anyway
for (auto index : selectedIndexes()) {
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->weak_from_this());
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->shared_from_this());
}
::setSelection(selectedInstances, true);
@ -53,7 +53,7 @@ ExplorerView::ExplorerView(QWidget* parent):
if (fromExplorer) return;
this->clearSelection();
for (InstanceRefWeak inst : newSelection) {
for (std::weak_ptr<Instance> inst : newSelection) {
if (inst.expired()) continue;
QModelIndex index = this->model.ObjectToIndex(inst.lock());
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
@ -104,14 +104,14 @@ void ExplorerView::buildContextMenu() {
QAction* instAction = new QAction(model.iconOf(type), QString::fromStdString(type->className));
insertObjectMenu->addAction(instAction);
connect(instAction, &QAction::triggered, this, [&]() {
if (getSelection().size() == 0 || getSelection()[0].expired()) return;
std::shared_ptr<Instance> instParent = getSelection()[0].lock();
if (getSelection().size() == 0) return;
std::shared_ptr<Instance> instParent = getSelection()[0];
std::shared_ptr<Instance> newInst = type->constructor();
newInst->SetParent(instParent);
});
}
}
void ExplorerView::updateRoot(InstanceRef newRoot) {
void ExplorerView::updateRoot(std::shared_ptr<Instance> newRoot) {
model.updateRoot(newRoot);
}

View file

@ -16,7 +16,7 @@ public:
// void dropEvent(QDropEvent*) override;
void buildContextMenu();
void updateRoot(InstanceRef newRoot);
void updateRoot(std::shared_ptr<Instance> newRoot);
private:
ExplorerModel model;
QMenu contextMenu;

View file

@ -1,7 +1,9 @@
#include "panes/propertiesview.h"
#include "common.h"
#include "datatypes/base.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include "datatypes/primitives.h"
#include "error/data.h"
#include "objects/base/member.h"
#include <QColorDialog>
@ -38,7 +40,7 @@ public:
if (index.column() == 0) return nullptr;
if (!index.parent().isValid() || view->currentInstance.expired()) return nullptr;
InstanceRef inst = view->currentInstance.lock();
std::shared_ptr<Instance> inst = view->currentInstance.lock();
// If the property is deeper than 1 layer, then it is considered composite
// Handle specially
@ -49,10 +51,10 @@ public:
std::string propertyName = !isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString()
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect();
Variant currentValue = inst->GetPropertyValue(propertyName).expect();
if (isComposite) {
if (meta.type == &Vector3::TYPE) {
if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>();
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
@ -65,9 +67,9 @@ public:
return nullptr;
}
if (meta.type == &Data::Float::TYPE) {
if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
spinBox->setValue(currentValue.get<Data::Float>());
spinBox->setValue(currentValue.get<float>());
if (meta.flags & PROP_UNIT_FLOAT) {
spinBox->setMinimum(0);
@ -76,24 +78,24 @@ public:
}
return spinBox;
} else if (meta.type == &Data::Int::TYPE) {
} else if (meta.type.descriptor == &INT_TYPE) {
QSpinBox* spinBox = new QSpinBox(parent);
spinBox->setValue(currentValue.get<Data::Int>());
spinBox->setValue(currentValue.get<int>());
return spinBox;
} else if (meta.type == &Data::String::TYPE) {
} else if (meta.type.descriptor == &STRING_TYPE) {
QLineEdit* lineEdit = new QLineEdit(parent);
lineEdit->setText(QString::fromStdString(currentValue.get<Data::String>()));
lineEdit->setText(QString::fromStdString(currentValue.get<std::string>()));
return lineEdit;
} else if (meta.type == &Color3::TYPE) {
} else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = new QColorDialog(parent->window());
Color3 color = currentValue.get<Color3>();
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
return colorDialog;
} else if (meta.type->fromString) {
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = new QLineEdit(parent);
lineEdit->setText(QString::fromStdString(currentValue.ToString()));
@ -107,7 +109,7 @@ public:
if (index.column() == 0) return;
if (!index.parent().isValid() || view->currentInstance.expired()) return;
InstanceRef inst = view->currentInstance.lock();
std::shared_ptr<Instance> inst = view->currentInstance.lock();
bool isComposite = index.parent().parent().isValid();
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
@ -115,10 +117,10 @@ public:
std::string propertyName = !index.parent().parent().isValid() ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString()
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect();
Variant currentValue = inst->GetPropertyValue(propertyName).expect();
if (isComposite) {
if (meta.type == &Vector3::TYPE) {
if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>();
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
@ -131,24 +133,24 @@ public:
return;
}
if (meta.type == &Data::Float::TYPE) {
if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
spinBox->setValue(currentValue.get<Data::Float>());
} else if (meta.type == &Data::Int::TYPE) {
spinBox->setValue(currentValue.get<float>());
} else if (meta.type.descriptor == &INT_TYPE) {
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
spinBox->setValue(currentValue.get<Data::Int>());
} else if (meta.type == &Data::String::TYPE) {
spinBox->setValue(currentValue.get<int>());
} else if (meta.type.descriptor == &STRING_TYPE) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
lineEdit->setText(QString::fromStdString((std::string)currentValue.get<Data::String>()));
} else if (meta.type == &Color3::TYPE) {
lineEdit->setText(QString::fromStdString((std::string)currentValue.get<std::string>()));
} else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
Color3 color = currentValue.get<Color3>();
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
} else if (meta.type->fromString) {
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
lineEdit->setText(QString::fromStdString((std::string)currentValue.ToString()));
@ -159,7 +161,7 @@ public:
if (index.column() == 0) return;
if (!index.parent().isValid() || view->currentInstance.expired()) return;
InstanceRef inst = view->currentInstance.lock();
std::shared_ptr<Instance> inst = view->currentInstance.lock();
bool isComposite = index.parent().parent().isValid();
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
@ -169,7 +171,7 @@ public:
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
if (isComposite) {
if (meta.type == &Vector3::TYPE) {
if (meta.type.descriptor == &Vector3::TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
float value = spinBox->value();
@ -186,22 +188,22 @@ public:
return;
}
if (meta.type == &Data::Float::TYPE) {
if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
inst->SetPropertyValue(propertyName, Data::Float((float)spinBox->value())).expect();
inst->SetPropertyValue(propertyName, (float)spinBox->value()).expect();
model->setData(index, spinBox->value());
} else if (meta.type == &Data::Int::TYPE) {
} else if (meta.type.descriptor == &INT_TYPE) {
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
inst->SetPropertyValue(propertyName, Data::Int((float)spinBox->value())).expect();
inst->SetPropertyValue(propertyName, (int)spinBox->value()).expect();
model->setData(index, spinBox->value());
} else if (meta.type == &Data::String::TYPE) {
} else if (meta.type.descriptor == &STRING_TYPE) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
inst->SetPropertyValue(propertyName, Data::String(lineEdit->text().toStdString())).expect();
inst->SetPropertyValue(propertyName, lineEdit->text().toStdString()).expect();
model->setData(index, lineEdit->text());
} else if (meta.type == &Color3::TYPE) {
} else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
QColor color = colorDialog->currentColor();
@ -209,14 +211,15 @@ public:
inst->SetPropertyValue(propertyName, color3).expect();
model->setData(index, QString::fromStdString(color3.ToString()), Qt::DisplayRole);
model->setData(index, color, Qt::DecorationRole);
} else if (meta.type->fromString) {
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
std::optional<Data::Variant> parsedResult = meta.type->fromString(lineEdit->text().toStdString());
result<Variant, DataParseError> parsedResult = meta.type.descriptor->fromString(lineEdit->text().toStdString(), meta.type);
if (!parsedResult) return;
inst->SetPropertyValue(propertyName, parsedResult.value()).expect();
model->setData(index, QString::fromStdString(parsedResult.value().ToString()));
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type, parsedResult.value());
Variant parsedValue = parsedResult.expect();
inst->SetPropertyValue(propertyName, parsedValue).expect();
model->setData(index, QString::fromStdString(parsedValue.ToString()));
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedValue);
}
}
};
@ -268,11 +271,11 @@ void PropertiesView::drawBranches(QPainter *painter, const QRect &rect, const QM
QTreeWidget::drawBranches(painter, rect, index);
}
void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
void PropertiesView::setSelected(std::optional<std::shared_ptr<Instance>> instance) {
clear();
currentInstance = {};
if (!instance) return;
InstanceRef inst = instance.value();
std::shared_ptr<Instance> inst = instance.value();
currentInstance = inst;
std::map<PropertyCategory, QTreeWidgetItem*> propertyCategories;
@ -296,35 +299,35 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
for (std::string property : properties) {
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
Variant currentValue = inst->GetPropertyValue(property).expect();
if (meta.type == &CFrame::TYPE || meta.flags & PROP_HIDDEN) continue;
if (meta.type.descriptor == &CFrame::TYPE || meta.flags & PROP_HIDDEN) continue;
QTreeWidgetItem* item = new QTreeWidgetItem;
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable);
item->setData(0, Qt::DisplayRole, QString::fromStdString(property));
if (meta.type == &Data::Bool::TYPE) {
item->setCheckState(1, (bool)currentValue.get<Data::Bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
} else if (meta.type == &Color3::TYPE) {
if (meta.type.descriptor == &BOOL_TYPE) {
item->setCheckState(1, (bool)currentValue.get<bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
} else if (meta.type.descriptor == &Color3::TYPE) {
Color3 color = currentValue.get<Color3>();
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else if (meta.type == &Vector3::TYPE) {
} else if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
// } else if (meta.type == &CFrame::TYPE) {
// } else if (meta.type.descriptor == &CFrame::TYPE) {
// Vector3 vector = currentValue.get<CFrame>().Position();
// item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
}
if (meta.type != &Color3::TYPE && (!meta.type->fromString || meta.flags & PROP_READONLY)) {
if (meta.type.descriptor != &Color3::TYPE && (!meta.type.descriptor->fromString || meta.flags & PROP_READONLY)) {
item->setDisabled(true);
}
rebuildCompositeProperty(item, meta.type, currentValue);
rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
propertyCategories[meta.category]->addChild(item);
propertyCategories[meta.category]->setExpanded(true);
@ -347,17 +350,17 @@ void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) {
// Necessary because otherwise this will catch setCheckState from onPropertyUpdated
if (ignorePropertyUpdates) return;
if (!item->parent() || (item->parent() && item->parent()->parent()) || currentInstance.expired()) return;
InstanceRef inst = currentInstance.lock();
std::shared_ptr<Instance> inst = currentInstance.lock();
std::string propertyName = item->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
if (meta.type == &Data::Bool::TYPE) {
inst->SetPropertyValue(propertyName, Data::Bool(item->checkState(1) == Qt::Checked)).expect();
if (meta.type.descriptor == &BOOL_TYPE) {
inst->SetPropertyValue(propertyName, item->checkState(1) == Qt::Checked).expect();
}
}
void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const Data::TypeInfo* type, Data::Variant value) {
void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc* type, Variant value) {
if (type == &Vector3::TYPE) {
// https://forum.qt.io/post/266837
foreach(auto i, item->takeChildren()) delete i;
@ -384,15 +387,15 @@ void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const Data:
}
// static auto lastUpdateTime = std::chrono::steady_clock::now();
void PropertiesView::onPropertyUpdated(InstanceRef inst, std::string property, Data::Variant newValue) {
void PropertiesView::onPropertyUpdated(std::shared_ptr<Instance> inst, std::string property, Variant newValue) {
// if (!currentInstance || currentInstance->expired() || instance != currentInstance->lock()) return;
// if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - lastUpdateTime).count() < 1000) return;
// lastUpdateTime = std::chrono::steady_clock::now();
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
Variant currentValue = inst->GetPropertyValue(property).expect();
if (meta.type == &CFrame::TYPE) return;
if (meta.type.descriptor == &CFrame::TYPE) return;
for (int categoryItemIdx = 0; categoryItemIdx < topLevelItemCount(); categoryItemIdx++) {
QTreeWidgetItem* categoryItem = topLevelItem(categoryItemIdx);
@ -401,27 +404,27 @@ void PropertiesView::onPropertyUpdated(InstanceRef inst, std::string property, D
if (item->data(0, Qt::DisplayRole).toString().toStdString() != property) continue;
if (meta.type == &Data::Bool::TYPE) {
if (meta.type.descriptor == &BOOL_TYPE) {
// This is done because otherwise propertyChanged will catch this change erroneously
ignorePropertyUpdates = true;
item->setCheckState(1, (bool)currentValue.get<Data::Bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
item->setCheckState(1, (bool)currentValue.get<bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
ignorePropertyUpdates = false;
} else if (meta.type == &Color3::TYPE) {
} else if (meta.type.descriptor == &Color3::TYPE) {
Color3 color = currentValue.get<Color3>();
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else if (meta.type == &Vector3::TYPE) {
} else if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
}
if (meta.type != &Color3::TYPE && (!meta.type->fromString || meta.flags & PROP_READONLY)) {
if (meta.type.descriptor != &Color3::TYPE && (!meta.type.descriptor->fromString || meta.flags & PROP_READONLY)) {
item->setDisabled(true);
}
rebuildCompositeProperty(item, meta.type, currentValue);
rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
return;
}

View file

@ -12,11 +12,11 @@ class PropertiesView : public QTreeWidget {
Q_DECLARE_PRIVATE(QTreeView)
bool ignorePropertyUpdates = false;
InstanceRefWeak currentInstance;
std::weak_ptr<Instance> currentInstance;
void propertyChanged(QTreeWidgetItem *item, int column);
void activateProperty(QTreeWidgetItem *item, int column);
void rebuildCompositeProperty(QTreeWidgetItem *item, const Data::TypeInfo*, Data::Variant);
void onPropertyUpdated(InstanceRef instance, std::string property, Data::Variant newValue);
void rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc*, Variant);
void onPropertyUpdated(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
friend PropertiesItemDelegate;
protected:
@ -26,5 +26,5 @@ public:
PropertiesView(QWidget* parent = nullptr);
~PropertiesView() override;
void setSelected(std::optional<InstanceRef> instance);
void setSelected(std::optional<std::shared_ptr<Instance>> instance);
};

View file

@ -9,14 +9,17 @@
#include <cstdio>
#include <memory>
#include <qboxlayout.h>
#include <qdebug.h>
#include <qevent.h>
#include <qmargins.h>
#include <qmdisubwindow.h>
#include <qlayout.h>
#include <qmimedata.h>
PlaceDocument::PlaceDocument(QWidget* parent):
QMdiSubWindow(parent) {
placeWidget = new MainGLWidget;
setAcceptDrops(true);
setWidget(placeWidget);
setWindowTitle("Place");
@ -74,6 +77,7 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
void PlaceDocument::init() {
timer.start(33, this);
placeWidget->buildContextMenu();
std::shared_ptr<Part> lastPart;
// Baseplate
@ -134,4 +138,19 @@ void PlaceDocument::init() {
gWorkspace()->AddChild(script);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
// mainWnd->openScriptDocument(script);
}
void PlaceDocument::dragEnterEvent(QDragEnterEvent* evt) {
// https://stackoverflow.com/a/14895393/16255372
if (evt->mimeData()->hasUrls()) {
evt->acceptProposedAction();
}
}
void PlaceDocument::dropEvent(QDropEvent* evt) {
auto urls = evt->mimeData()->urls();
if (urls.size() == 0) return;
QString fileName = urls[0].toLocalFile();
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openFile(fileName.toStdString());
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "mainglwidget.h"
#include <qevent.h>
#include <qmdisubwindow.h>
#include <QBasicTimer>
@ -25,4 +26,7 @@ public:
void closeEvent(QCloseEvent *closeEvent) override;
void init();
protected:
void dragEnterEvent(QDragEnterEvent*) override;
void dropEvent(QDropEvent*) override;
};

View file

@ -16,7 +16,7 @@
#include <qtextformat.h>
#include "mainwindow.h"
#include "objects/script.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#include <QPalette>
#include <QStyleHints>
@ -115,9 +115,9 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
setWindowTitle(QString::fromStdString(script->name));
// Add detector for script deletion to automatically close this document
scriptDeletionHandler = script->AncestryChanged->Connect([this, script](std::vector<Data::Variant> args) {
std::weak_ptr<Instance> child = args[0].get<Data::InstanceRef>();
std::weak_ptr<Instance> newParent = args[1].get<Data::InstanceRef>();
scriptDeletionHandler = script->AncestryChanged->Connect([this, script](std::vector<Variant> args) {
std::weak_ptr<Instance> child = args[0].get<InstanceRef>();
std::weak_ptr<Instance> newParent = args[1].get<InstanceRef>();
if (child.expired() || child.lock() != script || !newParent.expired()) return;
dynamic_cast<MainWindow*>(window())->closeScriptDocument(script);