Skip to content

bevy_app: Allow SubApps to be modified at runtime #6700

@soqb

Description

@soqb

What problem does this solve or what need does it fill?

Currently, Bevy provides no way to add, remove or replace SubApps 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 SubApps for "sandboxed" loading of dynamic plugins, where their initialization and updates can be controlled separately from the parent app. Without a way to modifiy SubApps 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 SubApps (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.
fn add_sub_app_system(mut params: ParamSet<(&mut World, &mut SubApps)>) {
    let should_change = {
        let world = params.p0();
        world.resource::<MySubAppInfo>().should_change()
    };
    if should_change {
        let mut app = params.p1();
        sub_apps.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
    }
}
  1. 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.
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::<MySubAppInfo>().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::<MySubAppInfo>().should_change() {
        app.add_sub_app("my_sub_app", MySubApp, MySubApp::runner);
    }
}));
  1. a way to pause the app while a function runs. this could take many forms e.g.
fn app_exclusive_system(app: &mut App) {
    if app.world.resource::<MySubAppInfo>().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

fn system(
    info: Res<MySubAppInfo>,
    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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-AppBevy apps and pluginsC-UsabilityA targeted quality-of-life change that makes Bevy easier to use

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions