System Chaining

Relevant official examples: system_chaining.


You can compose a single Bevy system from multiple Rust functions.

You can make functions that can take an input and produce an output, and be connected together to run as a single larger system.

This is called "system chaining", but beware that the term is somewhat misleading – you are not creating a chain of multiple systems to run in order; you are creating a single large Bevy system consisting of multiple Rust functions.

Note that system chaining is not a way of communicating between systems. If you want to pass data between systems, you should use Events instead.


One useful application is to be able to return errors from your system code (allowing the use of Rust's ? operator) and then have a separate function for handling them:

fn net_receive(mut netcode: ResMut<MyNetProto>) -> std::io::Result<()> {
    netcode.receive_updates()?;

    Ok(())
}

fn handle_io_errors(In(result): In<std::io::Result<()>>) {
    if let Err(e) = result {
        eprintln!("I/O error occurred: {}", e);
    }
}

Such functions cannot be registered individually as systems (Bevy doesn't know what to do with the input/output). You have to connect them in a chain:

fn main() {
    App::new()
        // ...
        .add_system(net_receive.chain(handle_io_errors))
        // ...
        .run();
}

Performance Warning

Beware that Bevy treats the whole chain as if it was a single big system, with all the combined resources and queries. This implies that parallelism could be limited, affecting performance.

Avoid adding a system that requires mutable access to anything, as part of multiple chains. It would block all affected chains (and other systems accessing the same data) from running in parallel.