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
103 changed files with 2403 additions and 1246 deletions

3
.gitignore vendored
View file

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

View file

@ -22,6 +22,7 @@ The project will be built using VCPKG and MSVC
* Qt 6.8.3 or higher, with MSVC toolchain
* CMake
* Git (for cloning the repo, optional)
* QScintilla already built (see [docs/qscintilla.md](./docs/qscintilla.md)) *\*likely temporary\**
To start, clone the repository:
@ -37,10 +38,14 @@ Now, generate the build files with cmake via the vcpkg preset:
cmake -Bbuild . --preset vcpkg
Then, finally, build in release mode:
Then, finally, build in release mode\*:
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.
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)
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" )
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;
out vec4 fColor;
uniform vec3 color;
void main() {
// float thickness = 0.2;
@ -20,5 +21,6 @@ void main() {
// else
// 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/data/analysis.cpp
src/data/codegen.cpp
src/enum/analysis.cpp
src/enum/codegen.cpp
)
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
target_link_libraries(autogen -lclang)
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIRS})
target_link_libraries(autogen ${CLANG_LIBRARY})
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIR})

View file

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

View file

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

View file

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

View file

@ -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 <fstream>
#include <filesystem>
#include "enum/analysis.h"
#include "enum/codegen.h"
#include "object/analysis.h"
#include "object/codegen.h"
#include "data/analysis.h"
#include "data/codegen.h"
#include "util.h"
// namespace data {
// #include "data/analysis.h"
@ -20,13 +23,17 @@ namespace fs = std::filesystem;
// https://clang.llvm.org/docs/LibClang.html
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
// https://stackoverflow.com/a/59206378/16255372
CXIndex index = clang_createIndex(0, 0);
CXTranslationUnit unit = clang_parseTranslationUnit(
index,
srcPath.c_str(), cargs, 5,
srcPathStr.c_str(), cargs, 5,
nullptr, 0,
CXTranslationUnit_None);
@ -50,16 +57,19 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
object::AnalysisState objectAnlyState;
data::AnalysisState dataAnlyState;
enum_::AnalysisState enumAnlyState;
fs::path relpath = fs::relative(srcPath, srcRoot);
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
object::analyzeClasses(cursor, srcRoot, &objectAnlyState);
data::analyzeClasses(cursor, srcRoot, &dataAnlyState);
std::string relpathStr = string_of(relpath);
printf("[AUTOGEN] Processing file %s...\n", relpathStr.c_str());
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
printf("[AUTOGEN] Generating file %s...\n", relpath.c_str());
std::ofstream outStream(outPath);
printf("[AUTOGEN] Generating file %s...\n", relpathStr.c_str());
std::ofstream outStream(outPathStr);
if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) {
outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n";
@ -68,11 +78,15 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
}
for (auto& [_, clazz] : objectAnlyState.classes) {
object::writeCodeForClass(outStream, relpath, clazz);
object::writeCodeForClass(outStream, relpathStr, clazz);
}
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();

View file

@ -80,7 +80,13 @@ static void processField(CXCursor cur, ClassAnalysis* state) {
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly;
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);
@ -156,7 +162,7 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
anly.name = className;
anly.baseClass = baseClass;
anly.headerPath = headerPath;
anly.headerPath = string_of(headerPath);
// Add misc flags and options
auto instanceDef = findAnnotation(cur, "OB::def_inst");

View file

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

View file

@ -25,11 +25,11 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
int stage = 0;
bool quoted = false;
int i = 0;
size_t i = 0;
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 == 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];
stage = 1;
continue;
@ -63,7 +63,7 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
currentValue += src[i];
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", i > 0 ? std::string(i, '~').c_str() : "");
abort();
@ -108,4 +108,8 @@ std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationNa
return CXChildVisit_Break;
});
return ret;
}
std::string string_of(std::filesystem::path path) {
return path.string();
}

View file

