refactoring + add pub
This commit is contained in:
		
							parent
							
								
									7ca48a2448
								
							
						
					
					
						commit
						e235fcc749
					
				
							
								
								
									
										27
									
								
								pub/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								pub/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <title>LJ Sketch</title>
 | 
			
		||||
    <meta charset="UTF-8"> 
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="style.css">
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
 | 
			
		||||
    <div id="main">
 | 
			
		||||
      <div id="toolbox">
 | 
			
		||||
	<div id="colors"></div>
 | 
			
		||||
	<input type="color" id="selectedColor"
 | 
			
		||||
	       class="button2x" value="#ffffff"></input>
 | 
			
		||||
	<input type="button" id="clearButton"
 | 
			
		||||
	       class="button2x" value="♻"></input>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
	<canvas id="canvas" width="1024" height="1024"></canvas>      
 | 
			
		||||
	<span class="footer">LJ Sketch</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
    <script src="main.js"></script>
 | 
			
		||||
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										130
									
								
								pub/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								pub/main.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,130 @@
 | 
			
		||||
const colors = [
 | 
			
		||||
    "#000000",
 | 
			
		||||
    "#1D2B53",
 | 
			
		||||
    "#7E2553",
 | 
			
		||||
    "#008751",
 | 
			
		||||
    "#AB5236",
 | 
			
		||||
    "#5F574F",
 | 
			
		||||
    "#C2C3C7",
 | 
			
		||||
    "#FFF1E8",
 | 
			
		||||
    "#FF004D",
 | 
			
		||||
    "#FFA300",
 | 
			
		||||
    "#FFEC27",
 | 
			
		||||
    "#00E436",
 | 
			
		||||
    "#29ADFF",
 | 
			
		||||
    "#83769C",
 | 
			
		||||
    "#FF77A8",
 | 
			
		||||
    "#FFCCAA"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const initUI = (colorsDiv, selectedColor) => {
 | 
			
		||||
    for (const color of colors) {
 | 
			
		||||
	(color => {
 | 
			
		||||
	    const div = document.createElement("div");
 | 
			
		||||
	    div.setAttribute("class", "color");	
 | 
			
		||||
	    div.setAttribute("style", "background: "+color+";");
 | 
			
		||||
	    div.onclick = evt => { selectedColor.value = color; };
 | 
			
		||||
	    colorsDiv.appendChild(div);
 | 
			
		||||
	})(color);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//Get Mouse Position
 | 
			
		||||
const getMousePoint = (canvas, evt, color) => {
 | 
			
		||||
    var rect = canvas.getBoundingClientRect();
 | 
			
		||||
    const x = Math.floor(canvas.width * (evt.clientX - rect.left) / rect.width);
 | 
			
		||||
    const y = Math.floor(canvas.height * (evt.clientY - rect.top) / rect.width);
 | 
			
		||||
    return { x, y, color };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateLine = (ctx, line) => {
 | 
			
		||||
    ctx.strokeStyle = line[0].color;
 | 
			
		||||
    ctx.beginPath();
 | 
			
		||||
    ctx.moveTo(line[0].x, line[0].y);
 | 
			
		||||
    for(let i=1; i < line.length; ++i) {
 | 
			
		||||
	ctx.lineTo(line[i].x, line[i].y);
 | 
			
		||||
    }
 | 
			
		||||
    ctx.stroke();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateCanvas = (canvas, ctx, lines) => {
 | 
			
		||||
    ctx.fillStyle = "black";
 | 
			
		||||
    ctx.fillRect(0, 0, canvas.width, canvas.height);    
 | 
			
		||||
    for(let line of lines) {
 | 
			
		||||
	updateLine(ctx, line);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initDrawing = (canvas, clearButton, selectedColor, ws) => {
 | 
			
		||||
    const ctx = canvas.getContext("2d");
 | 
			
		||||
    ctx.lineWidth = 2;
 | 
			
		||||
    let lines = [];
 | 
			
		||||
    let currentLine = null;
 | 
			
		||||
 | 
			
		||||
    const resetCanvas = () => {
 | 
			
		||||
	ctx.fillStyle = "black";
 | 
			
		||||
	ctx.fillRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
    };
 | 
			
		||||
    resetCanvas();
 | 
			
		||||
    
 | 
			
		||||
    clearButton.onclick = evt => {
 | 
			
		||||
	lines = [];
 | 
			
		||||
	resetCanvas();
 | 
			
		||||
	ws.send(JSON.stringify({ t: "clear" }));
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    canvas.addEventListener("mousedown", evt => {
 | 
			
		||||
	const pt = getMousePoint(canvas, evt, selectedColor.value);
 | 
			
		||||
	currentLine = [ pt ];
 | 
			
		||||
	ws.send(JSON.stringify({ ...pt, t: "moveTo" }));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    canvas.addEventListener("mousemove", evt => {
 | 
			
		||||
	if(evt.buttons === 1) {
 | 
			
		||||
	    const pt = getMousePoint(canvas, evt, selectedColor.value);
 | 
			
		||||
	    currentLine.push(pt);
 | 
			
		||||
	    updateLine(ctx, currentLine);
 | 
			
		||||
	    ws.send(JSON.stringify({ ...pt, t: "lineTo" }));
 | 
			
		||||
	}
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    canvas.addEventListener("mouseup", evt => {
 | 
			
		||||
	console.log(currentLine);
 | 
			
		||||
	currentLine = null;
 | 
			
		||||
	ws.send(JSON.stringify({ t: "stroke"})); 
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ws.addEventListener('message', function (event) {
 | 
			
		||||
	let j = JSON.parse(event.data);
 | 
			
		||||
	console.log(j);
 | 
			
		||||
	if (j.t === "line") {
 | 
			
		||||
	    let line = j.line.map(a => ({ x: a[0], y: a[1], color: a[2] }));
 | 
			
		||||
	    lines.push(line);
 | 
			
		||||
	    updateCanvas(canvas, ctx, lines);
 | 
			
		||||
	} else if (j.t == "clear") {
 | 
			
		||||
	    lines = [];
 | 
			
		||||
	    resetCanvas();	    
 | 
			
		||||
	}
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const initWs = () => {
 | 
			
		||||
    const socket = new WebSocket('ws://localhost:3000/ws');
 | 
			
		||||
 | 
			
		||||
    socket.addEventListener('open', function (event) {
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    return socket;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.onload = () => {
 | 
			
		||||
    const colorsDiv = document.querySelector("#colors");
 | 
			
		||||
    const selectedColor = document.querySelector("#selectedColor");
 | 
			
		||||
    const clearButton = document.querySelector("#clearButton");
 | 
			
		||||
    const canvas = document.querySelector("#canvas");
 | 
			
		||||
    initUI(colorsDiv, selectedColor);
 | 
			
		||||
    let ws = initWs();
 | 
			
		||||
    initDrawing(canvas, clearButton, selectedColor, ws);
 | 
			
		||||
    
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										66
									
								
								pub/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								pub/style.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
body {
 | 
			
		||||
    margin: 0px;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
    background: #222;
 | 
			
		||||
    color: #ddd;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#main {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(3, 1fr);
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
    grid-auto-rows: minmax(100px, auto);
 | 
			
		||||
  
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    right: 0px;
 | 
			
		||||
    bottom: 0px;
 | 
			
		||||
    left: 0px;
 | 
			
		||||
    margin: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#canvas {
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    display: block;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    aspect-ratio: 1/1;   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#canvas:hover {
 | 
			
		||||
    cursor: crosshair;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#toolbox {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    width: 128px;
 | 
			
		||||
    padding: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button2x {
 | 
			
		||||
    width: 128px !important;
 | 
			
		||||
    height: 128px !important;    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#toolbox > input {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 64px;
 | 
			
		||||
    height: 64px;
 | 
			
		||||
    font-size: 64px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#colors {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.color {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 64px;
 | 
			
		||||
    height: 64px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer {
 | 
			
		||||
    display: block;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,25 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::net::SocketAddr;
 | 
			
		||||
use tokio::sync::mpsc:: {Sender, Receiver};
 | 
			
		||||
use crate::ws_client::Line;
 | 
			
		||||
use crate::line::Line;
 | 
			
		||||
use tokio::sync::mpsc::{self, Sender, Receiver};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum GSMsg {
 | 
			
		||||
    NewClient((SocketAddr, Sender<GSMsg>)),
 | 
			
		||||
    NewLine(Line),
 | 
			
		||||
    DeleteClient(SocketAddr),
 | 
			
		||||
    NewLine(Line),
 | 
			
		||||
    Clear
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct State {
 | 
			
		||||
    pub gs_tx: Sender<GSMsg>
 | 
			
		||||
pub fn spawn() -> Sender<GSMsg> {
 | 
			
		||||
    let (tx, rx) = mpsc::channel(32);
 | 
			
		||||
    tokio::spawn(gen_server(rx));
 | 
			
		||||
    tx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn gen_server(mut rx: Receiver<GSMsg>) {
 | 
			
		||||
    let mut clients : HashMap<SocketAddr, Sender<GSMsg>> =
 | 
			
		||||
	HashMap::new();
 | 
			
		||||
    
 | 
			
		||||
    let mut lines : Vec<Line> = vec![];
 | 
			
		||||
async fn gen_server(mut rx: Receiver<GSMsg>) {
 | 
			
		||||
    let mut clients: HashMap<SocketAddr, Sender<GSMsg>> = HashMap::new();
 | 
			
		||||
    let mut lines: Vec<Line> = vec![];
 | 
			
		||||
    
 | 
			
		||||
    while let Some(msg) = rx.recv().await {
 | 
			
		||||
	match msg {
 | 
			
		||||
@ -36,8 +36,8 @@ pub async fn gen_server(mut rx: Receiver<GSMsg>) {
 | 
			
		||||
		lines.push(line);
 | 
			
		||||
	    },
 | 
			
		||||
	    GSMsg::DeleteClient(addr) => {
 | 
			
		||||
		tracing::info!("Client {addr} removed");		
 | 
			
		||||
		clients.remove(&addr);		
 | 
			
		||||
		tracing::info!("Client {addr} removed");
 | 
			
		||||
	    },
 | 
			
		||||
	    GSMsg::Clear => {
 | 
			
		||||
		send_all(&mut clients, &GSMsg::Clear).await;
 | 
			
		||||
@ -52,8 +52,8 @@ async fn send_all(
 | 
			
		||||
    msg: &GSMsg
 | 
			
		||||
) {
 | 
			
		||||
    let mut to_remove : Vec<SocketAddr> = vec![];
 | 
			
		||||
		
 | 
			
		||||
    for (addr, ref mut tx) in &mut *clients {
 | 
			
		||||
    
 | 
			
		||||
    for (addr, ref mut tx) in clients.iter() {
 | 
			
		||||
	let ret = tx
 | 
			
		||||
	    .send(msg.clone())
 | 
			
		||||
	    .await;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/line.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/line.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
use geo::Simplify;
 | 
			
		||||
 | 
			
		||||
pub type Line = Vec<(f32,f32,u32)>;
 | 
			
		||||
 | 
			
		||||
pub fn simplify_line(line: &Line) -> Line {
 | 
			
		||||
    if line.len() < 2 {
 | 
			
		||||
	return line.to_vec();
 | 
			
		||||
    }
 | 
			
		||||
    let color = line[0].2;
 | 
			
		||||
    let linestring : geo::LineString =
 | 
			
		||||
	line.iter()
 | 
			
		||||
	.map(| (x, y, _) | (*x as f64, *y as f64 ))
 | 
			
		||||
	.collect();
 | 
			
		||||
    let linestring = linestring.simplify(&4.0);
 | 
			
		||||
    linestring.0.iter()
 | 
			
		||||
	.map(| c | (c.x as f32, c.y as f32, color))
 | 
			
		||||
	.collect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,5 +1,6 @@
 | 
			
		||||
mod gen_server;
 | 
			
		||||
mod ws_client;
 | 
			
		||||
mod line;
 | 
			
		||||
 | 
			
		||||
use axum::{
 | 
			
		||||
    extract::{
 | 
			
		||||
@ -11,20 +12,15 @@ use axum::{
 | 
			
		||||
    Router,
 | 
			
		||||
    Extension
 | 
			
		||||
};
 | 
			
		||||
use axum::extract::connect_info::ConnectInfo;
 | 
			
		||||
use std::{net::SocketAddr, path::PathBuf};
 | 
			
		||||
use tower_http::{
 | 
			
		||||
    services::ServeDir,
 | 
			
		||||
    trace::{DefaultMakeSpan, TraceLayer},
 | 
			
		||||
};
 | 
			
		||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
 | 
			
		||||
use axum::extract::connect_info::ConnectInfo;
 | 
			
		||||
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::sync::{
 | 
			
		||||
    mpsc:: { self, Sender, Receiver },
 | 
			
		||||
    Mutex
 | 
			
		||||
};
 | 
			
		||||
use gen_server::{State,GSMsg,gen_server};
 | 
			
		||||
use tokio::sync::mpsc::Sender;
 | 
			
		||||
use gen_server::GSMsg;
 | 
			
		||||
 | 
			
		||||
const LISTEN_ON : &str = "0.0.0.0:3000";
 | 
			
		||||
 | 
			
		||||
@ -38,25 +34,19 @@ async fn main() {
 | 
			
		||||
        )
 | 
			
		||||
        .with(tracing_subscriber::fmt::layer())
 | 
			
		||||
        .init();
 | 
			
		||||
    
 | 
			
		||||
    let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets");
 | 
			
		||||
 | 
			
		||||
    let (gs_tx, gs_rx) : (Sender<GSMsg>, Receiver<GSMsg>) = mpsc::channel(32);
 | 
			
		||||
    
 | 
			
		||||
    let state = Arc::new(Mutex::new(State { gs_tx }));
 | 
			
		||||
    
 | 
			
		||||
    tokio::spawn(gen_server(gs_rx));
 | 
			
		||||
    let gs_tx = gen_server::spawn(); 
 | 
			
		||||
 | 
			
		||||
    let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("pub");
 | 
			
		||||
    
 | 
			
		||||
    let app = Router::new()
 | 
			
		||||
        .fallback_service(ServeDir::new(assets_dir)
 | 
			
		||||
			  .append_index_html_on_directories(true))
 | 
			
		||||
        .route("/ws", get(ws_handler))	
 | 
			
		||||
	.layer(Extension(state))
 | 
			
		||||
        .layer(
 | 
			
		||||
            TraceLayer::new_for_http()
 | 
			
		||||
                .make_span_with(DefaultMakeSpan::default()
 | 
			
		||||
				.include_headers(false)),
 | 
			
		||||
        );
 | 
			
		||||
	.layer(Extension(gs_tx))
 | 
			
		||||
        .layer(TraceLayer::new_for_http()
 | 
			
		||||
               .make_span_with(DefaultMakeSpan::default()
 | 
			
		||||
			       .include_headers(false)));
 | 
			
		||||
        
 | 
			
		||||
    let addr : SocketAddr = LISTEN_ON.parse().unwrap();
 | 
			
		||||
    
 | 
			
		||||
@ -69,7 +59,7 @@ async fn main() {
 | 
			
		||||
 | 
			
		||||
async fn ws_handler(
 | 
			
		||||
    ws: WebSocketUpgrade,
 | 
			
		||||
    Extension(state): Extension<Arc<Mutex<State>>>,
 | 
			
		||||
    Extension(gs_tx): Extension<Sender<GSMsg>>,
 | 
			
		||||
    user_agent: Option<TypedHeader<axum::headers::UserAgent>>,
 | 
			
		||||
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
 | 
			
		||||
) -> impl IntoResponse {
 | 
			
		||||
@ -78,9 +68,6 @@ async fn ws_handler(
 | 
			
		||||
    } else {
 | 
			
		||||
        String::from("Unknown browser")
 | 
			
		||||
    };
 | 
			
		||||
    tracing::info!("`{user_agent}` at {addr} connected.");
 | 
			
		||||
    // finalize the upgrade process by returning upgrade callback.
 | 
			
		||||
    // we can customize the callback by sending additional info such as address.
 | 
			
		||||
    ws.on_upgrade(move |socket| ws_client::handle_socket(socket, addr, state))
 | 
			
		||||
    tracing::info!("{addr} connected [{user_agent}].");
 | 
			
		||||
    ws.on_upgrade(move |socket| ws_client::handle_socket(socket, addr, gs_tx))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										174
									
								
								src/ws_client.rs
									
									
									
									
									
								
							
							
						
						
									
										174
									
								
								src/ws_client.rs
									
									
									
									
									
								
							@ -1,20 +1,14 @@
 | 
			
		||||
use crate::gen_server::{State,GSMsg};
 | 
			
		||||
 | 
			
		||||
use axum::extract::ws::{ Message, Message::Text, Message::Close, WebSocket };
 | 
			
		||||
use std::net::SocketAddr;
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::sync::{
 | 
			
		||||
    mpsc:: { self, Sender, Receiver },
 | 
			
		||||
    Mutex
 | 
			
		||||
};
 | 
			
		||||
use tokio::sync::mpsc::{self, Sender};
 | 
			
		||||
use serde::{Serialize,Deserialize};
 | 
			
		||||
use geo::Simplify;
 | 
			
		||||
 | 
			
		||||
use  core::ops::ControlFlow;
 | 
			
		||||
 | 
			
		||||
use crate::gen_server::GSMsg;
 | 
			
		||||
use crate::line::{Line,simplify_line};
 | 
			
		||||
		  
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug)]
 | 
			
		||||
#[serde(tag = "t")]
 | 
			
		||||
pub enum JMsg {
 | 
			
		||||
enum JMsg {
 | 
			
		||||
    #[serde(rename = "clear")]     
 | 
			
		||||
    Clear,
 | 
			
		||||
    #[serde(rename = "moveTo")] 
 | 
			
		||||
@ -27,126 +21,118 @@ pub enum JMsg {
 | 
			
		||||
    Line { line: Vec<(f32,f32,String)> }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type Line = Vec<(f32,f32,u32)>;
 | 
			
		||||
 | 
			
		||||
pub async fn handle_socket(
 | 
			
		||||
    mut socket: WebSocket,
 | 
			
		||||
    who: SocketAddr,
 | 
			
		||||
    state: Arc<Mutex<State>>
 | 
			
		||||
    gs_tx: Sender<GSMsg>
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    let (c_tx, mut c_rx) : (Sender<GSMsg>, Receiver<GSMsg>) = mpsc::channel(32);
 | 
			
		||||
    
 | 
			
		||||
    {
 | 
			
		||||
	state.lock()
 | 
			
		||||
	    .await
 | 
			
		||||
	    .gs_tx.send(GSMsg::NewClient((who, c_tx)))
 | 
			
		||||
	    .await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
    let (c_tx, mut c_rx) = mpsc::channel(32); 
 | 
			
		||||
    gs_tx.send(GSMsg::NewClient((who, c_tx))).await.unwrap();
 | 
			
		||||
    let mut line : Line = vec![];
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
	tokio::select! {
 | 
			
		||||
	    Some(msg) = socket.recv() => {
 | 
			
		||||
		match process_ws_msg(&state, &who, &mut line, msg).await {
 | 
			
		||||
		    ControlFlow::Break(()) => { return; },
 | 
			
		||||
		let Ok(msg) = msg else {
 | 
			
		||||
		    tracing::warn!("{who}: Error receiving packet: {msg:?}");
 | 
			
		||||
		    continue;
 | 
			
		||||
		};
 | 
			
		||||
		match process_ws_msg(&gs_tx, &who, &mut line, msg).await {
 | 
			
		||||
		    ControlFlow::Break(()) => break,
 | 
			
		||||
		    ControlFlow::Continue(()) => {}
 | 
			
		||||
		}
 | 
			
		||||
	    },
 | 
			
		||||
	    Some(msg) = c_rx.recv() => {
 | 
			
		||||
		match msg {
 | 
			
		||||
		    GSMsg::NewLine(line) => {
 | 
			
		||||
			socket.send(Message::Text(line_to_json(&line)))
 | 
			
		||||
			    .await.unwrap();
 | 
			
		||||
		    },
 | 
			
		||||
		    GSMsg::Clear => {
 | 
			
		||||
			let msg = serde_json::to_string(&JMsg::Clear).unwrap();
 | 
			
		||||
			socket.send(Message::Text(msg))
 | 
			
		||||
			    .await.unwrap();
 | 
			
		||||
		    },
 | 
			
		||||
		    msg => {
 | 
			
		||||
			tracing::info!("{who} should not get this: {:?}", msg)
 | 
			
		||||
		    }
 | 
			
		||||
		}
 | 
			
		||||
		process_gs_msg(&mut socket, &who, msg).await
 | 
			
		||||
	    },
 | 
			
		||||
	    else => {
 | 
			
		||||
		tracing::warn!("{who}: Connection lost unexpectedly.");
 | 
			
		||||
		return;
 | 
			
		||||
		break;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn process_gs_msg(socket: &mut WebSocket, who: &SocketAddr, msg: GSMsg) {
 | 
			
		||||
    match msg {
 | 
			
		||||
	GSMsg::NewLine(line) => {
 | 
			
		||||
	    socket.send(Message::Text(line_to_json(&line))).await.unwrap();
 | 
			
		||||
	},
 | 
			
		||||
	GSMsg::Clear => {
 | 
			
		||||
	    let msg = serde_json::to_string(&JMsg::Clear).unwrap();
 | 
			
		||||
	    socket.send(Message::Text(msg)).await.unwrap();
 | 
			
		||||
	},
 | 
			
		||||
	msg => {
 | 
			
		||||
	    tracing::info!("{who} should not get this: {:?}", msg);
 | 
			
		||||
	}
 | 
			
		||||
    }   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn process_ws_msg(
 | 
			
		||||
    state: &Arc<Mutex<State>>,
 | 
			
		||||
    gs_tx: &Sender<GSMsg>,
 | 
			
		||||
    who: &SocketAddr,
 | 
			
		||||
    line: &mut Line,
 | 
			
		||||
    msg: Result<Message,axum::Error>
 | 
			
		||||
    msg: Message
 | 
			
		||||
) -> ControlFlow<(),()> {
 | 
			
		||||
    match msg {
 | 
			
		||||
	Ok(Text(msg)) => {
 | 
			
		||||
	    let Ok(msg) : Result<JMsg,_> = serde_json::from_str(&msg) else {
 | 
			
		||||
		tracing::warn!("{who}: Can't parse JSON: {:?}", msg);
 | 
			
		||||
		return ControlFlow::Continue(());
 | 
			
		||||
	    };
 | 
			
		||||
	    tracing::debug!("{who}: '{:?}'", msg);
 | 
			
		||||
	    match msg {
 | 
			
		||||
		JMsg::Clear => {
 | 
			
		||||
		    state.lock()
 | 
			
		||||
			.await
 | 
			
		||||
			.gs_tx.send(GSMsg::Clear)
 | 
			
		||||
			.await.unwrap();			
 | 
			
		||||
		    line.clear();
 | 
			
		||||
		},
 | 
			
		||||
		JMsg::MoveTo { x, y, color } => {
 | 
			
		||||
		    *line = vec![ (x, y, parse_color(color)) ];
 | 
			
		||||
		},
 | 
			
		||||
		JMsg::LineTo { x, y, color } => {
 | 
			
		||||
		    line.push( (x, y, parse_color(color)) );
 | 
			
		||||
		},
 | 
			
		||||
		JMsg::Stroke => {
 | 
			
		||||
		    if line.len() > 1 {
 | 
			
		||||
			state.lock()
 | 
			
		||||
			    .await
 | 
			
		||||
			    .gs_tx.send(GSMsg::NewLine(simplify_line(line)))
 | 
			
		||||
			    .await.unwrap();		
 | 
			
		||||
	Text(text) => {
 | 
			
		||||
	    match serde_json::from_str(&text) {
 | 
			
		||||
		Ok(json) => {
 | 
			
		||||
		    tracing::debug!("{who}: '{:?}'", json);
 | 
			
		||||
		    match handle_ws_msg(line, json) {
 | 
			
		||||
			Ok(Some(req)) => gs_tx.send(req).await.unwrap(),
 | 
			
		||||
			Ok(None) => {},
 | 
			
		||||
			Err(err) => {
 | 
			
		||||
			    tracing::warn!("{who}: message error: {err}");
 | 
			
		||||
			}
 | 
			
		||||
		    }
 | 
			
		||||
		    *line = vec![];
 | 
			
		||||
		},
 | 
			
		||||
		JMsg::Line{..} => { panic!("recieved a line message :/"); }
 | 
			
		||||
		Err(err) => {
 | 
			
		||||
		    tracing::warn!("{who}: can't parse JSON: {err}");
 | 
			
		||||
		}
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
	Ok(Close(close)) => {
 | 
			
		||||
	Close(close) => {
 | 
			
		||||
	    tracing::info!("{who}: closing: {:?}", close);
 | 
			
		||||
	    state.lock()
 | 
			
		||||
		.await
 | 
			
		||||
		.gs_tx.send(GSMsg::DeleteClient(*who))
 | 
			
		||||
		.await.unwrap();		
 | 
			
		||||
	    gs_tx.send(GSMsg::DeleteClient(*who)).await.unwrap();
 | 
			
		||||
	    return ControlFlow::Break(());
 | 
			
		||||
	},
 | 
			
		||||
	_ => {
 | 
			
		||||
	    tracing::warn!("{who}: Can't handle message: {:?}", msg);
 | 
			
		||||
	    tracing::warn!("{who}: can't handle message: {:?}", msg);
 | 
			
		||||
	}
 | 
			
		||||
    }
 | 
			
		||||
    ControlFlow::Continue(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn simplify_line(line: &Line) -> Line {
 | 
			
		||||
    if line.len() < 2 {
 | 
			
		||||
	return line.to_vec();
 | 
			
		||||
    }
 | 
			
		||||
    let color = line[0].2;
 | 
			
		||||
    let linestring : geo::LineString =
 | 
			
		||||
	line.iter()
 | 
			
		||||
	.map(| (x, y, _) | (*x as f64, *y as f64 ))
 | 
			
		||||
	.collect();
 | 
			
		||||
    let linestring = linestring.simplify(&4.0);
 | 
			
		||||
    linestring.0.iter()
 | 
			
		||||
	.map(| c | (c.x as f32, c.y as f32, color))
 | 
			
		||||
	.collect()
 | 
			
		||||
fn handle_ws_msg(
 | 
			
		||||
    line: &mut Line,
 | 
			
		||||
    msg: JMsg
 | 
			
		||||
) -> Result<Option<GSMsg>, &'static str> {
 | 
			
		||||
    match msg {
 | 
			
		||||
	JMsg::Clear => {
 | 
			
		||||
	    line.clear();
 | 
			
		||||
	    return Ok(Some(GSMsg::Clear));
 | 
			
		||||
	},
 | 
			
		||||
	JMsg::MoveTo { x, y, color } => {
 | 
			
		||||
	    *line = vec![ (x, y, parse_color(color)?) ];
 | 
			
		||||
	},
 | 
			
		||||
	JMsg::LineTo { x, y, color } => {
 | 
			
		||||
	    line.push( (x, y, parse_color(color)?) );
 | 
			
		||||
	},
 | 
			
		||||
	JMsg::Stroke => {
 | 
			
		||||
	    if line.len() > 1 {
 | 
			
		||||
		let line2 = simplify_line(line);
 | 
			
		||||
		*line = vec![];		
 | 
			
		||||
		return Ok(Some(GSMsg::NewLine(line2)));
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
	JMsg::Line{..} => {
 | 
			
		||||
	    tracing::warn!("recieved a line message O_o");
 | 
			
		||||
	}
 | 
			
		||||
    };
 | 
			
		||||
    Ok(None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn line_to_json(line: &Line) -> String {
 | 
			
		||||
    let line = line.iter()
 | 
			
		||||
	.map(| (x, y, c) | {
 | 
			
		||||
@ -156,6 +142,10 @@ fn line_to_json(line: &Line) -> String {
 | 
			
		||||
    serde_json::to_string(&JMsg::Line{ line }).unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_color(s: String) -> u32 {
 | 
			
		||||
    u32::from_str_radix(&s[1..], 16).unwrap()
 | 
			
		||||
fn parse_color(s: String) -> Result<u32, &'static str> {
 | 
			
		||||
    if s.len() != 7 || &s[0..1] != "#" {
 | 
			
		||||
	Err("badly formated color.")
 | 
			
		||||
    } else {
 | 
			
		||||
	u32::from_str_radix(&s[1..], 16).map_err(|_| "unable to parse color")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user