|
| 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