Compare commits

..

42 commits

Author SHA1 Message Date
077b153953 fix(lua): accidentally added lua libs outside of lua.h 2025-06-08 16:24:59 +02:00
024d6a9243 2025-06-08 15:53:08 +02:00
b33ae784ca fix(autogen): not compiling on windows due to difference in fs::path char_type 2025-06-08 15:45:05 +02:00
c0deba3b2b fix(cmake): hacky workaround for qscintilla 2025-06-08 15:04:58 +02:00
c14dfbea32 chore: fixed qscintilla on windows 2025-06-08 15:04:58 +02:00
e57a781c01 chore: ported lua to vcpkg 2025-06-08 15:04:58 +02:00
9d72d7f47a fix(editor): DataModel not remembering file path when opening rather than saving 2025-06-08 03:50:13 +02:00
6d733e82d4 fix(lua): missing tab between arguments in print statement 2025-06-08 03:49:51 +02:00
0ca65b1306 fix(misc): more compatibility changes 2025-06-08 03:38:32 +02:00
c7b9e873ee chore: fix builds on non-linux systems 2025-06-07 01:04:01 +02:00
0c4ef35ac7 fix(editor): PartAssembly not notifying properties pane of changes to Position and Rotation 2025-06-05 23:52:53 +02:00
d5e24bf3ca refactor(autogen): changed type of size in Part to Vector3 and removed vec3 from autogen 2025-06-05 20:36:37 +02:00
2b650c0fed chore: added warnings and dependencies. also fixed warnings 2025-06-05 20:19:36 +02:00
253c617d19 fix(instance): not compiling due to bad usage of std::make_shared 2025-06-05 16:56:25 +02:00
f5931c746d feat(lua): added missing tostrings and removed debug starter objects 2025-06-05 15:31:07 +02:00
5f726ad92b fix(editor): combo box closes immediately now, and spinbox allows negative values 2025-06-05 15:22:15 +02:00
5f3bed1c58 refactor(enum): made SurfaceType an enum class and added support for enum class in autogen 2025-06-05 00:54:14 +02:00
6a58aa7fbd feat(editor): enum support in properties pane 2025-06-05 00:46:31 +02:00
1f296e5fe4 feat(autogen): enum integration with objects 2025-06-05 00:24:25 +02:00
46856a06e2 feat(autogen): added enums 2025-06-04 23:02:01 +02:00
53b1788588 feat(editor): click-off to deselect 2025-06-04 18:47:43 +02:00
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
3a3b2d12c9 fix(lua): use-after-free in signal connection (thread) 2025-05-27 22:13:45 +02:00
778a5e35a4 fix(lua): stack overflow in signal 2025-05-27 21:39:57 +02:00
18b12ea1ad refactor(editor): multi-object move support + more handle jank refactor 2025-05-27 03:13:49 +02:00
49f29b6af1 fix(lua): signal functions were being deleted after being called 2025-05-26 15:08:16 +02:00
99 changed files with 2346 additions and 1240 deletions

3
.gitignore vendored
View file

@ -1,6 +1,7 @@
/bin/ /bin/
/lib/ /lib/
/build/ /build/
/build-rel
/autogen/build /autogen/build
# Qt # Qt
@ -14,4 +15,4 @@
/.gdb_history /.gdb_history
# Excluded assets # Excluded assets
/assets/excluded /assets/excluded

View file

@ -38,10 +38,14 @@ Now, generate the build files with cmake via the vcpkg preset:
cmake -Bbuild . --preset vcpkg cmake -Bbuild . --preset vcpkg
Then, finally, build in release mode: Then, finally, build in release mode\*:
cmake --build build --config Release cmake --build build --config Release
The compiled binaries should then be placed in `./build/bin/` and should be ready for redistribution without any further work. The compiled binaries should then be placed in `./build/bin/` and should be ready for redistribution without any further work.
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected. If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.
\* Release mode is necessary as debug mode copies DLLs that are not linked to the output binary
DEVELOPER NOTE: AKA Not for you. If you get CUSTOM COMMAND BUILD errors just keep rerunning build

View file

@ -3,6 +3,12 @@ set(CMAKE_CXX_STANDARD 20)
project(openblocks VERSION 0.1.0) project(openblocks VERSION 0.1.0)
set(OpenGL_GL_PREFERENCE "GLVND") set(OpenGL_GL_PREFERENCE "GLVND")
if (MSVC)
add_compile_options(/W4)
else()
add_compile_options(-Wall -Wextra -pedantic -Wno-unused-parameter)
endif()
set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ) set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" )
add_subdirectory(autogen) add_subdirectory(autogen)

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

@ -5,6 +5,7 @@ uniform vec3 scale;
in vec3 vPos; in vec3 vPos;
out vec4 fColor; out vec4 fColor;
uniform vec3 color;
void main() { void main() {
// float thickness = 0.2; // float thickness = 0.2;
@ -20,5 +21,6 @@ void main() {
// else // else
// fColor = vec4(0); // fColor = vec4(0);
fColor = vec4(0.204, 0.584, 0.922, 1); // fColor = vec4(0.204, 0.584, 0.922, 1);
fColor = vec4(color, 1);
} }

View file

@ -8,8 +8,10 @@ add_executable(autogen
src/object/codegen.cpp src/object/codegen.cpp
src/data/analysis.cpp src/data/analysis.cpp
src/data/codegen.cpp src/data/codegen.cpp
src/enum/analysis.cpp
src/enum/codegen.cpp
) )
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen") set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
target_link_libraries(autogen -lclang) target_link_libraries(autogen ${CLANG_LIBRARY})
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIRS}) target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIR})

View file

@ -10,8 +10,6 @@
using namespace data; using namespace data;
static std::string toStaticName(std::string orig) { static std::string toStaticName(std::string orig) {
bool isSnakeCase = orig.find('_') == -1;
std::string newName = ""; std::string newName = "";
int wordStart = 0; int wordStart = 0;
for (char c : orig) { for (char c : orig) {
@ -46,7 +44,6 @@ static void processConstructor(CXCursor cur, ClassAnalysis* state) {
auto result = parseAnnotationString(propertyDef.value()); auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur)); std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
anly.name = result["name"]; anly.name = result["name"];
anly.functionName = "__ctor"; anly.functionName = "__ctor";
@ -174,6 +171,25 @@ static bool hasMethod(CXCursor cur, std::string methodName) {
return found; return found;
} }
static bool hasGenericMethod(CXCursor cur, std::string methodName) {
bool found = false;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_CXXMethod) return CXChildVisit_Continue;
if (x_clang_toString(clang_getCursorSpelling(cur)) != methodName) return CXChildVisit_Continue;
int numArgs = clang_Cursor_getNumArguments(cur);
CXCursor lastParam = clang_Cursor_getArgument(cur, numArgs - 1);
std::string lastParamType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(lastParam)));
if (lastParamType != "const TypeMeta") return CXChildVisit_Continue;
found = true;
return CXChildVisit_Break;
});
return found;
}
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) { static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly; ClassAnalysis anly;
@ -184,6 +200,8 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
anly.serializedName = result["name"]; anly.serializedName = result["name"];
anly.hasFromString = hasMethod(cur, "FromString"); anly.hasFromString = hasMethod(cur, "FromString");
anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize"); anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize");
anly.hasGenericDeserializer = hasGenericMethod(cur, "Deserialize");
anly.hasGenericFromString = hasGenericMethod(cur, "FromString");
if (anly.serializedName == "") if (anly.serializedName == "")
anly.serializedName = className; anly.serializedName = className;

View file

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

View file

@ -9,11 +9,6 @@
using namespace data; using namespace data;
static std::map<std::string, std::string> MAPPED_TYPE = { 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> LUA_CHECK_FUNCS = { static std::map<std::string, std::string> LUA_CHECK_FUNCS = {
@ -30,16 +25,36 @@ static std::map<std::string, std::string> LUA_TEST_FUNCS = {
{ "std::string", "lua_isstring" }, { "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) { static std::string getLuaMethodFqn(std::string className, std::string methodName) {
return "__lua_impl__" + className + "__" + methodName; return "__lua_impl__" + className + "__" + methodName;
} }
static std::string getMtName(std::string type) { static std::string getMtName(std::string type) {
if (type.starts_with("Data::")) // if (type.starts_with("Data::"))
return "__mt_" + type.substr(6); // return "__mt_" + type.substr(6);
return "__mt_" + type; 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) { static void writeLuaGetArgument(std::ofstream& out, std::string type, int narg, bool member) {
std::string varname = "arg" + std::to_string(narg); std::string varname = "arg" + std::to_string(narg);
narg += 1; // Arguments start at 1 narg += 1; // Arguments start at 1
@ -69,7 +84,7 @@ static void writeLuaTestArgument(std::ofstream& out, std::string type, int narg,
} }
static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) { 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 // Collect all method names to account for overloaded functions
std::map<std::string, std::vector<MethodAnalysis>> methods; std::map<std::string, std::vector<MethodAnalysis>> methods;
@ -102,14 +117,14 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Check number of arguments // Check number of arguments
out << "n == " << std::to_string(methodImpl.parameters.size() + 1); // Account for first argument as 'this' out << "n == " << std::to_string(methodImpl.parameters.size() + 1); // Account for first argument as 'this'
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
out << " && "; out << " && ";
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, true); writeLuaTestArgument(out, methodImpl.parameters[i].type, i, true);
} }
out << ") {\n"; // End if condition, start if body out << ") {\n"; // End if condition, start if body
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, true); writeLuaGetArgument(out, methodImpl.parameters[i].type, i, true);
} }
@ -122,7 +137,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Call function // Call function
out << "this_->" << methodImpl.functionName << "("; out << "this_->" << methodImpl.functionName << "(";
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
std::string varname = "arg" + std::to_string(i); std::string varname = "arg" + std::to_string(i);
if (i != 0) out << ", "; if (i != 0) out << ", ";
out << varname; out << varname;
@ -132,11 +147,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Return result // Return result
if (methodImpl.returnType != "void") { if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType]; out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
} }
if (methodImpl.returnType == "void") if (methodImpl.returnType == "void")
@ -171,7 +182,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Check number of arguments // Check number of arguments
out << "n == " << std::to_string(methodImpl.parameters.size()); out << "n == " << std::to_string(methodImpl.parameters.size());
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
out << " && "; out << " && ";
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, false); writeLuaTestArgument(out, methodImpl.parameters[i].type, i, false);
} }
@ -179,7 +190,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
out << ") {\n"; // End if condition, start if body out << ") {\n"; // End if condition, start if body
// Get the arguments // Get the arguments
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, false); writeLuaGetArgument(out, methodImpl.parameters[i].type, i, false);
} }
@ -195,7 +206,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
else else
out << fqn << "::" << methodImpl.functionName << "("; out << fqn << "::" << methodImpl.functionName << "(";
for (int i = 0; i < methodImpl.parameters.size(); i++) { for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
std::string varname = "arg" + std::to_string(i); std::string varname = "arg" + std::to_string(i);
if (i != 0) out << ", "; if (i != 0) out << ", ";
out << varname; out << varname;
@ -205,11 +216,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
// Return result // Return result
if (methodImpl.returnType != "void") { if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType]; out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
} }
if (methodImpl.returnType == "void") if (methodImpl.returnType == "void")
@ -228,45 +235,42 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
} }
static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) { static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name; std::string fqn = state.name;
out << "static int data_gc(lua_State*);\n" out << "static int data_" << state.name << "_gc(lua_State*);\n"
"static int data_index(lua_State*);\n" "static int data_" << state.name << "_index(lua_State*);\n"
"static int data_tostring(lua_State*);\n" "static int data_" << state.name << "_tostring(lua_State*);\n"
"static const struct luaL_Reg metatable [] = {\n" "static const struct luaL_Reg " << state.name << "_metatable [] = {\n"
" {\"__gc\", data_gc},\n" " {\"__gc\", data_" << state.name << "_gc},\n"
" {\"__index\", data_index},\n" " {\"__index\", data_" << state.name << "_index},\n"
" {\"__tostring\", data_tostring},\n" " {\"__tostring\", data_" << state.name << "_tostring},\n"
" {NULL, NULL} /* end of array */\n" " {NULL, NULL} /* end of array */\n"
"};\n\n"; "};\n\n";
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" " 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" " " << fqn << "** userdata = (" << fqn << "**)lua_newuserdata(L, sizeof(" << fqn << "));\n"
" *userdata = new " << fqn << "(*this);\n" " *userdata = new " << fqn << "(*this);\n"
" // Create the library's metatable\n" " // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_" << state.name << "\");\n" " luaL_newmetatable(L, \"__mt_" << state.name << "\");\n"
" luaL_register(L, NULL, metatable);\n" " luaL_register(L, NULL, " << state.name << "_metatable);\n"
" lua_setmetatable(L, n+1);\n" " lua_setmetatable(L, n+1);\n"
"}\n\n"; "}\n\n";
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" " " << fqn << "** userdata = (" << fqn << "**) luaL_testudata(L, idx, \"" << getMtName(state.name) << "\");\n"
" if (userdata == nullptr)\n" " if (userdata == nullptr)\n"
" return LuaCastError(lua_typename(L, idx), \"" << state.name << "\");\n" " return LuaCastError(lua_typename(L, idx), \"" << state.name << "\");\n"
" return Data::Variant(**userdata);\n" " return Variant(**userdata);\n"
"}\n\n"; "}\n\n";
// Indexing methods and properties // Indexing methods and properties
out << "static int data_index(lua_State* L) {\n" out << "static int data_" << state.name << "_index(lua_State* L) {\n"
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
"\n" "\n"
" std::string key(lua_tostring(L, 2));\n" " std::string key(lua_tostring(L, 2));\n"
@ -292,7 +296,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
valueExpr = "this_->" + prop.backingSymbol + "()"; valueExpr = "this_->" + prop.backingSymbol + "()";
// This largely depends on the type // This largely depends on the type
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n"; out << " " << pushLuaValue(type, valueExpr) << ";\n";
out << " return 1;\n"; out << " return 1;\n";
out << " }"; out << " }";
@ -318,7 +322,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// ToString // ToString
out << "\nint data_tostring(lua_State* L) {\n" out << "\nint data_" << state.name << "_tostring(lua_State* L) {\n"
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
" lua_pushstring(L, std::string(this_->ToString()).c_str());\n" " lua_pushstring(L, std::string(this_->ToString()).c_str());\n"
" return 1;\n" " return 1;\n"
@ -326,7 +330,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
// Destructor // Destructor
out << "\nint data_gc(lua_State* L) {\n" out << "\nint data_" << state.name << "_gc(lua_State* L) {\n"
" " << fqn << "** userdata = (" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n" " " << fqn << "** userdata = (" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
" delete *userdata;\n" " delete *userdata;\n"
" return 0;\n" " return 0;\n"
@ -334,15 +338,20 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
} }
static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) { static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name; std::string fqn = state.name;
out << "static int lib_index(lua_State*);\n" // If there are no static methods or properties, no need to create a library
"static const struct luaL_Reg lib_metatable [] = {\n" if (state.staticMethods.size() == 0 && state.staticProperties.size() == 0) return;
" {\"__index\", lib_index},\n"
out << "static int lib_" << state.name << "_index(lua_State*);\n"
"static int lib_" << state.name << "_tostring(lua_State*);\n"
"static const struct luaL_Reg lib_" << state.name << "_metatable [] = {\n"
" {\"__index\", lib_" << state.name << "_index},\n"
" {\"__tostring\", lib_" << state.name << "_tostring},\n"
" {NULL, NULL} /* end of array */\n" " {NULL, NULL} /* end of array */\n"
"};\n\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_getglobal(L, \"_G\");\n"
" lua_pushstring(L, \"" << state.name << "\");\n" " lua_pushstring(L, \"" << state.name << "\");\n"
"\n" "\n"
@ -350,16 +359,23 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
"\n" "\n"
" // Create the library's metatable\n" " // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n" " luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n"
" luaL_register(L, NULL, lib_metatable);\n" " luaL_register(L, NULL, lib_" << state.name << "_metatable);\n"
" lua_setmetatable(L, -2);\n" " lua_setmetatable(L, -2);\n"
"\n" "\n"
" lua_rawset(L, -3);\n" " lua_rawset(L, -3);\n"
" lua_pop(L, 1);\n" " lua_pop(L, 1);\n"
"}\n\n"; "}\n\n";
// tostring
out << "\nint lib_" << state.name << "_tostring(lua_State* L) {\n"
" lua_pushstring(L, \"" << state.name << "\");\n"
" return 1;\n"
"}\n\n";
// Indexing methods and properties // Indexing methods and properties
out << "static int lib_index(lua_State* L) {\n" out << "static int lib_" << state.name << "_index(lua_State* L) {\n"
" std::string key(lua_tostring(L, 2));\n" " std::string key(lua_tostring(L, 2));\n"
" lua_pop(L, 2);\n" " lua_pop(L, 2);\n"
"\n"; "\n";
@ -382,7 +398,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
else if (prop.backingType == PropertyBackingType::Method) else if (prop.backingType == PropertyBackingType::Method)
valueExpr = fqn + "::" + prop.backingSymbol + "()"; valueExpr = fqn + "::" + prop.backingSymbol + "()";
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n"; out << " " << pushLuaValue(type, valueExpr) << ";\n";
out << " return 1;\n"; out << " return 1;\n";
out << " }"; out << " }";
@ -408,22 +424,29 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
} }
void data::writeCodeForClass(std::ofstream& out, std::string headerPath, 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 << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
out << "#include \"" << headerPath << "\"\n\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 <pugixml.hpp>\n";
out << "#include \"lua.h\"\n\n"; out << "#include \"lua.h\"\n\n";
out << "const Data::TypeInfo " << fqn << "::TYPE = {\n" out << "const TypeDesc " << state.name << "::TYPE = {\n"
<< " .name = \"" << state.serializedName << "\",\n" << " .name = \"" << state.serializedName << "\",\n";
<< " .deserializer = &" << fqn << "::Deserialize,\n"; if (state.isSerializable) {
if (state.hasFromString) out << " .fromString = &" << fqn << "::FromString,\n"; out << " .serialize = toVariantFunction(&" << state.name << "::Serialize),";
out << " .fromLuaValue = &" << fqn << "::FromLuaValue,\n" if (state.hasGenericDeserializer)
<< "};\n\n"; out << " .deserialize = toVariantGenerator(&" << state.name << "::Deserialize),\n";
else
out << "const Data::TypeInfo& " << fqn << "::GetType() const {\n" out << " .deserialize = toVariantGeneratorNoMeta(&" << state.name << "::Deserialize),\n";
<< " return TYPE;\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"; << "};\n\n";
writeLuaMethodImpls(out, state); writeLuaMethodImpls(out, state);

View file

@ -0,0 +1,89 @@
#include "analysis.h"
#include "../util.h"
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <optional>
using namespace enum_;
static int findEnumEntryValue(CXCursor cur, bool* success = nullptr) {
int ret = -1;
if (success != nullptr) *success = false;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_IntegerLiteral) return CXChildVisit_Recurse;
// https://stackoverflow.com/a/63859988/16255372
auto res = clang_Cursor_Evaluate(cur);
ret = clang_EvalResult_getAsInt(res);
clang_EvalResult_dispose(res);
if (success != nullptr) *success = true;
return CXChildVisit_Break;
});
return ret;
}
static void processEnumEntry(CXCursor cur, EnumAnalysis* state, int* lastValue) {
EnumEntry anly;
std::string name = x_clang_toString(clang_getCursorSpelling(cur));
bool success;
int value = findEnumEntryValue(cur, &success);
// Default to lastValue + 1
if (!success) value = ++(*lastValue);
*lastValue = value;
anly.name = name;
anly.value = value;
state->entries.push_back(anly);
}
static void processEnum(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
EnumAnalysis anly;
anly.name = className;
int lastValue = -1;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_EnumConstantDecl) {
processEnumEntry(cur, &anly, &lastValue);
}
return CXChildVisit_Continue;
});
state->classes[className] = anly;
}
bool enum_::analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state) {
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_Namespace) return CXChildVisit_Recurse;
if (kind != CXCursor_EnumDecl) return CXChildVisit_Continue;
CXSourceLocation loc = clang_getCursorLocation(cur);
if (!clang_Location_isFromMainFile(loc)) return CXChildVisit_Continue; // This class is not from this header. Skip
std::string className = x_clang_toString(clang_getCursorDisplayName(cur));
// Forward-decls can slip through the cracks, this prevents that, but also allows us to filter non-instance classes in the src/objects directory
if (!findAnnotation(cur, "OB::def_enum")) return CXChildVisit_Continue; // Class is not "primary" declaration/is not instance, skip
if (state->classes.count(className) > 0) return CXChildVisit_Continue; // Class has already been analyzed, skip...
processEnum(cur, state, className, srcRoot);
return CXChildVisit_Continue;
});
return true;
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <clang-c/Index.h>
#include <map>
#include <string>
#include <vector>
namespace enum_ {
struct EnumEntry {
std::string name;
int value;
};
struct EnumAnalysis {
std::string name;
std::vector<EnumEntry> entries;
};
struct AnalysisState {
std::map<std::string, EnumAnalysis> classes;
};
bool analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state);
}

