summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Kobert <d-kobert@web.de>2019-06-11 23:50:39 +0200
committerDennis Kobert <d-kobert@web.de>2019-06-11 23:50:39 +0200
commitc28c9fafa2c74b101f7ce777aac722dcdeecefc6 (patch)
treea91f7c7db62c4191cf5788d6c4909c87ac4302c8
parentaec4f2e64ac4fb44bf14b026ac22b326e7007d02 (diff)
parent286be2a2fe89927c1a7bb6855b3d001a70dd312d (diff)
Merge branch 'webhogg'
-rw-r--r--.gitignore10
-rw-r--r--WebInterface/Web.config15
-rw-r--r--game_server/Cargo.toml3
-rwxr-xr-xgame_server/build.sh21
-rw-r--r--game_server/err73
-rw-r--r--game_server/rbuild.sh2
-rw-r--r--game_server/src/backend_connection.rs83
-rw-r--r--game_server/src/collide.rs238
-rw-r--r--game_server/src/game_logger.rs30
-rw-r--r--game_server/src/group.rs7
-rw-r--r--game_server/src/lobby.rs34
-rw-r--r--game_server/src/main.rs19
-rw-r--r--game_server/src/maths.rs370
-rw-r--r--game_server/src/scribble_group.rs65
-rw-r--r--game_server/src/server.rs (renamed from game_server/src/gameserver.rs)101
-rw-r--r--game_server/src/test_group.rs28
-rw-r--r--game_server/src/webhogg_game.rs13
-rw-r--r--game_server/src/webhogg_group.rs71
-rw-r--r--game_server/src/webhogg_player.rs3
-rw-r--r--game_server/src/ws_test.html14
-rw-r--r--scribble/index.html15
-rw-r--r--scribble/mainstyle.css53
-rw-r--r--scribble/script.js215
-rw-r--r--webhogg/wasm/Cargo.toml18
-rwxr-xr-xwebhogg/wasm/build7
-rw-r--r--webhogg/wasm/index.html11
-rw-r--r--webhogg/wasm/pkg/main.js9
-rw-r--r--webhogg/wasm/pkg/worker-graphics.js1
-rw-r--r--webhogg/wasm/src/lib.rs8
29 files changed, 1341 insertions, 196 deletions
diff --git a/.gitignore b/.gitignore
index fec9b91..6ff3a49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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 @@
- Compiling game-server v0.1.0 (/home/jan/projects/DiscoBot/game_server)
-error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
- --> src/backend_connection.rs:32:24
- |
-32 |  hyper::rt::run(hyper::rt::lazy(|| {
- |  ^^^^^^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `()`
- |
- = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
- = note: required by `futures::future::lazy::lazy`
-
-error[E0599]: 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
- --> src/backend_connection.rs:56:24
- |
-56 |  }).wait();
- |  ^^^^
- |
- = note: the method `wait` exists but the following trait bounds were not satisfied:
- `&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`
-
-error[E0308]: mismatched types
- --> src/backend_connection.rs:58:17
- |
-58 |  res
- |  ^^^ expected (), found enum `std::result::Result`
- |
- = note: expected type `()`
- found type `std::result::Result<http::response::Response<hyper::body::body::Body>, http::uri::InvalidUri>`
-
-error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
- --> src/backend_connection.rs:32:24
- |
-32 |   hyper::rt::run(hyper::rt::lazy(|| {
- |  ________________________^
-33 | |  let client = hyper::Client::builder()
-34 | |  .build::<_, hyper::Body>(
-35 | |  HttpsConnector::new(4).unwrap()
-... |
-59 | |  }
-60 | |  }));
- | |__________^ the trait `futures::future::Future` is not implemented for `()`
- |
- = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
- = note: required by `futures::future::lazy::Lazy`
-
-error[E0277]: the trait bound `(): futures::future::Future` is not satisfied
- --> src/backend_connection.rs:32:9
- |
-32 |  hyper::rt::run(hyper::rt::lazy(|| {
- |  ^^^^^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `()`
- |
- = note: required because of the requirements on the impl of `futures::future::IntoFuture` for `()`
- = note: required by `hyper::rt::run`
-
-error[E0063]: missing field `res_receiver` in initializer of `backend_connection::BackendConnection`
- --> src/backend_connection.rs:62:9
- |
-62 |  BackendConnection {
- |  ^^^^^^^^^^^^^^^^^ missing `res_receiver`
-
-error[E0609]: no field `request_sender` on type `&backend_connection::BackendConnection`
- --> src/backend_connection.rs:69:14
- |
-69 |  self.request_sender.send(
- |  ^^^^^^^^^^^^^^
-
-error: aborting due to 7 previous errors
-
-Some errors have detailed explanations: E0063, E0277, E0308, E0599, E0609.
-For more information about an error, try `rustc --explain E0063`.
-error: 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() {
+}