library

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit f7b8519646a7ea44b3d9d375bc2f7ce225694070
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Fri, 14 Aug 2026 22:47:43 -0400

Add basic multiplayer lobby

Diffstat:
A.gitignore | 1+
ACargo.lock | 483+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 3+++
Aclient/Cargo.lock | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/Cargo.toml | 13+++++++++++++
Aclient/assets/books.png | 0
Aclient/assets/meeple.png | 0
Aclient/src/game.rs | 27+++++++++++++++++++++++++++
Aclient/src/main.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/src/net.rs | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/src/render.rs | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon/Cargo.toml | 10++++++++++
Acommon/src/codec.rs | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon/src/game.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Acommon/src/lib.rs | 2++
Aflake.lock | 27+++++++++++++++++++++++++++
Aflake.nix | 41+++++++++++++++++++++++++++++++++++++++++
Aserver/Cargo.toml | 12++++++++++++
Aserver/src/main.rs | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver/src/net.rs | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 1640 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,483 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "macroquad", + "mio", + "postcard", + "serde", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "anyhow", + "glam", + "postcard", + "serde", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fontdue" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7894823fa221401399e2598f8b63f81ac77ff5c63248b7656779bff1632d7d3d" +dependencies = [ + "hashbrown", + "ttf-parser", +] + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" +dependencies = [ + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "macroquad" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbd54d99d4d3ac59deed7ee4927bbbf4eb33a55635530af3be9395a55746090" +dependencies = [ + "fontdue", + "glam", + "image", + "macroquad_macro", + "miniquad", + "quad-rand", +] + +[[package]] +name = "macroquad_macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b1d96218903768c1ce078b657c0d5965465c95a60d2682fd97443c9d2483dd" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "miniquad" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbaea978d741f3b60ced666b06a2a1e4a2bc81cb8b9dd29f4a40965e1f59e65" +dependencies = [ + "libc", + "ndk-sys", + "objc-rs", + "winapi", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a1e7069a2525126bf12a9f1f7916835fafade384fb27cabf698e745e2a1eb8" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quad-rand" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "glam", + "mio", + "postcard", + "serde", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "3" +members = ["client", "common", "server"] diff --git a/client/Cargo.lock b/client/Cargo.lock @@ -0,0 +1,432 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fontdue" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7894823fa221401399e2598f8b63f81ac77ff5c63248b7656779bff1632d7d3d" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser", +] + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "foldhash 0.2.0", + "rayon", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "library" +version = "0.1.0" +dependencies = [ + "macroquad", + "shipyard", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "macroquad" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbd54d99d4d3ac59deed7ee4927bbbf4eb33a55635530af3be9395a55746090" +dependencies = [ + "fontdue", + "glam", + "image", + "macroquad_macro", + "miniquad", + "quad-rand", +] + +[[package]] +name = "macroquad_macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b1d96218903768c1ce078b657c0d5965465c95a60d2682fd97443c9d2483dd" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "miniquad" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbaea978d741f3b60ced666b06a2a1e4a2bc81cb8b9dd29f4a40965e1f59e65" +dependencies = [ + "libc", + "ndk-sys", + "objc-rs", + "winapi", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a1e7069a2525126bf12a9f1f7916835fafade384fb27cabf698e745e2a1eb8" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quad-rand" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shipyard" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced12597d87bb2a67f3c712a9cea816bd9f822a5a83883f59ec8805b7ceba6a" +dependencies = [ + "hashbrown 0.16.1", + "lock_api", + "rayon", + "shipyard_proc", +] + +[[package]] +name = "shipyard_proc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e09f75ea0c5902cedcbb5889628232eb49d6d09728d7cc1a3f1736fa4e7ce7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[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" diff --git a/client/Cargo.toml b/client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" +default-run = "client" + +[dependencies] +anyhow = "1.0.104" +common = { path = "../common/" } +macroquad = { version = "0.4.16", features = ["glam-serde"] } +mio = { version = "1.2.2", features = ["net", "os-poll"] } +postcard = { version = "1.1.3", default-features = false, features = ["use-std"] } +serde = { version = "1.0.229", features = ["derive"] } diff --git a/client/assets/books.png b/client/assets/books.png Binary files differ. diff --git a/client/assets/meeple.png b/client/assets/meeple.png Binary files differ. diff --git a/client/src/game.rs b/client/src/game.rs @@ -0,0 +1,27 @@ +use common::game::{PlayerAction, PlayerActionKind}; +use macroquad::prelude::*; + +pub(crate) fn handle_input(player_name: &str) -> Option<PlayerAction> { + let velocity_x = if is_key_down(KeyCode::A) { + -1. + } else if is_key_down(KeyCode::D) { + 1. + } else { + 0. + }; + let velocity_y = if is_key_down(KeyCode::W) { + -1. + } else if is_key_down(KeyCode::S) { + 1. + } else { + 0. + }; + if velocity_x != 0. || velocity_y != 0. { + Some(PlayerAction::new( + player_name.to_string(), + PlayerActionKind::Move(Vec2::new(velocity_x, velocity_y)), + )) + } else { + None + } +} diff --git a/client/src/main.rs b/client/src/main.rs @@ -0,0 +1,80 @@ +use std::{ + sync::mpsc::channel, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; + +use common::{ + codec::Codec, + game::{GameState, PlayerAction, PlayerActionKind}, +}; +use macroquad::{ + prelude::*, + rand::{gen_range, srand}, +}; + +use crate::{ + game::handle_input, + net::{OnShutdown, handle_connection}, + render::{Camera, follow_local_player, load_sprites, render}, +}; + +pub mod game; +pub mod net; +pub mod render; + +#[macroquad::main("Library")] +async fn main() { + request_new_screen_size(1280., 720.); + let spritesheet = load_sprites(); + + let (client_to_server_tx, client_to_server_rx) = channel(); + let (server_to_client_tx, server_to_client_rx) = channel(); + thread::spawn(|| { + handle_connection( + "127.0.0.1:6767".parse().unwrap(), + server_to_client_tx, + client_to_server_rx, + ) + }); + + // seed the rng with current nanosecond timestamp to get (probably) different output on each run + srand( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos() as u64, + ); + + // choose a random three-letter lowercase string as our player name (for now) + let local_player_name = (0..3) + .map(|_| gen_range('a' as u8, 'z' as u8)) + .fold(String::new(), |acc, el| acc + &(el as char).to_string()); + + let _ = client_to_server_tx.send(PlayerAction::new( + local_player_name.to_string(), + PlayerActionKind::Join, + )); + + let mut camera = Camera::default(); + let mut codec = Codec::default(); + let mut game = GameState::default(); + let _on_shutdown = OnShutdown::new(client_to_server_tx.clone(), local_player_name.to_string()); + + loop { + let bytes: Vec<u8> = server_to_client_rx.try_iter().flatten().collect(); + if let Some(decoded) = codec.decode(&bytes).last() { + if let Ok(g) = postcard::from_bytes::<GameState>(decoded) { + game = g; + } + } + if let Some(action) = handle_input(&local_player_name) { + if let Err(_) = client_to_server_tx.send(action) { + return; + } + } + follow_local_player(&mut camera, &game, &local_player_name); + render(&camera, &game, &spritesheet); + next_frame().await; + } +} diff --git a/client/src/net.rs b/client/src/net.rs @@ -0,0 +1,113 @@ +use std::{ + io::{self, Read, Write}, + net::SocketAddr, + sync::{ + Arc, Mutex, + mpsc::{Receiver, Sender}, + }, + thread, + time::Duration, +}; + +use common::{ + codec::Codec, + game::{PlayerAction, PlayerActionKind}, +}; +use mio::{Events, Interest, Poll, Token, net::TcpStream}; + +const TOKEN: Token = Token(0); + +pub(crate) fn handle_connection(addr: SocketAddr, tx: Sender<Vec<u8>>, rx: Receiver<PlayerAction>) { + let mut stream = TcpStream::connect(addr).unwrap(); + let poll = Poll::new().unwrap(); + poll.registry() + .register(&mut stream, TOKEN, Interest::READABLE) + .unwrap(); + let shared_stream = Arc::new(Mutex::new(stream)); + let shared_stream_read = shared_stream.clone(); + let shared_stream_write = shared_stream.clone(); + let read_thread = thread::spawn(|| handle_server_read(poll, shared_stream_read, tx)); + let write_thread = thread::spawn(|| handle_server_write(shared_stream_write, rx)); + while !read_thread.is_finished() && !write_thread.is_finished() { + thread::sleep(Duration::from_millis(500)); + } +} + +pub(crate) fn handle_server_read( + mut poll: Poll, + stream: Arc<Mutex<TcpStream>>, + tx: Sender<Vec<u8>>, +) { + let mut events = Events::with_capacity(128); + let mut buffer = [0x00; 1024]; + let mut buffer_index; + loop { + // need an event loop here because otherwise this thread would hold a permanent lock on the + // stream while blocking on reading it + poll.poll(&mut events, None).unwrap(); + for event in events.iter() { + if event.is_readable() { + buffer_index = stream.lock().unwrap().read(&mut buffer).unwrap(); + if let Err(_) = tx.send(buffer[..buffer_index].to_vec()) { + break; + } + } + } + } +} + +fn handle_server_write(stream: Arc<Mutex<TcpStream>>, rx: Receiver<PlayerAction>) { + let mut all_sent; + loop { + match rx.recv() { + Ok(m) => match postcard::to_stdvec(&m) { + Ok(to_write) => { + all_sent = false; + while !all_sent { + match stream + .lock() + .unwrap() + .write_all(Codec::encode(&to_write).as_slice()) + { + Ok(_) => all_sent = true, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + all_sent = false; + } + Err(e) if e.kind() == io::ErrorKind::NotConnected => { + all_sent = false; + } + Err(_) => { + return; + } + } + } + } + Err(_) => (), + }, + Err(_) => return, + } + } +} + +pub(crate) struct OnShutdown { + tx: Sender<PlayerAction>, + local_player_name: String, +} + +impl OnShutdown { + pub(crate) fn new(tx: Sender<PlayerAction>, local_player_name: String) -> Self { + OnShutdown { + tx, + local_player_name, + } + } +} + +impl Drop for OnShutdown { + fn drop(&mut self) { + let _ = self.tx.send(PlayerAction::new( + self.local_player_name.clone(), + PlayerActionKind::Leave, + )); + } +} diff --git a/client/src/render.rs b/client/src/render.rs @@ -0,0 +1,85 @@ +use common::game::{GameState, SpriteIndex}; +use macroquad::prelude::*; + +#[derive(Debug, Default)] +pub(crate) struct Camera { + position: Vec2, +} + +#[derive(Debug)] +pub(crate) struct Spritesheet { + meeple: Sprite, +} + +#[derive(Debug, Clone)] +pub(crate) struct Sprite { + texture: Texture2D, + size: Vec2, +} + +impl Sprite { + fn render(&self, position: Vec2) { + draw_texture_ex( + &self.texture, + position.x, + position.y, + WHITE, + DrawTextureParams { + dest_size: Some(self.size), + ..Default::default() + }, + ); + } +} + +impl From<Image> for Sprite { + fn from(value: Image) -> Self { + const TEXTURE_SCALE: f32 = 10.; + let texture = Texture2D::from_image(&value); + let size = Vec2::new( + texture.width() / TEXTURE_SCALE, + texture.height() / TEXTURE_SCALE, + ); + Sprite { texture, size } + } +} + +pub(crate) fn load_sprites() -> Spritesheet { + Spritesheet { + meeple: Image::from_file_with_format( + include_bytes!("../assets/meeple.png"), + Some(ImageFormat::Png), + ) + .unwrap() + .into(), + } +} + +pub(crate) fn render(camera: &Camera, game: &GameState, spritesheet: &Spritesheet) { + clear_background(MAGENTA); + for (name, player) in game.players.iter() { + let sprite = match player.sprite_index { + SpriteIndex::Meeple => spritesheet.meeple.clone(), + }; + let position_camera = player.position - camera.position; + sprite.render(position_camera); + draw_text( + name, + position_camera.x + 12., + position_camera.y + sprite.size.y + 16., + 16., + BLACK, + ); + } +} + +pub(crate) fn follow_local_player(camera: &mut Camera, game: &GameState, local_player_name: &str) { + let screen_center = Vec2::new(screen_width() / 2., screen_height() / 2.); + if let Some((_, player)) = game + .players + .iter() + .find(|(name, _)| *name == local_player_name) + { + camera.position = player.position - screen_center; + } +} diff --git a/common/Cargo.toml b/common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +glam = { version = "0.27.0", features = ["scalar-math", "serde"] } +postcard = { version = "1.1.3", default-features = false, features = ["use-std"] } +serde = { version = "1.0.229", features = ["derive"] } diff --git a/common/src/codec.rs b/common/src/codec.rs @@ -0,0 +1,65 @@ +#[derive(Default, Debug)] +enum State { + #[default] + Start, + Data(u8), + Escape, + End, +} + +impl State { + const DELIMITER: u8 = 0xff; + const ESCAPE: u8 = 0xfe; + fn next(&mut self, input: u8) { + *self = match (input, &self) { + (Self::DELIMITER, State::Data(_) | State::Start | State::End) => State::End, + (Self::ESCAPE, State::Data(_) | State::Start | State::End) => State::Escape, + (d, _) => State::Data(d), + }; + } +} + +#[derive(Default, Debug)] +pub struct Codec { + state: State, + message: Vec<u8>, +} + +impl Codec { + fn next(&mut self, input: u8) -> Option<Vec<u8>> { + self.state.next(input); + match self.state { + State::Data(d) => { + self.message.push(d); + None + } + State::End => { + let complete = self.message.clone(); + self.message.clear(); + Some(complete) + } + State::Start | State::Escape => None, + } + } + pub fn decode(&mut self, input: &[u8]) -> Vec<Vec<u8>> { + let mut output = vec![]; + for byte in input { + if let Some(message) = self.next(*byte) { + output.push(message); + } + } + output + } + pub fn encode(input: &[u8]) -> Vec<u8> { + let mut output = vec![]; + for byte in input { + match *byte { + State::DELIMITER => output.extend_from_slice(&[State::ESCAPE, State::DELIMITER]), + State::ESCAPE => output.extend_from_slice(&[State::ESCAPE, State::ESCAPE]), + b => output.push(b), + } + } + output.push(State::DELIMITER); + output + } +} diff --git a/common/src/game.rs b/common/src/game.rs @@ -0,0 +1,50 @@ +use std::{collections::HashMap, time::SystemTime}; + +use glam::Vec2; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct GameState { + pub timestamp: SystemTime, + pub players: HashMap<String, Player>, +} + +impl Default for GameState { + fn default() -> Self { + Self { + timestamp: SystemTime::now(), + players: Default::default(), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Player { + pub name: String, + pub position: Vec2, + pub sprite_index: SpriteIndex, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct PlayerAction { + pub player_name: String, + pub kind: PlayerActionKind, +} + +impl PlayerAction { + pub fn new(player_name: String, kind: PlayerActionKind) -> Self { + PlayerAction { player_name, kind } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum PlayerActionKind { + Join, + Move(Vec2), + Leave, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SpriteIndex { + Meeple, +} diff --git a/common/src/lib.rs b/common/src/lib.rs @@ -0,0 +1,2 @@ +pub mod codec; +pub mod game; diff --git a/flake.lock b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "root": { + "inputs": { + "stable-pkgs": "stable-pkgs" + } + }, + "stable-pkgs": { + "locked": { + "lastModified": 1785989512, + "narHash": "sha256-HFQhkQcl5D1hUNoen3SGHCSFCt2Bg6uP+HgbrnA3InQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "445d861c6d31b4af0c79d8d4be2331f762a361d7", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix @@ -0,0 +1,41 @@ +{ + inputs = { + stable-pkgs.url = "github:nixos/nixpkgs?ref=nixos-26.05"; + }; + + outputs = + { self, stable-pkgs }: + let + system = "x86_64-linux"; + stable = import stable-pkgs { + inherit system; + config = { }; + overlays = [ ]; + }; + libInputs = with stable; [ + libX11 + libxkbcommon + libXi + libXcursor + libXrandr + libXxf86vm + libGL + ]; + lib-path = with stable; lib.makeLibraryPath libInputs; + in + { + devShells.${system}.default = stable.mkShellNoCC { + packages = with stable; [ + cargo + clippy + rust-analyzer + rustc + rustfmt + ]; + + shellHook = '' + export "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${lib-path}" + ''; + }; + }; +} diff --git a/server/Cargo.toml b/server/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +common = { path = "../common/" } +glam = { version = "0.27.0", features = ["scalar-math", "serde"] } +mio = { version = "1.2.2", features = ["net", "os-poll"] } +postcard = { version = "1.1.3", default-features = false, features = ["use-std"] } +serde = { version = "1.0.229", features = ["derive"] } diff --git a/server/src/main.rs b/server/src/main.rs @@ -0,0 +1,79 @@ +use std::{ + collections::LinkedList, + sync::{Arc, Mutex, mpsc::channel}, + thread, + time::{Duration, Instant}, +}; + +use common::{ + codec::Codec, + game::{GameState, Player, PlayerActionKind, SpriteIndex}, +}; +use glam::Vec2; +use postcard::to_stdvec; + +use crate::net::listen; + +pub mod net; + +const TICK_DURATION: Duration = Duration::from_millis(10); + +fn main() { + let handlers = Arc::new(Mutex::new(LinkedList::new())); + let (handler_to_main_tx, handler_to_main_rx) = channel(); + let handlers_clone = handlers.clone(); + let _listener = thread::spawn(|| listen(handlers_clone, handler_to_main_tx)); + let mut next_tick = Instant::now() + TICK_DURATION; + let mut game = GameState::default(); + loop { + let mut send_game_status = false; + + // Process all pending player actions + for player_action in handler_to_main_rx.try_iter() { + send_game_status = true; + match player_action.kind { + PlayerActionKind::Join => { + game.players.insert( + player_action.player_name.clone(), + Player { + name: player_action.player_name.clone(), + position: Vec2::ZERO, + sprite_index: SpriteIndex::Meeple, + }, + ); + } + PlayerActionKind::Move(vec2) => { + if let Some(player) = game.players.get_mut(&player_action.player_name) { + player.position += vec2; + } + } + PlayerActionKind::Leave => { + game.players.remove(&player_action.player_name); + } + } + } + + // Only bother to send the latest game status to clients if we really need to, instead of + // every tick + if send_game_status { + match handlers.lock() { + Ok(mut h) => { + let game_bytes = Codec::encode(&to_stdvec(&game).unwrap()); + for handler in h.iter() { + let _ = handler.tx.send(game_bytes.clone()); + } + h.extract_if(|handler| handler.thread.is_finished()) + .for_each(drop); + } + Err(_) => break, + } + } + + // Throttling + let now = Instant::now(); + if now < next_tick { + thread::sleep(next_tick - now); + } + next_tick = Instant::now() + TICK_DURATION; + } +} diff --git a/server/src/net.rs b/server/src/net.rs @@ -0,0 +1,117 @@ +use std::{ + collections::LinkedList, + io::{self, Read, Write}, + net::SocketAddr, + sync::{ + Arc, Mutex, + mpsc::{Receiver, Sender, channel}, + }, + thread::{self, JoinHandle}, + time::Duration, +}; + +use common::{codec::Codec, game::PlayerAction}; +use mio::{ + Events, Interest, Poll, Token, + net::{TcpListener, TcpStream}, +}; + +const TOKEN: Token = Token(0); + +#[derive(Debug)] +pub(crate) struct Handler { + _addr: SocketAddr, + pub thread: JoinHandle<()>, + pub tx: Sender<Vec<u8>>, +} + +pub(crate) fn listen(handlers: Arc<Mutex<LinkedList<Handler>>>, tx: Sender<PlayerAction>) { + let mut poll = Poll::new().unwrap(); + let mut events = Events::with_capacity(128); + let mut listener = TcpListener::bind("0.0.0.0:6767".parse().unwrap()).unwrap(); + poll.registry() + .register(&mut listener, TOKEN, Interest::READABLE) + .unwrap(); + loop { + poll.poll(&mut events, None).unwrap(); + for _ in events.iter() { + let (stream, _addr) = listener.accept().unwrap(); + let (main_to_handler_tx, main_to_handler_rx) = channel(); + if let Ok(addr) = stream.peer_addr() { + let tx_clone = tx.clone(); + let thread = + thread::spawn(|| handle_connection(stream, main_to_handler_rx, tx_clone)); + let handler = Handler { + _addr: addr, + thread, + tx: main_to_handler_tx.clone(), + }; + handlers.lock().unwrap().push_back(handler); + } + } + } +} + +fn handle_connection(mut stream: TcpStream, rx: Receiver<Vec<u8>>, tx: Sender<PlayerAction>) { + let poll = Poll::new().unwrap(); + poll.registry() + .register(&mut stream, TOKEN, Interest::READABLE) + .unwrap(); + let shared_stream = Arc::new(Mutex::new(stream)); + let shared_stream_read = shared_stream.clone(); + let shared_stream_write = shared_stream.clone(); + let read_thread = thread::spawn(|| handle_client_read(poll, shared_stream_read, tx)); + let write_thread = thread::spawn(|| handle_client_write(shared_stream_write, rx)); + while !read_thread.is_finished() && !write_thread.is_finished() { + thread::sleep(Duration::from_millis(500)); + } +} + +fn handle_client_read(mut poll: Poll, stream: Arc<Mutex<TcpStream>>, tx: Sender<PlayerAction>) { + let mut events = Events::with_capacity(128); + let mut codec = Codec::default(); + let mut buffer = [0x00; 1024]; + let mut buffer_index; + loop { + poll.poll(&mut events, None).unwrap(); + for event in events.iter() { + if event.is_readable() { + let mut stream = stream.lock().unwrap(); + match stream.read(&mut buffer) { + Ok(0) => return, + Ok(i) => { + buffer_index = i; + let actions = codec.decode(&buffer[..buffer_index]); + for action in actions { + match postcard::from_bytes(action.as_slice()) { + Ok(a) => { + if let Err(_) = tx.send(a) { + break; + } + } + Err(_) => (), + } + } + } + Err(e) => match e.kind() { + io::ErrorKind::WouldBlock => (), + _ => return, + }, + } + } + } + } +} + +fn handle_client_write(stream: Arc<Mutex<TcpStream>>, rx: Receiver<Vec<u8>>) { + loop { + match rx.recv() { + Ok(bytes) => { + if let Err(_) = stream.lock().unwrap().write_all(&bytes) { + return; + } + } + Err(_) => return, + } + } +}