Compare commits

...

23 commits

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

1
.gitignore vendored
View file

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

8
.vscode/launch.json vendored
View file

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

View file

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

View file

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

13
autogen/CMakeLists.txt Normal file
View file

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

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

@ -0,0 +1,299 @@
#include "analysis.h"
#include "util.h"
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <optional>
#include <filesystem>
namespace fs = std::filesystem;
// Very simple parser
// Example format:
// name="Hello!", world=Test, read_only
// Result:
// "name": "Hello!", "world": "Test", "read_only": ""
std::map<std::string, std::string> parseAnnotationString(std::string src) {
std::map<std::string, std::string> result;
std::string currentIdent = "";
std::string currentValue = "";
int stage = 0;
bool quoted = false;
int i = 0;
for (; i < src.length(); i++) {
if (src[i] == ' ' && (stage != 2 || !quoted)) continue; // Ignore spaces if not in stage 2 and quoted
if (src[i] == ',' && stage == 0) continue; // Let empty commas slip by
if (stage < 2 && (src[i] >= 'a' && src[i] <= 'z' || src[i] >= 'A' && src[i] <= 'Z' || src[i] >= '0' && src[i] <= '9' || src[i] == '_')) {
currentIdent += src[i];
stage = 1;
continue;
}
if (stage == 1 && src[i] == '=') { // What follows is a value
stage = 2;
continue;
}
if (stage == 1 && src[i] == ',') { // Value-less key
stage = 0;
result[currentIdent] = "";
currentIdent = "";
continue;
}
if (stage == 2 && quoted && src[i] == '"') { // Close a quoted string
quoted = false;
continue;
}
if (stage == 2 && !quoted && src[i] == '"') { // Start a quoted string
quoted = true;
continue;
}
if (stage == 2 && !quoted && (src[i] == ' ' || src[i] == ',')) { // Terminate the string
stage = 0;
result[currentIdent] = currentValue;
currentIdent = "";
currentValue = "";
continue;
}
if (stage == 2) { // Otherwise if in stage 2, simply add the character
currentValue += src[i];
continue;
}
fprintf(stderr, "Unexpected symbol: %c at index %d\n", src[i], i);
fprintf(stderr, "\t%s\n", src.c_str());
fprintf(stderr, "\t%s^\n", i > 0 ? std::string(i, '~').c_str() : "");
abort();
}
// Add the remaining value
if (stage == 1) {
result[currentIdent] = "";
} else if (stage == 2) {
result[currentIdent] = currentValue;
}
return result;
}
bool findInstanceAnnotation(CXCursor cur) {
bool found = false;
// Look for annotations in the class itself
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
// if (annString == "OB::INSTANCE") found = true;
if (annString == "OB::def_inst") found = true;
return CXChildVisit_Break;
});
return found;
}
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName) {
std::optional<std::string> ret = std::nullopt;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_AnnotateAttr) return CXChildVisit_Continue;
std::string annString = x_clang_toString(clang_getCursorDisplayName(cur));
if (annString != annotationName) return CXChildVisit_Continue;
// Look inside for a StringLiteral
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
// if (kind != CXCursor_StringLiteral) return CXChildVisit_Recurse;
// String literals cannot be parsed as CXCursor_StringLiteral. I don't know why.
if (kind != CXCursor_UnexposedExpr) return CXChildVisit_Recurse;
// https://stackoverflow.com/a/63859988/16255372
auto res = clang_Cursor_Evaluate(cur);
ret = clang_EvalResult_getAsStr(res);
clang_EvalResult_dispose(res);
return CXChildVisit_Break;
});
return CXChildVisit_Break;
});
return ret;
}
std::string findBaseClass(CXCursor cur) {
std::string baseClass = "";
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_CXXBaseSpecifier) return CXChildVisit_Continue;
baseClass = x_clang_toString(clang_getCursorDisplayName(cur));
return CXChildVisit_Break;
});
return baseClass;
}
void processField(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_prop");
if (!propertyDef) return;
PropertyAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string fieldName = x_clang_toString(clang_getCursorDisplayName(cur));
anly.name = result["name"];
anly.fieldName = fieldName;
anly.category = result["category"];
anly.onUpdateCallback = result["on_update"];
if (result.count("hidden"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Hidden;
if (result.count("no_save"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_NoSave;
if (result.count("unit_float"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_UnitFloat;
if (result.count("readonly"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly;
CXType type = clang_getCursorType(cur);
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str();
state->properties.push_back(anly);
// For cframe, add member fields
std::optional<std::string> cframePositionDef = findAnnotation(cur, "OB::cframe_position_prop");
if (cframePositionDef) {
auto cframePosition = parseAnnotationString(cframePositionDef.value());
PropertyAnalysis cframeProp;
cframeProp.backingFieldType = anly.backingFieldType;
cframeProp.fieldName = anly.fieldName;
cframeProp.name = cframePosition["name"];
cframeProp.category = anly.category;
cframeProp.cframeMember = CFrameMember_Position;
cframeProp.onUpdateCallback = anly.onUpdateCallback;
cframeProp.flags = PropertyFlag_NoSave;
state->properties.push_back(cframeProp);
};
std::optional<std::string> cframeRotationDef = findAnnotation(cur, "OB::cframe_rotation_prop");
if (cframeRotationDef) {
auto cframeRotation = parseAnnotationString(cframeRotationDef.value());
PropertyAnalysis cframeProp;
cframeProp.backingFieldType = anly.backingFieldType;
cframeProp.fieldName = anly.fieldName;
cframeProp.name = cframeRotation["name"];
cframeProp.category = anly.category;
cframeProp.cframeMember = CFrameMember_Rotation;
cframeProp.onUpdateCallback = anly.onUpdateCallback;
cframeProp.flags = PropertyFlag_NoSave;
state->properties.push_back(cframeProp);
};
}
void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly;
// Find base class
std::string baseClass = findBaseClass(cur);
// Find header file
CXSourceLocation loc = clang_getCursorLocation(cur);
CXFile file;
unsigned line, column, off;
clang_getFileLocation(loc, &file, &line, &column, &off);
std::string headerName = x_clang_toString(clang_getFileName(file));
fs::path headerPath = fs::relative(headerName, srcRoot);
anly.name = className;
anly.baseClass = baseClass;
anly.headerPath = headerPath;
// Add misc flags and options
auto instanceDef = findAnnotation(cur, "OB::def_inst");
auto result = parseAnnotationString(instanceDef.value());
if (result.count("service"))
anly.flags = anly.flags | ClassFlag_Service | ClassFlag_NotCreatable;
if (result.count("not_creatable"))
anly.flags = anly.flags | ClassFlag_NotCreatable;
if (result.count("hidden"))
anly.flags = anly.flags | ClassFlag_Hidden;
anly.abstract = result.count("abstract") > 0;
anly.explorerIcon = result["explorer_icon"];
// Find annotated fields
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_FieldDecl) return CXChildVisit_Continue;
processField(cur, &anly);
return CXChildVisit_Continue;
});
state->classes[className] = anly;
}
// https://clang.llvm.org/docs/LibClang.html
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state) {
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), "-D__AUTOGEN__", 0 };
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
// https://stackoverflow.com/a/59206378/16255372
CXIndex index = clang_createIndex(0, 0);
CXTranslationUnit unit = clang_parseTranslationUnit(
index,
path.c_str(), cargs, 5,
nullptr, 0,
CXTranslationUnit_None);
if (!unit) {
fprintf(stderr, "Failed to parse file\n");
return 1;
}
// Print errors
int ndiags = clang_getNumDiagnostics(unit);
for (int i = 0; i < ndiags; i++) {
CXDiagnostic diag = clang_getDiagnostic(unit, i);
CXString str = clang_formatDiagnostic(diag, 0);
fprintf(stderr, "diag: %s\n", clang_getCString(str));
clang_disposeString(str);
clang_disposeDiagnostic(diag);
}
CXCursor cursor = clang_getTranslationUnitCursor(unit);
bool flag = false;
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_ClassDecl) return CXChildVisit_Continue;
CXSourceLocation loc = clang_getCursorLocation(cur);
if (!clang_Location_isFromMainFile(loc)) return CXChildVisit_Continue; // This class is not from this header. Skip
std::string className = x_clang_toString(clang_getCursorDisplayName(cur));
// Forward-decls can slip through the cracks, this prevents that, but also allows us to filter non-instance classes in the src/objects directory
if (!findInstanceAnnotation(cur)) return CXChildVisit_Continue; // Class is not "primary" declaration/is not instance, skip
if (state->classes.count(className) > 0) return CXChildVisit_Continue; // Class has already been analyzed, skip...
processClass(cur, state, className, srcRoot);
return CXChildVisit_Continue;
});
return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

