diff options
author | metamuffin <yvchraiqi@protonmail.com> | 2022-06-09 09:46:00 +0200 |
---|---|---|
committer | metamuffin <yvchraiqi@protonmail.com> | 2022-06-09 09:46:00 +0200 |
commit | 7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0 (patch) | |
tree | 5586745b7a9b871b31512cba9f964dabda4651f0 | |
download | twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.bz2 twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.zst |
(reset git)
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Cargo.lock | 2395 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | client/Cargo.toml | 48 | ||||
-rw-r--r-- | client/src/client/helper.rs | 87 | ||||
-rw-r--r-- | client/src/client/mod.rs | 463 | ||||
-rw-r--r-- | client/src/lib.rs | 21 | ||||
-rw-r--r-- | client/src/main.rs | 41 | ||||
-rw-r--r-- | client/src/world/map.rs | 135 | ||||
-rw-r--r-- | client/src/world/mod.rs | 132 | ||||
-rw-r--r-- | renderer/Cargo.toml | 18 | ||||
-rw-r--r-- | renderer/src/main.rs | 254 | ||||
-rw-r--r-- | renderer/src/map.rs | 163 | ||||
-rw-r--r-- | snapshot/Cargo.toml | 27 | ||||
-rw-r--r-- | snapshot/src/format.rs | 85 | ||||
-rw-r--r-- | snapshot/src/lib.rs | 36 | ||||
-rw-r--r-- | snapshot/src/manager.rs | 140 | ||||
-rw-r--r-- | snapshot/src/receiver.rs | 245 | ||||
-rw-r--r-- | snapshot/src/snap.rs | 511 | ||||
-rw-r--r-- | snapshot/src/storage.rs | 176 |
20 files changed, 4984 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..992fa97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/client/maps +/client/downloading +/maps +/downloading
\ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..538fd5e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2395 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +dependencies = [ + "memchr 0.1.11", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr 2.4.1", +] + +[[package]] +name = "android_glue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger 0.9.0", + "lazy_static", + "lazycell", + "log 0.4.17", + "peeking_take_while", + "proc-macro2", + "quote", + "regex 1.5.5", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "buffer" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab7228d32b5d95be40adeba1d9461c8547b5dadf5f9cbfba09b6d578991df28" +dependencies = [ + "arrayvec", + "mac", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log 0.4.17", + "nix", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clang-sys" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.3", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "common" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "file_offset", + "num-traits 0.1.43", + "ref_slice", + "unreachable", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.3", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "datafile" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "common", + "hexdump", + "itertools 0.4.19", + "log 0.3.9", + "logger", + "zlib_minimal", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" +dependencies = [ + "log 0.3.9", + "regex 0.1.80", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log 0.4.17", + "regex 1.5.5", + "termcolor", +] + +[[package]] +name = "event_loop" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "common", + "hexdump", + "itertools 0.4.19", + "log 0.3.9", + "logger", + "net", + "socket", + "warn", +] + +[[package]] +name = "file_offset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad02ed760f100d78f087138613184d8e48d301c97f13804e250773d649900cd" + +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.13", + "winapi 0.3.9", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "gamenet_common" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "packer", + "serde", + "serde_derive", + "uuid", + "warn", +] + +[[package]] +name = "gamenet_ddnet" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "gamenet_common", + "packer", + "uuid", + "warn", +] + +[[package]] +name = "gamenet_teeworlds_0_5" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "gamenet_common", + "packer", + "uuid", + "warn", +] + +[[package]] +name = "gamenet_teeworlds_0_6" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "gamenet_common", + "packer", + "uuid", + "warn", +] + +[[package]] +name = "gamenet_teeworlds_0_7" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "gamenet_common", + "packer", + "uuid", + "warn", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log 0.4.17", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glutin" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2" +dependencies = [ + "android_glue", + "cgl", + "cocoa", + "core-foundation 0.9.3", + "glutin_egl_sys", + "glutin_emscripten_sys", + "glutin_gles2_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "lazy_static", + "libloading", + "log 0.4.17", + "objc", + "osmesa-sys", + "parking_lot", + "wayland-client", + "wayland-egl", + "winapi 0.3.9", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" +dependencies = [ + "gl_generator", + "winapi 0.3.9", +] + +[[package]] +name = "glutin_emscripten_sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" + +[[package]] +name = "glutin_gles2_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" +dependencies = [ + "gl_generator", + "objc", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexdump" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40283dadb02f3af778878be1d717b17b4e4ab92e1d935ab03a730b0542905f2" +dependencies = [ + "arrayvec", + "itertools 0.4.19", +] + +[[package]] +name = "huffman" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "itertools 0.4.19", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" + +[[package]] +name = "itertools" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "logger" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "env_logger 0.3.5", +] + +[[package]] +name = "mac" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1db08c0d0ddbb591e65f1da58d1cefccc94a2faa0c55bf979ce215a3e04d5e" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "map" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "common", + "datafile", + "ndarray", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matrixmultiply" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log 0.4.17", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "ndarray" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0603e041601804a61a33ebecbea220b43a86437b9ee20680a1f9fdaaa922a8" +dependencies = [ + "itertools 0.6.5", + "matrixmultiply", + "num-complex", + "num-traits 0.1.43", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-glue" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" +dependencies = [ + "lazy_static", + "libc", + "log 0.4.17", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "net" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "assert_matches", + "buffer", + "common", + "huffman", + "linear-map", + "matches", + "optional", + "void", + "warn", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr 2.4.1", + "minimal-lexical", +] + +[[package]] +name = "num-complex" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" +dependencies = [ + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "optional" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf72fc7f93afa4f42298f870abcc10ec1ab116198985719638bfe37535170092" + +[[package]] +name = "osmesa-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" +dependencies = [ + "shared_library", +] + +[[package]] +name = "packer" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "arrayvec", + "buffer", + "common", + "uuid", + "warn", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.13", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", +] + +[[package]] +name = "rawpointer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref_slice" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ed1d73fb92eba9b841ba2aef69533a060ccc0d3ec71c90aeda5996d4afb7a9" + +[[package]] +name = "regex" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +dependencies = [ + "aho-corasick 0.5.3", + "memchr 0.1.11", + "regex-syntax 0.3.9", + "thread_local", + "utf8-ranges", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick 0.7.18", + "memchr 2.4.1", + "regex-syntax 0.6.25", +] + +[[package]] +name = "regex-syntax" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log 0.4.17", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "skia-bindings" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f45439145d90b18bce3dc0bfb50c73b25a75666ac391089b23e4ee245a7f67" +dependencies = [ + "bindgen", + "cc", + "flate2", + "heck", + "lazy_static", + "regex 1.5.5", + "serde_json", + "tar", + "toml", + "ureq", +] + +[[package]] +name = "skia-safe" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e8f14ca7a6e7338e7cf6bfdad314c4e735003c1a56001e1e545bd22c994d43" +dependencies = [ + "bitflags", + "lazy_static", + "skia-bindings", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log 0.4.17", + "memmap2", + "nix", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "snapshot" +version = "0.0.1" +dependencies = [ + "buffer", + "common", + "gamenet_ddnet", + "gamenet_teeworlds_0_5", + "gamenet_teeworlds_0_6", + "gamenet_teeworlds_0_7", + "packer", + "vec_map", + "warn", +] + +[[package]] +name = "socket" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "buffer", + "common", + "hexdump", + "itertools 0.4.19", + "libc", + "log 0.3.9", + "mio 0.6.23", + "net", + "net2", + "rand 0.8.5", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0" +dependencies = [ + "kernel32-sys", + "libc", + "rand 0.3.23", + "redox_syscall 0.1.57", + "winapi 0.2.8", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +dependencies = [ + "kernel32-sys", + "libc", +] + +[[package]] +name = "thread_local" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +dependencies = [ + "thread-id", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "twclient" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "clap", + "common", + "crossbeam-channel", + "datafile", + "env_logger 0.9.0", + "event_loop", + "gamenet_ddnet", + "gamenet_teeworlds_0_5", + "gamenet_teeworlds_0_6", + "gamenet_teeworlds_0_7", + "hexdump", + "itertools 0.4.19", + "lazy_static", + "log 0.3.9", + "logger", + "map", + "ndarray", + "net", + "packer", + "rand 0.8.5", + "signal-hook", + "snapshot", + "socket", + "tempfile", + "warn", +] + +[[package]] +name = "twrenderer" +version = "0.1.0" +dependencies = [ + "env_logger 0.9.0", + "gl", + "glutin", + "log 0.4.17", + "signal-hook", + "skia-safe", + "twclient", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" +dependencies = [ + "base64", + "chunked_transfer", + "flate2", + "log 0.4.17", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "warn" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2638072850e7fde2e4e96d78204024000382e5666876bd8a3e7e9db6ed22cb" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.17", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-egl" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a" +dependencies = [ + "wayland-client", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[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-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[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.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.3", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log 0.4.17", + "mio 0.8.3", + "ndk", + "ndk-glue", + "ndk-sys", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi 0.3.9", + "x11-dl", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zlib_minimal" +version = "0.0.1" +source = "git+https://github.com/heinrich5991/libtw2#c67bb33d7f2dc5bd78ea8238ec6119b754c13da3" +dependencies = [ + "libc", + "libz-sys", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..19cae09 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["client","snapshot","renderer"]
\ No newline at end of file diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000..5f14df4 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "twclient" +version = "0.1.0" +edition = "2021" +authors = [ + "metamuffin <metamuffin@disroot.org>", + "heinrich5991 <heinrich5991@gmail.com>", +] +license = "AGPL-3.0-only" + +[dependencies] +socket = { git = "https://github.com/heinrich5991/libtw2" } +datafile = { git = "https://github.com/heinrich5991/libtw2" } +map = { git = "https://github.com/heinrich5991/libtw2" } +net = { git = "https://github.com/heinrich5991/libtw2" } +common = { git = "https://github.com/heinrich5991/libtw2" } +event_loop = { git = "https://github.com/heinrich5991/libtw2" } +gamenet_teeworlds_0_5 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_teeworlds_0_6 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_teeworlds_0_7 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_ddnet = { git = "https://github.com/heinrich5991/libtw2", optional = true } +logger = { git = "https://github.com/heinrich5991/libtw2" } +packer = { git = "https://github.com/heinrich5991/libtw2" } +# snapshot = { git = "https://github.com/heinrich5991/libtw2" } +snapshot = { path = "../snapshot" } + +ndarray = "0.9.1" +arrayvec = "0.5.2" +clap = "2.31.2" +hexdump = "0.1.1" +itertools = ">=0.3.0,<0.5.0" +log = "0.3.1" +rand = "0.8.3" +tempfile = "2.0.0" +warn = ">=0.1.1,<0.3.0" +env_logger = "0.9.0" +signal-hook = "0.3.14" +lazy_static = "1.4.0" +anyhow = "1.0.57" +crossbeam-channel = "0.5.4" + + +[features] +default = ["gamenet_ddnet_0_6"] +gamenet_0_5 = ["dep:gamenet_teeworlds_0_5", "snapshot/gamenet_teeworlds_0_5"] +gamenet_0_6 = ["dep:gamenet_teeworlds_0_6", "snapshot/gamenet_teeworlds_0_6"] +gamenet_0_7 = ["dep:gamenet_teeworlds_0_7", "snapshot/gamenet_teeworlds_0_7"] +gamenet_ddnet_0_6 = ["dep:gamenet_ddnet", "snapshot/gamenet_ddnet_0_6"] diff --git a/client/src/client/helper.rs b/client/src/client/helper.rs new file mode 100644 index 0000000..af19301 --- /dev/null +++ b/client/src/client/helper.rs @@ -0,0 +1,87 @@ +use arrayvec::ArrayVec; +use event_loop::{Chunk, Loop, PeerId}; +use gamenet::msg::{Game, System}; +use hexdump::hexdump_iter; +use itertools::Itertools; +use log::LogLevel; +use log::{log, log_enabled, warn}; +use packer::with_packer; +use snapshot::format::Item as SnapItem; +use std::{fmt, path::PathBuf}; + +pub trait LoopExt: Loop { + fn send_system<'a, S: Into<System<'a>>>(&mut self, pid: PeerId, msg: S) { + fn inner<L: Loop + ?Sized>(msg: System, pid: PeerId, evloop: &mut L) { + let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); + with_packer(&mut buf, |p| msg.encode(p).unwrap()); + evloop.send(Chunk { + pid, + vital: true, + data: &buf, + }) + } + inner(msg.into(), pid, self) + } + fn send_game<'a, G: Into<Game<'a>>>(&mut self, pid: PeerId, msg: G) { + fn inner<L: Loop + ?Sized>(msg: Game, pid: PeerId, evloop: &mut L) { + let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); + with_packer(&mut buf, |p| msg.encode(p).unwrap()); + evloop.send(Chunk { + pid, + vital: true, + data: &buf, + }) + } + inner(msg.into(), pid, self) + } +} +impl<L: Loop> LoopExt for L {} + +pub fn need_file(crc: i32, name: &str) -> bool { + let mut path = PathBuf::new(); + path.push("/tmp/maps"); + path.push(format!("{}_{:08x}.map", name, crc)); + !path.exists() +} + +pub fn hexdump(level: LogLevel, data: &[u8]) { + if log_enabled!(level) { + hexdump_iter(data).foreach(|s| log!(level, "{}", s)); + } +} +pub struct Warn<'a>(pub &'a [u8]); + +impl<'a, W: fmt::Debug> warn::Warn<W> for Warn<'a> { + fn warn(&mut self, w: W) { + warn!("{:?}", w); + hexdump(LogLevel::Warn, self.0); + } +} + +#[derive(Debug)] +pub struct WarnSnap<'a>(pub SnapItem<'a>); + +impl<'a, W: fmt::Debug> warn::Warn<W> for WarnSnap<'a> { + fn warn(&mut self, w: W) { + warn!("{:?} for {:?}", w, self.0); + } +} + +pub fn check_dummy_map(name: &[u8], crc: u32, size: i32) -> bool { + if name != b"dummy" { + return false; + } + match (crc, size) { + (0xbeae0b9f, 549) => {} + (0x6c760ac4, 306) => {} + _ => warn!("unknown dummy map, crc={}, size={}", crc, size), + } + true +} + +pub fn get_map_path(name: &str, crc: i32) -> PathBuf { + let mut path = PathBuf::new(); + path.push("/tmp/maps"); + path.push(format!("{}_{:08x}.map", name, crc)); + path +} diff --git a/client/src/client/mod.rs b/client/src/client/mod.rs new file mode 100644 index 0000000..c72cc0b --- /dev/null +++ b/client/src/client/mod.rs @@ -0,0 +1,463 @@ +pub mod helper; + +use self::helper::get_map_path; + +use super::gamenet::{ + enums::{Team, VERSION}, + msg::{ + self, + game::{ClSetTeam, ClStartInfo}, + system::{EnterGame, Info, Input, MapChange, MapData}, + system::{Ready, RequestMapData}, + Game, System, SystemOrGame, + }, + snap_obj::{obj_size, PlayerInput}, + SnapObj, +}; +use crate::client::helper::{check_dummy_map, hexdump, WarnSnap}; +use crate::{ + client::helper::{need_file, LoopExt, Warn}, + SHOULD_EXIT, +}; +use common::{num::Cast, pretty}; +use crossbeam_channel::{Receiver, Sender}; +use event_loop::{Addr, Application, Chunk, ConnlessChunk, Loop, PeerId, SocketLoop, Timeout}; +use log::LogLevel; +use log::{debug, error, info, log, warn}; +use packer::{IntUnpacker, Unpacker}; +use std::{ + borrow::{Borrow, Cow}, + collections::HashSet, + fs, + io::{self, Write}, + mem, + net::IpAddr, + sync::atomic::Ordering, + u32, +}; +use tempfile::{NamedTempFile, NamedTempFileOptions}; +use warn::Log; + +pub struct ClientConfig { + pub nick: String, + pub clan: String, + pub timeout: String, +} + +pub struct Client { + pid: PeerId, + config: ClientConfig, + + current_votes: HashSet<Vec<u8>>, + snaps: snapshot::Manager, + num_snaps_since_reset: u64, + dummy_map: bool, + state: ClientState, + download: Option<Download>, + + _interface_receive: Receiver<ClientMesgIn>, + interface_send: Sender<ClientMesgOut>, +} + +pub struct ClientInterface { + pub send: Sender<ClientMesgIn>, + pub receive: Receiver<ClientMesgOut>, +} + +pub enum ClientMesgIn {} +pub enum ClientMesgOut { + MapChange { name: String, crc: i32 }, + Snaps(Vec<(u16, SnapObj)>), +} + +#[derive(Clone, Copy, Debug)] +enum ClientState { + Connection, + MapChange, + MapData(i32, i32), + ConReady, + ReadyToEnter, +} + +impl Default for ClientState { + fn default() -> ClientState { + ClientState::Connection + } +} + +impl Client { + pub fn new_evloop() -> SocketLoop { + SocketLoop::client() + } + + pub fn new( + evloop: &mut SocketLoop, + ip: IpAddr, + port: u16, + config: ClientConfig, + ) -> (Self, ClientInterface) { + fs::create_dir_all("/tmp/maps").unwrap(); + fs::create_dir_all("/tmp/downloading").unwrap(); + let (a, b) = crossbeam_channel::unbounded(); + let (c, d) = crossbeam_channel::unbounded(); + ( + Client { + pid: evloop.connect(Addr { ip, port }), + config, + current_votes: HashSet::new(), + snaps: snapshot::Manager::new(), + num_snaps_since_reset: 0, + dummy_map: false, + state: ClientState::Connection, + download: None, + _interface_receive: b, + interface_send: c, + }, + ClientInterface { + receive: d, + send: a, + }, + ) + } + pub fn run(self, evloop: SocketLoop) { + evloop.run(self) + } +} + +impl<'a, L: Loop> Application<L> for Client { + fn needs_tick(&mut self) -> Timeout { + Timeout::inactive() + } + fn on_tick(&mut self, evloop: &mut L) { + if SHOULD_EXIT.load(Ordering::Relaxed) { + warn!("exiting peer {}", self.pid); + evloop.disconnect(self.pid, b"error"); + } + } + fn on_packet(&mut self, evloop: &mut L, chunk: Chunk) { + let pid = chunk.pid; + + let msg = match msg::decode(&mut Warn(chunk.data), &mut Unpacker::new(chunk.data)) { + Ok(m) => m, + Err(err) => { + warn!("decode error {:?}:", err); + hexdump(LogLevel::Warn, chunk.data); + return; + } + }; + debug!("{:?}", msg); + match msg { + SystemOrGame::Game(Game::SvMotd(..)) + | SystemOrGame::Game(Game::SvKillMsg(..)) + | SystemOrGame::Game(Game::SvTuneParams(..)) + | SystemOrGame::Game(Game::SvWeaponPickup(..)) + | SystemOrGame::System(System::InputTiming(..)) + | SystemOrGame::Game(Game::SvExtraProjectile(..)) => {} + SystemOrGame::Game(Game::SvChat(chat)) => { + if chat.client_id == -1 { + info!("[server]: {}", pretty::AlmostString::new(chat.message)); + } else { + info!( + "[team {}]: {}", + chat.team, + pretty::AlmostString::new(chat.message) + ) + } + } + SystemOrGame::Game(Game::SvBroadcast(broadcast)) => { + info!( + "broadcast: {}", + pretty::AlmostString::new(broadcast.message) + ); + } + _ => {} + } + + match msg { + SystemOrGame::System(ref msg) => match *msg { + System::MapChange(MapChange { crc, size, name }) => { + if let Some(_) = size.try_usize() { + if name.iter().any(|&b| b == b'/' || b == b'\\') { + error!("invalid map name"); + evloop.disconnect(pid, b"error"); + return; + } + match self.state { + ClientState::MapChange => {} + ClientState::ReadyToEnter if self.dummy_map => {} + _ => warn!("map change from state {:?}", self.state), + } + self.dummy_map = check_dummy_map(name, crc as u32, size); + self.current_votes.clear(); + self.num_snaps_since_reset = 0; + self.snaps.reset(); + info!("map change: {}", pretty::AlmostString::new(name)); + let name = String::from_utf8_lossy(name); + if let Cow::Owned(..) = name { + warn!("weird characters in map name"); + } + let mut start_download = false; + if need_file(crc, &name) { + if let Err(e) = self.open_download_file(crc, name.borrow()) { + error!("error opening file {:?}", e); + } else { + start_download = true; + } + } + if start_download { + info!("download starting"); + evloop.send_system(pid, RequestMapData { chunk: 0 }); + self.state = ClientState::MapData(crc, 0); + } else { + self.state = ClientState::ConReady; + evloop.send_system(pid, Ready); + + self.interface_send + .send(ClientMesgOut::MapChange { + name: name.to_string(), + crc, + }) + .unwrap(); + } + } else { + error!("invalid map size"); + evloop.disconnect(pid, b"error"); + return; + } + } + System::Snap(_) | System::SnapEmpty(_) | System::SnapSingle(_) => { + self.num_snaps_since_reset += 1; + let mut input = PlayerInput::default(); + { + let res = match *msg { + System::Snap(s) => self.snaps.snap(&mut Log, obj_size, s), + System::SnapEmpty(s) => self.snaps.snap_empty(&mut Log, obj_size, s), + System::SnapSingle(s) => self.snaps.snap_single(&mut Log, obj_size, s), + _ => unreachable!(), + }; + match res { + Ok(Some(snap)) => { + let snaps = snap + .items() + .filter_map(|item| { + SnapObj::decode_obj( + &mut WarnSnap(item), + item.type_id.into(), + &mut IntUnpacker::new(item.data), + ) + .map(|o| (item.id, o)) + .ok() + }) + .collect::<Vec<(u16, SnapObj)>>(); + + self.interface_send + .send(ClientMesgOut::Snaps(snaps.clone())) + .unwrap(); + + let client_id = snaps + .iter() + .filter_map(|(_id, i)| { + if let SnapObj::PlayerInfo(p) = i { + if p.local != 0 { + Some(p.client_id) + } else { + None + } + } else { + None + } + }) + .next() + .unwrap_or(1 << 15) + as u16; + + let mut target_position = None; + let mut my_position = (0, 0); + for (id, snap) in &snaps { + match snap { + SnapObj::Character(c) => { + let c = c.character_core; + if *id == client_id { + my_position = (c.x, c.y) + } else if c.hook_state != 0 { + target_position = Some((c.x, c.y)) + } + } + _ => {} + } + } + if let Some(target) = target_position { + if target.0 < my_position.0 { + input.direction = -1; + } + if target.0 > my_position.0 { + input.direction = 1; + } + if target.1 - 16 < my_position.1 { + input.jump = 1; + } + } + } + Ok(None) => { + self.num_snaps_since_reset -= 1; + } + Err(err) => warn!("snapshot error {:?}", err), + } + } + // DDNet needs the INPUT message as the first + // chunk of the packet. + evloop.force_flush(pid); + let tick = self.snaps.ack_tick().unwrap_or(-1); + evloop.send_system( + pid, + Input { + ack_snapshot: tick, + intended_tick: tick, + input_size: mem::size_of::<PlayerInput>().assert_i32(), + input, + }, + ); + } + _ => {} + }, + SystemOrGame::Game(ref msg) => match *msg { + _ => {} + }, + } + match self.state { + ClientState::Connection => unreachable!(), + ClientState::MapChange => {} // Handled above. + ClientState::MapData(cur_crc, cur_chunk) => match msg { + SystemOrGame::System(System::MapData(MapData { + last, + crc, + chunk, + data, + })) => { + if cur_crc == crc && cur_chunk == chunk { + let res = self.write_download_file(data); + if let Err(ref err) = res { + error!("error writing file {:?}", err); + } + if last != 0 || res.is_err() { + if !res.is_err() { + if let Err(err) = self.finish_download_file() { + error!("error finishing file {:?}", err); + } + if last != 1 { + warn!("weird map data packet"); + } + } + self.state = ClientState::ConReady; + evloop.send_system(pid, Ready); + + info!("download finished"); + } else { + let cur_chunk = cur_chunk.checked_add(1).unwrap(); + self.state = ClientState::MapData(cur_crc, cur_chunk); + evloop.send_system(pid, RequestMapData { chunk: cur_chunk }); + } + } else { + if cur_crc != crc || cur_chunk < chunk { + warn!("unsolicited map data crc={:08x} chunk={}", crc, chunk); + warn!("want crc={:08x} chunk={}", cur_crc, cur_chunk); + } + } + } + _ => {} + }, + ClientState::ConReady => match msg { + SystemOrGame::System(System::ConReady(..)) => { + evloop.send_game( + pid, + ClStartInfo { + name: self.config.nick.as_bytes(), + clan: self.config.clan.as_bytes(), + country: -1, + skin: b"limekittygirl", + use_custom_color: true, + color_body: 0xFF00FF, + color_feet: 0x550055, + }, + ); + self.state = ClientState::ReadyToEnter; + } + _ => {} + }, + ClientState::ReadyToEnter => match msg { + SystemOrGame::Game(Game::SvReadyToEnter(..)) => { + evloop.send_system(pid, EnterGame); + evloop.send_game(pid, ClSetTeam { team: Team::Red }); + } + _ => {} + }, + } + evloop.flush(pid); + } + fn on_connless_packet(&mut self, _: &mut L, chunk: ConnlessChunk) { + warn!( + "connless packet {} {:?}", + chunk.addr, + pretty::Bytes::new(chunk.data) + ); + } + fn on_connect(&mut self, _: &mut L, _: PeerId) { + unreachable!(); + } + fn on_ready(&mut self, evloop: &mut L, pid: PeerId) { + if pid != self.pid { + error!("not our pid: {} vs {}", pid, self.pid); + } + self.state = ClientState::MapChange; + evloop.send_system( + pid, + Info { + version: VERSION.as_bytes(), + password: Some(b""), + }, + ); + evloop.flush(pid); + } + fn on_disconnect(&mut self, _: &mut L, pid: PeerId, remote: bool, reason: &[u8]) { + if remote { + error!( + "disconnected pid={:?} error={}", + pid, + pretty::AlmostString::new(reason) + ); + } + } +} + +struct Download { + file: NamedTempFile, + crc: i32, + name: String, +} + +impl Client { + fn open_download_file(&mut self, crc: i32, name: &str) -> Result<(), io::Error> { + self.download = Some(Download { + file: NamedTempFileOptions::new() + .prefix(&format!("{}_{:08x}_", name, crc)) + .suffix(".map") + .create_in("/tmp/downloading")?, + crc, + name: name.to_string(), + }); + Ok(()) + } + fn write_download_file(&mut self, data: &[u8]) -> Result<(), io::Error> { + self.download.as_mut().unwrap().file.write_all(data) + } + fn finish_download_file(&mut self) -> Result<(), io::Error> { + let download = self.download.take().unwrap(); + let path = get_map_path(download.name.as_str(), download.crc); + download + .file + .persist(&path) + .map(|_| ()) + .map_err(|e| e.error)?; + Ok(()) + } +} diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 0000000..3c19df7 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,21 @@ +#![feature(exclusive_range_pattern)] + +pub mod client; +pub mod world; + +#[cfg(feature = "gamenet_ddnet_0_6")] +pub extern crate gamenet_ddnet as gamenet; +#[cfg(feature = "gamenet_0_5")] +pub extern crate gamenet_teeworlds_0_5 as gamenet; +#[cfg(feature = "gamenet_0_6")] +pub extern crate gamenet_teeworlds_0_6 as gamenet; +#[cfg(feature = "gamenet_0_7")] +pub extern crate gamenet_teeworlds_0_7 as gamenet; + + +use std::sync::atomic::AtomicBool; + +use lazy_static::lazy_static; +lazy_static! { + pub static ref SHOULD_EXIT: AtomicBool = AtomicBool::new(false); +} diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100644 index 0000000..b79f338 --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,41 @@ +use log::{error, log, warn}; +use signal_hook::{ + consts::{SIGINT, SIGTERM}, + iterator::Signals, + low_level::exit, +}; +use std::{net::IpAddr, str::FromStr, sync::atomic::Ordering, thread, time::Duration}; +use twclient::{ + client::{Client, ClientConfig}, + SHOULD_EXIT, +}; + +fn main() { + env_logger::init(); + + let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); + thread::spawn(move || { + for sig in signals.forever() { + warn!("received signal {:?}", sig); + SHOULD_EXIT.store(true, Ordering::Relaxed); + thread::sleep(Duration::from_secs(3)); + error!("exit timeout!"); + exit(1); + } + }); + + let config = ClientConfig { + nick: String::from("metamuffin"), + clan: String::from("rustacean"), + timeout: String::from("asgefdhjikhjfhjf"), + }; + let mut args = std::env::args().skip(1); + let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap(); + let port = u16::from_str(args.next().unwrap().as_str()).unwrap(); + drop(ip); + drop(port); + drop(config); + drop(Client::new_evloop()); + todo!() + // Client::run_thing(ip, port, config) +} diff --git a/client/src/world/map.rs b/client/src/world/map.rs new file mode 100644 index 0000000..fb67918 --- /dev/null +++ b/client/src/world/map.rs @@ -0,0 +1,135 @@ +use ::map as mapfile; +use anyhow::Error; +use common::{num::Cast, vec}; +use log::{info, log}; +use ndarray::Array2; +use std::{ + collections::{hash_map, HashMap}, + fs::File, + mem, +}; + +pub use mapfile::format; +pub use mapfile::{ + format::Tile, + reader::{self, Color, LayerTilemapType}, +}; +pub const TILE_NUM: u32 = 16; + +pub struct Layer { + pub color: Color, + pub image: Option<usize>, + pub tiles: Array2<Tile>, + pub kind: LayerTilemapType, +} + +pub struct Map { + pub layers: Vec<Layer>, + pub tilesets: HashMap<Option<usize>, Array2<Color>>, +} + +impl Map { + pub fn empty() -> Self { + Self { + layers: Vec::new(), + tilesets: HashMap::new(), + } + } + + pub fn load(file: File) -> Result<Self, Error> { + info!("loading map"); + let datafile = datafile::Reader::new(file).unwrap(); + let mut map = mapfile::Reader::from_datafile(datafile); + + let mut layers = vec![]; + for group_idx in map.group_indices() { + let group = map.group(group_idx).unwrap(); + + if group.parallax_x != 100 + || group.parallax_y != 100 + || group.offset_x != 0 + || group.offset_y != 0 + || group.clipping.is_some() + { + continue; + } + + for layer_idx in group.layer_indices { + let layer = map.layer(layer_idx).unwrap(); + + let tilemap = if let reader::LayerType::Tilemap(t) = layer.t { + t + } else { + continue; + }; + let normal = if let Some(n) = tilemap.type_.to_normal() { + n + } else { + continue; + }; + let tiles = map.layer_tiles(tilemap.tiles(normal.data)).unwrap(); + layers.push(Layer { + color: normal.color, + image: normal.image, + kind: tilemap.type_, + tiles, + }); + } + } + + let mut tilesets = HashMap::new(); + for layer in &layers { + match tilesets.entry(layer.image) { + hash_map::Entry::Occupied(_) => {} + hash_map::Entry::Vacant(v) => { + let data = match layer.image { + None => Array2::from_elem( + (1, 1), + Color { + alpha: 255, + red: 255, + blue: 255, + green: 0, + }, + ), + Some(image_idx) => { + let image = map.image(image_idx).unwrap(); + let height = image.height.usize(); + let width = image.width.usize(); + match image.data { + Some(d) => { + let data = map.image_data(d).unwrap(); + if data.len() % mem::size_of::<Color>() != 0 { + panic!("image shape invalid"); + } + let data: Vec<Color> = unsafe { vec::transmute(data) }; + Array2::from_shape_vec((height, width), data).unwrap() + } + None => { + continue; + // let image_name = map.image_name(image.name)?; + // // WARN? Unknown external image + // // WARN! Wrong dimensions + // str::from_utf8(&image_name).ok() + // .and_then(sanitize) + // .map(&mut external_tileset_loader) + // .transpose()? + // .unwrap_or(None) + // .unwrap_or_else(|| Array2::from_elem((1, 1), Color::white())) + } + } + } + }; + v.insert(data); + } + } + } + + info!( + "{} layers + {} tilesets loaded", + layers.len(), + tilesets.len() + ); + Ok(Self { tilesets, layers }) + } +} diff --git a/client/src/world/mod.rs b/client/src/world/mod.rs new file mode 100644 index 0000000..ae985a6 --- /dev/null +++ b/client/src/world/mod.rs @@ -0,0 +1,132 @@ +use gamenet::{ + enums::{Emote, Team, Weapon}, + SnapObj, +}; + +use self::map::Map; +use crate::client::{helper::get_map_path, ClientMesgOut}; +use std::{collections::BTreeMap, fs::File}; + +pub mod map; + +pub use gamenet::enums; + +#[derive(Debug)] +pub struct Tee { + pub local: bool, + pub latency: i32, + pub score: i32, + + pub team: Team, + pub weapon: Weapon, + pub armor: i32, + pub ammo: i32, + pub emote: Emote, + pub attack_tick: i32, + + pub tick: i32, + pub angle: i32, + pub x: i32, + pub y: i32, + pub vel_x: i32, + pub vel_y: i32, + pub hook_x: i32, + pub hook_y: i32, + pub hook_dx: i32, + pub hook_dy: i32, + pub hook_player: i32, + pub hook_state: i32, +} + +impl Default for Tee { + fn default() -> Self { + Self { + x: Default::default(), + y: Default::default(), + local: false, + team: Team::Spectators, + latency: Default::default(), + score: Default::default(), + weapon: Weapon::Shotgun, + armor: Default::default(), + ammo: Default::default(), + attack_tick: Default::default(), + emote: Emote::Normal, + tick: Default::default(), + angle: Default::default(), + vel_x: Default::default(), + vel_y: Default::default(), + hook_x: Default::default(), + hook_y: Default::default(), + hook_dx: Default::default(), + hook_dy: Default::default(), + hook_player: Default::default(), + hook_state: Default::default(), + } + } +} + +pub struct World { + pub map: Map, + pub tees: BTreeMap<u16, Tee>, +} + +impl World { + pub fn new() -> Self { + Self { + map: Map::empty(), + tees: BTreeMap::new(), + } + } + + pub fn update(&mut self, m: &ClientMesgOut) { + match m { + ClientMesgOut::MapChange { name, crc } => { + let file = File::open(get_map_path(name.as_str(), *crc)).unwrap(); + self.map = Map::load(file).unwrap(); + } + ClientMesgOut::Snaps(s) => { + self.tees.clear(); + for (id, o) in s { + match o { + SnapObj::ClientInfo(_o) => { + // TODO + } + SnapObj::PlayerInfo(o) => { + let e = self.tees.entry(*id).or_default(); + e.local = o.local == 1; + e.team = o.team; + e.latency = o.latency; + e.score = o.score; + } + SnapObj::Character(c) => { + let e = self.tees.entry(*id).or_default(); + e.ammo = c.ammo_count; + e.weapon = c.weapon; + e.emote = c.emote; + e.attack_tick = c.attack_tick; + + e.x = c.character_core.x; + e.y = c.character_core.y; + e.vel_x = c.character_core.vel_x; + e.vel_y = c.character_core.vel_y; + + e.tick = c.character_core.tick; + e.hook_x = c.character_core.hook_x; + e.hook_y = c.character_core.hook_y; + e.hook_player = c.character_core.hooked_player; + e.hook_dx = c.character_core.hook_dx; + e.hook_dy = c.character_core.hook_dy; + e.hook_state = c.character_core.hook_state; + } + _ => (), + } + } + } + } + } + + pub fn local_tee(&self) -> Option<&Tee> { + self.tees.values().find(|e| e.local) + } +} diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 0000000..6ec23ca --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "twrenderer" +version = "0.1.0" +edition = "2021" +authors = [ + "metamuffin <metamuffin@disroot.org>", + "heinrich5991 <heinrich5991@gmail.com>", +] +license = "AGPL-3.0-only" + +[dependencies] +twclient = { path = "../client" } +skia-safe = { version = "0.50.0", features = ["gl", "wayland"] } +glutin = "0.28.0" +gl = "0.14.0" +log = "0.4.17" +env_logger = "0.9.0" +signal-hook = "0.3.14" diff --git a/renderer/src/main.rs b/renderer/src/main.rs new file mode 100644 index 0000000..d763336 --- /dev/null +++ b/renderer/src/main.rs @@ -0,0 +1,254 @@ +pub mod map; + +use glutin::{ + event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowBuilder}, + ContextWrapper, GlProfile, PossiblyCurrent, +}; +use log::{error, info, warn}; +use map::MapRenderer; +use signal_hook::{ + consts::{SIGINT, SIGTERM}, + iterator::Signals, +}; +use skia_safe::{ + gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin}, + Canvas, Color, Color4f, ColorSpace, ColorType, Paint, Point, Surface, +}; +use std::{ + convert::TryInto, net::IpAddr, process::exit, str::FromStr, sync::atomic::Ordering, thread, + time::Duration, +}; +use twclient::{ + client::{Client, ClientConfig, ClientInterface}, + world::World, + SHOULD_EXIT, +}; + +fn main() { + env_logger::init(); + + let event_loop = EventLoop::new(); + let wb = WindowBuilder::new().with_title("teeworlds"); + + let cb = glutin::ContextBuilder::new() + .with_depth_buffer(0) + .with_stencil_buffer(8) + .with_pixel_format(24, 8) + .with_gl_profile(GlProfile::Core); + + // TODO + // #[cfg(not(feature = "wayland"))] + // let cb = cb.with_double_buffer(Some(true)); + + let windowed_context = cb.build_windowed(wb, &event_loop).unwrap(); + + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + gl::load_with(|s| windowed_context.get_proc_address(s)); + + let mut gr_context = skia_safe::gpu::DirectContext::new_gl(None, None).unwrap(); + + let fb_info = { + use gl::types::GLint; + let mut fboid: GLint = 0; + unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) }; + + FramebufferInfo { + fboid: fboid.try_into().unwrap(), + format: skia_safe::gpu::gl::Format::RGBA8.into(), + } + }; + + windowed_context + .window() + .set_inner_size(glutin::dpi::Size::new(glutin::dpi::LogicalSize::new( + 1024.0, 1024.0, + ))); + + let surface = create_surface(&windowed_context, &fb_info, &mut gr_context); + + struct Env { + surface: Surface, + gr_context: skia_safe::gpu::DirectContext, + windowed_context: ContextWrapper<PossiblyCurrent, Window>, + } + + let mut env = Env { + surface, + gr_context, + windowed_context, + }; + + let mut args = std::env::args().skip(1); + let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap(); + let port = u16::from_str(args.next().unwrap().as_str()).unwrap(); + let mut evloop = Client::new_evloop(); + let (client, client_interface) = Client::new( + &mut evloop, + ip, + port, + ClientConfig { + nick: "metamuffin".to_string(), + clan: "rustacean".to_string(), + timeout: "sdfhaiusdfhus".to_string(), + }, + ); + let mut network_thread = Some(std::thread::spawn(move || client.run(evloop))); + + let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); + info!("setting up signal handlers"); + thread::spawn(move || { + for sig in signals.forever() { + warn!("received signal {:?}", sig); + SHOULD_EXIT.store(true, Ordering::Relaxed); + thread::sleep(Duration::from_secs(3)); + error!("exit timeout!"); + exit(1); + } + }); + + let mut renderer = Renderer { + client_interface, + map_renderer: MapRenderer::new(), + world: World::new(), + }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + #[allow(deprecated)] + match event { + Event::LoopDestroyed => {} + Event::RedrawEventsCleared => { + if SHOULD_EXIT.load(Ordering::Relaxed) { + warn!("waiting for networt thread to finish"); + network_thread.take().unwrap().join().unwrap(); + warn!("exiting renderer"); + exit(0); + } + renderer.tick(); + env.windowed_context.window().request_redraw(); + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(physical_size) => { + env.surface = + create_surface(&env.windowed_context, &fb_info, &mut env.gr_context); + env.windowed_context.resize(physical_size) + } + WindowEvent::CloseRequested => { + warn!("renderer event loop stopped, telling the client to exit too"); + SHOULD_EXIT.store(true, Ordering::Relaxed); + *control_flow = ControlFlow::Exit + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode, + modifiers, + .. + }, + .. + } => { + if modifiers.logo() { + if let Some(VirtualKeyCode::Q) = virtual_keycode { + *control_flow = ControlFlow::Exit; + } + } + env.windowed_context.window().request_redraw(); + } + _ => (), + }, + Event::RedrawRequested(_) => { + { + let dims = (env.surface.width() as f32, env.surface.height() as f32); + let canvas = env.surface.canvas(); + renderer.draw(canvas, dims) + } + env.surface.canvas().flush(); + env.windowed_context.swap_buffers().unwrap(); + } + _ => (), + } + }); +} + +pub struct Renderer { + client_interface: ClientInterface, + map_renderer: MapRenderer, + world: World, +} + +impl Renderer { + pub fn tick(&mut self) { + for m in self.client_interface.receive.try_iter() { + self.world.update(&m); + match m { + twclient::client::ClientMesgOut::MapChange { .. } => { + self.map_renderer.map_changed(&self.world) + } + _ => (), + } + } + } + pub fn draw(&mut self, canvas: &mut Canvas, dims: (f32, f32)) { + canvas.clear(Color::TRANSPARENT); + let center = self.world.local_tee().map(|t| (t.x, t.y)).unwrap_or((0, 0)); + + canvas.save(); + canvas.translate((dims.0 / 2.0, dims.1 / 2.0)); + canvas.translate((-center.0 as f32, -center.1 as f32)); + + self.map_renderer.draw(&self.world, canvas); + + let tee_paint = Paint::new( + Color4f { + a: 1.0, + r: 1.0, + g: 0.0, + b: 1.0, + }, + &ColorSpace::new_srgb(), + ); + for t in self.world.tees.values() { + canvas.draw_circle( + Point { + x: t.x as f32, + y: t.y as f32, + }, + 16.0, + &tee_paint, + ); + } + + canvas.restore(); + } +} + +fn create_surface( + windowed_context: &ContextWrapper<PossiblyCurrent, Window>, + fb_info: &FramebufferInfo, + gr_context: &mut skia_safe::gpu::DirectContext, +) -> skia_safe::Surface { + let pixel_format = windowed_context.get_pixel_format(); + let size = windowed_context.window().inner_size(); + let backend_render_target = BackendRenderTarget::new_gl( + ( + size.width.try_into().unwrap(), + size.height.try_into().unwrap(), + ), + pixel_format.multisampling.map(|s| s.try_into().unwrap()), + pixel_format.stencil_bits.try_into().unwrap(), + *fb_info, + ); + Surface::from_backend_render_target( + gr_context, + &backend_render_target, + SurfaceOrigin::BottomLeft, + ColorType::RGBA8888, + None, + None, + ) + .unwrap() +} diff --git a/renderer/src/map.rs b/renderer/src/map.rs new file mode 100644 index 0000000..0ea3a6b --- /dev/null +++ b/renderer/src/map.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; + +use log::{info, warn}; +use skia_safe::{ + canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Paint, Rect, +}; +use twclient::world::{ + map::{format, TILE_NUM}, + World, +}; + +pub struct MapRenderer { + tileset: HashMap<Option<usize>, Image>, +} + +impl MapRenderer { + pub fn new() -> Self { + Self { + tileset: HashMap::new(), + } + } + + pub fn tick(&mut self) {} + pub fn map_changed(&mut self, world: &World) { + self.tileset.clear(); + for (key, t) in &world.map.tilesets { + let mut bytes: Vec<u8> = Vec::with_capacity(t.dim().0 * t.dim().1 * 4); + info!("loading tileset: {:?} => {:?}", key, t.dim()); + for ((_x, _y), c) in t.indexed_iter() { + bytes.push(c.red); + bytes.push(c.green); + bytes.push(c.blue); + bytes.push(c.alpha); + } + let d = skia_safe::Data::new_copy(&bytes); + let v = skia_safe::Image::from_raster_data( + &skia_safe::ImageInfo::new( + ISize::new(t.dim().0 as i32, t.dim().1 as i32), + skia_safe::ColorType::RGBA8888, + skia_safe::AlphaType::Opaque, + None, + ), + d, + t.dim().0 * 4, + ) + .unwrap(); + self.tileset.insert(*key, v); + } + } + + pub fn draw(&mut self, world: &World, canvas: &mut Canvas) { + let mut grid_paint = Paint::new( + Color4f { + a: 1.0, + r: 1.0, + g: 1.0, + b: 1.0, + }, + &ColorSpace::new_srgb(), + ); + grid_paint.set_style(skia_safe::PaintStyle::Stroke); + grid_paint.set_anti_alias(true); + + let center = world + .local_tee() + .map(|t| (t.x / 16, t.y / 16)) + .unwrap_or((0, 0)); + + let tile_rect = Rect { + top: center.1 as f32 * 16.0, + left: center.0 as f32 * 16.0, + bottom: center.1 as f32 * 16.0 + 16.0, + right: center.0 as f32 * 16.0 + 16.0, + }; + canvas.draw_rect(tile_rect, &grid_paint); + + for l in &world.map.layers { + let tileset = match world.map.tilesets.get(&l.image) { + Some(t) => t, + None => { + warn!("missing tileset for {:?}, skipping layer", l.image); + continue; + } + }; + if tileset.dim() == (1, 1) { + continue; + } + + for layer_y in (center.1 - 30)..(center.1 + 30) { + for layer_x in (center.0 - 30)..(center.0 + 30) { + let layer_x = layer_x.try_into().unwrap_or(0); + let layer_y = layer_y.try_into().unwrap_or(0); + + if layer_x >= l.tiles.dim().1 || layer_y >= l.tiles.dim().0 { + continue; + } + let tile = l.tiles[(layer_y, layer_x)]; + + let _rotate = tile.flags & format::TILEFLAG_ROTATE != 0; + let _vflip = tile.flags & format::TILEFLAG_VFLIP != 0; + let _hflip = tile.flags & format::TILEFLAG_HFLIP != 0; + let tile_x = tile.index as u32 % TILE_NUM; + let tile_y = tile.index as u32 / TILE_NUM; + + if tile_x == 0 && tile_y == 0 { + continue; + } + + let tile_rect = Rect { + top: layer_y as f32 * 16.0, + left: layer_x as f32 * 16.0, + bottom: layer_y as f32 * 16.0 + 16.0, + right: layer_x as f32 * 16.0 + 16.0, + }; + canvas.draw_rect(tile_rect, &grid_paint); + + const TL: u32 = 16; + canvas.draw_image_rect( + self.tileset.get(&l.image).unwrap(), + Some(( + &Rect { + top: (tile_y * TL) as f32, + left: (tile_x * TL) as f32, + bottom: ((tile_y + 1) * TL) as f32, + right: ((tile_x + 1) * TL) as f32, + }, + SrcRectConstraint::Strict, + )), + tile_rect, + &Paint::default(), + ); + // for iy in 0..TL { + // for ix in 0..TL { + // let (x, y) = ((layer_x as u32 * TL + iy), (layer_y as u32 * TL + ix)); + // let p_tile = + // tileset[((tile_y * TL + iy) as usize, (tile_x * TL + ix) as usize)]; + // let mut p = Paint::new( + // Color4f { + // a: 0.0, + // b: 0.0, + // g: 0.0, + // r: 0.0, + // }, + // &ColorSpace::new_srgb(), + // ); + // p.set_argb(p_tile.alpha, p_tile.red, p_tile.green, p_tile.green); + // p.set_style(PaintStyle::Fill); + // canvas.draw_rect( + // Rect { + // left: x as f32, + // top: y as f32, + // right: x as f32 + 1.0, + // bottom: y as f32 + 1.0, + // }, + // &p, + // ); + // } + // } + } + } + } + } +} diff --git a/snapshot/Cargo.toml b/snapshot/Cargo.toml new file mode 100644 index 0000000..165ba3c --- /dev/null +++ b/snapshot/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "snapshot" +version = "0.0.1" +authors = [ + "metamuffin <metamuffin@disroot.org>", + "heinrich5991 <heinrich5991@gmail.com>", +] +license = "AGPL-3.0-only" + +[dependencies] +buffer = "0.1.9" +common = { git = "https://github.com/heinrich5991/libtw2" } +gamenet_teeworlds_0_5 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_teeworlds_0_6 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_teeworlds_0_7 = { git = "https://github.com/heinrich5991/libtw2", optional = true } +gamenet_ddnet = { git = "https://github.com/heinrich5991/libtw2", optional = true } +packer = { git = "https://github.com/heinrich5991/libtw2" } +vec_map = "0.8.0" +warn = ">=0.1.1,<0.3.0" + + +[features] +default = ["gamenet_ddnet"] +gamenet_tw_0_5 = ["gamenet_teeworlds_0_5"] +gamenet_tw_0_6 = ["gamenet_teeworlds_0_6"] +gamenet_tw_0_7 = ["gamenet_teeworlds_0_7"] +gamenet_ddnet_0_6 = ["gamenet_ddnet"] diff --git a/snapshot/src/format.rs b/snapshot/src/format.rs new file mode 100644 index 0000000..1a22938 --- /dev/null +++ b/snapshot/src/format.rs @@ -0,0 +1,85 @@ +use buffer::CapacityError; +use packer::Packer; +use packer::Unpacker; +use packer; +use snap::Error; +use warn::Warn; +use warn::wrap; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Warning { + Packer(packer::Warning), + NonZeroPadding, + DuplicateDelete, + DuplicateUpdate, + UnknownDelete, + DeleteUpdate, + NumUpdatedItems, +} + +impl From<packer::Warning> for Warning { + fn from(w: packer::Warning) -> Warning { + Warning::Packer(w) + } +} + +pub fn key_to_type_id(key: i32) -> u16 { + ((key as u32 >> 16) & 0xffff) as u16 +} + +pub fn key_to_id(key: i32) -> u16 { + ((key as u32) & 0xffff) as u16 +} + +pub fn key(type_id: u16, id: u16) -> i32 { + (((type_id as u32) << 16) | (id as u32)) as i32 +} + +#[derive(Clone, Copy, Debug)] +pub struct Item<'a> { + pub type_id: u16, + pub id: u16, + pub data: &'a [i32], +} + +impl<'a> Item<'a> { + pub fn from_key(key: i32, data: &[i32]) -> Item { + Item { + type_id: key_to_type_id(key), + id: key_to_id(key), + data: data, + } + } + pub fn key(&self) -> i32 { + key(self.type_id, self.id) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct DeltaHeader { + pub num_deleted_items: i32, + pub num_updated_items: i32, +} + +impl DeltaHeader { + pub fn decode<W: Warn<Warning>>(warn: &mut W, p: &mut Unpacker) + -> Result<DeltaHeader, Error> + { + let result = DeltaHeader { + num_deleted_items: packer::positive(p.read_int(wrap(warn))?)?, + num_updated_items: packer::positive(p.read_int(wrap(warn))?)?, + }; + if p.read_int(wrap(warn))? != 0 { + warn.warn(Warning::NonZeroPadding); + } + Ok(result) + } + pub fn encode<'d, 's>(&self, mut p: Packer<'d, 's>) + -> Result<&'d [u8], CapacityError> + { + p.write_int(self.num_deleted_items)?; + p.write_int(self.num_updated_items)?; + p.write_int(0)?; + Ok(p.written()) + } +} diff --git a/snapshot/src/lib.rs b/snapshot/src/lib.rs new file mode 100644 index 0000000..992d898 --- /dev/null +++ b/snapshot/src/lib.rs @@ -0,0 +1,36 @@ + +#[cfg(feature = "gamenet_ddnet")] +extern crate gamenet_ddnet as gamenet; +#[cfg(feature = "gamenet_0_5")] +extern crate gamenet_teeworlds_0_5 as gamenet; +#[cfg(feature = "gamenet_0_6")] +extern crate gamenet_teeworlds_0_6 as gamenet; +#[cfg(feature = "gamenet_0_7")] +extern crate gamenet_teeworlds_0_7 as gamenet; + +extern crate buffer; +extern crate common; +extern crate packer; +extern crate vec_map; +extern crate warn; + +pub mod format; +pub mod manager; +pub mod receiver; +pub mod snap; +pub mod storage; + +pub use manager::Manager; +pub use receiver::DeltaReceiver; +pub use receiver::ReceivedDelta; +pub use snap::Delta; +pub use snap::DeltaReader; +pub use snap::Snap; +pub use storage::Storage; + +use common::num::Cast; +use std::ops; + +fn to_usize(r: ops::Range<u32>) -> ops::Range<usize> { + r.start.usize()..r.end.usize() +} diff --git a/snapshot/src/manager.rs b/snapshot/src/manager.rs new file mode 100644 index 0000000..65d0c72 --- /dev/null +++ b/snapshot/src/manager.rs @@ -0,0 +1,140 @@ +use Delta; +use DeltaReader; +use DeltaReceiver; +use ReceivedDelta; +use Snap; +use Storage; +use format; +use gamenet::msg::system; +use packer::Unpacker; +use receiver; +use snap; +use storage; +use warn::Warn; +use warn::wrap; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + Receiver(receiver::Error), + Snap(snap::Error), + Storage(storage::Error), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Warning { + Receiver(receiver::Warning), + Snap(format::Warning), + Storage(storage::Warning), +} + +impl From<receiver::Error> for Error { + fn from(err: receiver::Error) -> Error { + Error::Receiver(err) + } +} + +impl From<snap::Error> for Error { + fn from(err: snap::Error) -> Error { + Error::Snap(err) + } +} + +impl From<storage::Error> for Error { + fn from(err: storage::Error) -> Error { + Error::Storage(err) + } +} + +impl From<receiver::Warning> for Warning { + fn from(w: receiver::Warning) -> Warning { + Warning::Receiver(w) + } +} + +impl From<format::Warning> for Warning { + fn from(w: format::Warning) -> Warning { + Warning::Snap(w) + } +} + +impl From<storage::Warning> for Warning { + fn from(w: storage::Warning) -> Warning { + Warning::Storage(w) + } +} + +#[derive(Clone, Default)] +struct ManagerInner { + temp_delta: Delta, + reader: DeltaReader, + storage: Storage, +} + +#[derive(Clone, Default)] +pub struct Manager { + inner: ManagerInner, + receiver: DeltaReceiver, +} + +impl Manager { + pub fn new() -> Manager { + Default::default() + } + pub fn reset(&mut self) { + self.inner.storage.reset(); + self.receiver.reset(); + } + pub fn ack_tick(&self) -> Option<i32> { + self.inner.storage.ack_tick() + } + pub fn snap_empty<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapEmpty) + -> Result<Option<&Snap>, Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + let res = self.receiver.snap_empty(wrap(warn), snap); + self.inner.handle_msg(warn, object_size, res) + } + pub fn snap_single<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::SnapSingle) + -> Result<Option<&Snap>, Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + let res = self.receiver.snap_single(wrap(warn), snap); + self.inner.handle_msg(warn, object_size, res) + } + pub fn snap<W, O>(&mut self, warn: &mut W, object_size: O, snap: system::Snap) + -> Result<Option<&Snap>, Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + let res = self.receiver.snap(wrap(warn), snap); + self.inner.handle_msg(warn, object_size, res) + } +} + +impl ManagerInner { + fn handle_msg<W, O>(&mut self, warn: &mut W, object_size: O, res: Result<Option<ReceivedDelta>, receiver::Error>) + -> Result<Option<&Snap>, Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + Ok(match res? { + Some(delta) => Some(self.add_delta(warn, object_size, delta)?), + None => None, + }) + } + fn add_delta<W, O>(&mut self, warn: &mut W, object_size: O, delta: ReceivedDelta) + -> Result<&Snap, Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + let crc = delta.data_and_crc.map(|d| d.1); + if let Some((data, _)) = delta.data_and_crc { + self.reader.read(wrap(warn), &mut self.temp_delta, object_size, &mut Unpacker::new(data))?; + } else { + self.temp_delta.clear(); + } + Ok(self.storage.add_delta(wrap(warn), crc, delta.delta_tick, delta.tick, &self.temp_delta)?) + } +} diff --git a/snapshot/src/receiver.rs b/snapshot/src/receiver.rs new file mode 100644 index 0000000..49833d1 --- /dev/null +++ b/snapshot/src/receiver.rs @@ -0,0 +1,245 @@ +use common::num::Cast; +use gamenet::msg::system; +use std::ops; +use to_usize; +use vec_map::VecMap; +use warn::Warn; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Warning { + DuplicateSnap, + DifferingAttributes, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + OldDelta, + InvalidNumParts, + InvalidPart, + DuplicatePart, +} + +// TODO: How to handle `tick` overflowing? +#[derive(Clone, Debug)] +struct CurrentDelta { + tick: i32, + delta_tick: i32, + num_parts: i32, + crc: i32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ReceivedDelta<'a> { + pub delta_tick: i32, + pub tick: i32, + pub data_and_crc: Option<(&'a [u8], i32)>, +} + +#[derive(Clone, Default)] +pub struct DeltaReceiver { + previous_tick: Option<i32>, + current: Option<CurrentDelta>, + // `parts` points into `receive_buf`. + parts: VecMap<ops::Range<u32>>, + receive_buf: Vec<u8>, + result: Vec<u8>, +} + +impl DeltaReceiver { + pub fn new() -> DeltaReceiver { + Default::default() + } + pub fn reset(&mut self) { + self.previous_tick = None; + self.current = None; + } + fn can_receive(&self, tick: i32) -> bool { + self.current.as_ref().map(|c| c.tick <= tick) + .or(self.previous_tick.map(|t| t < tick)) + .unwrap_or(true) + } + fn init_delta(&mut self) { + self.parts.clear(); + self.receive_buf.clear(); + self.result.clear(); + } + fn finish_delta(&mut self, tick: i32) { + self.current = None; + self.previous_tick = Some(tick); + } + pub fn snap_empty<W>(&mut self, warn: &mut W, snap: system::SnapEmpty) + -> Result<Option<ReceivedDelta>, Error> + where W: Warn<Warning>, + { + if !self.can_receive(snap.tick) { + return Err(Error::OldDelta); + } + if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) { + warn.warn(Warning::DuplicateSnap); + } + self.init_delta(); + self.finish_delta(snap.tick); + Ok(Some(ReceivedDelta { + delta_tick: snap.tick.wrapping_sub(snap.delta_tick), + tick: snap.tick, + data_and_crc: None, + })) + } + pub fn snap_single<W>(&mut self, warn: &mut W, snap: system::SnapSingle) + -> Result<Option<ReceivedDelta>, Error> + where W: Warn<Warning>, + { + if !self.can_receive(snap.tick) { + return Err(Error::OldDelta); + } + if self.current.as_ref().map(|c| c.tick == snap.tick).unwrap_or(false) { + warn.warn(Warning::DuplicateSnap); + } + self.init_delta(); + self.finish_delta(snap.tick); + self.result.extend(snap.data); + Ok(Some(ReceivedDelta { + delta_tick: snap.tick.wrapping_sub(snap.delta_tick), + tick: snap.tick, + data_and_crc: Some((&self.result, snap.crc)), + })) + } + pub fn snap<W>(&mut self, warn: &mut W, snap: system::Snap) + -> Result<Option<ReceivedDelta>, Error> + where W: Warn<Warning>, + { + if !self.can_receive(snap.tick) { + return Err(Error::OldDelta); + } + if !(0 <= snap.num_parts && snap.num_parts <= 32) { + return Err(Error::InvalidNumParts); + } + if !(0 <= snap.part && snap.part < snap.num_parts) { + return Err(Error::InvalidPart); + } + if self.current.as_ref().map(|c| c.tick != snap.tick).unwrap_or(false) { + self.current = None; + } + if let None = self.current { + self.init_delta(); + self.current = Some(CurrentDelta { + tick: snap.tick, + delta_tick: snap.tick.wrapping_sub(snap.delta_tick), + num_parts: snap.num_parts, + crc: snap.crc, + }); + + // Checked above. + let num_parts = snap.num_parts.assert_usize(); + self.parts.reserve_len(num_parts); + } + let delta_tick; + let tick; + let crc; + let num_parts; + { + let current: &mut CurrentDelta = self.current.as_mut().unwrap(); + if snap.delta_tick != current.delta_tick + || snap.num_parts != current.num_parts + || snap.crc != current.crc + { + warn.warn(Warning::DifferingAttributes); + } + delta_tick = current.delta_tick; + tick = current.tick; + crc = current.crc; + num_parts = current.num_parts; + } + let part = snap.part.assert_usize(); + if self.parts.contains_key(part) { + return Err(Error::DuplicatePart); + } + let start = self.receive_buf.len().assert_u32(); + let end = (self.receive_buf.len() + snap.data.len()).assert_u32(); + self.receive_buf.extend(snap.data); + assert!(self.parts.insert(part, start..end).is_none()); + + if self.parts.len().assert_i32() != num_parts { + return Ok(None); + } + + self.finish_delta(tick); + self.result.reserve(self.receive_buf.len()); + for range in self.parts.values() { + self.result.extend(&self.receive_buf[to_usize(range.clone())]); + } + + Ok(Some(ReceivedDelta { + delta_tick: delta_tick, + tick: tick, + data_and_crc: Some((&self.result, crc)), + })) + } +} + +#[cfg(test)] +mod test { + use common::num::Cast; + use gamenet::msg::system::Snap; + use gamenet::msg::system::SnapEmpty; + use gamenet::msg::system::SnapSingle; + use super::DeltaReceiver; + use super::Error; + use super::ReceivedDelta; + use warn::Panic; + + #[test] + fn old() { + let mut receiver = DeltaReceiver::new(); + { + let result = receiver.snap_empty(&mut Panic, SnapEmpty { + tick: 1, + delta_tick: 2, + }).unwrap(); + + assert_eq!(result, Some(ReceivedDelta { + delta_tick: -1, + tick: 1, + data_and_crc: None, + })); + } + + assert_eq!(receiver.snap_single(&mut Panic, SnapSingle { + tick: 0, + delta_tick: 0, + data: b"123", + crc: 0, + }).unwrap_err(), Error::OldDelta); + } + + #[test] + fn reorder() { + let mut receiver = DeltaReceiver::new(); + let chunks: &[(i32, &[u8])] = &[ + (3, b"3"), + (2, b"2"), + (4, b"4_"), + (1, b"1__"), + (0, b"0"), + ]; + for &(i, c) in chunks { + let result = receiver.snap(&mut Panic, Snap { + tick: 2, + delta_tick: 1, + num_parts: chunks.len().assert_i32(), + part: i, + crc: 3, + data: c, + }).unwrap(); + if i != 0 { + assert_eq!(result, None); + } else { + assert_eq!(result, Some(ReceivedDelta { + delta_tick: 1, + tick: 2, + data_and_crc: Some((b"01__234_", 3)), + })); + } + } + } +} diff --git a/snapshot/src/snap.rs b/snapshot/src/snap.rs new file mode 100644 index 0000000..7b9768f --- /dev/null +++ b/snapshot/src/snap.rs @@ -0,0 +1,511 @@ +use buffer::CapacityError; +use common::num::Cast; +use format::DeltaHeader; +use format::Item; +use format::Warning; +use format::key; +use format::key_to_id; +use format::key_to_type_id; +use gamenet::enums::MAX_SNAPSHOT_PACKSIZE; +use gamenet::msg::system; +use packer::Packer; +use packer::Unpacker; +use packer::with_packer; +use packer; +use std::cmp; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::hash_map; +use std::fmt; +use std::iter; +use std::mem; +use std::ops; +use to_usize; +use warn::Warn; +use warn::wrap; + +// TODO: Actually obey this the same way as Teeworlds does. +pub const MAX_SNAPSHOT_SIZE: usize = 64 * 1024; // 64 KB + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + UnexpectedEnd, + IntOutOfRange, + DeletedItemsUnpacking, + ItemDiffsUnpacking, + TypeIdRange, + IdRange, + NegativeSize, + TooLongDiff, + TooLongSnap, + DeltaDifferingSizes, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum BuilderError { + DuplicateKey, + TooLongSnap, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct TooLongSnap; + +impl From<TooLongSnap> for Error { + fn from(_: TooLongSnap) -> Error { + Error::TooLongSnap + } +} + +impl From<TooLongSnap> for BuilderError { + fn from(_: TooLongSnap) -> BuilderError { + BuilderError::TooLongSnap + } +} + +impl From<packer::IntOutOfRange> for Error { + fn from(_: packer::IntOutOfRange) -> Error { + Error::IntOutOfRange + } +} + +impl From<packer::UnexpectedEnd> for Error { + fn from(_: packer::UnexpectedEnd) -> Error { + Error::UnexpectedEnd + } +} + +fn apply_delta(in_: Option<&[i32]>, delta: &[i32], out: &mut [i32]) + -> Result<(), Error> +{ + assert!(delta.len() == out.len()); + match in_ { + Some(in_) => { + if in_.len() != out.len() { + return Err(Error::DeltaDifferingSizes); + } + for i in 0..out.len() { + out[i] = in_[i].wrapping_add(delta[i]); + } + } + None => out.copy_from_slice(delta), + } + Ok(()) +} + +fn create_delta(from: Option<&[i32]>, to: &[i32], out: &mut [i32]) { + assert!(to.len() == out.len()); + match from { + Some(from) => { + assert!(from.len() == to.len()); + for i in 0..out.len() { + out[i] = to[i].wrapping_sub(from[i]); + } + }, + None => out.copy_from_slice(to), + } +} + +// TODO: Select a faster hasher? +#[derive(Clone, Default)] +pub struct Snap { + offsets: HashMap<i32, ops::Range<u32>>, + buf: Vec<i32>, +} + +impl Snap { + pub fn empty() -> Snap { + Default::default() + } + fn clear(&mut self) { + self.offsets.clear(); + self.buf.clear(); + } + fn item_from_offset(&self, offset: ops::Range<u32>) -> &[i32] { + &self.buf[to_usize(offset)] + } + pub fn item(&self, type_id: u16, id: u16) -> Option<&[i32]> { + self.offsets.get(&key(type_id, id)).map(|o| &self.buf[to_usize(o.clone())]) + } + pub fn items(&self) -> Items { + Items { + snap: self, + iter: self.offsets.iter(), + } + } + fn prepare_item_vacant<'a>(entry: hash_map::VacantEntry<'a, i32, ops::Range<u32>>, buf: &mut Vec<i32>, size: usize) + -> Result<&'a mut ops::Range<u32>, TooLongSnap> + { + let offset = buf.len(); + if offset + size > MAX_SNAPSHOT_SIZE { + return Err(TooLongSnap); + } + let start = offset.assert_u32(); + let end = (offset + size).assert_u32(); + buf.extend(iter::repeat(0).take(size)); + Ok(entry.insert(start..end)) + } + fn prepare_item(&mut self, type_id: u16, id: u16, size: usize) + -> Result<&mut [i32], Error> + { + let offset = match self.offsets.entry(key(type_id, id)) { + hash_map::Entry::Occupied(o) => o.into_mut(), + hash_map::Entry::Vacant(v) => Snap::prepare_item_vacant(v, &mut self.buf, size)?, + }.clone(); + Ok(&mut self.buf[to_usize(offset)]) + } + pub fn read_with_delta<W>(&mut self, warn: &mut W, from: &Snap, delta: &Delta) + -> Result<(), Error> + where W: Warn<Warning>, + { + self.clear(); + + let mut num_deletions = 0; + for item in from.items() { + if !delta.deleted_items.contains(&item.key()) { + let out = self.prepare_item(item.type_id, item.id, item.data.len())?; + out.copy_from_slice(item.data); + } else { + num_deletions += 1; + } + } + if num_deletions != delta.deleted_items.len() { + warn.warn(Warning::UnknownDelete); + } + + for (&key, offset) in &delta.updated_items { + let type_id = key_to_type_id(key); + let id = key_to_id(key); + let diff = &delta.buf[to_usize(offset.clone())]; + let out = self.prepare_item(type_id, id, diff.len())?; + let in_ = from.item(type_id, id); + + apply_delta(in_, diff, out)?; + } + Ok(()) + } + pub fn write<'d, 's>(&self, buf: &mut Vec<i32>, mut p: Packer<'d, 's>) + -> Result<&'d [u8], CapacityError> + { + let keys = buf; + keys.clear(); + keys.extend(self.offsets.keys().cloned()); + keys.sort_unstable_by_key(|&k| k as u32); + let data_size = self.buf.len() + .checked_add(self.offsets.len()).expect("snap size overflow") + .checked_mul(mem::size_of::<i32>()).expect("snap size overflow") + .assert_i32(); + p.write_int(data_size)?; + let num_items = self.offsets.len().assert_i32(); + p.write_int(num_items)?; + + let mut offset = 0; + for &key in &*keys { + p.write_int(offset)?; + let key_offset = self.offsets[&key].clone(); + offset = offset + .checked_add((key_offset.end - key_offset.start + 1).usize() + .checked_mul(mem::size_of::<i32>()) + .expect("item size overflow").assert_i32()) + .expect("offset overflow"); + } + for &key in &*keys { + p.write_int(key)?; + for &i in &self.buf[to_usize(self.offsets[&key].clone())] { + p.write_int(i)?; + } + } + Ok(p.written()) + } + pub fn crc(&self) -> i32 { + self.buf.iter().fold(0, |s, &a| s.wrapping_add(a)) + } + pub fn recycle(mut self) -> Builder { + self.clear(); + Builder { + snap: self, + } + } +} + +pub struct Items<'a> { + snap: &'a Snap, + iter: hash_map::Iter<'a, i32, ops::Range<u32>>, +} + +impl<'a> Iterator for Items<'a> { + type Item = Item<'a>; + fn next(&mut self) -> Option<Item<'a>> { + self.iter.next().map(|(&k, o)| { + Item::from_key(k, self.snap.item_from_offset(o.clone())) + }) + } + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl<'a> ExactSizeIterator for Items<'a> { + fn len(&self) -> usize { + self.iter.len() + } +} + +impl fmt::Debug for Snap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map().entries(self.items().map( + |Item { type_id, id, data }| ((type_id, id), data) + )).finish() + } +} + +#[derive(Clone, Default)] +pub struct Delta { + deleted_items: HashSet<i32>, + updated_items: HashMap<i32, ops::Range<u32>>, + buf: Vec<i32>, +} + +impl Delta { + pub fn new() -> Delta { + Default::default() + } + pub fn clear(&mut self) { + self.deleted_items.clear(); + self.updated_items.clear(); + self.buf.clear(); + } + fn prepare_update_item(&mut self, type_id: u16, id: u16, size: usize) -> &mut [i32] { + let key = key(type_id, id); + + let offset = self.buf.len(); + let start = offset.assert_u32(); + let end = (offset + size).assert_u32(); + self.buf.extend(iter::repeat(0).take(size)); + assert!(self.updated_items.insert(key, start..end).is_none()); + &mut self.buf[to_usize(start..end)] + } + pub fn create(&mut self, from: &Snap, to: &Snap) { + self.clear(); + for Item { type_id, id, .. } in from.items() { + if to.item(type_id, id).is_none() { + assert!(self.deleted_items.insert(key(type_id, id))); + } + } + for Item { type_id, id, data } in to.items() { + let from_data = from.item(type_id, id); + let out_delta = self.prepare_update_item(type_id, id, data.len()); + create_delta(from_data, data, out_delta); + } + } + pub fn write<'d, 's, O>(&self, object_size: O, mut p: Packer<'d, 's>) + -> Result<&'d [u8], CapacityError> + where O: FnMut(u16) -> Option<u32>, + { + let mut object_size = object_size; + with_packer(&mut p, |p| DeltaHeader { + num_deleted_items: self.deleted_items.len().assert_i32(), + num_updated_items: self.updated_items.len().assert_i32() + }.encode(p))?; + for &key in &self.deleted_items { + p.write_int(key)?; + } + for (&key, range) in &self.updated_items { + let data = &self.buf[to_usize(range.clone())]; + let type_id = key_to_type_id(key); + let id = key_to_id(key); + p.write_int(type_id.i32())?; + p.write_int(id.i32())?; + match object_size(type_id) { + Some(size) => assert!(size.usize() == data.len()), + None => p.write_int(data.len().assert_i32())?, + } + for &d in data { + p.write_int(d)?; + } + } + Ok(p.written()) + } +} + +#[derive(Clone, Default)] +pub struct DeltaReader { + buf: Vec<i32>, +} + +impl DeltaReader { + pub fn new() -> DeltaReader { + Default::default() + } + fn clear(&mut self) { + self.buf.clear(); + } + pub fn read<W, O>(&mut self, warn: &mut W, delta: &mut Delta, object_size: O, p: &mut Unpacker) + -> Result<(), Error> + where W: Warn<Warning>, + O: FnMut(u16) -> Option<u32>, + { + delta.clear(); + self.clear(); + + let mut object_size = object_size; + + let header = DeltaHeader::decode(warn, p)?; + while !p.as_slice().is_empty() { + self.buf.push(p.read_int(wrap(warn))?); + } + let split = header.num_deleted_items.assert_usize(); + if split > self.buf.len() { + return Err(Error::DeletedItemsUnpacking); + } + let (deleted_items, buf) = self.buf.split_at(split); + delta.deleted_items.extend(deleted_items); + if deleted_items.len() != delta.deleted_items.len() { + warn.warn(Warning::DuplicateDelete); + } + + let mut num_updates = 0; + let mut buf = buf.iter(); + // FIXME: Use `is_empty`. + while buf.len() != 0 { + let type_id = buf.next().ok_or(Error::ItemDiffsUnpacking)?; + let id = buf.next().ok_or(Error::ItemDiffsUnpacking)?; + + let type_id = type_id.try_u16().ok_or(Error::TypeIdRange)?; + let id = id.try_u16().ok_or(Error::IdRange)?; + + let size = match object_size(type_id) { + Some(s) => s.usize(), + None => { + let s = buf.next().ok_or(Error::ItemDiffsUnpacking)?; + s.try_usize().ok_or(Error::NegativeSize)? + } + }; + + if size > buf.len() { + return Err(Error::ItemDiffsUnpacking); + } + let (data, b) = buf.as_slice().split_at(size); + buf = b.iter(); + + let offset = delta.buf.len(); + let start = offset.try_u32().ok_or(Error::TooLongDiff)?; + let end = (offset + data.len()).try_u32().ok_or(Error::TooLongDiff)?; + delta.buf.extend(data.iter()); + + // In case of conflict, take later update (as the original code does). + if delta.updated_items.insert(key(type_id, id), start..end).is_some() { + warn.warn(Warning::DuplicateUpdate); + } + + if delta.deleted_items.contains(&key(type_id, id)) { + warn.warn(Warning::DeleteUpdate); + } + num_updates += 1; + } + + if num_updates != header.num_updated_items { + warn.warn(Warning::NumUpdatedItems); + } + + Ok(()) + } +} + +#[derive(Default)] +pub struct Builder { + snap: Snap, +} + +impl Builder { + pub fn new() -> Builder { + Default::default() + } + pub fn add_item(&mut self, type_id: u16, id: u16, data: &[i32]) + -> Result<(), BuilderError> + { + let offset = match self.snap.offsets.entry(key(type_id, id)) { + hash_map::Entry::Occupied(..) => return Err(BuilderError::DuplicateKey), + hash_map::Entry::Vacant(v) => { + Snap::prepare_item_vacant(v, &mut self.snap.buf, data.len())? + } + }.clone(); + self.snap.buf[to_usize(offset)].copy_from_slice(data); + Ok(()) + } + pub fn finish(self) -> Snap { + self.snap + } +} + +pub fn delta_chunks(tick: i32, delta_tick: i32, data: &[u8], crc: i32) -> DeltaChunks { + DeltaChunks { + tick: tick, + delta_tick: tick - delta_tick, + crc: crc, + cur_part: if !data.is_empty() { 0 } else { -1 }, + num_parts: ((data.len() + MAX_SNAPSHOT_PACKSIZE as usize - 1) / MAX_SNAPSHOT_PACKSIZE as usize).assert_i32(), + data: data, + } +} + +impl<'a> Into<system::System<'a>> for SnapMsg<'a> { + fn into(self) -> system::System<'a> { + match self { + SnapMsg::Snap(s) => system::System::Snap(s), + SnapMsg::SnapEmpty(s) => system::System::SnapEmpty(s), + SnapMsg::SnapSingle(s) => system::System::SnapSingle(s), + } + } +} + +#[derive(Clone, Copy)] +pub enum SnapMsg<'a> { + Snap(system::Snap<'a>), + SnapEmpty(system::SnapEmpty), + SnapSingle(system::SnapSingle<'a>), +} + +pub struct DeltaChunks<'a> { + tick: i32, + delta_tick: i32, + crc: i32, + cur_part: i32, + num_parts: i32, + data: &'a [u8], +} + +impl<'a> Iterator for DeltaChunks<'a> { + type Item = SnapMsg<'a>; + fn next(&mut self) -> Option<SnapMsg<'a>> { + if self.cur_part == self.num_parts { + return None; + } + let result = if self.num_parts == 0 { + SnapMsg::SnapEmpty(system::SnapEmpty { + tick: self.tick, + delta_tick: self.delta_tick, + }) + } else if self.num_parts == 1 { + SnapMsg::SnapSingle(system::SnapSingle { + tick: self.tick, + delta_tick: self.delta_tick, + crc: self.crc, + data: self.data, + }) + } else { + let index = self.cur_part.assert_usize(); + let start = MAX_SNAPSHOT_PACKSIZE as usize * index; + let end = cmp::min(MAX_SNAPSHOT_PACKSIZE as usize * (index + 1), self.data.len()); + SnapMsg::Snap(system::Snap { + tick: self.tick, + delta_tick: self.delta_tick, + num_parts: self.num_parts, + part: self.cur_part, + crc: self.crc, + data: &self.data[start..end], + }) + }; + self.cur_part += 1; + Some(result) + } +} diff --git a/snapshot/src/storage.rs b/snapshot/src/storage.rs new file mode 100644 index 0000000..c445d04 --- /dev/null +++ b/snapshot/src/storage.rs @@ -0,0 +1,176 @@ +use format; +use snap::Builder; +use snap::Delta; +use snap::Snap; +use snap; +use std::collections::VecDeque; +use warn::Warn; +use warn::wrap; + +// TODO: Separate server storage from client storage. +// TODO: Delete snapshots over time. + +#[derive(Clone)] +struct StoredSnap { + snap: Snap, + tick: i32, +} + +const MAX_STORED_SNAPSHOT: usize = 100; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UnknownSnap; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WeirdNegativeDeltaTick; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + OldDelta, + UnknownSnap, + InvalidCrc, + Unpack(snap::Error), +} + +impl From<snap::Error> for Error { + fn from(err: snap::Error) -> Error { + Error::Unpack(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Warning { + WeirdNegativeDeltaTick, + Unpack(format::Warning), +} + +impl From<format::Warning> for Warning { + fn from(w: format::Warning) -> Warning { + Warning::Unpack(w) + } +} + +#[derive(Clone, Default)] +pub struct Storage { + /// Queue that stores received snaps. + /// + /// The newest elements are in the front. + snaps: VecDeque<StoredSnap>, + free: Vec<Snap>, + ack_tick: Option<i32>, + delta: Delta, + delta_tick: Option<i32>, +} + +impl Storage { + pub fn new() -> Storage { + Default::default() + } + pub fn reset(&mut self) { + let self_free = &mut self.free; + // FIXME: Replace with something like `exhaust`. + self.snaps.drain(..).map(|s| self_free.push(s.snap)).count(); + self.ack_tick = None; + } + pub fn ack_tick(&self) -> Option<i32> { + self.ack_tick + } + pub fn add_delta<W>(&mut self, warn: &mut W, crc: Option<i32>, delta_tick: i32, tick: i32, delta: &Delta) + -> Result<&Snap, Error> + where W: Warn<Warning>, + { + if self.snaps.front().map(|s| s.tick).unwrap_or(-1) >= tick { + return Err(Error::OldDelta); + } + { + let empty = Snap::empty(); + let delta_snap; + if delta_tick >= 0 { + if let Some(i) = self.snaps.iter().position(|s| s.tick < delta_tick) { + let self_free = &mut self.free; + // FIXME: Replace with something like `exhaust`. + self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count(); + } + if let Some(d) = self.snaps.back() { + if d.tick == delta_tick { + delta_snap = &d.snap; + } else { + self.ack_tick = None; + return Err(Error::UnknownSnap); + } + } else { + self.ack_tick = None; + return Err(Error::UnknownSnap); + } + } else { + delta_snap = ∅ + if delta_tick != -1 { + warn.warn(Warning::WeirdNegativeDeltaTick); + } + } + if self.free.is_empty() { + self.free.push(Snap::empty()); + } + + let new_snap: &mut Snap = self.free.last_mut().unwrap(); + new_snap.read_with_delta(wrap(warn), delta_snap, delta)?; + if crc.map(|crc| crc != new_snap.crc()).unwrap_or(false) { + self.ack_tick = None; + return Err(Error::InvalidCrc); + } + self.ack_tick = Some(tick); + } + self.snaps.push_front(StoredSnap { + tick: tick, + snap: self.free.pop().unwrap(), + }); + if self.snaps.len() > MAX_STORED_SNAPSHOT { + self.free.push(self.snaps.pop_back().unwrap().snap); + } + Ok(&self.snaps.front().unwrap().snap) + } + pub fn new_builder(&mut self) -> Builder { + self.free.pop().unwrap_or_default().recycle() + } + pub fn set_delta_tick<W>(&mut self, warn: &mut W, tick: i32) + -> Result<(), UnknownSnap> + where W: Warn<WeirdNegativeDeltaTick>, + { + if tick < 0 { + if tick != -1 { + warn.warn(WeirdNegativeDeltaTick); + } + self.delta_tick = None; + return Ok(()); + } + if let Some(i) = self.snaps.iter().position(|s| s.tick < tick) { + let self_free = &mut self.free; + // FIXME: Replace with something like `exhaust`. + self.snaps.drain(i..).map(|s| self_free.push(s.snap)).count(); + } + if !self.snaps.back().map(|s| s.tick == tick).unwrap_or(false) { + self.delta_tick = None; + return Err(UnknownSnap); + } + self.delta_tick = Some(tick); + Ok(()) + } + pub fn delta_tick(&self) -> Option<i32> { + self.delta_tick + } + pub fn add_snap(&mut self, tick: i32, snap: Snap) -> &Delta { + self.snaps.push_front(StoredSnap { + snap: snap, + tick: tick, + }); + let tmp; + let delta_snap = if self.delta_tick.is_some() { + &self.snaps.back().unwrap().snap + } else { + tmp = Snap::empty(); + &tmp + }; + self.delta.create(delta_snap, &self.snaps.front().unwrap().snap); + &self.delta + } +} |