pub mod channel;
pub mod commands;
pub mod events;
use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
use bevy_ecs::message::{Message as BevyMessage, MessageReader, MessageWriter};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::{
schedule::common_conditions::resource_exists,
system::{Commands, ResMut},
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{io, marker::PhantomData};
use thiserror::Error;
use tracing::error;
use crate::{
channel::Channel,
events::{
ClientJoined, ClientLeft, MessageReceivedEvent, SessionConnectedEvent,
SessionDisconnectedEvent,
},
};
pub use tubes::ClientId;
pub mod prelude {
pub use crate::SessionPlugin;
pub use crate::channel::*;
pub use crate::commands::*;
pub use crate::events::*;
pub use tubes::ClientId;
}
#[cfg(not(feature = "debug"))]
pub trait Message: Serialize + DeserializeOwned + Send + Sync + 'static {}
#[cfg(not(feature = "debug"))]
impl<T> Message for T where for<'a> T: Serialize + Deserialize<'a> + Send + Sync + 'static {}
#[cfg(feature = "debug")]
pub trait Message: std::fmt::Debug + Serialize + DeserializeOwned + Send + Sync + 'static {}
#[cfg(feature = "debug")]
impl<T> Message for T where
for<'a> T: std::fmt::Debug + Serialize + Deserialize<'a> + Send + Sync + 'static
{
}
pub struct SessionPlugin<T: Message> {
_t: PhantomData<T>,
}
impl<T: Message> Default for SessionPlugin<T> {
fn default() -> Self {
Self { _t: PhantomData }
}
}
impl<T: Message> Plugin for SessionPlugin<T> {
fn build(&self, app: &mut App) {
app.add_message::<SessionConnectedEvent<T>>();
app.add_message::<SessionDisconnectedEvent<T>>();
app.add_message::<MessageReceivedEvent<T>>();
app.add_message::<ClientJoined<T>>();
app.add_message::<ClientLeft<T>>();
app.add_message::<TerminateEvent<T>>();
app.add_systems(
PreUpdate,
process_channel::<T>.run_if(resource_exists::<Channel<T>>),
);
app.add_systems(
PostUpdate,
terminate::<T>.run_if(resource_exists::<Channel<T>>),
);
}
}
fn process_channel<T: Message>(
mut channel: ResMut<Channel<T>>,
mut events: MessageWriter<MessageReceivedEvent<T>>,
mut joinevent: MessageWriter<ClientJoined<T>>,
mut leaveevent: MessageWriter<ClientLeft<T>>,
mut termevent: MessageWriter<TerminateEvent<T>>,
) {
if let Err(e) = channel.poll(&mut events, &mut joinevent, &mut leaveevent, &mut termevent) {
error!("Error polling for messages: {e}");
}
}
#[derive(BevyMessage)]
pub(crate) struct TerminateEvent<T: Message> {
pub(crate) _t: PhantomData<T>,
}
#[allow(clippy::needless_pass_by_value)]
fn terminate<T: Message>(
mut cmd: Commands,
channel: ResMut<Channel<T>>,
mut event: MessageReader<TerminateEvent<T>>,
) {
if !channel.is_host() && event.read().next().is_some() {
cmd.remove_resource::<Channel<T>>();
}
}
#[derive(Error, Debug)]
pub(crate) enum Fault {
#[error("Terminate connection")]
Terminate(#[from] io::Error),
}