Skip to content

Commit faeec29

Browse files
committed
Improve the components & change detection manuals
1 parent fb56158 commit faeec29

2 files changed

Lines changed: 76 additions & 36 deletions

File tree

documentation/ChangeDetection.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
# Change Detection
2+
13
## Change Detection
24

35
To avoid the error-prone manual management of `HasChanged*` tags, Stagehand provides zero-overhead abstractions that automatically toggle these tags when components are modified.
46

57
To enable these features, components must be defined using Stagehand's macros that are described in the Components manual.
68

9+
### How Change Tags Work
10+
11+
For every component defined with a change-tracking macro, Stagehand automatically generates an empty tag struct named `HasChanged<Name>`. For example, `FLOAT(Health)` generates `HasChangedHealth`. This tag is registered with Flecs using `flecs::With`, so it is added to every entity that has the component and is disabled by default. When a modification is detected, Stagehand enables the tag on the affected entity. At the end of each frame (in the `PostRender` pipeline phase), a built-in Stagehand system automatically disables all change tags on all entities, giving them per-frame semantics.
12+
713
Change tracking can be enabled or disabled at component definition time:
814

915
- Use normal macros (e.g. `FLOAT`, `GODOT_VARIANT`, `VECTOR`) to enable change tracking.
@@ -48,10 +54,16 @@ world.system<Position, Velocity>()
4854
Another system can then match on the relevant change detection tag to skip unnecessary work. For example:
4955
5056
```cpp
51-
// ...
52-
world.system<Position, const HasChangedVelocity>()
53-
.each([](stagehand::entity e, Position& position, Velocity& velocity) {
54-
e.set()
57+
// This system only runs for entities whose Velocity was modified this frame.
58+
world.system<Position, const Velocity>()
59+
.with<HasChangedVelocity>()
60+
.each([](stagehand::entity e, Position& position, const Velocity& velocity) {
61+
// Velocity changed — update any dependent state.
62+
e.modify(position, [&velocity](Position& p) {
63+
p.x += velocity.x;
64+
p.y += velocity.y;
65+
p.z += velocity.z;
66+
});
5567
});
5668
```
5769

documentation/Components.md

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,106 @@
1-
# Stagehand ECS Patterns & Best Practices
1+
# Defining and Registering Components
22

3-
Stagehand introduces facilities and patterns to streamline working with components.
3+
Through macros, boilerplate-free component definition and registration of components is possible. The macros also handle the per-component setup of [automatic change detection](ChangeDetection.md) behind the scenes. Appending an underscore to a macro name (e.g., `FLOAT_` instead of `FLOAT`) opts out of change detection for that component.
44

5-
## Defining and Registering Components
5+
## Manually defined & registered components
66

7-
Through macros, boilerplate-free component definition and registration of components is possible. The macros also handle the per-component setup of [automatic change detection](ChangeDetection.md) behind the scenes.
7+
When none of the macro categories below fit your needs — for example, when defining a component that inherits from an arbitrary base class or requires custom Flecs hooks — you can register components manually using the `REGISTER` macro together with the Flecs and Stagehand APIs directly.
88

9+
```cpp
10+
// 1. Define your struct.
11+
struct Velocity {
12+
float x = 0.0f;
13+
float y = 0.0f;
14+
float z = 0.0f;
15+
};
16+
17+
// 2. Register it. stagehand::register_component<T>() enables GDScript get/set access.
18+
REGISTER([](flecs::world& world) {
19+
world.component<Velocity>()
20+
.member<float>("x")
21+
.member<float>("y")
22+
.member<float>("z");
23+
stagehand::register_component<Velocity>("Velocity");
24+
});
25+
```
926
10-
### Manually defined & registered components
11-
27+
Change detection is not set up automatically with manual registration. To add it, declare a tag struct and call `stagehand::internal::register_change_detection_for_component<T, ChangeTag>(world)` inside your `REGISTER` block.
1228
13-
### Primitive C++ Types
29+
## Primitive C++ Types
1430
15-
#### `FLOAT(Name, ...)` / `FLOAT_(Name, ...)` - 32-bit (single precision) floating point number
31+
### `FLOAT(Name, ...)` / `FLOAT_(Name, ...)` - 32-bit (single precision) floating point number
1632
```cpp
1733
FLOAT(Health); // Default-initialised to 0.0f.
1834
FLOAT(MaxHealth, 100.0f); // Initialised with a default value.
1935
```
2036

