Compare commits

...

12 commits

57 changed files with 1318 additions and 407 deletions

View file

@ -4,8 +4,10 @@ find_package(Clang REQUIRED)
add_executable(autogen
src/main.cpp
src/util.cpp
src/analysis.cpp
src/codegen.cpp
src/object/analysis.cpp
src/object/codegen.cpp
src/data/analysis.cpp
src/data/codegen.cpp
)
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")

View file

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

View file

@ -0,0 +1,216 @@
#include "analysis.h"
#include "../util.h"
#include <cctype>
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <optional>
using namespace data;
static std::string toStaticName(std::string orig) {
bool isSnakeCase = orig.find('_') == -1;
std::string newName = "";
int wordStart = 0;
for (char c : orig) {
if (c == '_') {
wordStart = 1;
continue;
}
if (wordStart == 1)
newName += std::toupper(c);
else if (wordStart == 0)
newName += std::tolower(c);
else if (wordStart == 2)
newName += c;
if (c >= 'a' && c <= 'z')
wordStart = 2;
else
wordStart = 0;
}
newName[0] = std::tolower(newName[0]);
return newName;
}
// Constructors are stored the same way as static functions, but with the name "new"
static void processConstructor(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_ctor");
if (!propertyDef) return;
MethodAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
anly.name = result["name"];
anly.functionName = "__ctor";
anly.returnType = state->name;
// if name field is not provided, use new
if (anly.name == "") {
anly.name = "new";
}
// Populate parameter list
// https://stackoverflow.com/a/45867090/16255372
for (int i = 0; i < clang_Cursor_getNumArguments(cur); i++) {
CXCursor paramCur = clang_Cursor_getArgument(cur, i);
std::string paramName = x_clang_toString(clang_getCursorDisplayName(paramCur));
std::string paramType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(paramCur)));
MethodParameter param;
param.name = paramName;
param.type = paramType;
anly.parameters.push_back(param);
}
state->staticMethods.push_back(anly);
}
static void processMethod(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_method");
if (!propertyDef) return;
MethodAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
bool isStatic = clang_CXXMethod_isStatic(cur);
anly.name = result["name"];
anly.functionName = symbolName;
anly.returnType = x_clang_toString(clang_getTypeSpelling(retType));
// if name field is not provided, use fieldName instead, but capitalize the first character
if (anly.name == "") {
anly.name = symbolName;
if (!isStatic)
anly.name[0] = std::toupper(anly.name[0]);
else
anly.name[0] = std::tolower(anly.name[0]);
}
// Populate parameter list
// https://stackoverflow.com/a/45867090/16255372
for (int i = 0; i < clang_Cursor_getNumArguments(cur); i++) {
CXCursor paramCur = clang_Cursor_getArgument(cur, i);
std::string paramName = x_clang_toString(clang_getCursorDisplayName(paramCur));
std::string paramType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(paramCur)));
MethodParameter param;
param.name = paramName;
param.type = paramType;
anly.parameters.push_back(param);
}
// If it's a static method, push it into the library instead
if (clang_CXXMethod_isStatic(cur))
state->staticMethods.push_back(anly);
else
state->methods.push_back(anly);
}
// This processes both methods and fields
static void processProperty(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_data_prop");
if (!propertyDef) return;
PropertyAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
CXType retType = clang_getCursorResultType(cur);
bool isStatic = clang_getCursorKind(cur) == CXCursor_VarDecl || clang_CXXMethod_isStatic(cur);
anly.name = result["name"];
anly.backingSymbol = symbolName;
anly.valueType = x_clang_toString(clang_getTypeSpelling(retType));
anly.backingType = clang_getCursorKind(cur) == CXCursor_CXXMethod ? PropertyBackingType::Method : PropertyBackingType::Field;
// if name field is not provided, use fieldName instead, but capitalize the first character
if (anly.name == "") {
anly.name = symbolName;
if (!isStatic)
anly.name[0] = std::toupper(anly.name[0]);
else
anly.name = toStaticName(anly.name);
}
// If it's a static property, push it into the library instead
if (isStatic)
state->staticProperties.push_back(anly);
else
state->properties.push_back(anly);
}
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly;
std::string propertyDef = findAnnotation(cur, "OB::def_data").value();
auto result = parseAnnotationString(propertyDef);
anly.name = className;
anly.serializedName = result["name"];
anly.hasFromString = result.count("from_string") > 0;
if (anly.serializedName == "")
anly.serializedName = className;
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_Constructor) {
processConstructor(cur, &anly);
}
if (kind == CXCursor_CXXMethod || kind == CXCursor_FieldDecl || kind == CXCursor_VarDecl) {
processProperty(cur, &anly);
}
if (kind == CXCursor_CXXMethod) {
processMethod(cur, &anly);
}
return CXChildVisit_Continue;
});
state->classes[className] = anly;
}
bool data::analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state) {
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind == CXCursor_Namespace) return CXChildVisit_Recurse;
if (kind != CXCursor_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 (!findAnnotation(cur, "OB::def_data")) 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;
}

View file

@ -0,0 +1,51 @@
#pragma once
#include <clang-c/Index.h>
#include <map>
#include <string>
#include <vector>
namespace data {
enum PropertyBackingType {
Method,
Field
};
struct PropertyAnalysis {
std::string name;
std::string backingSymbol;
PropertyBackingType backingType;
std::string valueType;
};
struct MethodParameter {
std::string name;
std::string type;
};
struct MethodAnalysis {
std::string name;
std::string functionName;
std::string returnType;
std::vector<MethodParameter> parameters;
};
struct ClassAnalysis {
std::string name;
std::string serializedName;
std::string headerPath;
bool hasFromString = false;
std::vector<PropertyAnalysis> properties;
std::vector<MethodAnalysis> methods;
std::vector<PropertyAnalysis> staticProperties;
std::vector<MethodAnalysis> staticMethods;
};
struct AnalysisState {
std::map<std::string, ClassAnalysis> classes;
};
bool analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state);
}

View file