View file

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

View file

@ -11,11 +11,38 @@ find_package(pugixml 1.15 REQUIRED)
find_package(Stb REQUIRED) find_package(Stb REQUIRED)
include_directories(${Stb_INCLUDE_DIR}) 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") file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
list(APPEND SOURCES ${AUTOGEN_OUTS})
add_library(openblocks STATIC ${SOURCES}) add_library(openblocks STATIC ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks") set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_libraries(openblocks ${GLEW_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml) target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include") target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIR})
# Windows-specific dependencies # Windows-specific dependencies
if(WIN32) if(WIN32)

View file

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

View file

@ -1,12 +1,19 @@
#include "base.h" #include "base.h"
#include "error/data.h"
#include "meta.h" #include "meta.h"
#include <ios> #include <ios>
#include <sstream> #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) {} \ #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::~CLASS_NAME() = default; \
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \ 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; }; \ 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())); } 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(); return Data::Null();
} }
void Data::Null::PushLuaValue(lua_State* L) const {
lua_pushnil(L);
}
result<Data::Variant, LuaCastError> Data::Null::FromLuaValue(lua_State* L, int idx) {
return Data::Variant(Data::Null());
}
// //
IMPL_WRAPPER_CLASS(Bool, bool, "bool") IMPL_WRAPPER_CLASS(Bool, bool, "bool")
@ -39,27 +54,50 @@ IMPL_WRAPPER_CLASS(Int, int, "int")
IMPL_WRAPPER_CLASS(Float, float, "float") IMPL_WRAPPER_CLASS(Float, float, "float")
IMPL_WRAPPER_CLASS(String, std::string, "string") IMPL_WRAPPER_CLASS(String, std::string, "string")
// ToString
const Data::String Data::Bool::ToString() const { const Data::String Data::Bool::ToString() const {
return Data::String(value ? "true" : "false"); 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 { const Data::String Data::Int::ToString() const {
return Data::String(std::to_string(value)); 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) { Data::Variant Data::Int::Deserialize(pugi::xml_node node) {
return Data::Int(node.text().as_int()); 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) { std::optional<Data::Variant> Data::Int::FromString(std::string string) {
char* endPos; char* endPos;
int value = (int)std::strtol(string.c_str(), &endPos, 10); 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); 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) { std::optional<Data::Variant> Data::Float::FromString(std::string string) {
char* endPos; char* endPos;
float value = std::strtof(string.c_str(), &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); 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) { std::optional<Data::Variant> Data::String::FromString(std::string string) {
return Data::String(string); return Data::String(string);
} }
// PushLuaValue
void Data::Bool::PushLuaValue(lua_State* L) const {
lua_pushboolean(L, *this);
}
void Data::Int::PushLuaValue(lua_State* L) const {
lua_pushinteger(L, *this);
}
void Data::Float::PushLuaValue(lua_State* L) const {
lua_pushnumber(L, *this);
}
void Data::String::PushLuaValue(lua_State* L) const {
lua_pushstring(L, value.c_str());
}
// FromLuaValue
result<Data::Variant, LuaCastError> Data::Bool::FromLuaValue(lua_State* L, int idx) {
if (!lua_isboolean(L, idx))
return LuaCastError(lua_typename(L, idx), "boolean");
return Data::Variant(Data::Bool(lua_toboolean(L, idx)));
}
result<Data::Variant, LuaCastError> Data::Int::FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "integer");
return Data::Variant(Data::Int((int)lua_tonumber(L, idx)));
}
result<Data::Variant, LuaCastError> Data::Float::FromLuaValue(lua_State* L, int idx) {
if (!lua_isnumber(L, idx))
return LuaCastError(lua_typename(L, idx), "float");
return Data::Variant(Data::Float((float)lua_tonumber(L, idx)));
}
result<Data::Variant, LuaCastError> Data::String::FromLuaValue(lua_State* L, int idx) {
if (!lua_tostring(L, idx))
return LuaCastError(lua_typename(L, idx), "string");
return Data::Variant(Data::String(lua_tostring(L, idx)));
}

View file

@ -4,6 +4,10 @@
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <pugixml.hpp> #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 { \ #define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
const WRAPPED_TYPE value; \ const WRAPPED_TYPE value; \
@ -16,19 +20,24 @@ public: \
\ \
virtual const Data::String ToString() const override; \ virtual const Data::String ToString() const override; \
virtual void Serialize(pugi::xml_node node) 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 Data::Variant Deserialize(pugi::xml_node node); \
static std::optional<Data::Variant> FromString(std::string); \ static std::optional<Data::Variant> FromString(std::string); \
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
}; };
namespace Data { namespace Data {
class Variant; class Variant;
typedef std::function<Data::Variant(pugi::xml_node)> Deserializer; typedef std::function<Data::Variant(pugi::xml_node)> Deserializer;
typedef std::function<std::optional<Data::Variant>(std::string)> FromString; typedef std::function<std::optional<Data::Variant>(std::string)> FromString;
typedef std::function<result<Data::Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
struct TypeInfo { struct TypeInfo {
std::string name; std::string name;
Deserializer deserializer; Deserializer deserializer;
FromString fromString; FromString fromString;
FromLuaValue fromLuaValue;
}; };
class String; class String;
@ -38,6 +47,7 @@ namespace Data {
virtual const TypeInfo& GetType() const = 0; virtual const TypeInfo& GetType() const = 0;
virtual const Data::String ToString() const = 0; virtual const Data::String ToString() const = 0;
virtual void Serialize(pugi::xml_node node) const = 0; virtual void Serialize(pugi::xml_node node) const = 0;
virtual void PushLuaValue(lua_State*) const = 0;
}; };
class Null : Base { class Null : Base {
@ -49,7 +59,10 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) 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 Data::Variant Deserialize(pugi::xml_node node);
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
}; };
DEF_WRAPPER_CLASS(Bool, bool) DEF_WRAPPER_CLASS(Bool, bool)

View file

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

View file

@ -35,6 +35,7 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node parent) 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); static Data::Variant Deserialize(pugi::xml_node node);
operator glm::mat4() const; operator glm::mat4() const;

