bevy_subworlds 0.1.0

multi-world support for bevy
Documentation
# bevy_subworlds

Threaded multi-world management and signal transmission for bevy.

A SubWorld is a Component that stores a JoinHandle to a thread running an App. Supported
operations include sending an exit signal, checking if the SubWorld has completed its startup
schedule, and extracting data with pollable extract systems.

**Extraction** is when a system is sent to a SubWorld to be ran. The output of this system can
be polled with an `ExtractHandle`, similar to an async task or a bevy task.

**Signals** are _either_ components or resources. When a Signal is a Resource,
it sends or receives signals from a parent world. When a Signal is a Component,
it sends and receives signals to/from a subworld. Signals receivers/transmitters
are automatically inserted onto the SubWorld as components and within the SubWorld
as Resources when `SubWorldBuilder::with_signal` is called.

## Usage
This simple example spawns a SubWorld and ping/pongs a signal back and forth. It also
demonstrates extraction by reading and mutating a resource in the SubWorld.
```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(MinimalPlugins)
        .add_systems(Startup, spawn_subworld)
        .add_systems(Update, (
            send_pings,
            extract_data,
        ))
        .run();
}

// Your custom signal type
enum MySignal {
    Ping,
    Pong
}

#[derive(Resource)]
struct GameData {
    num: u32,
}

fn spawn_subworld(
    world_id: WorldId,
    mut commands: Commands,
) {
    SubWorld::build(world_id, &mut commands)
        .with_signal::<MySignal>() // < register your signal type
        .start(move |app| {
            app
                .insert_resource(GameData { num: 1 })
                .add_systems(Update, respond_to_pings);
        });
}

/// Extract and increment the GameState's `num` variable from each system.
fn extract_data(
    mut query: Query<(&SubWorld, Entity, Option<&mut ExtractHandle<u32>>)>,
    mut commands: Commands,
) {
    for (subworld, entity, handle) in &mut query {
        if let Some(mut handle) = handle {
            if let Ok(num) = handle.poll() {
                log::info!("Extracted Number: '{num}'. Time: '{:?}'", handle.duration().unwrap());
                commands.entity(entity).remove::<ExtractHandle<u32>>();

                if num > 100 {
                    subworld.exit();
                }
            }
        } else {
            let handle: ExtractHandle<u32> = subworld.extract(
                move |mut state: ResMut<GameData>| {
                    state.num += 1;
                    state.num
                }
            );

            commands.entity(entity).insert(handle);
        }
    }
}

/// Send pings to Subworlds
fn send_pings(
    mut query: Query<(&mut SignalRx<MySignal>, &SignalTx<MySignal>), With<SubWorld>>,
) {
    for (mut rx, tx) in &mut query {
        for signal in &mut rx {
            match signal {
                MySignal::Ping => log::info!("Ping Received!"),
                MySignal::Pong => {
                    tx.send(MySignal::Ping)
                }
            }
        }

        tx.send(MySignal::Ping);
    }
}

/// Respond to pings as a SubWorld
fn respond_to_pings(
    mut signal_rx: ResMut<SignalRx<MySignal>>,
    signal_tx: Res<SignalTx<MySignal>>,
) {
    for signal in &mut signal_rx {
        match signal {
            MySignal::Ping => {
                let _ = signal_tx.send(MySignal::Pong);
            },
            MySignal::Pong => {}
        }
    }
}
```