Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Maps

Services and callbacks are two kinds of providers that can both function as Bevy systems. Bevy systems have the benefit of granting you full access to the Bevy ECS, but there is a small amount of overhead involved in initializing and running systems. If you want a provider that does not need access to Bevy’s ECS, then you should consider using a map instead.

Maps are providers that have the minimum possible overhead. They are defined with as functions that don’t have any Bevy system parameters. Maps are good for doing quick transformations of data or for calling non-Bevy async functions.

Warning

Since blocking maps do not have any system parameters, they cannot access data inside of buffers.

Async maps can use the async channel to access buffer data, but each query needs to wait until the next execution flush takes place.

Simple usage

When building a series of services you can use Series::map_block(_) or Series::map_async(_) to quickly and easily create a map.

Similarly when chaining service in a workflow you can use Chain::map_block(_) or Chain::map_async(_).

These functions allow you to convert simple blocking or async functions (or closures) into providers with no additional steps.

Streams and Async Channel

Even though maps cannot directly access any Bevy system params, they can still have output streams. Async maps even get access to the async channel which means they can query for and modify Bevy ECS data while running in the async task pool.

To get access to these, you must define your function explicitly as a blocking or async map:

fn fibonacci_map_example(input: BlockingMap<u32, StreamOf<u32>>) {
    let order = input.request;
    let stream = input.streams;

    let mut current = 0;
    let mut next = 1;
    for _ in 0..order {
        stream.send(current);

        let sum = current + next;
        current = next;
        next = sum;
    }
}

let outcome = commands.request(10, fibonacci_map_example.as_map()).outcome();

Before passing your function into commands.request(_) you must apply .as_map() to it to convert it into a map.

Tip

If your map is part of a series or a chain, you can use Series::map(_) or Chain::map(_) instead to avoid the need to apply .as_map(). This is useful if you want to define your map as a closure inline in the series or chain.

Blocking maps do not get to access the async channel—neither do blocking services nor blocking callbacks—but async maps do. Just like accessing streams, you just need to make it explicit that you have an async map:

async fn navigate(
    input: AsyncMap<NavigationRequest, NavigationStreams>,
) -> Result<(), NavigationError> {
    // Clone the navigation graph resource so we can move the clone into the
    // async block.
    let nav_graph = input
        .channel
        .world(|world| {
            world.resource::<NavigationGraph>().clone()
        })
        .await;

    // Create a callback for fetching the latest position
    let get_position = |
        In(key): In<BufferKey<Vec2>>,
        access: BufferAccess<Vec2>,
    | {
        access.get_newest(&key).cloned()
    };
    let get_position = get_position.into_blocking_callback();

    // Unpack the input into simpler variables
    let NavigationRequest { destination, robot_position_key } = input.request;
    let location_stream = input.streams.location;

    loop {
        // Fetch the latest position using the async channel
        let position = input.channel.request_outcome(
            robot_position_key.clone(),
            get_position.clone()
        )
            .await
            .ok()
            .flatten();

        let Some(position) = position else {
            // Position has not been reported yet, so just try again later.
            continue;
        };

        // Send the current position out over an async stream
        location_stream.send(position);

        // TODO: Command the robot to proceed towards its destination
        // TODO: Break the loop when the robot arrives at its destination
    }

    Ok(())
}

With that, navigate can be passed into commands.request(_, navigate.as_map()) or into series.map(navigate) or chain.map(navigate).