use bevy::{ecs::system::SystemParam, prelude::*};
use iroh::EndpointId;
use serde::{Serialize, de::DeserializeOwned};
use crate::{
net::{Kind, Via},
node::{Inbox, Iroh, Receive, ToNet},
room::{RoomTopic, Rooms},
};
pub trait NetMessage: Serialize + DeserializeOwned + Send + Sync + 'static {}
impl<T: Serialize + DeserializeOwned + Send + Sync + 'static> NetMessage for T {}
#[derive(Debug, Clone)]
pub struct Received<T> {
pub from: EndpointId,
pub peer: Option<Entity>,
pub room: Entity,
pub via: Via,
pub msg: T,
}
impl<T: Send + Sync + 'static> Message for Received<T> {}
#[derive(SystemParam)]
pub struct NetSender<'w, 's> {
iroh: Option<Res<'w, Iroh>>,
rooms: Query<'w, 's, &'static RoomTopic>,
}
impl NetSender<'_, '_> {
pub fn broadcast<T: NetMessage>(&self, room: Entity, msg: &T) -> bool {
let (Some(iroh), Ok(topic)) = (self.iroh.as_deref(), self.rooms.get(room)) else {
return false;
};
let Ok(body) = postcard::to_stdvec(msg) else {
return false;
};
iroh.send(ToNet::Broadcast {
topic: topic.0,
kind: Kind::of::<T>(),
body,
});
true
}
pub fn broadcast_all<T: NetMessage>(&self, msg: &T) {
let Some(iroh) = self.iroh.as_deref() else {
return;
};
let Ok(body) = postcard::to_stdvec(msg) else {
return;
};
for topic in &self.rooms {
iroh.send(ToNet::Broadcast {
topic: topic.0,
kind: Kind::of::<T>(),
body: body.clone(),
});
}
}
pub fn send_to<T: NetMessage>(&self, room: Entity, to: EndpointId, msg: &T) -> bool {
let (Some(iroh), Ok(topic)) = (self.iroh.as_deref(), self.rooms.get(room)) else {
return false;
};
let Ok(body) = postcard::to_stdvec(msg) else {
return false;
};
iroh.send(ToNet::SendTo {
topic: topic.0,
to,
kind: Kind::of::<T>(),
body,
});
true
}
}
pub trait MessageAppExt {
fn add_net_message<T: NetMessage>(&mut self) -> &mut Self;
}
impl MessageAppExt for App {
fn add_net_message<T: NetMessage>(&mut self) -> &mut Self {
self.add_message::<Received<T>>()
.add_systems(PreUpdate, receive::<T>.in_set(Receive::Messages))
}
}
fn receive<T: NetMessage>(
mut inbox: ResMut<Inbox>,
rooms: Res<Rooms>,
mut out: MessageWriter<Received<T>>,
) {
for frame in inbox.take(Kind::of::<T>()) {
let Some(room) = rooms.by_topic(&frame.topic) else {
continue;
};
match postcard::from_bytes::<T>(&frame.body) {
Ok(msg) => {
out.write(Received {
from: frame.from,
peer: rooms.peer(room, frame.from),
room,
via: frame.via,
msg,
});
}
Err(e) => debug!(
"bevy_iroh: {} from {}: {e}",
std::any::type_name::<T>(),
frame.from.fmt_short()
),
}
}
}