Compare commits

...

23 commits

Author SHA1 Message Date
ec65c6eddc fix(autogen): set property function return error even when value does get set 2025-04-26 15:12:46 +02:00
b9c8022f6f fix(datatypes): instance ref serialized to wrong name 2025-04-26 15:12:22 +02:00
76554c8295 fix(autogen): syntax errors in codegen 2025-04-26 14:44:02 +02:00
c578b7361c chore: docs for autogen 2025-04-26 14:38:20 +02:00
0c3c6f43fc feat(autogen): better integration into cmake and replaced caching 2025-04-26 13:46:51 +02:00
a2e2210998 feat(autogen): convert classes to new autogen annotations 2025-04-26 13:19:47 +02:00
b8ee828d29 feat(autogen): add meta and cframe 2025-04-26 11:54:53 +02:00
8f20c11b36 feat(autogen): integrate autogen and add method for setters 2025-04-26 11:04:50 +02:00
8049d45b43 feat(autogen): generate basis for instance 2025-04-26 02:14:39 +02:00
85e1efe5b3 feat(autogen): analyze prop flags 2025-04-26 01:53:00 +02:00
10c78cd647 feat(autogen): analyze instance flags 2025-04-26 01:41:36 +02:00
febde86430 feat(autogen): scan for props 2025-04-26 01:16:44 +02:00
99a8cbe957 feat(autogen): basic autogen starter 2025-04-25 23:35:10 +02:00
28ddfed8b3 chore: added some attribution 2025-04-25 20:00:57 +02:00
9be6c103de feat(lua): deserializing values and setting instance properties 2025-04-25 11:32:11 +02:00
cbed2bac95 feat(lua): added instance property and child access via reference 2025-04-25 10:19:11 +02:00
6bb1d8b3a4 feat(datatype): added conversion function for lua objects 2025-04-25 02:14:30 +02:00
d8ffd574f5 misc: removed vestigial header file from ages ago 2025-04-25 02:02:25 +02:00
4bd1110202 fix(lua): better print function, and override require 2025-04-25 02:01:52 +02:00
99440cc3ee feat(script): added code to actually run lua code 2025-04-24 20:43:19 +02:00
c081469a49 feat(lua): added basis for lua scripting 2025-04-24 17:08:22 +02:00
4cfb327b65 feat(script): added source property 2025-04-24 16:33:34 +02:00
1858c703c7 feat(editor): added script document 2025-04-24 15:31:59 +02:00
71 changed files with 1669 additions and 386 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
/bin/
/lib/
/build/
/autogen/build
# Qt
/*.pro.user*

8
.vscode/launch.json vendored
View file

@ -11,6 +11,14 @@
"program": "${workspaceFolder}/build/bin/editor",
"args": [],
"cwd": "${workspaceFolder}",
},
{
"type": "lldb",
"request": "launch",
"name": "Debug Autogen",
"program": "${workspaceFolder}/autogen/build/autogen",
"args": ["../core/src", "../core/src/objects", "build/generated"],
"cwd": "${workspaceFolder}/autogen",
}
]
}

View file

@ -5,6 +5,8 @@ set(OpenGL_GL_PREFERENCE "GLVND")
set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" )
add_subdirectory(autogen)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )

View file

@ -2,4 +2,22 @@
[ Docs are work in progress! ]
For build instructions, see [BUILD.md](./BUILD.md)
For build instructions, see [BUILD.md](./BUILD.md)
## Credits and Attribution
### Libraries
* SDL2
* OpenGL
* glm
* ReactPhysics3D by Daniel Chappuis
* Dirent by Toni Ronkko
### Materials and Resources
* LearnOpenGL
### Assets
* Skybox by Jauhn Dabz

13
autogen/CMakeLists.txt Normal file
View file

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.30.0)
find_package(Clang REQUIRED)
add_executable(autogen
src/main.cpp
src/util.cpp
src/analysis.cpp
src/codegen.cpp
)
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
target_link_libraries(autogen -lclang)
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIRS})

299
autogen/src/analysis.cpp Normal file
View file

@ -0,0 +1,299 @@
#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>
#include <filesystem>
namespace fs = std::filesystem;
// Very simple parser
// Example format:
// name="Hello!", world=Test, read_only
// Result:
// "name": "Hello!", "world": "Test", "read_only": ""
std::map<std::string, std::string> parseAnnotationString(std::string src) {
std::map<std::string, std::string> result;
std::string currentIdent = "";
std::string currentValue = "";
int stage = 0;
bool quoted = false;
int 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] == '_')) {
currentIdent += src[i];
stage = 1;
continue;
}
if (stage == 1 && src[i] == '=') { // What follows is a value
stage = 2;
continue;
}
if (stage == 1 && src[i] == ',') { // Value-less key
stage = 0;
result[currentIdent] = "";
currentIdent = "";
continue;
}
if (stage == 2 && quoted && src[i] == '"') { // Close a quoted string
quoted = false;
continue;
}
if (stage == 2 && !quoted && src[i] == '"') { // Start a quoted string
quoted = true;
continue;
}
if (stage == 2 && !quoted && (src[i] == ' ' || src[i] == ',')) { // Terminate the string
stage = 0;
result[currentIdent] = currentValue;
currentIdent = "";
currentValue = "";
continue;
}
if (stage == 2) { // Otherwise if in stage 2, simply add the character
currentValue += src[i];
continue;
}
fprintf(stderr, "Unexpected symbol: %c at index %d\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();
}
// Add the remaining value
if (stage == 1) {
result[currentIdent] = "";
} else if (stage == 2) {
result[currentIdent] = currentValue;
}
return result;
}
bool findInstanceAnnotation(CXCursor cur) {
bool found = false;
// Look for annotations in the class itself
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
// if (annString == "OB::INSTANCE") found = true;
if (annString == "OB::def_inst") found = true;
return CXChildVisit_Break;
});
return found;
}
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName) {
std::optional<std::string> ret = std::nullopt;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
if (annString != annotationName) return CXChildVisit_Continue;
// Look inside for a StringLiteral
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
// if (kind != CXCursor_StringLiteral) return CXChildVisit_Recurse;
// String literals cannot be parsed as CXCursor_StringLiteral. I don't know why.
if (kind != CXCursor_UnexposedExpr) return CXChildVisit_Recurse;
// https://stackoverflow.com/a/63859988/16255372
auto res = clang_Cursor_Evaluate(cur);
ret = clang_EvalResult_getAsStr(res);
clang_EvalResult_dispose(res);
return CXChildVisit_Break;
});
return CXChildVisit_Break;
});
return ret;
}
std::string findBaseClass(CXCursor cur) {
std::string baseClass = "";
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_CXXBaseSpecifier) return CXChildVisit_Continue;
baseClass = x_clang_toString(clang_getCursorDisplayName(cur));
return CXChildVisit_Break;
});
return baseClass;
}
void processField(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_prop");
if (!propertyDef) return;
PropertyAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string fieldName = x_clang_toString(clang_getCursorDisplayName(cur));
anly.name = result["name"];
anly.fieldName = fieldName;
anly.category = result["category"];
anly.onUpdateCallback = result["on_update"];
if (result.count("hidden"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Hidden;
if (result.count("no_save"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_NoSave;
if (result.count("unit_float"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_UnitFloat;
if (result.count("readonly"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly;
CXType type = clang_getCursorType(cur);
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str();
state->properties.push_back(anly);
// For cframe, add member fields
std::optional<std::string> cframePositionDef = findAnnotation(cur, "OB::cframe_position_prop");
if (cframePositionDef) {
auto cframePosition = parseAnnotationString(cframePositionDef.value());
PropertyAnalysis cframeProp;
cframeProp.backingFieldType = anly.backingFieldType;
cframeProp.fieldName = anly.fieldName;
cframeProp.name = cframePosition["name"];
cframeProp.category = anly.category;
cframeProp.cframeMember = CFrameMember_Position;
cframeProp.onUpdateCallback = anly.onUpdateCallback;
cframeProp.flags = PropertyFlag_NoSave;
state->properties.push_back(cframeProp);
};
std::optional<std::string> cframeRotationDef = findAnnotation(cur, "OB::cframe_rotation_prop");
if (cframeRotationDef) {
auto cframeRotation = parseAnnotationString(cframeRotationDef.value());
PropertyAnalysis cframeProp;
cframeProp.backingFieldType = anly.backingFieldType;
cframeProp.fieldName = anly.fieldName;
cframeProp.name = cframeRotation["name"];
cframeProp.category = anly.category;
cframeProp.cframeMember = CFrameMember_Rotation;
cframeProp.onUpdateCallback = anly.onUpdateCallback;
cframeProp.flags = PropertyFlag_NoSave;
state->properties.push_back(cframeProp);
};
}
void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly;
// Find base class
std::string baseClass = findBaseClass(cur);
// Find header file
CXSourceLocation loc = clang_getCursorLocation(cur);
CXFile file;
unsigned line, column, off;
clang_getFileLocation(loc, &file, &line, &column, &off);
std::string headerName = x_clang_toString(clang_getFileName(file));
fs::path headerPath = fs::relative(headerName, srcRoot);
anly.name = className;
anly.baseClass = baseClass;
anly.headerPath = headerPath;
// Add misc flags and options
auto instanceDef = findAnnotation(cur, "OB::def_inst");
auto result = parseAnnotationString(instanceDef.value());
if (result.count("service"))
anly.flags = anly.flags | ClassFlag_Service | ClassFlag_NotCreatable;
if (result.count("not_creatable"))
anly.flags = anly.flags | ClassFlag_NotCreatable;
if (result.count("hidden"))
anly.flags = anly.flags | ClassFlag_Hidden;
anly.abstract = result.count("abstract") > 0;
anly.explorerIcon = result["explorer_icon"];
// Find annotated fields
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_FieldDecl) return CXChildVisit_Continue;
processField(cur, &anly);
return CXChildVisit_Continue;
});
state->classes[className] = anly;
}
// https://clang.llvm.org/docs/LibClang.html
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state) {
const char* cargs[] = { "-x", "c++", "-I", srcRoot.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,
path.c_str(), cargs, 5,
nullptr, 0,
CXTranslationUnit_None);
if (!unit) {
fprintf(stderr, "Failed to parse file\n");
return 1;
}
// Print errors
int ndiags = clang_getNumDiagnostics(unit);
for (int i = 0; i < ndiags; i++) {
CXDiagnostic diag = clang_getDiagnostic(unit, i);
CXString str = clang_formatDiagnostic(diag, 0);
fprintf(stderr, "diag: %s\n", clang_getCString(str));
clang_disposeString(str);
clang_disposeDiagnostic(diag);
}
CXCursor cursor = clang_getTranslationUnitCursor(unit);
bool flag = false;
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_ClassDecl) 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 (!findInstanceAnnotation(cur)) 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...
processClass(cur, state, className, srcRoot);
return CXChildVisit_Continue;
});
return true;
}

59
autogen/src/analysis.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <map>
#include <string>
#include <vector>
enum ClassFlags {
ClassFlag_NotCreatable = 1<<0,
ClassFlag_Service = 1<<1,
ClassFlag_Hidden = 1<<2,
};
enum PropertyFlags {
PropertyFlag_Hidden = 1 << 0,
PropertyFlag_NoSave = 1 << 1,
PropertyFlag_UnitFloat = 1 << 2,
PropertyFlag_Readonly = 1 << 3,
};
enum CFrameMember {
CFrameMember_None, // Not part of CFrame
CFrameMember_Position,
CFrameMember_Rotation
};
struct PropertyAnalysis {
std::string name;
std::string fieldName;
CFrameMember cframeMember = CFrameMember_None; // for cframe_position_prop etc.
std::string backingFieldType;
std::string onUpdateCallback;
std::string category;
PropertyFlags flags = (PropertyFlags)0;
};
// https://stackoverflow.com/a/1448478/16255372
inline ClassFlags operator|(ClassFlags a, ClassFlags b) {
return static_cast<ClassFlags>(static_cast<int>(a) | static_cast<int>(b));
}
inline PropertyFlags operator|(PropertyFlags a, PropertyFlags b) {
return static_cast<PropertyFlags>(static_cast<int>(a) | static_cast<int>(b));
}
struct ClassAnalysis {
std::string name;
std::string baseClass;
std::string headerPath;
bool abstract = false;
std::vector<PropertyAnalysis> properties;
ClassFlags flags = (ClassFlags)0;
std::string explorerIcon;
};
struct AnalysisState {
std::map<std::string, ClassAnalysis> classes;
};
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state);

228
autogen/src/codegen.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "codegen.h"
#include "analysis.h"
#include <map>
#include <string>
#include <variant>
std::map<std::string, std::string> CATEGORY_STR = {
{ "APPEARANCE", "PROP_CATEGORY_APPEARENCE" },
{ "DATA", "PROP_CATEGORY_DATA" },
{ "BEHAVIOR", "PROP_CATEGORY_BEHAVIOR" },
{ "PART", "PROP_CATEGORY_PART" },
{ "SURFACE", "PROP_CATEGORY_SURFACE" },
};
std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" },
{ "int", "Data::Int" },
{ "float", "Data::Float" },
{ "std::string", "Data::String" },
{ "glm::vec3", "Vector3" },
};
std::map<std::string, std::monostate> ENUM_TYPES = {
{ "SurfaceType", std::monostate() }
};
std::string parseWeakPtr(std::string weakPtrType) {
if (!weakPtrType.starts_with("std::weak_ptr")) return "";
int pos0 = weakPtrType.find("<");
int pos1 = weakPtrType.find(">");
std::string subtype = weakPtrType.substr(pos0+1, pos1-pos0-1);
return subtype;
}
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>()";
}
std::string mappedType = MAPPED_TYPE[fieldType];
return valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
}
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 + ")";
}
// InstanceRef
std::string subtype = parseWeakPtr(fieldType);
if (!subtype.empty()) {
return "Data::Variant(" + valueStr + ".expired() ? Data::InstanceRef() : Data::InstanceRef(std::dynamic_pointer_cast<Instance>(" + valueStr + ".lock())))";
}
std::string mappedType = MAPPED_TYPE[fieldType];
if (!mappedType.empty()) {
return mappedType + "(" + valueStr + ")";
}
return valueStr;
}
void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {";
out << "\n ";
bool first = true;
for (auto& prop : state.properties) {
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
// InstanceRef
std::string subtype = parseWeakPtr(prop.backingFieldType);
if (prop.flags & PropertyFlag_Readonly) {
out << "\n return AssignToReadOnlyMember(GetClass()->className, name)";
} else if (prop.cframeMember == CFrameMember_Position) {
out << "\n this->" << prop.fieldName << " = this->" << prop.fieldName << ".Rotation() + value.get<Vector3>();";
} 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>();"
<< "\n this->" << prop.fieldName << " = ref.expired() ? std::weak_ptr<" << subtype << ">() : std::dynamic_pointer_cast<" << subtype << ">(ref.lock());";
} else {
out << "\n this->" << prop.fieldName << " = " << castFromVariant("value", prop.backingFieldType) << ";";
if (!prop.onUpdateCallback.empty())
out << "\n " << prop.onUpdateCallback << "(name);";
}
out << "\n }";
first = false;
}
// If it's empty, just return the parent's impl
if (state.properties.empty()) {
out << "\n return " << state.baseClass << "::InternalSetPropertyValue(name, value);";
} else {
// Otherwise, add else and return
out << "else {";
out << "\n return " << state.baseClass << "::InternalSetPropertyValue(name, value);";
out << "\n }";
out << "\n return {};";
}
out << "\n};\n\n";
}
void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
out << "result<Data::Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
out << "\n ";
bool first = true;
for (auto& prop : state.properties) {
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
if (prop.cframeMember == CFrameMember_Position) {
out << "\n return Data::Variant(" << prop.fieldName << ".Position());";
} else if (prop.cframeMember == CFrameMember_Rotation) {
out << "\n return Data::Variant(" << prop.fieldName << ".ToEulerAnglesXYZ());";
} else {
out << "\n return Data::Variant(" << castToVariant(prop.fieldName, prop.backingFieldType) << ");";
}
out << "\n }";
first = false;
}
out << "\n return " << state.baseClass << "::InternalGetPropertyValue(name);";
// out << "\n return MemberNotFound(\"" << state.name << "\", name);";
out << "\n};\n\n";
}
void writePropertiesList(std::ofstream& out, ClassAnalysis state) {
out << "std::vector<std::string> " << state.name << "::InternalGetProperties() {\n";
out << " std::vector<std::string> properties = " << state.baseClass << "::InternalGetProperties();\n";
for (auto& prop : state.properties) {
out << " properties.push_back(\"" << prop.name << "\");\n";
}
out << " return properties;\n";
out << "};\n\n";
}
void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
out << "result<PropertyMeta, MemberNotFound> " << state.name << "::InternalGetPropertyMeta(std::string name) {";
out << "\n ";
bool first = true;
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 strFlags;
if (prop.flags & PropertyFlag_Readonly)
strFlags += " | PROP_READONLY";
if (prop.flags & PropertyFlag_Hidden)
strFlags += " | PROP_HIDDEN";
if (prop.flags & PropertyFlag_NoSave)
strFlags += " | PROP_NOSAVE";
if (prop.flags & PropertyFlag_UnitFloat)
strFlags += " | PROP_UNIT_FLOAT";
if (!strFlags.empty()) strFlags = strFlags.substr(3); // Remove leading pipe
else strFlags = "0"; // 0 == No option
std::string category = CATEGORY_STR[prop.category];
if (category.empty()) category = "PROP_CATEGORY_DATA";
out << "\n return PropertyMeta { &" << type << "::TYPE, " << strFlags << ", " << category << " };";
out << "\n }";
first = false;
}
out << "\n return " << state.baseClass << "::InternalGetPropertyMeta(name);";
// out << "\n return MemberNotFound(\"" << state.name << "\", name);";
out << "\n};\n\n";
}
void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
std::string strFlags;
if (state.flags & ClassFlag_NotCreatable)
strFlags += " | INSTANCE_NOTCREATABLE";
if (state.flags & ClassFlag_Service)
strFlags += " | INSTANCE_SERVICE";
if (state.flags & ClassFlag_Hidden)
strFlags += " | INSTANCE_HIDDEN";
if (!strFlags.empty()) strFlags = strFlags.substr(3); // Remove leading pipe
else strFlags = "0"; // 0 == No option
out << "/////////////////////////////////////////////////////////////////////////////////////////\n";
out << "// This file was automatically generated by autogen, and should not be edited manually //\n";
out << "/////////////////////////////////////////////////////////////////////////////////////////\n\n";
std::string constructorStr;
if (state.abstract) constructorStr = "nullptr";
else constructorStr = "&" + state.name + "::Create";
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
out << "#include \"" << state.headerPath << "\"\n\n";
out << "const InstanceType " << state.name << "::TYPE = {\n"
<< " .super = &" << state.baseClass << "::TYPE,\n"
<< " .className = \"" << state.name << "\",\n"
<< " .constructor = " << constructorStr << ",\n"
<< " .explorerIcon = \"" << state.explorerIcon << "\",\n"
<< " .flags = " << strFlags << ",\n"
<< "};\n\n";
out << "const InstanceType* " << state.name << "::GetClass() {\n"
<< " return &TYPE;\n"
<< "};\n\n";
// Special case for our Service class
if (state.baseClass == "Service") state.baseClass = "Instance";
writePropertySetHandler(out, state);
writePropertyGetHandler(out, state);
writePropertyMetaHandler(out, state);
writePropertiesList(out, state);
}

6
autogen/src/codegen.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "analysis.h"
#include <fstream>
void writeCodeForClass(std::ofstream& out, ClassAnalysis& state);

42
autogen/src/main.cpp Normal file
View file

@ -0,0 +1,42 @@
#include <clang-c/CXDiagnostic.h>
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/CXString.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <fstream>
#include <filesystem>
#include "analysis.h"
#include "codegen.h"
namespace fs = std::filesystem;
int main(int argc, char** argv) {
if (argc < 4) {
fprintf(stderr, "Usage: autogen <src-root> <src-file> <out-dir>\n");
return 1;
}
AnalysisState state;
fs::path srcRoot = argv[1];
fs::path srcPath = argv[2];
fs::path outPath = argv[3];
fs::path relpath = fs::relative(srcPath, srcRoot);
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
analyzeClasses(srcPath, srcRoot, &state);
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);
for (auto& [_, clazz] : state.classes) {
writeCodeForClass(outStream, clazz);
}
outStream.close();
return 0;
}

17
autogen/src/util.cpp Normal file
View file

@ -0,0 +1,17 @@
#include "util.h"
#include <clang-c/CXString.h>
static CXChildVisitResult _visitorFunc(CXCursor cursor, CXCursor parent, CXClientData client_data) {
X_CXCursorVisitor* func = (X_CXCursorVisitor*)client_data;
return (*func)(cursor, parent);
}
unsigned x_clang_visitChildren(CXCursor parent, X_CXCursorVisitor visitor) {
return clang_visitChildren(parent, _visitorFunc, &visitor);
}
std::string x_clang_toString(CXString string) {
std::string str(clang_getCString(string));
clang_disposeString(string);
return str;
}

11
autogen/src/util.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <clang-c/Index.h>
#include <functional>
#include <string>
typedef std::function<CXChildVisitResult(CXCursor cursor, CXCursor parent)> X_CXCursorVisitor;
unsigned x_clang_visitChildren(CXCursor parent, X_CXCursorVisitor visitor);
std::string x_clang_toString(CXString string);

View file

@ -0,0 +1,32 @@
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
FIND_PATH(QSCINTILLA_INCLUDE_DIR
NAMES Qsci/qsciglobal.h
PATHS
${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS}
$ENV{LIB_DIR}/include
/usr/local/include
/usr/include
PATH_SUFFIXES ${QSCINTILLA_PATH_SUFFIXES}
)
set(QSCINTILLA_LIBRARY_NAMES
qscintilla2-qt${QT_VERSION_MAJOR}
qscintilla2_qt${QT_VERSION_MAJOR}
libqt${QT_VERSION_MAJOR}scintilla2
libqscintilla2-qt${QT_VERSION_MAJOR}
qt${QT_VERSION_MAJOR}scintilla2
libqscintilla2-qt${QT_VERSION_MAJOR}.dylib
qscintilla2
)
find_library(QSCINTILLA_LIBRARY
NAMES ${QSCINTILLA_LIBRARY_NAMES}
PATHS
"${QT_LIBRARY_DIR}"
$ENV{LIB_DIR}/lib
/usr/local/lib
/usr/local/lib/qt${QT_VERSION_MAJOR}
/usr/lib
)

View file

@ -11,11 +11,38 @@ find_package(pugixml 1.15 REQUIRED)
find_package(Stb REQUIRED)
include_directories(${Stb_INCLUDE_DIR})
# PkgConfig packages
find_package(PkgConfig REQUIRED)
pkg_check_modules(LUAJIT REQUIRED luajit)
# Run autogen
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/objects" "src/objects/*.h")
# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Custom%20Commands.html
foreach (SRC ${AUTOGEN_SOURCES})
string(REGEX REPLACE "[.]h$" ".cpp" OUT_SRC_NAME ${SRC})
set(SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/objects/${SRC}")
set(OUT_PATH "${CMAKE_BINARY_DIR}/generated/${OUT_SRC_NAME}")
add_custom_command(
OUTPUT "${OUT_PATH}"
DEPENDS "${SRC_PATH}"
COMMAND "${CMAKE_BINARY_DIR}/autogen/autogen" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}"
)
list(APPEND AUTOGEN_OUTS "${OUT_PATH}")
endforeach()
add_custom_target(autogen_build ALL
DEPENDS ${AUTOGEN_OUTS}
)
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_libraries(openblocks ${GLEW_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include")
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIR})
# Windows-specific dependencies
if(WIN32)

View file

@ -1,14 +0,0 @@
#pragma once
#include <variant>
#include "datatypes/primitives.h"
typedef std::variant<VoidData, BoolData, StringData, IntData, FloatData> DataValue;
enum class DataType {
VOID = 0,
BOOL = 1,
STRING = 2,
INT = 3,
FLOAT = 4
};

View file

@ -1,12 +1,19 @@
#include "base.h"
#include "error/data.h"
#include "meta.h"
#include <ios>
#include <sstream>
#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 }; \
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())); }
@ -32,6 +39,14 @@ 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")
@ -39,27 +54,50 @@ 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");
}
Data::Variant Data::Bool::Deserialize(pugi::xml_node node) {
return Data::Bool(node.text().as_bool());
}
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');
}
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);
@ -67,17 +105,6 @@ std::optional<Data::Variant> Data::Int::FromString(std::string string) {
return Data::Int(value);
}
const Data::String Data::Float::ToString() const {
std::stringstream stream;
stream << std::noshowpoint << value;
return Data::String(stream.str());
}
Data::Variant Data::Float::Deserialize(pugi::xml_node node) {
return Data::Float(node.text().as_float());
}
std::optional<Data::Variant> Data::Float::FromString(std::string string) {
char* endPos;
float value = std::strtof(string.c_str(), &endPos);
@ -85,15 +112,50 @@ std::optional<Data::Variant> Data::Float::FromString(std::string string) {
return Data::Float(value);
}
const Data::String Data::String::ToString() const {
return *this;
}
Data::Variant Data::String::Deserialize(pugi::xml_node node) {
return Data::String(node.text().as_string());
}
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

@ -4,6 +4,10 @@
#include <functional>
#include <optional>
#include <pugixml.hpp>
#include "error/result.h"
#include "error/data.h"
extern "C" { typedef struct lua_State lua_State; }
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
const WRAPPED_TYPE value; \
@ -16,19 +20,24 @@ public: \
\
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); \
};
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;
struct TypeInfo {
std::string name;
Deserializer deserializer;
FromString fromString;
FromLuaValue fromLuaValue;
};
class String;
@ -38,6 +47,7 @@ namespace Data {
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 {
@ -49,7 +59,10 @@ namespace Data {
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)

View file

@ -145,4 +145,9 @@ Data::Variant Data::CFrame::Deserialize(pugi::xml_node node) {
node.child("R21").text().as_float(),
node.child("R22").text().as_float()
);
}
void Data::CFrame::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -35,6 +35,7 @@ namespace Data {
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node parent) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
operator glm::mat4() const;

View file

@ -1,5 +1,6 @@
#include "color3.h"
#include "meta.h" // IWYU pragma: keep
#include "panic.h"
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)) {};
@ -45,3 +46,9 @@ void Data::Color3::Serialize(pugi::xml_node node) const {
Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
return Color3::FromHex(node.text().get());
}
void Data::Color3::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -26,6 +26,7 @@ namespace Data {
virtual const Data::String ToString() const override;
std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
operator glm::vec3() const;

View file

@ -1,6 +1,7 @@
#include "meta.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include "datatypes/ref.h"
#include "logger.h"
#include "panic.h"
#include <variant>
@ -17,9 +18,15 @@ void Data::Variant::Serialize(pugi::xml_node node) const {
}, 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 instance: '%s'", node.name());
Logger::fatalErrorf("Unknown type for property: '%s'", node.name());
panic();
}
@ -36,4 +43,5 @@ std::map<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
{ "Vector3", &Data::Vector3::TYPE },
{ "CoordinateFrame", &Data::CFrame::TYPE },
{ "Color3", &Data::Color3::TYPE },
{ "Ref", &Data::InstanceRef::TYPE },
};

View file

@ -37,6 +37,7 @@ namespace Data {
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);
};

View file

@ -1,16 +1,22 @@
#include "datatypes/ref.h"
#include "color3.h"
#include "datatypes/base.h"
#include "error/data.h"
#include "logger.h"
#include "meta.h" // IWYU pragma: keep
#include <memory>
#include <optional>
#include "objects/base/instance.h"
#include "lua.h"
#include "objects/base/member.h"
Data::InstanceRef::InstanceRef() {};
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
Data::InstanceRef::~InstanceRef() = default;
const Data::TypeInfo Data::InstanceRef::TYPE = {
.name = "Instance",
// .deserializer = &Data::InstanceRef::Deserialize,
.name = "Ref",
.deserializer = &Data::InstanceRef::Deserialize,
.fromLuaValue = &Data::InstanceRef::FromLuaValue,
};
const Data::TypeInfo& Data::InstanceRef::GetType() const { return Data::InstanceRef::TYPE; };
@ -29,6 +35,102 @@ void Data::InstanceRef::Serialize(pugi::xml_node node) const {
// node.text().set(this->ToHex());
}
// Data::Variant Color3::Deserialize(pugi::xml_node node) {
// return Color3::FromHex(node.text().get());
// }
Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) {
return Data::InstanceRef();
}
static int inst_gc(lua_State*);
static int inst_index(lua_State*);
static int inst_newindex(lua_State*);
static const struct luaL_Reg metatable [] = {
{"__gc", inst_gc},
{"__index", inst_index},
{"__newindex", inst_newindex},
{NULL, NULL} /* end of array */
};
void Data::InstanceRef::PushLuaValue(lua_State* L) const {
if (ref.expired()) return lua_pushnil(L);
int n = lua_gettop(L);
auto userdata = (std::shared_ptr<Instance>**)lua_newuserdata(L, sizeof(void*));
// Create new pointer, and assign userdata a pointer to it
std::shared_ptr<Instance>* ptr = new std::shared_ptr<Instance>(ref);
*userdata = ptr;
// Create the instance's metatable
luaL_newmetatable(L, "__mt_instance");
luaL_register(L, NULL, metatable);
lua_setmetatable(L, n+1);
}
result<Data::Variant, LuaCastError> Data::InstanceRef::FromLuaValue(lua_State* L, int idx) {
if (lua_isnil(L, idx))
return Data::Variant(Data::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));
}
static int inst_gc(lua_State* L) {
// Destroy the contained shared_ptr
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, -1);
delete *userdata;
lua_pop(L, 1);
return 0;
}
// __index(t,k)
static int inst_index(lua_State* L) {
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, 1);
std::shared_ptr<Instance> inst = **userdata;
std::string key(lua_tostring(L, 2));
lua_pop(L, 2);
// Read property
std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key);
if (meta) {
Data::Variant value = inst->GetPropertyValue(key).expect();
value.PushLuaValue(L);
return 1;
}
// Look for child
std::optional<std::shared_ptr<Instance>> child = inst->FindFirstChild(key);
if (child) {
Data::InstanceRef(child.value()).PushLuaValue(L);
return 1;
}
return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), inst->GetClass()->className.c_str());
}
// __newindex(t,k,v)
static int inst_newindex(lua_State* L) {
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, 1);
std::shared_ptr<Instance> inst = **userdata;
std::string key(lua_tostring(L, 2));
// Validate property
std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key);
if (!meta)
return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), inst->GetClass()->className.c_str());
if (meta->flags & PROP_READONLY)
return luaL_error(L, "'%s' of %s is read-only", key.c_str(), inst->GetClass()->className.c_str());
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);
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;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "base.h"
#include "error/data.h"
#include <memory>
class Instance;
@ -20,6 +21,8 @@ namespace Data {
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override;
// static Data::Variant Deserialize(pugi::xml_node node);
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);
};
}

