Organize the server code a bit

This commit is contained in:
Gender Shrapnel 2020-06-01 17:19:28 +02:00
parent c940d35616
commit d7f606a036
2 changed files with 95 additions and 86 deletions

View File

@ -1,103 +1,22 @@
mod server;
mod simulation;
mod state;
use std::{sync::Arc};
use futures::SinkExt;
use serde_json::json;
use tera::{Context, Tera};
use tokio::sync::watch;
use warp::{Filter, reject::Reject, Rejection, ws::{Message, WebSocket}};
use simulation::Simulation;
use state::State;
struct WithTemplate {
name: &'static str,
context: Context,
}
use server::setup_server;
#[tokio::main]
async fn main() {
let mut simulation = Simulation::new();
let state_channel = simulation.state();
let tr = match Tera::new("templates/**/*.tera") {
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
let tr = Arc::new(tr);
let tera = move || {
let tr = tr.clone();
move |with_template| render(with_template, tr.clone())
};
let state = move || {
let state_channel = state_channel.clone();
move || state_channel.clone()
};
let sim = tokio::spawn(async move {
simulation.run().await;
});
let hello = warp::get()
.and(warp::path::end())
.map(state())
.and_then(|channel| async move { get_state(channel).await })
.map(|state| WithTemplate {
name: "stuff.tera",
context: Context::from_serialize(state).unwrap(),
})
.map(tera());
let wsstate = state();
let ws = warp::path("state")
.and(warp::ws())
.map(move |ws: warp::ws::Ws| (ws, wsstate()))
.map(|(ws, state): (warp::ws::Ws, watch::Receiver<State>)| {
ws.on_upgrade(move |websocket| watch_state(websocket, state))
});
let server =
tokio::spawn(async move { warp::serve(hello.or(ws)).run(([127, 0, 0, 1], 3030)).await });
let server = tokio::spawn(async move {
setup_server(state_channel).await;
});
tokio::try_join!(sim, server).unwrap();
}
async fn watch_state(mut ws: WebSocket, mut state_channel: watch::Receiver<State>) {
loop {
let state = state_channel.recv().await;
let result = match state {
Some(state) => ws.send(Message::text(json!(state).to_string())).await,
None => break
};
if let Err(_) = result {
break;
}
};
}
#[derive(Debug)]
struct SimulationUnavailable;
impl Reject for SimulationUnavailable {}
async fn get_state(mut channel: watch::Receiver<State>) -> Result<State, Rejection> {
match channel.recv().await {
Some(state) => Ok(state),
None => Err(warp::reject::custom(SimulationUnavailable))
}
}
fn render(template: WithTemplate, tera: Arc<Tera>) -> impl warp::Reply {
let render = tera
.render(template.name, &template.context)
.unwrap_or_else(|err| err.to_string());
warp::reply::html(render)
}

90
src/server.rs Normal file
View File

@ -0,0 +1,90 @@
use std::sync::Arc;
use futures::SinkExt;
use serde_json::json;
use tera::{Context, Tera};
use tokio::sync::watch;
use warp::{Filter, reject::Reject, Rejection, ws::{Message, WebSocket}};
use crate::state::State;
pub async fn setup_server(state_channel: watch::Receiver<State>) {
let tr = match Tera::new("templates/**/*.tera") {
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
let tr = Arc::new(tr);
let tera = move || {
let tr = tr.clone();
move |with_template| render(with_template, tr.clone())
};
let state = move || {
let state_channel = state_channel.clone();
move || state_channel.clone()
};
let index = warp::get()
.and(warp::path::end())
.map(state())
.and_then(|channel| async move { get_state(channel).await })
.map(|state| WithTemplate {
name: "stuff.tera",
context: Context::from_serialize(state).unwrap(),
})
.map(tera());
let wsstate = state();
let ws = warp::path("state")
.and(warp::ws())
.map(move |ws: warp::ws::Ws| (ws, wsstate()))
.map(|(ws, state): (warp::ws::Ws, watch::Receiver<State>)| {
ws.on_upgrade(move |websocket| watch_state(websocket, state))
});
warp::serve(index.or(ws)).run(([127, 0, 0, 1], 3030)).await;
}
struct WithTemplate {
name: &'static str,
context: Context,
}
async fn watch_state(mut ws: WebSocket, mut state_channel: watch::Receiver<State>) {
loop {
let state = state_channel.recv().await;
let result = match state {
Some(state) => ws.send(Message::text(json!(state).to_string())).await,
None => break
};
if let Err(_) = result {
break;
}
};
}
#[derive(Debug)]
struct SimulationUnavailable;
impl Reject for SimulationUnavailable {}
async fn get_state(mut channel: watch::Receiver<State>) -> Result<State, Rejection> {
match channel.recv().await {
Some(state) => Ok(state),
None => Err(warp::reject::custom(SimulationUnavailable))
}
}
fn render(template: WithTemplate, tera: Arc<Tera>) -> impl warp::Reply {
let render = tera
.render(template.name, &template.context)
.unwrap_or_else(|err| err.to_string());
warp::reply::html(render)
}