Skip to content

Commit 0a57cdd

Browse files
committed
Add schematics RFC
1 parent 366ce1d commit 0a57cdd

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

rfcs/-schematics.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Feature Name: `schematics`
2+
3+
## Summary
4+
5+
The goal of this RFC is to facilitate and standardize two-way conversion and synchronization between ECS worlds.
6+
The approach described here aims to be more user friendly than just handcoded synchronization systems, but more flexible than a prefab system.
7+
This design is buitl on top of the [many worlds RFC][many_worlds].
8+
9+
## Motivation
10+
11+
When devolping an editor for bevy, the information visible should not necessarily be in the same format that most runtime systems operate on.
12+
The runtime representation is often optimized for efficiency and use in those systems, while the information displayed in the editor should be optimized for
13+
easy understanding and stability agains changes.
14+
We propose to address this by adding another default world to the ones described in the [many worlds RFC][many_worlds] that we call the "schematic world",
15+
and which contains the information used in the editor and its scene file formats.
16+
17+
The main problem that arises from this approach is the synchronisation between the main and schematic worlds, so the RFC focuses on this aspect.
18+
Similar problems, where data needs to kept synchronized between worlds, might also appear in other areas.
19+
One example for this is the "render world", for which the extract phase could be adapted to use schematics.
20+
Another example could be a "server world" in a multiplayer game, that only contains information which is shared with other clients and the server.
21+
Finally the approach chosen here would help in implementing a "prefab" system.
22+
23+
## User-facing explanation
24+
25+
### The `CoreWorld::Schematic`
26+
27+
### The `SchematicQuery`
28+
29+
The main interface added by this RFC is the `SchematicQuery`, which you will usually only use in systems that run on `CoreWorld::Schematic`.
30+
We call such systems "schematics".
31+
A `SchematicQuery` works almost the same as a normal `Query`, but when iterating over components will additionaly return `SchematicCommands` for the component queried.
32+
This can be used to insert and modify components on the corresponding entity on `CoreWorld::Main` as well as spawn new entities there.
33+
The entities spawned in this way can not be modified by `SchematicCommands` from other systems, but will be remembered for this system similar to `Local` system parameters.
34+
35+
A typical schematic will look like this:
36+
37+
```rust
38+
#[derive(Component)]
39+
struct SchematicA(u32, Entity);
40+
41+
#[derive(Component)]
42+
struct MainA(u32);
43+
44+
#[derive(Component)]
45+
struct MainAChild(Entity);
46+
47+
/// Translates a `SchematicA` to a `MainA` as well as a child entity that has a `MainAChild`.
48+
/// The system can contain any other parameter besides the schematic query
49+
fn schematic_for_a(query: SchematicQuery<A, With<Marker>>, mut some_resource: ResMut<R>) {
50+
for (a, commands) in query {
51+
some_resource.do_something_with(a);
52+
// You can modify components
53+
commands.insert_or_update(MainA(a.0));
54+
// And spawn other entities.
55+
// This will only spawn an entity on the first execution. It will remember the entity
56+
// by the label "child" so `child_commands` will operate on the same entity the next
57+
// time this system is run.
58+
commands.spawn_or_select_child("child", |child_commands| {
59+
// Entity references within the schematic world need to be mapped in this way.
60+
// (This might not be needed depending on the implementation of many worlds)
61+
let entity = child_commands.map_entity(a.1);
62+
child_commands.insert_or_update(MainAChild(entity));
63+
});
64+
}
65+
}
66+
```
67+
68+
The `SchematicQuery` will automatically only iterate over components that were changed since the system last ran.
69+
Other than a normal query, the first generic argument can only be a component, so tuples or `Entity` are not allowed as the first argument.
70+
71+
The main methods of `SchematicCommands` are:
72+
* `insert_or_update<B: Bundle>(bundle: B)`
73+
* `spawn_or_select_child<L: SchematicLabel, F: FnOnce(SchematicCommands)>(label: L, F)`
74+
* `spawn_or_select<L: SchematicLabel, F: FnOnce(SchematicCommands)>(label: L, F)`
75+
* `despawn_if_exists<L: SchematicLabel>(label: L)`
76+
* `remove_if_exists<B: Bundle>()`
77+
78+
### The `default_schematic` system
79+
80+
Since with many components you want to just copy the same component from the schematic to the main world, a `default_schematic` is provided.
81+
The implementation of this is just:
82+
83+
```rust
84+
fn default_schematic<A: Clone>(query: SchematicQuery<A>) {
85+
for (a, commands) in query {
86+
commands.insert_or_update(a.clone());
87+
}
88+
}
89+
```
90+
91+
### Adding schematics to your app
92+
93+
`Schematic`s can be added to schedules like any other system
94+
```rust
95+
schedule.add_system(schematic_for_a);
96+
```
97+
98+
Usually you will want to syncronize between the app's `SchematicWorld` and `CoreWorld::Main`.
99+
In this case you can use
100+
101+
```rust
102+
app.add_schematic(default_schematic::<A>);
103+
```
104+
105+
## Implementation strategy
106+
107+
* `SchematicQuery<C, F>` is basically `(Query<C, (F, Changed<C>)>, Local<HashMap<Entity, SchematicData>>, AppCommands)` where `SchematicData` is something like
108+
```rust
109+
struct SchematicData {
110+
corresponding_entities: HashMap<SchematicLabelId, Entity>
111+
}
112+
```
113+
* `SchematicCommands` is something like
114+
```rust
115+
struct SchematicCommands<'a> {
116+
app_commands: &'a mut AppCommands,
117+
schematic_data: &'a mut SchematicData,
118+
current_label: Option<SchematicLabelId>,
119+
}
120+
```
121+
122+
## Drawbacks
123+
124+
* Adds another data model that programmers need to think about.
125+
* The design may not be very performant and use a lot of extra memory
126+
* Need to add additional code to pretty much any component, even if it is just `app.add_schematic(default_schematic::<C>)`
127+
* It is not possible to check invariants, e.g. synchronization should be idempotent, schmatics shouldn't touch same component, ...
128+
129+
## Rationale and alternatives
130+
131+
This design
132+
* Integrates well into ECS architecture
133+
* Is neutral with regard to usage
134+
* Can use existing code to achive parallel execution
135+
* Can live alongside synchronization systems that don't use `SchematicQuery`
136+
137+
The problem could also be solved by "prefabs", i.e. scenes that might expose certain fields of the components they contain.
138+
But this would be a lot more restrictive the "schematics" described here.
139+
What might make more sense is to implement prefabs atop schematics.
140+
141+
This design does not allow the schematic world to behave sensibly for use in the editor by itself.
142+
It would need something like [archetype invariants](https://github.com/bevyengine/bevy/issues/1481) additionally.
143+
144+
## Prior art
145+
146+
*TODO: Compare with Unity's system of converting game objects to ECS components*
147+
148+
See https://github.com/bevyengine/bevy/issues/3877
149+
150+
Discuss prior art, both the good and the bad, in relation to this proposal.
151+
This can include:
152+
153+
- Does this feature exist in other libraries and what experiences have their community had?
154+
- Papers: Are there any published papers or great posts that discuss this?
155+
156+
This section is intended to encourage you as an author to think about the lessons from other tools and provide readers of your RFC with a fuller picture.
157+
158+
Note that while precedent set by other engines is some motivation, it does not on its own motivate an RFC.
159+
160+
## Unresolved questions
161+
162+
- What parts of the design do you expect to resolve through the RFC process before this gets merged?
163+
- What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged?
164+
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
165+
166+
## \[Optional\] Future possibilities
167+
168+
Think about what the natural extension and evolution of your proposal would
169+
be and how it would affect Bevy as a whole in a holistic way.
170+
Try to use this section as a tool to more fully consider other possible
171+
interactions with the engine in your proposal.
172+
173+
This is also a good place to "dump ideas", if they are out of scope for the
174+
RFC you are writing but otherwise related.
175+
176+
Note that having something written down in the future-possibilities section
177+
is not a reason to accept the current or a future RFC; such notes should be
178+
in the section on motivation or rationale in this or subsequent RFCs.
179+
If a feature or change has no direct value on its own, expand your RFC to include the first valuable feature that would build on it.
180+
181+
[many_worlds]: https://github.com/bevyengine/rfcs/pull/43

0 commit comments

Comments
 (0)