View file

@ -0,0 +1,31 @@
#include "codegen.h"
#include "analysis.h"
#include <fstream>
#include <string>
#include <vector>
using namespace enum_;
void enum_::writeCodeForClass(std::ofstream& out, std::string headerPath, EnumAnalysis& state) {
out << "#include \"datatypes/enum.h\"\n\n";
out << "static std::pair<int, std::string> __values_" << state.name << "[] = {\n";
for (auto entry : state.entries) {
out << " { " << entry.value << ", \"" << entry.name << "\" },\n";
}
out << "};\n\n";
out << "static _EnumData __data_" << state.name << " = {\n"
<< " \"" << state.name << "\",\n"
<< " __values_" << state.name << ",\n"
<< " " << state.entries.size() << "\n"
<< "};\n\n";
out << "namespace EnumType {\n"
// extern is necessary here too to prevent "const" from marking Enum as implicitly static
// https://stackoverflow.com/questions/2190919/mixing-extern-and-const#comment2509591_2190981
<< " extern const Enum " << state.name << "(&__data_" << state.name << ");\n"
<< "}\n\n";
}

View file

@ -0,0 +1,10 @@
#pragma once
#include "analysis.h"
#include <fstream>
namespace enum_ {
void writeCodeForClass(std::ofstream& out, std::string headerPath, EnumAnalysis& state);
}

View file

@ -6,10 +6,13 @@
#include <cstdio> #include <cstdio>
#include <fstream> #include <fstream>
#include <filesystem> #include <filesystem>
#include "enum/analysis.h"
#include "enum/codegen.h"
#include "object/analysis.h" #include "object/analysis.h"
#include "object/codegen.h" #include "object/codegen.h"
#include "data/analysis.h" #include "data/analysis.h"
#include "data/codegen.h" #include "data/codegen.h"
#include "util.h"
// namespace data { // namespace data {
// #include "data/analysis.h" // #include "data/analysis.h"
@ -20,13 +23,17 @@ namespace fs = std::filesystem;
// https://clang.llvm.org/docs/LibClang.html // https://clang.llvm.org/docs/LibClang.html
int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) { int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), "-D__AUTOGEN__", 0 }; std::string srcRootStr = string_of(srcRoot);
std::string srcPathStr = string_of(srcPath);
std::string outPathStr = string_of(outPath);
const char* cargs[] = { "-xc++", "-std=c++17", "-I", srcRootStr.c_str(), "-D__AUTOGEN__", 0 };
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL // THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
// https://stackoverflow.com/a/59206378/16255372 // https://stackoverflow.com/a/59206378/16255372
CXIndex index = clang_createIndex(0, 0); CXIndex index = clang_createIndex(0, 0);
CXTranslationUnit unit = clang_parseTranslationUnit( CXTranslationUnit unit = clang_parseTranslationUnit(
index, index,
srcPath.c_str(), cargs, 5, srcPathStr.c_str(), cargs, 5,
nullptr, 0, nullptr, 0,
CXTranslationUnit_None); CXTranslationUnit_None);
@ -50,16 +57,19 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
object::AnalysisState objectAnlyState; object::AnalysisState objectAnlyState;
data::AnalysisState dataAnlyState; data::AnalysisState dataAnlyState;
enum_::AnalysisState enumAnlyState;
fs::path relpath = fs::relative(srcPath, srcRoot); fs::path relpath = fs::relative(srcPath, srcRoot);
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str()); std::string relpathStr = string_of(relpath);
object::analyzeClasses(cursor, srcRoot, &objectAnlyState); printf("[AUTOGEN] Processing file %s...\n", relpathStr.c_str());
data::analyzeClasses(cursor, srcRoot, &dataAnlyState); object::analyzeClasses(cursor, srcRootStr, &objectAnlyState);
data::analyzeClasses(cursor, srcRootStr, &dataAnlyState);
enum_::analyzeClasses(cursor, srcRootStr, &enumAnlyState);
fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it
printf("[AUTOGEN] Generating file %s...\n", relpath.c_str()); printf("[AUTOGEN] Generating file %s...\n", relpathStr.c_str());
std::ofstream outStream(outPath); std::ofstream outStream(outPathStr);
if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) { if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) {
outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n"; outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n";
@ -68,11 +78,15 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
} }
for (auto& [_, clazz] : objectAnlyState.classes) { for (auto& [_, clazz] : objectAnlyState.classes) {
object::writeCodeForClass(outStream, relpath, clazz); object::writeCodeForClass(outStream, relpathStr, clazz);
} }
for (auto& [_, clazz] : dataAnlyState.classes) { for (auto& [_, clazz] : dataAnlyState.classes) {
data::writeCodeForClass(outStream, relpath, clazz); data::writeCodeForClass(outStream, relpathStr, clazz);
}
for (auto& [_, clazz] : enumAnlyState.classes) {
enum_::writeCodeForClass(outStream, relpathStr, clazz);
} }
outStream.close(); outStream.close();

View file

@ -80,7 +80,13 @@ static void processField(CXCursor cur, ClassAnalysis* state) {
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly; anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly;
CXType type = clang_getCursorType(cur); CXType type = clang_getCursorType(cur);
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str(); anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type));
CXCursor typeCur = clang_getTypeDeclaration(type);
bool isEnum = findAnnotation(typeCur, "OB::def_enum").has_value();
if (isEnum) {
anly.backingFieldType = "EnumItem";
anly.backingFieldEnum = x_clang_toString(clang_getTypeSpelling(type));
}
state->properties.push_back(anly); state->properties.push_back(anly);
@ -156,7 +162,7 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
anly.name = className; anly.name = className;
anly.baseClass = baseClass; anly.baseClass = baseClass;
anly.headerPath = headerPath; anly.headerPath = string_of(headerPath);
// Add misc flags and options // Add misc flags and options
auto instanceDef = findAnnotation(cur, "OB::def_inst"); auto instanceDef = findAnnotation(cur, "OB::def_inst");

View file

@ -31,6 +31,7 @@ struct PropertyAnalysis {
std::string fieldName; std::string fieldName;
CFrameMember cframeMember = CFrameMember_None; // for cframe_position_prop etc. CFrameMember cframeMember = CFrameMember_None; // for cframe_position_prop etc.
std::string backingFieldType; std::string backingFieldType;
std::string backingFieldEnum;
std::string onUpdateCallback; std::string onUpdateCallback;
std::string category; std::string category;
PropertyFlags flags = (PropertyFlags)0; PropertyFlags flags = (PropertyFlags)0;

View file

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

View file

@ -25,11 +25,11 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
int stage = 0; int stage = 0;
bool quoted = false; bool quoted = false;
int i = 0; size_t i = 0;
for (; i < src.length(); i++) { for (; i < src.length(); i++) {
if (src[i] == ' ' && (stage != 2 || !quoted)) continue; // Ignore spaces if not in stage 2 and quoted if (src[i] == ' ' && (stage != 2 || !quoted)) continue; // Ignore spaces if not in stage 2 and quoted
if (src[i] == ',' && stage == 0) continue; // Let empty commas slip by if (src[i] == ',' && stage == 0) continue; // Let empty commas slip by
if (stage < 2 && (src[i] >= 'a' && src[i] <= 'z' || src[i] >= 'A' && src[i] <= 'Z' || src[i] >= '0' && src[i] <= '9' || src[i] == '_')) { if (stage < 2 && ((src[i] >= 'a' && src[i] <= 'z') || (src[i] >= 'A' && src[i] <= 'Z') || (src[i] >= '0' && src[i] <= '9') || src[i] == '_')) {
currentIdent += src[i]; currentIdent += src[i];
stage = 1; stage = 1;
continue; continue;
@ -63,7 +63,7 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
currentValue += src[i]; currentValue += src[i];
continue; continue;
} }
fprintf(stderr, "Unexpected symbol: %c at index %d\n", src[i], i); fprintf(stderr, "Unexpected symbol: %c at index %zu\n", src[i], i);
fprintf(stderr, "\t%s\n", src.c_str()); fprintf(stderr, "\t%s\n", src.c_str());
fprintf(stderr, "\t%s^\n", i > 0 ? std::string(i, '~').c_str() : ""); fprintf(stderr, "\t%s^\n", i > 0 ? std::string(i, '~').c_str() : "");
abort(); abort();
@ -108,4 +108,8 @@ std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationNa
return CXChildVisit_Break; return CXChildVisit_Break;
}); });
return ret; return ret;
}
std::string string_of(std::filesystem::path path) {
return path.string();
} }

View file

@ -5,6 +5,7 @@
#include <map> #include <map>
#include <optional> #include <optional>
#include <string> #include <string>
#include <filesystem>
typedef std::function<CXChildVisitResult(CXCursor cursor, CXCursor parent)> X_CXCursorVisitor; typedef std::function<CXChildVisitResult(CXCursor cursor, CXCursor parent)> X_CXCursorVisitor;
@ -19,4 +20,6 @@ std::string x_clang_toString(CXString string);
// "name": "Hello!", "world": "Test", "read_only": "" // "name": "Hello!", "world": "Test", "read_only": ""
std::map<std::string, std::string> parseAnnotationString(std::string src); std::map<std::string, std::string> parseAnnotationString(std::string src);
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName); std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName);
std::string string_of(std::filesystem::path path);

View file

@ -4,4 +4,5 @@ include_directories(${SDL2_INCLUDE_DIRS})
find_package(glfw3 REQUIRED) find_package(glfw3 REQUIRED)
add_executable(client "src/main.cpp") add_executable(client "src/main.cpp")
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw) target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
add_dependencies(client openblocks)

View file

@ -52,7 +52,7 @@ int main() {
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f), .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; if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
gWorkspace()->SyncPartPhysics(part); gWorkspace()->SyncPartPhysics(part);
@ -170,15 +170,15 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
} }
} else if (mode == 1) { } else if (mode == 1) {
if (key == GLFW_KEY_X && action == GLFW_PRESS) { if (key == GLFW_KEY_X && action == GLFW_PRESS) {
lastPart->size.x += shiftFactor; lastPart->size += Vector3(1, 0, 0) * shiftFactor;
gWorkspace()->SyncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Y && action == GLFW_PRESS) { if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->size.y += shiftFactor; lastPart->size += Vector3(0, 1, 0) * shiftFactor;
gWorkspace()->SyncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Z && action == GLFW_PRESS) { if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->size.z += shiftFactor; lastPart->size += Vector3(0, 0, 1) * shiftFactor;
gWorkspace()->SyncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
} }

42
cmake/FindClang.cmake Normal file
View file

@ -0,0 +1,42 @@
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
add_library(Clang::Clang UNKNOWN IMPORTED)
FIND_PATH(CLANG_INCLUDE_DIR
NAMES clang-c/Index.h
PATHS
$ENV{LIB_DIR}/include
/usr/local/include
/usr/include
${VCPKG_INSTALLED_DIR}/x64-windows/include
"C:/Program Files/LLVM/include"
PATH_SUFFIXES ${CLANG_PATH_SUFFIXES}
)
set(CLANG_LIBRARY_NAMES
libclang
clang
)
find_library(CLANG_LIBRARY
NAMES ${CLANG_LIBRARY_NAMES}
PATHS
$ENV{LIB_DIR}/lib
/usr/local/lib
/usr/lib
${VCPKG_INSTALLED_DIR}/x64-windows/lib
"C:/Program Files/LLVM/lib"
)
get_filename_component(CLANG_LIB_DIR ${CLANG_LIBRARY} DIRECTORY)
list(TRANSFORM CLANG_LIBRARY_NAMES APPEND ".dll" OUTPUT_VARIABLE CLANG_DLL_NAMES)
find_file(CLANG_DLLS
NAMES ${CLANG_DLL_NAMES}
PATHS
$ENV{LIB_DIR}/bin
/usr/local/bin
/usr/bin
${VCPKG_INSTALLED_DIR}/x64-windows/bin
"C:/Program Files/LLVM/bin"
)

View file

@ -17,7 +17,7 @@ pkg_check_modules(LUAJIT REQUIRED luajit)
link_directories(${LUAJIT_LIBRARY_DIRS}) link_directories(${LUAJIT_LIBRARY_DIRS})
# Run autogen # Run autogen
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.h") file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.h" "src/enum/*.h")
# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Custom%20Commands.html # https://cmake.org/cmake/help/book/mastering-cmake/chapter/Custom%20Commands.html
foreach (SRC ${AUTOGEN_SOURCES}) foreach (SRC ${AUTOGEN_SOURCES})
@ -27,8 +27,9 @@ foreach (SRC ${AUTOGEN_SOURCES})
add_custom_command( add_custom_command(
OUTPUT "${OUT_PATH}" OUTPUT "${OUT_PATH}"
DEPENDS autogen
DEPENDS "${SRC_PATH}" DEPENDS "${SRC_PATH}"
COMMAND "${CMAKE_BINARY_DIR}/autogen/autogen" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}" COMMAND "$<TARGET_FILE:autogen>" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}"
) )
list(APPEND AUTOGEN_OUTS "${OUT_PATH}") list(APPEND AUTOGEN_OUTS "${OUT_PATH}")
@ -45,6 +46,7 @@ set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS}) target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS})
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml) target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS}) target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS})
add_dependencies(openblocks autogen_build autogen)
# Windows-specific dependencies # Windows-specific dependencies
if(WIN32) if(WIN32)

View file

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

View file

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

View file

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

View file

