Compare commits

..

No commits in common. "5f5e10d39f09f4d60b4301e76f0158636afee6d1" and "816cc2c8c3e41012e622b7d25838717b7f27c332" have entirely different histories.

12 changed files with 574 additions and 851 deletions

2
.envrc
View file

@ -1 +1 @@
use flake use nix

487
Cargo.lock generated
View file

@ -3,10 +3,19 @@
version = 3 version = 3
[[package]] [[package]]
name = "anstream" name = "aho-corasick"
version = "0.6.18" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -19,62 +28,49 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.6" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.7" version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell", "windows-sys 0.52.0",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -84,9 +80,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.37" version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -94,9 +90,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.37" version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -106,9 +102,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.32" version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -118,38 +114,27 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.3" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "convert_case"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.29.0" version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm_winapi", "crossterm_winapi",
"derive_more", "libc",
"document-features",
"mio", "mio",
"parking_lot", "parking_lot",
"rustix",
"signal-hook", "signal-hook",
"signal-hook-mio", "signal-hook-mio",
"winapi", "winapi",
@ -165,56 +150,33 @@ dependencies = [
] ]
[[package]] [[package]]
name = "derive_more" name = "env_filter"
version = "2.0.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [ dependencies = [
"derive_more-impl", "log",
"regex",
] ]
[[package]] [[package]]
name = "derive_more-impl" name = "env_logger"
version = "2.0.1" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [ dependencies = [
"convert_case", "anstream",
"proc-macro2", "anstyle",
"quote", "env_filter",
"syn", "humantime",
"log",
] ]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
name = "errno"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.16" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -228,28 +190,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "humantime"
version = "1.70.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.172" version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -263,33 +219,33 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.27" version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.3" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi",
"windows-sys 0.52.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -305,58 +261,49 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets", "windows-targets 0.52.5",
] ]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.9.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e"
dependencies = [ dependencies = [
"libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
"zerocopy",
] ]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.3.1" version = "0.9.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "78674ef918c19451dbd250f8201f8619b494f64c9aa6f3adb28fd8a0f1f6da46"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core",
@ -364,61 +311,77 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.9.0-alpha.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"zerocopy",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.11" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "rustix" name = "regex"
version = "1.0.7" version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [ dependencies = [
"bitflags", "aho-corasick",
"errno", "memchr",
"libc", "regex-automata",
"linux-raw-sys", "regex-syntax",
"windows-sys 0.59.0",
] ]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "servicepoint"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce70bae3641ccafdeb9832ac367efd51243e0708ef35151ad8c2c4ee578aa4a"
dependencies = [
"bitvec",
"log",
"rand",
"thiserror",
]
[[package]] [[package]]
name = "servicepoint-life" name = "servicepoint-life"
version = "0.2.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"env_logger",
"log",
"rand", "rand",
"servicepoint", "servicepoint2",
]
[[package]]
name = "servicepoint2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d9aecc4d31a71578481de6c6d64383d374126c38469c9689067579c1d910fd"
dependencies = [
"log",
] ]
[[package]] [[package]]
@ -433,9 +396,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-mio" name = "signal-hook-mio"
version = "0.2.4" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [ dependencies = [
"libc", "libc",
"mio", "mio",
@ -444,18 +407,18 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.0" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -465,58 +428,26 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.101" version = "2.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.2" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -548,109 +479,157 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.5",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.48.5",
"windows_i686_gnu", "windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm", "windows_i686_gnullvm",
"windows_i686_msvc", "windows_i686_msvc 0.52.5",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.5",
] ]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "wyz" name = "windows_x86_64_msvc"
version = "0.5.1" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
dependencies = [
"tap",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -1,34 +1,12 @@
[package] [package]
name = "servicepoint-life" name = "servicepoint-life"
version = "0.2.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later"
description = "Small terminal app that generates conways game of life rules and simulates them on the servicepoint display."
repository = "https://git.berlin.ccc.de/vinzenz/servicepoint-life"
readme = "README.md"
keywords = ["cccb", "cccb-servicepoint", "conway", "game-of-life", "simulation"]
rust-version = "1.70.0"
[dependencies] [dependencies]
clap = { version = "4.5", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
rand = "0.8" rand = "0.9.0-alpha.1"
crossterm = "0.29" env_logger = "0.11.3"
servicepoint2 = { version = "0.4.2", default-features = false }
[dependencies.servicepoint] crossterm = "0.27.0"
package = "servicepoint" log = "0.4.21"
version = "0.14.0"
features = ["rand"]
default-features = false
[lints.clippy]
incompatible_msrv = "forbid"
[profile.release]
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
[profile.size-optimized]
inherits = "release"
opt-level = 'z' # Optimize for size
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary

View file

@ -1,13 +1,5 @@
# servicepoint-life # servicepoint-life
This is a small terminal app that generates conways game of life rules and simulates them on the servicepoint display. More fully featured game of life for the servicepoint display based on the example in the repo.
It uses the [servicepoint](https://git.berlin.ccc.de/servicepoint/servicepoint/) library for the display commands.
## Running
With Nix flakes, you can just `nix run git+https://git.berlin.ccc.de/vinzenz/servicepoint-life`.
Otherwise, you can `cargo install --git https://git.berlin.ccc.de/vinzenz/servicepoint-life` and then run `servicepoint-life`.
If you want to poke at the code you can always check out the repo and `cargo run` it.

View file

@ -1,64 +0,0 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1745925850,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"owner": "nix-community",
"repo": "naersk",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1746183838,
"narHash": "sha256-kwaaguGkAqTZ1oK0yXeQ3ayYjs8u/W7eEfrFpFfIDFA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "bf3287dac860542719fe7554e21e686108716879",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"naersk": "naersk",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

103
flake.nix
View file

@ -1,103 +0,0 @@
{
description = "Flake for servicepoint-life";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
nix-filter.url = "github:numtide/nix-filter";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{
self,
nixpkgs,
naersk,
nix-filter,
}:
let
lib = nixpkgs.lib;
supported-systems = [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
forAllSystems =
f:
lib.genAttrs supported-systems (
system:
f rec {
pkgs = nixpkgs.legacyPackages.${system};
naersk' = pkgs.callPackage naersk { };
selfPkgs = self.packages."${system}";
inherit system;
}
);
in
rec {
packages = forAllSystems (
{ pkgs, naersk', ... }:
rec {
servicepoint-life = naersk'.buildPackage rec {
strictDeps = true;
nativeBuildInputs = with pkgs; [ pkg-config ];
buildInputs = with pkgs; [
xe
xz
];
src = nix-filter.lib.filter {
root = ./.;
include = [
./Cargo.toml
./Cargo.lock
./src
./README.md
./LICENSE
];
};
};
default = servicepoint-life;
}
);
legacyPackages = packages;
apps = forAllSystems (
{ selfPkgs, ... }:
rec {
servicepoint-life = {
type = "app";
program = "${selfPkgs.servicepoint-life}/bin/servicepoint-life";
};
default = servicepoint-life;
}
);
devShells = forAllSystems (
{ pkgs, selfPkgs, ... }:
{
default = pkgs.mkShell rec {
inputsFrom = [ selfPkgs.default ];
packages = [
pkgs.gdb
(pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [
rustc
cargo
rustfmt
clippy
];
})
];
};
}
);
formatter = forAllSystems ({ pkgs, ... }: pkgs.nixfmt-rfc-style);
};
}

4
shell.nix Normal file
View file

@ -0,0 +1,4 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [ rustup cargo pkg-config xe lzma ];
}

View file

@ -1,153 +0,0 @@
use crate::{
print::{println_debug, println_info, println_warning},
simulation::{Simulation, SimulationEvent},
Cli,
};
use crossterm::{
event,
event::{Event, KeyCode, KeyEvent, KeyEventKind},
execute,
terminal::{
disable_raw_mode, enable_raw_mode, EnableLineWrap, EnterAlternateScreen,
LeaveAlternateScreen,
},
};
use servicepoint::{
Bitmap, BitmapCommand, BrightnessGrid, BrightnessGridCommand, UdpSocketExt,
FRAME_PACING, TILE_HEIGHT, TILE_WIDTH,
};
use std::{
io::stdout,
net::UdpSocket,
thread,
time::{Duration, Instant},
};
pub(crate) struct App {
connection: UdpSocket,
sim: Simulation,
target_duration: Duration,
pixels: Bitmap,
luma: BrightnessGrid,
terminated: bool,
}
impl App {
pub fn new(cli: Cli) -> Self {
let connection = UdpSocket::bind_connect(cli.destination)
.expect("Could not connect. Did you forget `--destination`?");
execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
.expect("could not enter alternate screen");
enable_raw_mode().expect("could not enable raw terminal mode");
Self {
connection,
sim: Simulation::new(),
terminated: false,
target_duration: FRAME_PACING * 4,
pixels: Bitmap::max_sized(),
luma: BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT),
}
}
pub(crate) fn run_iteration(&mut self) {
let start = Instant::now();
self.sim.run_iteration();
self.sim.draw_state(&mut self.pixels, &mut self.luma);
let cmd: BitmapCommand = self.pixels.clone().into();
self.connection.send_command(cmd).unwrap();
let cmd: BrightnessGridCommand = self.luma.clone().into();
self.connection.send_command(cmd).unwrap();
self.poll_events();
let tick_time = start.elapsed();
if tick_time < self.target_duration {
thread::sleep(self.target_duration - tick_time);
}
}
pub(crate) fn terminated(&self) -> bool {
self.terminated
}
fn poll_events(&mut self) -> bool {
while event::poll(Duration::from_secs(0)).expect("could not poll") {
let event = event::read().expect("could not read event");
if let Event::Key(KeyEvent {
kind: KeyEventKind::Press,
code,
..
}) = event
{
if let Some(sim_event) = self.handle_key(code) {
self.sim.handle_event(sim_event);
};
} else {
println_debug(format!("unhandled event {event:?}"));
}
}
false
}
fn handle_key(&mut self, code: KeyCode) -> Option<SimulationEvent> {
match code {
KeyCode::Char('d') => Some(SimulationEvent::RandomizeLeftPixels),
KeyCode::Char('e') => Some(SimulationEvent::RandomizeLeftLuma),
KeyCode::Char('f') => Some(SimulationEvent::RandomizeRightPixels),
KeyCode::Char('r') => Some(SimulationEvent::RandomizeRightLuma),
KeyCode::Right => Some(SimulationEvent::SeparatorAccelerate),
KeyCode::Left => Some(SimulationEvent::SeparatorDecelerate),
KeyCode::Char('h') => {
println_info("[h] help");
println_info("[q] quit");
println_info("[d] randomize left pixels");
println_info("[e] randomize left luma");
println_info("[r] randomize right pixels");
println_info("[f] randomize right luma");
println_info("[→] accelerate divider right");
println_info("[←] accelerate divider left");
None
}
KeyCode::Char('q') => {
println_warning("terminating");
self.terminated = true;
None
}
KeyCode::Up => {
self.target_duration = self
.target_duration
.saturating_sub(Duration::from_millis(1));
println_info(format!(
"increased simulation speed to {} ups",
1f64 / self.target_duration.as_secs_f64()
));
None
}
KeyCode::Down => {
self.target_duration = self
.target_duration
.saturating_add(Duration::from_millis(1));
println_info(format!(
"decreased simulation speed to {} ups",
1f64 / self.target_duration.as_secs_f64()
));
None
}
key_code => {
println_debug(format!("unhandled KeyCode {key_code:?}"));
None
}
}
}
}
impl Drop for App {
fn drop(&mut self) {
disable_raw_mode().expect("could not disable raw terminal mode");
execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen");
}
}

View file

@ -1,21 +1,29 @@
use servicepoint::{Grid, Value, ValueGrid}; use servicepoint2::Grid;
use crate::rules::Rules; use crate::rules::Rules;
pub(crate) struct Game<T: Value> pub(crate) struct Game<TState, TGrid, TKernel, const KERNEL_SIZE: usize>
where
TGrid: Grid<TState>,
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub field: ValueGrid<T>, pub field: TGrid,
pub rules: Rules<T>, pub rules: Rules<TState, TKernel, KERNEL_SIZE>,
} }
impl<T: Value> Game<T> impl<TState, TGrid, TKernel, const KERNEL_SIZE: usize> Game<TState, TGrid, TKernel, KERNEL_SIZE>
where
TGrid: Grid<TState>,
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub fn step(&mut self) { pub fn step(&mut self) {
self.field = self.field_iteration(); self.field = self.field_iteration();
} }
fn field_iteration(&self) -> ValueGrid<T> { fn field_iteration(&self) -> TGrid {
let mut next = ValueGrid::new(self.field.width(), self.field.height()); let mut next = TGrid::new(self.field.width(), self.field.height());
for x in 0..self.field.width() { for x in 0..self.field.width() {
for y in 0..self.field.height() { for y in 0..self.field.height() {
let old_state = self.field.get(x, y); let old_state = self.field.get(x, y);
@ -33,12 +41,14 @@ impl<T: Value> Game<T>
let mut count = 0; let mut count = 0;
let kernel = &self.rules.kernel; let kernel = &self.rules.kernel;
assert_eq!(KERNEL_SIZE % 2, 1);
let offset = KERNEL_SIZE as i32 / 2;
for (kernel_y, kernel_row) in kernel.iter().enumerate() { for (kernel_y, kernel_row) in kernel.iter().enumerate() {
let offset_y = kernel_y as i32 - 1; let offset_y = kernel_y as i32 - offset;
for (kernel_x, kernel_value) in kernel_row.iter().enumerate() { for (kernel_x, kernel_value) in kernel_row.iter().enumerate() {
let offset_x = kernel_x as i32 - 1; let offset_x = kernel_x as i32 - offset;
let neighbor_x = x + offset_x; let neighbor_x = x + offset_x;
let neighbor_y = y + offset_y; let neighbor_y = y + offset_y;

View file

@ -1,11 +1,30 @@
use crate::app::App; use std::io::stdout;
use clap::Parser; use std::num::Wrapping;
use std::thread;
use std::time::{Duration, Instant};
use clap::Parser;
use crossterm::{event, execute};
use crossterm::event::{Event, KeyCode, KeyEventKind};
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen,
};
use log::LevelFilter;
use rand::distributions::{Distribution, Standard};
use rand::Rng;
use servicepoint2::{
ByteGrid, CompressionCode, Connection, FRAME_PACING, Grid, Origin, PixelGrid, TILE_HEIGHT,
TILE_WIDTH,
};
use servicepoint2::Command::{BitmapLinearWin, CharBrightness};
use crate::game::Game;
use crate::print::{println_debug, println_info, println_warning};
use crate::rules::{generate_bb3, generate_u8b3};
mod app;
mod game; mod game;
mod print; mod print;
mod rules; mod rules;
mod simulation;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
@ -14,8 +33,258 @@ struct Cli {
} }
fn main() { fn main() {
let mut app = App::new(Cli::parse()); let connection = init();
while !app.terminated() {
app.run_iteration(); let mut left_pixels = Game {
rules: generate_bb3(),
field: PixelGrid::max_sized(),
};
let mut right_pixels = Game {
rules: generate_bb3(),
field: PixelGrid::max_sized(),
};
let mut left_luma = Game {
rules: generate_u8b3(),
field: ByteGrid::new(TILE_WIDTH, TILE_HEIGHT),
};
let mut right_luma = Game {
rules: generate_u8b3(),
field: ByteGrid::new(TILE_WIDTH, TILE_HEIGHT),
};
randomize(&mut left_luma.field);
randomize(&mut left_pixels.field);
randomize(&mut right_luma.field);
randomize(&mut right_pixels.field);
let mut pixels = PixelGrid::max_sized();
let mut luma = ByteGrid::new(TILE_WIDTH, TILE_HEIGHT);
let mut split_pixel = 0;
let mut split_speed: i32 = 1;
let mut iteration = Wrapping(0u8);
let mut target_duration = FRAME_PACING;
loop {
let start = Instant::now();
left_pixels.step();
right_pixels.step();
if iteration % Wrapping(10) == Wrapping(0) {
left_luma.step();
right_luma.step();
}
iteration += Wrapping(1u8);
if split_speed > 0 && split_pixel == pixels.width() {
split_pixel = 0;
(left_luma, right_luma) = (right_luma, left_luma);
(left_pixels, right_pixels) = (right_pixels, left_pixels);
randomize(&mut left_pixels.field);
randomize(&mut left_luma.field);
left_pixels.rules = generate_bb3();
left_luma.rules = generate_u8b3();
} else if split_speed < 0 && split_pixel == 0 {
split_pixel = pixels.width();
(left_luma, right_luma) = (right_luma, left_luma);
(left_pixels, right_pixels) = (right_pixels, left_pixels);
randomize(&mut right_pixels.field);
randomize(&mut right_luma.field);
right_pixels.rules = generate_bb3();
right_luma.rules = generate_u8b3();
}
split_pixel =
i32::clamp(split_pixel as i32 + split_speed, 0, pixels.width() as i32) as usize;
draw_pixels(
&mut pixels,
&left_pixels.field,
&right_pixels.field,
split_pixel,
);
draw_luma(
&mut luma,
&left_luma.field,
&right_luma.field,
split_pixel / 8,
);
send_to_screen(&connection, &pixels, &luma);
while event::poll(Duration::from_secs(0)).expect("could not poll") {
match event::read().expect("could not read event").try_into() {
Err(_) => {}
Ok(AppEvent::RandomizeLeftPixels) => {
randomize(&mut left_pixels.field);
println_debug("randomized left pixels");
}
Ok(AppEvent::RandomizeRightPixels) => {
randomize(&mut right_pixels.field);
println_info("randomized right pixels");
}
Ok(AppEvent::RandomizeLeftLuma) => {
randomize(&mut left_luma.field);
println_info("randomized left luma");
}
Ok(AppEvent::RandomizeRightLuma) => {
randomize(&mut right_luma.field);
println_info("randomized right luma");
}
Ok(AppEvent::SeparatorAccelerate) => {
split_speed += 1;
println_info(format!("increased separator speed to {split_speed}"));
}
Ok(AppEvent::SeparatorDecelerate) => {
split_speed -= 1;
println_info(format!("decreased separator speed to {split_speed}"));
}
Ok(AppEvent::Close) => {
println_warning("terminating");
de_init();
return;
}
Ok(AppEvent::SimulationSpeedUp) => {
target_duration = target_duration.saturating_sub(Duration::from_millis(1));
println_info(format!("increased simulation speed to {} ups", 1f64 / target_duration.as_secs_f64()));
}
Ok(AppEvent::SimulationSpeedDown) => {
target_duration = target_duration.saturating_add(Duration::from_millis(1));
println_info(format!("decreased simulation speed to {} ups", 1f64 / target_duration.as_secs_f64()));
}
}
}
let tick_time = start.elapsed();
if tick_time < target_duration {
thread::sleep(target_duration - tick_time);
}
} }
} }
enum AppEvent {
Close,
RandomizeLeftPixels,
RandomizeRightPixels,
RandomizeLeftLuma,
RandomizeRightLuma,
SeparatorAccelerate,
SeparatorDecelerate,
SimulationSpeedUp,
SimulationSpeedDown,
}
impl TryFrom<Event> for AppEvent {
type Error = ();
fn try_from(event: Event) -> Result<Self, Self::Error> {
match event {
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
match key_event.code {
KeyCode::Char('h') => {
println_info("[h] help");
println_info("[q] quit");
println_info("[d] randomize left pixels");
println_info("[e] randomize left luma");
println_info("[r] randomize right pixels");
println_info("[f] randomize right luma");
println_info("[→] accelerate divider right");
println_info("[←] accelerate divider left");
Err(())
}
KeyCode::Char('q') => Ok(AppEvent::Close),
KeyCode::Char('d') => Ok(AppEvent::RandomizeLeftPixels),
KeyCode::Char('e') => Ok(AppEvent::RandomizeLeftLuma),
KeyCode::Char('f') => Ok(AppEvent::RandomizeRightPixels),
KeyCode::Char('r') => Ok(AppEvent::RandomizeRightLuma),
KeyCode::Right => Ok(AppEvent::SeparatorAccelerate),
KeyCode::Left => Ok(AppEvent::SeparatorDecelerate),
KeyCode::Up => Ok(AppEvent::SimulationSpeedUp),
KeyCode::Down => Ok(AppEvent::SimulationSpeedDown),
key_code => {
println_debug(format!("unhandled KeyCode {key_code:?}"));
Err(())
}
}
}
event => {
println_debug(format!("unhandled event {event:?}"));
Err(())
}
}
}
}
fn draw_pixels(pixels: &mut PixelGrid, left: &PixelGrid, right: &PixelGrid, split_index: usize) {
for x in 0..pixels.width() {
let left_or_right = if x < split_index { left } else { right };
for y in 0..pixels.height() {
let set = x == split_index || left_or_right.get(x, y);
pixels.set(x, y, set);
}
}
}
fn draw_luma(luma: &mut ByteGrid, left: &ByteGrid, right: &ByteGrid, split_tile: usize) {
for x in 0..luma.width() {
let left_or_right = if x < split_tile { left } else { right };
for y in 0..luma.height() {
let set = u8::max(48, left_or_right.get(x, y));
let set = set as f32 / u8::MAX as f32 * 12f32;
luma.set(x, y, set as u8);
}
}
}
fn send_to_screen(connection: &Connection, pixels: &PixelGrid, luma: &ByteGrid) {
let pixel_cmd = BitmapLinearWin(Origin(0, 0), pixels.clone(), CompressionCode::Uncompressed);
connection
.send(pixel_cmd.into())
.expect("could not send pixels");
connection
.send(CharBrightness(Origin(0, 0), luma.clone()).into())
.expect("could not send brightness");
}
fn randomize<TGrid, TValue>(field: &mut TGrid)
where
TGrid: Grid<TValue>,
Standard: Distribution<TValue>,
{
let mut rng = rand::thread_rng();
for y in 0..field.height() {
for x in 0..field.width() {
field.set(x, y, rng.gen());
}
}
}
fn init() -> Connection {
env_logger::builder()
.filter_level(LevelFilter::Info)
.parse_default_env()
.init();
execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
.expect("could not enter alternate screen");
enable_raw_mode().expect("could not enable raw terminal mode");
Connection::open(Cli::parse().destination)
.expect("Could not connect. Did you forget `--destination`?")
}
fn de_init() {
disable_raw_mode().expect("could not disable raw terminal mode");
execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen");
}

View file

@ -1,30 +1,30 @@
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use servicepoint::Value;
use crate::print::println_info; use crate::print::println_info;
pub struct Rules<T: Value> const MAX_BRIGHTNESS: u8 = 12;
pub struct Rules<TState, TKernel, const KERNEL_SIZE: usize>
where
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub kernel: Kernel3x3, pub kernel: [[TKernel; KERNEL_SIZE]; KERNEL_SIZE],
pub count_neighbor: Box<dyn Fn(T, bool) -> i32>, pub count_neighbor: Box<dyn Fn(TState, TKernel) -> i32>,
pub next_state: Box<dyn Fn(T, i32) -> T>, pub next_state: Box<dyn Fn(TState, i32) -> TState>,
} }
type Kernel3x3 = [[bool; 3]; 3]; pub const MOORE_NEIGHBORHOOD: [[bool; 3]; 3] =
[[true, true, true], [true, false, true], [true, true, true]];
pub const MOORE_NEIGHBORHOOD: Kernel3x3 = [ pub const NEUMANN_NEIGHBORHOOD: [[bool; 3]; 3] = [
[true, true, true],
[true, false, true],
[true, true, true]
];
pub const NEUMANN_NEIGHBORHOOD: Kernel3x3 = [
[false, true, false], [false, true, false],
[true, false, true], [true, false, true],
[false, true, false], [false, true, false],
]; ];
pub const DIAGONALS_NEIGHBORHOOD: Kernel3x3 = [ pub const DIAGONALS_NEIGHBORHOOD: [[bool; 3]; 3] = [
[true, false, true], [true, false, true],
[false, false, false], [false, false, false],
[true, false, true], [true, false, true],
@ -39,11 +39,16 @@ pub fn count_true_neighbor(neighbor_state: bool, kernel_value: bool) -> i32 {
} }
#[must_use] #[must_use]
pub fn generate_bb3() -> Rules<bool> { pub fn generate_bb3() -> Rules<bool, bool, 3> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let kernel = choose_neighborhood(&mut rng); let is_moore = rng.gen_bool(1.0 / 2.0);
let max_neighbors = count_max_neighbors(kernel); let kernel = if is_moore {
MOORE_NEIGHBORHOOD
} else {
NEUMANN_NEIGHBORHOOD
};
let max_neighbors = if is_moore { 8 } else { 4 };
let birth = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[0]); let birth = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[0]);
let survive = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[]); let survive = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[]);
@ -73,11 +78,17 @@ fn generate_neighbor_counts(count: u8, rng: &mut ThreadRng, exclude: &[i32]) ->
} }
#[must_use] #[must_use]
pub fn generate_u8b3() -> Rules<u8> { pub fn generate_u8b3() -> Rules<u8, bool, 3> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let kernel = choose_neighborhood(&mut rng); let kernel = match rng.gen_range(0..3) {
let alive_threshold = u8::max(1, rng.gen()); 0 => MOORE_NEIGHBORHOOD,
1 => NEUMANN_NEIGHBORHOOD,
2 => DIAGONALS_NEIGHBORHOOD,
_ => panic!(),
};
let alive_threshold = rng.gen();
let birth = generate_neighbor_counts(rng.gen_range(1..=9), &mut rng, &[0]); let birth = generate_neighbor_counts(rng.gen_range(1..=9), &mut rng, &[0]);
let survive = generate_neighbor_counts( let survive = generate_neighbor_counts(
@ -86,8 +97,8 @@ pub fn generate_u8b3() -> Rules<u8> {
&[], &[],
); );
let add = rng.gen_range(1..15); let add = rng.gen_range(5..40);
let sub = rng.gen_range(1..15); let sub = rng.gen_range(5..40);
println_info(format!("generated u8b3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}, alive_thresh: {alive_threshold}, delta: {add}/{sub}")); println_info(format!("generated u8b3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}, alive_thresh: {alive_threshold}, delta: {add}/{sub}"));
@ -104,24 +115,3 @@ pub fn generate_u8b3() -> Rules<u8> {
}), }),
} }
} }
fn choose_neighborhood(rng: &mut ThreadRng) -> Kernel3x3 {
match rng.gen_range(0..3) {
0 => MOORE_NEIGHBORHOOD,
1 => NEUMANN_NEIGHBORHOOD,
2 => DIAGONALS_NEIGHBORHOOD,
_ => unreachable!(),
}
}
fn count_max_neighbors<const SIZE: usize>(kernel: [[bool; SIZE]; SIZE]) -> u8 {
let mut result = 0;
for row in kernel {
for cell in row {
if cell {
result += 1;
}
}
}
result
}

View file

@ -1,179 +0,0 @@
use crate::{
game::Game,
print::{println_debug, println_info},
rules::{generate_bb3, generate_u8b3},
};
use rand::{distributions::Standard, prelude::Distribution, Rng};
use servicepoint::{
Bitmap, Brightness, BrightnessGrid, Grid, Value, ValueGrid, PIXEL_HEIGHT, PIXEL_WIDTH,
TILE_HEIGHT, TILE_SIZE, TILE_WIDTH,
};
use std::num::Wrapping;
pub(crate) struct Simulation {
pub(crate) left_pixels: Game<bool>,
pub(crate) right_pixels: Game<bool>,
pub(crate) left_luma: Game<u8>,
pub(crate) right_luma: Game<u8>,
split_pixel: usize,
pub(crate) split_speed: i32,
iteration: Wrapping<u8>,
}
pub enum SimulationEvent {
RandomizeLeftPixels,
RandomizeRightPixels,
RandomizeLeftLuma,
RandomizeRightLuma,
SeparatorAccelerate,
SeparatorDecelerate,
}
impl Simulation {
pub fn new() -> Simulation {
let left_pixels = Game {
rules: generate_bb3(),
field: make_randomized(PIXEL_WIDTH, PIXEL_HEIGHT),
};
let right_pixels = Game {
rules: generate_bb3(),
field: make_randomized(PIXEL_WIDTH, PIXEL_HEIGHT),
};
let left_luma = Game {
rules: generate_u8b3(),
field: make_randomized(TILE_WIDTH, TILE_HEIGHT),
};
let right_luma = Game {
rules: generate_u8b3(),
field: make_randomized(TILE_WIDTH, TILE_HEIGHT),
};
Self {
left_pixels,
right_pixels,
left_luma,
right_luma,
split_pixel: 0,
split_speed: 1,
iteration: Wrapping(0u8),
}
}
fn swap_left_right(&mut self) {
std::mem::swap(&mut self.left_luma, &mut self.right_luma);
std::mem::swap(&mut self.left_pixels, &mut self.right_pixels);
}
fn regenerate(pixels: &mut Game<bool>, luma: &mut Game<u8>) {
randomize(&mut pixels.field);
randomize(&mut luma.field);
pixels.rules = generate_bb3();
luma.rules = generate_u8b3();
}
pub(crate) fn run_iteration(&mut self) {
self.left_pixels.step();
self.right_pixels.step();
self.left_luma.step();
self.right_luma.step();
self.iteration += Wrapping(1u8);
if self.split_speed > 0 && self.split_pixel == self.left_pixels.field.width() {
self.split_pixel = 0;
self.swap_left_right();
Self::regenerate(&mut self.left_pixels, &mut self.left_luma);
} else if self.split_speed < 0 && self.split_pixel == 0 {
self.split_pixel = self.left_pixels.field.width();
self.swap_left_right();
Self::regenerate(&mut self.right_pixels, &mut self.right_luma);
}
self.split_pixel = i32::clamp(
self.split_pixel as i32 + self.split_speed,
0,
self.left_pixels.field.width() as i32,
) as usize;
}
pub(crate) fn draw_state(&self, pixels: &mut Bitmap, luma: &mut BrightnessGrid) {
for x in 0..pixels.width() {
let left_or_right = if x < self.split_pixel {
&self.left_pixels.field
} else {
&self.right_pixels.field
};
for y in 0..pixels.height() {
let set = x == self.split_pixel || left_or_right.get(x, y);
pixels.set(x, y, set);
}
}
let split_tile = self.split_pixel / TILE_SIZE;
for x in 0..luma.width() {
let left_or_right = if x < split_tile {
&self.left_luma.field
} else {
&self.right_luma.field
};
for y in 0..luma.height() {
let set = left_or_right.get(x, y) as f32 / u8::MAX as f32 * u8::from(Brightness::MAX) as f32;
let set = (set as u8).max(1);
let set = Brightness::try_from(set).unwrap();
luma.set(x, y, set);
}
}
}
pub(crate) fn handle_event(&mut self, event: SimulationEvent) {
match event {
SimulationEvent::RandomizeLeftPixels => {
randomize(&mut self.left_pixels.field);
println_debug("randomized left pixels");
}
SimulationEvent::RandomizeRightPixels => {
randomize(&mut self.right_pixels.field);
println_info("randomized right pixels");
}
SimulationEvent::RandomizeLeftLuma => {
randomize(&mut self.left_luma.field);
println_info("randomized left luma");
}
SimulationEvent::RandomizeRightLuma => {
randomize(&mut self.right_luma.field);
println_info("randomized right luma");
}
SimulationEvent::SeparatorAccelerate => {
self.split_speed += 1;
println_info(format!("increased separator speed to {}", self.split_speed));
}
SimulationEvent::SeparatorDecelerate => {
self.split_speed -= 1;
println_info(format!("decreased separator speed to {}", self.split_speed));
}
}
}
}
fn make_randomized<T: Value>(width: usize, height: usize) -> ValueGrid<T>
where
Standard: Distribution<T>,
{
let mut grid = ValueGrid::new(width, height);
randomize(&mut grid);
grid
}
fn randomize<T: Value>(field: &mut ValueGrid<T>)
where
Standard: Distribution<T>,
{
let mut rng = rand::thread_rng();
for y in 0..field.height() {
for x in 0..field.width() {
field.set(x, y, rng.gen());
}
}
}