~starkingdoms/starkingdoms

70a2f492c79c50a70a7ceacf281a5a82a57af5c6 — core 2 years ago 49ce5c9 + c663cfb
Merge remote-tracking branch 'origin/master'

# Conflicts:
#	Cargo.lock
#	client/src/lib.rs
#	server/src/client_handler.rs
#	server/src/handler.rs
#	server/src/main.rs
M Cargo.lock => Cargo.lock +45 -0
@@ 3,6 3,17 @@
version = 3

[[package]]
name = "async-recursion"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.13",
]

[[package]]
name = "async_io_stream"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 102,6 113,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "client"
version = "0.1.0"
dependencies = [
 "console_log",
 "futures",
 "js-sys",
 "lazy_static",
 "log",
 "protocol",
 "rmp-serde",
 "serde",
 "url",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "ws_stream_wasm",
]

[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 435,6 465,15 @@ dependencies = [
]

[[package]]
name = "markdown"
version = "1.0.0-alpha.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de49c677e95e00eaa74c42a0b07ea55e1e0b1ebca5b2cbc7657f288cd714eb"
dependencies = [
 "unicode-id",
]

[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 950,6 989,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"

[[package]]
name = "unicode-id"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a"

[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"

M client/Cargo.toml => client/Cargo.toml +14 -2
@@ 11,7 11,6 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "Window"]}
console_log = { version = "1", features = ["color"] }
log = "0.4"
futures = { version = "0.3", default-features = false }


@@ 21,4 20,17 @@ starkingdoms-protocol = { version = "0.1.0", path = "../protocol" }
rmp-serde = "1.1"
ws_stream_wasm = "0.7"
serde = { version = "1", features = ["derive"] }
lazy_static = "1.4"
\ No newline at end of file
lazy_static = "1.4"
markdown = "1.0.0-alpha.7" # DO NOT DOWNGRADE
async-recursion = "1"


[dependencies.web-sys]
version = "0.3.4"
features = [
    'Document',
    'Element',
    'HtmlElement',
    'Node',
    'Window',
]
\ No newline at end of file

M client/src/lib.rs => client/src/lib.rs +85 -5
@@ 1,19 1,30 @@
use std::error::Error;
use futures::stream::{SplitSink, SplitStream};
use futures::StreamExt;
use log::{debug, error, info, Level, trace, warn};
use log::{error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use ws_stream_wasm::{WsMessage, WsMeta, WsStream};
use starkingdoms_protocol::State;
use starkingdoms_protocol::PROTOCOL_VERSION;
use starkingdoms_protocol::MessageS2C;
use starkingdoms_protocol::MessageC2S;
use ws_stream_wasm::{WsErr, WsMessage, WsMeta, WsStream};
use protocol::State;
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 async_recursion::async_recursion;
use futures::FutureExt;

use wasm_bindgen_futures::JsFuture;
use web_sys::Window;

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


@@ 25,11 36,13 @@ extern {

#[wasm_bindgen]
pub async fn rust_init(gateway: &str, username: &str) -> Result<(), JsError> {
    update_status("Starting logger...");

    console_log::init_with_level(Level::Debug).unwrap();

    info!("Logger setup successfully");

    match main(gateway, username).await {
    match main(gateway, username, 1).await {
        Ok(c) => c,
        Err(e) => {
            error!("Error initializing gateway client: {}", e);


@@ 58,11 71,41 @@ lazy_static! {
    }));
}

pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {
pub const MAX_CONNECTION_TRIES: i32 = 10;

#[async_recursion(?Send)]
#[allow(clippy::only_used_in_recursion)]
pub async fn main(gateway: &str, username: &str, backoff: i32) -> Result<(), Box<dyn Error>> {
    info!("Backing off connection: waiting {} seconds", backoff * backoff);

    wait_for(sleep(backoff * backoff * 1000)).await;

    if backoff > MAX_CONNECTION_TRIES {
        update_status("Connection to server failed");
        return Err("Hit backoff limit during reconnection attempt".into());
    }
    if backoff == 1 {
        update_status("Connecting to server...");
    } else {
        update_status(&format!("Connection failed, retrying... (try {}/10)", backoff));
    }

    info!("FAST CONNECT: {}", gateway);
    let gateway_url = url::Url::parse(gateway)?;
    trace!("Gateway URL parsed");
    let (_ws, ws_stream) = WsMeta::connect(gateway_url, None).await?;
    let (_ws, ws_stream) = match WsMeta::connect(gateway_url, None).await {
        Ok(r) => r,
        Err(e) => {
            return match e {
                WsErr::ConnectionFailed { .. } => {
                    main(gateway, username, backoff + 1).await
                },
                _ => {
                    Err(e.into())
                }
            }
        }
    };
    trace!("Connected to gateway socket");
    let (tx, rx) = ws_stream.split();



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

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

    update_status("Handshaking with server...");

    send!(client_data.tx, &MessageC2S::Hello {
        next_state: State::Play,
        version: PROTOCOL_VERSION,


@@ 104,6 149,8 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {

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

    update_status(&format!("Connected! Username: {}", username));

    Ok(())
}



@@ 133,7 180,18 @@ pub async fn update_socket() -> Result<(), JsError> {
            }
            MessageS2C::Chat { from, message } => {
                info!("[CHAT] {}: {}", from, message);
                // TODO: Handle

                let window: Window = web_sys::window().expect("no global `window` exists");
                let document = window.document().expect("should have a document on window");
                let chatbox = document.get_element_by_id("chats").expect("chatbox does not exist");

                let new_elem = document.create_element("div").expect("could not create element");

                let msg_formatted = markdown::to_html(&format!("**[{}]** {}", from, message));

                new_elem.set_inner_html(&msg_formatted);

                chatbox.append_child(&new_elem).unwrap();
            },
            _ => {
                warn!("server sent unexpected packet {:?}, ignoring", msg);


@@ 142,4 200,26 @@ pub async fn update_socket() -> Result<(), JsError> {
    }

    Ok(())
}

#[wasm_bindgen]
pub fn update_status(new_status: &str) {
    let window: Window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let status = document.get_element_by_id("status").expect("statusbox does not exist");
    status.set_inner_html(new_status);
}

#[wasm_bindgen]
pub fn sleep(ms: i32) -> js_sys::Promise {
    js_sys::Promise::new(&mut |resolve, _| {
        web_sys::window()
            .unwrap()
            .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms)
            .unwrap();
    })
}

pub async fn wait_for(promise: js_sys::Promise) -> JsFuture {
    wasm_bindgen_futures::JsFuture::from(promise)
}
\ No newline at end of file

M client/src/macros.rs => client/src/macros.rs +5 -5
@@ 1,8 1,8 @@
use std::error::Error;
use std::io;
use futures::{AsyncRead, AsyncWrite, FutureExt, Stream, StreamExt};
use futures::stream::SplitStream;
use serde::{Deserialize, Serialize};


use futures::{FutureExt, StreamExt};

use serde::{Serialize};
use ws_stream_wasm::WsMessage;

#[macro_export]

M justfile => justfile +4 -1
@@ 1,6 1,9 @@
run_server: build_client_bundle
run_server:
    cargo run --bin server

run_http: build_client_bundle
    cd web && python3 -m http.server

build_client_bundle:
    rm -rf web/dist
    RUST_BACKTRACE=1 wasm-pack build --target web client

M server/src/client_handler.rs => server/src/client_handler.rs +1 -1
@@ 109,7 109,7 @@ pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: 
                        MessageC2S::Chat { message } => {
                            info!("[{}] CHAT: [{}] {}", remote_addr, username, message);

                            for (addr, client_thread) in mgr.handlers.read().await.iter() {
                            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) => {

M server/src/handler.rs => server/src/handler.rs +2 -2
@@ 1,8 1,8 @@
use std::collections::HashMap;
use std::error::Error;

use std::net::SocketAddr;
use std::sync::Arc;
use serde::Serialize;

use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock;
use tungstenite::Message;

M server/src/macros.rs => server/src/macros.rs +6 -6
@@ 1,9 1,9 @@
use std::error::Error;
use std::io;
use futures::{AsyncRead, AsyncWrite, FutureExt, Stream, StreamExt};
use futures::stream::SplitStream;
use serde::{Deserialize, Serialize};
use tokio_tungstenite::WebSocketStream;


use futures::{FutureExt, StreamExt};

use serde::{Serialize};

use tungstenite::Message;

#[macro_export]

M server/src/main.rs => server/src/main.rs +2 -2
@@ 4,7 4,7 @@ use std::sync::Arc;
use hyper::{Body, header, Request, Response, Server, server::conn::AddrStream, StatusCode, upgrade};
use hyper::service::{make_service_fn, service_fn};
use tokio_tungstenite::WebSocketStream;
use tungstenite::{Error, handshake};
use tungstenite::{handshake};
use futures::stream::StreamExt;
use lazy_static::lazy_static;
use log::{error, info, Level};


@@ 151,7 151,7 @@ async fn main() {
    });

    let mgr_timer = cmgr.clone();
    let timer_thread = tokio::spawn(async move {
    let _timer_thread = tokio::spawn(async move {
        timer_main(mgr_timer).await;
    });


M server/src/timer.rs => server/src/timer.rs +4 -4
@@ 1,7 1,7 @@
use std::error::Error;

use std::time::Duration;
use log::{error, trace};
use tokio::sync::mpsc::Receiver;
use log::{error};

use tokio::time::sleep;
use crate::handler::{ClientHandlerMessage, ClientManager};



@@ 9,7 9,7 @@ pub async fn timer_main(mgr: ClientManager) {
    loop {
        sleep(Duration::from_millis(5)).await;

        for (addr, client_thread) in mgr.handlers.read().await.iter() {
        for (_addr, client_thread) in mgr.handlers.read().await.iter() {
            match client_thread.tx.send(ClientHandlerMessage::Tick).await {
                Ok(_) => (),
                Err(e) => {

M web/index.html => web/index.html +1 -1
@@ 6,7 6,7 @@
    </head>

    <body>
        <form target="/play.html" method="GET">
        <form action="/play.html" method="GET">
            <label for="server">Gateway server</label>
            <input type="text" name="server" id="server" value="ws://localhost:3000/ws" required />
            <br>

M web/play.html => web/play.html +3 -2
@@ 11,16 11,17 @@
    <body>
        <div class="chatbox">
            <div id="chats">
                <p>hello: blsdkjf</p>

            </div>
            <input id="chat-value" type="text" placeholder="chat text goes here" />
            <button id="chat-submit">submit</button>
            <p id="status">Loading WASM module...</p>
        </div>

        <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, update_socket } from "./dist/client.js";
            import init, { rust_init, send_chat, update_socket, update_status } from "./dist/client.js";
            init().then(() => {
                const urlSearchParams = new URLSearchParams(window.location.search);