@ -1,23 +1,24 @@
#include "cframe.h" #include "cframe.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "error/data.h"
#include "physics/util.h" #include "physics/util.h"
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp> #include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <glm/matrix.hpp> #include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h> #include <reactphysics3d/mathematics/Transform.h>
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#define GLM_ENABLE_EXPERIMENTAL #define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp> #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 CFrame 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::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) : translation(x, y, z)
, rotation({ , rotation({
// { R00, R01, R02 }, // { 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) : translation(translation)
, rotation(rotation) { , rotation(rotation) {
} }
Data::CFrame::CFrame(Vector3 position, glm::quat quat) CFrame::CFrame(Vector3 position, glm::quat quat)
: translation(position) : translation(position)
, rotation(quat) { , 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) { 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 }; return { s, u, -f };
} }
Data::CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up) CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
: translation(position) : translation(position)
, rotation(::lookAt(position, lookAt, up)) { , 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()); return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
} }
Data::CFrame Data::CFrame::pointToward(Vector3 position, Vector3 toward) { CFrame 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)); 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) { CFrame CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
return Data::CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up); 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 // 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); 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)); return rp::Transform(glmToRp(translation), glmToRp(rotation));
} }
Vector3 Data::CFrame::ToEulerAnglesXYZ() { Vector3 CFrame::ToEulerAnglesXYZ() {
float x; float x;
float y; float y;
float z; float z;
@ -88,37 +89,37 @@ Vector3 Data::CFrame::ToEulerAnglesXYZ() {
return Vector3(x, y, z); 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()); 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) }; return CFrame { -translation * glm::transpose(glm::inverse(rotation)), glm::inverse(rotation) };
} }
// Operators // 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 }; 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; 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 }; 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; return *this + -vector;
} }
// Serialization // 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("X").text().set(std::to_string(this->X()));
node.append_child("Y").text().set(std::to_string(this->Y())); node.append_child("Y").text().set(std::to_string(this->Y()));
node.append_child("Z").text().set(std::to_string(this->Z())); 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) { result<CFrame, DataParseError> CFrame::Deserialize(pugi::xml_node node) {
return Data::CFrame( return CFrame(
node.child("X").text().as_float(), node.child("X").text().as_float(),
node.child("Y").text().as_float(), node.child("Y").text().as_float(),
node.child("Z").text().as_float(), node.child("Z").text().as_float(),

View file

@ -3,72 +3,69 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "error/data.h"
#include <glm/ext/quaternion_float.hpp> #include <glm/ext/quaternion_float.hpp>
#include <glm/gtc/matrix_access.hpp> #include <glm/gtc/matrix_access.hpp>
#include <glm/matrix.hpp> #include <glm/matrix.hpp>
namespace reactphysics3d { class Transform; }; namespace reactphysics3d { class Transform; };
namespace Data { class DEF_DATA_(name="CoordinateFrame") CFrame {
class DEF_DATA_(name="CoordinateFrame") CFrame : public Base { AUTOGEN_PREAMBLE_DATA
AUTOGEN_PREAMBLE_DATA
glm::vec3 translation; glm::vec3 translation;
glm::mat3 rotation; 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();
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant CFrame(glm::vec3, glm::mat3);
static CFrame pointToward(Vector3 position, Vector3 toward); public:
// Creates a cframe looking at position + toward, whilst aligning its up to up. // CFrame(float x, float y, float z);
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999), // CFrame(const glm::vec3&);
// then the right is used instead // CFrame(const rp::Vector3&);
// Up and right must NOT be linearly dependent DEF_DATA_CTOR CFrame();
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right); 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);
virtual ~CFrame();
DEF_DATA_PROP static const CFrame IDENTITY; // Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
static const CFrame YToZ; 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; DEF_DATA_PROP static const CFrame IDENTITY;
virtual void Serialize(pugi::xml_node parent) const override; static const CFrame YToZ;
static Data::Variant Deserialize(pugi::xml_node node);
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; static void PushLuaLibrary(lua_State*);
operator reactphysics3d::Transform() const;
//inline static CFrame identity() { } operator glm::mat4() const;
DEF_DATA_PROP inline Vector3 Position() const { return translation; } operator reactphysics3d::Transform() const;
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_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); } //inline static CFrame identity() { }
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); } DEF_DATA_PROP inline Vector3 Position() const { return translation; }
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); } 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_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3); 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_METHOD Vector3 ToEulerAnglesXYZ();
DEF_DATA_OP Data::CFrame operator *(Data::CFrame) const; DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
DEF_DATA_OP Vector3 operator *(Vector3) const;
DEF_DATA_OP Data::CFrame operator +(Vector3) const;
DEF_DATA_OP Data::CFrame operator -(Vector3) const;
};
}
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,23 @@
#include "color3.h" #include "color3.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <algorithm>
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)) {}; 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(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)); 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; std::stringstream ss;
ss << "FF" << std::hex << std::uppercase << std::setfill('0') ss << "FF" << std::hex << std::uppercase << std::setfill('0')
<< std::setw(2) << int(r*255) << std::setw(2) << int(r*255)
@ -25,7 +27,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 r = float(std::stoi(hex.substr(2, 2), nullptr, 16)) / 255;
float g = float(std::stoi(hex.substr(4, 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; float b = float(std::stoi(hex.substr(6, 2), nullptr, 16)) / 255;
@ -35,10 +37,10 @@ Data::Color3 Data::Color3::FromHex(std::string hex) {
// Serialization // Serialization
void Data::Color3::Serialize(pugi::xml_node node) const { void Color3::Serialize(pugi::xml_node node) const {
node.text().set(this->ToHex()); 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()); return Color3::FromHex(node.text().get());
} }

View file

@ -2,36 +2,33 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp> #include <glm/ext/vector_float3.hpp>
namespace Data { class DEF_DATA Color3 {
class DEF_DATA Color3 : public Base { AUTOGEN_PREAMBLE_DATA
AUTOGEN_PREAMBLE_DATA
float r; float r;
float g; float g;
float b; float b;
public:
DEF_DATA_CTOR Color3(float r, float g, float b);
Color3(const glm::vec3&);
~Color3();
DEF_DATA_METHOD static Color3 FromHex(std::string hex); public:
DEF_DATA_CTOR Color3(float r, float g, float b);
Color3(const glm::vec3&);
virtual ~Color3();
virtual const Data::String ToString() const override; DEF_DATA_METHOD static Color3 FromHex(std::string hex);
DEF_DATA_METHOD std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const override;
static Data::Variant Deserialize(pugi::xml_node node);
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; } operator glm::vec3() const;
DEF_DATA_PROP inline float G() const { return g; }
DEF_DATA_PROP inline float B() const { return b; }
};
}
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,72 @@
#include "enum.h"
#include "datatypes/base.h"
#include "datatypes/variant.h"
#include "error/data.h"
#include <pugixml.hpp>
TypeMeta::TypeMeta(const Enum* enum_) : descriptor(&EnumItem::TYPE), enum_(enum_) {}
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 Enum::FromValueInternal(int value) const {
auto result = this->FromValue(value);
if (!result) return EnumItem(data, "", value);
return result.value();
}
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");
}

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

@ -0,0 +1,59 @@
#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::string name;
std::pair<int, std::string>* values;
int count;
};
class EnumItem;
class DEF_DATA Enum {
_EnumData* data;
public:
Enum(_EnumData*);
virtual ~Enum() = default;
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;
EnumItem FromValueInternal(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);
virtual ~EnumItem() = default;
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 "datatypes/base.h"
#include "error/data.h" #include "error/data.h"
#include "logger.h" #include "logger.h"
#include "meta.h" // IWYU pragma: keep #include "variant.h" // IWYU pragma: keep
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "objects/base/instance.h" #include "objects/base/instance.h"
@ -10,47 +10,55 @@
#include "objects/base/member.h" #include "objects/base/member.h"
#include <pugixml.hpp> #include <pugixml.hpp>
Data::InstanceRef::InstanceRef() {}; TypeMeta::TypeMeta(const InstanceType* instType) : descriptor(&InstanceRef::TYPE), instType(instType) {}
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
Data::InstanceRef::~InstanceRef() = default;
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", .name = "Ref",
.deserializer = &Data::InstanceRef::Deserialize, .serialize = toVariantFunction(&InstanceRef::Serialize),
.fromLuaValue = &Data::InstanceRef::FromLuaValue, .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 std::string InstanceRef::ToString() const {
const Data::String Data::InstanceRef::ToString() const {
return ref.expired() ? "" : ref.lock()->name; return ref.expired() ? "" : ref.lock()->name;
} }
Data::InstanceRef::operator std::weak_ptr<Instance>() { InstanceRef::operator std::weak_ptr<Instance>() {
return ref; return ref;
} }
// Serialization // Serialization
void Data::InstanceRef::Serialize(pugi::xml_node node) const { void InstanceRef::Serialize(pugi::xml_node node) const {
// node.text().set(this->ToHex()); // Handled by Instance
panic();
} }
Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) { result<InstanceRef, DataParseError> InstanceRef::Deserialize(pugi::xml_node node) {
return Data::InstanceRef(); // Handled by Instance
panic();
} }
static int inst_gc(lua_State*); static int inst_gc(lua_State*);
static int inst_index(lua_State*); static int inst_index(lua_State*);
static int inst_newindex(lua_State*); static int inst_newindex(lua_State*);
static int inst_tostring(lua_State*);
static const struct luaL_Reg metatable [] = { static const struct luaL_Reg metatable [] = {
{"__gc", inst_gc}, {"__gc", inst_gc},
{"__index", inst_index}, {"__index", inst_index},
{"__newindex", inst_newindex}, {"__newindex", inst_newindex},
{"__tostring", inst_tostring},
{NULL, NULL} /* end of array */ {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); if (ref.expired()) return lua_pushnil(L);
int n = lua_gettop(L); int n = lua_gettop(L);
@ -68,14 +76,14 @@ void Data::InstanceRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1); 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)) if (lua_isnil(L, idx))
return Data::Variant(Data::InstanceRef()); return Variant(InstanceRef());
if (!lua_isuserdata(L, idx)) if (!lua_isuserdata(L, idx))
return LuaCastError(lua_typename(L, idx), "Instance"); return LuaCastError(lua_typename(L, idx), "Instance");
// TODO: overhaul this to support other types // TODO: overhaul this to support other types
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, idx); 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) { static int inst_gc(lua_State* L) {
@ -97,7 +105,7 @@ static int inst_index(lua_State* L) {
// Read property // Read property
std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key); std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key);
if (meta) { if (meta) {
Data::Variant value = inst->GetPropertyValue(key).expect(); Variant value = inst->GetPropertyValue(key).expect();
value.PushLuaValue(L); value.PushLuaValue(L);
return 1; return 1;
} }
@ -105,7 +113,7 @@ static int inst_index(lua_State* L) {
// Look for child // Look for child
std::optional<std::shared_ptr<Instance>> child = inst->FindFirstChild(key); std::optional<std::shared_ptr<Instance>> child = inst->FindFirstChild(key);
if (child) { if (child) {
Data::InstanceRef(child.value()).PushLuaValue(L); InstanceRef(child.value()).PushLuaValue(L);
return 1; return 1;
} }
@ -127,11 +135,21 @@ static int inst_newindex(lua_State* L) {
if (key == "Parent" && inst->IsParentLocked()) 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()); 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); lua_pop(L, 3);
if (value.isError()) if (value.isError())
return luaL_error(L, "%s", value.errorMessage().value().c_str()); return luaL_error(L, "%s", value.errorMessage().value().c_str());
inst->SetPropertyValue(key, value.expect()).expect(); inst->SetPropertyValue(key, value.expect()).expect();
return 0; return 0;
}
static int inst_tostring(lua_State* L) {
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, 1);
std::shared_ptr<Instance> inst = **userdata;
lua_pushstring(L, inst->name.c_str());
return 1;
} }

View file

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

View file

@ -1,7 +1,8 @@
#include "signal.h" #include "signal.h"
#include "datatypes/base.h" #include "datatypes/base.h"
#include "meta.h" #include "variant.h"
#include "lua.h" #include "lua.h"
#include <cstdio>
#include <pugixml.hpp> #include <pugixml.hpp>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -21,24 +22,44 @@ LuaSignalConnection::LuaSignalConnection(lua_State* L, std::weak_ptr<Signal> par
// https://stackoverflow.com/a/31952046/16255372 // https://stackoverflow.com/a/31952046/16255372
// Save function so it doesn't get GC'd // Save function and current thread so they don't get GC'd
function = luaL_ref(L, LUA_REGISTRYINDEX); function = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1); lua_pushthread(L);
thread = luaL_ref(L, LUA_REGISTRYINDEX);
} }
LuaSignalConnection::~LuaSignalConnection() { LuaSignalConnection::~LuaSignalConnection() {
// Remove LuaSignalConnectionthread so that it can get properly GC'd // Remove LuaSignalConnectionthread so that it can get properly GC'd
luaL_unref(state, LUA_REGISTRYINDEX, function); luaL_unref(state, LUA_REGISTRYINDEX, function);
luaL_unref(state, LUA_REGISTRYINDEX, thread);
} }
void LuaSignalConnection::Call(std::vector<Data::Variant> args) { #if 0
static void stackdump(lua_State* L) {
printf("%d\n", lua_gettop(L));
fflush(stdout);
lua_getfield(L, LUA_GLOBALSINDEX, "tostring");
for (int i = lua_gettop(L)-1; i >= 1; i--) {
lua_pushvalue(L, -1);
lua_pushvalue(L, i);
lua_call(L, 1, 1);
const char* str = lua_tostring(L, -1);
lua_pop(L, 1);
printf("%s: %s\n", lua_typename(L, lua_type(L, i)), str);
}
lua_pop(L, 1);
printf("\n\n");
fflush(stdout);
}
#endif
void LuaSignalConnection::Call(std::vector<Variant> args) {
lua_State* thread = lua_newthread(state); lua_State* thread = lua_newthread(state);
// Push function // Push function
lua_rawgeti(thread, LUA_REGISTRYINDEX, function); lua_rawgeti(thread, LUA_REGISTRYINDEX, function);
luaL_unref(thread, LUA_REGISTRYINDEX, function);
for (Data::Variant arg : args) { for (Variant arg : args) {
arg.PushLuaValue(thread); arg.PushLuaValue(thread);
} }
@ -47,15 +68,17 @@ void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
Logger::error(lua_tostring(thread, -1)); Logger::error(lua_tostring(thread, -1));
lua_pop(thread, 1); // Pop return value lua_pop(thread, 1); // Pop return value
} }
lua_pop(state, 1); // Pop thread
} }
// //
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; this->function = func;
} }
void CSignalConnection::Call(std::vector<Data::Variant> args) { void CSignalConnection::Call(std::vector<Variant> args) {
function(args); function(args);
} }
@ -63,7 +86,7 @@ void CSignalConnection::Call(std::vector<Data::Variant> args) {
SignalConnectionHolder::SignalConnectionHolder() : heldConnection() {} SignalConnectionHolder::SignalConnectionHolder() : heldConnection() {}
SignalConnectionHolder::SignalConnectionHolder(std::shared_ptr<SignalConnection> connection) : heldConnection(connection) {} SignalConnectionHolder::SignalConnectionHolder(std::shared_ptr<SignalConnection> connection) : heldConnection(connection) {}
SignalConnectionHolder::SignalConnectionHolder(Data::SignalConnectionRef other) : heldConnection(other) {} SignalConnectionHolder::SignalConnectionHolder(SignalConnectionRef other) : heldConnection(other) {}
SignalConnectionHolder::~SignalConnectionHolder() { SignalConnectionHolder::~SignalConnectionHolder() {
// printf("Prediscon!\n"); // printf("Prediscon!\n");
@ -74,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())); auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
connections.push_back(conn); connections.push_back(conn);
return SignalConnectionRef(conn); return SignalConnectionRef(conn);
@ -86,7 +109,7 @@ SignalConnectionRef Signal::Connect(lua_State* state) {
return SignalConnectionRef(conn); 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())); auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
onceConnections.push_back(conn); onceConnections.push_back(conn);
return SignalConnectionRef(conn); return SignalConnectionRef(conn);
@ -109,7 +132,7 @@ int Signal::Wait(lua_State* thread) {
return lua_yield(thread, 0); 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) { for (std::shared_ptr<SignalConnection> connection : connections) {
connection->Call(args); connection->Call(args);
} }
@ -125,7 +148,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
auto prevThreads = std::move(waitingThreads); auto prevThreads = std::move(waitingThreads);
waitingThreads = std::vector<std::pair<int, lua_State*>>(); waitingThreads = std::vector<std::pair<int, lua_State*>>();
for (auto& [threadId, thread] : prevThreads) { for (auto& [threadId, thread] : prevThreads) {
for (Data::Variant arg : args) { for (Variant arg : args) {
arg.PushLuaValue(thread); arg.PushLuaValue(thread);
} }
@ -142,7 +165,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
} }
void Signal::Fire() { void Signal::Fire() {
return Fire(std::vector<Data::Variant> {}); return Fire(std::vector<Variant> {});
} }
void Signal::DisconnectAll() { void Signal::DisconnectAll() {
@ -199,29 +222,32 @@ static const struct luaL_Reg signal_metatable [] = {
{NULL, NULL} /* end of array */ {NULL, NULL} /* end of array */
}; };
Data::SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {} SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
Data::SignalRef::~SignalRef() = default; SignalRef::~SignalRef() = default;
const Data::TypeInfo Data::SignalRef::TYPE = { const TypeDesc SignalRef::TYPE = {
.name = "Signal", .name = "Signal",
.fromLuaValue = &Data::SignalRef::FromLuaValue, .serialize = nullptr,
.deserialize = nullptr,
.toString = toVariantFunction(&SignalRef::ToString),
.fromString = nullptr,
.pushLuaValue = toVariantFunction(&SignalRef::PushLuaValue),
.fromLuaValue = &SignalRef::FromLuaValue,
}; };
const Data::TypeInfo& Data::SignalRef::GetType() const { return Data::SignalRef::TYPE; }; const std::string SignalRef::ToString() const {
return "Signal";
const Data::String Data::SignalRef::ToString() const {
return Data::String("Signal");
} }
Data::SignalRef::operator std::weak_ptr<Signal>() { SignalRef::operator std::weak_ptr<Signal>() {
return signal; return signal;
} }
void Data::SignalRef::Serialize(pugi::xml_node node) const { void SignalRef::Serialize(pugi::xml_node node) const {
// Not serializable // Not serializable
} }
void Data::SignalRef::PushLuaValue(lua_State* L) const { void SignalRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L); int n = lua_gettop(L);
auto userdata = (std::weak_ptr<Signal>**)lua_newuserdata(L, sizeof(std::weak_ptr<Signal>)); auto userdata = (std::weak_ptr<Signal>**)lua_newuserdata(L, sizeof(std::weak_ptr<Signal>));
@ -234,10 +260,10 @@ void Data::SignalRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1); 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"); auto userdata = (std::weak_ptr<Signal>**)luaL_checkudata(L, 1, "__mt_signal");
lua_pop(L, 1); lua_pop(L, 1);
return Data::Variant(Data::SignalRef(**userdata)); return Variant(SignalRef(**userdata));
} }
static int signal_gc(lua_State* L) { static int signal_gc(lua_State* L) {
@ -320,29 +346,32 @@ static const struct luaL_Reg signalconnection_metatable [] = {
{NULL, NULL} /* end of array */ {NULL, NULL} /* end of array */
}; };
Data::SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {} SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
Data::SignalConnectionRef::~SignalConnectionRef() = default; SignalConnectionRef::~SignalConnectionRef() = default;
const Data::TypeInfo Data::SignalConnectionRef::TYPE = { const TypeDesc SignalConnectionRef::TYPE = {
.name = "Signal", .name = "Signal",
.fromLuaValue = &Data::SignalConnectionRef::FromLuaValue, .serialize = nullptr,
.deserialize = nullptr,
.toString = toVariantFunction(&SignalConnectionRef::ToString),
.fromString = nullptr,
.pushLuaValue = toVariantFunction(&SignalConnectionRef::PushLuaValue),
.fromLuaValue = &SignalConnectionRef::FromLuaValue,
}; };
const Data::TypeInfo& Data::SignalConnectionRef::GetType() const { return Data::SignalConnectionRef::TYPE; }; const std::string SignalConnectionRef::ToString() const {
return "Connection";
const Data::String Data::SignalConnectionRef::ToString() const {
return Data::String("Connection");
} }
Data::SignalConnectionRef::operator std::weak_ptr<SignalConnection>() { SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
return signalConnection; return signalConnection;
} }
void Data::SignalConnectionRef::Serialize(pugi::xml_node node) const { void SignalConnectionRef::Serialize(pugi::xml_node node) const {
// Not serializable // Not serializable
} }
void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const { void SignalConnectionRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L); int n = lua_gettop(L);
auto userdata = (std::weak_ptr<SignalConnection>**)lua_newuserdata(L, sizeof(std::weak_ptr<SignalConnection>)); auto userdata = (std::weak_ptr<SignalConnection>**)lua_newuserdata(L, sizeof(std::weak_ptr<SignalConnection>));
@ -355,10 +384,10 @@ void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1); 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"); auto userdata = (std::weak_ptr<SignalConnection>**)luaL_checkudata(L, 1, "__mt_signalconnection");
lua_pop(L, 1); lua_pop(L, 1);
return Data::Variant(Data::SignalConnectionRef(**userdata)); return Variant(SignalConnectionRef(**userdata));
} }
static int signalconnection_tostring(lua_State* L) { static int signalconnection_tostring(lua_State* L) {

View file

@ -15,7 +15,7 @@
class Instance; class Instance;
class Signal; class Signal;
namespace Data { class SignalConnectionRef; } class SignalConnectionRef;
class SignalConnection : public std::enable_shared_from_this<SignalConnection> { class SignalConnection : public std::enable_shared_from_this<SignalConnection> {
protected: protected:
@ -23,7 +23,7 @@ protected:
SignalConnection(std::weak_ptr<Signal> parent); SignalConnection(std::weak_ptr<Signal> parent);
virtual void Call(std::vector<Data::Variant>) = 0; virtual void Call(std::vector<Variant>) = 0;
friend Signal; friend Signal;
public: public:
inline bool Connected() { return !parentSignal.expired(); }; inline bool Connected() { return !parentSignal.expired(); };
@ -33,27 +33,28 @@ public:
}; };
class CSignalConnection : public SignalConnection { class CSignalConnection : public SignalConnection {
std::function<void(std::vector<Data::Variant>)> function; std::function<void(std::vector<Variant>)> function;
friend Signal; friend Signal;
protected: protected:
void Call(std::vector<Data::Variant>) override; void Call(std::vector<Variant>) override;
public: 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);
virtual ~CSignalConnection() = default;
}; };
class LuaSignalConnection : public SignalConnection { class LuaSignalConnection : public SignalConnection {
lua_State* state; lua_State* state;
int function; int function, thread;
friend Signal; friend Signal;
protected: protected:
void Call(std::vector<Data::Variant>) override; void Call(std::vector<Variant>) override;
public: public:
LuaSignalConnection(lua_State*, std::weak_ptr<Signal> parent); LuaSignalConnection(lua_State*, std::weak_ptr<Signal> parent);
LuaSignalConnection (const LuaSignalConnection&) = delete; LuaSignalConnection (const LuaSignalConnection&) = delete;
LuaSignalConnection& operator= (const LuaSignalConnection&) = delete; LuaSignalConnection& operator= (const LuaSignalConnection&) = delete;
~LuaSignalConnection(); virtual ~LuaSignalConnection();
}; };
// Holds a signal connection such that when the holder is deleted (either via its parent object being deleted, or being overwritten), // Holds a signal connection such that when the holder is deleted (either via its parent object being deleted, or being overwritten),
@ -63,7 +64,7 @@ class SignalConnectionHolder {
public: public:
SignalConnectionHolder(); SignalConnectionHolder();
SignalConnectionHolder(std::shared_ptr<SignalConnection>); SignalConnectionHolder(std::shared_ptr<SignalConnection>);
SignalConnectionHolder(Data::SignalConnectionRef other); SignalConnectionHolder(SignalConnectionRef other);
~SignalConnectionHolder(); ~SignalConnectionHolder();
// Prevent SignalConnectionHolder being accidentally copied, making it useless // Prevent SignalConnectionHolder being accidentally copied, making it useless
@ -90,12 +91,12 @@ public:
Signal& operator= (const Signal&) = delete; Signal& operator= (const Signal&) = delete;
void DisconnectAll(); void DisconnectAll();
void Fire(std::vector<Data::Variant> args); void Fire(std::vector<Variant> args);
void Fire(); void Fire();
Data::SignalConnectionRef Connect(std::function<void(std::vector<Data::Variant>)> callback); SignalConnectionRef Connect(std::function<void(std::vector<Variant>)> callback);
Data::SignalConnectionRef Connect(lua_State*); SignalConnectionRef Connect(lua_State*);
Data::SignalConnectionRef Once(std::function<void(std::vector<Data::Variant>)> callback); SignalConnectionRef Once(std::function<void(std::vector<Variant>)> callback);
Data::SignalConnectionRef Once(lua_State*); SignalConnectionRef Once(lua_State*);
int Wait(lua_State*); int Wait(lua_State*);
}; };
@ -105,43 +106,36 @@ public:
virtual ~SignalSource(); virtual ~SignalSource();
}; };
namespace Data { class SignalRef {
class SignalRef : public Data::Base { std::weak_ptr<Signal> signal;
std::weak_ptr<Signal> signal;
public: public:
SignalRef(std::weak_ptr<Signal>); SignalRef(std::weak_ptr<Signal>);
~SignalRef(); virtual ~SignalRef();
virtual const TypeInfo& GetType() const override; static const TypeDesc TYPE;
static const TypeInfo TYPE;
operator std::weak_ptr<Signal>(); operator std::weak_ptr<Signal>();
virtual const Data::String ToString() const override; virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const override; virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const override; virtual void PushLuaValue(lua_State*) const;
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
}; };
class SignalConnectionRef : public Data::Base { class SignalConnectionRef {
std::weak_ptr<SignalConnection> signalConnection; std::weak_ptr<SignalConnection> signalConnection;
public: public:
SignalConnectionRef(std::weak_ptr<SignalConnection>); SignalConnectionRef(std::weak_ptr<SignalConnection>);
~SignalConnectionRef(); virtual ~SignalConnectionRef();
virtual const TypeInfo& GetType() const override; static const TypeDesc TYPE;
static const TypeInfo TYPE;
operator std::weak_ptr<SignalConnection>(); operator std::weak_ptr<SignalConnection>();
virtual const Data::String ToString() const override; virtual const std::string ToString() const;
virtual void Serialize(pugi::xml_node node) const override; virtual void Serialize(pugi::xml_node node) const;
virtual void PushLuaValue(lua_State*) const override; virtual void PushLuaValue(lua_State*) const;
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
}; };
}
using Data::SignalRef;
using Data::SignalConnectionRef;

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,107 +7,112 @@
#include <string> #include <string>
#include <pugixml.hpp> #include <pugixml.hpp>
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include "error/data.h"
#include <sstream> #include <sstream>
namespace rp = reactphysics3d; namespace rp = reactphysics3d;
Data::Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {}; Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {}; Vector3::Vector3(const glm::vec3& src) : vector(src) {};
Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {}; 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(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); Vector3 Vector3::ZERO(0, 0, 0);
Data::Vector3 Data::Vector3::ONE(1, 1, 1); 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 // https://stackoverflow.com/a/46424921/16255372
std::stringstream stream; std::stringstream stream;
stream << std::setprecision(8) << std::noshowpoint << X() << ", " << Y() << ", " << Z(); stream << std::setprecision(8) << std::noshowpoint << X() << ", " << Y() << ", " << Z();
return stream.str(); return stream.str();
} }
Data::Vector3::operator glm::vec3() const { return vector; }; Vector3::operator glm::vec3() const { return vector; };
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); }; Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
// Operators // Operators
Data::Vector3 Data::Vector3::operator *(float scale) const { Vector3 Vector3::operator *(float scale) const {
return Data::Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale); return Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale);
} }
Data::Vector3 Data::Vector3::operator /(float scale) const { Vector3 Vector3::operator /(float scale) const {
return Data::Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale); return Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale);
} }
// Component-wise // Component-wise
Data::Vector3 Data::Vector3::operator *(Data::Vector3 other) const { Vector3 Vector3::operator *(Vector3 other) const {
return Data::Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z()); return Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z());
} }
Data::Vector3 Data::Vector3::operator +(Data::Vector3 other) const { Vector3 Vector3::operator /(Vector3 other) const {
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z()); return Vector3(this->X() / other.X(), this->Y() / other.Y(), this->Z() / other.Z());
} }
Data::Vector3 Data::Vector3::operator -(Data::Vector3 other) const { Vector3 Vector3::operator +(Vector3 other) const {
return Data::Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z()); return Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
} }
Data::Vector3 Data::Vector3::operator -() const { Vector3 Vector3::operator -(Vector3 other) const {
return Data::Vector3(-this->X(), -this->Y(), -this->Z()); return Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
} }
bool Data::Vector3::operator ==(Data::Vector3 other) const { Vector3 Vector3::operator -() const {
return Vector3(-this->X(), -this->Y(), -this->Z());
}
bool Vector3::operator ==(Vector3 other) const {
return this->X() == other.X() && this->Y() == other.Y() && this->Z() == other.Z(); 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(); 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(); 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); 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); return glm::dot(this->vector, other.vector);
} }
// Serialization // 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("X").text().set(std::to_string(this->X()));
node.append_child("Y").text().set(std::to_string(this->Y())); node.append_child("Y").text().set(std::to_string(this->Y()));
node.append_child("Z").text().set(std::to_string(this->Z())); node.append_child("Z").text().set(std::to_string(this->Z()));
} }
Data::Variant Data::Vector3::Deserialize(pugi::xml_node node) { result<Vector3, DataParseError> 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()); 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]; float components[3];
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (string.length() == 0) return std::nullopt; if (string.length() == 0) return DataParseError(string, "Vector3");
while (string[0] == ' ' && string.length() > 0) string.erase(0, 1); while (string[0] == ' ' && string.length() > 0) string.erase(0, 1);
size_t nextPos = string.find(","); size_t nextPos = string.find(",");
if (nextPos == -1) nextPos = string.length(); if (nextPos == std::string::npos) nextPos = string.length();
std::string term = string.substr(0, nextPos); std::string term = string.substr(0, nextPos);
string.erase(0, nextPos+1); string.erase(0, nextPos+1);
char* cpos; char* cpos;
float value = std::strtof(term.c_str(), &cpos); float value = std::strtof(term.c_str(), &cpos);
if (cpos == term.c_str()) return std::nullopt; if (cpos == term.c_str()) return DataParseError(string, "Vector3");
components[i] = value; components[i] = value;
} }
return Data::Vector3(components[0], components[1], components[2]); return Vector3(components[0], components[1], components[2]);
} }

View file

@ -2,64 +2,72 @@
#include "base.h" #include "base.h"
#include "datatypes/annotation.h" #include "datatypes/annotation.h"
#include "error/data.h"
#include <glm/ext/vector_float3.hpp> #include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp> #include <glm/geometric.hpp>
#include <reactphysics3d/mathematics/Vector3.h>
namespace reactphysics3d { class Vector3; }; // namespace reactphysics3d { class Vector3; };
namespace Data { class DEF_DATA Vector3 {
class DEF_DATA Vector3 : public Base { AUTOGEN_PREAMBLE_DATA
AUTOGEN_PREAMBLE_DATA glm::vec3 vector;
glm::vec3 vector;
public:
DEF_DATA_CTOR Vector3();
DEF_DATA_CTOR Vector3(float x, float y, float z);
inline Vector3(float value) : Vector3(value, value, value) {}
Vector3(const glm::vec3&);
Vector3(const reactphysics3d::Vector3&);
virtual ~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: static void PushLuaLibrary(lua_State*);
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 Data::Vector3 ZERO; operator glm::vec3() const;
DEF_DATA_PROP static Data::Vector3 ONE; operator reactphysics3d::Vector3() const;
virtual const Data::String ToString() const override; DEF_DATA_PROP inline float X() const { return vector.x; }
virtual void Serialize(pugi::xml_node node) const override; 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); DEF_DATA_METHOD Vector3 Cross(Vector3) const;
static std::optional<Data::Variant> FromString(std::string); DEF_DATA_METHOD float Dot(Vector3) const;
static void PushLuaLibrary(lua_State*);
operator glm::vec3() const; // Operators
operator reactphysics3d::Vector3() const; 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; // 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_OP bool operator <(Vector3) const;
DEF_DATA_PROP inline float Y() const { return vector.y; } DEF_DATA_OP bool operator >(Vector3) const;
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_METHOD Data::Vector3 Cross(Data::Vector3) const; DEF_DATA_OP bool operator ==(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 <(Data::Vector3) const; // Augmented shorthands
DEF_DATA_OP bool operator >(Data::Vector3) const; inline Vector3 operator *=(float factor) const { return *this * factor; }
inline Vector3 operator /=(float factor) const { return *this / factor; }
inline Vector3 operator *=(Vector3 factor) const { return *this * factor; }
inline Vector3 operator /=(Vector3 factor) const { return *this / factor; }
inline Vector3 operator +=(Vector3 vector) const { return *this + vector; }
inline Vector3 operator -=(Vector3 vector) const { return *this + vector; }
};
DEF_DATA_OP bool operator ==(Data::Vector3) const; inline void printVec(Vector3 vec) {
};
}
using Data::Vector3;
inline void printVec(Data::Vector3 vec) {
printf("(%f, %f, %f)\n", vec.X(), vec.Y(), vec.Z()); printf("(%f, %f, %f)\n", vec.X(), vec.Y(), vec.Z());
} }

View file

@ -0,0 +1,13 @@
#pragma once
// Markers for the autogen engine to generate getters, setters, lua, etc.
// Base macros
#ifdef __AUTOGEN__
#define def_enum(...) clang::annotate("OB::def_enum", #__VA_ARGS__)
#else
#define def_enum(...)
#endif
// Helper macros
#define DEF_ENUM [[ def_enum() ]]

3
core/src/enum/meta.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
#include "surface.h"

33
core/src/enum/surface.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "datatypes/enum.h"
#include "enum/annotation.h"
enum DEF_ENUM NormalId {
Right = 0,
Top = 1,
Back = 2,
Left = 3,
Bottom = 4,
Front = 5
};
enum class DEF_ENUM SurfaceType {
Smooth = 0,
Glue = 1,
Weld = 2,
Studs = 3,
Inlet = 4,
Universal = 5,
Hinge = 6,
Motor = 7,
};
namespace EnumType {
extern const Enum NormalId;
extern const Enum SurfaceType;
};
class Vector3;
NormalId faceFromNormal(Vector3);
Vector3 normalFromFace(NormalId);

View file

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

View file

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

View file

@ -40,7 +40,7 @@ CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe; CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
CFrame inverseFrame = localFrame.Inverse(); CFrame inverseFrame = localFrame.Inverse();
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + adornee->size * 0.5f); Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f) + adornee->size * 0.5f;
Vector3 handlePos = localFrame * (handleOffset * face.normal); Vector3 handlePos = localFrame * (handleOffset * face.normal);

View file

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

View file

@ -1,6 +1,7 @@
#include "instance.h" #include "instance.h"
#include "common.h" #include "common.h"
#include "datatypes/meta.h" #include "datatypes/primitives.h"
#include "datatypes/variant.h"
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/ref.h" #include "datatypes/ref.h"
#include "error/instance.h" #include "error/instance.h"
@ -11,6 +12,7 @@
#include "logger.h" #include "logger.h"
#include "panic.h" #include "panic.h"
#include <algorithm> #include <algorithm>
#include <cctype>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
@ -27,6 +29,7 @@ const InstanceType Instance::TYPE = {
.className = "Instance", .className = "Instance",
.constructor = NULL, // Instance is abstract and therefore not creatable .constructor = NULL, // Instance is abstract and therefore not creatable
.explorerIcon = "instance", .explorerIcon = "instance",
.flags = 0
}; };
// Instance is abstract, so it should not implement GetClass directly // Instance is abstract, so it should not implement GetClass directly
@ -96,7 +99,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
} }
OnAncestryChanged(updatedChild, newParent); 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 // Old workspace used to exist, and workspaces differ
if (!oldWorkspace.expired() && oldWorkspace != _workspace) { if (!oldWorkspace.expired() && oldWorkspace != _workspace) {
@ -109,7 +112,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
} }
// Update ancestry in descendants // Update ancestry in descendants
for (InstanceRef child : children) { for (std::shared_ptr<Instance> child : children) {
child->updateAncestry(updatedChild, newParent); child->updateAncestry(updatedChild, newParent);
} }
} }
@ -179,11 +182,13 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Properties // Properties
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) { result<Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
name[0] = toupper(name[0]); // Ignore case of first character
return InternalGetPropertyValue(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) {
name[0] = toupper(name[0]); // Ignore case of first character
auto result = InternalSetPropertyValue(name, value); auto result = InternalSetPropertyValue(name, value);
if (result.isSuccess() && sendUpdateEvent) { if (result.isSuccess() && sendUpdateEvent) {
InternalUpdateProperty(name); InternalUpdateProperty(name);
@ -193,37 +198,38 @@ fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std:
} }
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) { result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
name[0] = toupper(name[0]); // Ignore case of first character
return InternalGetPropertyMeta(name); return InternalGetPropertyMeta(name);
} }
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) { result<Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
if (name == "Name") { if (name == "Name") {
return Data::Variant(Data::String(this->name)); return Variant(this->name);
} else if (name == "Parent") { } else if (name == "Parent") {
return Data::Variant(Data::InstanceRef(this->parent)); return Variant(InstanceRef(this->parent));
} else if (name == "ClassName") { } else if (name == "ClassName") {
return Data::Variant(Data::String(GetClass()->className)); return Variant(GetClass()->className);
} }
return MemberNotFound(GetClass()->className, name); return MemberNotFound(GetClass()->className, name);
} }
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) { result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
if (name == "Name") { if (name == "Name") {
return PropertyMeta { &Data::String::TYPE }; return PropertyMeta { &STRING_TYPE, 0 };
} else if (name == "Parent") { } else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, }; return PropertyMeta { &InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") { } 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); 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") { if (name == "Name") {
this->name = (std::string)value.get<Data::String>(); this->name = (std::string)value.get<std::string>();
} else if (name == "Parent") { } 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())); SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
} else if (name == "ClassName") { } else if (name == "ClassName") {
return AssignToReadOnlyMember(GetClass()->className, name); return AssignToReadOnlyMember(GetClass()->className, name);
@ -259,7 +265,8 @@ std::vector<std::string> Instance::GetProperties() {
// Serialization // Serialization
void Instance::Serialize(pugi::xml_node parent) { void Instance::Serialize(pugi::xml_node parent, RefStateSerialize state) {
if (state == nullptr) state = std::make_shared<__RefStateSerialize>();
pugi::xml_node node = parent.append_child("Item"); pugi::xml_node node = parent.append_child("Item");
node.append_attribute("class").set_value(this->GetClass()->className); node.append_attribute("class").set_value(this->GetClass()->className);
@ -269,24 +276,65 @@ void Instance::Serialize(pugi::xml_node parent) {
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing"); 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... 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); #if 1
// Special consideration for Part.Size
// It should be serialized as "size" to be compatible with rbxl files
// This is optional, as they can still be opened otherwise, but I opted
// to keep this enabled
if (IsA("Part") && name == "Size")
name = "size";
#endif
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type.descriptor->name);
propertyNode.append_attribute("name").set_value(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 // Add children
for (InstanceRef child : this->children) { for (std::shared_ptr<Instance> child : this->children) {
child->Serialize(node); 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) {
if (state == nullptr) state = std::make_shared<__RefStateDeserialize>();
std::string className = node.attribute("class").value(); std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) { if (INSTANCE_MAP.count(className) == 0) {
return NoSuchInstance(className); return NoSuchInstance(className);
} }
// This will error if an abstract instance is used in the file. Oh well, not my prob rn. // 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(); object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className); // const InstanceType* type = INSTANCE_MAP.at(className);
@ -300,13 +348,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()); Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue; continue;
} }
Data::Variant value = Data::Variant::Deserialize(propertyNode); auto meta = meta_.expect();
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
// 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 // Read children
for (pugi::xml_node childNode : node.children("Item")) { 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()) { if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage(); std::get<NoSuchInstance>(child.error().value()).logMessage();
continue; continue;
@ -319,7 +405,7 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
// DescendantsIterator // DescendantsIterator
DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : current(current), root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), siblingIndex { 0 } { } DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), current(current), siblingIndex { 0 } { }
DescendantsIterator::self_type DescendantsIterator::operator++(int _) { DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
// If the current item is dummy, an error has occurred, this is not supposed to happen. // If the current item is dummy, an error has occurred, this is not supposed to happen.
@ -344,7 +430,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
} }
// If we've hit the end of this item's children, move one up // If we've hit the end of this item's children, move one up
while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= (siblingIndex.back() + 1)) { while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= size_t(siblingIndex.back() + 1)) {
siblingIndex.pop_back(); siblingIndex.pop_back();
current = current->GetParent().value(); current = current->GetParent().value();
@ -362,7 +448,8 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
return *this; return *this;
} }
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) { std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
if (state == nullptr) state = std::make_shared<__RefStateClone>();
std::shared_ptr<Instance> newInstance = GetClass()->constructor(); std::shared_ptr<Instance> newInstance = GetClass()->constructor();
// Copy properties // Copy properties
@ -371,9 +458,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue; if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update InstanceRef properties using map above // Update std::shared_ptr<Instance> properties using map above
if (meta.type == &Data::InstanceRef::TYPE) { if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>(); std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
if (refWeak.expired()) continue; if (refWeak.expired()) continue;
auto ref = refWeak.lock(); auto ref = refWeak.lock();
@ -381,17 +468,17 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (remappedRef) { if (remappedRef) {
// If the instance has already been remapped, set the new value // 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 { } else {
// Otheriise, queue this property to be updated later, and keep its current value // Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref]; auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newInstance, property)); refs.push_back(std::make_pair(newInstance, property));
state->refsAwaitingRemap[ref] = refs; state->refsAwaitingRemap[ref] = refs;
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); newInstance->SetPropertyValue(property, InstanceRef(ref)).expect();
} }
} else { } else {
Data::Variant value = GetPropertyValue(property).expect(); Variant value = GetPropertyValue(property).expect();
newInstance->SetPropertyValue(property, value).expect(); newInstance->SetPropertyValue(property, value).expect();
} }
} }
@ -401,8 +488,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
// Remap queued properties // Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) { 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 // Clone children
for (std::shared_ptr<Instance> child : GetChildren()) { for (std::shared_ptr<Instance> child : GetChildren()) {
@ -421,9 +509,9 @@ std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetRefe
for (std::string property : propertyNames) { for (std::string property : propertyNames) {
PropertyMeta meta = GetPropertyMeta(property).expect(); 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; if (ref.expired()) continue;
referenceProperties.push_back(std::make_pair(property, ref.lock())); referenceProperties.push_back(std::make_pair(property, ref.lock()));
} }

View file

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

View file

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

View file

@ -2,16 +2,26 @@
// Helper struct used for remapping reference when cloning/serializing // Helper struct used for remapping reference when cloning/serializing
#include "datatypes/base.h"
#include <map> #include <map>
#include <memory> #include <memory>
#include <vector> #include <vector>
class Instance; class Instance;
template <typename T> template <typename T, typename U, typename K>
struct __RefState { struct __RefState {
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances; std::map<K, U> remappedInstances;
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap; std::map<K, std::vector<T>> refsAwaitingRemap;
int count = 0;
}; };
template <typename T> template <typename T, typename U, typename K>
using RefState = std::shared_ptr<__RefState<T>>; 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/base/service.h"
#include "objects/meta.h" #include "objects/meta.h"
#include "objects/script/serverscriptservice.h" #include "objects/script/serverscriptservice.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include "workspace.h" #include "workspace.h"
#include "logger.h" #include "logger.h"
#include "panic.h" #include "panic.h"
@ -49,7 +49,7 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
pugi::xml_document doc; pugi::xml_document doc;
pugi::xml_node root = doc.append_child("openblocks"); pugi::xml_node root = doc.append_child("openblocks");
for (InstanceRef child : this->GetChildren()) { for (std::shared_ptr<Instance> child : this->GetChildren()) {
child->Serialize(root); child->Serialize(root);
} }
@ -59,50 +59,6 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
Logger::info("Place saved successfully"); 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::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::ifstream inStream(path); std::ifstream inStream(path);
pugi::xml_document doc; pugi::xml_document doc;
@ -110,11 +66,31 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
pugi::xml_node rootNode = doc.child("openblocks"); pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>(); std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
for (pugi::xml_node childNode : rootNode.children("Item")) { 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->currentFile = path;
newModel->Init(); newModel->Init();
return newModel; return newModel;
@ -146,7 +122,7 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
} }
std::shared_ptr<DataModel> DataModel::CloneModel() { 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(); std::shared_ptr<DataModel> newModel = DataModel::New();
// Copy properties // Copy properties
@ -155,9 +131,9 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue; if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update InstanceRef properties using map above // Update std::shared_ptr<Instance> properties using map above
if (meta.type == &Data::InstanceRef::TYPE) { if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>(); std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
if (refWeak.expired()) continue; if (refWeak.expired()) continue;
auto ref = refWeak.lock(); auto ref = refWeak.lock();
@ -165,17 +141,17 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
if (remappedRef) { if (remappedRef) {
// If the instance has already been remapped, set the new value // 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 { } else {
// Otheriise, queue this property to be updated later, and keep its current value // Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref]; auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newModel, property)); refs.push_back(std::make_pair(newModel, property));
state->refsAwaitingRemap[ref] = refs; state->refsAwaitingRemap[ref] = refs;
newModel->SetPropertyValue(property, Data::InstanceRef(ref)).expect(); newModel->SetPropertyValue(property, InstanceRef(ref)).expect();
} }
} else { } else {
Data::Variant value = GetPropertyValue(property).expect(); Variant value = GetPropertyValue(property).expect();
newModel->SetPropertyValue(property, value).expect(); newModel->SetPropertyValue(property, value).expect();
} }
} }
@ -185,7 +161,7 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
// Remap queued properties // Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) { 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 // Clone services

