bevy_app: Allow `SubApp`s to be modified at runtime
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
## What problem does this solve or what need does it fill?
Currently, Bevy provides no way to add, remove or replace `SubApp`s while the app is running.
One can currently (IIRC): end the running of the app, recreate the app with the changed `SubApp` and then run the app. This is unwieldy, slow, and prevents one from easily maintaining parent app state (e.g. windows).
I use `SubApp`s for "sandboxed" loading of dynamic plugins, where their initialization and updates can be controlled separately from the parent app. Without a way to modifiy `SubApp`s after the parent app starts running, hot-reloading is impossible.
## What solution would you like?
1. Add an exclusive `SystemParam` which provides access to the list of `SubApp`s (but i'm not sure how this would be done considering the app is outside of the scope of the ecs and in the future one app may have many worlds), e.g.
```rust
fn add_sub_app_system(mut params: ParamSet<(&mut World, &mut SubApps)>) {
let should_change = {
let world = params.p0();
world.resource::().should_change()
};
if should_change {
let mut app = params.p1();
sub_apps.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
}
}
```
2. Add composable runner functions, which would be a significant breaking change (although i believe most users use the Bevy-provided runners: `run_once`, the `ScheduleRunnerPlugin` runner or the `winit` runner) e.g.
```rust
let mut App::new();
app.add_plugins(MinimalPlugins);
let old_runner = app.take_runner(); // new method that replaces the runner with `run_once`
// new `Runner` struct that seperates behavior into three distinct phases.
app.set_runner(Runner {
start: old_runner.start,
update: Box::new(move |app: &mut App| { // now by mutable reference rather than by value.
old_runner.update();
if app.world.resource::().should_change() {
app.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
}
}),
end: old_runner.end,
});
// this could be sugared to something like
let old_runner = app.take_runner();
app.set_runner(old_runner.wrap_update(|app, old_update| {
old_update();
if app.world.resource::().should_change() {
app.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
}
}));
```
3. a way to pause the app while a function runs. this could take many forms e.g.
```rust
fn app_exclusive_system(app: &mut App) {
if app.world.resource::().should_change() {
app.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
}
}
let mut app = App::new();
app.add_app_exclusive_system(app_exclusive_system);
```
or
```rust
fn system(
info: Res,
mut app_commands: AppCommands // or maybe `&mut AppCommandQueue` since rarely used
) {
if info.should_change() {
app_commands.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
// or
app_commands.push(|app: &mut App| {
app.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
});
}
}
```
## What alternative(s) have you considered?
Not allowing `SubApp` modification.
## Additional context
None.
Contributor guide
Assessment
This issue has not been assessed yet.