refactor(autogen): changed the way props and instances are defined via macros

This commit is contained in:
maelstrom 2025-04-30 18:16:35 +02:00
parent fc998a1c3f
commit e5f543ef4a
15 changed files with 116 additions and 67 deletions

View file

@ -137,10 +137,17 @@ std::string findBaseClass(CXCursor cur) {
return baseClass;
}
std::string currentCategory = "";
void processField(CXCursor cur, ClassAnalysis* state) {
std::optional<std::string> propertyDef = findAnnotation(cur, "OB::def_prop");
if (!propertyDef) return;
// Update current category
std::optional<std::string> categoryDef = findAnnotation(cur, "OB::def_prop_category");
if (categoryDef) {
currentCategory = parseAnnotationString(categoryDef.value())["category"];
}
PropertyAnalysis anly;
auto result = parseAnnotationString(propertyDef.value());
@ -150,6 +157,15 @@ void processField(CXCursor cur, ClassAnalysis* state) {
anly.fieldName = fieldName;
anly.category = result["category"];
anly.onUpdateCallback = result["on_update"];
if (anly.category == "")
anly.category = currentCategory;
// if name field is not provided, use fieldName instead, but capitalize the first character
if (anly.name == "") {
anly.name = fieldName;
anly.name[0] = std::toupper(anly.name[0]);
}
if (result.count("hidden"))
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Hidden;
@ -234,6 +250,7 @@ void processClass(CXCursor cur, AnalysisState* state, std::string className, std
anly.explorerIcon = result["explorer_icon"];
// Find annotated fields
currentCategory = "";
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
CXCursorKind kind = clang_getCursorKind(cur);
if (kind != CXCursor_FieldDecl) return CXChildVisit_Continue;

View file

@ -2,26 +2,34 @@
// Markers for the autogen engine to generate getters, setters, lua, etc.
// Base macros
#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 def_prop_category(...) clang::annotate("OB::def_prop_category", #__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 def_prop_category(...)
#define cframe_position_prop(...)
#define cframe_rotation_prop(...)
#endif
// Helper macros
#define DEF_INST [[ def_inst() ]]
#define DEF_INST_(...) [[ def_inst(__VA_ARGS__) ]]
#define DEF_INST_ABSTRACT [[ def_inst(abstract) ]]
#define DEF_INST_ABSTRACT_(...) [[ def_inst(__VA_ARGS__, abstract) ]]
#define DEF_INST_SERVICE [[ def_inst(service) ]]
#define DEF_INST_SERVICE_(...) [[ def_inst(__VA_ARGS__, service) ]]
#define DEF_PROP [[ def_prop() ]]
#define DEF_PROP_(...) [[ def_prop(__VA_ARGS__) ]]
// Categories
#define DEF_PROP_CATEGORY(CATEGORY) [[ def_prop_category(category=CATEGORY) ]]
#define AUTOGEN_PREAMBLE \
protected: \
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name) override; \

View file

@ -13,7 +13,7 @@ class DataModel;
class Service;
// The root instance to all objects in the hierarchy
class INSTANCE_WITH(abstract) DataModel : public Instance {
class DEF_INST_(abstract) DataModel : public Instance {
AUTOGEN_PREAMBLE
private:
void DeserializeService(pugi::xml_node node);

View file

@ -31,7 +31,7 @@ enum HandlesType {
RotateHandles,
};
class INSTANCE_WITH(abstract) Handles : public Instance {
class DEF_INST_(abstract) Handles : public Instance {
AUTOGEN_PREAMBLE
public:
bool nixAxes = false; // XYZ -> ZXY, used with rotation

View file

@ -14,7 +14,7 @@
class Part;
class Workspace;
class INSTANCE_WITH(abstract) JointInstance : public Instance {
class DEF_INST_ABSTRACT JointInstance : public Instance {
AUTOGEN_PREAMBLE
std::weak_ptr<Part> oldPart0;
@ -31,14 +31,10 @@ protected:
virtual void breakJoint() = 0;
public:
[[ def_prop(name="Part0", on_update=onUpdated) ]]
std::weak_ptr<Part> part0;
[[ def_prop(name="Part1", on_update=onUpdated) ]]
std::weak_ptr<Part> part1;
[[ def_prop(name="C0", on_update=onUpdated) ]]
CFrame c0;
[[ def_prop(name="C1", on_update=onUpdated) ]]
CFrame c1;
DEF_PROP_(on_update=onUpdated) std::weak_ptr<Part> part0;
DEF_PROP_(on_update=onUpdated) std::weak_ptr<Part> part1;
DEF_PROP_(on_update=onUpdated) CFrame c0;
DEF_PROP_(on_update=onUpdated) CFrame c1;
JointInstance(const InstanceType*);
~JointInstance();

View file

@ -5,7 +5,7 @@
#include "objects/joint/jointinstance.h"
#include <memory>
class INSTANCE Rotate : public JointInstance {
class DEF_INST Rotate : public JointInstance {
AUTOGEN_PREAMBLE
rp::HingeJoint* joint = nullptr;

View file

@ -5,7 +5,7 @@
#include "objects/joint/jointinstance.h"
#include <memory>
class INSTANCE Snap : public JointInstance {
class DEF_INST Snap : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;

View file

@ -5,7 +5,7 @@
#include "objects/joint/jointinstance.h"
#include <memory>
class INSTANCE Weld : public JointInstance {
class DEF_INST Weld : public JointInstance {
AUTOGEN_PREAMBLE
rp::FixedJoint* joint = nullptr;

View file

@ -3,7 +3,7 @@
#include "objects/annotation.h"
#include "objects/base/service.h"
class INSTANCE_SERVICE() JointsService : public Service {
class DEF_INST_SERVICE JointsService : public Service {
AUTOGEN_PREAMBLE
private:
std::optional<std::shared_ptr<Workspace>> jointWorkspace();

View file

@ -28,7 +28,7 @@ struct PartConstructParams {
class Snap;
class INSTANCE_WITH(explorer_icon="part") Part : public Instance {
class DEF_INST_(explorer_icon="part") Part : public Instance {
AUTOGEN_PREAMBLE
protected:
// Joints where this part is Part0
@ -50,36 +50,33 @@ protected:
void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) override;
void onUpdated(std::string);
public:
[[ def_prop(name="Velocity", on_update=onUpdated) ]]
Vector3 velocity;
DEF_PROP_CATEGORY(DATA)
DEF_PROP_(on_update=onUpdated) Vector3 velocity;
[[ def_prop(name="CFrame", on_update=onUpdated), cframe_position_prop(name="Position"), cframe_rotation_prop(name="Rotation") ]]
CFrame cframe;
[[ def_prop(name="Size", category=PART, on_update=onUpdated) ]]
glm::vec3 size;
[[ def_prop(name="Color", category=APPEARANCE) ]]
Color3 color;
[[ def_prop(name="Transparency", category=APPEARANCE) ]]
float transparency = 0.f;
DEF_PROP_CATEGORY(PART)
DEF_PROP_(on_update=onUpdated) glm::vec3 size;
DEF_PROP_CATEGORY(APPEARANCE)
DEF_PROP Color3 color;
DEF_PROP float transparency = 0.f;
bool selected = false;
[[ def_prop(name="Anchored", category=BEHAVIOR, on_update=onUpdated) ]]
bool anchored = false;
[[ def_prop(name="Locked", category=BEHAVIOR) ]]
bool locked = false;
rp::RigidBody* rigidBody = nullptr;
DEF_PROP_CATEGORY(BEHAVIOR)
DEF_PROP bool anchored = false;
DEF_PROP bool locked = false;
[[ def_prop(name="TopSurface", category=SURFACE) ]]
SurfaceType topSurface = SurfaceType::SurfaceStuds;
[[ def_prop(name="BottomSurface", category=SURFACE) ]]
SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
[[ def_prop(name="LeftSurface", category=SURFACE) ]]
SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="RightSurface", category=SURFACE) ]]
SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="FrontSurface", category=SURFACE) ]]
SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
[[ def_prop(name="BackSurface", category=SURFACE) ]]
SurfaceType backSurface = SurfaceType::SurfaceSmooth;
DEF_PROP_CATEGORY(SURFACE)
DEF_PROP SurfaceType topSurface = SurfaceType::SurfaceStuds;
DEF_PROP SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
DEF_PROP SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
DEF_PROP SurfaceType backSurface = SurfaceType::SurfaceSmooth;
rp::RigidBody* rigidBody = nullptr;
inline SurfaceType GetSurfaceFromFace(NormalId face) { return surfaceFromFace(face); }

View file

@ -4,7 +4,7 @@
#include "objects/base/instance.h"
#include <memory>
class INSTANCE_WITH(explorer_icon="script") Script : public Instance {
class DEF_INST_(explorer_icon="script") Script : public Instance {
AUTOGEN_PREAMBLE
public:
Script();

View file

@ -4,7 +4,7 @@
#include "objects/base/service.h"
#include "lua.h"
class INSTANCE_SERVICE() ScriptContext : public Service {
class DEF_INST_SERVICE ScriptContext : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;

View file

@ -5,7 +5,7 @@
// Container class for server scripts
// Also handles/manages running server scripts on run
class INSTANCE_SERVICE() ServerScriptService : public Service {
class DEF_INST_SERVICE ServerScriptService : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;

View file

@ -31,7 +31,7 @@ class Rotate;
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
class INSTANCE_SERVICE(explorer_icon="workspace") Workspace : public Service {
class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
AUTOGEN_PREAMBLE
rp::PhysicsWorld* physicsWorld = nullptr;

View file

@ -44,10 +44,12 @@ Finally, create a build target that depends on your `AUTOGEN_OUTS`, and make `AL
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) ]]`
- `DEF_INST` - Simply annotate with `def_inst`, do not do anything more. Equivalent to `[[ def_inst() ]]`
- `DEF_INST_(...)` - Same as above, but allows you to pass in arguments to `def_inst`. Equivalent to `[[ def_inst(...) ]]`
- `DEF_INST_SERVICE` - Annotates with `def_inst`, but passes `service` as an argument, marking the instance as a service.
- `DEF_INST_SERVICE_(...)` - Same as above, but lets you add additional arguments
- `DEF_INST_ABSTRACT - Annotates with `def_isnt`, but passes `abstract`
- `DEF_INST_ABSTRACT_(...) - Same as above, but let's you pass additional arguments
Then, there is the command itself:
@ -71,7 +73,7 @@ Properties are also a key component. Inside every class annotated with `def_inst
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)
- `name` - Option, name of the property. If this is not specified, it will use the field's name, with the first letter capitalized
- `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
@ -80,6 +82,11 @@ Here are its parameters:
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)
Similarly to `def_inst`, `def_prop` also has its aliases:
- `DEF_PROP` - Annotates the field with `def_prop`. Equivalent to `[[ def_prop() ]]`
- `DEF_PROP_(...)` - Same as above, but allows you to pass in arguments. Equivalent to `[[ def_prop(...) ]]`
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=)
@ -87,26 +94,50 @@ In Part, it is necessary to expose the position and rotation components of the C
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.
Setting the category of each can be cumbersome and annoying. Rather than setting it on each field, you can use `DEF_PROP_CATEGORY` to apply the specified
category to the current field and all that follow it.
Sometimes, you will need to reference another object in your header. For instance, if you use a specific object type for a `weak_ptr` (e.g.
`std::weak_ptr<Part>`). Doing so with an `#include` will unnecessarily add a dependency into the build chain for that translation unit. Instead, you may
opt to use a forward-declaration before your class declaration to avoid this overhead.
However, Autogen cannot tell where this class came from to automatically include its header in the generated file. So, you must include its header in an `#ifdef`
block via `__AUTOGEN_EXTRA_INCLUDES__`:
#ifdef __AUTOGEN_EXTRA_INCLUDES__
#include "objects/part.h"
#endif
class Part;
class DEF_INST SomeObject {
AUTOGEN_PREAMBLE
public:
DEF_PROP std::weak_ptr<Part> myPart;
}
Autogen will automatically define `__AUTOGEN_EXTRA_INCLDUES__` before including `SomeObject`'s header file, so the `#ifdef` block will evaluate, pulling in
the necessary dependencies
### Example
Here is an example of an instance annotated using Autogen
class INSTANCE Part {
class DEF_INST Part {
AUTOGEN_PREAMBLE
public:
[[ def_prop(name="Color") ]]
Color3 color;
DEF_PROP_CATEGORY("APPEARANCE")
DEF_PROP Color3 color;
DEF_PROP CFrame cframe;
[[ def_prop(name="CFrame"), cframe_position_prop(name="Position") ]]
CFrame cframe;
[[ def_prop(name="ReadOnly", readonly) ]]
DEF_PROP_CATEGORY("DATA")
DEF_PROP_(name="ReadOnly", readonly)
int readOnlyValue;
[[ def_prop(name="Ephemeral", no_save) ]]
DEF_PROP_(no_save)
std::string ephemeral;
[[ def_prop(name="SuperSecretValue", no_save, hidden) ]]
DEF_PROP_(name="SuperSecretValue", no_save, hidden)
std::string superSecret;
};