View file

@ -16,8 +16,8 @@ class Service;
class DEF_INST_(abstract) DataModel : public Instance { class DEF_INST_(abstract) DataModel : public Instance {
AUTOGEN_PREAMBLE AUTOGEN_PREAMBLE
private: private:
void DeserializeService(pugi::xml_node node); // void DeserializeService(pugi::xml_node node, RefStateDeserialize);
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>); static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefStateClone);
public: public:
std::map<std::string, std::shared_ptr<Service>> services; 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 "meta.h"
#include "objects/folder.h"
#include "objects/joint/jointinstance.h" #include "objects/joint/jointinstance.h"
#include "objects/joint/rotate.h" #include "objects/joint/rotate.h"
#include "objects/joint/rotatev.h" #include "objects/joint/rotatev.h"
#include "objects/joint/weld.h" #include "objects/joint/weld.h"
#include "objects/jointsservice.h" #include "objects/jointsservice.h"
#include "objects/model.h"
#include "objects/part.h" #include "objects/part.h"
#include "objects/joint/snap.h" #include "objects/joint/snap.h"
#include "objects/script.h" #include "objects/script.h"
@ -23,6 +25,8 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "RotateV", &RotateV::TYPE }, { "RotateV", &RotateV::TYPE },
{ "JointInstance", &JointInstance::TYPE }, { "JointInstance", &JointInstance::TYPE },
{ "Script", &Script::TYPE }, { "Script", &Script::TYPE },
{ "Model", &Model::TYPE },
// { "Folder", &Folder::TYPE },
// Services // 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