21-
#### `DOUBLE(Name, ...)` / `DOUBLE_(Name, ...)` - 64-bit (double precision) floating point number
37+
### `DOUBLE(Name, ...)` / `DOUBLE_(Name, ...)` - 64-bit (double precision) floating point number
2238
```cpp
2339
DOUBLE(Gravity); // Default-initialised to 0.0.
2440
DOUBLE(Pi, 3.14159265359); // Initialised with a default value.
2541
```
2642

27-
#### `INT8(Name, ...)` / `INT8_(Name, ...)` - 8-bit signed integer
43+
### `INT8(Name, ...)` / `INT8_(Name, ...)` - 8-bit signed integer
2844
```cpp
2945
INT8(Level); // Default-initialised to 0.
3046
INT8(MaxLevel, 99); // Initialised with a default value.
3147
```
3248

33-
#### `UINT8(Name, ...)` / `UINT8_(Name, ...)` - 8-bit unsigned integer
49+
### `UINT8(Name, ...)` / `UINT8_(Name, ...)` - 8-bit unsigned integer
3450
```cpp
3551
UINT8(Lives); // Default-initialised to 0.
3652
UINT8(MaxLives, 3); // Initialised with a default value.
3753
```
3854

39-
#### `INT16(Name, ...)` / `INT16_(Name, ...)` - 16-bit signed integer
55+
### `INT16(Name, ...)` / `INT16_(Name, ...)` - 16-bit signed integer
4056
```cpp
4157
INT16(Damage); // Default-initialised to 0.
4258
INT16(BaseDamage, 10); // Initialised with a default value.
4359
```
4460

45-
#### `UINT16(Name, ...)` / `UINT16_(Name, ...)` - 16-bit unsigned integer
61+
### `UINT16(Name, ...)` / `UINT16_(Name, ...)` - 16-bit unsigned integer
4662
```cpp
4763
UINT16(Ammo); // Default-initialised to 0.
4864
UINT16(MaxAmmo, 250); // Initialised with a default value.
4965
```
5066

51-
#### `INT32(Name, ...)` / `INT32_(Name, ...)` - 32-bit signed integer
67+
### `INT32(Name, ...)` / `INT32_(Name, ...)` - 32-bit signed integer
5268
```cpp
5369
INT32(Score); // Default-initialised to 0.
5470
INT32(HighScore, 10000); // Initialised with a default value.
5571
```
5672

57-
#### `UINT32(Name, ...)` / `UINT32_(Name, ...)` - 32-bit unsigned integer
73+
### `UINT32(Name, ...)` / `UINT32_(Name, ...)` - 32-bit unsigned integer
5874
```cpp
5975
UINT32(Gold); // Default-initialised to 0.
6076
UINT32(StartGold, 50); // Initialised with a default value.
6177
```
6278

63-
#### `INT64(Name, ...)` / `INT64_(Name, ...)` - 64-bit signed integer
79+
### `INT64(Name, ...)` / `INT64_(Name, ...)` - 64-bit signed integer
6480
```cpp
65-
INT64(ParticleCount); // Default-initialised to 0.
81+
INT64(AHugeSignedNumber); // Default-initialised to 0.
6682
INT64(AVeryLowInteger, -135761385678932632); // Initialised with a default value.
6783
```
6884

69-
#### `UINT64(Name, ...)` / `UINT64_(Name, ...)` - 64-bit unsigned integer
85+
### `UINT64(Name, ...)` / `UINT64_(Name, ...)` - 64-bit unsigned integer
7086
```cpp
71-
UINT64(IDontEvenKnowWhenIWouldUseThis); // Default-initialised to 0.
87+
UINT64(ParticleCount); // Default-initialised to 0.
7288
UINT64(AVeryHighInteger, 53925713520814); // Initialised with a default value.
7389
```
7490

75-
### Other C++ Types
91+
## Other C++ Types
7692

77-
#### `TAG(Name)` / `TAG_(Name)`
93+
### `TAG(Name)` / `TAG_(Name)`
94+
95+
Empty marker component for entity classification and filtering. Tags carry no data; change detection does not apply to them.
7896
```cpp
7997
TAG(IsPlayer); // Defines a tag component.
80-
TAG_(IsGrounded); // Alias for TAG, no change detection applicable for tags.
98+
TAG_(IsGrounded); // Alias for TAG; change detection is not applicable to tags.
8199
```
82100