View file

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

View file

@ -26,6 +26,7 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
std::string ToHex() const; std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) 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 Data::Variant Deserialize(pugi::xml_node node);
operator glm::vec3() const; operator glm::vec3() const;

View file

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

View file

@ -37,6 +37,7 @@ namespace Data {
Data::String ToString() const; Data::String ToString() const;
void Serialize(pugi::xml_node node) const; void Serialize(pugi::xml_node node) const;
void PushLuaValue(lua_State* state) const;
static Data::Variant Deserialize(pugi::xml_node node); static Data::Variant Deserialize(pugi::xml_node node);
}; };

View file

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

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "base.h" #include "base.h"
#include "error/data.h"
#include <memory> #include <memory>
class Instance; class Instance;
@ -20,6 +21,8 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override; virtual void Serialize(pugi::xml_node node) const override;
// static Data::Variant Deserialize(pugi::xml_node node); virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
}; };
} }

View file

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

View file

@ -26,6 +26,7 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) 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 Data::Variant Deserialize(pugi::xml_node node);
static std::optional<Data::Variant> FromString(std::string); static std::optional<Data::Variant> FromString(std::string);

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

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

View file

@ -52,6 +52,13 @@ public:
}, error().value()); }, 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 // Equivalent to .success
operator std::optional<T_Result>() { return success(); } operator std::optional<T_Result>() { return success(); }
operator bool() { return isSuccess(); } operator bool() { return isSuccess(); }

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

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