@ -13,7 +13,7 @@
#include "objects/joint/jointinstance.h" #include "objects/joint/jointinstance.h"
#include "objects/joint/snap.h" #include "objects/joint/snap.h"
#include "rendering/renderer.h" #include "rendering/renderer.h"
#include "rendering/surface.h" #include "enum/surface.h"
#include <cstdio> #include <cstdio>
#include <glm/common.hpp> #include <glm/common.hpp>
#include <memory> #include <memory>
@ -83,7 +83,7 @@ Vector3 Part::GetAABB() {
Vector3 min(0, 0, 0); Vector3 min(0, 0, 0);
Vector3 max(0, 0, 0); Vector3 max(0, 0, 0);
for (Vector3 vert : verts) { for (Vector3 vert : verts) {
Vector3 worldVert = this->cframe.Rotation() * ((Vector3)this->size * vert); Vector3 worldVert = this->cframe.Rotation() * (this->size * vert);
expandMaxExtents(&min, &max, worldVert); expandMaxExtents(&min, &max, worldVert);
} }
@ -120,7 +120,7 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
case Front: return frontSurface; case Front: return frontSurface;
case Back: return backSurface; case Back: return backSurface;
} }
return SurfaceSmooth; // Unreachable return SurfaceType::Smooth; // Unreachable
} }
float Part::GetSurfaceParamA(Vector3 face) { float Part::GetSurfaceParamA(Vector3 face) {
@ -199,14 +199,14 @@ bool Part::checkSurfacesTouching(CFrame surfaceFrame, Vector3 size, Vector3 myFa
} }
std::optional<std::shared_ptr<JointInstance>> makeJointFromSurfaces(SurfaceType a, SurfaceType b) { std::optional<std::shared_ptr<JointInstance>> makeJointFromSurfaces(SurfaceType a, SurfaceType b) {
if (a == SurfaceWeld || b == SurfaceWeld || a == SurfaceGlue || b == SurfaceGlue) return Weld::New(); if (a == SurfaceType::Weld || b == SurfaceType::Weld || a == SurfaceType::Glue || b == SurfaceType::Glue) return Weld::New();
if ((a == SurfaceStuds && (b == SurfaceInlets || b == SurfaceUniversal)) if ((a == SurfaceType::Studs && (b == SurfaceType::Inlet || b == SurfaceType::Universal))
|| (a == SurfaceInlets && (b == SurfaceStuds || b == SurfaceUniversal)) || (a == SurfaceType::Inlet && (b == SurfaceType::Studs || b == SurfaceType::Universal))
|| (a == SurfaceUniversal && (b == SurfaceStuds || b == SurfaceInlets || b == SurfaceUniversal))) || (a == SurfaceType::Universal && (b == SurfaceType::Studs || b == SurfaceType::Inlet || b == SurfaceType::Universal)))
return Snap::New(); return Snap::New();
if (a == SurfaceHinge) if (a == SurfaceType::Hinge)
return Rotate::New(); return Rotate::New();
if (a == SurfaceMotor) if (a == SurfaceType::Motor)
return RotateV::New(); return RotateV::New();
return std::nullopt; return std::nullopt;
} }
@ -224,7 +224,7 @@ void Part::MakeJoints() {
// TEMPORARY // TEMPORARY
// TODO: Use more efficient algorithm to *actually* find nearby parts) // TODO: Use more efficient algorithm to *actually* find nearby parts)
for (auto it = workspace().value()->GetDescendantsStart(); it != workspace().value()->GetDescendantsEnd(); it++) { 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 == 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 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(); std::shared_ptr<Part> otherPart = obj->CastTo<Part>().expect();
@ -251,7 +251,7 @@ void Part::MakeJoints() {
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace)); SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
// If it is a hinge, only attach if actually touching the "hinge" // If it is a hinge, only attach if actually touching the "hinge"
if ((mySurface == SurfaceHinge || mySurface == SurfaceMotor) && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue; if ((mySurface == SurfaceType::Hinge || mySurface == SurfaceType::Motor) && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue;
// Create contacts // Create contacts
// Contact always occurs at the center of Part0's surface (even if that point does not overlap both surfaces) // Contact always occurs at the center of Part0's surface (even if that point does not overlap both surfaces)

View file

@ -8,7 +8,7 @@
#include "datatypes/signal.h" #include "datatypes/signal.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "rendering/surface.h" #include "enum/surface.h"
#include <optional> #include <optional>
#include <reactphysics3d/reactphysics3d.h> #include <reactphysics3d/reactphysics3d.h>
#include <vector> #include <vector>
@ -18,9 +18,9 @@ namespace rp = reactphysics3d;
// For easy construction from C++. Maybe should be removed? // For easy construction from C++. Maybe should be removed?
struct PartConstructParams { struct PartConstructParams {
glm::vec3 position; Vector3 position;
glm::vec3 rotation; Vector3 rotation;
glm::vec3 size; Vector3 size;
Color3 color; Color3 color;
bool anchored = false; bool anchored = false;
@ -58,7 +58,9 @@ public:
CFrame cframe; CFrame cframe;
DEF_PROP_CATEGORY(PART) DEF_PROP_CATEGORY(PART)
DEF_PROP_(on_update=onUpdated) glm::vec3 size; // Special compatibility changes for this property were made in
// Instance::Serialize
DEF_PROP_(on_update=onUpdated) Vector3 size;
DEF_PROP_CATEGORY(APPEARANCE) DEF_PROP_CATEGORY(APPEARANCE)
DEF_PROP Color3 color; DEF_PROP Color3 color;
@ -70,12 +72,12 @@ public:
DEF_PROP bool locked = false; DEF_PROP bool locked = false;
DEF_PROP_CATEGORY(SURFACE) DEF_PROP_CATEGORY(SURFACE)
DEF_PROP SurfaceType topSurface = SurfaceType::SurfaceStuds; DEF_PROP SurfaceType topSurface = SurfaceType::Studs;
DEF_PROP SurfaceType bottomSurface = SurfaceType::SurfaceInlets; DEF_PROP SurfaceType bottomSurface = SurfaceType::Inlet;
DEF_PROP SurfaceType leftSurface = SurfaceType::SurfaceSmooth; DEF_PROP SurfaceType leftSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType rightSurface = SurfaceType::SurfaceSmooth; DEF_PROP SurfaceType rightSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType frontSurface = SurfaceType::SurfaceSmooth; DEF_PROP SurfaceType frontSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType backSurface = SurfaceType::SurfaceSmooth; DEF_PROP SurfaceType backSurface = SurfaceType::Smooth;
DEF_PROP_CATEGORY(SURFACE_INPUT) DEF_PROP_CATEGORY(SURFACE_INPUT)
DEF_PROP float topParamA = -0.5; DEF_PROP float topParamA = -0.5;
@ -107,7 +109,7 @@ public:
static inline std::shared_ptr<Part> New() { return std::make_shared<Part>(); }; 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 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(); } inline Vector3 position() { return cframe.Position(); }

View file

@ -15,15 +15,7 @@ int script_wait(lua_State*);
int script_delay(lua_State*); int script_delay(lua_State*);
Script::Script(): Instance(&TYPE) { Script::Script(): Instance(&TYPE) {
source = "workspace.Part.Touched:Connect(function(otherPart)\n" source = "print(\"Hello, world!\")";
" print(\"Touched by: \", otherPart.Name)\n"
"end)\n"
"\n"
"workspace.Part.TouchEnded:Connect(function(otherPart)\n"
" print(\"Touched ended with: \", otherPart.Name)\n"
"end)\n"
"\n"
"error(\"Test\")";
} }
Script::~Script() { Script::~Script() {
@ -40,11 +32,14 @@ void Script::Run() {
// Initialize script globals // Initialize script globals
lua_getglobal(Lt, "_G"); lua_getglobal(Lt, "_G");
InstanceRef(shared_from_this()).PushLuaValue(Lt);
lua_setfield(Lt, -2, "script");
Data::InstanceRef(dataModel().value()).PushLuaValue(Lt); InstanceRef(dataModel().value()).PushLuaValue(Lt);
lua_setfield(Lt, -2, "game"); 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_setfield(Lt, -2, "workspace");
lua_pushlightuserdata(Lt, scriptContext.get()); lua_pushlightuserdata(Lt, scriptContext.get());
@ -117,6 +112,8 @@ int script_delay(lua_State* L) {
luaL_checktype(L, 2, LUA_TFUNCTION); luaL_checktype(L, 2, LUA_TFUNCTION);
lua_State* Lt = lua_newthread(L); // Create a new thread lua_State* Lt = lua_newthread(L); // Create a new thread
// I think this is memory abuse??
// Wouldn't popping the thread in this case make it eligible for garbage collection?
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
lua_xmove(L, Lt, 1); // move func lua_xmove(L, Lt, 1); // move func
lua_pop(L, 1); // pop secs lua_pop(L, 1); // pop secs

View file

@ -43,9 +43,9 @@ void ScriptContext::InitService() {
// luaopen_debug(state); // luaopen_debug(state);
luaopen_bit(state); luaopen_bit(state);
Data::Vector3::PushLuaLibrary(state); Vector3::PushLuaLibrary(state);
Data::CFrame::PushLuaLibrary(state); CFrame::PushLuaLibrary(state);
Data::Color3::PushLuaLibrary(state); Color3::PushLuaLibrary(state);
// TODO: custom os library // TODO: custom os library
@ -94,7 +94,7 @@ void ScriptContext::PushThreadSleep(lua_State* thread, float delay) {
} }
void ScriptContext::RunSleepingThreads() { void ScriptContext::RunSleepingThreads() {
for (int i = 0; i < sleepingThreads.size();) { for (size_t i = 0; i < sleepingThreads.size();) {
bool deleted = false; bool deleted = false;
SleepingThread sleep = sleepingThreads[i]; SleepingThread sleep = sleepingThreads[i];
@ -162,6 +162,7 @@ static int g_print(lua_State* L) {
const char* str = lua_tostring(L, -1); // convert result into c-string const char* str = lua_tostring(L, -1); // convert result into c-string
lua_pop(L, 1); // pop result lua_pop(L, 1); // pop result
if (i > 1) buf += '\t';
buf += str; buf += str;
} }

View file

@ -5,7 +5,7 @@
// Container class for server scripts // Container class for server scripts
// Also handles/manages running server scripts on run // 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 AUTOGEN_PREAMBLE
protected: protected:
void InitService() override; void InitService() override;

View file

@ -1,5 +1,5 @@
#include "workspace.h" #include "workspace.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include "datatypes/ref.h" #include "datatypes/ref.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
@ -24,7 +24,7 @@ Workspace::~Workspace() {
PhysicsEventListener::PhysicsEventListener(Workspace* parent) : workspace(parent) {} PhysicsEventListener::PhysicsEventListener(Workspace* parent) : workspace(parent) {}
void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData& data) { void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData& data) {
for (int i = 0; i < data.getNbContactPairs(); i++) { for (size_t i = 0; i < data.getNbContactPairs(); i++) {
auto pair = data.getContactPair(i); auto pair = data.getContactPair(i);
auto type = pair.getEventType(); auto type = pair.getEventType();
if (type == rp::CollisionCallback::ContactPair::EventType::ContactStay) continue; if (type == rp::CollisionCallback::ContactPair::EventType::ContactStay) continue;
@ -33,11 +33,11 @@ void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData&
auto part1 = reinterpret_cast<Part*>(pair.getBody2()->getUserData())->shared<Part>(); auto part1 = reinterpret_cast<Part*>(pair.getBody2()->getUserData())->shared<Part>();
if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart) { if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart) {
part0->Touched->Fire({ (Data::Variant)Data::InstanceRef(part1) }); part0->Touched->Fire({ (Variant)InstanceRef(part1) });
part1->Touched->Fire({ (Data::Variant)Data::InstanceRef(part0) }); part1->Touched->Fire({ (Variant)InstanceRef(part0) });
} else if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactExit) { } else if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactExit) {
part0->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part1) }); part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
part1->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part0) }); part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
} }
} }
} }
@ -59,7 +59,7 @@ void Workspace::InitService() {
// Sync all parts // Sync all parts
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) { for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it; std::shared_ptr<Instance> obj = *it;
if (!obj->IsA<Part>()) continue; if (!obj->IsA<Part>()) continue;
std::shared_ptr<Part> part = obj->CastTo<Part>().expect(); std::shared_ptr<Part> part = obj->CastTo<Part>().expect();
this->SyncPartPhysics(part); this->SyncPartPhysics(part);
@ -68,7 +68,7 @@ void Workspace::InitService() {
// Activate all joints // Activate all joints
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) { for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it; std::shared_ptr<Instance> obj = *it;
if (!obj->IsA<JointInstance>()) continue; if (!obj->IsA<JointInstance>()) continue;
std::shared_ptr<JointInstance> joint = obj->CastTo<JointInstance>().expect(); std::shared_ptr<JointInstance> joint = obj->CastTo<JointInstance>().expect();
joint->UpdateProperty("Part0"); joint->UpdateProperty("Part0");
@ -84,8 +84,6 @@ void Workspace::InitService() {
void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) { void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
if (!physicsWorld) return; if (!physicsWorld) return;
glm::mat4 rotMat = glm::mat4(1.0f);
rp::Transform transform = part->cframe; rp::Transform transform = part->cframe;
if (!part->rigidBody) { if (!part->rigidBody) {
part->rigidBody = physicsWorld->createRigidBody(transform); part->rigidBody = physicsWorld->createRigidBody(transform);
@ -127,17 +125,19 @@ void Workspace::PhysicsStep(float deltaTime) {
// Step the simulation a few steps // Step the simulation a few steps
physicsWorld->update(std::min(deltaTime / 2, (1/60.f))); 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 // 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++) { for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it; std::shared_ptr<Instance> obj = *it;
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly if (!obj->IsA<Part>()) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
// Sync properties
const rp::Transform& transform = part->rigidBody->getTransform(); const rp::Transform& transform = part->rigidBody->getTransform();
part->cframe = CFrame(transform); part->cframe = CFrame(transform);
part->velocity = part->rigidBody->getLinearVelocity(); part->velocity = part->rigidBody->getLinearVelocity();
// part->rigidBody->enableGravity(true); // part->rigidBody->enableGravity(true);
// RotateV/Motor joint
for (auto& joint : part->secondaryJoints) { for (auto& joint : part->secondaryJoints) {
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue; if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
@ -146,6 +146,16 @@ void Workspace::PhysicsStep(float deltaTime) {
// part->rigidBody->enableGravity(false); // part->rigidBody->enableGravity(false);
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate); 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();
~Workspace(); ~Workspace();
DEF_PROP float fallenPartsDestroyHeight = -500;
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); }; // static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); }; static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };

100
core/src/partassembly.cpp Normal file
View file

@ -0,0 +1,100 @@
#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"
#include "objects/part.h"
#include <glm/common.hpp>
#include <memory>
#include <vector>
PartAssembly::PartAssembly(std::vector<std::shared_ptr<Part>> parts, bool worldMode) : parts(parts) {
if (parts.size() == 0) return;
if (parts.size() == 1 && !worldMode) {
_assemblyOrigin = parts[0]->cframe;
_bounds = parts[0]->size;
return;
}
glm::vec3 min = parts[0]->position(), max = parts[0]->position();
for (auto part : parts) {
Vector3 aabbSize = part->GetAABB();
expandAABB(min, max, part->position() - aabbSize / 2.f);
expandAABB(min, max, part->position() + aabbSize / 2.f);
}
glm::vec3 pos, size;
getAABBCoords(pos, size, min, max);
_assemblyOrigin = CFrame() + pos;
_bounds = size;
}
PartAssembly PartAssembly::FromSelection(std::vector<std::shared_ptr<Instance>> newSelection) {
std::vector<std::shared_ptr<Part>> selection;
for (std::weak_ptr<Instance> obj : newSelection) {
if (obj.expired() || !obj.lock()->IsA<Part>()) continue;
selection.push_back(obj.lock()->CastTo<Part>().expect());
}
return PartAssembly(selection, editorToolHandles.worldMode);
}
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;
}
void PartAssembly::TransformBy(CFrame transform) {
for (auto part : parts) {
part->cframe = transform * part->cframe;
part->UpdateProperty("CFrame");
part->UpdateProperty("Position");
part->UpdateProperty("Rotation");
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
sendPropertyUpdatedSignal(part, "Position", Variant(part->cframe));
sendPropertyUpdatedSignal(part, "Rotation", Variant(part->cframe));
}
_assemblyOrigin = transform * _assemblyOrigin;
}
void PartAssembly::Scale(Vector3 newSize, bool scaleUp) {
if (parts.size() == 1) {
parts[0]->size = newSize;
parts[0]->UpdateProperty("Size");
sendPropertyUpdatedSignal(parts[0], "Size", Variant(parts[0]->size));
_bounds = newSize;
return;
}
float sx = newSize.X() / _bounds.X(), sy = newSize.Y() / _bounds.Y(), sz = newSize.Z() / _bounds.Z();
float factor = scaleUp ? glm::max(sx, sy, sz) : glm::min(sx, sy, sz);
for (auto part : parts) {
Vector3 localOff = _assemblyOrigin.Inverse() * part->cframe.Position();
localOff = localOff * factor;
part->cframe = part->cframe.Rotation() + _assemblyOrigin * localOff;
part->UpdateProperty("CFrame");
part->UpdateProperty("Position");
part->UpdateProperty("Rotation");
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
sendPropertyUpdatedSignal(part, "Position", Variant(part->cframe));
sendPropertyUpdatedSignal(part, "Rotation", Variant(part->cframe));
part->size *= factor;
part->UpdateProperty("Size");
sendPropertyUpdatedSignal(part, "Size", Variant(part->size));
}
_bounds = _bounds * factor;
}

35
core/src/partassembly.h Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "datatypes/cframe.h"
#include <memory>
#include <vector>
class Part;
class Instance;
const std::vector<std::shared_ptr<Instance>> getSelection();
class PartAssembly {
CFrame _assemblyOrigin;
Vector3 _bounds;
std::vector<std::shared_ptr<Part>> parts;
public:
PartAssembly(std::vector<std::shared_ptr<Part>>, bool worldMode = false);
static PartAssembly FromSelection(std::vector<std::shared_ptr<Instance>> selection = getSelection());
inline CFrame assemblyOrigin() { return _assemblyOrigin; };
inline Vector3 bounds() { return _bounds; };
// Transforms the assembly such that newOrigin is now this assembly's new assemblyOrigin
void SetOrigin(CFrame newOrigin);
// Rotates and translates the assembly by the transformation
void TransformBy(CFrame transform);
// Scales the assembly to the desired size
// If multiple parts are selected, finds the greatest scale factor of each component pair, and
// scales it up by that amount
void Scale(Vector3 newSize, bool scaleUp = true);
};

View file

@ -4,7 +4,7 @@
#include "panic.h" #include "panic.h"
// GNU/Linux implementation // GNU/Linux implementation
#if defined(_POSIX_VERSION) || defined(__linux) || defined(__linux__) #if defined(_POSIX_VERSION) || defined(__linux) || defined(__linux__) || defined(__unix__)
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>

View file

@ -4,11 +4,11 @@
template <typename T> template <typename T>
bool operator ==(std::optional<std::weak_ptr<T>> a, std::optional<std::weak_ptr<T>> b) { bool operator ==(std::optional<std::weak_ptr<T>> a, std::optional<std::weak_ptr<T>> b) {
return (!a.has_value() || a.value().expired()) && (!b.has_value() || b.value().expired()) return ((!a.has_value() || a.value().expired()) && (!b.has_value() || b.value().expired()))
|| (a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock(); || ((a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock());
} }
template <typename T> template <typename T>
bool operator ==(std::weak_ptr<T> a, std::weak_ptr<T> b) { bool operator ==(std::weak_ptr<T> a, std::weak_ptr<T> b) {
return a.expired() && b.expired() || (!a.expired() && !b.expired() && a.lock() == b.lock()); return (a.expired() && b.expired()) || (!a.expired() && !b.expired() && a.lock() == b.lock());
} }

View file

@ -1,5 +1,7 @@
#include "defaultmeshes.h" #include "defaultmeshes.h"
#pragma warning( disable : 4305 )
static float CUBE_VERTICES[] = { static float CUBE_VERTICES[] = {
// positions // normals // texture coords // positions // normals // texture coords
0.5, -0.5, -0.5, -0.0, -0.0, -1.0, 1.0, 0.0, 0.5, -0.5, -0.5, -0.0, -0.0, -1.0, 1.0, 0.0,

View file

@ -16,8 +16,10 @@
#include "datatypes/cframe.h" #include "datatypes/cframe.h"
#include "datatypes/color3.h" #include "datatypes/color3.h"
#include "datatypes/vector.h"
#include "handles.h" #include "handles.h"
#include "math_helper.h" #include "math_helper.h"
#include "partassembly.h"
#include "rendering/torus.h" #include "rendering/torus.h"
#include "shader.h" #include "shader.h"
#include "mesh.h" #include "mesh.h"
@ -26,7 +28,7 @@
#include "../common.h" #include "../common.h"
#include "../objects/part.h" #include "../objects/part.h"
#include "skybox.h" #include "skybox.h"
#include "surface.h" #include "enum/surface.h"
#include "texture3d.h" #include "texture3d.h"
#include "renderer.h" #include "renderer.h"
@ -50,8 +52,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
viewportWidth = width, viewportHeight = height; viewportWidth = width, viewportHeight = height;
glViewport(0, 0, width, height); glViewport(0, 0, width, height);
int argc = 1;
char* argv = const_cast<char*>("");
initMeshes(); initMeshes();
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
@ -131,7 +131,7 @@ void renderParts() {
// Sort by nearest // Sort by nearest
std::map<float, std::shared_ptr<Part>> sorted; std::map<float, std::shared_ptr<Part>> sorted;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) { for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it; std::shared_ptr<Instance> inst = *it;
if (inst->GetClass()->className != "Part") continue; if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (part->transparency > 0.00001) { if (part->transparency > 0.00001) {
@ -140,7 +140,7 @@ void renderParts() {
} else { } else {
glm::mat4 model = part->cframe; glm::mat4 model = part->cframe;
// if (part->name == "camera") model = camera.getLookAt(); // if (part->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size); model = glm::scale(model, (glm::vec3)part->size);
shader->set("model", model); shader->set("model", model);
shader->set("material", Material { shader->set("material", Material {
.diffuse = part->color, .diffuse = part->color,
@ -152,12 +152,12 @@ void renderParts() {
shader->set("texScale", part->size); shader->set("texScale", part->size);
shader->set("transparency", part->transparency); shader->set("transparency", part->transparency);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface); shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface); shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface); shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface); shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface); shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface); shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
CUBE_MESH->bind(); CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount); glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
@ -170,7 +170,7 @@ void renderParts() {
std::shared_ptr<Part> part = it->second; std::shared_ptr<Part> part = it->second;
glm::mat4 model = part->cframe; glm::mat4 model = part->cframe;
// if (part->name == "camera") model = camera.getLookAt(); // if (part->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size); model = glm::scale(model, (glm::vec3)part->size);
shader->set("model", model); shader->set("model", model);
shader->set("material", Material { shader->set("material", Material {
.diffuse = part->color, .diffuse = part->color,
@ -182,12 +182,12 @@ void renderParts() {
shader->set("texScale", part->size); shader->set("texScale", part->size);
shader->set("transparency", part->transparency); shader->set("transparency", part->transparency);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface); shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface); shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface); shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface); shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface); shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface); shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
CUBE_MESH->bind(); CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount); glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
@ -225,20 +225,19 @@ void renderSurfaceExtras() {
ghostShader->set("viewPos", camera.cameraPos); ghostShader->set("viewPos", camera.cameraPos);
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) { for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it; std::shared_ptr<Instance> inst = *it;
if (!inst->IsA("Part")) continue; if (!inst->IsA("Part")) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
NormalId face = (NormalId)i; NormalId face = (NormalId)i;
SurfaceType type = part->GetSurfaceFromFace(face); SurfaceType type = part->GetSurfaceFromFace(face);
if (type <= SurfaceType::SurfaceUniversal) continue; if (type <= SurfaceType::Universal) continue;
Vector3 surfaceCenter = part->cframe * (normalFromFace(face) * part->size / 2.f); Vector3 surfaceCenter = part->cframe * (normalFromFace(face) * part->size / 2.f);
glm::mat4 model = CFrame::pointToward(surfaceCenter, part->cframe.Rotation() * normalFromFace(face)); glm::mat4 model = CFrame::pointToward(surfaceCenter, part->cframe.Rotation() * normalFromFace(face));
model = glm::scale(model, glm::vec3(0.4,0.4,0.4)); model = glm::scale(model, glm::vec3(0.4,0.4,0.4));
ghostShader->set("model", model); ghostShader->set("model", model);
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
CYLINDER_MESH->bind(); CYLINDER_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CYLINDER_MESH->vertexCount); glDrawArrays(GL_TRIANGLES, 0, CYLINDER_MESH->vertexCount);
@ -271,6 +270,9 @@ static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0,
void renderHandles() { void renderHandles() {
if (!editorToolHandles.active) return; if (!editorToolHandles.active) return;
auto assembly = PartAssembly::FromSelection();
if (assembly.bounds() == Vector3::ZERO) return;
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glCullFace(GL_BACK); glCullFace(GL_BACK);
glFrontFace(GL_CCW); // This is right... Probably..... glFrontFace(GL_CCW); // This is right... Probably.....
@ -365,7 +367,7 @@ void renderAABB() {
ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f)); ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest // Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) { for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue; if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = CFrame::IDENTITY + part->cframe.Position(); glm::mat4 model = CFrame::IDENTITY + part->cframe.Position();
@ -405,7 +407,7 @@ void renderWireframe() {
wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f)); wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest // Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) { for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue; if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = part->cframe; glm::mat4 model = part->cframe;
@ -442,17 +444,19 @@ void renderOutlines() {
outlineShader->set("viewPos", camera.cameraPos); outlineShader->set("viewPos", camera.cameraPos);
outlineShader->set("thickness", 0.4f); outlineShader->set("thickness", 0.4f);
// outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f)); outlineShader->set("color", glm::vec3(0.204, 0.584, 0.922));
glm::vec3 min, max; glm::vec3 min, max;
bool first = true; bool first = true;
int count = 0;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) { for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it; std::shared_ptr<Instance> inst = *it;
if (inst->GetClass() != &Part::TYPE) continue; if (inst->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (!part->selected) continue; if (!part->selected) continue;
count++;
if (first) if (first)
min = part->position(), max = part->position(); min = part->position(), max = part->position();
first = false; first = false;
@ -471,7 +475,7 @@ void renderOutlines() {
} }
// Render AABB of selected parts // Render AABB of selected parts
if (first) return; if (count <= 1) return;
glm::vec3 outlineSize, outlinePos; glm::vec3 outlineSize, outlinePos;
outlineSize = (max - min); outlineSize = (max - min);
@ -487,6 +491,41 @@ void renderOutlines() {
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount); glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
} }
void renderSelectionAssembly() {
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
PartAssembly selectionAssembly = PartAssembly::FromSelection();
// Use shader
outlineShader->use();
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
glm::mat4 view = camera.getLookAt();
outlineShader->set("projection", projection);
outlineShader->set("view", view);
// Pass in the camera position
outlineShader->set("viewPos", camera.cameraPos);
outlineShader->set("thickness", 0.4f);
outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
glm::mat4 model = selectionAssembly.assemblyOrigin();
model = glm::scale(model, (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.1));
outlineShader->set("model", model);
outlineShader->set("scale", (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.05));
outlineShader->set("thickness", 0.2f);
OUTLINE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
}
void renderRotationArcs() { void renderRotationArcs() {
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return; if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
@ -517,14 +556,14 @@ void renderRotationArcs() {
// Pass in the camera position // Pass in the camera position
handleShader->set("viewPos", camera.cameraPos); 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) { for (HandleFace face : HandleFace::Faces) {
if (glm::any(glm::lessThan(face.normal, glm::vec3(0)))) continue; 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); 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 { handleShader->set("material", Material {
.diffuse = glm::abs(face.normal), .diffuse = glm::abs(face.normal),
@ -599,6 +638,7 @@ void render(GLFWwindow* window) {
renderParts(); renderParts();
renderSurfaceExtras(); renderSurfaceExtras();
renderOutlines(); renderOutlines();
// renderSelectionAssembly();
renderRotationArcs(); renderRotationArcs();
if (wireframeRendering) if (wireframeRendering)
renderWireframe(); renderWireframe();

View file

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

View file

@ -1,25 +0,0 @@
#pragma once
enum NormalId {
Right = 0,
Top = 1,
Back = 2,
Left = 3,
Bottom = 4,
Front = 5
};
enum SurfaceType {
SurfaceSmooth = 0,
SurfaceGlue = 1,
SurfaceWeld = 2,
SurfaceStuds = 3,
SurfaceInlets = 4,
SurfaceUniversal = 5,
SurfaceHinge = 6,
SurfaceMotor = 7,
};
namespace Data { class Vector3; } using Data::Vector3;
NormalId faceFromNormal(Vector3);
Vector3 normalFromFace(NormalId);

View file

@ -69,6 +69,7 @@ endif()
target_include_directories(editor PUBLIC "../core/src" "../include" ${QSCINTILLA_INCLUDE_DIR}) target_include_directories(editor PUBLIC "../core/src" "../include" ${QSCINTILLA_INCLUDE_DIR})
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia ${QSCINTILLA_LIBRARY}) target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia ${QSCINTILLA_LIBRARY})
add_dependencies(editor openblocks)
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so # Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually # we have to include it manually

View file

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

View file

@ -4,23 +4,27 @@
#include <qnamespace.h> #include <qnamespace.h>
#include <qsoundeffect.h> #include <qsoundeffect.h>
#include <string> #include <string>
#include "./ui_mainwindow.h"
#include "mainglwidget.h" #include "mainglwidget.h"
#include "datatypes/vector.h"
#include "handles.h" #include "handles.h"
#include "logger.h" #include "logger.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "common.h" #include "common.h"
#include "math_helper.h" #include "math_helper.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "partassembly.h"
#include "physics/util.h" #include "physics/util.h"
#include "rendering/renderer.h" #include "rendering/renderer.h"
#include "rendering/shader.h" #include "rendering/shader.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#define PI 3.14159 #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)); 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); setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setMouseTracking(true); setMouseTracking(true);
} }
@ -113,12 +117,14 @@ CFrame snapCFrame(CFrame frame) {
return CFrame(frame.Position(), frame.Position() + closestVec1, closestVec2); return CFrame(frame.Position(), frame.Position() + closestVec1, closestVec2);
} }
bool tryMouseContextMenu = false;
bool isMouseDragging = false; bool isMouseDragging = false;
std::weak_ptr<Part> draggingObject; std::weak_ptr<Part> draggingObject;
std::optional<HandleFace> draggingHandle; std::optional<HandleFace> draggingHandle;
Vector3 initialHitPos; Vector3 initialHitPos;
Vector3 initialHitNormal; Vector3 initialHitNormal;
CFrame initialFrame; CFrame initialFrame;
PartAssembly initialAssembly({});
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) { void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging || draggingObject.expired() || mainWindow()->selectedTool >= TOOL_SMOOTH) return; if (!isMouseDragging || draggingObject.expired() || mainWindow()->selectedTool >= TOOL_SMOOTH) return;
@ -171,6 +177,7 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
} }
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp) // Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
Vector3 dragStartHandleOffset;
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) { void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return; if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
@ -183,7 +190,9 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height())); glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
pointDir = glm::normalize(pointDir); pointDir = glm::normalize(pointDir);
CFrame handleCFrame = getHandleCFrame(draggingHandle.value()); // We use lastDragStartPos instead to consider the mouse's actual position, rather than the center
// of the handle. That way, transformations are "smoother" and do not jump the first movement
CFrame handleCFrame = getHandleCFrame(draggingHandle.value()) + dragStartHandleOffset;
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false // Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation(); CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
@ -200,60 +209,64 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
glm::vec3 handlePoint, rb; glm::vec3 handlePoint, rb;
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb); get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
// Find new part position // We transform the handlePoint to the handle's cframe, and get it's length (Z)
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position(); float diff = (handleCFrame.Inverse() * handlePoint).Z();
// Vector3 absDiff = ((Vector3)handlePoint - handleCFrame.Position()); // Commented out because it is functionally identical to the below
Vector3 absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
// Apply snapping in the current frame // Apply snapping
glm::vec3 diff = centerPoint - (glm::vec3)part->position(); if (snappingFactor() > 0) {
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor()); diff = round(diff / snappingFactor()) * snappingFactor();
absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
Vector3 oldSize = part->size;
switch (mainWindow()->selectedTool) {
case TOOL_MOVE: {
// Add difference
part->cframe = part->cframe + diff;
} break;
case TOOL_SCALE: {
// Find local difference
glm::vec3 localDiff = frame.Inverse() * diff;
// Find outwarwd difference
localDiff = localDiff * glm::sign(draggingHandle->normal);
// Special case: minimum size to size mod snapping factor
if (snappingFactor() > 0 && glm::all(glm::lessThan((part->size + localDiff) * glm::abs(draggingHandle->normal), glm::vec3(0.01f)))) {
// I tried something fancy here, but honestly I'm not smart enough. Return;
// glm::vec3 finalSize = part->size + localDiff;
// finalSize = glm::mod(finalSize * glm::abs(draggingHandle->normal), snappingFactor()) + finalSize * (glm::vec3(1) - glm::abs(draggingHandle->normal));
// localDiff = finalSize - part->size;
return;
}
// Minimum size of 0.01f
localDiff = glm::max(part->size + localDiff, 0.01f) - part->size;
diff = frame * (localDiff * glm::sign(draggingHandle->normal));
// Add local difference to size
part->size += localDiff;
// If ctrl is not pressed, offset the part by half the size difference to keep the other bound where it was originally
if (!(evt->modifiers() & Qt::ControlModifier))
part->cframe = part->cframe + diff * 0.5f;
} break;
default:
Logger::error("Invalid tool was set to be handled by handleLinearTransform\n");
} }
if (snappingFactor() != 0 && mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav")) PartAssembly selectionAssembly = PartAssembly::FromSelection();
playSound("./assets/excluded/switch.wav");
gWorkspace()->SyncPartPhysics(part); if (editorToolHandles.handlesType == MoveHandles) {
part->UpdateProperty("Position"); selectionAssembly.TransformBy(CFrame() + absDiff);
part->UpdateProperty("Size"); } else if (editorToolHandles.handlesType == ScaleHandles) {
sendPropertyUpdatedSignal(part, "Position", part->position()); if (evt->modifiers() & Qt::AltModifier) {
sendPropertyUpdatedSignal(part, "Size", Vector3(part->size)); // If size gets too small, don't
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f), glm::vec3(0.001f))))
return;
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f, diff > 0);
} else {
// If size gets too small, don't
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff), glm::vec3(0.001f))))
return;
selectionAssembly.TransformBy(CFrame() + absDiff * 0.5f);
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff, diff > 0);
}
}
}
void MainGLWidget::startLinearTransform(QMouseEvent* evt) {
if (!editorToolHandles.active) return;
QPoint position = evt->pos();
// This was actually quite a difficult problem to solve, managing to get the handle to go underneath the cursor
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
pointDir = glm::normalize(pointDir);
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
// Segment from axis stretching -4096 to +4096 rel to handle's position
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
glm::vec3 axisSegment1 = handleCFrame.Position() + (-handleCFrame.LookVector() * -4096.0f);
// Segment from camera stretching 4096 forward
glm::vec3 mouseSegment0 = camera.cameraPos;
glm::vec3 mouseSegment1 = camera.cameraPos + pointDir * 4096.0f;
// Closest point on the axis segment between the two segments
glm::vec3 handlePoint, rb;
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
dragStartHandleOffset = (Vector3)handlePoint - handleCFrame.Position();
} }
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490) // Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
@ -262,7 +275,6 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return; if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y()); glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = getHandleAdornee();
// Calculate part pos as screen point // Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f); glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
@ -294,11 +306,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
glm::vec3 angles = handleNormal * sign * glm::vec3(angle); glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles); CFrame newFrame = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
initialAssembly.SetOrigin(newFrame);
gWorkspace()->SyncPartPhysics(part);
part->UpdateProperty("Rotation");
sendPropertyUpdatedSignal(part, "Rotation", part->cframe.ToEulerAnglesXYZ());
} }
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) { std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
@ -333,6 +342,7 @@ void MainGLWidget::wheelEvent(QWheelEvent* evt) {
} }
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) { void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
tryMouseContextMenu = false;
handleCameraRotate(evt); handleCameraRotate(evt);
handleObjectDrag(evt); handleObjectDrag(evt);
handleCursorChange(evt); handleCursorChange(evt);
@ -351,6 +361,7 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
} }
void MainGLWidget::mousePressEvent(QMouseEvent* evt) { void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
tryMouseContextMenu = evt->button() == Qt::RightButton;
switch(evt->button()) { switch(evt->button()) {
// Camera drag // Camera drag
case Qt::RightButton: { case Qt::RightButton: {
@ -366,17 +377,19 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
auto handle = raycastHandle(pointDir); auto handle = raycastHandle(pointDir);
if (handle.has_value()) { if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y()); startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
initialFrame = getHandleAdornee()->cframe; initialAssembly = PartAssembly::FromSelection();
initialFrame = initialAssembly.assemblyOrigin();
isMouseDragging = true; isMouseDragging = true;
draggingHandle = handle; draggingHandle = handle;
startLinearTransform(evt);
return; return;
} }
// raycast part // raycast part
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000); std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return; if (!rayHit || !partFromBody(rayHit->body)) { setSelection({}); return; }
std::shared_ptr<Part> part = partFromBody(rayHit->body); std::shared_ptr<Part> part = partFromBody(rayHit->body);
if (part->locked) return; if (part->locked) { setSelection({}); return; }
initialFrame = part->cframe; initialFrame = part->cframe;
initialHitPos = rayHit->worldPoint; initialHitPos = rayHit->worldPoint;
initialHitNormal = rayHit->worldNormal; initialHitNormal = rayHit->worldNormal;
@ -407,10 +420,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
isMouseDragging = true; isMouseDragging = true;
draggingObject = part; draggingObject = part;
if (evt->modifiers() & Qt::ControlModifier) { 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++) { for (size_t i = 0; i < currentSelection.size(); i++) {
InstanceRefWeak inst = currentSelection[i]; std::shared_ptr<Instance> inst = currentSelection[i];
if (!inst.expired() && inst.lock() == part) { if (inst == part) {
currentSelection.erase(currentSelection.begin() + i); currentSelection.erase(currentSelection.begin() + i);
goto skipAddPart; goto skipAddPart;
} }
@ -418,8 +431,8 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
currentSelection.push_back(part); currentSelection.push_back(part);
skipAddPart: skipAddPart:
setSelection(currentSelection); setSelection(currentSelection);
}else } else
setSelection(std::vector<InstanceRefWeak> { part }); setSelection({ part });
// Disable bit so that we can ignore the part while raycasting // Disable bit so that we can ignore the part while raycasting
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10); // part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
@ -435,6 +448,23 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
isMouseDragging = false; isMouseDragging = false;
draggingObject = {}; draggingObject = {};
draggingHandle = std::nullopt; 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; static int moveZ = 0;
@ -478,11 +508,11 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
if (evt->key() == Qt::Key_O) if (evt->key() == Qt::Key_O)
Logger::error("error message"); Logger::error("error message");
if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired()) if (evt->key() == Qt::Key_C && getSelection().size() > 0)
getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace()); getSelection()[0]->Clone().value()->SetParent(gWorkspace());
if (evt->key() == Qt::Key_H && getSelection().size() > 0 && !getSelection()[0].expired()) if (evt->key() == Qt::Key_H && getSelection().size() > 0)
Logger::infof("Object at: 0x%x\n", getSelection()[0].lock().get()); Logger::infof("Object at: 0x%x\n", getSelection()[0].get());
} }
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) { void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
@ -501,4 +531,4 @@ float MainGLWidget::snappingFactor() {
case GridSnappingMode::SNAP_OFF: return 0; case GridSnappingMode::SNAP_OFF: return 0;
} }
return 0; return 0;
} }