@ -0,0 +1,436 @@
#include "codegen.h"
#include "analysis.h"
#include <fstream>
#include <map>
#include <string>
#include <variant>
#include <vector>
using namespace data;
static std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" },
{ "int", "Data::Int" },
{ "float", "Data::Float" },
{ "std::string", "Data::String" },
{ "glm::vec3", "Vector3" },
};
static std::map<std::string, std::string> LUA_CHECK_FUNCS = {
{ "bool", "lua_toboolean" },
{ "int", "luaL_checkinteger" },
{ "float", "luaL_checknumber" },
{ "std::string", "luaL_checkstring" },
};
static std::map<std::string, std::string> LUA_TEST_FUNCS = {
{ "bool", "lua_isboolean" },
{ "int", "lua_isinteger" },
{ "float", "lua_isnumber" },
{ "std::string", "lua_isstring" },
};
static std::string getLuaMethodFqn(std::string className, std::string methodName) {
return "__lua_impl__" + className + "__" + methodName;
}
static std::string getMtName(std::string type) {
if (type.starts_with("Data::"))
return "__mt_" + type.substr(6);
return "__mt_" + type;
}
static void writeLuaGetArgument(std::ofstream& out, std::string type, int narg, bool member) {
std::string varname = "arg" + std::to_string(narg);
narg += 1; // Arguments start at 1
std::string checkFunc = LUA_CHECK_FUNCS[type];
if (checkFunc != "") {
out << " " << type << " " << varname << " = " << checkFunc << "(L, " << std::to_string(member ? narg + 1: narg) << ");\n";
} else {
std::string udataname = getMtName(type);
out << " " << type << " " << varname << " = *(" << type << "*)luaL_checkudata(L, " << std::to_string(member ? narg + 1 : narg) << ", \"" << udataname << "\");\n";
}
}
// Returns an expression which tests that the given argument by index matches a certain type
static void writeLuaTestArgument(std::ofstream& out, std::string type, int narg, bool member) {
std::string varname = "arg" + std::to_string(narg);
narg += 1; // Arguments start at 1
std::string testFunc = LUA_TEST_FUNCS[type];
if (testFunc != "") {
// Arguments start at 1
out << testFunc << "(L, " << std::to_string(member ? narg + 1 : narg) << ")";
} else {
std::string udataname = getMtName(type);
out << "luaL_testudata(L, " << std::to_string(member ? narg + 1 : narg) << ", \"" << udataname << "\")";
}
}
static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
// Collect all method names to account for overloaded functions
std::map<std::string, std::vector<MethodAnalysis>> methods;
std::map<std::string, std::vector<MethodAnalysis>> staticMethods;
for (MethodAnalysis method : state.methods) {
methods[method.name].push_back(method);
}
for (MethodAnalysis method : state.staticMethods) {
staticMethods[method.name].push_back(method);
}
for (auto& [name, methodImpls] : methods) {
std::string methodFqn = getLuaMethodFqn(state.name, name);
out << "static int " << methodFqn << "(lua_State* L) {\n"
" auto this__ = (Data::Base*)lua_touserdata(L, 1);\n"
" if (&this__->GetType() != &" << fqn << "::TYPE) return luaL_typerror(L, 0, \"" << state.name << "\");\n"
" " << fqn << "* this_ = (" << fqn << "*)this__;\n\n"
" int n = lua_gettop(L);\n";
out << " ";
// Support multiple overloads of the same function
bool first = true;
for (MethodAnalysis methodImpl : methodImpls) {
if (!first) out << " else ";
first = false;
// Check to see if the arguments possibly match this implementation's parameter types
out << "if (";
// Check number of arguments
out << "n == " << std::to_string(methodImpl.parameters.size() + 1); // Account for first argument as 'this'
for (int i = 0; i < methodImpl.parameters.size(); i++) {
out << " && ";
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, true);
}
out << ") {\n"; // End if condition, start if body
for (int i = 0; i < methodImpl.parameters.size(); i++) {
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, true);
}
// Store result
if (methodImpl.returnType != "void")
out << " " << methodImpl.returnType << " result = ";
else
out << " ";
// Call function
out << "this_->" << methodImpl.functionName << "(";
for (int i = 0; i < methodImpl.parameters.size(); i++) {
std::string varname = "arg" + std::to_string(i);
if (i != 0) out << ", ";
out << varname;
}
out << ");\n"; // End function call
// Return result
if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
}
if (methodImpl.returnType == "void")
out << " return 0;\n";
else
out << " return 1;\n";
out << " }";
}
// No function implementation matched
out << "\n\n return luaL_error(L, \"No definition of function '%s' in %s matches these argument types\", \"" << name << "\", \"" << state.name << "\");\n";
out << "}\n\n"; // End function
}
for (auto& [name, methodImpls] : staticMethods) {
std::string methodFqn = getLuaMethodFqn(state.name, name);
out << "static int " << methodFqn << "(lua_State* L) {\n"
" int n = lua_gettop(L);\n";
out << " ";
// Support multiple overloads of the same function
bool first = true;
for (MethodAnalysis methodImpl : methodImpls) {
if (!first) out << " else ";
first = false;
// Check to see if the arguments possibly match this implementation's parameter types
out << "if (";
// Check number of arguments
out << "n == " << std::to_string(methodImpl.parameters.size());
for (int i = 0; i < methodImpl.parameters.size(); i++) {
out << " && ";
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, false);
}
out << ") {\n"; // End if condition, start if body
// Get the arguments
for (int i = 0; i < methodImpl.parameters.size(); i++) {
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, false);
}
// Store result
if (methodImpl.returnType != "void")
out << " " << methodImpl.returnType << " result = ";
else
out << " ";
// Call function
if (methodImpl.functionName == "__ctor")
out << fqn << "(";
else
out << fqn << "::" << methodImpl.functionName << "(";
for (int i = 0; i < methodImpl.parameters.size(); i++) {
std::string varname = "arg" + std::to_string(i);
if (i != 0) out << ", ";
out << varname;
}
out << ");\n"; // End function call
// Return result
if (methodImpl.returnType != "void") {
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
if (mappedType == "")
out << " result.PushLuaValue(L);\n";
else
out << " " << mappedType << "(result).PushLuaValue(L);\n";
}
if (methodImpl.returnType == "void")
out << " return 0;\n";
else
out << " return 1;\n";
out << " }";
}
// No function implementation matched
out << "\n\n return luaL_error(L, \"No definition of function '%s' in %s matches these argument types\", \"" << name << "\", \"" << state.name << "\");\n";
out << "}\n\n"; // End function
}
}
static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
out << "static int data_gc(lua_State*);\n"
"static int data_index(lua_State*);\n"
"static int data_tostring(lua_State*);\n"
"static const struct luaL_Reg metatable [] = {\n"
" {\"__gc\", data_gc},\n"
" {\"__index\", data_index},\n"
" {\"__tostring\", data_tostring},\n"
" {NULL, NULL} /* end of array */\n"
"};\n\n";
out << "void Data::" << state.name << "::PushLuaValue(lua_State* L) const {\n"
" int n = lua_gettop(L);\n"
// " // I'm torn... should this be Data::Variant, or Data::Base?\n"
// " // If I ever decouple typing from Data::Base, I'll switch it to variant,\n"
// " // otherwise, it doesn't make much sense to represent it as one\n"
" " << fqn << "* userdata = (" << fqn << "*)lua_newuserdata(L, sizeof(" << fqn << "));\n"
" new(userdata) " << fqn << "(*this);\n"
" // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_" << state.name << "\");\n"
" luaL_register(L, NULL, metatable);\n"
" lua_setmetatable(L, n+1);\n"
"}\n\n";
out << "result<Data::Variant, LuaCastError> Data::" << state.name << "::FromLuaValue(lua_State* L, int idx) {\n"
" " << fqn << "* userdata = (" << fqn << "*) luaL_testudata(L, idx, \"" << getMtName(state.name) << "\");\n"
" if (userdata == nullptr)\n"
" return LuaCastError(lua_typename(L, idx), \"" << state.name << "\");\n"
" return Data::Variant(*userdata);\n"
"}\n\n";
// Indexing methods and properties
out << "static int data_index(lua_State* L) {\n"
" auto this__ = (Data::Base*)lua_touserdata(L, 1);\n"
" if (&this__->GetType() != &" << fqn << "::TYPE) return luaL_typerror(L, 0, \"" << state.name << "\");\n"
" " << fqn << "* this_ = (" << fqn << "*)this__;\n"
"\n"
" std::string key(lua_tostring(L, 2));\n"
" lua_pop(L, 2);\n"
"\n";
out << " ";
bool first = true;
for (PropertyAnalysis prop : state.properties) {
if (!first) out << " else ";
first = false;
out << "if (key == \"" << prop.name << "\") {\n";
std::string type = MAPPED_TYPE[prop.valueType];
if (type == "") type = prop.valueType;
std::string valueExpr;
if (prop.backingType == PropertyBackingType::Field)
valueExpr = "this_->" + prop.backingSymbol;
else if (prop.backingType == PropertyBackingType::Method)
valueExpr = "this_->" + prop.backingSymbol + "()";
// This largely depends on the type
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
out << " return 1;\n";
out << " }";
}
std::map<std::string, bool> accountedMethods;
for (MethodAnalysis method : state.methods) {
if (accountedMethods[method.name]) continue;
if (!first) out << " else ";
first = false;
accountedMethods[method.name] = true;
out << "if (key == \"" << method.name << "\") {\n";
out << " lua_pushcfunction(L, " << getLuaMethodFqn(state.name, method.name) << ");\n";
out << " return 1;\n";
out << " }";
}
out << "\n\n"
" return luaL_error(L, \"%s is not a valid member of %s\\n\", key.c_str(), \"" << state.name << "\");\n"
"}\n\n";
// ToString
out << "\nint data_tostring(lua_State* L) {\n"
" auto this_ = (" << fqn << "*)lua_touserdata(L, 1);\n"
" lua_pushstring(L, std::string(this_->ToString()).c_str());\n"
" return 1;\n"
"}\n\n";
// Destructor
out << "\nint data_gc(lua_State* L) {\n"
" auto this_ = (" << fqn << "*)lua_touserdata(L, 1);\n"
" delete this_;\n"
" return 0;\n"
"}\n\n";
}
static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
out << "static int lib_index(lua_State*);\n"
"static const struct luaL_Reg lib_metatable [] = {\n"
" {\"__index\", lib_index},\n"
" {NULL, NULL} /* end of array */\n"
"};\n\n";
out << "void Data::" << state.name << "::PushLuaLibrary(lua_State* L) {\n"
" lua_getglobal(L, \"_G\");\n"
" lua_pushstring(L, \"" << state.name << "\");\n"
"\n"
" lua_newuserdata(L, 0);\n"
"\n"
" // Create the library's metatable\n"
" luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n"
" luaL_register(L, NULL, lib_metatable);\n"
" lua_setmetatable(L, -2);\n"
"\n"
" lua_rawset(L, -3);\n"
" lua_pop(L, 1);\n"
"}\n\n";
// Indexing methods and properties
out << "static int lib_index(lua_State* L) {\n"
" std::string key(lua_tostring(L, 2));\n"
" lua_pop(L, 2);\n"
"\n";
out << " ";
bool first = true;
for (PropertyAnalysis prop : state.staticProperties) {
if (!first) out << " else ";
first = false;
out << "if (key == \"" << prop.name << "\") {\n";
std::string type = MAPPED_TYPE[prop.valueType];
if (type == "") type = prop.valueType;
std::string valueExpr;
if (prop.backingType == PropertyBackingType::Field)
valueExpr = fqn + "::" + prop.backingSymbol;
else if (prop.backingType == PropertyBackingType::Method)
valueExpr = fqn + "::" + prop.backingSymbol + "()";
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
out << " return 1;\n";
out << " }";
}
std::map<std::string, bool> accountedMethods;
for (MethodAnalysis method : state.staticMethods) {
if (accountedMethods[method.name]) continue;
if (!first) out << " else ";
first = false;
accountedMethods[method.name] = true;
out << "if (key == \"" << method.name << "\") {\n";
out << " lua_pushcfunction(L, " << getLuaMethodFqn(state.name, method.name) << ");\n";
out << " return 1;\n";
out << " }";
}
out << "\n\n"
" return luaL_error(L, \"%s is not a valid member of %s\\n\", key.c_str(), \"" << state.name << "\");\n"
"}\n\n";
}
void data::writeCodeForClass(std::ofstream& out, std::string headerPath, ClassAnalysis& state) {
std::string fqn = "Data::" + state.name;
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
out << "#include \"" << headerPath << "\"\n\n";
out << "#include \"datatypes/meta.h\"\n";
out << "#include <pugixml.hpp>\n";
out << "#include \"lua.h\"\n\n";
out << "const Data::TypeInfo " << fqn << "::TYPE = {\n"
<< " .name = \"" << state.serializedName << "\",\n"
<< " .deserializer = &" << fqn << "::Deserialize,\n";
if (state.hasFromString) out << " .fromString = &" << fqn << "::FromString,\n";
out << " .fromLuaValue = &" << fqn << "::FromLuaValue,\n"
<< "};\n\n";
out << "const Data::TypeInfo& " << fqn << "::GetType() const {\n"
<< " return TYPE;\n"
<< "};\n\n";
writeLuaMethodImpls(out, state);
writeLuaValueGenerator(out, state);
writeLuaLibraryGenerator(out, state);
}

