use crate::set::StdbSet;
use bevy_app::{App, Plugin, PreUpdate};
use bevy_ecs::prelude::{IntoScheduleConfigs, Message, Messages, Mut, Resource, World};
use crossbeam_channel::{Sender, unbounded};
use std::any::{Any, TypeId, type_name};
use std::sync::Arc;
pub type ChannelRegistrationCallback = dyn Fn(&mut App) + Send + Sync;
struct ChannelEntry {
type_id: TypeId,
drain: Box<dyn Fn(&mut World) + Send + Sync>,
sender: Box<dyn Any + Send + Sync>,
}
#[derive(Resource, Default)]
pub struct StdbChannels {
channels: Vec<ChannelEntry>,
}
impl StdbChannels {
pub fn sender<T: Message>(&self) -> Sender<T> {
self.channels
.iter()
.find(|entry| entry.type_id == TypeId::of::<T>())
.and_then(|entry| entry.sender.downcast_ref::<Sender<T>>())
.unwrap_or_else(|| panic!("unregistered channel for `{}`", type_name::<T>()))
.clone()
}
}
pub(crate) struct ChannelBridgePlugin {
pub(crate) channel_registrations: Vec<Arc<ChannelRegistrationCallback>>,
}
impl Plugin for ChannelBridgePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<StdbChannels>();
app.add_systems(PreUpdate, drain_channels.in_set(StdbSet::Flush));
for register in &self.channel_registrations {
register(app);
}
}
}
fn drain_channels(world: &mut World) {
world.resource_scope(|world, registry: Mut<StdbChannels>| {
for entry in ®istry.channels {
(entry.drain)(world);
}
});
}
pub(crate) fn register_channel<T: Message>(app: &mut App) {
assert!(
!app.world()
.resource::<StdbChannels>()
.channels
.iter()
.any(|entry| entry.type_id == TypeId::of::<T>()),
"attempted to register channel for message type `{}` more than once",
type_name::<T>(),
);
let (tx, rx) = unbounded::<T>();
app.add_message::<T>();
app.world_mut()
.resource_mut::<StdbChannels>()
.channels
.push(ChannelEntry {
type_id: TypeId::of::<T>(),
drain: Box::new(move |world: &mut World| {
world
.resource_mut::<Messages<T>>()
.write_batch(rx.try_iter());
}),
sender: Box::new(tx),
});
}
pub(crate) fn channel_sender<T: Message>(world: &World) -> Sender<T> {
world.resource::<StdbChannels>().sender::<T>()
}