Skip to content
This repository has been archived by the owner on Dec 12, 2022. It is now read-only.

Commit

Permalink
WIP on joining a game
Browse files Browse the repository at this point in the history
  • Loading branch information
totorigolo committed May 10, 2020
1 parent 3f1a734 commit a6164c7
Show file tree
Hide file tree
Showing 16 changed files with 682 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"rust-lang.rust",
"serayuzgur.crates",
"streetsidesoftware.code-spell-checker",
"tyriar.sort-lines"
"tyriar.sort-lines",
"zamerick.vscode-caddyfile-syntax"
]
}
39 changes: 39 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Both configurations assume that the game server is accessible.
#
# Ports:
# - 42800 -> dev
# - 42801 -> prod-like
# - 42802 -> game server API
# - 42803 -> WebPack Dev Server

# Delegating to WebPack Dev Server on port 42803
:42800 {
log

route /api/* {
uri strip_prefix /api
reverse_proxy localhost:42802
}

reverse_proxy * localhost:42803

# Enable compression
encode gzip zstd
}

# Production-like from ./dist
:42801 {
route /api/* {
uri strip_prefix /api
reverse_proxy localhost:42802
}

root * ./dist
file_server

# If no file matches, serve the index (single page app support)
try_files {path} /index.html

# Enable compression
encode gzip zstd
}
9 changes: 2 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ wasm-bindgen = "0.2"
wasm-logger = "0.2"
# yew = "0.16"
# yew-router = "0.12"
yew-router = { git = "https://github.com/yewstack/yew", branch = "v0.16.0" }
yew = { git = "https://github.com/yewstack/yew", branch = "v0.16.0" }
# yew = { git = "https://github.com/yewstack/yew", branch = "v0.16.0" }
# yew-router = { git = "https://github.com/yewstack/yew", branch = "v0.16.0" }
yew = { path = "../yew/yew" }
yew-router = { path = "../yew/yew-router" }


# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
Expand All @@ -53,6 +56,6 @@ wasm-bindgen-futures = "0.4"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
# This makes the compiled code faster and smaller, but it makes compiling
# slower, so it's only enabled in release mode.
lto = true
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</p>

<h3>
<a href="#">Play online</a>
<a href="https://cards.busy.ovh/">Play online</a>
<span> | </span>
<a href="#">Dev corner</a>
</h3>
Expand All @@ -27,28 +27,49 @@ TBD

## 🚴 Usage

If you just want to play just head to our [web site](#). If instead you enjoy playing with code, or you want to give us a hand, keep reading.
If you just want to play just head to our [web site](https://cards.busy.ovh/).
If instead you enjoy playing with code, or you want to give us a hand, keep
reading.

### 🐑 Prerequisites

1. Install Rust using [rustup](https://www.rust-lang.org/tools/install).
2. Install [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
3. Install [Node.js](https://nodejs.org/) (for `npm`).
4. Install [Caddy](https://caddyserver.com/docs/download), for reverse proxy.
* We recommend using the version from the official distribution.
* Be careful to install Caddy v2.

### 🛠️ How to build

```bash
# Needed only the first time
npm install

# To build and start a web server, watching for changes
# Afterwards, go to: http://localhost:8000/
npm start

# To build, use one of:
npm run build:dev
npm run build:prod

# To run the tests
npm run test:rs

# To build and start a web server, watching for changes.
# Afterwards, go to: http://localhost:42803/.
# However, this won't serve the API. Read below for how-to.
npm start
```

The instructions above are for the frontend only. If you want the full website,
i.e. with the API, you need to run the game server and serve it at `/api`.

To simplify this setup, there is a `Caddyfile` in this repository that comes
pre-configured. Just make sure to respect the ports explained in the file.

```bash
# To start the reverse proxy
caddy run --watch

# Or to start as a daemon
caddy start --watch
caddy stop
```
201 changes: 201 additions & 0 deletions src/agents/game_ws_mgr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#![allow(unused_imports)] // TODO: Clean imports

use anyhow::Result;
use log::*;
use serde::{Deserialize, Serialize};
use serde_json::json;
use yew::format::Json;
use yew::prelude::*;
use yew::services::websocket::{WebSocketService, WebSocketStatus, WebSocketTask};
use yew::worker::*;

// Re-export this for convenience
pub use yew::agent::{Dispatched, Dispatcher};

pub struct GameWsMgr {
link: AgentLink<Self>,
subscribers: Vec<HandlerId>,

ws_service: WebSocketService,
ws: WebSocketConnection,

ws_history: Vec<String>, // TODO: Try using Cows
}

#[derive(Debug)]
pub enum WebSocketConnection {
None,
Pending(WebSocketTask),
Connected(WebSocketTask),
}

impl WebSocketConnection {
fn is_none(&self) -> bool {
match &self {
Self::None => true,
_ => false,
}
}

fn is_pending(&self) -> bool {
match &self {
Self::Pending(_) => true,
_ => false,
}
}

fn is_connected(&self) -> bool {
match &self {
Self::Connected(_) => true,
_ => false,
}
}

fn connected(&mut self) {
*self = match std::mem::replace(self, WebSocketConnection::None) {
WebSocketConnection::Pending(ws) => WebSocketConnection::Connected(ws),
ws => {
error!("Ignoring incoherent connected message, status is {:?}.", ws);
ws
}
};
}
}

#[derive(Debug)]
pub enum Msg {
FailedToConnect(String),
WsNotification(WebSocketStatus),
WsReceived(Result<WsResponse>), // TODO: Try use Cow or Rc
}

#[derive(Serialize, Deserialize, Debug)]
pub enum GameWsRequest {
ConnectSocket { address: String },
CloseSocket,
Send(WsRequest),
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum GameWsResponse {
Closed,
Connecting,
Connected,
FailedToConnect(String),
ErrorOccurred,
Received(WsResponse),
ReceivedError(String), // TODO: Refine error type
}

/// This type is used as a request which sent to websocket connection.
#[derive(Serialize, Deserialize, Debug)]
pub struct WsRequest(serde_json::Value);

/// This type is an expected response from a websocket connection.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WsResponse(serde_json::Value);

impl Agent for GameWsMgr {
type Reach = Context;
type Message = Msg;
type Input = GameWsRequest;
type Output = GameWsResponse;

fn create(link: AgentLink<Self>) -> Self {
GameWsMgr {
link,
subscribers: Vec::with_capacity(10), // TODO: Tune capacities
ws_service: WebSocketService::new(),
ws: WebSocketConnection::None,
ws_history: Vec::with_capacity(500),
}
}

fn update(&mut self, msg: Self::Message) {
trace!("Agent update: {:?}", msg);
match msg {
Msg::FailedToConnect(reason) => {
self.broadcast_to_subscribers(GameWsResponse::FailedToConnect(reason))
}
Msg::WsNotification(status) => {
let out = match status {
WebSocketStatus::Opened => GameWsResponse::Connected,
WebSocketStatus::Closed => GameWsResponse::Closed,
WebSocketStatus::Error => GameWsResponse::ErrorOccurred,
};
self.broadcast_to_subscribers(out);
}
Msg::WsReceived(data) => {
let out = match data {
Ok(data) => GameWsResponse::Received(data),
Err(err) => GameWsResponse::ReceivedError(err.to_string()),
};
self.broadcast_to_subscribers(out);
}
}
}

fn handle_input(&mut self, input: Self::Input, _sender: HandlerId) {
trace!("Received in GameWsMgr from {:?}: {:?}", _sender, input);
match input {
GameWsRequest::ConnectSocket { address } => {
let callback = self.link.callback(|Json(data)| Msg::WsReceived(data));
let notification = self.link.callback(Msg::WsNotification);
match self.ws_service.connect(&address, callback, notification) {
Ok(task) => {
self.ws_history
.push(format!("Connecting to {}...", address));
self.ws = WebSocketConnection::Pending(task);

self.broadcast_to_subscribers(GameWsResponse::Connecting);
}
Err(e) => {
let err = format!("WebSocket connection failed: {}", e);
error!("{}", err);
self.ws_history.push(err.clone());
trace!("Before send_message");
self.link.send_message(Msg::FailedToConnect(err));
trace!("After send_message");
}
}
}
GameWsRequest::CloseSocket => {
self.ws_history.push(format!("Closed"));
self.ws = WebSocketConnection::None;
}
GameWsRequest::Send(data) => {
if let WebSocketConnection::Connected(ws) = &mut self.ws {
ws.send(Json(&data));
self.ws_history.push(format!("> {}", data.0));
} else {
error!("Tried to send on non-opened WebSocket. Ignoring.");
}
}
}
}

fn connected(&mut self, id: HandlerId) {
trace!("New connection: {:?}", id);
if !self.subscribers.contains(&id) {
self.subscribers.push(id);
}
}

fn disconnected(&mut self, id: HandlerId) {
// Remove the subscriber (efficiently, hence swap_remove)
if let Some(pos) = self.subscribers.iter().position(|x| *x == id) {
self.subscribers.swap_remove(pos);
trace!("Subscriber disconnected: {:?}", id);
} else {
warn!("Disconnection but no associated subscriber.");
}
}
}

impl GameWsMgr {
fn broadcast_to_subscribers(&self, output: GameWsResponse) {
for sub in self.subscribers.iter() {
self.link.respond(*sub, output.clone());
}
}
}
1 change: 1 addition & 0 deletions src/agents/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod game_ws_mgr;
pub mod notifications;
pub mod state_mgr;
2 changes: 1 addition & 1 deletion src/components/navbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Component for Navbar {

<div class=("navbar-menu", is_active)>
<NavLink classes="navbar-item" route=AppRoute::CreateGame>
{ "New game" }
{ "Start a game" }
</NavLink>
<NavLink classes="navbar-item" route=AppRoute::ListGames>
{ "List games" }
Expand Down
Loading

0 comments on commit a6164c7

Please sign in to comment.