feat(autogen): add meta and cframe

This commit is contained in:
maelstrom 2025-04-26 11:54:53 +02:00
parent 8f20c11b36
commit b8ee828d29
7 changed files with 152 additions and 14 deletions

View file

@ -163,6 +163,40 @@ void processField(CXCursor cur, ClassAnalysis* state) {
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str(); anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str();
state->properties.push_back(anly); 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) { void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {

View file

@ -17,13 +17,20 @@ enum PropertyFlags {
PropertyFlag_Readonly = 1 << 3, PropertyFlag_Readonly = 1 << 3,
}; };
enum CFrameMember {
CFrameMember_None, // Not part of CFrame
CFrameMember_Position,
CFrameMember_Rotation
};
struct PropertyAnalysis { struct PropertyAnalysis {
std::string name; std::string name;
std::string fieldName; std::string fieldName;
CFrameMember cframeMember = CFrameMember_None; // for cframe_position_prop etc.
std::string backingFieldType; std::string backingFieldType;
std::string onUpdateCallback; std::string onUpdateCallback;
std::string category; std::string category;
PropertyFlags flags; PropertyFlags flags = (PropertyFlags)0;
}; };
// https://stackoverflow.com/a/1448478/16255372 // https://stackoverflow.com/a/1448478/16255372

View file

@ -4,6 +4,14 @@
#include <string> #include <string>
#include <variant> #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 = { std::map<std::string, std::string> MAPPED_TYPE = {
{ "bool", "Data::Bool" }, { "bool", "Data::Bool" },
{ "int", "Data::Int" }, { "int", "Data::Int" },
@ -27,6 +35,19 @@ std::string castFromVariant(std::string valueStr, std::string fieldType) {
return valueStr + ".get<" + (!mappedType.empty() ? mappedType : 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 + ")";
}
std::string mappedType = MAPPED_TYPE[fieldType];
if (!mappedType.empty()) {
return mappedType + "(" + valueStr + ")";
}
return valueStr;
}
void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) { void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {"; out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {";
@ -37,8 +58,14 @@ void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
if (prop.flags & PropertyFlag_Readonly) { if (prop.flags & PropertyFlag_Readonly) {
out << "\n return AssignToReadOnlyMember(\"" << state.name << "\", name)"; out << "\n return AssignToReadOnlyMember(\"" << state.name << "\", 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 { } else {
out << "\n this->" << prop.fieldName << " = " << castFromVariant("value", prop.backingFieldType) << ";"; out << "\n this->" << prop.fieldName << " = " << castFromVariant("value", prop.backingFieldType) << ";";
if (!prop.onUpdateCallback.empty())
out << "\n " << prop.onUpdateCallback << "(name);";
} }
out << "\n }"; out << "\n }";
@ -50,6 +77,69 @@ void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
out << "\n};\n\n"; 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 MemberNotFound(\"" << state.name << "\", name);";
out << "\n};\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";
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 MemberNotFound(\"" << state.name << "\", name);";
out << "\n};\n\n";
}
void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) { void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
std::string strFlags; std::string strFlags;
if (state.flags & ClassFlag_NotCreatable) if (state.flags & ClassFlag_NotCreatable)
@ -61,6 +151,10 @@ void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
if (!strFlags.empty()) strFlags = strFlags.substr(3); // Remove leading pipe if (!strFlags.empty()) strFlags = strFlags.substr(3); // Remove leading pipe
else strFlags = "0"; // 0 == No option 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";
out << "#include \"" << state.headerPath << "\"\n\n"; out << "#include \"" << state.headerPath << "\"\n\n";
out << "const InstanceType " << state.name << "::TYPE = {\n" out << "const InstanceType " << state.name << "::TYPE = {\n"
<< " .super = &" << state.baseClass << "::TYPE,\n" << " .super = &" << state.baseClass << "::TYPE,\n"
@ -75,4 +169,6 @@ void writeCodeForClass(std::ofstream& out, ClassAnalysis& state) {
<< "};\n\n"; << "};\n\n";
writePropertySetHandler(out, state); writePropertySetHandler(out, state);
writePropertyGetHandler(out, state);
writePropertyMetaHandler(out, state);
} }

View file

@ -245,6 +245,11 @@ result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name)
} }
} }
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) { return MemberNotFound(GetClass()->className, name); }
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Data::Variant value) { return MemberNotFound(GetClass()->className, name); }
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) { return MemberNotFound(GetClass()->className, name); }
void Instance::UpdateProperty(std::string name) { void Instance::UpdateProperty(std::string name) {
PropertyMeta meta = GetPropertyMeta(name).expect(); PropertyMeta meta = GetPropertyMeta(name).expect();
if (!meta.updateCallback) return; // Nothing to update, exit. if (!meta.updateCallback) return; // Nothing to update, exit.
@ -282,7 +287,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

@ -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,10 +61,7 @@ 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;
}; };

View file

@ -74,13 +74,13 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(CFrame::FromEule
.type = &Vector3::TYPE, .type = &Vector3::TYPE,
.codec = cframePositionCodec(), .codec = cframePositionCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this), .updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE .flags = PROP_NOSAVE
}}, { "Rotation", { }}, { "Rotation", {
.backingField = &cframe, .backingField = &cframe,
.type = &Vector3::TYPE, .type = &Vector3::TYPE,
.codec = cframeRotationCodec(), .codec = cframeRotationCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this), .updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE .flags = PROP_NOSAVE
}}, { "Velocity", { }}, { "Velocity", {
.backingField = &velocity, .backingField = &velocity,
.type = &Vector3::TYPE, .type = &Vector3::TYPE,

View file

@ -52,7 +52,7 @@ public:
const static InstanceType TYPE; const static InstanceType TYPE;
Vector3 velocity; Vector3 velocity;
[[ def_prop(name="cframe"), cframe_position_prop(name="Position"), cframe_rotation_prop(name="Rotation") ]] [[ def_prop(name="CFrame"), cframe_position_prop(name="Position"), cframe_rotation_prop(name="Rotation") ]]
CFrame cframe; CFrame cframe;
[[ def_prop(name="Size", category=PART) ]] [[ def_prop(name="Size", category=PART) ]]
glm::vec3 size; glm::vec3 size;