Skip to content

Async Plugin Construction #18187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

SpecificProtagonist
Copy link
Contributor

@SpecificProtagonist SpecificProtagonist commented Mar 7, 2025

Objective

Since the issue was made, Bevy's plugins have been split into three stages that are (typically) run before app update: Build, finish and cleanup. They also have a ready method to delay the switch from the build to the finish phase.

This allows plugins to delay up to twice while waiting on other plugins, but the specific dependency is implicit even when reading the code and the ordering of plugins matters. Solve this by making plugin building concurrent.

Also, I've just now seen #8607, which also makes plugins asynchronous. I'll need to take a closer look, but afaik it has the goal of running on bevy_tasks and doesn't address dependencies / plugin ordering (as it build plugins one at a time).

Solution

Adds Plugin::build_async, which runs concurrently for all plugins. The plugin can wait for the world to reach whatever state it needs to continue. While you can define a custom condition, there's also a helper for waiting on a resource; please suggest other ones.

This is intended to replace the old plugin lifecycle machinery, which can be deprecated (and eventually removed) in a later PR, which will make things much simpler. As noted in #1255, we'll want to defer app construction actions (like add_systems); with this PR, the collected actions can be run by turning them into an async plugin.

As a followup, we should be able to use a waker and achieve the goals of #8607.

Testing

(TODO: Add plenty of tests if we decide to do this)

Showcase

#[derive(Resource)]
struct A(i32);

struct MyPlugin;

impl Plugin for MyPlugin {
    async fn build_async(self: Box<Self>, mut ctx: PluginContext<'_>) {
        let a = ctx.resource::<A>().await.unwrap();
        println!("got A: {}", a.0);
    }
}

struct OtherPlugin;

impl Plugin for OtherPlugin {
    async fn build_async(self: Box<Self>, mut ctx: PluginContext<'_>) {
        ctx.app().world_mut().insert_resource(A(1));
    }
}

Migration Guide

(TODO depending on changes)

@SpecificProtagonist SpecificProtagonist added C-Feature A new feature, making something new possible A-App Bevy apps and plugins X-Contentious There are nontrivial implications that should be thought through S-Needs-Review Needs reviewer attention (from anyone!) to move forward D-Async Deals with asynchronous abstractions labels Mar 7, 2025
@JMS55
Copy link
Contributor

JMS55 commented Mar 7, 2025

Waiting on a specific plugin to finish would be nice since sometimes you have:

  • Plugin A sometimes inserts a resource, sometimes doesn't
  • Plugin B wants to wait for Plugin A to finish, and then check if the resource exists or not

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-App Bevy apps and plugins C-Feature A new feature, making something new possible D-Async Deals with asynchronous abstractions S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Plugin Dependencies
2 participants