use core::ffi::c_void;
use core::marker::PhantomData;
use crate::abi::{NetMessage, Status, Str};
use crate::{Host, Load, Off};
pub const MAX_MESSAGE: usize = 4096;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Peer {
Server,
Player(u64),
}
impl Peer {
fn to_raw(self) -> u64 {
match self {
Self::Server => NetMessage::SERVER,
Self::Player(key) => key,
}
}
fn from_raw(raw: u64) -> Self {
if raw == NetMessage::SERVER {
Self::Server
} else {
Self::Player(raw)
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SendError {
#[error("the message is over {MAX_MESSAGE} bytes")]
TooLarge,
#[error("the peer takes no messages now")]
NotReady,
#[error("over the rate of messages to the peer")]
OverRate,
#[error("the message does not encode: {0}")]
Encode(String),
#[error("the loader refused the message")]
Refused,
}
#[derive(Debug)]
pub struct Channel<M> {
host: Host,
id: u32,
_message: PhantomData<fn(M) -> M>,
}
impl<M> Clone for Channel<M> {
fn clone(&self) -> Self {
*self
}
}
impl<M> Copy for Channel<M> {}
impl<M: serde::Serialize> Channel<M> {
pub fn send(&self, to: Peer, message: &M) -> Result<(), SendError> {
if to == Peer::Player(0) {
return Err(SendError::NotReady);
}
let bytes = postcard::to_allocvec(message).map_err(|e| SendError::Encode(e.to_string()))?;
send_raw(self.host, self.id, to.to_raw(), &bytes)
}
pub fn broadcast(&self, message: &M) -> Result<(), SendError> {
let bytes = postcard::to_allocvec(message).map_err(|e| SendError::Encode(e.to_string()))?;
send_raw(self.host, self.id, 0, &bytes)
}
}
impl<M> Channel<M> {
#[must_use]
pub fn is_ready(&self, peer: Peer) -> bool {
if peer == Peer::Player(0) {
return false;
}
let api = self.host.api();
(api.net_ready)(self.host.handle(), peer.to_raw()).is_ok()
}
}
fn send_raw(host: Host, id: u32, peer: u64, bytes: &[u8]) -> Result<(), SendError> {
if bytes.len() > MAX_MESSAGE {
return Err(SendError::TooLarge);
}
let api = host.api();
let size = u32::try_from(bytes.len()).map_err(|_| SendError::TooLarge)?;
match (api.net_send)(host.handle(), id, peer, bytes.as_ptr(), size) {
Status::OK => Ok(()),
Status::MISSING => Err(SendError::NotReady),
Status::REFUSED => Err(SendError::OverRate),
Status::BAD_ARGS => Err(SendError::TooLarge),
_ => Err(SendError::Refused),
}
}
struct Handler<F> {
handler: F,
}
unsafe extern "system" fn deliver<M, F>(message: *const NetMessage, user: *mut c_void)
where
M: serde::de::DeserializeOwned,
F: Fn(Peer, M),
{
let Some(message) = (unsafe { message.as_ref() }) else {
return;
};
let bytes = if message.size == 0 || message.data.is_null() {
&[][..]
} else {
unsafe { core::slice::from_raw_parts(message.data, message.size as usize) }
};
let handler = unsafe { &*user.cast::<Handler<F>>() };
let from = Peer::from_raw(message.peer);
crate::host::guard(
"panic in a channel handler",
|| (),
|| match postcard::from_bytes::<M>(bytes) {
Ok(value) => (handler.handler)(from, value),
Err(error) => crate::debug!("a message on a channel does not decode: {error}"),
},
);
}
impl Load<'_> {
pub fn channel<M, F>(&mut self, name: &str, version: u16, handler: F) -> Result<Channel<M>, Off>
where
M: serde::Serialize + serde::de::DeserializeOwned + 'static,
F: Fn(Peer, M) + Send + Sync + 'static,
{
let host = self.host();
let full = format!("{name}@{version}");
let mut id = 0u32;
crate::load::hand_over(
Handler { handler },
|user| {
(host.api().net_channel)(
host.handle(),
Str::new(&full),
deliver::<M, F>,
user,
&raw mut id,
)
},
&format!("channel {full}"),
)?;
Ok(Channel {
host,
id,
_message: PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peers_round_trip_through_their_raw_form() {
for peer in [Peer::Server, Peer::Player(7), Peer::Player(0)] {
assert_eq!(Peer::from_raw(peer.to_raw()), peer);
}
}
}