View file

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

View file

@ -6,37 +6,89 @@
#include <cstdio>
#include <fstream>
#include <filesystem>
#include "analysis.h"
#include "codegen.h"
#include "object/analysis.h"
#include "object/codegen.h"
#include "data/analysis.h"
#include "data/codegen.h"
// namespace data {
// #include "data/analysis.h"
// #include "data/codegen.h"
// }
namespace fs = std::filesystem;
// https://clang.llvm.org/docs/LibClang.html
int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), "-D__AUTOGEN__", 0 };
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
// https://stackoverflow.com/a/59206378/16255372
CXIndex index = clang_createIndex(0, 0);
CXTranslationUnit unit = clang_parseTranslationUnit(
index,
srcPath.c_str(), cargs, 5,
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);
object::AnalysisState objectAnlyState;
data::AnalysisState dataAnlyState;
fs::path relpath = fs::relative(srcPath, srcRoot);
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
object::analyzeClasses(cursor, srcRoot, &objectAnlyState);
data::analyzeClasses(cursor, srcRoot, &dataAnlyState);
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);
if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) {
outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n";
outStream << "// This file was automatically generated by autogen, and should not be edited manually //\n";
outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n\n";
}
for (auto& [_, clazz] : objectAnlyState.classes) {
object::writeCodeForClass(outStream, relpath, clazz);
}
for (auto& [_, clazz] : dataAnlyState.classes) {
data::writeCodeForClass(outStream, relpath, clazz);
}
outStream.close();
return 0;
}
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;
return processHeader(srcRoot, srcPath, outPath);
}

View file