View file

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

View file

@ -43,14 +43,6 @@ constexpr FieldCodec classNameCodec() {
Instance::Instance(const InstanceType* type) { Instance::Instance(const InstanceType* type) {
this->name = type->className; 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 () { Instance::~Instance () {
@ -154,6 +146,14 @@ bool Instance::IsA(std::string className) {
return cur != nullptr; 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; static std::shared_ptr<Instance> DUMMY_INSTANCE;
DescendantsIterator Instance::GetDescendantsStart() { DescendantsIterator Instance::GetDescendantsStart() {
return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE); return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE);
@ -186,67 +186,77 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
// Properties // Properties
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) { result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
auto meta_ = GetPropertyMeta(name); return InternalGetPropertyValue(name);
if (!meta_) return MemberNotFound(GetClass()->className, name);
auto meta = meta_.expect();
return meta.codec.read(meta.backingField);
} }
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value) { fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value) {
auto meta_ = GetPropertyMeta(name); auto result = InternalSetPropertyValue(name, value);
if (!meta_) return MemberNotFound(GetClass()->className, name); if (result.isSuccess()) sendPropertyUpdatedSignal(shared_from_this(), name, value);
auto meta = meta_.expect(); return result;
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 {};
} }
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) { result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
MemberMap* current = &*memberMap; return InternalGetPropertyMeta(name);
while (true) {
// We look for the property in current member map
auto it = current->members.find(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) { void Instance::UpdateProperty(std::string name) {
PropertyMeta meta = GetPropertyMeta(name).expect(); // TODO: temporary workaround because I'm too lazy to implement this in autogen
if (!meta.updateCallback) return; // Nothing to update, exit. InternalSetPropertyValue(name, InternalGetPropertyValue(name).expect()).expect();
meta.updateCallback.value()(name);
// PropertyMeta meta = GetPropertyMeta(name).expect();
// if (!meta.updateCallback) return; // Nothing to update, exit.
// meta.updateCallback.value()(name);
} }
std::vector<std::string> Instance::GetProperties() { std::vector<std::string> Instance::GetProperties() {
if (cachedMemberList.has_value()) return cachedMemberList.value(); if (cachedMemberList.has_value()) return cachedMemberList.value();
std::vector<std::string> memberList; std::vector<std::string> memberList = InternalGetProperties();
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);
cachedMemberList = memberList; cachedMemberList = memberList;
return memberList; return memberList;
@ -262,7 +272,7 @@ void Instance::Serialize(pugi::xml_node parent) {
pugi::xml_node propertiesNode = node.append_child("Properties"); pugi::xml_node propertiesNode = node.append_child("Properties");
for (std::string name : GetProperties()) { for (std::string name : GetProperties()) {
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing"); PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing");
if (meta.flags & (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); pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name); propertyNode.append_attribute("name").set_value(name);

View file

@ -25,9 +25,11 @@ class Workspace;
typedef int InstanceFlags; typedef int InstanceFlags;
// This instance should only be instantiated in special circumstances (i.e. by DataModel) and should be creatable directly via any API // 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 // 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 describing information about an instance
struct InstanceType { struct InstanceType {
@ -63,11 +65,15 @@ private:
friend JointInstance; // This isn't ideal, but oh well friend JointInstance; // This isn't ideal, but oh well
protected: protected:
bool parentLocked = false; bool parentLocked = false;
std::unique_ptr<MemberMap> memberMap;
Instance(const InstanceType*); Instance(const InstanceType*);
virtual ~Instance(); 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 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 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); virtual void OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWorkspace, std::shared_ptr<Workspace> newWorkspace);
@ -100,6 +106,7 @@ public:
DescendantsIterator GetDescendantsEnd(); DescendantsIterator GetDescendantsEnd();
// Utility functions // Utility functions
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); } inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
std::optional<std::shared_ptr<Instance>> FindFirstChild(std::string);
// Properties // Properties
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name); result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);

View file

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

View file

@ -18,3 +18,6 @@ void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent
void Service::InitService() { void Service::InitService() {
} }
void Service::OnRun() {
}

View file

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

View file

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

View file

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

View file

@ -23,17 +23,6 @@ static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0,
static rp3d::PhysicsCommon common; static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld(); 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) { Handles::Handles(): Instance(&TYPE) {
} }

View file

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

View file

@ -11,42 +11,7 @@
#include <reactphysics3d/engine/PhysicsWorld.h> #include <reactphysics3d/engine/PhysicsWorld.h>
#include "ptr_helpers.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) { 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() { JointInstance::~JointInstance() {

View file

@ -1,13 +1,21 @@
#pragma once #pragma once
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "../annotation.h"
#include <memory> #include <memory>
#include <optional> #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 Part;
class Workspace; class Workspace;
class JointInstance : public Instance { class INSTANCE_WITH(abstract) JointInstance : public Instance {
AUTOGEN_PREAMBLE
std::weak_ptr<Part> oldPart0; std::weak_ptr<Part> oldPart0;
std::weak_ptr<Part> oldPart1; std::weak_ptr<Part> oldPart1;
protected: protected:
@ -23,9 +31,13 @@ protected:
public: public:
const static InstanceType TYPE; const static InstanceType TYPE;
[[ def_prop(name="Part0", on_update=onUpdated) ]]
std::weak_ptr<Part> part0; std::weak_ptr<Part> part0;
[[ def_prop(name="Part1", on_update=onUpdated) ]]
std::weak_ptr<Part> part1; std::weak_ptr<Part> part1;
[[ def_prop(name="C0", on_update=onUpdated) ]]
CFrame c0; CFrame c0;
[[ def_prop(name="C1", on_update=onUpdated) ]]
CFrame c1; CFrame c1;
JointInstance(const InstanceType*); JointInstance(const InstanceType*);

View file

@ -10,16 +10,6 @@
#include <reactphysics3d/constraint/FixedJoint.h> #include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/engine/PhysicsWorld.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) { Snap::Snap(): JointInstance(&TYPE) {
} }

View file

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

View file

@ -10,16 +10,6 @@
#include <reactphysics3d/constraint/FixedJoint.h> #include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/engine/PhysicsWorld.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) { Weld::Weld(): JointInstance(&TYPE) {
} }

