~starkingdoms/starkingdoms

5c97ea362efb1fd079466bfaae1ed07be603b783 — core 2 years ago dda1b9e
hell is over!
M Cargo.lock => Cargo.lock +1 -0
@@ 76,6 76,7 @@ dependencies = [
 "console_log",
 "futures",
 "js-sys",
 "lazy_static",
 "log",
 "protocol",
 "rmp-serde",

M client/Cargo.toml => client/Cargo.toml +2 -1
@@ 20,4 20,5 @@ url = "2.3"
protocol = { version = "0.1.0", path = "../protocol" }
rmp-serde = "1.1"
ws_stream_wasm = "0.7"
serde = { version = "1", features = ["derive"] }
\ No newline at end of file
serde = { version = "1", features = ["derive"] }
lazy_static = "1.4"
\ No newline at end of file

A client/src/chat.rs => client/src/chat.rs +19 -0
@@ 0,0 1,19 @@
use wasm_bindgen::prelude::*;
use protocol::MessageC2S;
use crate::CLIENT;
use futures::SinkExt;

#[wasm_bindgen]
pub async fn send_chat(message: &str) -> Result<(), JsError> {
    let client_data = &mut CLIENT.write()?.client_data;

    if let Some(data) = client_data {
        send!(data.tx, &MessageC2S::Chat {
            message: message.to_string()
        }).await?;
    } else {
        return Err(JsError::new("Client not yet connected to server"));
    }

    Ok(())
}
\ No newline at end of file

M client/src/lib.rs => client/src/lib.rs +63 -11
@@ 1,7 1,7 @@
use std::error::Error;
use futures::stream::{SplitSink, SplitStream};
use futures::StreamExt;
use log::{debug, error, info, Level, trace};
use log::{debug, error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use ws_stream_wasm::{WsMessage, WsMeta, WsStream};
use protocol::State;


@@ 9,9 9,14 @@ use protocol::PROTOCOL_VERSION;
use protocol::MessageS2C;
use protocol::MessageC2S;
use futures::SinkExt;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::sync::RwLock;
use futures::FutureExt;

#[macro_use]
pub mod macros;
pub mod chat;

#[wasm_bindgen]
extern {


@@ 19,11 24,6 @@ extern {
}

#[wasm_bindgen]
pub fn send_chat(chat: &str) {
    info!("sending chat: {}", chat);
}

#[wasm_bindgen]
pub async fn rust_init(gateway: &str, username: &str) -> Result<(), JsError> {
    console_log::init_with_level(Level::Debug).unwrap();



@@ 37,17 37,27 @@ pub async fn rust_init(gateway: &str, username: &str) -> Result<(), JsError> {
        }
    };

    info!("Gateway client exited");
    info!("Gateway client set up successfully");

    Ok(())
}

pub struct Client {
    pub client_data: Option<ClientData>,
}

pub struct ClientData {
    pub state: State,
    pub tx: SplitSink<WsStream, WsMessage>,
    pub rx: SplitStream<WsStream>
}

lazy_static! {
    pub static ref CLIENT: Arc<RwLock<Client>> = Arc::new(RwLock::new(Client {
        client_data: None
    }));
}

pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {
    info!("FAST CONNECT: {}", gateway);
    let gateway_url = url::Url::parse(gateway)?;


@@ 56,7 66,7 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {
    trace!("Connected to gateway socket");
    let (tx, rx) = ws_stream.split();

    let mut client = Client {
    let mut client_data = ClientData {
        state: State::Handshake,
        tx,
        rx


@@ 64,7 74,7 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {

    trace!("Split stream, handshaking with server");

    send!(client.tx, &MessageC2S::Hello {
    send!(client_data.tx, &MessageC2S::Hello {
        next_state: State::Play,
        version: PROTOCOL_VERSION,
        requested_username: username.to_string()


@@ 72,22 82,64 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {

    trace!("Sent handshake start packet");

    if let Some(msg) = recv_now!(client.rx)? {
    if let Some(msg) = recv_now!(client_data.rx)? {
        let typed_msg: MessageS2C = msg;

        match typed_msg {
            MessageS2C::Hello { version, given_username, next_state } => {
                info!("FAST CONNECT - connected to server protocol {} given username {}, switching to state {:?}", version, given_username, next_state);
                client.state = next_state;
                client_data.state = next_state;
            },
            MessageS2C::Goodbye { reason } => {
                error!("server disconnected before finishing handshake: {:?}", reason);
                return Err(format!("disconnected by server: {:?}", reason).into());
            },
            _ => {
                warn!("received unexpected packet from server: {:?}", typed_msg);
            }
        }
    } else {
        error!("Server closed the connection")
    }

    CLIENT.write()?.client_data = Some(client_data);

    Ok(())
}

#[wasm_bindgen]
pub async fn update_socket() -> Result<(), JsError> {
    let mut client = CLIENT.write()?;

    if client.client_data.is_none() {
        return Err(JsError::new("Client not yet initialized"));
    }

    let client_data = client.client_data.as_mut().unwrap();

    let maybe_msg: Option<MessageS2C> = match recv!(client_data.rx) {
        Ok(r) => r,
        Err(e) => {
            return Err(JsError::new(e))
        }
    };

    if let Some(msg) = maybe_msg {
        match msg {
            MessageS2C::Goodbye { reason } => {
                info!("server sent disconnect: {:?}", reason);
                client.client_data = None;
                return Err(JsError::new("disconnected by server"));
            }
            MessageS2C::Chat { from, message } => {
                info!("[CHAT] {}: {}", from, message);
                // TODO: Handle
            },
            _ => {
                warn!("server sent unexpected packet {:?}, ignoring", msg);
            }
        }
    }

    Ok(())
}
\ No newline at end of file

M client/src/macros.rs => client/src/macros.rs +7 -15
@@ 18,24 18,16 @@ macro_rules! recv {
        {
            if let Some(future_result) = $reader.next().now_or_never() {
                if let Some(msg) = future_result {
                    match msg {
                        Ok(msg) => {
                            if msg.is_binary() {
                                match rmp_serde::from_slice(&msg.into_data()) {
                                    Ok(d) => Ok(Some(d)),
                                    Err(e) => {
                                        log::error!("error deserializing message: {}", e);
                                        Ok(None)
                                    }
                                }
                            } else {
                    if let WsMessage::Binary(msg) = msg {
                        match rmp_serde::from_slice(&msg) {
                            Ok(d) => Ok(Some(d)),
                            Err(e) => {
                                log::error!("error deserializing message: {}", e);
                                Ok(None)
                            }
                        },
                        Err(e) => {
                            log::error!("error receiving message: {}", e);
                            Ok(None)
                        }
                    } else {
                        Ok(None)
                    }
                } else {
                    log::error!("pipe closed");

M protocol/src/lib.rs => protocol/src/lib.rs +10 -1
@@ 2,7 2,7 @@ use serde::{Deserialize, Serialize};

pub const PROTOCOL_VERSION: u32 = 1;

#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum State {
    Handshake,
    Play


@@ 18,6 18,10 @@ pub enum MessageC2S {

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        message: String
    }
}



@@ 31,6 35,11 @@ pub enum MessageS2C {

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        from: String,
        message: String
    }
}


M server/src/client_handler.rs => server/src/client_handler.rs +31 -5
@@ 13,19 13,24 @@ use crate::{send, recv};

pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: Receiver<ClientHandlerMessage>, mut client_tx: SplitSink<WebSocketStream<Upgraded>, Message>, mut client_rx: SplitStream<WebSocketStream<Upgraded>>) -> Result<(), Box<dyn Error>> {
    let mut state = State::Handshake;
    let mut username = String::new();

    loop {
        if let Some(msg) = rx.recv().await {
            match msg {
                ClientHandlerMessage::Tick => {} // this intentionally does nothing
                ClientHandlerMessage::Tick => {} // this intentionally does nothing,
                ClientHandlerMessage::ChatMessage { from, message } => {
                    send!(client_tx, &MessageS2C::Chat {
                        message,
                        from
                    }).await?;
                }
            }
        } else {
            info!("channel closed, shutting down");
            break;
        }

        info!("here");

        if let Some(pkt) = recv!(client_rx)? {
            match state {
                State::Handshake => {


@@ 69,13 74,22 @@ pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: 

                            send!(client_tx, &MessageS2C::Hello {
                                    version,
                                    given_username: requested_username,
                                    given_username: requested_username.clone(),
                                    next_state,
                                }).await?;
                            state = next_state;
                            username = requested_username;
                        },
                        MessageC2S::Goodbye { reason } => {
                            info!("client sent goodbye: {:?}", reason);
                            break;
                        },
                        MessageC2S::Chat { .. } => {
                            error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
                            send!(client_tx, &MessageS2C::Goodbye {
                                    reason: GoodbyeReason::UnexpectedPacket,
                                }).await?;
                            break;
                        }
                    }
                }


@@ 87,10 101,22 @@ pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: 
                                    reason: GoodbyeReason::UnexpectedPacket,
                                }).await?;
                            break;
                        }
                        },
                        MessageC2S::Goodbye { reason } => {
                            info!("client sent goodbye: {:?}", reason);
                            break;
                        },
                        MessageC2S::Chat { message } => {
                            info!("[{}] CHAT: [{}] {}", remote_addr, username, message);

                            for (addr, client_thread) in mgr.handlers.read().await.iter() {
                                match client_thread.tx.send(ClientHandlerMessage::ChatMessage { from: username.clone(), message: message.clone() }).await {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("unable to update a client thread: {}", e);
                                    }
                                }
                            }
                        }
                    }
                }

M server/src/handler.rs => server/src/handler.rs +2 -1
@@ 20,5 20,6 @@ pub struct ClientHandler {
}

pub enum ClientHandlerMessage {
    Tick
    Tick,
    ChatMessage { from: String, message: String }
}
\ No newline at end of file

M server/src/main.rs => server/src/main.rs +2 -2
@@ 7,7 7,7 @@ use tokio_tungstenite::WebSocketStream;
use tungstenite::{Error, handshake};
use futures::stream::StreamExt;
use lazy_static::lazy_static;
use log::{error, info};
use log::{error, info, Level};
use tokio::sync::RwLock;
use protocol::State;
use crate::handler::{ClientHandler, ClientManager};


@@ 117,7 117,7 @@ lazy_static! {

#[tokio::main]
async fn main() {
    simple_logger::init_with_env().expect("Unable to start logging service");
    simple_logger::init_with_level(Level::Debug).expect("Unable to start logging service");

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));


M web/index.html => web/index.html +9 -9
@@ 6,14 6,14 @@
    </head>

    <body>
        <script type="module">
            // If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
            //                                     v
            import init from "./dist/client.js";

            init().then(() => {
                // wasm-pack code here
            })
        </script>
        <form target="/play.html" method="GET">
            <label for="server">Gateway server</label>
            <input type="text" name="server" id="server" value="ws://localhost:3000/ws" required />
            <br>
            <label for="username">Username</label>
            <input type="text" name="username" id="username" required />
            <br>
            <button>Launch!</button>
        </form>
    </body>
</html>
\ No newline at end of file

M web/play.html => web/play.html +11 -5
@@ 20,13 20,19 @@
        <script type="module">
            // If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
            //                                     v
            import init, { rust_init, send_chat } from "./dist/client.js";
            import init, { rust_init, send_chat, update_socket } from "./dist/client.js";
            init().then(() => {
                rust_init("ws://localhost:3000/ws", "core");
                const urlSearchParams = new URLSearchParams(window.location.search);

                document.getElementById("chat-submit").addEventListener("click", e => {
                    send_chat(document.getElementById("chat-value").value);
                })
                rust_init(urlSearchParams.get("server"), urlSearchParams.get("username")).then(() => {
                    document.getElementById("chat-submit").addEventListener("click", e => {
                        send_chat(document.getElementById("chat-value").value);
                    });

                    setInterval(() => {
                        update_socket();
                    }, 5);
                });
            })
        </script>
    </body>