diff options
author | Dennis Kobert <d-kobert@web.de> | 2019-06-11 23:50:39 +0200 |
---|---|---|
committer | Dennis Kobert <d-kobert@web.de> | 2019-06-11 23:50:39 +0200 |
commit | c28c9fafa2c74b101f7ce777aac722dcdeecefc6 (patch) | |
tree | a91f7c7db62c4191cf5788d6c4909c87ac4302c8 | |
parent | aec4f2e64ac4fb44bf14b026ac22b326e7007d02 (diff) | |
parent | 286be2a2fe89927c1a7bb6855b3d001a70dd312d (diff) |
Merge branch 'webhogg'
29 files changed, 1341 insertions, 196 deletions
@@ -244,6 +244,11 @@ FakesAssemblies/ **/*.Server/ModelManifest.xml _Pvt_Extensions +# auxiliary files +/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json +/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json +/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json + # Paket dependency manager .paket/paket.exe paket-files/ @@ -268,7 +273,4 @@ __pycache__/ Cargo.lock # dont save that target (bad boy) -/game_server/target -/DSACore/PropertiesDSALib-Auxiliary-CommandInfo.json -/DSACore/PropertiesDSALib-DSA_Game-Characters-Character.json -/DSACore/PropertiesNewtonsoft-Json-Linq-JProperty.json +**/*/target diff --git a/WebInterface/Web.config b/WebInterface/Web.config deleted file mode 100644 index 741b7d8..0000000 --- a/WebInterface/Web.config +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0"?> -<configuration> - <!-- - Eine Beschreibung der Änderungen von 'web.config' finden Sie unter 'http://go.microsoft.com/fwlink/?LinkId=235367'. - - Die folgenden Attribute können für die <httpRuntime>-Kennung festgelegt werden. - <system.Web> - <httpRuntime targetFramework="4.5" /> - </system.Web> - --> - <system.web> - <compilation debug="false" targetFramework="4.5"/> - <pages controlRenderingCompatibilityVersion="4.0"/> - </system.web> -</configuration>
\ No newline at end of file diff --git a/game_server/Cargo.toml b/game_server/Cargo.toml index 97c0e77..bc14942 100644 --- a/game_server/Cargo.toml +++ b/game_server/Cargo.toml @@ -7,7 +7,8 @@ description = "A general game server for connections to web clients. Currently ( [dependencies] log = "0.4" -pretty_env_logger = "0.3" +fern = "0.5.8" +colored = "1.8" reqwest = "0.9" websocket = "0.22" hyper = "0.10" diff --git a/game_server/build.sh b/game_server/build.sh deleted file mode 100755 index 1eb61a1..0000000 --- a/game_server/build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env sh - -case $1 in - ("") - if rustup run stable cargo --color always build; then - echo build success! - RUST_LOG=debug target/debug/game-server - else - echo build failed! - fi - ;; - -r) - sh build.sh &> err && cat err | tac - ;; - -c) - rustup run stable cargo clean - ;; - *) - echo invalid argument - ;; -esac diff --git a/game_server/err b/game_server/err deleted file mode 100644 index 6ca8a6f..0000000 --- a/game_server/err +++ /dev/null @@ -1,73 +0,0 @@ -[0m[0m[1m[32m Compiling[0m game-server v0.1.0 (/home/jan/projects/DiscoBot/game_server) -[0m[1m[38;5;9merror[E0277][0m[0m[1m: the trait bound `(): futures::future::Future` is not satisfied[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:32:24[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m32[0m[0m [0m[0m[1m[38;5;12m| [0m[0m hyper::rt::run(hyper::rt::lazy(|| {[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^^^^^^^^^^^^^[0m[0m [0m[0m[1m[38;5;9mthe trait `futures::future::Future` is not implemented for `()`[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required by `futures::future::lazy::lazy`[0m - -[0m[1m[38;5;9merror[E0599][0m[0m[1m: no method named `wait` found for type `std::result::Result<futures::future::map_err::MapErr<futures::future::map::Map<hyper::client::ResponseFuture, [closure@src/backend_connection.rs:54:34: 54:68]>, [closure@src/backend_connection.rs:55:38: 55:73]>, http::uri::InvalidUri>` in the current scope[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:56:24[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m56[0m[0m [0m[0m[1m[38;5;12m| [0m[0m }).wait();[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^^[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: the method `wait` exists but the following trait bounds were not satisfied:[0m -[0m `&mut std::result::Result<futures::future::map_err::MapErr<futures::future::map::Map<hyper::client::ResponseFuture, [closure@src/backend_connection.rs:54:34: 54:68]>, [closure@src/backend_connection.rs:55:38: 55:73]>, http::uri::InvalidUri> : futures::future::Future`[0m - -[0m[1m[38;5;9merror[E0308][0m[0m[1m: mismatched types[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:58:17[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m58[0m[0m [0m[0m[1m[38;5;12m| [0m[0m res[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^[0m[0m [0m[0m[1m[38;5;9mexpected (), found enum `std::result::Result`[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: expected type `[0m[0m[1m()[0m[0m`[0m -[0m found type `[0m[0m[1mstd::result::Result<http::response::Response<hyper::body::body::Body>, http::uri::InvalidUri>[0m[0m`[0m - -[0m[1m[38;5;9merror[E0277][0m[0m[1m: the trait bound `(): futures::future::Future` is not satisfied[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:32:24[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m32[0m[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m hyper::rt::run(hyper::rt::lazy(|| {[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m________________________^[0m -[0m[1m[38;5;12m33[0m[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|[0m[0m [0m[0m let client = hyper::Client::builder()[0m -[0m[1m[38;5;12m34[0m[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|[0m[0m [0m[0m .build::<_, hyper::Body>([0m -[0m[1m[38;5;12m35[0m[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|[0m[0m [0m[0m HttpsConnector::new(4).unwrap()[0m -[0m[1m[38;5;12m...[0m[0m [0m[0m[1m[38;5;9m|[0m -[0m[1m[38;5;12m59[0m[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|[0m[0m [0m[0m }[0m -[0m[1m[38;5;12m60[0m[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|[0m[0m [0m[0m }));[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m[1m[38;5;9m|__________^[0m[0m [0m[0m[1m[38;5;9mthe trait `futures::future::Future` is not implemented for `()`[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required by `futures::future::lazy::Lazy`[0m - -[0m[1m[38;5;9merror[E0277][0m[0m[1m: the trait bound `(): futures::future::Future` is not satisfied[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:32:9[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m32[0m[0m [0m[0m[1m[38;5;12m| [0m[0m hyper::rt::run(hyper::rt::lazy(|| {[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^^^^^^^^^^^^[0m[0m [0m[0m[1m[38;5;9mthe trait `futures::future::Future` is not implemented for `()`[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`[0m -[0m [0m[0m[1m[38;5;12m= [0m[0m[1mnote[0m[0m: required by `hyper::rt::run`[0m - -[0m[1m[38;5;9merror[E0063][0m[0m[1m: missing field `res_receiver` in initializer of `backend_connection::BackendConnection`[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:62:9[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m62[0m[0m [0m[0m[1m[38;5;12m| [0m[0m BackendConnection {[0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^^^^^^^^^^^^^^^[0m[0m [0m[0m[1m[38;5;9mmissing `res_receiver`[0m - -[0m[1m[38;5;9merror[E0609][0m[0m[1m: no field `request_sender` on type `&backend_connection::BackendConnection`[0m -[0m [0m[0m[1m[38;5;12m--> [0m[0msrc/backend_connection.rs:69:14[0m -[0m [0m[0m[1m[38;5;12m|[0m -[0m[1m[38;5;12m69[0m[0m [0m[0m[1m[38;5;12m| [0m[0m self.request_sender.send([0m -[0m [0m[0m[1m[38;5;12m| [0m[0m [0m[0m[1m[38;5;9m^^^^^^^^^^^^^^[0m - -[0m[1m[38;5;9merror[0m[0m[1m: aborting due to 7 previous errors[0m - -[0m[1mSome errors have detailed explanations: E0063, E0277, E0308, E0599, E0609.[0m -[0m[1mFor more information about an error, try `rustc --explain E0063`.[0m -[0m[0m[1m[31merror:[0m Could not compile `game-server`. - -To learn more, run the command again with --verbose. -build failed! diff --git a/game_server/rbuild.sh b/game_server/rbuild.sh deleted file mode 100644 index 22b10b5..0000000 --- a/game_server/rbuild.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -sh build.sh &> err && cat err | tac diff --git a/game_server/src/backend_connection.rs b/game_server/src/backend_connection.rs index 9307c4a..a751b30 100644 --- a/game_server/src/backend_connection.rs +++ b/game_server/src/backend_connection.rs @@ -1,31 +1,90 @@ use reqwest::{Response, Client, Url, UrlError, Error as ReqError}; +use std::sync::mpsc::{Sender, Receiver}; +use std::sync::mpsc; +use crate::server::{UserId, Token}; +use crate::group::GroupId; pub struct BackendConnection { host: String, - client: Client, - last_response: Option<Result<Response, ReqError>> + req_sender: Sender<RequestData>, + res_rec: Receiver<ResponseResult>, + max_uid: u32, +} + +#[derive(Debug)] +pub enum BackendError { + UrlError(UrlError), + RequestError(ReqError), + InvalidTokenFormat, + InvalidToken, + BadResponse(Response), +} + +pub type TokenValidity = Result<TokenResponse, BackendError>; +pub type RequestData = Url; +pub type ResponseResult = Result<Response, ReqError>; + +pub struct TokenResponse { + pub group_id: GroupId, + pub group_type: String, + pub group_name: String, + pub user_id: UserId, } impl BackendConnection { + fn run_background(req_rec: Receiver<RequestData>, res_sender: Sender<ResponseResult>) { + let client = Client::new(); + loop { + let request_data = req_rec.recv().unwrap(); + let location = request_data; + let request = client.get(location); + let response = request.send(); + res_sender.send(response).unwrap(); + } + } + pub fn new(host: &str) -> Self { + let (req_sender, req_rec): (Sender<RequestData>, Receiver<RequestData>) + = mpsc::channel(); + let (res_sender, res_rec): (Sender<ResponseResult>, Receiver<ResponseResult>) + = mpsc::channel(); + std::thread::spawn(move || Self::run_background(req_rec, res_sender)); BackendConnection { host: host.to_string(), - client: Client::new(), - last_response: None + req_sender, + res_rec, + max_uid: 420, } } - pub fn request(&mut self, location: &str) -> Result<(), UrlError> { - Ok(self.last_response = - Some(self.client.get(Url::parse(&format!("{}{}", self.host, location))?) - .send())) + pub fn request(&self, location: &str) -> Result<(), UrlError> { + Ok(self.req_sender.send(Url::parse(&format!("{}{}", self.host, location))?).unwrap()) } - pub fn get_response(&self) -> &Option<Result<Response, ReqError>> { - &self.last_response + pub fn get_response(&self) -> ResponseResult { + self.res_rec.recv().unwrap() } - pub fn host_name<'a>(&'a self) -> &'a str { - &self.host + pub fn validate_token(&mut self, token: &Token) -> TokenValidity { + let location = format!("/api/lobby/tokens/{}", token); + self.request(&location).map_err(|err| BackendError::UrlError(err))?; + let response = self.get_response().map_err(|err| BackendError::RequestError(err))?; + if response.status().is_success() { + // zu Testzwecken werden noch keine JSON-Daten deserialisiert + // Dennis Server gibt ja noch nix zurück + self.max_uid += 1; + Ok(TokenResponse { + group_id: 12, + group_type: "scribble".to_string(), + group_name: "Scribble".to_string(), + user_id: self.max_uid - 1, + }) + } else if response.status() == reqwest::StatusCode::NOT_FOUND { + Err(BackendError::InvalidToken) + } else if response.status().is_client_error() { + Err(BackendError::InvalidTokenFormat) + } else { + Err(BackendError::BadResponse(response)) + } } } diff --git a/game_server/src/collide.rs b/game_server/src/collide.rs new file mode 100644 index 0000000..16b5357 --- /dev/null +++ b/game_server/src/collide.rs @@ -0,0 +1,238 @@ +use crate::maths::{Vec2, AABox, RBox}; + +pub trait Collide<Rhs> { + fn collides(&self, other: &Rhs) -> bool; +} + +impl Collide<Vec2> for Vec2 { + fn collides(&self, other: &Self) -> bool { + self == other + } +} + +impl Collide<Vec2> for AABox { + fn collides(&self, other: &Vec2) -> bool { + self.pos < *other && other < &(self.pos + self.size) + } +} + +impl Collide<AABox> for AABox { + fn collides(&self, other: &Self) -> bool { + self.pos.x < other.pos.x + other.size.x && other.pos.x < self.pos.x + self.size.x + && self.pos.y < other.pos.y + other.size.y && other.pos.y < self.pos.y + self.size.y + } +} + +impl Collide<Vec2> for RBox { + fn collides(&self, other: &Vec2) -> bool { + let v1_diff = *other + self.v1 * (-self.v1.scalar(&(*other - self.pos)) / self.v1.distance2()); + let v2_diff = *other + self.v2 * (-self.v2.scalar(&(*other - self.pos)) / self.v2.distance2()); + + let v1_dist = ((v1_diff - self.pos) / self.v2).x; + let v2_dist = ((v2_diff - self.pos) / self.v1).x; + 0.0 <= v1_dist && v2_dist <= 1.0 + && 0.0 <= v2_dist && v2_dist <= 1.0 + //v1_diff < self.pos + self.v2 && self.pos < v1_diff + //&& v2_diff < self.pos + self.v1 && self.pos < v2_diff + } +} + +impl Collide<AABox> for RBox { + fn collides(&self, other: &AABox) -> bool { + let other_size = other.pos + other.size; + + // project points onto a orthogonal line + let v1_diff = other.pos + self.v1 * (-self.v1.scalar(&(other.pos - self.pos)) / self.v1.distance2()); + let v2_diff = other.pos + self.v2 * (-self.v2.scalar(&other.pos) / self.v2.distance2()); + let v1_diff_size = other_size + self.v1 * (-self.v1.scalar(&(other_size - self.pos)) / self.v1.distance2()); + let v2_diff_size = other_size + self.v2 * (-self.v2.scalar(&(other_size - self.pos)) / self.v2.distance2()); + + // calculate the distance + let v1_dist = ((v1_diff - self.pos) / self.v2); + let v2_dist = ((v2_diff - self.pos) / self.v1); + let v1_dist_size = ((v1_diff_size - self.pos) / self.v2); + let v2_dist_size = ((v2_diff_size - self.pos) / self.v1); + + let v1_dist = if v1_dist.x.is_finite() {v1_dist.x} else {v1_dist.y}; + let v2_dist = if v2_dist.x.is_finite() {v2_dist.x} else {v2_dist.y}; + let v1_dist_size = if v1_dist_size.x.is_finite() {v1_dist_size.x} else {v1_dist_size.y}; + let v2_dist_size = if v2_dist_size.x.is_finite() {v2_dist_size.x} else {v2_dist_size.y}; + + let minx = f32::min(self.pos.x, f32::min((self.pos + self.v1).x, (self.pos + self.v2).x)); + let maxx = f32::max(self.pos.x, f32::max((self.pos + self.v1).x, (self.pos + self.v2).x)); + let miny = f32::min(self.pos.y, f32::min((self.pos + self.v1).y, (self.pos + self.v2).y)); + let maxy = f32::max(self.pos.y, f32::max((self.pos + self.v1).y, (self.pos + self.v2).y)); + + 0.0 <= v1_dist_size && v1_dist <= 1.0 + && 0.0 <= v2_dist_size && v2_dist <= 1.0 + && other.pos.x <= maxx && minx <= other.pos.x + other.size.x + && other.pos.y <= maxy && miny <= other.pos.y + other.size.y + } +} + +impl<S, T: Collide<S>> Collide<S> for Vec<T> { + fn collides(&self, other: &S) -> bool { + self.iter().any(|x| x.collides(other)) + } +} + +#[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_collide_dot_dot() { + let a = Vec2{x: 1.0, y: 7.5}; + assert!(a.collides(&a)); + } + + #[test] + fn test_not_collide_dot_dot() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: 5.0, y: 7.5}; + assert!(!a.collides(&b)); + } + + #[test] + fn test_collide_aabox_dot() { + let a = Vec2{x: 1.0, y: 2.5}; + let b = Vec2{x: 3.0, y: 7.5}; + let c = Vec2{x: 1.5, y: 5.0}; + let aa_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&c)); + } + + #[test] + fn test_not_collide_aabox_dot() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: 3.0, y: 2.5}; + let c = Vec2{x: 0.5, y: 5.0}; + let aa_box = AABox{pos: a, size: b}; + + assert!(!(aa_box.collides(&c))); + } + + #[test] + fn test_collide_aabox_aabox_intersecting() { + let a = Vec2{x: 1.0, y: 2.5}; + let b = Vec2{x: 3.0, y: 2.5}; + let aa_box = AABox{pos: a, size: b}; + let a = Vec2{x: 2.0, y: 3.5}; + let b = Vec2{x: 3.0, y: 7.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&bb_box)); + } + + #[test] + fn test_collide_aabox_aabox_crossed() { + let a = Vec2{x: 2.0, y: 0.5}; + let b = Vec2{x: 1.0, y: 7.5}; + let aa_box = AABox{pos: a, size: b}; + let a = Vec2{x: 1.0, y: 3.5}; + let b = Vec2{x: 5.0, y: 4.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&bb_box)); + } + + #[test] + fn test_not_collide_aabox_aabox() { + let a = Vec2{x: 1.0, y: 1.0}; + let b = Vec2{x: 1.0, y: 1.0}; + let aa_box = AABox{pos: a, size: b}; + let a = Vec2{x: 3.0, y: 3.5}; + let b = Vec2{x: 3.0, y: 7.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(!(aa_box.collides(&bb_box))); + } + + #[test] + fn test_collide_Rbox_dot() { + let a = Vec2{x: 1.0, y: 1.0}; + let b = Vec2{x: 1.0, y: 1.0}; + let c = Vec2{x: 1.0, y: -1.0}; + let aa_box = RBox{pos: a, v1: b, v2: c}; + + let c = Vec2{x: 1.6, y: 0.6}; + + assert!(aa_box.collides(&c)); + } + + #[test] + fn test_not_collide_rbox_dot() { + let a = Vec2{x: 1.0, y: 1.0}; + let b = Vec2{x: 1.0, y: 1.0}; + let c = Vec2{x: 1.0, y: -1.0}; + let aa_box = RBox{pos: a, v1: b, v2: c}; + + let c = Vec2{x: 1.4, y: 0.4}; + + assert!(!(aa_box.collides(&c))); + } + + #[test] + fn test_collide_rbox_aabox_intersecting() { + let a = Vec2{x: 1.0, y: 2.5}; + let b = Vec2{x: 0.0, y: 2.5}; + let c = Vec2{x: 3.0, y: 0.5}; + let aa_box = RBox{pos: a, v1: b, v2: c}; + let a = Vec2{x: 2.0, y: 3.5}; + let b = Vec2{x: 3.0, y: 7.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&bb_box)); + } + + #[test] + fn test_collide_rbox_aabox_edges_touch() { + let a = Vec2{x: 4.0, y: 5.5}; + let b = Vec2{x: 1.0, y: 7.5}; + let aa_box = RBox::new(a, b, 3.9); + let a = Vec2{x: 0.0, y: 0.5}; + let b = Vec2{x: 4.0, y: 5.0}; + let bb_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&bb_box)); + } + + #[test] + fn test_collide_rbox_aabox_crossed() { + let a = Vec2{x: 2.0, y: 0.5}; + let b = Vec2{x: 1.0, y: 7.5}; + let aa_box = RBox::new(a, b, 3.9); + let a = Vec2{x: 0.0, y: 4.5}; + let b = Vec2{x: 15.0, y: 1.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(aa_box.collides(&bb_box)); + } + + #[test] + fn test_not_collide_rbox_aabox_next_to() { + let a = Vec2{x: 2.0, y: 0.5}; + let b = Vec2{x: 1.0, y: 7.5}; + let aa_box = RBox::new(a, b, 3.9); + let a = Vec2{x: 5.0, y: 40.5}; + let b = Vec2{x: 15.0, y: 1.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(!aa_box.collides(&bb_box)); + } + + #[test] + fn test_not_collide_rbox_aabox() { + let a = Vec2{x: 1.0, y: 1.0}; + let b = Vec2{x: 0.0, y: 1.0}; + let c = Vec2{x: 1.0, y: 0.0}; + let aa_box = RBox{pos: a, v1: b, v2: c}; + let a = Vec2{x: 3.0, y: 3.5}; + let b = Vec2{x: 3.0, y: 7.5}; + let bb_box = AABox{pos: a, size: b}; + + assert!(!(aa_box.collides(&bb_box))); + } + +} diff --git a/game_server/src/game_logger.rs b/game_server/src/game_logger.rs new file mode 100644 index 0000000..c51bfc5 --- /dev/null +++ b/game_server/src/game_logger.rs @@ -0,0 +1,30 @@ +use colored::*; + +fn color_level(level: log::Level) -> colored::ColoredString { + let text = format!("{: <8}", level); + match level { + log::Level::Error => text.red().bold(), + log::Level::Warn => text.yellow(), + log::Level::Info => text.green(), + log::Level::Debug => text.cyan(), + log::Level::Trace => text.magenta(), + } +} + +pub fn init_logger() { + fern::Dispatch::new().format(|out, message, record|{ + out.finish(format_args!( + "{} {} > {}", + color_level(record.level()), + record.target(), + message + ) + ) + }) + .level(log::LevelFilter::Debug) + .level_for("hyper", log::LevelFilter::Off) + .level_for("tokio_reactor", log::LevelFilter::Off) + .level_for("reqwest", log::LevelFilter::Off) + .chain(std::io::stdout()) + .apply().unwrap(); +} diff --git a/game_server/src/group.rs b/game_server/src/group.rs index 55e4fbf..74a04f7 100644 --- a/game_server/src/group.rs +++ b/game_server/src/group.rs @@ -1,8 +1,13 @@ +use crate::server::{UserId, GameClient, GameServerError}; + pub type GroupId = u32; pub trait Group { fn id(&self) -> GroupId; + fn group_type(&self) -> String; fn name(&self) -> String; - fn run(&self); + fn run(&mut self); + + fn add_client(&mut self, id: UserId, client: GameClient) -> Result<(), GameServerError>; } diff --git a/game_server/src/lobby.rs b/game_server/src/lobby.rs index fe3bdee..6d11a5f 100644 --- a/game_server/src/lobby.rs +++ b/game_server/src/lobby.rs @@ -1,27 +1,55 @@ use std::collections::HashMap; -use super::group::{Group, GroupId}; +use crate::group::{Group, GroupId}; +use crate::scribble_group::ScribbleGroup; + +use crate::server::{UserId, GameClient, GameServerError}; pub struct Lobby { groups: HashMap<GroupId, Box<Group>>, } +#[allow(dead_code)] impl Lobby { - pub fn new() -> Lobby { + pub fn new() -> Self { Self { groups: HashMap::new(), } } + fn generate_group(group_type: &str, id: GroupId, name: &str) -> Option<Box<Group>> { + match group_type { + "scribble" => { + Some(Box::new(ScribbleGroup::new(id, name.to_string()))) + }, + _ => None, + } + } + pub fn add_group(&mut self, group: Box<Group>) { self.groups.insert(group.id(), group); } - pub fn iter<'a>(&'a self) -> GroupIterator<'a> { + pub fn add_client(&mut self, group_type: &str, group_id: GroupId, group_name: &str, + user_id: UserId, client: GameClient) -> Result<(), GameServerError> { + if !self.groups.contains_key(&group_id) { + let mut group = match Self::generate_group(group_type, group_id, group_name) { + Some(x) => x, + _ => return Err(GameServerError::GroupCreationError(format!("failed to generate '{}' group", group_type))), + }; + group.run(); + self.groups.insert(group_id, group); + } + let group = self.groups.get_mut(&group_id).unwrap(); + group.add_client(user_id, client) + } + + pub fn iter<'b>(&'b self) -> GroupIterator<'b> { GroupIterator { groups: self.groups.values() } } } +#[allow(dead_code)] pub struct GroupIterator<'a> { groups: std::collections::hash_map::Values<'a, GroupId, Box<Group>> } diff --git a/game_server/src/main.rs b/game_server/src/main.rs index e129283..cfd9787 100644 --- a/game_server/src/main.rs +++ b/game_server/src/main.rs @@ -1,20 +1,23 @@ mod group; -mod test_group; +mod maths; +mod scribble_group; +mod webhogg_group; +mod webhogg_game; +mod collide; mod lobby; -mod gameserver; +mod server; mod backend_connection; -#[macro_use] extern crate log; -use pretty_env_logger; +mod game_logger; -use backend_connection::BackendConnection; +#[macro_use] extern crate log; fn main() { - pretty_env_logger::init(); + game_logger::init_logger(); - let addr = ("127.0.0.1", 5001); + let addr = ("0.0.0.0", 5001); info!("create game server on {:?}", addr); - let gameserver = gameserver::GameServer::new(addr); + let mut gameserver = server::GameServer::new(addr); gameserver.run().unwrap(); } diff --git a/game_server/src/maths.rs b/game_server/src/maths.rs new file mode 100644 index 0000000..b9303af --- /dev/null +++ b/game_server/src/maths.rs @@ -0,0 +1,370 @@ +#[derive(Clone, Copy, Debug)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} + +impl std::ops::Add for Vec2 { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y + } + } +} + +impl std::ops::AddAssign<Vec2> for Vec2 { + fn add_assign(&mut self, other: Vec2) { + self.x += other.x; + self.y += other.y; + } +} + +impl std::ops::Sub for Vec2 { + type Output = Self; + fn sub(self, other: Self) -> Self { + Self { + x: self.x - other.x, + y: self.y - other.y + } + } +} + +impl std::ops::SubAssign<Vec2> for Vec2 { + fn sub_assign(&mut self, other: Vec2) { + self.x -= other.x; + self.y -= other.y; + } +} + +impl std::ops::Neg for Vec2 { + type Output = Self; + fn neg(self) -> Self { + Self { + x: -self.x, + y: -self.y + } + } +} + +impl std::ops::Mul<f32> for Vec2 { + type Output = Self; + fn mul(self, scale: f32) -> Self { + Self { + x: self.x * scale, + y: self.y * scale + } + } +} + +impl std::ops::Div<f32> for Vec2 { + type Output = Self; + fn div(self, scale: f32) -> Self { + Self { + x: self.x / scale, + y: self.y / scale + } + } +} + +impl std::ops::Div<Vec2> for Vec2 { + type Output = Self; + fn div(self, scale: Vec2) -> Self { + Self { + x: self.x / scale.x, + y: self.y / scale.y + } + } +} + + +impl std::cmp::PartialOrd for Vec2 { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + if self.x <= other.x && self.y <= other.y { + Some(std::cmp::Ordering::Less) + } else if self.x > other.x && self.y > other.y { + Some(std::cmp::Ordering::Greater) + } else { + None + } + } +} + +impl std::cmp::PartialEq for Vec2 { + fn eq(&self, other: &Self) -> bool { + f32::abs(self.x - other.x) < 1e-8 + && f32::abs(self.y - other.y) < 1e-8 + } +} + +impl std::cmp::Eq for Vec2 {} + +impl Vec2 { + pub fn distance(&self) -> f32 { + f32::sqrt(self.distance2()) + } + + pub fn distance2(&self) -> f32 { + self.scalar(self) + } + + pub fn scalar(&self, other: &Vec2) -> f32 { + self.x * other.x + self.y * other.y + } + + pub fn norm(&self) -> Vec2 { + let len = self.distance(); + Vec2 { + x: self.x / len, + y: self.y / len, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct AABox { + pub pos: Vec2, + /// the size may not be smaller than zero + pub size: Vec2, +} + +impl std::ops::Add<Vec2> for AABox { + type Output = Self; + fn add(self, other: Vec2) -> Self { + Self { + pos: self.pos + other, + size: self.size, + } + } +} + +impl std::ops::AddAssign<Vec2> for AABox { + fn add_assign(&mut self, other: Vec2) { + self.pos += other + } +} + +impl std::ops::Sub<Vec2> for AABox { + type Output = Self; + fn sub(self, other: Vec2) -> Self { + Self { + pos: self.pos + other, + size: self.size + } + } +} + +impl std::ops::SubAssign<Vec2> for AABox { + fn sub_assign(&mut self, other: Vec2) { + self.pos -= other + } +} + +impl std::cmp::PartialEq for AABox { + fn eq(&self, other: &Self) -> bool { + self.pos == other.pos + && self.size == other.size + } +} + +impl std::cmp::Eq for AABox {} + +#[derive(Clone, Copy, Debug)] +pub struct RBox { + /// origin + pub pos: Vec2, + /// Vwctor1 + pub v1: Vec2, + /// Vector2 + pub v2: Vec2, +} + +impl RBox { + pub fn new(pos: Vec2, orientation: Vec2, width: f32) -> Self { + let scale = width / orientation.distance(); + let orth = Vec2 {x: orientation.x / scale, y: -orientation.y / scale}; + Self { + pos: pos, + v1: orientation, + v2: orth, + } + } +} + +impl std::ops::Add<Vec2> for RBox { + type Output = Self; + fn add(self, other: Vec2) -> Self { + Self { + pos: self.pos + other, + v1: self.v1, + v2: self.v2, + } + } +} + +impl std::ops::AddAssign<Vec2> for RBox { + fn add_assign(&mut self, other: Vec2) { + self.pos += other + } +} + +impl std::ops::Sub<Vec2> for RBox { + type Output = Self; + fn sub(self, other: Vec2) -> Self { + Self { + pos: self.pos + other, + v1: self.v1 + other, + v2: self.v2, + } + } +} + +impl std::ops::SubAssign<Vec2> for RBox { + fn sub_assign(&mut self, other: Vec2) { + self.pos -= other + } +} + +impl std::cmp::PartialEq for RBox { + fn eq(&self, other: &Self) -> bool { + self.pos == other.pos + && self.v1 == other.v1 + && self.v1 == self.v2 + } +} + +impl std::cmp::Eq for RBox {} + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + #[test] + fn test_less_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + + assert!(b < a); + } + + #[test] + fn test_less_vec2_fail() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: 3.0, y: 2.5}; + + assert!(!(a < b)); + } + + #[test] + fn test_greater_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + + assert!(a > b); + } + + #[test] + fn test_greater_vec2_fail() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: 3.0, y: 2.5}; + + assert!(!(a > b)); + } + + + #[test] + fn test_add_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let c = Vec2{x: -2.0, y: 10.0}; + + assert_eq!(a + b, c); + } + + #[test] + fn test_neg_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -1.0, y: -7.5}; + + assert_eq!(-a, b); + } + + #[test] + fn test_sub_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let c = Vec2{x: 4.0, y: 5.0}; + + assert_eq!(a - b, c); + } + + #[test] + fn test_distance_vec2() { + let a = Vec2{x: 2.0, y: 2.0}; + + assert!(f32::abs(a.distance() - 2.0) < 1e8); + } + + #[test] + fn test_distance2_vec2() { + let a = Vec2{x: 1.0, y: 2.0}; + + assert!(f32::abs(a.distance2() - 5.0) < 1e8); + } + + #[test] + fn test_norm_vec2() { + let a = Vec2{x: 2.0, y: -2.0}; + let b = Vec2{x: std::f32::consts::FRAC_1_SQRT_2, y: -std::f32::consts::FRAC_1_SQRT_2}; + + assert_eq!(a.norm(), b); + } + + #[test] + fn test_add_aabox_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let mut aa_box = AABox{pos: a, size: b}; + let bb_box = AABox{pos: a + b,size: b}; + aa_box += b; + + assert_eq!(aa_box, bb_box); + } + + #[test] + fn test_sub_aabox_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let mut aa_box = AABox{pos: a, size: b}; + let bb_box = AABox{pos: a - b,size: b}; + aa_box -= b; + + assert_eq!(aa_box, bb_box); + } + + #[test] + fn test_add_rbox_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let c = Vec2{x: -3.0, y: 2.5}; + let mut aa_box = RBox{pos: a, v1: b, v2: c}; + let bb_box = RBox{pos: a + b, v1: b, v2: c}; + aa_box += b; + + assert_eq!(aa_box, bb_box); + } + + #[test] + fn test_sub_rbox_vec2() { + let a = Vec2{x: 1.0, y: 7.5}; + let b = Vec2{x: -3.0, y: 2.5}; + let c = Vec2{x: -3.0, y: 2.5}; + let mut aa_box = RBox{pos: a, v1: b, v2: c}; + let bb_box = RBox{pos: a - b, v1: b, v2: c}; + aa_box -= b; + + assert_eq!(aa_box, bb_box); + } +} diff --git a/game_server/src/scribble_group.rs b/game_server/src/scribble_group.rs new file mode 100644 index 0000000..8980e7b --- /dev/null +++ b/game_server/src/scribble_group.rs @@ -0,0 +1,65 @@ +use crate::group::{Group, GroupId}; +use crate::server::{UserId, GameClient, + ClientSender, ClientReceiver, + GameServerError}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +pub struct ScribbleGroup { + id: GroupId, + name: String, + senders: Arc<Mutex<HashMap<UserId, ClientSender>>> +} + +impl Group for ScribbleGroup { + fn id(&self) -> GroupId { + self.id + } + + fn group_type(&self) -> String { + "scribble".to_string() + } + + fn name(&self) -> String { + self.name.clone() + } + + fn run(&mut self) { + info!("a new group {}:'{}' runs now", self.id, self.name); + } + + fn add_client(&mut self, id: UserId, client: GameClient) -> Result<(), GameServerError> { + debug!("user {} joined the group {}:'{}'", id, self.id, self.name); + let (sen, rec) = client.split(); + self.senders.lock().unwrap().insert(id, sen); + let senders_mutex = self.senders.clone(); + let self_uid = id; + std::thread::spawn(move || Self::broadcast_clients(self_uid, rec, senders_mutex)); + Ok(()) + } +} + +impl ScribbleGroup { + pub fn new(id: GroupId, name: String) -> Self { + Self { id, name, senders: Arc::new(Mutex::new(HashMap::new())) } + } + + fn broadcast_clients(self_uid: UserId, mut rec: ClientReceiver, senders_mutex: Arc<Mutex<HashMap<UserId, ClientSender>>>) { + loop { + let message = match rec.recv_message() { + Ok(x) => x, + _ => break + }; + //trace!("got message: '{:?}'", message); + let mut senders = senders_mutex.lock().unwrap(); + for (uid, sender) in senders.iter_mut() { + if self_uid != *uid { + sender.send_message(&message) + .unwrap_or_else(|_| debug!("tried to send message to {}, but failed", *uid)); + } + } + } + senders_mutex.lock().unwrap().remove(&self_uid); + info!("client {} has left", self_uid); + } +} diff --git a/game_server/src/gameserver.rs b/game_server/src/server.rs index 9334a27..5b1a7a9 100644 --- a/game_server/src/gameserver.rs +++ b/game_server/src/server.rs @@ -1,24 +1,45 @@ use websocket::{OwnedMessage, + stream::sync::Splittable, sync::Server, client::sync::Client, - server::{NoTlsAcceptor, InvalidConnection, - sync::AcceptResult}}; + server::{NoTlsAcceptor, + sync::AcceptResult}, + receiver, sender}; use std::net::{SocketAddr, ToSocketAddrs, TcpStream}; -use std::sync::mpsc; -use std::sync::mpsc::{Sender, Receiver}; -use super::lobby::Lobby; -use super::backend_connection::BackendConnection; +use std::sync::{mpsc, + mpsc::{Sender, Receiver}}; +use crate::lobby::Lobby; +use crate::backend_connection::BackendConnection; + +pub type ClientReceiver = receiver::Reader<<TcpStream as Splittable>::Reader>; +pub type ClientSender = sender::Writer<<TcpStream as Splittable>::Writer>; const PROTOCOL: &str = "tuesday"; -type Token = u32; +pub type Token = u32; +pub type UserId = u32; #[derive(Debug)] pub enum GameServerError { BindError(std::io::Error), HandshakeRequestError, InvalidProtocolError, - AcceptError(std::io::Error) + AcceptError(std::io::Error), + GroupError(String), + GroupCreationError(String), +} + +impl std::fmt::Display for GameServerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + GameServerError::BindError(e) => write!(f, "BindError: {}", e), + GameServerError::HandshakeRequestError => write!(f, "HandshakeRequestError"), + GameServerError::InvalidProtocolError => write!(f, "InvalidProtocolError"), + GameServerError::AcceptError(e) => write!(f, "AcceptError: {}", e), + GameServerError::GroupError(e) => write!(f, "GroupError: {}", e), + GameServerError::GroupCreationError(e) => write!(f, "GroupCreationError: {}", e), + } + } } pub struct GameServer { @@ -47,11 +68,20 @@ impl GameClient { .recv_message() .ok()?; if let OwnedMessage::Text(text) = message { - text.parse::<Token>().ok() + text.parse().ok() } else { None } } + + fn host_name(&self) -> SocketAddr { + self.addr + } + + pub fn split(self) -> (ClientSender, ClientReceiver) { + let (rec, sen) = self.client.split().unwrap(); + (sen, rec) + } } type ClientConnection = Result<GameClient, GameServerError>; @@ -66,38 +96,59 @@ impl GameServer { info!("got a C# backend connection"); GameServer { addr, - lobby, - backend, + lobby: lobby, + backend: backend, } } - pub fn run(&self) -> Result<(), GameServerError> { + pub fn run(&mut self) -> Result<(), GameServerError> { let reader = self.read_clients(); loop { - let mut connection = reader.recv().unwrap()?; + let connection = reader.recv().unwrap()?; self.add_client(connection); } - Ok(()) } - fn add_client(&self, mut client: GameClient) { - std::thread::spawn(move || { - println!("Token: {:?}", client.require_token()); - loop { std::thread::sleep(std::time::Duration::from_millis(100)); } - }); + fn add_client(&mut self, mut client: GameClient) { + let token = client.require_token(); + if let Some(token) = token { + let result = self.backend.validate_token(&token); + match result { + Err(err) => warn!("client's token {} is not valid: '{:?}'", + token, err), + Ok(result) => { + debug!("client validation was successfull"); + let user_id = result.user_id; + let group_id = result.group_id; + let group_type = result.group_type; + let group_name = result.group_name; + debug!("add client: (id:{}, token:{}, host:{}) to \"{}\"", + user_id, token, client.host_name(), group_name); + //clients.lock().unwrap().insert(token, client); + self.lobby.add_client(&group_type, group_id, + &group_name, user_id, client) + .unwrap_or_else(|e| warn!("failed to add client: {}", e)); + } + } + } else { + warn!("client sent invalid token"); + } } fn read_clients(&self) -> Receiver<ClientConnection> { - let (s, r): (Sender<ClientConnection>, Receiver<ClientConnection>) + let (sen, rec): (Sender<ClientConnection>, Receiver<ClientConnection>) = mpsc::channel(); let addr = self.addr; std::thread::spawn(move || { - let result = Self::handle_requests(addr, &s).or_else(|e| s.send(Err(e))); + match Self::handle_requests(addr, &sen) { + Err(e) => sen.send(Err(e)).unwrap(), + _ => (), + } }); - r + rec } - fn handle_requests(addr: SocketAddr, s: &Sender<ClientConnection>) -> Result<(), GameServerError> { + fn handle_requests(addr: SocketAddr, sen: &Sender<ClientConnection>) -> Result<(), GameServerError> { let server = match Server::<NoTlsAcceptor>::bind(addr) { Ok(v) => v, Err(e) => { @@ -107,7 +158,7 @@ impl GameServer { }; info!("webserver is being launched"); for req in server { - s.send(Ok(Self::handle_request(req)?)).unwrap(); + sen.send(Ok(Self::handle_request(req)?)).unwrap(); } info!("webserver is being shut down"); Ok(()) @@ -138,7 +189,7 @@ impl GameServer { } } }, - Err(e) => { + Err(_) => { warn!("invalid client request"); Err(GameServerError::HandshakeRequestError) } diff --git a/game_server/src/test_group.rs b/game_server/src/test_group.rs deleted file mode 100644 index bd570e3..0000000 --- a/game_server/src/test_group.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::group::{Group, GroupId}; - -pub struct TestGroup { - id: GroupId, - name: String, -} - -impl Group for TestGroup { - fn id(&self) -> GroupId { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } - - fn run(&self) { - let id = self.id; - let name = self.name.to_owned(); - std::thread::spawn(move || /*loop { println!("> group nr.{} wishes you: '{}'", id, name) }*/()); - } -} - -impl TestGroup { - pub fn new(id: GroupId, name: String) -> Self { - TestGroup { id, name } - } -} diff --git a/game_server/src/webhogg_game.rs b/game_server/src/webhogg_game.rs new file mode 100644 index 0000000..7b94fcb --- /dev/null +++ b/game_server/src/webhogg_game.rs @@ -0,0 +1,13 @@ +use crate::maths::Vec2; + +pub struct WebhoggPlayer { + pos: Vec2, +} + +pub struct WebhoggGame { + player1: WebhoggPlayer, + player2: WebhoggPlayer, +} + +impl WebhoggGame { +} diff --git a/game_server/src/webhogg_group.rs b/game_server/src/webhogg_group.rs new file mode 100644 index 0000000..091f7f8 --- /dev/null +++ b/game_server/src/webhogg_group.rs @@ -0,0 +1,71 @@ +use crate::group::{Group, GroupId}; +use crate::server::{UserId, GameClient, + ClientSender, ClientReceiver, + GameServerError}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +pub struct WebhoggGroup { + id: GroupId, + name: String, + senders: Arc<Mutex<HashMap<UserId, ClientSender>>> +} + +impl Group for WebhoggGroup { + fn id(&self) -> GroupId { + self.id + } + + fn group_type(&self) -> String { + "webhogg".to_string() + } + + fn name(&self) -> String { + self.name.clone() + } + + fn run(&mut self) { + info!("a new group {}:'{}' runs now", self.id, self.name); + } + + fn add_client(&mut self, id: UserId, client: GameClient) -> Result<(), GameServerError> { + if self.senders.lock().unwrap().len() > 1 { + return Err(GameServerError::GroupError( + format!("user {} was not able to join the {} group, {}", + "because the client limit has been exceeded", + id, self.name))); + } + debug!("user {} joined the group {}:'{}'", id, self.id, self.name); + let (sen, rec) = client.split(); + self.senders.lock().unwrap().insert(id, sen); + let senders_mutex = self.senders.clone(); + let self_uid = id; + std::thread::spawn(move || Self::broadcast_clients(self_uid, rec, senders_mutex)); + Ok(()) + } +} + +impl WebhoggGroup { + pub fn new(id: GroupId, name: String) -> Self { + Self { id, name, senders: Arc::new(Mutex::new(HashMap::new())) } + } + + fn broadcast_clients(self_uid: UserId, mut rec: ClientReceiver, senders_mutex: Arc<Mutex<HashMap<UserId, ClientSender>>>) { + loop { + let message = match rec.recv_message() { + Ok(x) => x, + _ => break + }; + //trace!("got message: '{:?}'", message); + let mut senders = senders_mutex.lock().unwrap(); + for (uid, sender) in senders.iter_mut() { + if self_uid != *uid { + sender.send_message(&message) + .unwrap_or_else(|_| debug!("tried to send message to {}, but failed", *uid)); + } + } + } + senders_mutex.lock().unwrap().remove(&self_uid); + info!("client {} has left", self_uid); + } +} diff --git a/game_server/src/webhogg_player.rs b/game_server/src/webhogg_player.rs new file mode 100644 index 0000000..38b9596 --- /dev/null +++ b/game_server/src/webhogg_player.rs @@ -0,0 +1,3 @@ +pub struct WebhoggPlayer { + +} diff --git a/game_server/src/ws_test.html b/game_server/src/ws_test.html index ea259b7..3b3d4ce 100644 --- a/game_server/src/ws_test.html +++ b/game_server/src/ws_test.html @@ -12,6 +12,7 @@ <div id='cons'>connected</div><br> <button onclick='test_connection()'>Launch</button><br> <span>Server address: </span><input id='addr'></input> + <div align='right'><span>Message</span><input id='msg'></input> <button onclick='send_text()'>Send</button></div> <div id='chat' style='background: rgb(20, 20, 20); padding-left: 20px; margin: 40px' /> </body> <script> @@ -22,11 +23,12 @@ function get_addr() { function test_connection() { let a = 'ws://' + get_addr(); add_text('create a new connection at "' + a + '"'); - const ws = new WebSocket(a, 'tuesday'); + ws = new WebSocket(a, 'tuesday'); ws.addEventListener('open', function (event) { add_text('connection established'); toggle_connected(true); - ws.send('1230123'); + // send token + ws.send('42'); }); ws.addEventListener('error', function (event) { add_text('ws error occured: "' + event + '"'); @@ -37,10 +39,16 @@ function test_connection() { toggle_connected(false); }); ws.addEventListener('message', function (event) { - add_text('got ws message: ' + event.data); + add_text('got ws message: "' + event.data + '"'); }); } +function send_text() { + let msg = document.getElementById('msg').value; + ws.send(msg); + add_text('sent message: "' + msg + '"'); +} + function add_text(text, color='white') { let c = document.getElementById('chat'); let n = document.createElement('span'); diff --git a/scribble/index.html b/scribble/index.html new file mode 100644 index 0000000..ee7d052 --- /dev/null +++ b/scribble/index.html @@ -0,0 +1,15 @@ +<!doctype html> +<html> + <header> + <title> Scribblio </title> + <script src="./script.js"></script> + <link rel="stylesheet" type="text/css" href="mainstyle.css"> + </header> + <body> + <div class="wrapper"> + <div class="toolbar"> + </div><br> + <canvas id="my-canvas" width="1000" height="1000"></canvas> + </div> + </body> +</html> diff --git a/scribble/mainstyle.css b/scribble/mainstyle.css new file mode 100644 index 0000000..82045d1 --- /dev/null +++ b/scribble/mainstyle.css @@ -0,0 +1,53 @@ +body { + padding: 0,2; + margin: 0; + background: #222222; +} +.wrapper { + max-width: 900px; + margin: auto; + font-family: "Arial"; +} + +.toolbar{ + width: 100%; + background-color: #440044; + overflow: auto; +} + +.toolbar a { + float: left; + width: 11%; + text-align: center; + padding: 6px 5px; + transition: all 0.5s ease; + color: white; +} + +/* Change color on hover */ +.toolbar a:hover { + background-color: #000; + } + +/* Change color on selected icon */ +.selected { + background-color: #000; +} + +#my-canvas{ + width: 100%; + background: white; + border: 3px solid #000000; +} + +#img-data-div{ + width: 100%; + max-width: 900px; + height: 200px; +} + +/* Resize image to container */ +.toolbar a img{ + max-width:100%; + height:auto; +} diff --git a/scribble/script.js b/scribble/script.js new file mode 100644 index 0000000..75173ae --- /dev/null +++ b/scribble/script.js @@ -0,0 +1,215 @@ +const socket = new WebSocket("ws://localhost:5001", "tuesday"); + +//var exampleSocket = new WebSocket("ws://192.168.3.42:5001", "tuesday"); +socket.onopen = function (event) { + console.log("Connection established"); + // Display user friendly messages for the successful establishment of connection + socket.send("42"); +}; +socket.onmessage = function (event) { + console.log(event.data); + AddBrushPoint(JSON.parse(event.data)); + DrawBrush(); + SaveCanvasImage(); + RedrawCanvasImage(); +}; +window.onbeforeunload = function () { + socket.onclose = function () { }; // disable onclose handler first + console.log("Connection terminated"); + socket.close(); +}; + +let canvas; +let context; +let savedImageData; +let dragging = false; +let strokeColor = 'black'; +let fillColor = 'black'; +let color = 'black'; +let lineWidth = 2; +let currentTool = "brush"; +let canvasHeight = 1000; +let canvasWidth = 1000; + +let usingBrush = false; +let brushPoints = new Array(); + +class ShapeBoundingBox { + constructor(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; + } +} + +class DrawPoint { + constructor(x, y, mouseDown) { + this.x = x; + this.y = y; + this.mouseDown = mouseDown; + } +} + +class MouseDownPos { + constructor(x, y) { + this.x = x; + this.y = y; + } +} + +class Location { + constructor(x, y) { + this.x = x; + this.y = y; + } +} + + +let shapeBoundingBox = new ShapeBoundingBox(0, 0, 0, 0); +let mousedown = new MouseDownPos(0, 0); +let loc = new Location(0, 0); + +document.addEventListener('DOMContentLoaded', setupCanvas); + +function setupCanvas() { + canvas = document.getElementById('my-canvas'); + context = canvas.getContext('2d'); + context.strokeStyles = strokeColor; + context.lineWidth = lineWidth; + canvas.addEventListener("mousedown", ReactToMouseDown); + canvas.addEventListener("mousemove", ReactToMouseMove); + canvas.addEventListener("mouseup", ReactToMouseUp); +} + + +function GetMousePosition(x, y) { + let canvasSizeData = canvas.getBoundingClientRect(); + return { + x: (x - canvasSizeData.left) * (canvas.width / canvasSizeData.width), + y: (y - canvasSizeData.top) * (canvas.height / canvasSizeData.height) + }; +} + +function SaveCanvasImage() { + savedImageData = context.getImageData(0, 0, canvas.width, canvas.height); +} + +function RedrawCanvasImage() { + context.putImageData(savedImageData, 0, 0); +} + +function UpdateRubberbandSizeData(location) { + shapeBoundingBox.width = Math.abs(location.x - mousedown.x); + shapeBoundingBox.height = Math.abs(location.y - mousedown.y); + + if (location.x > mousedown.x) { + shapeBoundingBox.left = mousedown.x; + } else { + shapeBoundingBox.left = location.x; + } + if (location.y > mousedown.y) { + shapeBoundingBox.top = mousedown.y; + } else { + shapeBoundingBox.top = location.y; + } +} + +function AddNetBrushPoint(x, y, mouseDown) { + let point = new DrawPoint(x, y, mouseDown); + socket.send(JSON.stringify(point)); + + AddBrushPoint(point); +} + +function AddBrushPoint(point) { + brushPoints.push(point); +} + +function DrawBrush() { + for (let i = 1; i < brushPoints.length; i++) { + context.beginPath(); + if (brushPoints[i].mouseDown) { + context.moveTo(brushPoints[i - 1].x, brushPoints[i - 1].y); + } else { + context.moveTo(brushPoints[i].x - 1, brushPoints[i].y); + } + context.lineTo(brushPoints[i].x, brushPoints[i].y) + context.closePath(); + context.stroke(); + } +} + +function UpdateRubberbandOnMove(location) { + UpdateRubberbandSizeData(location); + drawRubberbandShape(location); +} + +function drawRubberbandShape(location) { + context.strokeStyle = strokeColor; + context.fillStyle = fillColor; + + if (currentTool === "brush") { + DrawBrush(); + } else if (currentTool === "line") { + context.beginPath(); + context.moveTo(mousedown.x, mousedown.y); + context.lineTo(location.x, location.y); + context.closePath(); + context.stroke(); + } else if (currentTool === "rectangle") { + context.strokeRect(shapeBoundingBox.left, shapeBoundingBox.top, + shapeBoundingBox.width, shapeBoundingBox.height); + } +} + +function ReactToMouseDown(e) { + // Change the mouse pointer to a crosshair + canvas.style.cursor = "crosshair"; + // Store location + loc = GetMousePosition(e.clientX, e.clientY); + // Save the current canvas image + SaveCanvasImage(); + // Store mouse position when clicked + mousedown.x = loc.x; + mousedown.y = loc.y; + // Store that yes the mouse is being held down + dragging = true; + + if (currentTool === "brush") { + usingBrush = true; + AddNetBrushPoint(mousedown.x, mousedown.y); + } +}; + +function ReactToMouseMove(e) { + canvas.style.cursor = "crosshair"; + loc = GetMousePosition(e.clientX, e.clientY); + + if (currentTool === "brush" && dragging && usingBrush) { + if (loc.x > 0 && loc.x < canvasWidth && loc.y > 0 && loc.y < canvasHeight) { + AddNetBrushPoint(loc.x, loc.y, true); + } + RedrawCanvasImage(); + DrawBrush(); + } else if (dragging) { + RedrawCanvasImage(); + UpdateRubberbandOnMove(loc); + } +}; + +function ReactToMouseUp(e) { + canvas.style.cursor = "default"; + loc = GetMousePosition(e.clientX, e.clientY); + RedrawCanvasImage(); + UpdateRubberbandOnMove(loc); + dragging = false; + usingBrush = false; + + brushXPoints = new Array(); + brushYPoints = new Array(); + brushDownPos = new Array(); + if (currentTool === "brush") { + AddBrushPoint(loc.x, loc.y); + } +} diff --git a/webhogg/wasm/Cargo.toml b/webhogg/wasm/Cargo.toml new file mode 100644 index 0000000..7978928 --- /dev/null +++ b/webhogg/wasm/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "webhogg-wasm" +version = "0.1.0" +authors = [ + "natrixaeria", + "TrueDoctor <dennis@kobert.dev>" +] +edition = "2018" +description = "WebAssembly frontend for the webhogg project" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true + +[dependencies] +wasm-bindgen = "0.2" diff --git a/webhogg/wasm/build b/webhogg/wasm/build new file mode 100755 index 0000000..e14f004 --- /dev/null +++ b/webhogg/wasm/build @@ -0,0 +1,7 @@ +#!/bin/bash + +cargo build --target wasm32-unknown-unknown --release +wasm-bindgen target/wasm32-unknown-unknown/release/webhogg_wasm.wasm --out-dir bin/ --no-typescript --target no-modules --remove-producers-section --remove-name-section +wasm-opt -Oz bin/webhogg_wasm_bg.wasm -o bin/webhogg-wasm.wasm +rm bin/webhogg_wasm_bg.wasm +mv bin/webhogg_wasm.js bin/webhogg-wasm.js diff --git a/webhogg/wasm/index.html b/webhogg/wasm/index.html new file mode 100644 index 0000000..eae1cc2 --- /dev/null +++ b/webhogg/wasm/index.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> + <head> + <meta charset='utf-8'/> + <title>webhogg</title> + </head> + <body> + <canvas id='c'>your browser is incompetent</canvas> + <script src='pkg/main.js'></script> + </body> +</html> diff --git a/webhogg/wasm/pkg/main.js b/webhogg/wasm/pkg/main.js new file mode 100644 index 0000000..eec740d --- /dev/null +++ b/webhogg/wasm/pkg/main.js @@ -0,0 +1,9 @@ +async function main() { + let fetchingSource = fetch('bin/webhogg-wasm.wasm'); + let fetchedSource = await fetchingSource; + let source = await fetchedSource.text(); + //alert(source) + let workerGraphics = new Worker('pkg/worker-graphics.js'); +} + +main(); diff --git a/webhogg/wasm/pkg/worker-graphics.js b/webhogg/wasm/pkg/worker-graphics.js new file mode 100644 index 0000000..8360014 --- /dev/null +++ b/webhogg/wasm/pkg/worker-graphics.js @@ -0,0 +1 @@ +console.log('lelel'); diff --git a/webhogg/wasm/src/lib.rs b/webhogg/wasm/src/lib.rs new file mode 100644 index 0000000..cb334fb --- /dev/null +++ b/webhogg/wasm/src/lib.rs @@ -0,0 +1,8 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn enrty() { +} + +fn main() { +} |