View file

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

View file

@ -2,18 +2,6 @@
#include "workspace.h" #include "workspace.h"
#include <memory> #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) { JointsService::JointsService(): Service(&TYPE) {
} }
@ -24,7 +12,7 @@ void JointsService::InitService() {
initialized = true; initialized = true;
// Clear children before any new joints are added // 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(); inst->Destroy();
} }
} }

View file

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

View file

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

View file

@ -50,118 +50,12 @@ constexpr FieldCodec cframeRotationCodec() {
}; };
} }
const InstanceType Part::TYPE = {
.super = &Instance::TYPE,
.className = "Part",
.constructor = &Part::CreateGeneric,
.explorerIcon = "part",
};
const InstanceType* Part::GetClass() {
return &TYPE;
}
Part::Part(): Part(PartConstructParams { .size = glm::vec3(2, 1.2, 4), .color = Color3(0.639216f, 0.635294f, 0.647059f) }) { Part::Part(): 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), 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) { 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() { Part::~Part() {

View file

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

View file

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

View file

@ -1,15 +1,22 @@
#pragma once #pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include <memory> #include <memory>
class Script : public Instance { class INSTANCE_WITH(explorer_icon="script") Script : public Instance {
AUTOGEN_PREAMBLE
public: public:
const static InstanceType TYPE; const static InstanceType TYPE;
Script(); Script();
~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<Script> New() { return std::make_shared<Script>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Script>(); }; static inline std::shared_ptr<Instance> Create() { return std::make_shared<Script>(); };
virtual const InstanceType* GetClass() override; virtual const InstanceType* GetClass() override;

View file

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

View file

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

View file

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

View file

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

View file

@ -5,18 +5,6 @@
#include "physics/util.h" #include "physics/util.h"
#include <reactphysics3d/engine/PhysicsCommon.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; rp::PhysicsCommon* Workspace::physicsCommon = new rp::PhysicsCommon;
Workspace::Workspace(): Service(&TYPE) { Workspace::Workspace(): Service(&TYPE) {

View file

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

115
docs/autogen.md Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,8 +4,10 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "objects/meta.h" #include "objects/meta.h"
#include "objects/script.h"
#include <memory> #include <memory>
#include <qaction.h> #include <qaction.h>
#include <qtreeview.h>
#define M_mainWindow dynamic_cast<MainWindow*>(window()) #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() { void ExplorerView::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete); contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator(); contextMenu.addSeparator();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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