View file

@ -6,6 +6,7 @@
#include <QOpenGLWidget> #include <QOpenGLWidget>
#include <QWidget> #include <QWidget>
#include <memory> #include <memory>
#include <qmenu.h>
class HandleFace; class HandleFace;
class MainWindow; class MainWindow;
@ -16,6 +17,7 @@ public:
void updateCycle(); void updateCycle();
std::shared_ptr<Part> lastPart; std::shared_ptr<Part> lastPart;
void buildContextMenu();
protected: protected:
void initializeGL() override; void initializeGL() override;
void resizeGL(int w, int h) override; void resizeGL(int w, int h) override;
@ -26,6 +28,7 @@ protected:
void handleLinearTransform(QMouseEvent* evt); void handleLinearTransform(QMouseEvent* evt);
void handleRotationalTransform(QMouseEvent* evt); void handleRotationalTransform(QMouseEvent* evt);
void handleCursorChange(QMouseEvent* evt); void handleCursorChange(QMouseEvent* evt);
void startLinearTransform(QMouseEvent* evt);
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir); std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
void wheelEvent(QWheelEvent* evt) override; void wheelEvent(QWheelEvent* evt) override;
@ -35,6 +38,8 @@ protected:
void keyPressEvent(QKeyEvent* evt) override; void keyPressEvent(QKeyEvent* evt) override;
void keyReleaseEvent(QKeyEvent* evt) override; void keyReleaseEvent(QKeyEvent* evt) override;
QMenu contextMenu;
MainWindow* mainWindow(); MainWindow* mainWindow();
float snappingFactor(); float snappingFactor();
}; };

View file

@ -3,6 +3,7 @@
#include "common.h" #include "common.h"
#include "logger.h" #include "logger.h"
#include "objects/datamodel.h" #include "objects/datamodel.h"
#include "objects/model.h"
#include "placedocument.h" #include "placedocument.h"
#include "script/scriptdocument.h" #include "script/scriptdocument.h"
#include <cstdio> #include <cstdio>
@ -19,6 +20,7 @@
#include <pugixml.hpp> #include <pugixml.hpp>
#include <qtextcursor.h> #include <qtextcursor.h>
#include <qtextedit.h> #include <qtextedit.h>
#include <vector>
#ifdef _NDEBUG #ifdef _NDEBUG
#define NDEBUG #define NDEBUG
@ -71,7 +73,7 @@ MainWindow::MainWindow(QWidget *parent)
if (isDarkMode()) if (isDarkMode())
QIcon::setFallbackThemeName("editor-dark"); QIcon::setFallbackThemeName("editor-dark");
else else
QIcon::setFallbackThemeName("editor"); QIcon::setThemeName("editor");
// qApp->setStyle(QStyleFactory::create("fusion")); // qApp->setStyle(QStyleFactory::create("fusion"));
defaultMessageHandler = qInstallMessageHandler(logQtMessage); defaultMessageHandler = qInstallMessageHandler(logQtMessage);
@ -83,6 +85,8 @@ MainWindow::MainWindow(QWidget *parent)
this->close(); this->close();
}); });
ui->explorerView->buildContextMenu();
connectActionHandlers(); connectActionHandlers();
// Update properties // Update properties
@ -90,17 +94,17 @@ MainWindow::MainWindow(QWidget *parent)
if (newSelection.size() == 0) return; if (newSelection.size() == 0) return;
if (newSelection.size() > 1) if (newSelection.size() > 1)
ui->propertiesView->setSelected(std::nullopt); ui->propertiesView->setSelected(std::nullopt);
ui->propertiesView->setSelected(newSelection[0].lock()); ui->propertiesView->setSelected(newSelection[0]);
}); });
addSelectionListener([&](auto oldSelection, auto newSelection, bool __) { 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; if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock()); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = false; part->selected = false;
} }
for (InstanceRefWeak inst : newSelection) { for (std::weak_ptr<Instance> inst : newSelection) {
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue; if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock()); std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = true; part->selected = true;
@ -141,10 +145,6 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
} }
void MainWindow::connectActionHandlers() { void MainWindow::connectActionHandlers() {
// Explorer View
ui->explorerView->buildContextMenu();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); }); 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->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(); }); 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, [&]() { connect(ui->actionDelete, &QAction::triggered, this, [&]() {
for (InstanceRefWeak inst : getSelection()) { for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->SetParent(std::nullopt); inst.lock()->SetParent(std::nullopt);
} }
setSelection(std::vector<InstanceRefWeak> {}); setSelection({});
}); });
connect(ui->actionCopy, &QAction::triggered, this, [&]() { connect(ui->actionCopy, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc; pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) { for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(rootDoc); inst.lock()->Serialize(rootDoc);
} }
@ -314,7 +314,7 @@ void MainWindow::connectActionHandlers() {
}); });
connect(ui->actionCut, &QAction::triggered, this, [&]() { connect(ui->actionCut, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc; pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) { for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(rootDoc); inst.lock()->Serialize(rootDoc);
inst.lock()->SetParent(std::nullopt); inst.lock()->SetParent(std::nullopt);
@ -338,16 +338,16 @@ void MainWindow::connectActionHandlers() {
rootDoc.load_string(encoded.c_str()); rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) { 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; } if (!inst) { inst.logError(); continue; }
gWorkspace()->AddChild(inst.expect()); gWorkspace()->AddChild(inst.expect());
} }
}); });
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() { 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(); const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return; if (!mimeData || !mimeData->hasFormat("application/xml")) return;
@ -358,12 +358,50 @@ void MainWindow::connectActionHandlers() {
rootDoc.load_string(encoded.c_str()); rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) { 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; } if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect()); 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, [&]() { connect(ui->actionSaveModel, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave); std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave);
if (!path) return; if (!path) return;
@ -373,7 +411,7 @@ void MainWindow::connectActionHandlers() {
pugi::xml_document modelDoc; pugi::xml_document modelDoc;
pugi::xml_node modelRoot = modelDoc.append_child("openblocks"); pugi::xml_node modelRoot = modelDoc.append_child("openblocks");
for (InstanceRefWeak inst : getSelection()) { for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(modelRoot); inst.lock()->Serialize(modelRoot);
} }
@ -382,8 +420,8 @@ void MainWindow::connectActionHandlers() {
}); });
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() { connect(ui->actionInsertModel, &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];
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen); std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen);
if (!path) return; if (!path) return;
@ -393,13 +431,46 @@ void MainWindow::connectActionHandlers() {
modelDoc.load(inStream); modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) { 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; } if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect()); 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() { void MainWindow::updateToolbars() {
ui->actionToolSelect->setChecked(selectedTool == TOOL_SELECT); ui->actionToolSelect->setChecked(selectedTool == TOOL_SELECT);
ui->actionToolMove->setChecked(selectedTool == TOOL_MOVE); ui->actionToolMove->setChecked(selectedTool == TOOL_MOVE);

View file

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

View file

@ -176,6 +176,8 @@
<addaction name="actionCut"/> <addaction name="actionCut"/>
<addaction name="actionPaste"/> <addaction name="actionPaste"/>
<addaction name="actionPasteInto"/> <addaction name="actionPasteInto"/>
<addaction name="actionGroupObjects"/>
<addaction name="actionUngroupObjects"/>
</widget> </widget>
<widget class="QToolBar" name="snappingOptions"> <widget class="QToolBar" name="snappingOptions">
<property name="windowTitle"> <property name="windowTitle">
@ -768,6 +770,40 @@
<enum>QAction::MenuRole::NoRole</enum> <enum>QAction::MenuRole::NoRole</enum>
</property> </property>
</action> </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> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View file

@ -10,11 +10,11 @@
std::map<std::string, QIcon> instanceIconCache; std::map<std::string, QIcon> instanceIconCache;
ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent) ExplorerModel::ExplorerModel(std::shared_ptr<Instance> dataRoot, QWidget *parent)
: QAbstractItemModel(parent) : QAbstractItemModel(parent)
, rootItem(dataRoot) { , rootItem(dataRoot) {
// TODO: Don't use lambdas and handlers like that // 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()) { if (oldParent.has_value()) {
auto children = oldParent.value()->GetChildren(); auto children = oldParent.value()->GetChildren();
size_t idx = std::find(children.begin(), children.end(), object) - children.begin(); 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 (newParent.has_value()) endInsertRows();
if (oldParent.has_value()) endRemoveRows(); if (oldParent.has_value()) endRemoveRows();
}; };
@ -45,24 +45,24 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
? static_cast<Instance*>(parent.internalPointer()) ? static_cast<Instance*>(parent.internalPointer())
: rootItem.get(); : rootItem.get();
if (parentItem->GetChildren().size() >= row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN)) if (parentItem->GetChildren().size() >= (size_t)row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN))
return createIndex(row, column, parentItem->GetChildren()[row].get()); return createIndex(row, column, parentItem->GetChildren()[row].get());
return {}; return {};
} }
QModelIndex ExplorerModel::toIndex(InstanceRef item) { QModelIndex ExplorerModel::toIndex(std::shared_ptr<Instance> item) {
if (item == rootItem || !item->GetParent().has_value()) if (item == rootItem || !item->GetParent().has_value())
return {}; 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 // Check above ensures this item is not root, so value() must be valid
for (int i = 0; i < parentItem->GetChildren().size(); i++) for (size_t i = 0; i < parentItem->GetChildren().size(); i++)
if (parentItem->GetChildren()[i] == item) if (parentItem->GetChildren()[i] == item)
return createIndex(i, 0, item.get()); return createIndex(i, 0, item.get());
return QModelIndex{}; return QModelIndex{};
} }
QModelIndex ExplorerModel::ObjectToIndex(InstanceRef item) { QModelIndex ExplorerModel::ObjectToIndex(std::shared_ptr<Instance> item) {
return toIndex(item); return toIndex(item);
} }
@ -72,14 +72,14 @@ QModelIndex ExplorerModel::parent(const QModelIndex &index) const {
Instance* childItem = static_cast<Instance*>(index.internalPointer()); Instance* childItem = static_cast<Instance*>(index.internalPointer());
// NORISK: The parent must exist if the child was obtained from it during this frame // 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) if (parentItem == rootItem)
return {}; return {};
// Check above ensures this item is not root, so value() must be valid // 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++) for (size_t i = 0; i < parentParent->GetChildren().size(); i++)
if (parentParent->GetChildren()[i] == parentItem) if (parentParent->GetChildren()[i] == parentItem)
return createIndex(i, 0, parentItem.get()); return createIndex(i, 0, parentItem.get());
return QModelIndex{}; return QModelIndex{};
@ -156,7 +156,7 @@ bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow,
Logger::infof("Moved %d from %s", count, sourceParent->name.c_str()); Logger::infof("Moved %d from %s", count, sourceParent->name.c_str());
if ((sourceRow + count) >= sourceParent->GetChildren().size()) { if (size_t(sourceRow + count) >= sourceParent->GetChildren().size()) {
Logger::fatalErrorf("Attempt to move rows %d-%d from %s (%s) while it only has %zu children.", sourceRow, sourceRow + count, sourceParent->name.c_str(), sourceParent->GetClass()->className.c_str(), sourceParent->GetChildren().size()); Logger::fatalErrorf("Attempt to move rows %d-%d from %s (%s) while it only has %zu children.", sourceRow, sourceRow + count, sourceParent->name.c_str(), sourceParent->GetClass()->className.c_str(), sourceParent->GetChildren().size());
return false; return false;
} }
@ -169,7 +169,7 @@ bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow,
} }
bool ExplorerModel::removeRows(int row, int count, const QModelIndex& parentIdx) { bool ExplorerModel::removeRows(int row, int count, const QModelIndex& parentIdx) {
Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get(); // Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get();
for (int i = row; i < (row + count); i++) { for (int i = row; i < (row + count); i++) {
//parent->GetChildren()[i]->SetParent(nullptr); //parent->GetChildren()[i]->SetParent(nullptr);
@ -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; if (!index.isValid()) return rootItem;
return static_cast<Instance*>(index.internalPointer())->shared_from_this(); return static_cast<Instance*>(index.internalPointer())->shared_from_this();
} }
struct DragDropSlot { 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) { 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; return true;
} }
InstanceRef parentInst = fromIndex(parent); std::shared_ptr<Instance> parentInst = fromIndex(parent);
for (InstanceRef instance : slot->instances) { for (std::shared_ptr<Instance> instance : slot->instances) {
instance->SetParent(parentInst); instance->SetParent(parentInst);
} }
@ -225,7 +225,7 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
return true; return true;
} }
void ExplorerModel::updateRoot(InstanceRef newRoot) { void ExplorerModel::updateRoot(std::shared_ptr<Instance> newRoot) {
beginResetModel(); beginResetModel();
rootItem = newRoot; rootItem = newRoot;
endResetModel(); endResetModel();

View file

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

View file

@ -33,16 +33,15 @@ ExplorerView::ExplorerView(QWidget* parent):
this->expand(model.ObjectToIndex(gWorkspace())); this->expand(model.ObjectToIndex(gWorkspace()));
connect(this, &QTreeView::customContextMenuRequested, this, [&](const QPoint& point) { connect(this, &QTreeView::customContextMenuRequested, this, [&](const QPoint& point) {
QModelIndex index = this->indexAt(point);
contextMenu.exec(this->viewport()->mapToGlobal(point)); contextMenu.exec(this->viewport()->mapToGlobal(point));
}); });
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) { 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 selectedInstances.reserve(selectedIndexes().count()); // This doesn't reserve everything, but should enhance things anyway
for (auto index : selectedIndexes()) { 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); ::setSelection(selectedInstances, true);
@ -53,7 +52,7 @@ ExplorerView::ExplorerView(QWidget* parent):
if (fromExplorer) return; if (fromExplorer) return;
this->clearSelection(); this->clearSelection();
for (InstanceRefWeak inst : newSelection) { for (std::weak_ptr<Instance> inst : newSelection) {
if (inst.expired()) continue; if (inst.expired()) continue;
QModelIndex index = this->model.ObjectToIndex(inst.lock()); QModelIndex index = this->model.ObjectToIndex(inst.lock());
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select); this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
@ -104,14 +103,14 @@ void ExplorerView::buildContextMenu() {
QAction* instAction = new QAction(model.iconOf(type), QString::fromStdString(type->className)); QAction* instAction = new QAction(model.iconOf(type), QString::fromStdString(type->className));
insertObjectMenu->addAction(instAction); insertObjectMenu->addAction(instAction);
connect(instAction, &QAction::triggered, this, [&]() { connect(instAction, &QAction::triggered, this, [&]() {
if (getSelection().size() == 0 || getSelection()[0].expired()) return; if (getSelection().size() == 0) return;
std::shared_ptr<Instance> instParent = getSelection()[0].lock(); std::shared_ptr<Instance> instParent = getSelection()[0];
std::shared_ptr<Instance> newInst = type->constructor(); std::shared_ptr<Instance> newInst = type->constructor();
newInst->SetParent(instParent); newInst->SetParent(instParent);
}); });
} }
} }
void ExplorerView::updateRoot(InstanceRef newRoot) { void ExplorerView::updateRoot(std::shared_ptr<Instance> newRoot) {
model.updateRoot(newRoot); model.updateRoot(newRoot);
} }