View file

@ -2,6 +2,7 @@
#include <glm/ext/quaternion_geometric.hpp>
#include <string>
#include "meta.h" // IWYU pragma: keep
#include "panic.h"
Data::Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
@ -100,4 +101,9 @@ std::optional<Data::Variant> Data::Vector3::FromString(std::string string) {
}
return Data::Vector3(components[0], components[1], components[2]);
}
void Data::Vector3::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -26,6 +26,7 @@ namespace Data {
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);

8
core/src/error/data.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "error.h"
class LuaCastError : public Error {
public:
inline LuaCastError(std::string sourceType, std::string targetType) : Error("InstanceCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
};

View file

@ -52,6 +52,13 @@ public:
}, error().value());
}
std::optional<std::string> errorMessage() {
if (isSuccess()) return std::nullopt;
return std::visit([&](auto&& it) {
return it.message();
}, error().value());
}
// Equivalent to .success
operator std::optional<T_Result>() { return success(); }
operator bool() { return isSuccess(); }

7
core/src/lua.h Normal file
View file

@ -0,0 +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>
}

View file

@ -0,0 +1,31 @@
#pragma once
// Markers for the autogen engine to generate getters, setters, lua, etc.
#ifdef __AUTOGEN__
#define def_inst(...) clang::annotate("OB::def_inst", #__VA_ARGS__)
#define INSTANCE [[ def_inst() ]]
#define INSTANCE_WITH(...) [[ def_inst(__VA_ARGS__) ]]
#define INSTANCE_SERVICE(...) [[ def_inst(__VA_ARGS__, service) ]]
#define def_prop(...) clang::annotate("OB::def_prop", #__VA_ARGS__)
#define cframe_position_prop(...) clang::annotate("OB::cframe_position_prop", #__VA_ARGS__)
#define cframe_rotation_prop(...) clang::annotate("OB::cframe_rotation_prop", #__VA_ARGS__)
#else
#define def_inst(...)
#define INSTANCE
#define INSTANCE_WITH(...)
#define INSTANCE_SERVICE(...)
#define def_prop(...)
#define cframe_position_prop(...)
#define cframe_rotation_prop(...)
#endif
#define AUTOGEN_PREAMBLE \
protected: \
result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name) override; \
fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value) override; \
result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name) override; \
std::vector<std::string> InternalGetProperties() override; \
private:

View file

@ -43,14 +43,6 @@ constexpr FieldCodec classNameCodec() {
Instance::Instance(const InstanceType* type) {
this->name = type->className;
this->memberMap = std::make_unique<MemberMap>( MemberMap {
.super = std::nullopt,
.members = {
{ "Name", { .backingField = &name, .type = &Data::String::TYPE, .codec = fieldCodecOf<Data::String, std::string>() } },
{ "ClassName", { .backingField = const_cast<InstanceType*>(type), .type = &Data::String::TYPE, .codec = classNameCodec(), .flags = (PropertyFlags)(PROP_READONLY | PROP_NOSAVE) } },
}
});
}
Instance::~Instance () {
@ -154,6 +146,14 @@ bool Instance::IsA(std::string className) {
return cur != nullptr;
}
std::optional<std::shared_ptr<Instance>> Instance::FindFirstChild(std::string name) {
for (auto child : children) {
if (child->name == name)
return child;
}
return std::nullopt;
}
static std::shared_ptr<Instance> DUMMY_INSTANCE;
DescendantsIterator Instance::GetDescendantsStart() {
return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE);
@ -186,67 +186,77 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Properties
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
auto meta_ = GetPropertyMeta(name);
if (!meta_) return MemberNotFound(GetClass()->className, name);
auto meta = meta_.expect();
return meta.codec.read(meta.backingField);
return InternalGetPropertyValue(name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value) {
auto meta_ = GetPropertyMeta(name);
if (!meta_) return MemberNotFound(GetClass()->className, name);
auto meta = meta_.expect();
if (meta.flags & PROP_READONLY) AssignToReadOnlyMember(GetClass()->className, name);
meta.codec.write(value, meta.backingField);
if (meta.updateCallback) meta.updateCallback.value()(name);
sendPropertyUpdatedSignal(shared_from_this(), name, value);
return {};
auto result = InternalSetPropertyValue(name, value);
if (result.isSuccess()) sendPropertyUpdatedSignal(shared_from_this(), name, value);
return result;
}
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
MemberMap* current = &*memberMap;
while (true) {
// We look for the property in current member map
auto it = current->members.find(name);
return InternalGetPropertyMeta(name);
}
// It is found, return it
if (it != current->members.end())
return it->second;
// It is not found, If there are no other maps to search, return null
if (!current->super.has_value())
return MemberNotFound(GetClass()->className, name);
// Search in the parent
current = current->super->get();
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
if (name == "Name") {
return Data::Variant(Data::String(this->name));
} else if (name == "Parent") {
return Data::Variant(Data::InstanceRef(this->parent));
} else if (name == "ClassName") {
return Data::Variant(Data::String(GetClass()->className));
}
return MemberNotFound(GetClass()->className, name);
}
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
if (name == "Name") {
return PropertyMeta { &Data::String::TYPE };
} else if (name == "Parent") {
return PropertyMeta { &Data::InstanceRef::TYPE, };
} else if (name == "ClassName") {
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
}
return MemberNotFound(GetClass()->className, name);
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Data::Variant value) {
if (name == "Name") {
this->name = (std::string)value.get<Data::String>();
} else if (name == "Parent") {
std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();
SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
} else if (name == "ClassName") {
return AssignToReadOnlyMember(GetClass()->className, name);
} else {
return MemberNotFound(GetClass()->className, name);
}
return {};
}
std::vector<std::string> Instance::InternalGetProperties() {
std::vector<std::string> members;
members.push_back("Name");
members.push_back("Parent");
members.push_back("ClassName");
return members;
}
void Instance::UpdateProperty(std::string name) {
PropertyMeta meta = GetPropertyMeta(name).expect();
if (!meta.updateCallback) return; // Nothing to update, exit.
meta.updateCallback.value()(name);
// TODO: temporary workaround because I'm too lazy to implement this in autogen
InternalSetPropertyValue(name, InternalGetPropertyValue(name).expect()).expect();
// PropertyMeta meta = GetPropertyMeta(name).expect();
// if (!meta.updateCallback) return; // Nothing to update, exit.
// meta.updateCallback.value()(name);
}
std::vector<std::string> Instance::GetProperties() {
if (cachedMemberList.has_value()) return cachedMemberList.value();
std::vector<std::string> memberList;
MemberMap* current = &*memberMap;
do {
for (auto const& [key, _] : current->members) {
// Don't add it if it's already in the list
if (std::find(memberList.begin(), memberList.end(), key) == memberList.end())
memberList.push_back(key);
}
if (!current->super.has_value())
break;
current = &*current->super.value();
} while (true);
std::vector<std::string> memberList = InternalGetProperties();
cachedMemberList = memberList;
return memberList;
@ -262,7 +272,7 @@ void Instance::Serialize(pugi::xml_node parent) {
pugi::xml_node propertiesNode = node.append_child("Properties");
for (std::string name : GetProperties()) {
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing");
if (meta.flags & (PropertyFlags::PROP_NOSAVE | PropertyFlags::PROP_READONLY)) continue; // This property should not be serialized. Skip...
if (meta.flags & (PROP_NOSAVE | PROP_READONLY)) continue; // This property should not be serialized. Skip...
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name);

View file

@ -25,9 +25,11 @@ class Workspace;
typedef int InstanceFlags;
// This instance should only be instantiated in special circumstances (i.e. by DataModel) and should be creatable directly via any API
const InstanceFlags INSTANCE_NOTCREATABLE = (InstanceFlags)0x1;
const InstanceFlags INSTANCE_NOTCREATABLE = (InstanceFlags)1<<0;
// This instance is a service
const InstanceFlags INSTANCE_SERVICE = (InstanceFlags)0x2;
const InstanceFlags INSTANCE_SERVICE = (InstanceFlags)1<<1;
// This instance should be hidden from the explorer
const InstanceFlags INSTANCE_HIDDEN = (InstanceFlags)1<<2;
// Struct describing information about an instance
struct InstanceType {
@ -63,11 +65,15 @@ private:
friend JointInstance; // This isn't ideal, but oh well
protected:
bool parentLocked = false;
std::unique_ptr<MemberMap> memberMap;
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<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name);
virtual std::vector<std::string> InternalGetProperties();
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
virtual void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
virtual void OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWorkspace, std::shared_ptr<Workspace> newWorkspace);
@ -100,6 +106,7 @@ public:
DescendantsIterator GetDescendantsEnd();
// Utility functions
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
std::optional<std::shared_ptr<Instance>> FindFirstChild(std::string);
// Properties
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);