83-
#### `ENUM(Name, [UnderlyingType])` / `ENUM_(Name, [UnderlyingType])`
101+
### `ENUM(Name, [UnderlyingType])` / `ENUM_(Name, [UnderlyingType])`
102+
103+
Registers an existing C++ `enum class` as an ECS component. The optional `UnderlyingType` tells Stagehand which integer type to use for GDScript serialisation; it should match the enum's declared underlying type (defaults to `uint8_t`).
84104
```cpp
85105
enum class AIState { Idle, Patrol, Chase };
86106
ENUM(AIState); // Defaults to uint8_t underlying type.
@@ -92,38 +112,46 @@ enum class Weather { Sunny, Rainy };
92112
ENUM_(Weather); // Opt out of change detection.
93113
```
94114
95-
#### `POINTER(Name, Type, [DefaultValue])` / `POINTER_(Name, Type, [DefaultValue])`
115+
### `POINTER(Name, Type, [DefaultValue])` / `POINTER_(Name, Type, [DefaultValue])`
116+
117+
Component wrapping a raw `Type*` pointer to an external object. Default-initialises to `nullptr` unless a value is provided.
96118
```cpp
97119
POINTER(CurrentTarget, Targetable); // Default-initialised to nullptr.
98120
POINTER(GameWindow, void, nullptr); // Initialised with an explicit default value.
99121
```
100122

101-
#### `VECTOR(Name, ElementType, [Initialiser])` / `VECTOR_(Name, ElementType, [Initialiser])`
123+
### `VECTOR(Name, ElementType, [Initialiser])` / `VECTOR_(Name, ElementType, [Initialiser])`
124+
125+
Dynamic-size array component backed by `std::vector<ElementType>`.
102126
```cpp
103127
VECTOR(PathNodes, godot::Vector3); // Default-initialised to an empty vector.
104128
VECTOR(ItemIDs, int, {10, 23, 42}); // Initialised with a default list.
105129
```
106130
107-
#### `ARRAY(Name, ElementType, Size, [Initialiser])` / `ARRAY_(Name, ElementType, Size, [Initialiser])`
131+
### `ARRAY(Name, ElementType, Count, [Initialiser])` / `ARRAY_(Name, ElementType, Count, [Initialiser])`
132+
133+
Fixed-size array component backed by `std::array<ElementType, Count>`. `Count` must be a compile-time constant.
108134
```cpp
109135
ARRAY(InputBuffer, uint8_t, 8); // An array of 8 elements that are default-initialised.
110136
ARRAY(ModelView, float, 4, {1,0,0,1}); // Initialised with a default list.
111137
```
112138

113-
#### `STRUCT(Name, { ... })` / `STRUCT_(Name, { ... })`
139+
### `STRUCT(Name, { ... })` / `STRUCT_(Name, { ... })`
140+
141+
Multi-field aggregate struct component with automatic member reflection via Boost.PFR. All fields are individually registered with Flecs and exposed to GDScript as a `Dictionary`. The struct must be an aggregate type: no user-declared constructors, no virtual functions, and no private or protected non-static data members. All field types must be individually convertible to `godot::Variant`.
114142
```cpp
115143
STRUCT(PlayerStats, { float age; int rank; }); // Members are default-initialised.
116144
STRUCT(ColoredPoint, { godot::Vector3 position = godot::Vector3(0, 0, 0); godot::Color color = godot::Color(1, 1, 1); }); // Members initialised with default values.
117145
STRUCT_(TrackedPosition, { float x = 0.0f; float y = 0.0f; float z = 0.0f; }); // Opt out of change detection.
118146
```
119147
120-
### Godot Variants
148+
## Godot Variants
121149
122-
#### `GODOT_VARIANT(Name, Type, [DefaultValue])` / `GODOT_VARIANT_(Name, Type, [DefaultValue])`
150+
### `GODOT_VARIANT(Name, Type, [DefaultValue])` / `GODOT_VARIANT_(Name, Type, [DefaultValue])`
123151
124-
These macros wrap Godot's built-in Variant types as components. They support both struct-based (Plain Old Data) types and class-based (handle) types.
152+
These macros wrap Godot's built-in Variant types as components. They support both struct-based (Plain Old Data) types and class-based (handle) types. `GODOT_VARIANT` includes change detection; `GODOT_VARIANT_` opts out of it.
125153
126-
#### Struct-based (Plain Old Data) Variants
154+
### Struct-based (Plain Old Data) Variants
127155
```cpp
128156
// Color
129157
GODOT_VARIANT(BackgroundColor, godot::Color); // Default-initialised.
@@ -190,7 +218,7 @@ GODOT_VARIANT(CameraProjection, godot::Projection);
190218
GODOT_VARIANT(CustomProjection, godot::Projection, godot::Projection());
191219
```
192220

193-
#### Class-based (handle) Variants
221+
### Class-based (handle) Variants
194222
```cpp
195223
// Array
196224
GODOT_VARIANT(ChildNodes, godot::Array);

0 commit comments

Comments
 (0)