# 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 => {}
}
}
}
```