View file

@ -44,12 +44,11 @@ std::function<void(std::string name)> memberFunctionOf(void(T::*func)(std::strin
return std::bind(func, obj, std::placeholders::_1);
}
enum PropertyFlags {
PROP_HIDDEN = 1 << 0, // Hidden from the editor
PROP_NOSAVE = 1 << 1, // Do not serialize
PROP_UNIT_FLOAT = 1 << 2, // Float between 0 and 1
PROP_READONLY = 1 << 3, // Read only property, do not write
};
typedef int PropertyFlags;
const PropertyFlags PROP_HIDDEN = 1 << 0; // Hidden from the editor
const PropertyFlags PROP_NOSAVE = 1 << 1; // Do not serialize
const PropertyFlags PROP_UNIT_FLOAT = 1 << 2; // Float between 0 and 1
const PropertyFlags PROP_READONLY = 1 << 3; // Read only property, do not write
enum PropertyCategory {
PROP_CATEGORY_APPEARENCE,
@ -62,17 +61,9 @@ enum PropertyCategory {
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE;
struct PropertyMeta {
void* backingField;
const Data::TypeInfo* type;
FieldCodec codec;
std::optional<std::function<void(std::string name)>> updateCallback;
PropertyFlags flags;
PropertyCategory category = PROP_CATEGORY_DATA;
};
typedef std::variant<PropertyMeta> MemberMeta;
struct MemberMap {
std::optional<std::unique_ptr<MemberMap>> super;
std::map<std::string, PropertyMeta> members;
};
typedef std::variant<PropertyMeta> MemberMeta;

View file

@ -17,4 +17,7 @@ void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent
}
void Service::InitService() {
}
void Service::OnRun() {
}

View file

@ -8,6 +8,7 @@ class Service : public Instance {
protected:
Service(const InstanceType* type);
virtual void InitService();
virtual void OnRun();
void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) override;

View file

@ -4,6 +4,7 @@
#include "objects/base/refstate.h"
#include "objects/base/service.h"
#include "objects/meta.h"
#include "objects/script/serverscriptservice.h"
#include "workspace.h"
#include "logger.h"
#include "panic.h"
@ -12,31 +13,24 @@
#include <memory>
#include <optional>
const InstanceType DataModel::TYPE = {
.super = &Instance::TYPE,
.className = "DataModel",
.constructor = nullptr,
};
const InstanceType* DataModel::GetClass() {
return &TYPE;
}
DataModel::DataModel()
: Instance(&TYPE) {
this->name = "Place";
}
void DataModel::Init() {
void DataModel::Init(bool runMode) {
// Create the workspace if it doesn't exist
if (this->services.count("Workspace") == 0) {
this->services["Workspace"] = std::make_shared<Workspace>();
AddChild(this->services["Workspace"]);
}
GetService<ServerScriptService>();
// Init all services
for (auto [_, service] : this->services) {
service->InitService();
if (runMode) service->OnRun();
}
}
@ -128,12 +122,13 @@ result<std::shared_ptr<Service>, NoSuchService> DataModel::GetService(std::strin
if (services.count(className) != 0)
return std::dynamic_pointer_cast<Service>(services[className]);
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
if (!INSTANCE_MAP[className] || ~(INSTANCE_MAP[className]->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) == 0) {
return NoSuchService(className);
}
services[className] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[className]->constructor());
AddChild(std::dynamic_pointer_cast<Instance>(services[className]));
services[className]->InitService();
return std::dynamic_pointer_cast<Service>(services[className]);
}

View file

@ -2,6 +2,7 @@
#include "error/instance.h"
#include "error/result.h"
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include "objects/base/refstate.h"
#include <memory>
@ -12,7 +13,8 @@ class DataModel;
class Service;
// The root instance to all objects in the hierarchy
class DataModel : public Instance {
class INSTANCE_WITH(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>);
@ -24,7 +26,7 @@ public:
std::optional<std::string> currentFile;
DataModel();
void Init();
void Init(bool runMode = false);
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
virtual const InstanceType* GetClass() override;

View file

@ -23,17 +23,6 @@ static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0,
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
const InstanceType Handles::TYPE = {
.super = &Instance::TYPE,
.className = "Handles",
// .constructor = &Workspace::Create,
// .explorerIcon = "",
};
const InstanceType* Handles::GetClass() {
return &TYPE;
}
Handles::Handles(): Instance(&TYPE) {
}

View file

@ -2,6 +2,7 @@
#include "base.h"
#include "datatypes/cframe.h"
#include "objects/annotation.h"
#include "objects/base/service.h"
#include "objects/part.h"
#include <array>
@ -30,7 +31,8 @@ enum HandlesType {
RotateHandles,
};
class Handles : public Instance {
class INSTANCE_WITH(abstract) Handles : public Instance {
AUTOGEN_PREAMBLE
public:
const static InstanceType TYPE;

View file

@ -11,42 +11,7 @@
#include <reactphysics3d/engine/PhysicsWorld.h>
#include "ptr_helpers.h"
const InstanceType JointInstance::TYPE = {
.super = &Instance::TYPE,
.className = "JointInstance",
};
const InstanceType* JointInstance::GetClass() {
return &TYPE;
}
JointInstance::JointInstance(const InstanceType* type): Instance(type) {
this->memberMap = std::make_unique<MemberMap>(MemberMap {
.super = std::move(this->memberMap),
.members = {
{ "Part0", {
.backingField = &part0,
.type = &Data::InstanceRef::TYPE,
.codec = fieldCodecOf<Data::InstanceRef, std::weak_ptr<Instance>>(),
.updateCallback = memberFunctionOf(&JointInstance::onUpdated, this),
}}, { "Part1", {
.backingField = &part1,
.type = &Data::InstanceRef::TYPE,
.codec = fieldCodecOf<Data::InstanceRef, std::weak_ptr<Instance>>(),
.updateCallback = memberFunctionOf(&JointInstance::onUpdated, this),
}}, { "C0", {
.backingField = &c0,
.type = &CFrame::TYPE,
.codec = fieldCodecOf<CFrame>(),
.updateCallback = memberFunctionOf(&JointInstance::onUpdated, this),
}}, { "C1", {
.backingField = &c1,
.type = &CFrame::TYPE,
.codec = fieldCodecOf<CFrame>(),
.updateCallback = memberFunctionOf(&JointInstance::onUpdated, this),
}},
}
});
}
JointInstance::~JointInstance() {

View file

@ -1,13 +1,21 @@
#pragma once
#include "objects/base/instance.h"
#include "../annotation.h"
#include <memory>
#include <optional>
//this is necessary ebcause we use std::weak_ptr<Part> without including it in this file
#ifdef __AUTOGEN_EXTRA_INCLUDES__
#include "../part.h"
#endif
class Part;
class Workspace;
class JointInstance : public Instance {
class INSTANCE_WITH(abstract) JointInstance : public Instance {
AUTOGEN_PREAMBLE
std::weak_ptr<Part> oldPart0;
std::weak_ptr<Part> oldPart1;
protected:
@ -23,9 +31,13 @@ protected:
public:
const static InstanceType TYPE;
[[ def_prop(name="Part0", on_update=onUpdated) ]]
std::weak_ptr<Part> part0;
[[ def_prop(name="Part1", on_update=onUpdated) ]]
std::weak_ptr<Part> part1;
[[ def_prop(name="C0", on_update=onUpdated) ]]
CFrame c0;
[[ def_prop(name="C1", on_update=onUpdated) ]]
CFrame c1;
JointInstance(const InstanceType*);

View file

@ -10,16 +10,6 @@
#include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
const InstanceType Snap::TYPE = {
.super = &JointInstance::TYPE,
.className = "Snap",
.constructor = &Snap::Create,
};
const InstanceType* Snap::GetClass() {
return &TYPE;
}
Snap::Snap(): JointInstance(&TYPE) {
}

View file

@ -1,10 +1,13 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include "objects/joint/jointinstance.h"
#include <memory>
class Snap : public JointInstance {
class INSTANCE Snap : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;
virtual void buildJoint() override;

View file

@ -10,16 +10,6 @@
#include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
const InstanceType Weld::TYPE = {
.super = &JointInstance::TYPE,
.className = "Weld",
.constructor = &Weld::Create,
};
const InstanceType* Weld::GetClass() {
return &TYPE;
}
Weld::Weld(): JointInstance(&TYPE) {
}

View file

@ -1,10 +1,13 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include "objects/joint/jointinstance.h"
#include <memory>
class Weld : public JointInstance {
class INSTANCE Weld : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;
virtual void buildJoint() override;

View file

@ -2,18 +2,6 @@
#include "workspace.h"
#include <memory>
const InstanceType JointsService::TYPE = {
.super = &Instance::TYPE,
.className = "JointsService",
.constructor = &JointsService::Create,
.flags = INSTANCE_NOTCREATABLE | INSTANCE_SERVICE,
};
const InstanceType* JointsService::GetClass() {
return &TYPE;
}
JointsService::JointsService(): Service(&TYPE) {
}
@ -24,7 +12,7 @@ void JointsService::InitService() {
initialized = true;
// Clear children before any new joints are added
for (std::shared_ptr<Instance> inst : dataModel().value()->GetService<JointsService>()->GetChildren()) {
for (std::shared_ptr<Instance> inst : GetChildren()) {
inst->Destroy();
}
}

View file

@ -1,8 +1,10 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/service.h"
class JointsService : public Service {
class INSTANCE_SERVICE() JointsService : public Service {
AUTOGEN_PREAMBLE
private:
std::optional<std::shared_ptr<Workspace>> jointWorkspace();
protected:

View file

@ -4,15 +4,23 @@
#include "objects/part.h"
#include "objects/joint/snap.h"
#include "objects/script.h"
#include "objects/script/scriptcontext.h"
#include "objects/script/serverscriptservice.h"
#include "objects/workspace.h"
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "Instance", &Instance::TYPE },
{ "Part", &Part::TYPE },
{ "Workspace", &Workspace::TYPE },
{ "DataModel", &DataModel::TYPE },
{ "Part", &Part::TYPE },
{ "Snap", &Snap::TYPE },
{ "JointInstance", &JointInstance::TYPE },
{ "JointsService", &JointsService::TYPE },
{ "Script", &Script::TYPE },
// Services
{ "Workspace", &Workspace::TYPE },
{ "JointsService", &JointsService::TYPE },
{ "ScriptContext", &ScriptContext::TYPE },
{ "ServerScriptService", &ServerScriptService::TYPE },
};

View file

@ -50,118 +50,12 @@ constexpr FieldCodec cframeRotationCodec() {
};
}
const InstanceType Part::TYPE = {
.super = &Instance::TYPE,
.className = "Part",
.constructor = &Part::CreateGeneric,
.explorerIcon = "part",
};
const InstanceType* Part::GetClass() {
return &TYPE;
}
Part::Part(): Part(PartConstructParams { .size = glm::vec3(2, 1.2, 4), .color = Color3(0.639216f, 0.635294f, 0.647059f) }) {
}
Part::Part(PartConstructParams params): Instance(&TYPE), cframe(CFrame::FromEulerAnglesXYZ((Vector3)params.rotation) + params.position),
size(params.size), color(params.color), anchored(params.anchored), locked(params.locked) {
this->memberMap = std::make_unique<MemberMap>(MemberMap {
.super = std::move(this->memberMap),
.members = {
{ "Anchored", {
.backingField = &anchored,
.type = &Data::Bool::TYPE,
.codec = fieldCodecOf<Data::Bool, bool>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.category = PROP_CATEGORY_BEHAVIOR,
}}, { "Locked", {
.backingField = &locked,
.type = &Data::Bool::TYPE,
.codec = fieldCodecOf<Data::Bool, bool>(),
.category = PROP_CATEGORY_BEHAVIOR,
}}, { "Position", {
.backingField = &cframe,
.type = &Vector3::TYPE,
.codec = cframePositionCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE
}}, { "Rotation", {
.backingField = &cframe,
.type = &Vector3::TYPE,
.codec = cframeRotationCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE
}}, { "Velocity", {
.backingField = &velocity,
.type = &Vector3::TYPE,
.codec = fieldCodecOf<Vector3>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
}}, { "CFrame", {
.backingField = &cframe,
.type = &CFrame::TYPE,
.codec = fieldCodecOf<CFrame>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
}}, { "Size", {
.backingField = &size,
.type = &Vector3::TYPE,
.codec = fieldCodecOf<Vector3, glm::vec3>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.category = PROP_CATEGORY_PART,
}}, { "Color", {
.backingField = &color,
.type = &Color3::TYPE,
.codec = fieldCodecOf<Color3>(),
.category = PROP_CATEGORY_APPEARENCE,
}}, { "Transparency", {
.backingField = &transparency,
.type = &Data::Float::TYPE,
.codec = fieldCodecOf<Data::Float, float>(),
.flags = PROP_UNIT_FLOAT,
.category = PROP_CATEGORY_APPEARENCE,
}},
// Surfaces
{ "TopSurface", {
.backingField = &topSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}}, { "BottomSurface", {
.backingField = &bottomSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}}, { "FrontSurface", {
.backingField = &frontSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}}, { "BackSurface", {
.backingField = &backSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}}, { "RightSurface", {
.backingField = &rightSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}}, { "LeftSurface", {
.backingField = &leftSurface,
.type = &Data::Int::TYPE, // Replace with enum
.codec = fieldCodecOf<Data::Int, int>(),
.flags = PROP_HIDDEN,
.category = PROP_CATEGORY_SURFACE,
}},
}
});
}
Part::~Part() {

View file

@ -11,6 +11,7 @@
#include <optional>
#include <reactphysics3d/reactphysics3d.h>
#include <vector>
#include "annotation.h"
namespace rp = reactphysics3d;
@ -27,7 +28,8 @@ struct PartConstructParams {
class Snap;
class Part : public Instance {
class INSTANCE_WITH(explorer_icon="part") Part : public Instance {
AUTOGEN_PREAMBLE
protected:
// Joints where this part is Part0
std::vector<std::weak_ptr<JointInstance>> primaryJoints;
@ -49,22 +51,35 @@ protected:
public:
const static InstanceType TYPE;
[[ def_prop(name="Velocity", on_update=onUpdated) ]]
Vector3 velocity;
[[ def_prop(name="CFrame", on_update=onUpdated), cframe_position_prop(name="Position"), cframe_rotation_prop(name="Rotation") ]]
CFrame cframe;
[[ def_prop(name="Size", category=PART, on_update=onUpdated) ]]
glm::vec3 size;
[[ def_prop(name="Color", category=APPEARANCE) ]]
Color3 color;
[[ def_prop(name="Transparency", category=APPEARANCE) ]]
float transparency = 0.f;
bool selected = false;
[[ def_prop(name="Anchored", category=BEHAVIOR, on_update=onUpdated) ]]
bool anchored = false;
[[ def_prop(name="Locked", category=BEHAVIOR) ]]
bool locked = false;
rp::RigidBody* rigidBody = nullptr;
[[ def_prop(name="TopSurface", category=SURFACE) ]]
SurfaceType topSurface = SurfaceType::SurfaceStuds;
[[ def_prop(name="BottomSurface", category=SURFACE) ]]
SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
[[ def_prop(name="LeftSurface", category=SURFACE) ]]
SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="RightSurface", category=SURFACE) ]]
SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="FrontSurface", category=SURFACE) ]]
SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="BackSurface", category=SURFACE) ]]
SurfaceType backSurface = SurfaceType::SurfaceSmooth;
Part();
@ -73,7 +88,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 CreateGeneric() { return std::make_shared<Part>(); };
static inline InstanceRef Create() { return std::make_shared<Part>(); };
virtual const InstanceType* GetClass() override;
inline Vector3 position() { return cframe.Position(); }

View file

@ -1,19 +1,42 @@
#include "script.h"
#include "logger.h"
#include "objects/base/instance.h"
const InstanceType Script::TYPE = {
.super = &Instance::TYPE,
.className = "Script",
.constructor = &Script::Create,
.explorerIcon = "script",
};
const InstanceType* Script::GetClass() {
return &TYPE;
}
#include "objects/base/member.h"
#include "objects/script/scriptcontext.h"
#include "objects/workspace.h"
#include "lua.h"
Script::Script(): Instance(&TYPE) {
source = "print \"Hello, world!\"";
}
Script::~Script() {
}
void Script::Run() {
lua_State* L = dataModel().value()->GetService<ScriptContext>()->state;
// Initialize script globals
lua_getglobal(L, "_G");
lua_pushstring(L, "game");
Data::InstanceRef(dataModel().value()).PushLuaValue(L);
lua_rawset(L, -3);
lua_pushstring(L, "workspace");
Data::InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(L);
lua_rawset(L, -3);
lua_pop(L, 1);
luaL_loadstring(L, source.c_str());
int status = lua_pcall(L, 0, LUA_MULTRET, 0);
if (status != 0) {
Logger::error(lua_tostring(L, -1));
lua_pop(L, 1);
}
}
void Script::Stop() {
// TODO:
}

View file

@ -1,15 +1,22 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include <memory>
class Script : public Instance {
class INSTANCE_WITH(explorer_icon="script") Script : public Instance {
AUTOGEN_PREAMBLE
public:
const static InstanceType TYPE;
Script();
~Script();
[[ def_prop(name="Source", hidden) ]]
std::string source;
void Run();
void Stop();
static inline std::shared_ptr<Script> New() { return std::make_shared<Script>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Script>(); };
virtual const InstanceType* GetClass() override;

View file

@ -0,0 +1,90 @@
#include "scriptcontext.h"
#include "logger.h"
#include <luajit-2.1/lauxlib.h>
#include <luajit-2.1/lua.h>
#include <luajit-2.1/lualib.h>
static int g_print(lua_State*);
static int g_require(lua_State*);
static const struct luaL_Reg luaglobals [] = {
{"print", g_print},
{"require", g_require},
{NULL, NULL} /* end of array */
};
std::string unsafe_globals[] = {
"loadfile", "loadstring", "load", "dofile", "getfenv", "setfenv"
};
ScriptContext::ScriptContext(): Service(&TYPE) {
}
ScriptContext::~ScriptContext() {
if (state)
lua_close(state);
}
void ScriptContext::InitService() {
if (initialized) return;
initialized = true;
state = luaL_newstate();
luaopen_base(state);
luaopen_math(state);
luaopen_string(state);
luaopen_table(state);
// luaopen_io(state);
// luaopen_os(state);
// luaopen_package(state);
// luaopen_debug(state);
luaopen_bit(state);
// TODO: custom os library
// Override print
// https://stackoverflow.com/a/4514193/16255372
lua_getglobal(state, "_G");
luaL_register(state, NULL, luaglobals);
// Remove misc dangerous functions
for (std::string key : unsafe_globals) {
lua_pushstring(state, key.c_str());
lua_pushnil(state);
lua_rawset(state, -3);
}
lua_pop(state, 1);
}
// https://www.lua.org/source/5.1/lbaselib.c.html
static int g_print(lua_State* L) {
std::string buf;
int nargs = lua_gettop(L);
lua_getglobal(L, "tostring");
for (int i=1; i <= nargs; i++) {
lua_pushvalue(L, -1); // push tostring
lua_pushvalue(L, i); // push current arg
lua_call(L, 1, 1); // call tostring with current arg (#1 arguments)
// lua_call automatically pops function and arguments
const char* str = lua_tostring(L, -1); // convert result into c-string
lua_pop(L, 1); // pop result
buf += str;
}
Logger::info(buf);
return 0;
}
static int g_require(lua_State* L) {
int nargs = lua_gettop(L);
if (nargs < 1) return luaL_error(L, "expected argument module");
return luaL_error(L, "require is not yet implemented");
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/service.h"
#include "lua.h"
class INSTANCE_SERVICE() ScriptContext : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;
bool initialized = false;
public:
const static InstanceType TYPE;
ScriptContext();
~ScriptContext();
lua_State* state;
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ScriptContext>(); };
virtual const InstanceType* GetClass() override;
};

View file

@ -0,0 +1,26 @@
#include "serverscriptservice.h"
#include "objects/script.h"
#include "objects/workspace.h"
ServerScriptService::ServerScriptService(): Service(&TYPE) {
}
ServerScriptService::~ServerScriptService() = default;
void ServerScriptService::InitService() {
if (initialized) return;
initialized = true;
}
void ServerScriptService::OnRun() {
auto workspace = dataModel().value()->GetService<Workspace>();
for (auto it = workspace->GetDescendantsStart(); it != workspace->GetDescendantsEnd(); it++) {
if (!it->IsA<Script>()) continue;
it->CastTo<Script>().expect()->Run();
}
for (auto it = GetDescendantsStart(); it != GetDescendantsEnd(); it++) {
if (!it->IsA<Script>()) continue;
it->CastTo<Script>().expect()->Run();
}
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/service.h"
// Container class for server scripts
// Also handles/manages running server scripts on run
class INSTANCE_SERVICE() ServerScriptService : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;
void OnRun() override;
bool initialized = false;
public:
const static InstanceType TYPE;
ServerScriptService();
~ServerScriptService();
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ServerScriptService>(); };
virtual const InstanceType* GetClass() override;
};

View file

@ -5,18 +5,6 @@
#include "physics/util.h"
#include <reactphysics3d/engine/PhysicsCommon.h>
const InstanceType Workspace::TYPE = {
.super = &Instance::TYPE,
.className = "Workspace",
.constructor = &Workspace::Create,
.explorerIcon = "workspace",
.flags = INSTANCE_NOTCREATABLE | INSTANCE_SERVICE,
};
const InstanceType* Workspace::GetClass() {
return &TYPE;
}
rp::PhysicsCommon* Workspace::physicsCommon = new rp::PhysicsCommon;
Workspace::Workspace(): Service(&TYPE) {

View file

@ -1,5 +1,6 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/service.h"
#include <memory>
#include <reactphysics3d/body/RigidBody.h>
@ -29,7 +30,9 @@ class Weld;
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
class Workspace : public Service {
class INSTANCE_SERVICE(explorer_icon="workspace") Workspace : public Service {
AUTOGEN_PREAMBLE
rp::PhysicsWorld* physicsWorld = nullptr;
static rp::PhysicsCommon* physicsCommon;

115
docs/autogen.md Normal file
View file

@ -0,0 +1,115 @@
# AUTOGEN
Autogen is a tool used in this project to automatically fill in reflection metadata, making it easy to interface with Instances dynamically at runtime.
The goal is to minimize manual boilerplate by having Autogen generate it for us. Currently, it is used to generate Instance metadata and property metadata.
In the future, it will also be used to generate Lua interfaces, etc.
## How to use
### In CMake
Autogen operates on a directory of files. First, collect all the header files in the directory you want to parse
# Run autogen
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/objects" "src/objects/*.h")
Then, for each source, derive an `OUT_PATH` wherein each file now ends in ".cpp" and is in the desired output directory.
Now, you want to run `add_custom_command` to call autogen with the sources root, source path, and output path for the generated file.
Don't forget to set the `OUTPUT` and `DEPENDS` properties of `add_custom_command`.
Then, add each `OUT_PATH` to a list, `AUTOGEN_OUTS`
foreach (SRC ${AUTOGEN_SOURCES})
string(REGEX REPLACE "[.]h$" ".cpp" OUT_SRC_NAME ${SRC})
set(SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/objects/${SRC}")
set(OUT_PATH "${CMAKE_BINARY_DIR}/generated/${OUT_SRC_NAME}")
add_custom_command(
OUTPUT "${OUT_PATH}"
DEPENDS "${SRC_PATH}"
COMMAND "${CMAKE_BINARY_DIR}/autogen/autogen" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}"
)
list(APPEND AUTOGEN_OUTS "${OUT_PATH}")
endforeach()
Finally, create a build target that depends on your `AUTOGEN_OUTS`, and make `ALL` depend on it
add_custom_target(autogen_build ALL
DEPENDS ${AUTOGEN_OUTS}
)
### In Code
Autogen is an annotation processor, it largely only interacts with C++ annotations (`[[ ... ]]`).
It uses libClang to parse through the header and look for classes annotated with `OB::def_inst`. In `annotation.h`, there are various aliases for this command:
- `INSTANCE` - Simply annotate with `def_inst`, do not do anything more. Equivalent to `[[ def_inst() ]]`
- `INSTANCE_WITH(...)` - Same as above, but allows you to pass in arguments to `def_inst`. Equivalent to `[[ def_inst(...) ]]`
- `INSTANCE_SERVICE(...)` - Same as above, but adds `, service` to the end of the argument list, marking the instance as a service.
Equivalent to `[[ def_inst(..., service) ]]`
Then, there is the command itself:
def_inst(abstract?, not_creatable?, service?, explorer_icon=?)
It comes with various parameters/flags:
- `abstract` - Flag, the class is a base class for other instances, and cannot/should not be constructed (Autogen will set `.constructor` to `nullptr`)
- `not_creatable` - Flag, the instance is not creatable via code/APIs, only internally. (Adds the `INSTANCE_NOTCREATABLE` flag)
- `service` - Flag, the instance is a service. Additionally, enable the `not_creatable` flag as well (Adds both `INSTANCE_NOTCREATABLE` and `INSTANCE_SERVICE` flags)
- `explorer_icon` - Option, string to pass into editor for the icon of the instance in the explorer
A file will be generated in `build/generated/<header>.cpp` with the `TYPE` automatically filled in, as well as an implementation for `GetClass()`
Note that it is also necessary to add `AUTOGEN_PREAMBLE` to the top of your class definition, to make sure that various functions for properties, etc. are
declared such that they can later be defined in the generated source file.
Properties are also a key component. Inside every class annotated with `def_inst`, the analyzer will scan for any `OB::def_prop` annotations as well.
def_prop(name=, hidden?, no_save?, unit_float?, readonly?, category=?, on_update=?)
Here are its parameters:
- `name` - Required parameter, this is the name of the property itself. The name of the field is not taken into consideration (subject to change)
- `hidden` - Flag, marks the property as hidden from the editor.
- `no_save` - Flag, the property should not be deserialized nor serialized
- `readonly` - Flag, the property cannot be assigned to
- `category` - Option, the category the property will appear in in the editor. Accepted values are: `DATA` (default), `APPEARANCE`, `PART`, `BEHAVIOR`, `SURFACE`
- `on_update` - Option, callback to call after the property has been assigned to. Should accept a std::string containing the property name and return void
The type of the property, and conversion to and from it and the datatype system is automatically inferred. `std::string` is interpreted as `Data::String`, and `std::weak_ptr<T>` is also converted to/from `Data::InstanceRef`. In the future, if weird edge-case types are introduced, the code generator may need to be extended. See [Extending Autogen](#extending-autogen)
In Part, it is necessary to expose the position and rotation components of the CFrame as properties, so there are two commands for this case (these should be added alongside `def_prop` on the CFrame property):
cframe_position_prop(name=)
cframe_rotation_prop(name=)
They simply create properties of the component with the specified name. They will inherit the category and update callback from the CFrame property, and will be flagged with NOSAVE. A getter/setter is automatically generated which generates a new CFrame with the specific component changed for you.
### Example
Here is an example of an instance annotated using Autogen
class INSTANCE Part {
AUTOGEN_PREAMBLE
public:
[[ def_prop(name="Color") ]]
Color3 color;
[[ def_prop(name="CFrame"), cframe_position_prop(name="Position") ]]
CFrame cframe;
[[ def_prop(name="ReadOnly", readonly) ]]
int readOnlyValue;
[[ def_prop(name="Ephemeral", no_save) ]]
std::string ephemeral;
[[ def_prop(name="SuperSecretValue", no_save, hidden) ]]
std::string superSecret;
};
# Extending Autogen
WIP.

View file

@ -13,7 +13,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(QScintilla)
set(TS_FILES editor_en_US.ts)
@ -30,8 +30,10 @@ set(PROJECT_SOURCES
panes/explorermodel.cpp
panes/propertiesview.h
panes/propertiesview.cpp
placedocument.cpp
placedocument.h
placedocument.cpp
script/scriptdocument.h
script/scriptdocument.cpp
${TS_FILES}
)
@ -63,8 +65,8 @@ else()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()
target_include_directories(editor PUBLIC "../core/src" "../include")
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia)
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})
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually

View file

@ -5,13 +5,16 @@
#include "objects/datamodel.h"
#include "objects/jointsservice.h"
#include "objects/joint/snap.h"
#include "objects/script.h"
#include "placedocument.h"
#include "script/scriptdocument.h"
#include <map>
#include <memory>
#include <qclipboard.h>
#include <qglobal.h>
#include <qmessagebox.h>
#include <qmimedata.h>
#include <qnamespace.h>
#include <qstylefactory.h>
#include <qstylehints.h>
#include <qmdisubwindow.h>
@ -36,17 +39,19 @@ inline bool isDarkMode() {
QtMessageHandler defaultMessageHandler = nullptr;
std::map<QtMsgType, Logger::LogLevel> QT_MESSAGE_TYPE_TO_LOG_LEVEL = {
{ QtMsgType::QtInfoMsg, Logger::LogLevel::INFO },
{ QtMsgType::QtSystemMsg, Logger::LogLevel::INFO },
{ QtMsgType::QtDebugMsg, Logger::LogLevel::DEBUG },
{ QtMsgType::QtWarningMsg, Logger::LogLevel::WARNING },
{ QtMsgType::QtCriticalMsg, Logger::LogLevel::ERROR },
{ QtMsgType::QtFatalMsg, Logger::LogLevel::FATAL_ERROR },
};
// std::map<QtMsgType, Logger::LogLevel> QT_MESSAGE_TYPE_TO_LOG_LEVEL = {
// { QtMsgType::QtInfoMsg, Logger::LogLevel::INFO },
// { QtMsgType::QtSystemMsg, Logger::LogLevel::INFO },
// { QtMsgType::QtDebugMsg, Logger::LogLevel::DEBUG },
// { QtMsgType::QtWarningMsg, Logger::LogLevel::WARNING },
// { QtMsgType::QtCriticalMsg, Logger::LogLevel::ERROR },
// { QtMsgType::QtFatalMsg, Logger::LogLevel::FATAL_ERROR },
// };
void logQtMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
Logger::log("[Qt] " + msg.toStdString(), QT_MESSAGE_TYPE_TO_LOG_LEVEL[type]);
// Logger::log("[Qt] " + msg.toStdString(), QT_MESSAGE_TYPE_TO_LOG_LEVEL[type]);
Logger::LogLevel logLevel = type == QtMsgType::QtFatalMsg ? Logger::LogLevel::FATAL_ERROR : Logger::LogLevel::DEBUG;
Logger::log("[Qt] " + msg.toStdString(), logLevel);
// if (defaultMessageHandler) defaultMessageHandler(type, context, msg);
}
@ -133,10 +138,17 @@ MainWindow::MainWindow(QWidget *parent)
// ui->explorerView->Init(ui);
placeDocument = new PlaceDocument(this);
placeDocument->setAttribute(Qt::WA_DeleteOnClose, true);
ui->mdiArea->addSubWindow(placeDocument);
ui->mdiArea->currentSubWindow()->showMaximized();
ui->mdiArea->findChild<QTabBar*>()->setExpanding(false);
placeDocument->init();
ui->mdiArea->setTabsClosable(true);
auto script = Script::New();
gWorkspace()->AddChild(script);
// ui->mdiArea->addSubWindow(new ScriptDocument(script));
}
void MainWindow::closeEvent(QCloseEvent* evt) {
@ -474,6 +486,14 @@ std::optional<std::string> MainWindow::openFileDialog(QString filter, QString de
return dialog.selectedFiles().front().toStdString();
}
void MainWindow::openScriptDocument(std::shared_ptr<Script> script) {
ScriptDocument* doc = new ScriptDocument(script);
doc->setAttribute(Qt::WA_DeleteOnClose, true);
ui->mdiArea->addSubWindow(doc);
ui->mdiArea->setActiveSubWindow(doc);
doc->showMaximized();
}
MainWindow::~MainWindow()
{
delete ui;

View file

@ -7,6 +7,7 @@
#include "qcoreevent.h"
#include <QMainWindow>
#include <QLineEdit>
#include <memory>
#include <qfiledialog.h>
enum SelectedTool {
@ -29,6 +30,8 @@ enum GridSnappingMode {
SNAP_OFF,
};
class Script;
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
@ -47,6 +50,8 @@ public:
GridSnappingMode snappingMode;
bool editSoundEffects = true;
void openScriptDocument(std::shared_ptr<Script>);
Ui::MainWindow *ui;
private:
PlaceDocument* placeDocument;

View file

@ -1,5 +1,7 @@
#include "explorermodel.h"
#include "common.h"
#include "objects/base/instance.h"
#include "objects/base/member.h"
#include <qicon.h>
#include <qmimedata.h>
#include <QWidget>
@ -43,7 +45,7 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
? static_cast<Instance*>(parent.internalPointer())
: rootItem.get();
if (parentItem->GetChildren().size() >= row)
if (parentItem->GetChildren().size() >= row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN))
return createIndex(row, column, parentItem->GetChildren()[row].get());
return {};
}

View file

@ -4,8 +4,10 @@
#include "mainwindow.h"
#include "objects/base/instance.h"
#include "objects/meta.h"
#include "objects/script.h"
#include <memory>
#include <qaction.h>
#include <qtreeview.h>
#define M_mainWindow dynamic_cast<MainWindow*>(window())
@ -70,6 +72,16 @@ void ExplorerView::keyPressEvent(QKeyEvent* event) {
}
}
void ExplorerView::mouseDoubleClickEvent(QMouseEvent *event) {
QModelIndex index = indexAt(event->pos());
std::shared_ptr<Instance> inst = model.fromIndex(index);
if (!inst->IsA<Script>()) return QTreeView::mouseDoubleClickEvent(event);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openScriptDocument(inst->CastTo<Script>().expect());
QTreeView::mouseDoubleClickEvent(event);
}
void ExplorerView::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator();

View file

@ -12,6 +12,7 @@ public:
~ExplorerView() override;
void keyPressEvent(QKeyEvent*) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
// void dropEvent(QDropEvent*) override;
void buildContextMenu();

View file

@ -1,6 +1,7 @@
#include "panes/propertiesview.h"
#include "common.h"
#include "datatypes/base.h"
#include "objects/base/member.h"
#include <QColorDialog>
#include <QLineEdit>
@ -296,7 +297,7 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
// if (meta.type == &CFrame::TYPE) continue;
if (meta.type == &CFrame::TYPE || meta.flags & PROP_HIDDEN) continue;
QTreeWidgetItem* item = new QTreeWidgetItem;
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable);
@ -311,9 +312,9 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
} else if (meta.type == &Vector3::TYPE) {
Vector3 vector = currentValue.get<Vector3>();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else if (meta.type == &CFrame::TYPE) {
Vector3 vector = currentValue.get<CFrame>().Position();
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
// } else if (meta.type == &CFrame::TYPE) {
// Vector3 vector = currentValue.get<CFrame>().Position();
// item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
} else {
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
}

View file

@ -14,6 +14,7 @@ PlaceDocument::PlaceDocument(QWidget* parent):
QMdiSubWindow(parent) {
placeWidget = new MainGLWidget;
setWidget(placeWidget);
setWindowTitle("Place");
_runState = RUN_STOPPED;
}
@ -32,7 +33,7 @@ void PlaceDocument::setRunState(RunState newState) {
std::shared_ptr<DataModel> newModel = editModeDataModel->CloneModel();
gDataModel = newModel;
gDataModel->Init();
gDataModel->Init(true);
} else if (newState == RUN_PAUSED && _runState == RUN_RUNNING) {
_runState = RUN_PAUSED;
} else if (newState == RUN_STOPPED) {
@ -40,7 +41,6 @@ void PlaceDocument::setRunState(RunState newState) {
// GC: Check to make sure gDataModel gets properly garbage collected prior to this
gDataModel = editModeDataModel;
gDataModel->Init();
}
}
@ -49,10 +49,6 @@ void PlaceDocument::closeEvent(QCloseEvent *closeEvent) {
closeEvent->ignore();
}
void PlaceDocument::keyPressEvent(QKeyEvent *keyEvent) {
printf("Getting\n");
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void PlaceDocument::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {

View file

@ -24,6 +24,5 @@ public:
void setRunState(RunState);
void closeEvent(QCloseEvent *closeEvent) override;
void keyPressEvent(QKeyEvent *keyEvent) override;
void init();
};

View file

@ -0,0 +1,49 @@
#include "scriptdocument.h"
#include <Qsci/qsciscintilla.h>
#include <Qsci/qscilexer.h>
#include <qboxlayout.h>
#include <qfont.h>
#include <qdebug.h>
#include <qglobal.h>
#include <qlayout.h>
#include "objects/script.h"
ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
script(script), QMdiSubWindow(parent) {
setWindowTitle(QString::fromStdString(script->name));
// QFrame* frame = new QFrame;
// QVBoxLayout* frameLayout = new QVBoxLayout;
// frame->setLayout(frameLayout);
scintilla = new QsciScintilla;
// frameLayout->addWidget(scintilla);
// setWidget(frame);
setWidget(scintilla);
QFont font;
font.setFamily("Consolas");
font.setStyleHint(QFont::Monospace);
font.setPointSize(12);
// scintilla->setMargins(2);
scintilla->setScrollWidth(1); // Hide scrollbars on empty document, it will grow automatically
scintilla->setMarginType(1, QsciScintilla::NumberMargin);
scintilla->setMarginWidth(1, "0000");
scintilla->setMarginsForegroundColor(palette().windowText().color());
scintilla->setMarginsBackgroundColor(palette().window().color());
scintilla->setCaretForegroundColor(palette().text().color());
scintilla->setFont(font);
scintilla->setText(QString::fromStdString(script->source));
connect(scintilla, &QsciScintilla::textChanged, [this]() {
// this-> is important here, as otherwise it will refer to the
// parameter passed in, which will get gc'ed eventually
this->script->source = scintilla->text().toStdString();
});
}
ScriptDocument::~ScriptDocument() {
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <Qsci/qsciscintilla.h>
#include <memory>
#include <qmdisubwindow.h>
class Script;
class ScriptDocument : public QMdiSubWindow {
std::shared_ptr<Script> script;
QsciScintilla* scintilla;
public:
ScriptDocument(std::shared_ptr<Script> script, QWidget* parent = nullptr);
~ScriptDocument() override;
};