refactoring

This commit is contained in:
Marc Planard 2024-02-29 15:54:45 +01:00
parent 837338cb69
commit 059118c535
6 changed files with 93 additions and 73 deletions

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.75"
axum = { version = "0.6.20", features = ["ws", "headers"] } axum = { version = "0.6.20", features = ["ws", "headers"] }
futures = "0.3.28" futures = "0.3.28"
geo = "0.26.0" geo = "0.26.0"

View File

@ -14,6 +14,8 @@
class="button2x" value="#ffffff"></input> class="button2x" value="#ffffff"></input>
<input type="button" id="clearButton" <input type="button" id="clearButton"
class="button2x" value="♻"></input> class="button2x" value="♻"></input>
<!-- <input type="button" id="errButton"
class="button2x" value="err"></input> !-->
</div> </div>
<div> <div>

View File

@ -55,7 +55,7 @@ const updateCanvas = (canvas, ctx, lines) => {
} }
}; };
const initDrawing = (canvas, clearButton, selectedColor, ws) => { const initDrawing = (canvas, clearButton, errButton, selectedColor, ws) => {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.lineWidth = 2; ctx.lineWidth = 2;
let lines = []; let lines = [];
@ -68,10 +68,14 @@ const initDrawing = (canvas, clearButton, selectedColor, ws) => {
resetCanvas(); resetCanvas();
clearButton.onclick = evt => { clearButton.onclick = evt => {
lines = [];
resetCanvas();
ws.send(JSON.stringify({ t: "clear" })); ws.send(JSON.stringify({ t: "clear" }));
}; };
if (errButton) {
errButton.onclick = evt => {
ws.send(JSON.stringify({ t: "error", msg: "this is an error" }));
};
}
canvas.addEventListener("mousedown", evt => { canvas.addEventListener("mousedown", evt => {
const pt = getMousePoint(canvas, evt, selectedColor.value); const pt = getMousePoint(canvas, evt, selectedColor.value);
@ -89,7 +93,7 @@ const initDrawing = (canvas, clearButton, selectedColor, ws) => {
}); });
canvas.addEventListener("mouseup", evt => { canvas.addEventListener("mouseup", evt => {
console.log(currentLine); // console.log(currentLine);
currentLine = null; currentLine = null;
ws.send(JSON.stringify({ t: "stroke"})); ws.send(JSON.stringify({ t: "stroke"}));
}); });
@ -109,7 +113,7 @@ const initDrawing = (canvas, clearButton, selectedColor, ws) => {
}; };
const initWs = errorBox => { const initWs = errorBox => {
const socket = new WebSocket('ws://localhost:3000/ws'); const socket = new WebSocket('ws://'+ location.host + '/ws');
socket.addEventListener('open', function (event) { socket.addEventListener('open', function (event) {
console.log("Open", event); console.log("Open", event);
@ -131,9 +135,10 @@ window.onload = () => {
const colorsDiv = document.querySelector("#colors"); const colorsDiv = document.querySelector("#colors");
const selectedColor = document.querySelector("#selectedColor"); const selectedColor = document.querySelector("#selectedColor");
const clearButton = document.querySelector("#clearButton"); const clearButton = document.querySelector("#clearButton");
const errButton = document.querySelector("#errButton");
const errorBox = document.querySelector("#errorBox"); const errorBox = document.querySelector("#errorBox");
const canvas = document.querySelector("#canvas"); const canvas = document.querySelector("#canvas");
initUI(colorsDiv, selectedColor); initUI(colorsDiv, selectedColor);
let ws = initWs(errorBox); let ws = initWs(errorBox);
initDrawing(canvas, clearButton, selectedColor, ws); initDrawing(canvas, clearButton, errButton, selectedColor, ws);
}; };

View File

@ -25,7 +25,10 @@ async fn gen_server(mut rx: Receiver<GSMsg>) {
match msg { match msg {
GSMsg::NewClient((addr, c_tx)) => { GSMsg::NewClient((addr, c_tx)) => {
for line in &lines { for line in &lines {
c_tx.send(GSMsg::NewLine(line.clone())).await.unwrap(); let ret = c_tx.send(GSMsg::NewLine(line.clone())).await;
if let Err(err) = ret {
tracing::warn!("Client {addr} send error: {err}");
}
} }
clients.insert(addr, c_tx); clients.insert(addr, c_tx);
tracing::info!("NewClient {addr}"); tracing::info!("NewClient {addr}");
@ -53,11 +56,9 @@ async fn send_all(
let mut to_remove : Vec<SocketAddr> = vec![]; let mut to_remove : Vec<SocketAddr> = vec![];
for (addr, ref mut tx) in clients.iter() { for (addr, ref mut tx) in clients.iter() {
let ret = tx let ret = tx.send(msg.clone()).await;
.send(msg.clone()) if let Err(err) = ret {
.await; tracing::warn!("Client {addr} abruptly disconnected: {err}");
if ret.is_err() {
tracing::warn!("Client {addr} abruptly disconnected");
to_remove.push(*addr); to_remove.push(*addr);
} }
} }

View File

@ -25,9 +25,10 @@ use gen_server::GSMsg;
const LISTEN_ON : &str = "0.0.0.0:3000"; const LISTEN_ON : &str = "0.0.0.0:3000";
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> anyhow::Result<()> {
tracing_subscriber::registry() tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::try_from_default_env() //.with(tracing_subscriber::EnvFilter::try_from_default_env()
.with(tracing_subscriber::EnvFilter::try_from_env("LJ_SKETCH")
.unwrap_or_else(|_| "lj_sketch=info,tower_http=info".into())) .unwrap_or_else(|_| "lj_sketch=info,tower_http=info".into()))
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
.init(); .init();
@ -45,13 +46,12 @@ async fn main() {
.make_span_with(DefaultMakeSpan::default() .make_span_with(DefaultMakeSpan::default()
.include_headers(false))); .include_headers(false)));
let addr : SocketAddr = LISTEN_ON.parse().unwrap(); let addr : SocketAddr = LISTEN_ON.parse()?;
tracing::info!("listening on {}", addr);
tracing::info!("listening on {}", addr);
axum::Server::bind(&addr) axum::Server::bind(&addr)
.serve(app.into_make_service_with_connect_info::<SocketAddr>()) .serve(app.into_make_service_with_connect_info::<SocketAddr>())
.await .await?;
.unwrap(); Ok(())
} }
async fn ws_handler( async fn ws_handler(

View File

@ -1,8 +1,10 @@
use axum::extract::ws::{Message, Message::Text, Message::Close, WebSocket}; use axum::extract::ws::{Message, WebSocket};
use std::net::SocketAddr; use std::net::SocketAddr;
use tokio::sync::mpsc::{self, Sender}; use tokio::sync::mpsc::{self, Sender};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use anyhow::{Result,anyhow};
use crate::gen_server::GSMsg; use crate::gen_server::GSMsg;
use crate::line::{Line, simplify_line}; use crate::line::{Line, simplify_line};
@ -22,12 +24,23 @@ enum JMsg {
} }
pub async fn handle_socket( pub async fn handle_socket(
mut socket: WebSocket, socket: WebSocket,
who: SocketAddr, who: SocketAddr,
gs_tx: Sender<GSMsg> gs_tx: Sender<GSMsg>
) { ) {
match handle_socket_(socket, who, gs_tx).await {
Ok(()) => {},
Err(err) => tracing::warn!("{who}: WS Handler error: {err}")
}
}
pub async fn handle_socket_(
mut socket: WebSocket,
who: SocketAddr,
gs_tx: Sender<GSMsg>
) -> Result<()> {
let (chan_tx, mut chan_rx) = mpsc::channel(32); let (chan_tx, mut chan_rx) = mpsc::channel(32);
gs_tx.send(GSMsg::NewClient((who, chan_tx))).await.unwrap(); gs_tx.send(GSMsg::NewClient((who, chan_tx))).await?;
let mut line : Line = vec![]; let mut line : Line = vec![];
loop { loop {
@ -37,18 +50,15 @@ pub async fn handle_socket(
tracing::warn!("{who}: Error receiving packet: {msg:?}"); tracing::warn!("{who}: Error receiving packet: {msg:?}");
continue; continue;
}; };
match process_ws_msg(&gs_tx, &who, &mut line, msg).await { match process_ws_msg(&gs_tx, &who, &mut line, msg).await? {
ControlFlow::Break(()) => break, ControlFlow::Break(()) => break Ok(()),
ControlFlow::Continue(()) => {} ControlFlow::Continue(()) => {}
} }
}, },
Some(msg) = chan_rx.recv() => { Some(msg) = chan_rx.recv() => {
process_gs_msg(&mut socket, &who, msg).await process_gs_msg(&mut socket, &who, msg).await?
}, },
else => { else => break Err(anyhow!("{who}: Connection lost unexpectedly."))
tracing::warn!("{who}: Connection lost unexpectedly.");
break;
}
} }
} }
} }
@ -57,19 +67,20 @@ async fn process_gs_msg(
socket: &mut WebSocket, socket: &mut WebSocket,
who: &SocketAddr, who: &SocketAddr,
msg: GSMsg msg: GSMsg
) { ) -> Result<()> {
match msg { match msg {
GSMsg::NewLine(line) => { GSMsg::NewLine(line) => {
socket.send(Message::Text(line_to_json(&line))).await.unwrap(); socket.send(Message::Text(line_to_json(&line)?)).await?;
}, },
GSMsg::Clear => { GSMsg::Clear => {
let msg = serde_json::to_string(&JMsg::Clear).unwrap(); let msg = serde_json::to_string(&JMsg::Clear)?;
socket.send(Message::Text(msg)).await.unwrap(); socket.send(Message::Text(msg)).await?;
}, },
msg => { msg => {
tracing::info!("{who} should not get this: {:?}", msg); tracing::info!("{who} should not get this: {:?}", msg);
} }
} }
Ok(())
} }
async fn process_ws_msg( async fn process_ws_msg(
@ -77,75 +88,75 @@ async fn process_ws_msg(
who: &SocketAddr, who: &SocketAddr,
line: &mut Line, line: &mut Line,
msg: Message msg: Message
) -> ControlFlow<(),()> { ) -> Result<ControlFlow<(),()>> {
match msg { match msg {
Text(text) => match serde_json::from_str(&text) { Message::Text(text) => {
Ok(json) => { tracing::debug!("{who}: sent: {text}");
tracing::debug!("{who}: '{json:?}'"); match serde_json::from_str(&text) {
match handle_ws_msg(line, json) { Ok(json) => {
Ok(Some(req)) => gs_tx.send(req).await.unwrap(), tracing::debug!("{who}: got json: {json:?}");
Ok(None) => {}, match handle_ws_msg(line, json) {
Err(err) => { Ok(Some(req)) => gs_tx.send(req).await?,
tracing::warn!("{who}: message error: {err}"); Ok(None) => {},
Err(err) => {
tracing::warn!("{who}: message error: {err}");
}
} }
} },
}, Err(err) => tracing::warn!("{who}: can't parse JSON: {err} in: {text}")
Err(err) => {
tracing::warn!("{who}: can't parse JSON: {err}");
} }
}, },
Close(close) => { Message::Close(close) => {
tracing::info!("{who}: closing: {close:?}"); tracing::info!("{who}: closing: {close:?}");
gs_tx.send(GSMsg::DeleteClient(*who)).await.unwrap(); gs_tx.send(GSMsg::DeleteClient(*who)).await?;
return ControlFlow::Break(()); return Ok(ControlFlow::Break(()));
}, },
_ => { _ => {
tracing::warn!("{who}: can't handle message: {msg:?}"); tracing::warn!("{who}: can't handle message: {msg:?}");
} }
}; };
ControlFlow::Continue(()) Ok(ControlFlow::Continue(()))
} }
fn handle_ws_msg( fn handle_ws_msg(
line: &mut Line, line: &mut Line,
msg: JMsg msg: JMsg
) -> Result<Option<GSMsg>, &'static str> { ) -> Result<Option<GSMsg>> {
match msg { match msg {
JMsg::Clear => { JMsg::Clear => Ok(Some(GSMsg::Clear)),
line.clear();
return Ok(Some(GSMsg::Clear));
},
JMsg::MoveTo { x, y, color } => { JMsg::MoveTo { x, y, color } => {
*line = vec![ (x, y, parse_color(color)?) ]; line.clear();
line.push((x, y, parse_color(color)?));
Ok(None)
}, },
JMsg::LineTo { x, y, color } => { JMsg::LineTo { x, y, color } => {
line.push( (x, y, parse_color(color)?) ); line.push( (x, y, parse_color(color)?) );
Ok(None)
}, },
JMsg::Stroke => { JMsg::Stroke if line.len() > 1 => {
if line.len() > 1 { let simple_line = simplify_line(line);
let simple_line = simplify_line(line); line.clear();
*line = vec![]; Ok(Some(GSMsg::NewLine(simple_line)))
return Ok(Some(GSMsg::NewLine(simple_line)));
}
}, },
JMsg::Line{..} => { JMsg::Stroke => Err(anyhow!("can't stroke a 1 point 'line'")),
return Err("recieved a line message O_o"); JMsg::Line{..} => Err(anyhow!("recieved a line message O_o"))
} }
};
Ok(None)
} }
fn line_to_json(line: &Line) -> String { fn line_to_json(line: &Line) -> Result<String> {
let line = line.iter() let line = line.iter()
.map(| (x, y, c) | (*x, *y, format!("#{:06x}", c))) .map(| (x, y, c) | (*x, *y, format!("#{:06x}", c)))
.collect(); .collect();
serde_json::to_string(&JMsg::Line{ line }).unwrap() let json = serde_json::to_string(&JMsg::Line{ line })?;
Ok(json)
} }
fn parse_color(s: String) -> Result<u32, &'static str> { fn parse_color(s: String) -> Result<u32> {
if s.len() != 7 || &s[0..1] != "#" { if s.len() != 7 || &s[0..1] != "#" {
Err("badly formated color.") Err(anyhow!("badly formated color"))
} else { } else {
u32::from_str_radix(&s[1..], 16).map_err(|_| "unable to parse color") u32::from_str_radix(&s[1..], 16)
.map_err(|_| anyhow!("unable to parse color"))
} }
} }