bevyengine / bevyengine/bevy

bevy_app: Allow `SubApp`s to be modified at runtime

Open
#6,700 5 comments 3 reactions 0 assignees View on GitHub
A-App C-Usability
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.