diff options
-rw-r--r-- | game.js | 11 | ||||
-rw-r--r-- | server/.gitignore | 1 | ||||
-rw-r--r-- | server/Cargo.lock | 202 | ||||
-rw-r--r-- | server/Cargo.toml | 2 | ||||
-rw-r--r-- | server/src/main.rs | 69 |
5 files changed, 202 insertions, 83 deletions
@@ -2,11 +2,15 @@ window.addEventListener('load', function () { const socket = new WebSocket("ws://localhost:3000"); socket.binaryType = "arraybuffer"; - socket.addEventListener('open', (e) => { - socket.send('Hello!'); + let imageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/09/Croatia_Opatija_Maiden_with_the_Seagull_BW_2014-10-10_10-35-13.jpg"; + let puzzleWidth = 4; + let puzzleHeight = 3; + socket.addEventListener('open', () => { + socket.send(`new ${puzzleWidth} ${puzzleHeight} ${imageUrl}`); }); socket.addEventListener('message', (e) => { console.log(e.data); + setTimeout(() => socket.send('poll'), 1000); }); const getById = (id) => document.getElementById(id); const playArea = getById("play-area"); @@ -17,9 +21,6 @@ window.addEventListener('load', function () { let pieceZIndexCounter = 1; let draggingPiece = null; let nibSize = 12; - let puzzleWidth = 4; - let puzzleHeight = 3; - let imageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/09/Croatia_Opatija_Maiden_with_the_Seagull_BW_2014-10-10_10-35-13.jpg"; let pieceWidth = 70; let pieceHeight; document.body.style.setProperty('--image', `url("${imageUrl}")`);// TODO : escaping diff --git a/server/.gitignore b/server/.gitignore index eb5a316..4a547fc 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1 +1,2 @@ target +database.sled diff --git a/server/Cargo.lock b/server/Cargo.lock index 4de3dad..6e02d34 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -18,18 +18,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -58,9 +46,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -105,6 +93,30 @@ dependencies = [ ] [[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -131,24 +143,22 @@ dependencies = [ ] [[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -193,6 +203,15 @@ dependencies = [ ] [[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -220,24 +239,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown", -] - -[[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -261,6 +262,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -273,7 +283,7 @@ dependencies = [ "anyhow", "futures-util", "rand", - "rusqlite", + "sled", "tokio", "tokio-tungstenite", "tungstenite", @@ -286,13 +296,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] -name = "libsqlite3-sys" -version = "0.30.1" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "pkg-config", - "vcpkg", + "autocfg", + "scopeguard", ] [[package]] @@ -338,10 +348,29 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "parking_lot" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] [[package]] name = "pin-project-lite" @@ -356,12 +385,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -419,17 +442,12 @@ dependencies = [ ] [[package]] -name = "rusqlite" -version = "0.32.1" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", ] [[package]] @@ -439,6 +457,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -459,6 +483,22 @@ dependencies = [ ] [[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + +[[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -581,12 +621,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -599,6 +633,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/server/Cargo.toml b/server/Cargo.toml index 2da818a..8ef7d7b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" anyhow = "1.0.86" futures-util = "0.3" rand = { version = "0.8.5", features = ["std", "std_rng"] } -rusqlite = "0.32.1" +sled = "0.34.7" tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread", "net", "io-util", "sync", "time"] } tokio-tungstenite = "0.23.1" tungstenite = "0.23.0" diff --git a/server/src/main.rs b/server/src/main.rs index d879ae6..81f2581 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,8 +2,24 @@ use futures_util::{SinkExt, StreamExt}; use std::net::SocketAddr; use tokio::io::AsyncWriteExt; use tungstenite::protocol::Message; +use rand::Rng; +use std::sync::LazyLock; +use anyhow::anyhow; -async fn handle_connection(conn: &mut tokio::net::TcpStream) -> anyhow::Result<()> { +const PUZZLE_ID_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const PUZZLE_ID_LEN: usize = 6; + +fn generate_puzzle_id() -> [u8; PUZZLE_ID_LEN] { + let mut rng = rand::thread_rng(); + [(); 6].map(|()| PUZZLE_ID_CHARSET[rng.gen_range(0..PUZZLE_ID_CHARSET.len())]) +} + +struct Database { + puzzles: sled::Tree, + pieces: sled::Tree, +} + +async fn handle_connection(database: &Database, conn: &mut tokio::net::TcpStream) -> anyhow::Result<()> { let mut ws = tokio_tungstenite::accept_async_with_config( conn, Some(tungstenite::protocol::WebSocketConfig { @@ -13,13 +29,48 @@ async fn handle_connection(conn: &mut tokio::net::TcpStream) -> anyhow::Result<( }), ) .await?; + let mut puzzle_id = None; while let Some(message) = ws.next().await { let message = message?; if matches!(message, Message::Close(_)) { break; } - println!("{:?}", message); - ws.send(message).await?; + if let Message::Text(text) = &message { + let text = text.trim(); + if let Some(dimensions) = text.strip_prefix("new ") { + let mut parts = dimensions.split(' '); + let width: u8 = parts.next().ok_or_else(|| anyhow!("no width"))?.parse()?; + let height: u8 = parts.next().ok_or_else(|| anyhow!("no height"))?.parse()?; + let url: &str = parts.next().ok_or_else(|| anyhow!("no url"))?; + if (width as u16) * (height as u16) > 1000 { + return Err(anyhow!("too many pieces")); + } + let mut puzzle_data = vec![width, height]; + puzzle_data.extend(url.as_bytes()); + let mut id; + loop { + id = generate_puzzle_id(); + let data = std::mem::take(&mut puzzle_data); + if database.puzzles.compare_and_swap(id, None::<&'static [u8; 0]>, Some(&data[..]))?.is_ok() { + break; + } + } + drop(puzzle_data); // should be empty now + puzzle_id = Some(id); + let pieces_data: Vec<u8>; + { + let mut rng = rand::thread_rng(); + pieces_data = (0..(width as u16) * (height as u16) * 4).map(|_| rng.gen()).collect(); + } + database.pieces.insert(id, pieces_data)?; + ws.send(Message::Text(format!("id: {}", std::str::from_utf8(&id)?))).await?; + } else if text == "poll" { + let puzzle_id = puzzle_id.ok_or_else(|| anyhow!("poll without puzzle ID"))?; + let pieces = database.pieces.get(&puzzle_id)?.ok_or_else(|| anyhow!("bad puzzle ID: {puzzle_id:?}"))?; + let pieces = pieces.to_vec(); + ws.send(Message::Binary(pieces)).await?; + } + } } Ok(()) } @@ -41,6 +92,16 @@ async fn main() { tokio::time::sleep(std::time::Duration::from_secs(3600)).await; } }); + static DATABASE_VALUE: LazyLock<Database> = LazyLock::new(|| { + let db = sled::open("database.sled").expect("error opening database"); + let puzzles = db.open_tree("PUZZLES").expect("error opening puzzles tree"); + let pieces = db.open_tree("PIECES").expect("error opening pieces tree"); + Database { + puzzles, + pieces + } + }); + let database: &Database = &DATABASE_VALUE; loop { let (mut stream, addr) = match listener.accept().await { Ok(result) => result, @@ -50,7 +111,7 @@ async fn main() { } }; tokio::task::spawn(async move { - match handle_connection(&mut stream).await { + match handle_connection(database, &mut stream).await { Ok(()) => {} Err(e) => { eprintln!("Error handling connection to {addr}: {e}"); |