View file

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

View file

@ -1,30 +1,36 @@
#include "panes/propertiesview.h" #include "panes/propertiesview.h"
#include "common.h" #include "common.h"
#include "datatypes/base.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 "objects/base/member.h"
#include <QColorDialog> #include <QColorDialog>
#include <QComboBox>
#include <QLineEdit> #include <QLineEdit>
#include <QSpinBox> #include <QSpinBox>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <QPainter> #include <QPainter>
#include <QTime> #include <QTime>
#include <cfloat>
#include <cmath>
#include <functional> #include <functional>
#include <qapplication.h>
#include <qcombobox.h>
#include <qevent.h>
#include <qnamespace.h> #include <qnamespace.h>
#include <qtreewidget.h> #include <qtreewidget.h>
class PropertiesItemDelegate : public QStyledItemDelegate { class PropertiesItemDelegate : public QStyledItemDelegate {
PropertiesView* view; PropertiesView* view;
public: public:
PropertiesItemDelegate(PropertiesView* parent) : view(parent), QStyledItemDelegate(parent) {} PropertiesItemDelegate(PropertiesView* parent) : QStyledItemDelegate(parent), view(parent) {}
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override { void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override {
// https://stackoverflow.com/a/76645757/16255372 // https://stackoverflow.com/a/76645757/16255372
// https://stackoverflow.com/a/70078448/16255372 // https://stackoverflow.com/a/70078448/16255372
int indent = dynamic_cast<PropertiesView*>(parent())->indentation();
QStyledItemDelegate::initStyleOption(option, index); QStyledItemDelegate::initStyleOption(option, index);
if (!index.parent().isValid()) { if (!index.parent().isValid()) {
@ -38,7 +44,7 @@ public:
if (index.column() == 0) return nullptr; if (index.column() == 0) return nullptr;
if (!index.parent().isValid() || view->currentInstance.expired()) 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 // If the property is deeper than 1 layer, then it is considered composite
// Handle specially // Handle specially
@ -49,10 +55,10 @@ public:
std::string propertyName = !isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() std::string propertyName = !isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString()
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString(); : view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect(); Variant currentValue = inst->GetPropertyValue(propertyName).expect();
if (isComposite) { if (isComposite) {
if (meta.type == &Vector3::TYPE) { if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>(); Vector3 vector = currentValue.get<Vector3>();
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0; float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
@ -65,9 +71,11 @@ public:
return nullptr; return nullptr;
} }
if (meta.type == &Data::Float::TYPE) { if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent); QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
spinBox->setValue(currentValue.get<Data::Float>()); spinBox->setValue(currentValue.get<float>());
spinBox->setMinimum(-INFINITY);
spinBox->setMaximum(INFINITY);
if (meta.flags & PROP_UNIT_FLOAT) { if (meta.flags & PROP_UNIT_FLOAT) {
spinBox->setMinimum(0); spinBox->setMinimum(0);
@ -76,24 +84,45 @@ public:
} }
return spinBox; return spinBox;
} else if (meta.type == &Data::Int::TYPE) { } else if (meta.type.descriptor == &INT_TYPE) {
QSpinBox* spinBox = new QSpinBox(parent); QSpinBox* spinBox = new QSpinBox(parent);
spinBox->setValue(currentValue.get<Data::Int>()); spinBox->setMinimum(INT_MIN);
spinBox->setMaximum(INT_MAX);
spinBox->setValue(currentValue.get<int>());
return spinBox; return spinBox;
} else if (meta.type == &Data::String::TYPE) { } else if (meta.type.descriptor == &STRING_TYPE) {
QLineEdit* lineEdit = new QLineEdit(parent); QLineEdit* lineEdit = new QLineEdit(parent);
lineEdit->setText(QString::fromStdString(currentValue.get<Data::String>())); lineEdit->setText(QString::fromStdString(currentValue.get<std::string>()));
return lineEdit; return lineEdit;
} else if (meta.type == &Color3::TYPE) { } else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = new QColorDialog(parent->window()); QColorDialog* colorDialog = new QColorDialog(parent->window());
Color3 color = currentValue.get<Color3>(); Color3 color = currentValue.get<Color3>();
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B())); colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
return colorDialog; return colorDialog;
} else if (meta.type->fromString) { } else if (meta.type.descriptor == &EnumItem::TYPE) {
QComboBox* comboBox = new QComboBox(parent);
EnumItem enumItem = currentValue.get<EnumItem>();
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
for (size_t i = 0; i < siblingItems.size(); i++) {
comboBox->addItem(QString::fromStdString(siblingItems[i].Name()));
if (siblingItems[i].Value() == enumItem.Value())
comboBox->setCurrentIndex(i);
}
// If a selection is made
// https://forum.qt.io/post/426902
connect(comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, comboBox, index]() {
setModelData(comboBox, view->model(), index);
view->closeEditor(comboBox, QAbstractItemDelegate::EndEditHint::NoHint);
});
return comboBox;
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = new QLineEdit(parent); QLineEdit* lineEdit = new QLineEdit(parent);
lineEdit->setText(QString::fromStdString(currentValue.ToString())); lineEdit->setText(QString::fromStdString(currentValue.ToString()));
@ -107,7 +136,7 @@ public:
if (index.column() == 0) return; if (index.column() == 0) return;
if (!index.parent().isValid() || view->currentInstance.expired()) 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(); bool isComposite = index.parent().parent().isValid();
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : ""; std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
@ -115,10 +144,10 @@ public:
std::string propertyName = !index.parent().parent().isValid() ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() 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(); : view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect(); Variant currentValue = inst->GetPropertyValue(propertyName).expect();
if (isComposite) { if (isComposite) {
if (meta.type == &Vector3::TYPE) { if (meta.type.descriptor == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>(); Vector3 vector = currentValue.get<Vector3>();
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0; float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
@ -131,24 +160,33 @@ public:
return; return;
} }
if (meta.type == &Data::Float::TYPE) { if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor); QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
spinBox->setValue(currentValue.get<Data::Float>()); spinBox->setValue(currentValue.get<float>());
} else if (meta.type == &Data::Int::TYPE) { } else if (meta.type.descriptor == &INT_TYPE) {
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor); QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
spinBox->setValue(currentValue.get<Data::Int>()); spinBox->setValue(currentValue.get<int>());
} else if (meta.type == &Data::String::TYPE) { } else if (meta.type.descriptor == &STRING_TYPE) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor); QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
lineEdit->setText(QString::fromStdString((std::string)currentValue.get<Data::String>())); lineEdit->setText(QString::fromStdString((std::string)currentValue.get<std::string>()));
} else if (meta.type == &Color3::TYPE) { } else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor); QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
Color3 color = currentValue.get<Color3>(); Color3 color = currentValue.get<Color3>();
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B())); colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
} else if (meta.type->fromString) { } else if (meta.type.descriptor == &EnumItem::TYPE) {
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
EnumItem enumItem = currentValue.get<EnumItem>();
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
for (size_t i = 0; i < siblingItems.size(); i++) {
if (siblingItems[i].Value() == enumItem.Value())
comboBox->setCurrentIndex(i);
}
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor); QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
lineEdit->setText(QString::fromStdString((std::string)currentValue.ToString())); lineEdit->setText(QString::fromStdString((std::string)currentValue.ToString()));
@ -159,7 +197,7 @@ public:
if (index.column() == 0) return; if (index.column() == 0) return;
if (!index.parent().isValid() || view->currentInstance.expired()) 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(); bool isComposite = index.parent().parent().isValid();
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : ""; std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
@ -169,7 +207,7 @@ public:
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
if (isComposite) { if (isComposite) {
if (meta.type == &Vector3::TYPE) { if (meta.type.descriptor == &Vector3::TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor); QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
float value = spinBox->value(); float value = spinBox->value();
@ -186,22 +224,22 @@ public:
return; return;
} }
if (meta.type == &Data::Float::TYPE) { if (meta.type.descriptor == &FLOAT_TYPE) {
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor); 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()); 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); 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()); 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); 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()); model->setData(index, lineEdit->text());
} else if (meta.type == &Color3::TYPE) { } else if (meta.type.descriptor == &Color3::TYPE) {
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor); QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
QColor color = colorDialog->currentColor(); QColor color = colorDialog->currentColor();
@ -209,14 +247,22 @@ public:
inst->SetPropertyValue(propertyName, color3).expect(); inst->SetPropertyValue(propertyName, color3).expect();
model->setData(index, QString::fromStdString(color3.ToString()), Qt::DisplayRole); model->setData(index, QString::fromStdString(color3.ToString()), Qt::DisplayRole);
model->setData(index, color, Qt::DecorationRole); model->setData(index, color, Qt::DecorationRole);
} else if (meta.type->fromString) { } else if (meta.type.descriptor == &EnumItem::TYPE) {
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
EnumItem newItem = siblingItems[comboBox->currentIndex()];
inst->SetPropertyValue(propertyName, newItem).expect("Failed to set enum value in properties pane");
model->setData(index, QString::fromStdString(newItem.Name()), Qt::DisplayRole);
} else if (meta.type.descriptor->fromString) {
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor); 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; if (!parsedResult) return;
inst->SetPropertyValue(propertyName, parsedResult.value()).expect(); Variant parsedValue = parsedResult.expect();
model->setData(index, QString::fromStdString(parsedResult.value().ToString())); inst->SetPropertyValue(propertyName, parsedValue).expect();
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type, parsedResult.value()); model->setData(index, QString::fromStdString(parsedValue.ToString()));
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedValue);
} }
} }
}; };
@ -268,11 +314,11 @@ void PropertiesView::drawBranches(QPainter *painter, const QRect &rect, const QM
QTreeWidget::drawBranches(painter, rect, index); QTreeWidget::drawBranches(painter, rect, index);
} }
void PropertiesView::setSelected(std::optional<InstanceRef> instance) { void PropertiesView::setSelected(std::optional<std::shared_ptr<Instance>> instance) {
clear(); clear();
currentInstance = {}; currentInstance = {};
if (!instance) return; if (!instance) return;
InstanceRef inst = instance.value(); std::shared_ptr<Instance> inst = instance.value();
currentInstance = inst; currentInstance = inst;
std::map<PropertyCategory, QTreeWidgetItem*> propertyCategories; std::map<PropertyCategory, QTreeWidgetItem*> propertyCategories;
@ -296,35 +342,37 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
for (std::string property : properties) { for (std::string property : properties) {
PropertyMeta meta = inst->GetPropertyMeta(property).expect(); 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; QTreeWidgetItem* item = new QTreeWidgetItem;
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable); item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable);
item->setData(0, Qt::DisplayRole, QString::fromStdString(property)); item->setData(0, Qt::DisplayRole, QString::fromStdString(property));
if (meta.type == &Data::Bool::TYPE) { if (meta.type.descriptor == &BOOL_TYPE) {
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);
} else if (meta.type == &Color3::TYPE) { } else if (meta.type.descriptor == &Color3::TYPE) {
Color3 color = currentValue.get<Color3>(); Color3 color = currentValue.get<Color3>();
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B())); item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); 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>(); Vector3 vector = currentValue.get<Vector3>();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); 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(); // Vector3 vector = currentValue.get<CFrame>().Position();
// item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); // item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else { } else if (meta.type.descriptor == &EnumItem::TYPE) {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.get<EnumItem>().Name()));
} else {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); 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); item->setDisabled(true);
} }
rebuildCompositeProperty(item, meta.type, currentValue); rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
propertyCategories[meta.category]->addChild(item); propertyCategories[meta.category]->addChild(item);
propertyCategories[meta.category]->setExpanded(true); propertyCategories[meta.category]->setExpanded(true);
@ -347,17 +395,17 @@ void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) {
// Necessary because otherwise this will catch setCheckState from onPropertyUpdated // Necessary because otherwise this will catch setCheckState from onPropertyUpdated
if (ignorePropertyUpdates) return; if (ignorePropertyUpdates) return;
if (!item->parent() || (item->parent() && item->parent()->parent()) || currentInstance.expired()) 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(); std::string propertyName = item->data(0, Qt::DisplayRole).toString().toStdString();
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
if (meta.type == &Data::Bool::TYPE) { if (meta.type.descriptor == &BOOL_TYPE) {
inst->SetPropertyValue(propertyName, Data::Bool(item->checkState(1) == Qt::Checked)).expect(); 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) { if (type == &Vector3::TYPE) {
// https://forum.qt.io/post/266837 // https://forum.qt.io/post/266837
foreach(auto i, item->takeChildren()) delete i; foreach(auto i, item->takeChildren()) delete i;
@ -384,15 +432,15 @@ void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const Data:
} }
// static auto lastUpdateTime = std::chrono::steady_clock::now(); // 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 (!currentInstance || currentInstance->expired() || instance != currentInstance->lock()) return;
// if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - lastUpdateTime).count() < 1000) return; // if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - lastUpdateTime).count() < 1000) return;
// lastUpdateTime = std::chrono::steady_clock::now(); // lastUpdateTime = std::chrono::steady_clock::now();
PropertyMeta meta = inst->GetPropertyMeta(property).expect(); 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++) { for (int categoryItemIdx = 0; categoryItemIdx < topLevelItemCount(); categoryItemIdx++) {
QTreeWidgetItem* categoryItem = topLevelItem(categoryItemIdx); QTreeWidgetItem* categoryItem = topLevelItem(categoryItemIdx);
@ -401,27 +449,27 @@ void PropertiesView::onPropertyUpdated(InstanceRef inst, std::string property, D
if (item->data(0, Qt::DisplayRole).toString().toStdString() != property) continue; 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 // This is done because otherwise propertyChanged will catch this change erroneously
ignorePropertyUpdates = true; 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; ignorePropertyUpdates = false;
} else if (meta.type == &Color3::TYPE) { } else if (meta.type.descriptor == &Color3::TYPE) {
Color3 color = currentValue.get<Color3>(); Color3 color = currentValue.get<Color3>();
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B())); item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); 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>(); Vector3 vector = currentValue.get<Vector3>();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else { } else {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); 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); item->setDisabled(true);
} }
rebuildCompositeProperty(item, meta.type, currentValue); rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
return; return;
} }

View file

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

View file

@ -5,18 +5,21 @@
#include "objects/joint/snap.h" #include "objects/joint/snap.h"
#include "objects/script.h" #include "objects/script.h"
#include "objects/script/scriptcontext.h" #include "objects/script/scriptcontext.h"
#include "rendering/surface.h" #include "enum/surface.h"
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
#include <qboxlayout.h> #include <qboxlayout.h>
#include <qdebug.h>
#include <qevent.h> #include <qevent.h>
#include <qmargins.h> #include <qmargins.h>
#include <qmdisubwindow.h> #include <qmdisubwindow.h>
#include <qlayout.h> #include <qlayout.h>
#include <qmimedata.h>
PlaceDocument::PlaceDocument(QWidget* parent): PlaceDocument::PlaceDocument(QWidget* parent):
QMdiSubWindow(parent) { QMdiSubWindow(parent) {
placeWidget = new MainGLWidget; placeWidget = new MainGLWidget;
setAcceptDrops(true);
setWidget(placeWidget); setWidget(placeWidget);
setWindowTitle("Place"); setWindowTitle("Place");
@ -74,6 +77,7 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
void PlaceDocument::init() { void PlaceDocument::init() {
timer.start(33, this); timer.start(33, this);
placeWidget->buildContextMenu();
std::shared_ptr<Part> lastPart; std::shared_ptr<Part> lastPart;
// Baseplate // Baseplate
@ -89,49 +93,28 @@ void PlaceDocument::init() {
gWorkspace()->SyncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
gWorkspace()->AddChild(lastPart = Part::New({ gWorkspace()->AddChild(lastPart = Part::New({
.position = glm::vec3(0), .position = glm::vec3(-3.8),
.rotation = glm::vec3(-2.6415927, 1.1415926, 2.57075), .rotation = glm::vec3(0),
.size = glm::vec3(4, 1.2, 2), .size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f), .color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
})); }));
gWorkspace()->SyncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
auto part0 = lastPart; auto part0 = lastPart;
gWorkspace()->AddChild(lastPart = Part::New({
.position = glm::vec3(1.7610925, 0.48568499, -0.82623518),
// .rotation = glm::vec3(0.5, 2, 1),
.rotation = glm::vec3(-2.6415927, 1.1415926, -2.141639),
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
gWorkspace()->SyncPartPhysics(lastPart);
auto part1 = lastPart;
lastPart = Part::New(); lastPart = Part::New();
shit = part1; }
part0->anchored = true; void PlaceDocument::dragEnterEvent(QDragEnterEvent* evt) {
part0->UpdateProperty("Anchored"); // https://stackoverflow.com/a/14895393/16255372
if (evt->mimeData()->hasUrls()) {
evt->acceptProposedAction();
}
}
// auto snap = Snap::New(); void PlaceDocument::dropEvent(QDropEvent* evt) {
// snap->part0 = part0; auto urls = evt->mimeData()->urls();
// snap->part1 = part1; if (urls.size() == 0) return;
// snap->c0 = part1->cframe; QString fileName = urls[0].toLocalFile();
// snap->c1 = part0->cframe;
// gWorkspace()->AddChild(snap);
// snap->UpdateProperty("Part0");
// snap->UpdateProperty("Part1");
// part0->backSurface = SurfaceWeld;
// part1->frontSurface = SurfaceWeld;
// part0->backSurface = SurfaceHinge;
part0->backSurface = SurfaceMotor;
// part1->frontSurface = SurfaceHinge;
std::shared_ptr<Script> script = Script::New();
gWorkspace()->AddChild(script);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window()); MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
// mainWnd->openScriptDocument(script); mainWnd->openFile(fileName.toStdString());
} }

View file

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

View file

@ -16,7 +16,7 @@
#include <qtextformat.h> #include <qtextformat.h>
#include "mainwindow.h" #include "mainwindow.h"
#include "objects/script.h" #include "objects/script.h"
#include "datatypes/meta.h" #include "datatypes/variant.h"
#include <QPalette> #include <QPalette>
#include <QStyleHints> #include <QStyleHints>
@ -110,14 +110,14 @@ class ObLuaLexer : public QsciLexerLua {
}; };
ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent): ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
script(script), QMdiSubWindow(parent) { QMdiSubWindow(parent), script(script) {
setWindowTitle(QString::fromStdString(script->name)); setWindowTitle(QString::fromStdString(script->name));
// Add detector for script deletion to automatically close this document // Add detector for script deletion to automatically close this document
scriptDeletionHandler = script->AncestryChanged->Connect([this, script](std::vector<Data::Variant> args) { scriptDeletionHandler = script->AncestryChanged->Connect([this, script](std::vector<Variant> args) {
std::weak_ptr<Instance> child = args[0].get<Data::InstanceRef>(); std::weak_ptr<Instance> child = args[0].get<InstanceRef>();
std::weak_ptr<Instance> newParent = args[1].get<Data::InstanceRef>(); std::weak_ptr<Instance> newParent = args[1].get<InstanceRef>();
if (child.expired() || child.lock() != script || !newParent.expired()) return; if (child.expired() || child.lock() != script || !newParent.expired()) return;
dynamic_cast<MainWindow*>(window())->closeScriptDocument(script); dynamic_cast<MainWindow*>(window())->closeScriptDocument(script);
@ -125,7 +125,7 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
QFrame* frame = new QFrame; QFrame* frame = new QFrame;
QVBoxLayout* frameLayout = new QVBoxLayout; QVBoxLayout* frameLayout = new QVBoxLayout;
frameLayout->setMargin(0); frameLayout->setContentsMargins({0, 0, 0, 0});
frame->setLayout(frameLayout); frame->setLayout(frameLayout);
scintilla = new QsciScintilla(this); scintilla = new QsciScintilla(this);