Compare commits
23 commits
1ba9911036
...
ec65c6eddc
Author | SHA1 | Date | |
---|---|---|---|
ec65c6eddc | |||
b9c8022f6f | |||
76554c8295 | |||
c578b7361c | |||
0c3c6f43fc | |||
a2e2210998 | |||
b8ee828d29 | |||
8f20c11b36 | |||
8049d45b43 | |||
85e1efe5b3 | |||
10c78cd647 | |||
febde86430 | |||
99a8cbe957 | |||
28ddfed8b3 | |||
9be6c103de | |||
cbed2bac95 | |||
6bb1d8b3a4 | |||
d8ffd574f5 | |||
4bd1110202 | |||
99440cc3ee | |||
c081469a49 | |||
4cfb327b65 | |||
1858c703c7 |
71 changed files with 1669 additions and 386 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
/bin/
|
||||
/lib/
|
||||
/build/
|
||||
/autogen/build
|
||||
|
||||
# Qt
|
||||
/*.pro.user*
|
||||
|
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
@ -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",
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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 )
|
||||
|
|
20
README.md
20
README.md
|
@ -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
13
autogen/CMakeLists.txt
Normal 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
299
autogen/src/analysis.cpp
Normal 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
59
autogen/src/analysis.h
Normal 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
228
autogen/src/codegen.cpp
Normal 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
6
autogen/src/codegen.h
Normal 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
42
autogen/src/main.cpp
Normal 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
17
autogen/src/util.cpp
Normal 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
11
autogen/src/util.h
Normal 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);
|
32
cmake/FindQScintilla.cmake
Normal file
32
cmake/FindQScintilla.cmake
Normal 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
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
|
@ -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)));
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
};
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
8
core/src/error/data.h
Normal 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) {}
|
||||
};
|
|
@ -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
7
core/src/lua.h
Normal 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>
|
||||
}
|
31
core/src/objects/annotation.h
Normal file
31
core/src/objects/annotation.h
Normal 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:
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
|
@ -17,4 +17,7 @@ void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent
|
|||
}
|
||||
|
||||
void Service::InitService() {
|
||||
}
|
||||
|
||||
void Service::OnRun() {
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 },
|
||||
};
|
|
@ -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() {
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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:
|
||||
}
|
|
@ -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;
|
||||
|
|
90
core/src/objects/script/scriptcontext.cpp
Normal file
90
core/src/objects/script/scriptcontext.cpp
Normal 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");
|
||||
}
|
23
core/src/objects/script/scriptcontext.h
Normal file
23
core/src/objects/script/scriptcontext.h
Normal 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;
|
||||
};
|
26
core/src/objects/script/serverscriptservice.cpp
Normal file
26
core/src/objects/script/serverscriptservice.cpp
Normal 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();
|
||||
}
|
||||
}
|
23
core/src/objects/script/serverscriptservice.h
Normal file
23
core/src/objects/script/serverscriptservice.h
Normal 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;
|
||||
};
|
|
@ -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) {
|
||||
|
|
|
@ -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
115
docs/autogen.md
Normal 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.
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -12,6 +12,7 @@ public:
|
|||
~ExplorerView() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent*) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
// void dropEvent(QDropEvent*) override;
|
||||
|
||||
void buildContextMenu();
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -24,6 +24,5 @@ public:
|
|||
void setRunState(RunState);
|
||||
|
||||
void closeEvent(QCloseEvent *closeEvent) override;
|
||||
void keyPressEvent(QKeyEvent *keyEvent) override;
|
||||
void init();
|
||||
};
|
49
editor/script/scriptdocument.cpp
Normal file
49
editor/script/scriptdocument.cpp
Normal 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() {
|
||||
}
|
16
editor/script/scriptdocument.h
Normal file
16
editor/script/scriptdocument.h
Normal 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;
|
||||
};
|
Loading…
Add table
Reference in a new issue