@ -5,6 +5,7 @@
#include <map>
#include <optional>
#include <string>
#include <filesystem>
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": ""
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)
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),
}));
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
gWorkspace()->SyncPartPhysics(part);
@ -170,15 +170,15 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
}
} else if (mode == 1) {
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
lastPart->size.x += shiftFactor;
lastPart->size += Vector3(1, 0, 0) * shiftFactor;
gWorkspace()->SyncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->size.y += shiftFactor;
lastPart->size += Vector3(0, 1, 0) * shiftFactor;
gWorkspace()->SyncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->size.z += shiftFactor;
lastPart->size += Vector3(0, 0, 1) * shiftFactor;
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

@ -1,5 +1,12 @@
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
add_library(QScintilla::QScintilla UNKNOWN IMPORTED)
### NECESSARY TO PREVENT staticMetaObject ERROR!!! See qscintilla.prf AKA qmake config
if(WIN32)
add_compile_definitions(QSCINTILLA_DLL)
endif()
FIND_PATH(QSCINTILLA_INCLUDE_DIR
NAMES Qsci/qsciglobal.h
PATHS
@ -7,6 +14,7 @@ FIND_PATH(QSCINTILLA_INCLUDE_DIR
$ENV{LIB_DIR}/include
/usr/local/include
/usr/include
${VCPKG_INSTALLED_DIR}/x64-windows/include
PATH_SUFFIXES ${QSCINTILLA_PATH_SUFFIXES}
)
@ -29,4 +37,20 @@ PATHS
/usr/local/lib
/usr/local/lib/qt${QT_VERSION_MAJOR}
/usr/lib
${VCPKG_INSTALLED_DIR}/x64-windows/lib
)
get_filename_component(QSCINTILLA_LIB_DIR ${QSCINTILLA_LIBRARY} DIRECTORY)
list(TRANSFORM QSCINTILLA_LIBRARY_NAMES APPEND ".dll" OUTPUT_VARIABLE QSCINTILLA_DLL_NAMES)
find_file(QSCINTILLA_DLLS
NAMES ${QSCINTILLA_DLL_NAMES}
PATHS
"${QT_LIBRARY_DIR}"
$ENV{LIB_DIR}/lib
/usr/local/lib
/usr/local/lib/qt${QT_VERSION_MAJOR}
/usr/lib
${QSCINTILLA_LIB_DIR}
${VCPKG_INSTALLED_DIR}/x64-windows/lib
)

View file

@ -14,9 +14,10 @@ include_directories(${Stb_INCLUDE_DIR})
# PkgConfig packages
find_package(PkgConfig REQUIRED)
pkg_check_modules(LUAJIT REQUIRED luajit)
link_directories(${LUAJIT_LIBRARY_DIRS})
# 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
foreach (SRC ${AUTOGEN_SOURCES})
@ -26,8 +27,9 @@ foreach (SRC ${AUTOGEN_SOURCES})
add_custom_command(
OUTPUT "${OUT_PATH}"
DEPENDS autogen
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}")
@ -41,8 +43,10 @@ file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
list(APPEND SOURCES ${AUTOGEN_OUTS})
add_library(openblocks STATIC ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS})
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIR})
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS})
add_dependencies(openblocks autogen_build autogen)
# Windows-specific dependencies
if(WIN32)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,8 @@
#include "signal.h"
#include "datatypes/base.h"
#include "meta.h"
#include "variant.h"
#include "lua.h"
#include <cstdio>
#include <pugixml.hpp>
#include <memory>
#include <vector>
@ -21,24 +22,44 @@ LuaSignalConnection::LuaSignalConnection(lua_State* L, std::weak_ptr<Signal> par
// 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);
lua_pop(L, 1);
lua_pushthread(L);
thread = luaL_ref(L, LUA_REGISTRYINDEX);
}
LuaSignalConnection::~LuaSignalConnection() {
// Remove LuaSignalConnectionthread so that it can get properly GC'd
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);
// Push 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);
}
@ -47,15 +68,17 @@ void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
Logger::error(lua_tostring(thread, -1));
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;
}
void CSignalConnection::Call(std::vector<Data::Variant> args) {
void CSignalConnection::Call(std::vector<Variant> args) {
function(args);
}
@ -63,7 +86,7 @@ void CSignalConnection::Call(std::vector<Data::Variant> args) {
SignalConnectionHolder::SignalConnectionHolder() : heldConnection() {}
SignalConnectionHolder::SignalConnectionHolder(std::shared_ptr<SignalConnection> connection) : heldConnection(connection) {}
SignalConnectionHolder::SignalConnectionHolder(Data::SignalConnectionRef other) : heldConnection(other) {}
SignalConnectionHolder::SignalConnectionHolder(SignalConnectionRef other) : heldConnection(other) {}
SignalConnectionHolder::~SignalConnectionHolder() {
// printf("Prediscon!\n");
@ -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()));
connections.push_back(conn);
return SignalConnectionRef(conn);
@ -86,7 +109,7 @@ SignalConnectionRef Signal::Connect(lua_State* state) {
return SignalConnectionRef(conn);
}
SignalConnectionRef Signal::Once(std::function<void(std::vector<Data::Variant>)> callback) {
SignalConnectionRef Signal::Once(std::function<void(std::vector<Variant>)> callback) {
auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
onceConnections.push_back(conn);
return SignalConnectionRef(conn);
@ -109,7 +132,7 @@ int Signal::Wait(lua_State* thread) {
return lua_yield(thread, 0);
}
void Signal::Fire(std::vector<Data::Variant> args) {
void Signal::Fire(std::vector<Variant> args) {
for (std::shared_ptr<SignalConnection> connection : connections) {
connection->Call(args);
}
@ -125,7 +148,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
auto prevThreads = std::move(waitingThreads);
waitingThreads = std::vector<std::pair<int, lua_State*>>();
for (auto& [threadId, thread] : prevThreads) {
for (Data::Variant arg : args) {
for (Variant arg : args) {
arg.PushLuaValue(thread);
}
@ -142,7 +165,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
}
void Signal::Fire() {
return Fire(std::vector<Data::Variant> {});
return Fire(std::vector<Variant> {});
}
void Signal::DisconnectAll() {
@ -199,29 +222,32 @@ static const struct luaL_Reg signal_metatable [] = {
{NULL, NULL} /* end of array */
};
Data::SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
Data::SignalRef::~SignalRef() = default;
SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
SignalRef::~SignalRef() = default;
const Data::TypeInfo Data::SignalRef::TYPE = {
const TypeDesc SignalRef::TYPE = {
.name = "Signal",
.fromLuaValue = &Data::SignalRef::FromLuaValue,
.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 Data::String Data::SignalRef::ToString() const {
return Data::String("Signal");
const std::string SignalRef::ToString() const {
return "Signal";
}
Data::SignalRef::operator std::weak_ptr<Signal>() {
SignalRef::operator std::weak_ptr<Signal>() {
return signal;
}
void Data::SignalRef::Serialize(pugi::xml_node node) const {
void SignalRef::Serialize(pugi::xml_node node) const {
// Not serializable
}
void Data::SignalRef::PushLuaValue(lua_State* L) const {
void SignalRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L);
auto userdata = (std::weak_ptr<Signal>**)lua_newuserdata(L, sizeof(std::weak_ptr<Signal>));
@ -234,10 +260,10 @@ void Data::SignalRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::SignalRef::FromLuaValue(lua_State* L, int idx) {
result<Variant, LuaCastError> SignalRef::FromLuaValue(lua_State* L, int idx) {
auto userdata = (std::weak_ptr<Signal>**)luaL_checkudata(L, 1, "__mt_signal");
lua_pop(L, 1);
return Data::Variant(Data::SignalRef(**userdata));
return Variant(SignalRef(**userdata));
}
static int signal_gc(lua_State* L) {
@ -320,29 +346,32 @@ static const struct luaL_Reg signalconnection_metatable [] = {
{NULL, NULL} /* end of array */
};
Data::SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
Data::SignalConnectionRef::~SignalConnectionRef() = default;
SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
SignalConnectionRef::~SignalConnectionRef() = default;
const Data::TypeInfo Data::SignalConnectionRef::TYPE = {
const TypeDesc SignalConnectionRef::TYPE = {
.name = "Signal",
.fromLuaValue = &Data::SignalConnectionRef::FromLuaValue,
.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 Data::String Data::SignalConnectionRef::ToString() const {
return Data::String("Connection");
const std::string SignalConnectionRef::ToString() const {
return "Connection";
}
Data::SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
return signalConnection;
}
void Data::SignalConnectionRef::Serialize(pugi::xml_node node) const {
void SignalConnectionRef::Serialize(pugi::xml_node node) const {
// Not serializable
}
void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
void SignalConnectionRef::PushLuaValue(lua_State* L) const {
int n = lua_gettop(L);
auto userdata = (std::weak_ptr<SignalConnection>**)lua_newuserdata(L, sizeof(std::weak_ptr<SignalConnection>));
@ -355,10 +384,10 @@ void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
result<Variant, LuaCastError> SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
auto userdata = (std::weak_ptr<SignalConnection>**)luaL_checkudata(L, 1, "__mt_signalconnection");
lua_pop(L, 1);
return Data::Variant(Data::SignalConnectionRef(**userdata));
return Variant(SignalConnectionRef(**userdata));
}
static int signalconnection_tostring(lua_State* L) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {
public:
inline LuaCastError(std::string sourceType, std::string targetType) : Error("InstanceCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
inline LuaCastError(std::string sourceType, std::string targetType) : Error("LuaCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
};
class DataParseError : public Error {
public:
inline DataParseError(std::string parsedString, std::string targetType) : Error("DataParseError", "Failed to parse '" + parsedString + "' into value of type " + targetType) {}
};

View file

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

View file

@ -40,7 +40,7 @@ CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
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);

View file

@ -1,7 +1,7 @@
#pragma once
extern "C" {
#include <luajit-2.1/luajit.h>
#include <luajit-2.1/lauxlib.h>
#include <luajit-2.1/lualib.h>
#include <luajit-2.1/lua.h>
#include <luajit.h>
#include <lauxlib.h>
#include <lualib.h>
#include <lua.h>
}

View file

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

View file

@ -1,6 +1,7 @@
#include "instance.h"
#include "common.h"
#include "datatypes/meta.h"
#include "datatypes/primitives.h"
#include "datatypes/variant.h"
#include "datatypes/base.h"
#include "datatypes/ref.h"
#include "error/instance.h"
@ -11,6 +12,7 @@
#include "logger.h"
#include "panic.h"
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <memory>
@ -27,6 +29,7 @@ const InstanceType Instance::TYPE = {
.className = "Instance",
.constructor = NULL, // Instance is abstract and therefore not creatable
.explorerIcon = "instance",
.flags = 0
};
// 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);
AncestryChanged->Fire({updatedChild.has_value() ? Data::InstanceRef(updatedChild.value()) : Data::InstanceRef(), newParent.has_value() ? Data::InstanceRef(newParent.value()) : Data::InstanceRef()});
AncestryChanged->Fire({updatedChild.has_value() ? InstanceRef(updatedChild.value()) : InstanceRef(), newParent.has_value() ? InstanceRef(newParent.value()) : InstanceRef()});
// Old workspace used to exist, and workspaces differ
if (!oldWorkspace.expired() && oldWorkspace != _workspace) {
@ -109,7 +112,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
}
// Update ancestry in descendants
for (InstanceRef child : children) {
for (std::shared_ptr<Instance> child : children) {
child->updateAncestry(updatedChild, newParent);
}
}
@ -179,11 +182,13 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Properties
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
result<Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
name[0] = toupper(name[0]); // Ignore case of first character
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);
if (result.isSuccess() && sendUpdateEvent) {
InternalUpdateProperty(name);
@ -193,37 +198,38 @@ fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std:
}
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
name[0] = toupper(name[0]); // Ignore case of first character
return InternalGetPropertyMeta(name);
}
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
result<Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
if (name == "Name") {
return Data::Variant(Data::String(this->name));
return Variant(this->name);
} else if (name == "Parent") {
return Data::Variant(Data::InstanceRef(this->parent));
return Variant(InstanceRef(this->parent));
} else if (name == "ClassName") {
return Data::Variant(Data::String(GetClass()->className));
return Variant(GetClass()->className);
}
return MemberNotFound(GetClass()->className, name);
}
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
if (name == "Name") {
return PropertyMeta { &Data::String::TYPE };
return PropertyMeta { &STRING_TYPE, 0 };
} else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, };
return PropertyMeta { &InstanceRef::TYPE, PROP_NOSAVE };
} else if (name == "ClassName") {
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
return PropertyMeta { &STRING_TYPE, PROP_NOSAVE | PROP_READONLY };
}
return MemberNotFound(GetClass()->className, name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Data::Variant value) {
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Variant value) {
if (name == "Name") {
this->name = (std::string)value.get<Data::String>();
this->name = (std::string)value.get<std::string>();
} else if (name == "Parent") {
std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();
std::weak_ptr<Instance> ref = value.get<InstanceRef>();
SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
} else if (name == "ClassName") {
return AssignToReadOnlyMember(GetClass()->className, name);
@ -259,7 +265,8 @@ std::vector<std::string> Instance::GetProperties() {
// 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");
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");
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);
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(name).expect("Declared property is missing").get<InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef != "") {
// If the instance has already been remapped, set the new value
propertyNode.text().set(remappedRef);
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(propertyNode);
state->refsAwaitingRemap[ref] = refs;
}
} else {
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
}
}
// Remap self
std::string remappedId = "OB" + std::to_string(state->count++);
state->remappedInstances[shared_from_this()] = remappedId;
node.append_attribute("referent").set_value(remappedId);
// Remap queued properties
for (pugi::xml_node ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.text().set(remappedId);
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Add children
for (InstanceRef child : this->children) {
child->Serialize(node);
for (std::shared_ptr<Instance> child : this->children) {
child->Serialize(node, state);
}
}
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
result<std::shared_ptr<Instance>, NoSuchInstance> Instance::Deserialize(pugi::xml_node node, RefStateDeserialize state) {
if (state == nullptr) state = std::make_shared<__RefStateDeserialize>();
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
return NoSuchInstance(className);
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = INSTANCE_MAP[className]->constructor();
std::shared_ptr<Instance> object = INSTANCE_MAP[className]->constructor();
object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className);
@ -300,13 +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());
continue;
}
Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
auto meta = meta_.expect();
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
if (propertyNode.text().empty())
continue;
std::string refId = propertyNode.text().as_string();
auto remappedRef = state->remappedInstances[refId]; // TODO: I think this is okay? Maybe?? Add null check?
if (remappedRef) {
// If the instance has already been remapped, set the new value
object->SetPropertyValue(propertyName, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[refId];
refs.push_back(std::make_pair(object, propertyName));
state->refsAwaitingRemap[refId] = refs;
object->SetPropertyValue(propertyName, InstanceRef()).expect();
}
} else {
auto valueResult = Variant::Deserialize(propertyNode, meta.type);
if (valueResult.isError()) {
valueResult.logError();
continue;
}
auto value = valueResult.expect();
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
}
}
// Remap self
std::string remappedId = node.attribute("referent").value();
state->remappedInstances[remappedId] = object;
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[remappedId]) {
ref.first->SetPropertyValue(ref.second, InstanceRef(object)).expect();
}
state->refsAwaitingRemap[remappedId].clear();
// Read children
for (pugi::xml_node childNode : node.children("Item")) {
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
result<std::shared_ptr<Instance>, NoSuchInstance> child = Instance::Deserialize(childNode, state);
if (child.isError()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
@ -319,7 +405,7 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
// 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 _) {
// 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
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();
current = current->GetParent().value();
@ -362,7 +448,8 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
return *this;
}
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) {
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
if (state == nullptr) state = std::make_shared<__RefStateClone>();
std::shared_ptr<Instance> newInstance = GetClass()->constructor();
// Copy properties
@ -371,9 +458,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
// Update InstanceRef properties using map above
if (meta.type == &Data::InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
// Update std::shared_ptr<Instance> properties using map above
if (meta.type.descriptor == &InstanceRef::TYPE) {
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
if (refWeak.expired()) continue;
auto ref = refWeak.lock();
@ -381,17 +468,17 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
if (remappedRef) {
// If the instance has already been remapped, set the new value
newInstance->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
newInstance->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
} else {
// Otheriise, queue this property to be updated later, and keep its current value
auto& refs = state->refsAwaitingRemap[ref];
refs.push_back(std::make_pair(newInstance, property));
state->refsAwaitingRemap[ref] = refs;
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
newInstance->SetPropertyValue(property, InstanceRef(ref)).expect();
}
} else {
Data::Variant value = GetPropertyValue(property).expect();
Variant value = GetPropertyValue(property).expect();
newInstance->SetPropertyValue(property, value).expect();
}
}
@ -401,8 +488,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
// Remap queued properties
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
ref.first->SetPropertyValue(ref.second, InstanceRef(newInstance)).expect();
}
state->refsAwaitingRemap[shared_from_this()].clear();
// Clone children
for (std::shared_ptr<Instance> child : GetChildren()) {
@ -421,9 +509,9 @@ std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetRefe
for (std::string property : propertyNames) {
PropertyMeta meta = GetPropertyMeta(property).expect();
if (meta.type != &Data::InstanceRef::TYPE) continue;
if (meta.type.descriptor != &InstanceRef::TYPE) continue;
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<Data::InstanceRef>();
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<InstanceRef>();
if (ref.expired()) continue;
referenceProperties.push_back(std::make_pair(property, ref.lock()));
}

View file

@ -42,8 +42,6 @@ struct InstanceType {
InstanceFlags flags;
};
typedef std::pair<std::shared_ptr<Instance>, std::string> _RefStatePropertyCell;
class DescendantsIterator;
class JointInstance;
@ -71,8 +69,8 @@ protected:
Instance(const InstanceType*);
virtual ~Instance();
virtual result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value);
virtual result<Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Variant value);
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name);
virtual void InternalUpdateProperty(std::string name);
virtual std::vector<std::string> InternalGetProperties();
@ -116,8 +114,8 @@ public:
std::string GetFullName();
// Properties
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Data::Variant value, bool sendUpdateEvent = true);
result<Variant, MemberNotFound> GetPropertyValue(std::string name);
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent = true);
result<PropertyMeta, MemberNotFound> GetPropertyMeta(std::string name);
// Manually trigger the update of a property. Useful internally when setting properties directly
void UpdateProperty(std::string name);
@ -135,14 +133,11 @@ public:
}
// Serialization
void Serialize(pugi::xml_node parent);
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
std::optional<std::shared_ptr<Instance>> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>());
void Serialize(pugi::xml_node parent, RefStateSerialize state = {});
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node, RefStateDeserialize state = {});
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
class DescendantsIterator {
public:
@ -154,7 +149,7 @@ public:
typedef int difference_type;
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 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;
struct PropertyMeta {
const Data::TypeInfo* type;
const TypeMeta type;
PropertyFlags flags;
PropertyCategory category = PROP_CATEGORY_DATA;
};

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

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

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

View file

@ -13,7 +13,7 @@
#include "objects/joint/jointinstance.h"
#include "objects/joint/snap.h"
#include "rendering/renderer.h"
#include "rendering/surface.h"
#include "enum/surface.h"
#include <cstdio>
#include <glm/common.hpp>
#include <memory>
@ -83,7 +83,7 @@ Vector3 Part::GetAABB() {
Vector3 min(0, 0, 0);
Vector3 max(0, 0, 0);
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);
}
@ -120,7 +120,7 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
case Front: return frontSurface;
case Back: return backSurface;
}
return SurfaceSmooth; // Unreachable
return SurfaceType::Smooth; // Unreachable
}
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) {
if (a == SurfaceWeld || b == SurfaceWeld || a == SurfaceGlue || b == SurfaceGlue) return Weld::New();
if ((a == SurfaceStuds && (b == SurfaceInlets || b == SurfaceUniversal))
|| (a == SurfaceInlets && (b == SurfaceStuds || b == SurfaceUniversal))
|| (a == SurfaceUniversal && (b == SurfaceStuds || b == SurfaceInlets || b == SurfaceUniversal)))
if (a == SurfaceType::Weld || b == SurfaceType::Weld || a == SurfaceType::Glue || b == SurfaceType::Glue) return Weld::New();
if ((a == SurfaceType::Studs && (b == SurfaceType::Inlet || b == SurfaceType::Universal))
|| (a == SurfaceType::Inlet && (b == SurfaceType::Studs || b == SurfaceType::Universal))
|| (a == SurfaceType::Universal && (b == SurfaceType::Studs || b == SurfaceType::Inlet || b == SurfaceType::Universal)))
return Snap::New();
if (a == SurfaceHinge)
if (a == SurfaceType::Hinge)
return Rotate::New();
if (a == SurfaceMotor)
if (a == SurfaceType::Motor)
return RotateV::New();
return std::nullopt;
}
@ -224,7 +224,7 @@ void Part::MakeJoints() {
// TEMPORARY
// TODO: Use more efficient algorithm to *actually* find nearby parts)
for (auto it = workspace().value()->GetDescendantsStart(); it != workspace().value()->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
std::shared_ptr<Instance> obj = *it;
if (obj == shared_from_this()) continue; // Skip ourselves
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Part> otherPart = obj->CastTo<Part>().expect();
@ -251,7 +251,7 @@ void Part::MakeJoints() {
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
// 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
// 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/vector.h"
#include "objects/base/instance.h"
#include "rendering/surface.h"
#include "enum/surface.h"
#include <optional>
#include <reactphysics3d/reactphysics3d.h>
#include <vector>
@ -18,9 +18,9 @@ namespace rp = reactphysics3d;
// For easy construction from C++. Maybe should be removed?
struct PartConstructParams {
glm::vec3 position;
glm::vec3 rotation;
glm::vec3 size;
Vector3 position;
Vector3 rotation;
Vector3 size;
Color3 color;
bool anchored = false;
@ -58,7 +58,9 @@ public:
CFrame cframe;
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 Color3 color;
@ -70,12 +72,12 @@ public:
DEF_PROP bool locked = false;
DEF_PROP_CATEGORY(SURFACE)
DEF_PROP SurfaceType topSurface = SurfaceType::SurfaceStuds;
DEF_PROP SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
DEF_PROP SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType backSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType topSurface = SurfaceType::Studs;
DEF_PROP SurfaceType bottomSurface = SurfaceType::Inlet;
DEF_PROP SurfaceType leftSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType rightSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType frontSurface = SurfaceType::Smooth;
DEF_PROP SurfaceType backSurface = SurfaceType::Smooth;
DEF_PROP_CATEGORY(SURFACE_INPUT)
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(PartConstructParams params) { return std::make_shared<Part>(params); };
static inline InstanceRef Create() { return std::make_shared<Part>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Part>(); };
inline Vector3 position() { return cframe.Position(); }

View file

@ -15,15 +15,7 @@ int script_wait(lua_State*);
int script_delay(lua_State*);
Script::Script(): Instance(&TYPE) {
source = "workspace.Part.Touched:Connect(function(otherPart)\n"
" 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\")";
source = "print(\"Hello, world!\")";
}
Script::~Script() {
@ -40,11 +32,14 @@ void Script::Run() {
// Initialize script globals
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");
Data::InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
lua_setfield(Lt, -2, "workspace");
lua_pushlightuserdata(Lt, scriptContext.get());
@ -117,6 +112,8 @@ int script_delay(lua_State* L) {
luaL_checktype(L, 2, LUA_TFUNCTION);
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_xmove(L, Lt, 1); // move func
lua_pop(L, 1); // pop secs

View file

@ -43,9 +43,9 @@ void ScriptContext::InitService() {
// luaopen_debug(state);
luaopen_bit(state);
Data::Vector3::PushLuaLibrary(state);
Data::CFrame::PushLuaLibrary(state);
Data::Color3::PushLuaLibrary(state);
Vector3::PushLuaLibrary(state);
CFrame::PushLuaLibrary(state);
Color3::PushLuaLibrary(state);
// TODO: custom os library
@ -94,7 +94,7 @@ void ScriptContext::PushThreadSleep(lua_State* thread, float delay) {
}
void ScriptContext::RunSleepingThreads() {
for (int i = 0; i < sleepingThreads.size();) {
for (size_t i = 0; i < sleepingThreads.size();) {
bool deleted = false;
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
lua_pop(L, 1); // pop result
if (i > 1) buf += '\t';
buf += str;
}

View file

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

View file

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

View file

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

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"
// 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 <sys/types.h>

View file

@ -4,11 +4,11 @@
template <typename T>
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())
|| (a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock();
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());
}
template <typename T>
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"
#pragma warning( disable : 4305 )
static float CUBE_VERTICES[] = {
// positions // normals // texture coords
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/color3.h"
#include "datatypes/vector.h"
#include "handles.h"
#include "math_helper.h"
#include "partassembly.h"
#include "rendering/torus.h"
#include "shader.h"
#include "mesh.h"
@ -26,7 +28,7 @@
#include "../common.h"
#include "../objects/part.h"
#include "skybox.h"
#include "surface.h"
#include "enum/surface.h"
#include "texture3d.h"
#include "renderer.h"
@ -50,8 +52,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
viewportWidth = width, viewportHeight = height;
glViewport(0, 0, width, height);
int argc = 1;
char* argv = const_cast<char*>("");
initMeshes();
glEnable(GL_DEPTH_TEST);
@ -131,7 +131,7 @@ void renderParts() {
// Sort by nearest
std::map<float, std::shared_ptr<Part>> sorted;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (part->transparency > 0.00001) {
@ -140,7 +140,7 @@ void renderParts() {
} else {
glm::mat4 model = part->cframe;
// 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("material", Material {
.diffuse = part->color,
@ -152,12 +152,12 @@ void renderParts() {
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
@ -170,7 +170,7 @@ void renderParts() {
std::shared_ptr<Part> part = it->second;
glm::mat4 model = part->cframe;
// 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("material", Material {
.diffuse = part->color,
@ -182,12 +182,12 @@ void renderParts() {
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
@ -225,20 +225,19 @@ void renderSurfaceExtras() {
ghostShader->set("viewPos", camera.cameraPos);
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (!inst->IsA("Part")) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
for (int i = 0; i < 6; i++) {
NormalId face = (NormalId)i;
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);
glm::mat4 model = CFrame::pointToward(surfaceCenter, part->cframe.Rotation() * normalFromFace(face));
model = glm::scale(model, glm::vec3(0.4,0.4,0.4));
ghostShader->set("model", model);
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
CYLINDER_MESH->bind();
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() {
if (!editorToolHandles.active) return;
auto assembly = PartAssembly::FromSelection();
if (assembly.bounds() == Vector3::ZERO) return;
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW); // This is right... Probably.....
@ -365,7 +367,7 @@ void renderAABB() {
ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = CFrame::IDENTITY + part->cframe.Position();
@ -405,7 +407,7 @@ void renderWireframe() {
wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = part->cframe;
@ -442,17 +444,19 @@ void renderOutlines() {
outlineShader->set("viewPos", camera.cameraPos);
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;
bool first = true;
int count = 0;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
std::shared_ptr<Instance> inst = *it;
if (inst->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (!part->selected) continue;
count++;
if (first)
min = part->position(), max = part->position();
first = false;
@ -471,7 +475,7 @@ void renderOutlines() {
}
// Render AABB of selected parts
if (first) return;
if (count <= 1) return;
glm::vec3 outlineSize, outlinePos;
outlineSize = (max - min);
@ -487,6 +491,41 @@ void renderOutlines() {
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() {
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
@ -517,14 +556,14 @@ void renderRotationArcs() {
// Pass in the camera position
handleShader->set("viewPos", camera.cameraPos);
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(getSelection()[0].lock());
PartAssembly assembly = PartAssembly::FromSelection();
for (HandleFace face : HandleFace::Faces) {
if (glm::any(glm::lessThan(face.normal, glm::vec3(0)))) continue;
glm::mat4 model = part->cframe * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
glm::mat4 model = assembly.assemblyOrigin() * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
handleShader->set("model", model);
float radius = glm::max(part->size.x, part->size.y, part->size.z) / 2.f + 2.f;
float radius = glm::max(assembly.bounds().X(), assembly.bounds().Y(), assembly.bounds().Z()) / 2.f + 2.f;
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
@ -599,6 +638,7 @@ void render(GLFWwindow* window) {
renderParts();
renderSurfaceExtras();
renderOutlines();
// renderSelectionAssembly();
renderRotationArcs();
if (wireframeRendering)
renderWireframe();

View file

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

View file

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

15
docs/qscintilla.md Normal file
View file

@ -0,0 +1,15 @@
In order to build openblocks on Windows, qscintilla will need to already be installed.
To do this, first download the source archive from [`https://www.riverbankcomputing.com/static/Downloads/QScintilla/2.14.1/QScintilla_src-2.14.1.tar.gz`](https://www.riverbankcomputing.com/static/Downloads/QScintilla/2.14.1/QScintilla_src-2.14.1.tar.gz)
Next, launch the *x64 Native Tools Command Prompt for VS 2022*, and cd into the directory that you extracted the archive to
Now, run `qmake` from your Qt's bin directory to configure it
Once that's done, build and install the project using `nmake install`
The library should now automatically be installed into your Qt installed directory
---
To uninstall the library, run `nmake uninstall`

View file

@ -69,6 +69,7 @@ endif()
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})
add_dependencies(editor openblocks)
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually
@ -99,6 +100,13 @@ if (WIN32)
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
# Also don't want to clutter with plugins, add only needed ones
# Copy over QScintilla DLLs
# TODO: Use a better approach?
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${QSCINTILLA_DLLS} $<TARGET_FILE_DIR:editor>
)
# Copy qt.conf to override default plugins location
add_custom_command(
TARGET editor POST_BUILD

View file

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

View file

@ -4,23 +4,27 @@
#include <qnamespace.h>
#include <qsoundeffect.h>
#include <string>
#include "./ui_mainwindow.h"
#include "mainglwidget.h"
#include "datatypes/vector.h"
#include "handles.h"
#include "logger.h"
#include "mainwindow.h"
#include "common.h"
#include "math_helper.h"
#include "objects/base/instance.h"
#include "partassembly.h"
#include "physics/util.h"
#include "rendering/renderer.h"
#include "rendering/shader.h"
#include "datatypes/meta.h"
#include "datatypes/variant.h"
#define PI 3.14159
#define M_mainWindow dynamic_cast<MainWindow*>(window())
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent), contextMenu(this) {
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setMouseTracking(true);
}
@ -113,12 +117,14 @@ CFrame snapCFrame(CFrame frame) {
return CFrame(frame.Position(), frame.Position() + closestVec1, closestVec2);
}
bool tryMouseContextMenu = false;
bool isMouseDragging = false;
std::weak_ptr<Part> draggingObject;
std::optional<HandleFace> draggingHandle;
Vector3 initialHitPos;
Vector3 initialHitNormal;
CFrame initialFrame;
PartAssembly initialAssembly({});
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging || draggingObject.expired() || mainWindow()->selectedTool >= TOOL_SMOOTH) return;
@ -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)
Vector3 dragStartHandleOffset;
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
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()));
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
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
@ -200,60 +209,64 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
glm::vec3 handlePoint, rb;
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
// Find new part position
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
// We transform the handlePoint to the handle's cframe, and get it's length (Z)
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
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor());
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");
// Apply snapping
if (snappingFactor() > 0) {
diff = round(diff / snappingFactor()) * snappingFactor();
absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
}
if (snappingFactor() != 0 && mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
playSound("./assets/excluded/switch.wav");
PartAssembly selectionAssembly = PartAssembly::FromSelection();
gWorkspace()->SyncPartPhysics(part);
part->UpdateProperty("Position");
part->UpdateProperty("Size");
sendPropertyUpdatedSignal(part, "Position", part->position());
sendPropertyUpdatedSignal(part, "Size", Vector3(part->size));
if (editorToolHandles.handlesType == MoveHandles) {
selectionAssembly.TransformBy(CFrame() + absDiff);
} else if (editorToolHandles.handlesType == ScaleHandles) {
if (evt->modifiers() & Qt::AltModifier) {
// 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)
@ -262,7 +275,6 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = getHandleAdornee();
// Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
@ -294,11 +306,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
gWorkspace()->SyncPartPhysics(part);
part->UpdateProperty("Rotation");
sendPropertyUpdatedSignal(part, "Rotation", part->cframe.ToEulerAnglesXYZ());
CFrame newFrame = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
initialAssembly.SetOrigin(newFrame);
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
@ -333,6 +342,7 @@ void MainGLWidget::wheelEvent(QWheelEvent* evt) {
}
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
tryMouseContextMenu = false;
handleCameraRotate(evt);
handleObjectDrag(evt);
handleCursorChange(evt);
@ -351,6 +361,7 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
}
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
tryMouseContextMenu = evt->button() == Qt::RightButton;
switch(evt->button()) {
// Camera drag
case Qt::RightButton: {
@ -366,17 +377,19 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
initialFrame = getHandleAdornee()->cframe;
initialAssembly = PartAssembly::FromSelection();
initialFrame = initialAssembly.assemblyOrigin();
isMouseDragging = true;
draggingHandle = handle;
startLinearTransform(evt);
return;
}
// raycast part
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);
if (part->locked) return;
if (part->locked) { setSelection({}); return; }
initialFrame = part->cframe;
initialHitPos = rayHit->worldPoint;
initialHitNormal = rayHit->worldNormal;
@ -407,10 +420,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
isMouseDragging = true;
draggingObject = part;
if (evt->modifiers() & Qt::ControlModifier) {
std::vector<InstanceRefWeak> currentSelection = getSelection();
for (int i = 0; i < currentSelection.size(); i++) {
InstanceRefWeak inst = currentSelection[i];
if (!inst.expired() && inst.lock() == part) {
std::vector<std::shared_ptr<Instance>> currentSelection = getSelection();
for (size_t i = 0; i < currentSelection.size(); i++) {
std::shared_ptr<Instance> inst = currentSelection[i];
if (inst == part) {
currentSelection.erase(currentSelection.begin() + i);
goto skipAddPart;
}
@ -418,8 +431,8 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
currentSelection.push_back(part);
skipAddPart:
setSelection(currentSelection);
}else
setSelection(std::vector<InstanceRefWeak> { part });
} else
setSelection({ part });
// Disable bit so that we can ignore the part while raycasting
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
@ -435,6 +448,23 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
isMouseDragging = false;
draggingObject = {};
draggingHandle = std::nullopt;
// Open context menu
if (tryMouseContextMenu)
contextMenu.exec(QCursor::pos());
tryMouseContextMenu = false;
}
void MainGLWidget::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionCopy);
contextMenu.addAction(M_mainWindow->ui->actionCut);
contextMenu.addAction(M_mainWindow->ui->actionPaste);
contextMenu.addAction(M_mainWindow->ui->actionPasteInto);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionSaveModel);
contextMenu.addAction(M_mainWindow->ui->actionInsertModel);
}
static int moveZ = 0;
@ -478,11 +508,11 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
if (evt->key() == Qt::Key_O)
Logger::error("error message");
if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired())
getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace());
if (evt->key() == Qt::Key_C && getSelection().size() > 0)
getSelection()[0]->Clone().value()->SetParent(gWorkspace());
if (evt->key() == Qt::Key_H && getSelection().size() > 0 && !getSelection()[0].expired())
Logger::infof("Object at: 0x%x\n", getSelection()[0].lock().get());
if (evt->key() == Qt::Key_H && getSelection().size() > 0)
Logger::infof("Object at: 0x%x\n", getSelection()[0].get());
}
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
@ -501,4 +531,4 @@ float MainGLWidget::snappingFactor() {
case GridSnappingMode::SNAP_OFF: return 0;
}
return 0;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more