use std::task::{Context, Poll, Waker};
use std::{fmt, pin::Pin};
use serde::de::DeserializeOwned;
use smol::future::FutureExt;
use ustr::Ustr;
use crate::{Client, ConnectionOptions, Event, error::*};
mod socket;
pub(crate) use socket::*;
#[derive(Default)]
pub struct Connection<S: DeserializeOwned + Send + 'static = serde_json::Value> {
state: ConnectionState<S>,
}
impl<S: DeserializeOwned + Send + 'static> Connection<S> {
pub fn new(
url: impl Into<String>,
name: impl Into<Ustr>,
game: Option<impl Into<Ustr>>,
options: ConnectionOptions,
) -> Self {
Connection {
state: ConnectionState::Connecting(Connecting(Box::pin(Client::connect(
url.into(),
name.into(),
game.map(|g| g.into()),
options,
)))),
}
}
pub fn update(&mut self) -> Vec<Event> {
match self.state {
ConnectionState::Connecting(Connecting(ref mut future)) => match try_future(future) {
Some(Ok(client)) => {
self.state = ConnectionState::Connected(client);
let later_events = self.update();
let mut events = Vec::with_capacity(later_events.len() + 1);
events.push(Event::Connected);
events.extend(later_events);
events
}
Some(Err(err)) => {
self.state = ConnectionState::Disconnected(err);
vec![Event::Error(Error::Elsewhere)]
}
None => vec![],
},
ConnectionState::Connected(ref mut client) => {
let mut events = client.update();
if let Some(Event::Error(error)) =
events.pop_if(|e| matches!(e, Event::Error(err) if err.is_fatal()))
{
self.state = ConnectionState::Disconnected(error);
events.push(Event::Error(Error::Elsewhere));
}
events
}
ConnectionState::Disconnected(_) => vec![],
}
}
pub fn try_next_event(&mut self) -> Option<Event> {
match self.state {
ConnectionState::Connecting(Connecting(ref mut future)) => match try_future(future) {
Some(Ok(client)) => {
self.state = ConnectionState::Connected(client);
Some(Event::Connected)
}
Some(Err(err)) => {
self.state = ConnectionState::Disconnected(err);
Some(Event::Error(Error::Elsewhere))
}
None => None,
},
ConnectionState::Connected(ref mut client) => match client.try_next_event() {
Some(Event::Error(error)) if error.is_fatal() => {
self.state = ConnectionState::Disconnected(error);
Some(Event::Error(Error::Elsewhere))
}
option => option,
},
ConnectionState::Disconnected(_) => None,
}
}
pub fn state(&self) -> &ConnectionState<S> {
&self.state
}
pub fn state_mut(&mut self) -> &mut ConnectionState<S> {
&mut self.state
}
pub fn state_type(&self) -> ConnectionStateType {
self.state.state_type()
}
pub fn client(&self) -> Option<&Client<S>> {
match &self.state {
ConnectionState::Connected(client) => Some(client),
_ => None,
}
}
pub fn client_mut(&mut self) -> Option<&mut Client<S>> {
match &mut self.state {
ConnectionState::Connected(client) => Some(client),
_ => None,
}
}
pub fn is_connecting(&self) -> bool {
self.state_type() == ConnectionStateType::Connecting
}
pub fn is_connected(&self) -> bool {
self.state_type() == ConnectionStateType::Connected
}
pub fn is_disconnected(&self) -> bool {
self.state_type() == ConnectionStateType::Disconnected
}
pub fn err(&self) -> &Error {
match &self.state {
ConnectionState::Disconnected(err) => err,
_ => &Error::ClientDisconnected,
}
}
pub fn into_err(self) -> Error {
match self.state {
ConnectionState::Disconnected(err) => err,
_ => Error::ClientDisconnected,
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum ConnectionState<S: DeserializeOwned + 'static> {
Connecting(Connecting<S>),
Connected(Client<S>),
Disconnected(Error),
}
impl<S: DeserializeOwned + 'static> ConnectionState<S> {
pub fn state_type(&self) -> ConnectionStateType {
match self {
ConnectionState::Connecting(_) => ConnectionStateType::Connecting,
ConnectionState::Connected { .. } => ConnectionStateType::Connected,
ConnectionState::Disconnected(_) => ConnectionStateType::Disconnected,
}
}
}
impl<S: DeserializeOwned + 'static> Default for ConnectionState<S> {
fn default() -> Self {
ConnectionState::Disconnected(Error::ClientDisconnected)
}
}
impl<S: DeserializeOwned + 'static> fmt::Debug for ConnectionState<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
use ConnectionState::*;
match self {
Connecting(_) => write!(f, "Connecting"),
Connected(_) => write!(f, "Connected"),
Disconnected(err) => write!(f, "Disconnected: {}", err),
}
}
}
pub struct Connecting<S: DeserializeOwned + 'static>(
Pin<Box<dyn Future<Output = Result<Client<S>, Error>> + Send>>,
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionStateType {
Connecting,
Connected,
Disconnected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConnectionStateTransition {
pub old: ConnectionStateType,
pub new: ConnectionStateType,
}
fn try_future<T, F: FutureExt<Output = T> + Unpin>(future: &mut F) -> Option<T> {
let mut context = Context::from_waker(Waker::noop());
match future.poll(&mut context) {
Poll::Ready(value) => Some(value),
Poll::Pending => None,
}
}