client lobby refactor
This commit is contained in:
parent
01c1a4554a
commit
c96e019071
|
|
@ -737,3 +737,20 @@ input {
|
|||
.zoom {
|
||||
zoom: 200%;
|
||||
}
|
||||
|
||||
.client-lobby-player-list {
|
||||
@extend .row-list;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
&>* {
|
||||
border: 1px solid white;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use core::{num::NonZeroU8, sync::atomic::AtomicBool, time::Duration};
|
||||
use std::collections::VecDeque;
|
||||
use std::{collections::VecDeque, rc::Rc};
|
||||
|
||||
use futures::{
|
||||
SinkExt, StreamExt,
|
||||
|
|
@ -21,12 +21,13 @@ use werewolves_proto::{
|
|||
player::PlayerId,
|
||||
role::RoleTitle,
|
||||
};
|
||||
use yew::{html::Scope, prelude::*};
|
||||
use yew::{html::Scope, prelude::*, suspense::use_future};
|
||||
|
||||
use crate::{
|
||||
clients::client::connection::{Connection2, ConnectionError},
|
||||
components::{
|
||||
Button, Identity, Notification,
|
||||
client::{ClientNav, InputName},
|
||||
client::{ClientNav, Signin},
|
||||
},
|
||||
storage::StorageKey,
|
||||
};
|
||||
|
|
@ -184,7 +185,7 @@ impl Connection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum ClientEvent {
|
||||
Disconnected,
|
||||
Waiting,
|
||||
|
|
@ -219,6 +220,168 @@ impl TryFrom<ServerMessage> for ClientEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum ClientEvent2 {
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Waiting,
|
||||
ShowRole(RoleTitle),
|
||||
|
||||
Lobby {
|
||||
joined: bool,
|
||||
players: Rc<[PublicIdentity]>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct ClientContext {
|
||||
pub error_cb: Callback<Option<WerewolfError>>,
|
||||
pub forced_identity: Option<Identification>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Client2(ClientProps { auto_join }: &ClientProps) -> Html {
|
||||
let client_state = use_state(|| ClientEvent2::Connecting);
|
||||
let ClientContext {
|
||||
error_cb,
|
||||
forced_identity,
|
||||
} = use_context::<ClientContext>().unwrap_or_default();
|
||||
let force = use_force_update();
|
||||
|
||||
let ident = if let Some(Identification { player_id, public }) = forced_identity {
|
||||
(player_id, public)
|
||||
} else {
|
||||
match PlayerId::load_from_storage()
|
||||
.and_then(|pid| PublicIdentity::load_from_storage().map(|ident| (pid, ident)))
|
||||
{
|
||||
Ok((pid, ident)) => (pid, ident),
|
||||
Err(StorageError::KeyNotFound(_)) => {
|
||||
let on_signin = Callback::from(move |ident: PublicIdentity| {
|
||||
PlayerId::new().save_to_storage().expect("saving player id");
|
||||
ident.save_to_storage().expect("saving ident");
|
||||
force.force_update();
|
||||
});
|
||||
return html! {
|
||||
<Signin callback={on_signin}/>
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
error_cb.emit(Some(err.into()));
|
||||
PlayerId::delete();
|
||||
PublicIdentity::delete();
|
||||
force.force_update();
|
||||
return html! {};
|
||||
}
|
||||
}
|
||||
};
|
||||
let ident = use_state(|| ident);
|
||||
let (send, recv) = yew::platform::pinned::mpsc::unbounded();
|
||||
let send = use_state(|| send);
|
||||
let recv = use_mut_ref(|| recv);
|
||||
let connection = use_mut_ref(|| Connection2::new(client_state.setter(), ident.clone(), recv));
|
||||
|
||||
let content = match &*client_state {
|
||||
ClientEvent2::Disconnected => html! {<p>{"disconnected"}</p>},
|
||||
ClientEvent2::Connecting => {
|
||||
if let Err(err) = connection
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| ConnectionError::ConnectionAlreadyActive)
|
||||
.and_then(|mut conn| conn.start())
|
||||
{
|
||||
error_cb.emit(Some(err.into()));
|
||||
}
|
||||
if *auto_join {
|
||||
let _ = send.send_now(ClientMessage::Hello);
|
||||
}
|
||||
html! {<p>{"connecting..."}</p>}
|
||||
}
|
||||
ClientEvent2::Waiting => html! {<p>{"waiting..."}</p>},
|
||||
ClientEvent2::ShowRole(role_title) => {
|
||||
let send = (*send).clone();
|
||||
let error_cb = error_cb.clone();
|
||||
let on_click = Callback::from(move |_| {
|
||||
if let Err(err) = send.clone().send_now(ClientMessage::RoleAck) {
|
||||
error_cb.emit(Some(err.into()));
|
||||
}
|
||||
});
|
||||
html! {
|
||||
<div class="game-start-role">
|
||||
<p>{format!("Your role: {role_title}")}</p>
|
||||
<button onclick={on_click}>{"got it"}</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ClientEvent2::Lobby { joined, players } => {
|
||||
let player = {
|
||||
html! {
|
||||
<Identity ident={ident.1.clone()} class="zoom"/>
|
||||
}
|
||||
};
|
||||
let player_list = players
|
||||
.iter()
|
||||
.map(|ident| {
|
||||
html! {
|
||||
<Identity ident={ident.clone()}/>
|
||||
}
|
||||
})
|
||||
.collect::<Html>();
|
||||
|
||||
let button_send = (*send).clone();
|
||||
let err_cb = error_cb.clone();
|
||||
let button = if *joined {
|
||||
let cb = move |_| {
|
||||
if let Err(err) = button_send.send_now(ClientMessage::Goodbye) {
|
||||
err_cb.emit(Some(err.into()));
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<Button on_click={cb}>{"leave"}</Button>
|
||||
}
|
||||
} else {
|
||||
let cb = move |_| {
|
||||
if let Err(err) = button_send.send_now(ClientMessage::Hello) {
|
||||
err_cb.emit(Some(err.into()));
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<Button on_click={cb}>{"join"}</Button>
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="column-list gap">
|
||||
{player}
|
||||
<h2>{"there are currently "}{players.len()}{" players in the lobby"}</h2>
|
||||
{button}
|
||||
<div class="client-lobby-player-list">
|
||||
{player_list}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let nav = {
|
||||
let send = (*send).clone();
|
||||
let error_cb = error_cb.clone();
|
||||
let client_nav_msg_cb = move |msg| {
|
||||
if let Err(err) = send.send_now(msg) {
|
||||
error_cb.emit(Some(err.into()))
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<ClientNav message_callback={client_nav_msg_cb} />
|
||||
}
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
{nav}
|
||||
{content}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
player: Option<Identification>,
|
||||
send: Sender<ClientMessage>,
|
||||
|
|
@ -284,7 +447,7 @@ impl Component for Client {
|
|||
scope.send_message(Message::SetPublicIdentity(public));
|
||||
});
|
||||
return html! {
|
||||
<InputName callback={callback}/>
|
||||
<Signin callback={callback}/>
|
||||
};
|
||||
} else if self.recv.is_some() {
|
||||
// Player info loaded, but connection isn't started
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
use core::num::NonZeroU8;
|
||||
use core::time::Duration;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use gloo::net::websocket::{self, futures::WebSocket};
|
||||
use instant::Instant;
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use werewolves_proto::message::{PlayerUpdate, ServerMessage};
|
||||
use werewolves_proto::{
|
||||
message::{ClientMessage, Identification, PublicIdentity},
|
||||
player::PlayerId,
|
||||
};
|
||||
use yew::{platform::pinned::mpsc::UnboundedReceiver, prelude::*};
|
||||
|
||||
use crate::clients::client::ClientEvent2;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Error)]
|
||||
pub enum ConnectionError {
|
||||
#[error("connection already active")]
|
||||
ConnectionAlreadyActive,
|
||||
}
|
||||
|
||||
fn url() -> String {
|
||||
format!(
|
||||
"{}client",
|
||||
option_env!("LOCAL")
|
||||
.map(|_| crate::clients::DEBUG_URL)
|
||||
.unwrap_or(crate::clients::LIVE_URL)
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Connection2 {
|
||||
state: UseStateSetter<ClientEvent2>,
|
||||
ident: UseStateHandle<(PlayerId, PublicIdentity)>,
|
||||
receiver: Rc<RefCell<UnboundedReceiver<ClientMessage>>>,
|
||||
active: Rc<RefCell<()>>,
|
||||
}
|
||||
|
||||
impl Connection2 {
|
||||
pub fn new(
|
||||
state: UseStateSetter<ClientEvent2>,
|
||||
ident: UseStateHandle<(PlayerId, PublicIdentity)>,
|
||||
receiver: Rc<RefCell<UnboundedReceiver<ClientMessage>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
state,
|
||||
ident,
|
||||
receiver,
|
||||
active: Rc::new(RefCell::new(())),
|
||||
}
|
||||
}
|
||||
fn identification(&self) -> Identification {
|
||||
Identification {
|
||||
player_id: self.ident.0.clone(),
|
||||
public: self.ident.1.clone(),
|
||||
}
|
||||
}
|
||||
async fn connect_ws() -> WebSocket {
|
||||
let url = url();
|
||||
loop {
|
||||
match WebSocket::open(&url) {
|
||||
Ok(ws) => break ws,
|
||||
Err(err) => {
|
||||
log::error!("connect: {err}");
|
||||
yew::platform::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_message(msg: &impl Serialize) -> websocket::Message {
|
||||
#[cfg(feature = "json")]
|
||||
{
|
||||
websocket::Message::Text(serde_json::to_string(msg).expect("message serialization"))
|
||||
}
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
websocket::Message::Bytes({
|
||||
let mut v = Vec::new();
|
||||
ciborium::into_writer(msg, &mut v).expect("serializing message");
|
||||
v
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<(), ConnectionError> {
|
||||
let active = self
|
||||
.active
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| ConnectionError::ConnectionAlreadyActive)?;
|
||||
core::mem::drop(active);
|
||||
let mut conn = self.clone();
|
||||
yew::platform::spawn_local(async move {
|
||||
let active = conn.active.clone();
|
||||
conn.active = Rc::new(RefCell::new(()));
|
||||
let active_borrow = active.borrow_mut();
|
||||
conn.run().await;
|
||||
core::mem::drop(active_borrow);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
const CONNECT_WAIT: Duration = Duration::from_secs(3);
|
||||
let url = url();
|
||||
let mut last_connect: Option<Instant> = None;
|
||||
'outer: loop {
|
||||
if let Some(last_connect) = last_connect.as_ref() {
|
||||
let time_since_last = Instant::now() - *last_connect;
|
||||
if time_since_last <= CONNECT_WAIT {
|
||||
yew::platform::time::sleep(CONNECT_WAIT.saturating_sub(time_since_last)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
last_connect = Some(Instant::now());
|
||||
log::info!("connecting to {url}");
|
||||
let mut ws = Self::connect_ws().await.fuse();
|
||||
log::info!("connected to {url}");
|
||||
|
||||
log::debug!("sending self ident");
|
||||
if let Err(err) = ws.send(Self::encode_message(&self.identification())).await {
|
||||
log::error!("websocket identification send: {err}");
|
||||
continue 'outer;
|
||||
};
|
||||
|
||||
log::debug!("sending get state");
|
||||
if let Err(err) = ws
|
||||
.send(Self::encode_message(&ClientMessage::GetState))
|
||||
.await
|
||||
{
|
||||
log::error!("websocket identification send: {err}");
|
||||
continue 'outer;
|
||||
};
|
||||
log::debug!("beginning listening loop");
|
||||
|
||||
loop {
|
||||
let mut recv = self.receiver.borrow_mut();
|
||||
let msg = futures::select! {
|
||||
r = ws.next() => {
|
||||
match r {
|
||||
Some(Ok(msg)) => msg,
|
||||
Some(Err(err)) => {
|
||||
log::error!("websocket recv: {err}");
|
||||
continue 'outer;
|
||||
},
|
||||
None => {
|
||||
log::warn!("websocket closed");
|
||||
continue 'outer;
|
||||
},
|
||||
}
|
||||
}
|
||||
r = recv.next() => {
|
||||
match r {
|
||||
Some(msg) => {
|
||||
log::info!("sending message: {msg:?}");
|
||||
if let Err(err) = ws.send(
|
||||
Self::encode_message(&msg)
|
||||
).await {
|
||||
log::error!("websocket send error: {err}");
|
||||
continue 'outer;
|
||||
}
|
||||
continue;
|
||||
},
|
||||
None => {
|
||||
log::info!("recv channel closed");
|
||||
return;
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
core::mem::drop(recv);
|
||||
let parse = {
|
||||
#[cfg(feature = "json")]
|
||||
{
|
||||
match msg {
|
||||
websocket::Message::Text(text) => {
|
||||
serde_json::from_str::<ServerMessage>(&text)
|
||||
}
|
||||
websocket::Message::Bytes(items) => serde_json::from_slice(&items),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
match msg {
|
||||
websocket::Message::Text(_) => {
|
||||
log::error!("text messages not supported in cbor mode; discarding");
|
||||
continue;
|
||||
}
|
||||
websocket::Message::Bytes(bytes) => {
|
||||
ciborium::from_reader::<ServerMessage, _>(bytes.as_slice())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
match parse {
|
||||
Ok(msg) => {
|
||||
if let Some(state) = self.message_to_client_state(msg) {
|
||||
self.state.set(state);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("parsing server message: {err}; ignoring.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn message_to_client_state(&self, msg: ServerMessage) -> Option<ClientEvent2> {
|
||||
log::debug!("received message: {msg:?}");
|
||||
Some(match msg {
|
||||
ServerMessage::Disconnect => ClientEvent2::Disconnected,
|
||||
ServerMessage::LobbyInfo {
|
||||
joined,
|
||||
mut players,
|
||||
} => {
|
||||
const LAST: NonZeroU8 = NonZeroU8::new(0xFF).unwrap();
|
||||
players.sort_by(|l, r| l.number.unwrap_or(LAST).cmp(&r.number.unwrap_or(LAST)));
|
||||
ClientEvent2::Lobby {
|
||||
joined,
|
||||
players: players.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
ServerMessage::GameStart { role } => ClientEvent2::ShowRole(role),
|
||||
ServerMessage::InvalidMessageForGameState => {
|
||||
log::error!("invalid message for game state");
|
||||
return None;
|
||||
}
|
||||
ServerMessage::NoSuchTarget => {
|
||||
log::error!("no such target");
|
||||
return None;
|
||||
}
|
||||
ServerMessage::Update(PlayerUpdate::Number(new_num)) => {
|
||||
let (pid, mut ident) = (*self.ident).clone();
|
||||
ident.number = Some(new_num);
|
||||
self.ident.set((pid, ident));
|
||||
return None;
|
||||
}
|
||||
ServerMessage::GameOver(_)
|
||||
| ServerMessage::Sleep
|
||||
| ServerMessage::Reset
|
||||
| ServerMessage::GameInProgress => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod client {
|
||||
mod client;
|
||||
pub mod connection;
|
||||
pub use client::*;
|
||||
}
|
||||
pub mod host {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ use yew::prelude::*;
|
|||
use crate::components::Button;
|
||||
|
||||
#[derive(Debug, PartialEq, Properties)]
|
||||
pub struct InputProps {
|
||||
pub callback: Callback<PublicIdentity, ()>,
|
||||
pub struct SigninProps {
|
||||
pub callback: Callback<PublicIdentity>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn InputName(props: &InputProps) -> Html {
|
||||
pub fn Signin(props: &SigninProps) -> Html {
|
||||
let callback = props.callback.clone();
|
||||
let num_value = use_state(String::new);
|
||||
let name_value = use_state(String::new);
|
||||
|
|
@ -44,27 +44,7 @@ pub fn InputName(props: &InputProps) -> Html {
|
|||
number,
|
||||
});
|
||||
});
|
||||
// let num_value = num_value.clone();
|
||||
// let on_change = move |ev: InputEvent| {
|
||||
// let data = ev.data();
|
||||
// if let Some(z) = data.as_ref().and_then(|d| d.trim().parse::<u8>().ok())
|
||||
// && !(z == 0 && num_value.is_empty())
|
||||
// {
|
||||
// let new_value = format!("{}{z}", num_value.as_str());
|
||||
// num_value.set(new_value);
|
||||
|
||||
// return;
|
||||
// } else if data.is_none()
|
||||
// && let Some(target) = ev.target_dyn_into::<HtmlInputElement>()
|
||||
// {
|
||||
// num_value.set(target.value());
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if let Some(target) = ev.target_dyn_into::<HtmlInputElement>() {
|
||||
// target.set_value(num_value.as_str());
|
||||
// }
|
||||
// };
|
||||
let on_change = crate::components::input_element_number_oninput(num_value);
|
||||
html! {
|
||||
<div class="signin">
|
||||
|
|
|
|||
|
|
@ -37,17 +37,3 @@ pub fn Identity(props: &IdentityProps) -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
pub struct StatefulIdentityProps {
|
||||
pub ident: UseStateHandle<PublicIdentity>,
|
||||
#[prop_or_default]
|
||||
pub class: Option<String>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn StatefulIdentity(props: &StatefulIdentityProps) -> Html {
|
||||
html! {
|
||||
<Identity ident={props.ident.deref().clone()} class={props.class.clone()}/>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ use werewolves_proto::{
|
|||
message::{Identification, PublicIdentity},
|
||||
player::PlayerId,
|
||||
};
|
||||
use yew::prelude::*;
|
||||
use yew::{context::ContextProviderProps, prelude::*};
|
||||
|
||||
use crate::clients::{
|
||||
client::{Client, ClientProps, Message},
|
||||
client::{Client, Client2, ClientContext, ClientProps, Message},
|
||||
host::{Host, HostEvent},
|
||||
};
|
||||
|
||||
|
|
@ -70,25 +70,62 @@ fn main() {
|
|||
clients.append_child(&dupe).unwrap();
|
||||
}
|
||||
|
||||
let client =
|
||||
yew::Renderer::<Client>::with_root_and_props(dupe, ClientProps { auto_join: true })
|
||||
.render();
|
||||
client.send_message(Message::ForceIdentity(Identification {
|
||||
let client = yew::Renderer::<ContextProvider<ClientContext>>::with_root_and_props(
|
||||
dupe,
|
||||
ContextProviderProps {
|
||||
context: ClientContext {
|
||||
error_cb: error_callback.clone(),
|
||||
forced_identity: Some(Identification {
|
||||
player_id,
|
||||
public: PublicIdentity {
|
||||
name: name.to_string(),
|
||||
pronouns: Some(String::from("he/him")),
|
||||
number: None,
|
||||
},
|
||||
}));
|
||||
client.send_message(Message::SetErrorCallback(error_callback.clone()));
|
||||
}
|
||||
} else {
|
||||
let client = yew::Renderer::<Client>::with_root_and_props(
|
||||
app_element,
|
||||
ClientProps { auto_join: false },
|
||||
}),
|
||||
},
|
||||
children: html! {
|
||||
<Client2 auto_join=true/>
|
||||
},
|
||||
},
|
||||
)
|
||||
.render();
|
||||
client.send_message(Message::SetErrorCallback(error_callback));
|
||||
|
||||
// let client = yew::Renderer::<Client2>::with_root_and_props(
|
||||
// dupe,
|
||||
// ClientProps { auto_join: true },
|
||||
// )
|
||||
// .render();
|
||||
|
||||
// client.send_message(Message::ForceIdentity(Identification {
|
||||
// player_id,
|
||||
// public: PublicIdentity {
|
||||
// name: name.to_string(),
|
||||
// pronouns: Some(String::from("he/him")),
|
||||
// number: None,
|
||||
// },
|
||||
// }));
|
||||
// client.send_message(Message::SetErrorCallback(error_callback.clone()));
|
||||
}
|
||||
} else {
|
||||
let client = yew::Renderer::<ContextProvider<ClientContext>>::with_root_and_props(
|
||||
app_element,
|
||||
ContextProviderProps {
|
||||
context: ClientContext {
|
||||
error_cb: error_callback.clone(),
|
||||
forced_identity: None,
|
||||
},
|
||||
children: html! {
|
||||
<Client2 auto_join=false/>
|
||||
},
|
||||
},
|
||||
)
|
||||
.render();
|
||||
// let client = yew::Renderer::<Client>::with_root_and_props(
|
||||
// app_element,
|
||||
// ClientProps { auto_join: false },
|
||||
// )
|
||||
// .render();
|
||||
// client.send_message(Message::SetErrorCallback(error_callback));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use gloo::storage::errors::StorageError;
|
||||
use thiserror::Error;
|
||||
use werewolves_proto::error::GameError;
|
||||
use werewolves_proto::{error::GameError, message::ClientMessage};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Error)]
|
||||
use crate::clients::client::connection::ConnectionError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WerewolfError {
|
||||
#[error("{0}")]
|
||||
GameError(#[from] GameError),
|
||||
|
|
@ -13,6 +15,10 @@ pub enum WerewolfError {
|
|||
InvalidTarget,
|
||||
#[error("send error: {0}")]
|
||||
SendError(#[from] futures::channel::mpsc::SendError),
|
||||
#[error("send error: {0}")]
|
||||
ClientSendError(#[from] yew::platform::pinned::mpsc::SendError<ClientMessage>),
|
||||
#[error("connection error: {0}")]
|
||||
ConnectionError(#[from] ConnectionError),
|
||||
}
|
||||
|
||||
impl From<StorageError> for WerewolfError {
|
||||
|
|
|
|||
Loading…
Reference in New Issue