@ -1,5 +1,5 @@
#include "analysis.h"
#include "util.h"
#include "../util.h"
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/Index.h>
@ -7,77 +7,11 @@
#include <optional>
#include <filesystem>
using namespace object;
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) {
static bool findInstanceAnnotation(CXCursor cur) {
bool found = false;
// Look for annotations in the class itself
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
@ -93,38 +27,7 @@ bool findInstanceAnnotation(CXCursor cur) {
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) {
static std::string findBaseClass(CXCursor cur) {
std::string baseClass = "";
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
@ -137,8 +40,8 @@ std::string findBaseClass(CXCursor cur) {
return baseClass;
}
std::string currentCategory = "";
void processField(CXCursor cur, ClassAnalysis* state) {
static std::string currentCategory = "";
static void processField(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_prop");
if (!propertyDef) return;
@ -216,7 +119,7 @@ void processField(CXCursor cur, ClassAnalysis* state) {
};
}
void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
ClassAnalysis anly;
// Find base class
@ -264,36 +167,7 @@ void processClass(CXCursor cur, AnalysisState* state, std::string className, std
}
// 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;
bool object::analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state) {
// Search for classes
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);

View file

@ -1,9 +1,12 @@
#pragma once
#include <clang-c/Index.h>
#include <map>
#include <string>
#include <vector>
namespace object {
enum ClassFlags {
ClassFlag_NotCreatable = 1<<0,
ClassFlag_Service = 1<<1,
@ -56,4 +59,6 @@ struct AnalysisState {
std::map<std::string, ClassAnalysis> classes;
};
bool analyzeClasses(std::string path, std::string srcRoot, AnalysisState* state);
bool analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state);
}

View file

@ -4,15 +4,18 @@
#include <string>
#include <variant>
std::map<std::string, std::string> CATEGORY_STR = {
using namespace object;
static 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" },
{ "SURFACE_INPUT", "PROP_CATEGORY_SURFACE_INPUT" },
};
std::map<std::string, std::string> MAPPED_TYPE = {
static std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" },
{ "int", "Data::Int" },
{ "float", "Data::Float" },
@ -20,11 +23,11 @@ std::map<std::string, std::string> MAPPED_TYPE = {
{ "glm::vec3", "Vector3" },
};
std::map<std::string, std::monostate> ENUM_TYPES = {
static std::map<std::string, std::monostate> ENUM_TYPES = {
{ "SurfaceType", std::monostate() }
};
std::string parseWeakPtr(std::string weakPtrType) {
static std::string parseWeakPtr(std::string weakPtrType) {
if (!weakPtrType.starts_with("std::weak_ptr")) return "";
int pos0 = weakPtrType.find("<");
@ -34,7 +37,7 @@ std::string parseWeakPtr(std::string weakPtrType) {
return subtype;
}
std::string castFromVariant(std::string valueStr, std::string fieldType) {
static std::string castFromVariant(std::string valueStr, std::string fieldType) {
// Manual exception for now, enums will get their own system eventually
if (fieldType == "SurfaceType") {
return "(SurfaceType)(int)" + valueStr + ".get<Data::Int>()";
@ -44,7 +47,7 @@ std::string castFromVariant(std::string valueStr, std::string fieldType) {
return valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
}
std::string castToVariant(std::string valueStr, std::string fieldType) {
static std::string castToVariant(std::string valueStr, std::string fieldType) {
// Manual exception for now, enums will get their own system eventually
if (fieldType == "SurfaceType") {
return "Data::Int((int)" + valueStr + ")";
@ -63,7 +66,7 @@ std::string castToVariant(std::string valueStr, std::string fieldType) {
return valueStr;
}
void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
static void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {";
out << "\n ";
@ -104,7 +107,7 @@ void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "\n};\n\n";
}
void writePropertyUpdateHandler(std::ofstream& out, ClassAnalysis state) {
static void writePropertyUpdateHandler(std::ofstream& out, ClassAnalysis state) {
out << "void " << state.name << "::InternalUpdateProperty(std::string name) {";
out << "\n ";
@ -124,7 +127,7 @@ void writePropertyUpdateHandler(std::ofstream& out, ClassAnalysis state) {
out << "\n};\n\n";
}
void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
out << "result<Data::Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
out << "\n ";
@ -150,7 +153,7 @@ void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
out << "\n};\n\n";
}
void writePropertiesList(std::ofstream& out, ClassAnalysis state) {
static 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";
@ -163,7 +166,7 @@ void writePropertiesList(std::ofstream& out, ClassAnalysis state) {
out << "};\n\n";
}
void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
out << "result<PropertyMeta, MemberNotFound> " << state.name << "::InternalGetPropertyMeta(std::string name) {";
out << "\n ";
@ -203,7 +206,7 @@ void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
out << "\n};\n\n";
}
void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
void object::writeCodeForClass(std::ofstream& out, std::string headerPath, ClassAnalysis& state) {
std::string strFlags;
if (state.flags & ClassFlag_NotCreatable)
strFlags += " | INSTANCE_NOTCREATABLE";
@ -214,16 +217,13 @@ void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
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 << "#include \"datatypes/meta.h\"\n\n";
out << "const InstanceType " << state.name << "::TYPE = {\n"
<< " .super = &" << state.baseClass << "::TYPE,\n"
<< " .className = \"" << state.name << "\",\n"

View file

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

View file

@ -1,5 +1,6 @@
#include "util.h"
#include <clang-c/CXString.h>
#include <optional>
static CXChildVisitResult _visitorFunc(CXCursor cursor, CXCursor parent, CXClientData client_data) {
X_CXCursorVisitor* func = (X_CXCursorVisitor*)client_data;
@ -14,4 +15,97 @@ std::string x_clang_toString(CXString string) {
std::string str(clang_getCString(string));
clang_disposeString(string);
return str;
}
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;
}
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;
}

View file

@ -2,10 +2,21 @@
#include <clang-c/Index.h>
#include <functional>
#include <map>
#include <optional>
#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);
std::string x_clang_toString(CXString string);
// 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::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName);

View file

@ -16,12 +16,12 @@ 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")
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.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(SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/${SRC}")
set(OUT_PATH "${CMAKE_BINARY_DIR}/generated/${OUT_SRC_NAME}")
add_custom_command(

View file

@ -1,5 +1,7 @@
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
#include "objects/datamodel.h"
#include "datatypes/meta.h"
#include "common.h"
#include <memory>

View file

@ -2,6 +2,7 @@
#include "objects/base/instance.h"
#include "objects/handles.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
#include "camera.h"
#include <functional>
#include <memory>

View file

@ -0,0 +1,33 @@
#pragma once
// Markers for the autogen engine to generate getters, setters, lua, etc.
// Base macros
#ifdef __AUTOGEN__
#define def_data(...) clang::annotate("OB::def_data", #__VA_ARGS__)
#define def_data_prop(...) clang::annotate("OB::def_data_prop", #__VA_ARGS__)
#define def_data_method(...) clang::annotate("OB::def_data_method", #__VA_ARGS__)
#define def_data_ctor(...) clang::annotate("OB::def_data_ctor", #__VA_ARGS__)
#else
#define def_data(...)
#define def_data_prop(...)
#define def_data_method(...)
#define def_data_ctor(...)
#define def_data_op(...)
#endif
// Helper macros
#define DEF_DATA [[ def_data() ]] // Data declaration
#define DEF_DATA_(...) [[ def_data(__VA_ARGS__) ]]
#define DEF_DATA_PROP [[ def_data_prop() ]] // Property. Getter or field
#define DEF_DATA_METHOD [[ def_data_method() ]] // Method
#define DEF_DATA_CTOR [[ def_data_ctor() ]] // Constructor (i.e. .new)
#define DEF_DATA_OP [[ def_data_op() ]] // Operator (e.g. +, -, *)
#define AUTOGEN_PREAMBLE_DATA \
public: \
virtual const TypeInfo& GetType() const override; \
static const TypeInfo TYPE; \
virtual void PushLuaValue(lua_State*) const override; \
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
private:

View file

@ -3,6 +3,7 @@
#include "meta.h"
#include <ios>
#include <sstream>
#include <pugixml.hpp>
#include "lua.h"
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \

View file

@ -3,14 +3,15 @@
#include <string>
#include <functional>
#include <optional>
#include <pugixml.hpp>
#include "error/result.h"
#include "error/data.h"
extern "C" { typedef struct lua_State lua_State; }
namespace pugi { class xml_node; };
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
const WRAPPED_TYPE value; \
WRAPPED_TYPE value; \
public: \
CLASS_NAME(WRAPPED_TYPE); \
~CLASS_NAME(); \

View file

@ -6,6 +6,8 @@
#include <glm/gtc/quaternion.hpp>
#include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#include "datatypes/meta.h"
#include <pugixml.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
// #include "meta.h" // IWYU pragma: keep
@ -56,12 +58,6 @@ Data::CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
}
Data::CFrame::~CFrame() = default;
const Data::TypeInfo Data::CFrame::TYPE = {
.name = "CoordinateFrame",
.deserializer = &Data::CFrame::Deserialize,
};
const Data::TypeInfo& Data::CFrame::GetType() const { return Vector3::TYPE; };
const Data::String Data::CFrame::ToString() const {
return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
@ -149,9 +145,4 @@ Data::Variant Data::CFrame::Deserialize(pugi::xml_node node) {
node.child("R21").text().as_float(),
node.child("R22").text().as_float()
);
}
void Data::CFrame::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -1,17 +1,18 @@
#pragma once
#include "base.h"
#include "datatypes/annotation.h"
#include "datatypes/vector.h"
#include <glm/ext/quaternion_float.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#include <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
namespace reactphysics3d { class Transform; };
namespace Data {
class CFrame : Base {
class DEF_DATA_(name="CoordinateFrame") CFrame : public Base {
AUTOGEN_PREAMBLE_DATA
glm::vec3 translation;
glm::mat3 rotation;
@ -20,50 +21,48 @@ namespace Data {
// CFrame(float x, float y, float z);
// CFrame(const glm::vec3&);
// CFrame(const rp::Vector3&);
CFrame();
CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
CFrame(const rp::Transform&);
DEF_DATA_CTOR CFrame();
DEF_DATA_CTOR CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
DEF_DATA_CTOR CFrame(Vector3 , Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
CFrame(const reactphysics3d::Transform&);
CFrame(Vector3 position, glm::quat quat);
CFrame(Vector3 position, Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
~CFrame();
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
static CFrame pointToward(Vector3 position, Vector3 toward);
static const CFrame IDENTITY;
DEF_DATA_PROP static const CFrame IDENTITY;
static const CFrame YToZ;
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node parent) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static void PushLuaLibrary(lua_State*);
operator glm::mat4() const;
operator rp::Transform() const;
operator reactphysics3d::Transform() const;
//inline static CFrame identity() { }
inline Vector3 Position() const { return translation; }
inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
CFrame Inverse() const;
inline float X() const { return translation.x; }
inline float Y() const { return translation.y; }
inline float Z() const { return translation.z; }
DEF_DATA_PROP inline Vector3 Position() const { return translation; }
DEF_DATA_PROP inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
DEF_DATA_METHOD CFrame Inverse() const;
DEF_DATA_PROP inline float X() const { return translation.x; }
DEF_DATA_PROP inline float Y() const { return translation.y; }
DEF_DATA_PROP inline float Z() const { return translation.z; }
inline Vector3 RightVector() { return glm::column(rotation, 0); }
inline Vector3 UpVector() { return glm::column(rotation, 1); }
inline Vector3 LookVector() { return -glm::column(rotation, 2); }
DEF_DATA_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); }
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); }
Vector3 ToEulerAnglesXYZ();
static CFrame FromEulerAnglesXYZ(Vector3);
DEF_DATA_METHOD Vector3 ToEulerAnglesXYZ();
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
// Operators
Data::CFrame operator *(Data::CFrame) const;
Vector3 operator *(Vector3) const;
Data::CFrame operator +(Vector3) const;
Data::CFrame operator -(Vector3) const;
DEF_DATA_OP Data::CFrame operator *(Data::CFrame) const;
DEF_DATA_OP Vector3 operator *(Vector3) const;
DEF_DATA_OP Data::CFrame operator +(Vector3) const;
DEF_DATA_OP Data::CFrame operator -(Vector3) const;
};
}

View file

@ -1,17 +1,13 @@
#include "color3.h"
#include "meta.h" // IWYU pragma: keep
#include "panic.h"
#include "datatypes/meta.h"
#include <pugixml.hpp>
#include <sstream>
#include <iomanip>
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() = default;
const Data::TypeInfo Data::Color3::TYPE = {
.name = "Color3",
.deserializer = &Data::Color3::Deserialize,
};
const Data::TypeInfo& Data::Color3::GetType() const { return Data::Color3::TYPE; };
const Data::String Data::Color3::ToString() const {
return std::to_string(int(r*256)) + ", " + std::to_string(int(g*256)) + ", " + std::to_string(int(b*256));
@ -45,10 +41,4 @@ void Data::Color3::Serialize(pugi::xml_node node) const {
Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
return Color3::FromHex(node.text().get());
}
void Data::Color3::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -1,39 +1,36 @@
#pragma once
#include "base.h"
#include <glm/ext/quaternion_geometric.hpp>
#include "datatypes/annotation.h"
#include <glm/ext/vector_float3.hpp>
#include <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
namespace Data {
class Color3 : Base {
class DEF_DATA Color3 : public Base {
AUTOGEN_PREAMBLE_DATA
float r;
float g;
float b;
public:
Color3(float r, float g, float b);
DEF_DATA_CTOR Color3(float r, float g, float b);
Color3(const glm::vec3&);
~Color3();
static Color3 FromHex(std::string hex);
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
DEF_DATA_METHOD static Color3 FromHex(std::string hex);
virtual const Data::String ToString() const override;
std::string ToHex() const;
DEF_DATA_METHOD std::string ToHex() const;
virtual void Serialize(pugi::xml_node node) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static void PushLuaLibrary(lua_State*);
operator glm::vec3() const;
inline float R() const { return r; }
inline float G() const { return g; }
inline float B() const { return b; }
DEF_DATA_PROP inline float R() const { return r; }
DEF_DATA_PROP inline float G() const { return g; }
DEF_DATA_PROP inline float B() const { return b; }
};
}

View file

@ -4,6 +4,7 @@
#include "datatypes/ref.h"
#include "logger.h"
#include "panic.h"
#include <pugixml.hpp>
#include <variant>
Data::String Data::Variant::ToString() const {

View file

@ -8,6 +8,7 @@
#include "objects/base/instance.h"
#include "lua.h"
#include "objects/base/member.h"
#include <pugixml.hpp>
Data::InstanceRef::InstanceRef() {};
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};

View file

@ -7,7 +7,7 @@
class Instance;
namespace Data {
class InstanceRef : Base {
class InstanceRef : public Base {
std::weak_ptr<Instance> ref;
public:
InstanceRef();

View file

@ -1,8 +1,16 @@
#include "vector.h"
#include <cstdio>
#include <cstdlib>
#include <glm/ext/quaternion_geometric.hpp>
#include <iomanip>
#include <reactphysics3d/mathematics/Vector3.h>
#include <string>
#include "meta.h" // IWYU pragma: keep
#include "panic.h"
#include <pugixml.hpp>
#include "datatypes/base.h"
#include "datatypes/meta.h"
#include <sstream>
namespace rp = reactphysics3d;
Data::Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
@ -10,13 +18,6 @@ Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y,
Data::Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
Data::Vector3::~Vector3() = default;
const Data::TypeInfo Data::Vector3::TYPE = {
.name = "Vector3",
.deserializer = &Data::Vector3::Deserialize,
.fromString = &Data::Vector3::FromString,
};
const Data::TypeInfo& Data::Vector3::GetType() const { return Data::Vector3::TYPE; };
Data::Vector3 Data::Vector3::ZERO(0, 0, 0);
Data::Vector3 Data::Vector3::ONE(1, 1, 1);
@ -109,9 +110,4 @@ std::optional<Data::Variant> Data::Vector3::FromString(std::string string) {
}
return Data::Vector3(components[0], components[1], components[2]);
}
void Data::Vector3::PushLuaValue(lua_State* L) const {
// TODO:
panic();
}

View file

@ -1,61 +1,60 @@
#pragma once
#include "base.h"
#include <glm/ext/quaternion_geometric.hpp>
#include "datatypes/annotation.h"
#include <glm/ext/vector_float3.hpp>
#include <reactphysics3d/reactphysics3d.h>
#include <glm/geometric.hpp>
namespace rp = reactphysics3d;
namespace reactphysics3d { class Vector3; };
namespace Data {
class Vector3 : Base {
class DEF_DATA_(from_string) Vector3 : public Base {
AUTOGEN_PREAMBLE_DATA
glm::vec3 vector;
public:
Vector3();
Vector3(float x, float y, float z);
DEF_DATA_CTOR Vector3();
DEF_DATA_CTOR Vector3(float x, float y, float z);
Vector3(const glm::vec3&);
Vector3(const rp::Vector3&);
Vector3(const reactphysics3d::Vector3&);
~Vector3();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
static Data::Vector3 ZERO;
static Data::Vector3 ONE;
DEF_DATA_PROP static Data::Vector3 ZERO;
DEF_DATA_PROP static Data::Vector3 ONE;
virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node node) const override;
virtual void PushLuaValue(lua_State*) const override;
static Data::Variant Deserialize(pugi::xml_node node);
static std::optional<Data::Variant> FromString(std::string);
static void PushLuaLibrary(lua_State*);
operator glm::vec3() const;
operator rp::Vector3() const;
operator reactphysics3d::Vector3() const;
inline float X() const { return vector.x; }
inline float Y() const { return vector.y; }
inline float Z() const { return vector.z; }
inline float Magnitude() const { return glm::length(vector); }
inline Data::Vector3 Unit() const { return glm::normalize(vector); }
inline Data::Vector3 Abs() const { return glm::abs(vector); }
DEF_DATA_PROP inline float X() const { return vector.x; }
DEF_DATA_PROP inline float Y() const { return vector.y; }
DEF_DATA_PROP inline float Z() const { return vector.z; }
DEF_DATA_METHOD inline float Magnitude() const { return glm::length(vector); }
DEF_DATA_METHOD inline Data::Vector3 Unit() const { return glm::normalize(vector); }
DEF_DATA_METHOD inline Data::Vector3 Abs() const { return glm::abs(vector); }
Data::Vector3 Cross(Data::Vector3) const;
float Dot(Data::Vector3) const;
DEF_DATA_METHOD Data::Vector3 Cross(Data::Vector3) const;
DEF_DATA_METHOD float Dot(Data::Vector3) const;
// Operators
Data::Vector3 operator *(float) const;
Data::Vector3 operator /(float) const;
Data::Vector3 operator *(Data::Vector3) const; // Component-wise
Data::Vector3 operator +(Data::Vector3) const;
Data::Vector3 operator -(Data::Vector3) const;
Data::Vector3 operator -() const;
DEF_DATA_OP Data::Vector3 operator *(float) const;
DEF_DATA_OP Data::Vector3 operator /(float) const;
DEF_DATA_OP Data::Vector3 operator *(Data::Vector3) const; // Component-wise
DEF_DATA_OP Data::Vector3 operator +(Data::Vector3) const;
DEF_DATA_OP Data::Vector3 operator -(Data::Vector3) const;
DEF_DATA_OP Data::Vector3 operator -() const;
bool operator <(Data::Vector3) const;
bool operator >(Data::Vector3) const;
DEF_DATA_OP bool operator <(Data::Vector3) const;
DEF_DATA_OP bool operator >(Data::Vector3) const;
bool operator ==(Data::Vector3) const;
DEF_DATA_OP bool operator ==(Data::Vector3) const;
};
}

View file

@ -6,6 +6,7 @@
#include "error/instance.h"
#include "objects/base/member.h"
#include "objects/base/refstate.h"
#include "objects/datamodel.h"
#include "objects/meta.h"
#include "logger.h"
#include "panic.h"
@ -17,6 +18,7 @@
#include <string>
#include <utility>
#include <vector>
#include <pugixml.hpp>
#include "ptr_helpers.h"
// Static so that this variable name is "local" to this source file
@ -32,15 +34,6 @@ const InstanceType Instance::TYPE = {
// return &TYPE_;
// }
constexpr FieldCodec classNameCodec() {
return FieldCodec {
.write = nullptr,
.read = [](void* source) -> Data::Variant {
return Data::String(((const InstanceType*)source)->className);
},
};
}
Instance::Instance(const InstanceType* type) {
this->name = type->className;
}

View file

@ -10,7 +10,6 @@
#include <optional>
#include <string>
#include <vector>
#include <pugixml.hpp>
#include "error/instance.h"
#include "error/result.h"
@ -23,6 +22,8 @@ typedef std::shared_ptr<Instance>(*InstanceConstructor)();
class DataModel;
class Workspace;
namespace pugi { class xml_node; };
typedef int InstanceFlags;
// This instance should only be instantiated in special circumstances (i.e. by DataModel) and should be creatable directly via any API
const InstanceFlags INSTANCE_NOTCREATABLE = (InstanceFlags)1<<0;

View file

@ -1,48 +1,10 @@
#pragma once
#include "../../datatypes/base.h"
#include "datatypes/meta.h"
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include "datatypes/base.h"
#include <variant>
class Instance;
struct FieldCodec {
void (*write)(Data::Variant source, void* destination);
Data::Variant (*read)(void* source);
};
template <typename T, typename U>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
*(U*)destination = (U)source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return T(*(U*)source);
},
};
}
template <typename T>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
*(T*)destination = source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return *(T*)source;
},
};
}
template <typename T>
std::function<void(std::string name)> memberFunctionOf(void(T::*func)(std::string), T* obj) {
return std::bind(func, obj, std::placeholders::_1);
}
typedef int PropertyFlags;
const PropertyFlags PROP_HIDDEN = 1 << 0; // Hidden from the editor
@ -56,9 +18,10 @@ enum PropertyCategory {
PROP_CATEGORY_BEHAVIOR,
PROP_CATEGORY_PART,
PROP_CATEGORY_SURFACE,
PROP_CATEGORY_SURFACE_INPUT,
};
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE;
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE_INPUT;
struct PropertyMeta {
const Data::TypeInfo* type;

View file

@ -1,4 +1,5 @@
#include "service.h"
#include "objects/datamodel.h"
#include "logger.h"
#include "panic.h"
#include <memory>

View file

@ -2,8 +2,11 @@
// Services are top-level singletons and belong to a specific DataModel
// They serve one specific task and can be accessed using game:GetService
#include "objects/datamodel.h"
#include "objects/base/instance.h"
#include <memory>
class DataModel;
class Service : public Instance {
protected:
Service(const InstanceType* type);

View file

@ -5,9 +5,11 @@
#include "objects/base/service.h"
#include "objects/meta.h"
#include "objects/script/serverscriptservice.h"
#include "datatypes/meta.h"
#include "workspace.h"
#include "logger.h"
#include "panic.h"
#include <pugixml.hpp>
#include <cstdio>
#include <fstream>
#include <memory>

View file

@ -4,7 +4,7 @@
#include "../annotation.h"
#include <memory>
#include <optional>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include "datatypes/cframe.h"
//this is necessary ebcause we use std::weak_ptr<Part> without including it in this file
#ifdef __AUTOGEN_EXTRA_INCLUDES__

View file

@ -5,10 +5,12 @@
#include "objects/joint/jointinstance.h"
#include <memory>
namespace reactphysics3d { class HingeJoint; }
class DEF_INST Rotate : public JointInstance {
AUTOGEN_PREAMBLE
rp::HingeJoint* joint = nullptr;
reactphysics3d::HingeJoint* joint = nullptr;
virtual void buildJoint() override;
virtual void breakJoint() override;

View file

@ -0,0 +1,54 @@
#include "rotatev.h"
#include "objects/jointsservice.h"
#include "objects/part.h"
#include "objects/workspace.h"
#include "rendering/renderer.h"
#include <reactphysics3d/constraint/HingeJoint.h>
#define PI 3.14159
RotateV::RotateV(): JointInstance(&TYPE) {
}
RotateV::~RotateV() {
}
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
void RotateV::buildJoint() {
// Only if both parts are set, are not the same part, are part of a workspace, and are part of the same workspace, we build the joint
if (part0.expired() || part1.expired() || part0.lock() == part1.lock() || !workspaceOfPart(part0.lock()) || workspaceOfPart(part0.lock()) != workspaceOfPart(part1.lock())) return;
// Don't build the joint if we're not part of either a workspace or JointsService
if ((!GetParent() || GetParent().value()->GetClass() != &JointsService::TYPE) && !workspace()) return;
std::shared_ptr<Workspace> workspace = workspaceOfPart(part0.lock()).value();
if (!workspace->physicsWorld) return;
// Update Part1's rotation and cframe prior to creating the joint as reactphysics3d locks rotation based on how it
// used to be rather than specifying an anchor rotation, so whatever.
CFrame newFrame = part0.lock()->cframe * (c0 * c1.Inverse());
part1.lock()->cframe = newFrame;
workspace->SyncPartPhysics(part1.lock());
// Do NOT use Abs() in this scenario. For some reason that breaks it
rp::HingeJointInfo jointInfo(part0.lock()->rigidBody, part1.lock()->rigidBody, (part0.lock()->cframe * c0).Position(), -(part0.lock()->cframe * c0).LookVector().Unit());
jointInfo.isCollisionEnabled = false;
this->joint = dynamic_cast<rp::HingeJoint*>(workspace->physicsWorld->createJoint(jointInfo));
jointWorkspace = workspace;
// part1.lock()->rigidBody->getCollider(0)->setCollideWithMaskBits(0b10);
// part1.lock()->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
// part0.lock()->rigidBody->getCollider(0)->setCollideWithMaskBits(0b01);
// part0.lock()->rigidBody->getCollider(0)->setCollisionCategoryBits(0b01);
}
void RotateV::breakJoint() {
// If the joint doesn't exist, or its workspace expired (not our problem anymore), then no need to do anything
if (!this->joint || jointWorkspace.expired() || !jointWorkspace.lock()->physicsWorld) return;
jointWorkspace.lock()->physicsWorld->destroyJoint(this->joint);
this->joint = nullptr;
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "objects/annotation.h"
#include "objects/base/instance.h"
#include "objects/joint/jointinstance.h"
#include <memory>
namespace reactphysics3d { class HingeJoint; }
class DEF_INST RotateV : public JointInstance {
AUTOGEN_PREAMBLE
reactphysics3d::HingeJoint* joint = nullptr;
virtual void buildJoint() override;
virtual void breakJoint() override;
public:
RotateV();
~RotateV();
static inline std::shared_ptr<RotateV> New() { return std::make_shared<RotateV>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<RotateV>(); };
};

View file

@ -5,10 +5,12 @@
#include "objects/joint/jointinstance.h"
#include <memory>
namespace reactphysics3d { class FixedJoint; }
class DEF_INST Snap : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;
reactphysics3d::FixedJoint* joint = nullptr;
virtual void buildJoint() override;
virtual void breakJoint() override;

View file

@ -5,10 +5,12 @@
#include "objects/joint/jointinstance.h"
#include <memory>
namespace reactphysics3d { class FixedJoint; }
class DEF_INST Weld : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;
reactphysics3d::FixedJoint* joint = nullptr;
virtual void buildJoint() override;
virtual void breakJoint() override;

View file

@ -1,5 +1,6 @@
#include "jointsservice.h"
#include "workspace.h"
#include "datamodel.h"
#include <memory>
JointsService::JointsService(): Service(&TYPE) {

View file

@ -1,5 +1,8 @@
#include "meta.h"
#include "objects/joint/jointinstance.h"
#include "objects/joint/rotate.h"
#include "objects/joint/rotatev.h"
#include "objects/joint/weld.h"
#include "objects/jointsservice.h"
#include "objects/part.h"
#include "objects/joint/snap.h"
@ -7,6 +10,7 @@
#include "objects/script/scriptcontext.h"
#include "objects/script/serverscriptservice.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "Instance", &Instance::TYPE },
@ -14,6 +18,9 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "Part", &Part::TYPE },
{ "Snap", &Snap::TYPE },
{ "Weld", &Weld::TYPE },
{ "Rotate", &Rotate::TYPE },
{ "RotateV", &RotateV::TYPE },
{ "JointInstance", &JointInstance::TYPE },
{ "Script", &Script::TYPE },

View file

@ -7,6 +7,7 @@
#include "datatypes/vector.h"
#include "objects/base/member.h"
#include "objects/joint/rotate.h"
#include "objects/joint/rotatev.h"
#include "objects/joint/weld.h"
#include "objects/jointsservice.h"
#include "objects/joint/jointinstance.h"
@ -18,42 +19,6 @@
#include <memory>
#include <optional>
// template <typename T, typename U>
// constexpr FieldCodec fieldCodecOf() {
// return FieldCodec {
// .write = [](Data::Variant source, void* destination) {
// *(U*)destination = (U)source.get<T>();
// },
// .read = [](void* source) -> Data::Variant {
// return T(*(U*)source);
// },
// };
// }
constexpr FieldCodec cframePositionCodec() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
CFrame* cframe = static_cast<CFrame*>(destination);
*cframe = cframe->Rotation() + source.get<Vector3>();
},
.read = [](void* source) -> Data::Variant {
return static_cast<CFrame*>(source)->Position();
},
};
}
constexpr FieldCodec cframeRotationCodec() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
CFrame* cframe = static_cast<CFrame*>(destination);
*cframe = CFrame::FromEulerAnglesXYZ(source.get<Vector3>()) + cframe->Position();
},
.read = [](void* source) -> Data::Variant {
return static_cast<CFrame*>(source)->ToEulerAnglesXYZ();
},
};
}
Part::Part(): Part(PartConstructParams { .size = glm::vec3(2, 1.2, 4), .color = Color3(0.639216f, 0.635294f, 0.647059f) }) {
}
@ -93,7 +58,8 @@ void Part::onUpdated(std::string property) {
workspace().value()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
// When position/rotation/size is manually edited, break all joints, they don't apply anymore
BreakJoints();
if (property != "Anchored")
BreakJoints();
}
// Expands provided extents to fit point
@ -157,6 +123,32 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
return SurfaceSmooth; // Unreachable
}
float Part::GetSurfaceParamA(Vector3 face) {
// printVec(face);
// printf("Enum: %d\n", faceFromNormal(face));
switch (faceFromNormal(face)) {
case Top: return topParamA;
case Bottom: return bottomParamA;
case Right: return rightParamA;
case Left: return leftParamA;
case Front: return frontParamA;
case Back: return backParamA;
}
return 0; // Unreachable
}
float Part::GetSurfaceParamB(Vector3 face) {
switch (faceFromNormal(face)) {
case Top: return topParamB;
case Bottom: return bottomParamB;
case Right: return rightParamB;
case Left: return leftParamB;
case Front: return frontParamB;
case Back: return backParamB;
}
return 0; // Unreachable
}
bool Part::checkJointContinuity(std::shared_ptr<Part> otherPart) {
// Make sure that the two parts don't depend on one another
@ -214,6 +206,8 @@ std::optional<std::shared_ptr<JointInstance>> makeJointFromSurfaces(SurfaceType
return Snap::New();
if (a == SurfaceHinge)
return Rotate::New();
if (a == SurfaceMotor)
return RotateV::New();
return std::nullopt;
}
@ -257,7 +251,7 @@ void Part::MakeJoints() {
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
// If it is a hinge, only attach if actually touching the "hinge"
if (mySurface == SurfaceHinge && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue;
if ((mySurface == SurfaceHinge || mySurface == SurfaceMotor) && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue;
// Create contacts
// Contact always occurs at the center of Part0's surface (even if that point does not overlap both surfaces)

View file

@ -26,7 +26,7 @@ struct PartConstructParams {
bool locked = false;
};
class Snap;
class Workspace;
class DEF_INST_(explorer_icon="part") Part : public Instance {
AUTOGEN_PREAMBLE
@ -46,6 +46,7 @@ protected:
bool checkSurfacesTouching(CFrame surfaceFrame, Vector3 size, Vector3 myFace, Vector3 otherFace, std::shared_ptr<Part> otherPart);
friend JointInstance;
friend Workspace;
void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) override;
void onUpdated(std::string);
@ -64,7 +65,7 @@ public:
bool selected = false;
DEF_PROP_CATEGORY(BEHAVIOR)
DEF_PROP bool anchored = false;
DEF_PROP_(on_update=onUpdated) bool anchored = false;
DEF_PROP bool locked = false;
DEF_PROP_CATEGORY(SURFACE)
@ -75,10 +76,27 @@ public:
DEF_PROP SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType backSurface = SurfaceType::SurfaceSmooth;
DEF_PROP_CATEGORY(SURFACE_INPUT)
DEF_PROP float topParamA = -0.5;
DEF_PROP float bottomParamA = -0.5;
DEF_PROP float leftParamA = -0.5;
DEF_PROP float rightParamA = -0.5;
DEF_PROP float frontParamA = -0.5;
DEF_PROP float backParamA = -0.5;
DEF_PROP float topParamB = 0.5;
DEF_PROP float bottomParamB = 0.5;
DEF_PROP float leftParamB = 0.5;
DEF_PROP float rightParamB = 0.5;
DEF_PROP float frontParamB = 0.5;
DEF_PROP float backParamB = 0.5;
rp::RigidBody* rigidBody = nullptr;
inline SurfaceType GetSurfaceFromFace(NormalId face) { return surfaceFromFace(face); }
float GetSurfaceParamA(Vector3 face);
float GetSurfaceParamB(Vector3 face);
Part();
Part(PartConstructParams params);

View file

@ -4,6 +4,8 @@
#include "objects/base/member.h"
#include "objects/script/scriptcontext.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
#include "datatypes/ref.h"
#include "lua.h"
Script::Script(): Instance(&TYPE) {

View file

@ -1,5 +1,9 @@
#include "scriptcontext.h"
#include "datatypes/cframe.h"
#include "datatypes/meta.h"
#include "datatypes/vector.h"
#include "logger.h"
#include <cstdint>
#include <luajit-2.1/lauxlib.h>
#include <luajit-2.1/lua.h>
#include <luajit-2.1/lualib.h>
@ -39,6 +43,10 @@ void ScriptContext::InitService() {
// luaopen_debug(state);
luaopen_bit(state);
Data::Vector3::PushLuaLibrary(state);
Data::CFrame::PushLuaLibrary(state);
Data::Color3::PushLuaLibrary(state);
// TODO: custom os library
// Override print

View file

@ -1,6 +1,7 @@
#include "serverscriptservice.h"
#include "objects/script.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
ServerScriptService::ServerScriptService(): Service(&TYPE) {
}

View file

@ -1,8 +1,11 @@
#include "workspace.h"
#include "datatypes/vector.h"
#include "objects/base/instance.h"
#include "objects/jointsservice.h"
#include "objects/joint/jointinstance.h"
#include "objects/datamodel.h"
#include "physics/util.h"
#include <memory>
#include <reactphysics3d/engine/PhysicsCommon.h>
rp::PhysicsCommon* Workspace::physicsCommon = new rp::PhysicsCommon;
@ -102,6 +105,16 @@ void Workspace::PhysicsStep(float deltaTime) {
const rp::Transform& transform = part->rigidBody->getTransform();
part->cframe = CFrame(transform);
part->velocity = part->rigidBody->getLinearVelocity();
// part->rigidBody->enableGravity(true);
for (auto& joint : part->secondaryJoints) {
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
std::shared_ptr<JointInstance> motor = joint.lock()->CastTo<JointInstance>().expect();
float rate = motor->part0.lock()->GetSurfaceParamB(motor->c0.LookVector().Unit()) * 30;
// part->rigidBody->enableGravity(false);
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate);
}
}
}

View file

@ -2,11 +2,14 @@
#include "objects/annotation.h"
#include "objects/base/service.h"
#include <glm/ext/vector_float3.hpp>
#include <memory>
#include <reactphysics3d/body/RigidBody.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
namespace rp = reactphysics3d;
struct RaycastResult {
rp::Vector3 worldPoint;
rp::Vector3 worldNormal;
@ -28,6 +31,7 @@ class Part;
class Snap;
class Weld;
class Rotate;
class RotateV;
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
@ -41,6 +45,7 @@ class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
friend Snap;
friend Weld;
friend Rotate;
friend RotateV;
protected:
void InitService() override;
bool initialized = false;

View file

@ -17,6 +17,7 @@ enum SurfaceType {
SurfaceInlets = 4,
SurfaceUniversal = 5,
SurfaceHinge = 6,
SurfaceMotor = 7,
};
namespace Data { class Vector3; } using Data::Vector3;

View file

@ -13,7 +13,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(QScintilla)
find_package(QScintilla REQUIRED)
set(TS_FILES editor_en_US.ts)

View file

@ -13,6 +13,7 @@
#include "physics/util.h"
#include "rendering/renderer.h"
#include "rendering/shader.h"
#include "datatypes/meta.h"
#define PI 3.14159

View file

@ -3,12 +3,8 @@
#include "common.h"
#include "logger.h"
#include "objects/datamodel.h"
#include "objects/jointsservice.h"
#include "objects/joint/snap.h"
#include "objects/script.h"
#include "placedocument.h"
#include "script/scriptdocument.h"
#include <map>
#include <memory>
#include <qclipboard.h>
#include <qglobal.h>
@ -18,6 +14,7 @@
#include <qstylefactory.h>
#include <qstylehints.h>
#include <qmdisubwindow.h>
#include <pugixml.hpp>
#ifdef _NDEBUG
#define NDEBUG
@ -145,10 +142,6 @@ MainWindow::MainWindow(QWidget *parent)
placeDocument->init();
ui->mdiArea->setTabsClosable(true);
// auto script = Script::New();
// gWorkspace()->AddChild(script);
// ui->mdiArea->addSubWindow(new ScriptDocument(script));
}
void MainWindow::closeEvent(QCloseEvent* evt) {

View file

@ -1,6 +1,7 @@
#include "panes/propertiesview.h"
#include "common.h"
#include "datatypes/base.h"
#include "datatypes/meta.h"
#include "objects/base/member.h"
#include <QColorDialog>
@ -249,7 +250,8 @@ QStringList PROPERTY_CATEGORY_NAMES {
"Data",
"Behavior",
"Part",
"Surface"
"Surface",
"Surface Inputs",
};
QModelIndex PropertiesView::indexAt(const QPoint &point) const {
@ -330,6 +332,9 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
// Remove child-less categories
for (int i = 0; i <= PROPERTY_CATEGORY_MAX; i++) {
if (i == PROP_CATEGORY_SURFACE_INPUT)
propertyCategories[(PropertyCategory)i]->setExpanded(false);
if (propertyCategories[(PropertyCategory)i]->childCount() > 0) continue;
int idx = indexOfTopLevelItem(propertyCategories[(PropertyCategory)i]);
delete takeTopLevelItem(idx);

View file

@ -1,7 +1,9 @@
#include "placedocument.h"
#include "common.h"
#include "mainglwidget.h"
#include "mainwindow.h"
#include "objects/joint/snap.h"
#include "objects/script.h"
#include "rendering/surface.h"
#include <cstdio>
#include <memory>
@ -122,6 +124,12 @@ void PlaceDocument::init() {
// part0->backSurface = SurfaceWeld;
// part1->frontSurface = SurfaceWeld;
part0->backSurface = SurfaceHinge;
// part0->backSurface = SurfaceHinge;
part0->backSurface = SurfaceMotor;
// part1->frontSurface = SurfaceHinge;
std::shared_ptr<Script> script = Script::New();
gWorkspace()->AddChild(script);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openScriptDocument(script);
}

View file

@ -1,14 +1,37 @@
#include "scriptdocument.h"
#include <Qsci/qsciscintilla.h>
#include <Qsci/qscilexer.h>
#include <Qsci/qscilexerlua.h>
#include <Qsci/qsciscintillabase.h>
#include <Qsci/qscistyle.h>
#include <map>
#include <qboxlayout.h>
#include <qcolor.h>
#include <qfont.h>
#include <qdebug.h>
#include <qglobal.h>
#include <qlayout.h>
#include "objects/script.h"
std::map<int, const QColor> DARK_MODE_COLOR_SCHEME = {{
{QsciLexerLua::Comment, QColor("#808080")},
{QsciLexerLua::LineComment, QColor("#808080")},
{QsciLexerLua::Number, QColor("#6897BB")},
{QsciLexerLua::Keyword, QColor("#CC7832")},
{QsciLexerLua::String, QColor("#6A8759")},
{QsciLexerLua::Character, QColor("#6A8759")},
{QsciLexerLua::LiteralString, QColor("#6A8759")},
{QsciLexerLua::Preprocessor, QColor("#FF00FF")}, // Obsolete since Lua 4.0, but whatever
{QsciLexerLua::Operator, QColor("#FFFFFF")},
{QsciLexerLua::Identifier, QColor("#FFFFFF")},
{QsciLexerLua::UnclosedString, QColor("#6A8759")},
{QsciLexerLua::BasicFunctions, QColor("#CC7832")},
{QsciLexerLua::StringTableMathsFunctions, QColor("#CC7832")},
{QsciLexerLua::CoroutinesIOSystemFacilities, QColor("#CC7832")},
{QsciLexerLua::Label, QColor("#FFFFFF")},
}};
ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
script(script), QMdiSubWindow(parent) {
@ -38,6 +61,20 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
scintilla->setText(QString::fromStdString(script->source));
QsciLexerLua* lexer = new QsciLexerLua;
lexer->setFont(font);
scintilla->setLexer(lexer);
// Set color scheme
// https://stackoverflow.com/a/26318796/16255372
for (auto& [style, color] : DARK_MODE_COLOR_SCHEME) {
lexer->setColor(color, style);
}
// lexer->setAutoIndentStyle(QsciScintilla::AiOpening | QsciScintilla::AiMaintain | QsciScintilla::AiClosing);
// scintilla->setAutoIndent(true);
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

5
tools/incltree.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/bash
(cd core/src; cinclude2dot --paths > /tmp/tree.dot)
dot -Tsvg /tmp/tree.dot -o /tmp/tree.svg
inkscape /tmp/tree.svg &