From 9e157460efb991040a0b3118f968aea894e275c7 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Fri, 18 Aug 2023 22:14:26 +0100 Subject: [PATCH 01/29] Implement basic GUI --- Cargo.lock | 2516 ++++++++++++++++++++++++++------ Cargo.toml | 3 +- monument/gui/Cargo.toml | 20 + monument/gui/src/main.rs | 77 + monument/gui/src/parameters.rs | 106 ++ monument/gui/src/params_gui.rs | 196 +++ monument/gui/src/project.rs | 142 ++ monument/gui/src/search.rs | 67 + monument/gui/src/utils.rs | 57 + 9 files changed, 2765 insertions(+), 419 deletions(-) create mode 100644 monument/gui/Cargo.toml create mode 100644 monument/gui/src/main.rs create mode 100644 monument/gui/src/parameters.rs create mode 100644 monument/gui/src/params_gui.rs create mode 100644 monument/gui/src/project.rs create mode 100644 monument/gui/src/search.rs create mode 100644 monument/gui/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 641d0537..59ad4d49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,83 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e08104bebc65a46f8bc7aa733d39ea6874bfa7156f41a46b805785e3af1587d" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3083ac5a97521e35388ca80cf365b6be5210962cc59f11ee238cd92ac2fa9524" +dependencies = [ + "enumset", + "kurbo", +] + +[[package]] +name = "accesskit_consumer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47393f706a2d2f9d1ebd109351f886afd256a09d2308861a6dec0853a625e2" +dependencies = [ + "accesskit", + "parking_lot", +] + +[[package]] +name = "accesskit_macos" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabafb94d8a4dd6b20fe4112f943756ff8dc9778e3d742fb5478bf7f000a3282" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2", + "once_cell", + "parking_lot", +] + +[[package]] +name = "accesskit_windows" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662496f45a2e2ddff05e28d0a9fc2b319cc4f886d3664e3469c3d30800598962" +dependencies = [ + "accesskit", + "accesskit_consumer", + "arrayvec 0.7.4", + "once_cell", + "parking_lot", + "paste", + "windows 0.42.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f270416d033ab5b2a8fa72a976dfdad0db1ea194721f16cadbdb45ff219779f" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_windows", + "parking_lot", + "winit", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -17,11 +94,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -37,9 +126,49 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "arboard" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" +dependencies = [ + "clipboard-win", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot", + "thiserror", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "atty" @@ -54,15 +183,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -75,9 +204,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bellframe" @@ -87,7 +216,7 @@ dependencies = [ "dirs", "edit-distance", "factorial", - "itertools", + "itertools 0.12.1", "memchr", "minidom", "quickcheck", @@ -112,47 +241,92 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys", + "objc2-encode", +] [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.58", ] [[package]] name = "bytes" -version = "1.1.0" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "calloop" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +dependencies = [ + "bitflags 1.3.2", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] [[package]] name = "cc" -version = "1.0.72" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -160,6 +334,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "clap" version = "2.34.0" @@ -169,12 +358,76 @@ dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "clipboard-win" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + [[package]] name = "colored" version = "2.1.0" @@ -185,11 +438,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -197,54 +460,201 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] -name = "crossbeam-deque" -version = "0.8.1" +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.6" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossfont" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "3eb5a3822b594afc99b503cc1859b94686d3c3efdd60507a28587dab80ee1071" dependencies = [ - "cfg-if", - "lazy_static", + "cocoa 0.25.0", + "core-foundation", + "core-foundation-sys", + "core-graphics 0.23.2", + "core-text", + "dwrote", + "foreign-types 0.5.0", + "freetype-rs", + "libc", + "log", + "objc", + "once_cell", + "pkg-config", + "servo-fontconfig", + "winapi", ] [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix", + "nix 0.28.0", "windows-sys 0.52.0", ] +[[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 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", +] + +[[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 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.58", +] + [[package]] name = "datasize" version = "0.2.15" @@ -262,7 +672,16 @@ checksum = "613e4ee15899913285b7612004bbd490abd605be7b11d35afada5902fb6b91d5" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn 1.0.109", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", ] [[package]] @@ -292,27 +711,167 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.3", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "serde", + "serde_derive", + "winapi", + "wio", +] + +[[package]] +name = "ecolor" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b601108bca3af7650440ace4ca55b2daf52c36f2635be3587d77b16efd8d0691" +dependencies = [ + "bytemuck", +] + [[package]] name = "edit-distance" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" +[[package]] +name = "eframe" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea929ec5819fef373728bb0e55003ce921975039cfec3ca8305bb024e5b7b32" +dependencies = [ + "bytemuck", + "egui", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "js-sys", + "percent-encoding", + "raw-window-handle 0.5.2", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winit", +] + +[[package]] +name = "egui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a5e883a316e53866977450eecfbcac9c48109c2ab3394af29feb83fcde4ea9" +dependencies = [ + "accesskit", + "ahash", + "epaint", + "nohash-hasher", + "tracing", +] + +[[package]] +name = "egui-winit" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5696bdbe60898b81157f07ae34fe02dbfd522174bd6e620942c269cd7307901f" +dependencies = [ + "accesskit_winit", + "arboard", + "egui", + "instant", + "smithay-clipboard", + "tracing", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4b5960cb1bae1c403a6c9027a745210a41913433b10c73b6e7d76a1017f8b4" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset", + "tracing", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "either" -version = "1.6.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "emath" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5277249c8c3430e7127e4f2c40a77485e7baf11ae132ce9b3253a8ed710df0a0" +dependencies = [ + "bytemuck", +] [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -323,12 +882,54 @@ dependencies = [ "regex", ] +[[package]] +name = "epaint" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de14b65fe5e423e0058f77a8beb2c863b056d0566d6c4ce0d097aa5814cb705a" +dependencies = [ + "ab_glyph", + "ahash", + "atomic_refcell", + "bytemuck", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "factorial" version = "0.4.0" @@ -339,6 +940,31 @@ dependencies = [ "primal-sieve", ] +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -351,7 +977,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] @@ -360,92 +1007,204 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] +[[package]] +name = "freetype-rs" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" +dependencies = [ + "bitflags 1.3.2", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] -name = "futures-io" -version = "0.3.19" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] -name = "futures-sink" -version = "0.3.19" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] [[package]] -name = "futures-task" -version = "0.3.19" +name = "glow" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] [[package]] -name = "futures-util" -version = "0.3.19" +name = "glutin" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" dependencies = [ - "futures-core", - "futures-io", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "bitflags 1.3.2", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading 0.7.4", + "objc2", + "once_cell", + "raw-window-handle 0.5.2", + "wayland-sys 0.30.1", + "windows-sys 0.45.0", + "x11-dl", ] [[package]] -name = "gcd" -version = "2.3.0" +name = "glutin_egl_sys" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +checksum = "af784eb26c5a68ec85391268e074f0aa618c096eadb5d6330b0911cf34fe57c5" +dependencies = [ + "gl_generator", + "windows-sys 0.45.0", +] [[package]] -name = "getrandom" -version = "0.2.3" +name = "glutin_glx_sys" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "gl_generator", + "x11-dl", ] [[package]] -name = "gimli" -version = "0.28.0" +name = "glutin_wgl_sys" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +dependencies = [ + "gl_generator", +] [[package]] name = "h2" -version = "0.3.10" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -453,7 +1212,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.7.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -466,12 +1225,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - [[package]] name = "hashbrown" version = "0.14.3" @@ -498,9 +1251,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e18e4ebaca9e578405839cdf9098dc09a11ad532d94a6105a87c00c11b8a835" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hmap" @@ -508,11 +1261,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92fbd0445bda1ac099b76a3fbad67668f915f1873cc1d9b47c02889211fa1ace" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" -version = "0.2.6" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -521,9 +1283,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -532,21 +1294,21 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -559,7 +1321,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.2", + "socket2", "tokio", "tower-service", "tracing", @@ -579,13 +1341,18 @@ dependencies = [ "tokio-native-tls", ] +[[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" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -598,29 +1365,40 @@ checksum = "74086667896a940438f2118212f313abba4aff3831fef6f4b17d02add5c8bb60" [[package]] name = "indexmap" -version = "1.7.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "autocfg", - "hashbrown 0.11.2", + "equivalent", + "hashbrown", ] [[package]] -name = "indexmap" -version = "2.2.3" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "equivalent", - "hashbrown 0.14.3", + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "ipnet" -version = "2.3.1" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] [[package]] name = "itertools" @@ -633,19 +1411,47 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "kneasle_ringing_utils" version = "0.1.20" @@ -653,6 +1459,15 @@ dependencies = [ "number_prefix", ] +[[package]] +name = "kurbo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +dependencies = [ + "arrayvec 0.7.4", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -665,23 +1480,81 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "matches" -version = "0.1.9" +name = "malloc_buf" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memmap2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] [[package]] name = "memoffset" @@ -694,9 +1567,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minidom" @@ -709,21 +1582,23 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "log", + "wasi", "windows-sys 0.48.0", ] @@ -737,11 +1612,11 @@ dependencies = [ "gcd", "hmap", "index_vec", - "itertools", + "itertools 0.12.1", "kneasle_ringing_utils", "log", "num_cpus", - "ordered-float", + "ordered-float 4.2.0", "sysinfo", ] @@ -756,11 +1631,11 @@ dependencies = [ "difference", "hmap", "index_vec", - "itertools", + "itertools 0.12.1", "kneasle_ringing_utils", "log", "monument", - "ordered-float", + "ordered-float 4.2.0", "path-slash", "rayon", "regex", @@ -772,6 +1647,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "monument_gui" +version = "0.14.3" +dependencies = [ + "bellframe", + "eframe", + "index_vec", + "itertools 0.11.0", + "log", + "monument", + "ordered-float 3.9.2", + "simple_logger", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -791,64 +1680,236 @@ dependencies = [ ] [[package]] -name = "nix" -version = "0.27.1" +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags 1.3.2", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle 0.5.2", + "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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" +dependencies = [ + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", + "once_cell", + "parking_lot", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.4", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ - "bitflags 2.4.2", - "cfg-if", "libc", ] [[package]] -name = "ntapi" +name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "winapi", + "malloc_buf", ] [[package]] -name = "num-traits" -version = "0.2.14" +name = "objc-foundation" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" dependencies = [ - "autocfg", + "block", + "objc", + "objc_id", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "objc-sys" +version = "0.2.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe31e5425d3d0b89a15982c024392815da40689aceb34bad364d58732bcfd649" dependencies = [ - "hermit-abi 0.3.7", - "libc", + "block2", + "objc-sys", + "objc2-encode", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "objc2-encode" +version = "2.0.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" dependencies = [ - "libc", + "objc-sys", ] [[package]] -name = "number_prefix" -version = "0.4.0" +name = "objc_id" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -861,31 +1922,42 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -898,6 +1970,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "4.2.0" @@ -907,6 +1988,44 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owned_ttf_parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "path-slash" version = "0.2.1" @@ -915,15 +2034,15 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -933,15 +2052,28 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "png" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] [[package]] -name = "ppv-lite86" -version = "0.2.16" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "primal-bit" @@ -969,6 +2101,16 @@ dependencies = [ "smallvec", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -978,7 +2120,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.104", + "syn 1.0.109", "version_check", ] @@ -995,9 +2137,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1021,63 +2163,56 @@ checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn 1.0.109", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", - "rand_chacha", "rand_core", - "rand_hc", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "ppv-lite86", - "rand_core", + "getrandom", ] [[package]] -name = "rand_core" -version = "0.6.3" +name = "raw-window-handle" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" dependencies = [ - "getrandom", + "cty", ] [[package]] -name = "rand_hc" -version = "0.3.1" +name = "raw-window-handle" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1095,28 +2230,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", - "redox_syscall", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1126,9 +2262,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1137,24 +2273,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", @@ -1174,9 +2301,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -1193,6 +2323,28 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "rxml" version = "0.9.1" @@ -1212,9 +2364,18 @@ checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] [[package]] name = "same-file" @@ -1227,25 +2388,42 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "lazy_static", - "winapi", + "windows-sys 0.52.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61270629cc6b4d77ec1907db1033d5c2e1a404c412743621981a871dc9c12339" +dependencies = [ + "crossfont", + "log", + "smithay-client-toolkit", + "tiny-skia", +] [[package]] name = "security-framework" -version = "2.4.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1256,9 +2434,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -1281,14 +2459,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1316,12 +2494,39 @@ dependencies = [ "serde", ] +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + [[package]] name = "shortlist" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac4b749905cb3eaf8804e4d419186471f5a03789a8609c70b5d535839b85161" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simple_logger" version = "4.3.3" @@ -1336,15 +2541,27 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smartstring" @@ -1358,23 +2575,42 @@ dependencies = [ ] [[package]] -name = "socket2" -version = "0.4.2" +name = "smithay-client-toolkit" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ - "libc", - "winapi", + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", ] [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1389,6 +2625,12 @@ 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 = "structopt" version = "0.3.26" @@ -1410,14 +2652,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.104", + "syn 1.0.109", ] [[package]] name = "syn" -version = "1.0.104" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1426,20 +2668,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sysinfo" -version = "0.30.5" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +checksum = "26d7c217777061d5a2d652aea771fb9ba98b6dade657204b08c4b9604d11555b" dependencies = [ "cfg-if", "core-foundation-sys", @@ -1447,21 +2695,40 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "windows", + "windows 0.52.0", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] name = "tempfile" -version = "3.2.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi", + "fastrand", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -1473,15 +2740,38 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "time" -version = "0.3.16" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", "libc", + "num-conv", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -1489,55 +2779,80 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "bytemuck", + "cfg-if", + "png", + "safe_arch", + "tiny-skia-path", +] [[package]] -name = "time-macros" -version = "0.2.5" +name = "tiny-skia-path" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "c114d32f0c2ee43d585367cb013dfaba967ab9f62b90d9af0d696e955e70fa6c" dependencies = [ - "time-core", + "arrayref", + "bytemuck", ] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2", "windows-sys 0.48.0", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -1545,28 +2860,28 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.9", ] [[package]] @@ -1580,91 +2895,106 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.3", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.6", ] [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.2.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -1688,9 +3018,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1698,20 +3028,13 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[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" @@ -1720,9 +3043,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1730,24 +3053,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn 1.0.104", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -1757,9 +3080,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1767,33 +3090,135 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags 1.3.2", + "downcast-rs", + "libc", + "nix 0.24.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.3", + "once_cell", + "smallvec", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags 1.3.2", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "lazy_static", + "log", + "pkg-config", +] [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle 0.5.2", + "url", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1812,9 +3237,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1825,6 +3250,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0286ba339aa753e70765d521bb0242cc48e1194562bfa2a2ad7ac8a6de28f5d5" +dependencies = [ + "windows-implement", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows" version = "0.52.0" @@ -1832,7 +3273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] @@ -1841,7 +3282,40 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9539b6bd3eadbd9de66c9666b22d802b833da7e996bc06896142e09854a61767" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -1850,7 +3324,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] @@ -1859,137 +3333,343 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "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.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +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]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winit" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" +dependencies = [ + "bitflags 1.3.2", + "cocoa 0.24.1", + "core-foundation", + "core-graphics 0.22.3", + "dispatch", + "instant", + "libc", + "log", + "mio", + "ndk", + "ndk-glue", + "objc", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.4.3", + "raw-window-handle 0.5.2", + "sctk-adwaita", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys 0.36.1", + "x11-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.6.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wio" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi", ] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] diff --git a/Cargo.toml b/Cargo.toml index 8f90c8d8..0e61153e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = ["utils", "bellframe", "monument/lib", "monument/cli"] +members = ["utils", "bellframe", "monument/lib", "monument/cli", "monument/gui"] +default-members = ["monument/cli"] resolver = "2" # Do some optimisation for debug builds. -O1 is still fast enough, but makes things like file IO diff --git a/monument/gui/Cargo.toml b/monument/gui/Cargo.toml new file mode 100644 index 00000000..804b77df --- /dev/null +++ b/monument/gui/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "monument_gui" +version = "0.14.3" +edition = "2021" + +authors = ["Ben White-Horne "] +description = "Graphical interface to Monument, a fast and flexible composing engine." +readme = "../README.md" +license = "MIT" +repository = "https://github.com/kneasle/ringing" + +[dependencies] +bellframe = { version = "0.13.0", path = "../../bellframe/", features = ["serde"] } +monument = { version = "0.14.3", path = "../lib/" } +eframe = "0.20" +itertools = "0.11.0" +ordered-float = "3.7" +index_vec = "0.1" +simple_logger = "4.2" +log = "0.4" diff --git a/monument/gui/src/main.rs b/monument/gui/src/main.rs new file mode 100644 index 00000000..d54bca1a --- /dev/null +++ b/monument/gui/src/main.rs @@ -0,0 +1,77 @@ +mod parameters; +mod params_gui; +mod project; +mod search; +mod utils; + +use std::sync::{Arc, Mutex}; + +use eframe::egui; +use parameters::Parameters; +use project::Project; +use search::SearchThreadHandle; +use simple_logger::SimpleLogger; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn main() { + SimpleLogger::new() + .without_timestamps() + .with_colors(true) + .with_level(log::LevelFilter::Info) + .init() + .unwrap(); + + eframe::run_native( + &format!("Monument v{}", VERSION), + eframe::NativeOptions::default(), + Box::new(MonumentApp::new), + ); +} + +/// Singleton struct which holds all the data for one running instance of Monument +struct MonumentApp { + projects: Vec>>, + search_handle: SearchThreadHandle, +} + +impl MonumentApp { + #[allow(clippy::new_ret_no_self)] + fn new(_cc: &eframe::CreationContext<'_>) -> Box { + Box::new(Self { + projects: vec![Arc::new(Mutex::new(Project::default()))], + search_handle: SearchThreadHandle::new(), + }) + } +} + +impl eframe::App for MonumentApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + let current_project_mutex: &Arc<_> = &self.projects[0]; + let mut current_project = current_project_mutex.lock().unwrap(); + + let should_begin_search = egui::SidePanel::left("Params panel") + .default_width(400.0) + .show(ctx, |ui| { + current_project.draw_left_panel(ui, &self.search_handle) + }) + .inner; + + egui::SidePanel::right("Comps panel") + .default_width(300.0) + .show(ctx, |ui| { + ui.heading("Composition display"); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Compositions"); + current_project.draw_composition_list(ui); + }); + + if should_begin_search { + drop(current_project); // Release the mutex before `search_handle` acquires it again + self.search_handle + .queue_search(current_project_mutex.clone(), ctx.clone()); + } + } +} diff --git a/monument/gui/src/parameters.rs b/monument/gui/src/parameters.rs new file mode 100644 index 00000000..0a20436a --- /dev/null +++ b/monument/gui/src/parameters.rs @@ -0,0 +1,106 @@ +use std::ops::{Deref, DerefMut}; + +use monument::parameters::{Call, Method}; + +#[derive(Debug, Clone)] +pub struct Parameters { + inner: monument::parameters::Parameters, + pub maybe_unused_methods: Vec<(bool, Method)>, + pub maybe_unused_calls: Vec<(bool, Call)>, +} + +impl Parameters { + pub fn used_methods(&self) -> impl Iterator { + self.maybe_unused_methods + .iter() + .filter(|(used, _m)| *used) + .map(|(_, m)| m) + } + + pub fn used_calls(&self) -> impl Iterator { + self.maybe_unused_calls + .iter() + .filter(|(used, _c)| *used) + .map(|(_, c)| c) + } + + pub fn to_monument(&self) -> monument::Parameters { + todo!() + } + + pub fn yorkshire_s8_qps() -> Self { + let stage = Stage::MAJOR; + let cc_lib = bellframe::MethodLib::cc_lib().unwrap(); + + let make_method = |title: &str, id: u16| { + let mut method = cc_lib.get_by_title(title).unwrap(); + method.set_lead_end_label(); + monument::parameters::Method { + id: monument::parameters::MethodId(id), + + custom_shorthand: String::new(), + inner: method, + count_range: OptionalRangeInclusive::OPEN, + start_indices: vec![0], + end_indices: (0..32).collect_vec(), + allowed_courses: vec![Mask::parse_with_stage("1*", stage).unwrap().into()], + } + }; + + // Calls + let bob = Call::lead_end_call(CallId(0), PlaceNot::parse("14", stage).unwrap(), "-", -1.8); + let single = Call::lead_end_call( + CallId(1), + PlaceNot::parse("1234", stage).unwrap(), + "s", + -2.5, + ); + let maybe_unused_calls = vec![(true, bob), (true, single)]; + + let monument_params = monument::Parameters { + length: len_range(1250, 1350), + stage, + num_comps: 100, + require_truth: true, + + methods: index_vec::index_vec![], + splice_style: SpliceStyle::LeadLabels, + splice_weight: 0.0, + calls: index_vec::index_vec![], + call_display_style: CallDisplayStyle::CallingPositions(stage.tenor()), + atw_weight: None, // Don't calculate atw + require_atw: false, + + start_row: RowBuf::rounds(stage), + end_row: RowBuf::rounds(stage), + part_head_group: PartHeadGroup::one_part(stage), + course_weights: vec![], + + music_types: index_vec::index_vec![], + start_stroke: Stroke::Hand, + }; + crate::Parameters { + inner: monument_params, + maybe_unused_methods: vec![ + (true, make_method("Cambridge Surprise Major", 0)), + (true, make_method("Yorkshire Surprise Major", 1)), + (false, make_method("Superlative Surprise Major", 2)), + ], + maybe_unused_calls, + } + } +} + +impl Deref for Parameters { + type Target = monument::Parameters; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Parameters { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/monument/gui/src/params_gui.rs b/monument/gui/src/params_gui.rs new file mode 100644 index 00000000..215ddb42 --- /dev/null +++ b/monument/gui/src/params_gui.rs @@ -0,0 +1,196 @@ +use bellframe::Stroke; +use eframe::egui; +use monument::parameters::{default_shorthand, Call, SpliceStyle}; + +use crate::utils::{len_range, ParamTable}; + +pub fn draw_params_gui(params: &mut crate::Parameters, ui: &mut egui::Ui) { + #[allow(clippy::type_complexity)] + let sections: [(&str, fn(&mut crate::Parameters, &mut egui::Ui)); 5] = [ + ("General", draw_general_params), + ( + &format!( + "Methods ({}/{})", + params.used_methods().count(), + params.maybe_unused_methods.len() + ), + draw_method_params, + ), + ( + &format!( + "Calls ({}/{})", + params.used_calls().count(), + params.maybe_unused_calls.len() + ), + draw_calls_params, + ), + ("Music", draw_music_params), + ("Courses", draw_courses_params), + ]; + + let mut is_first = true; + for (idx, (name, draw_fn)) in sections.into_iter().enumerate() { + if !is_first { + ui.add_space(30.0); + } + is_first = false; + egui::CollapsingHeader::new(name) + .id_source(idx) // Use index as an ID source because header names may change + .default_open(true) + .show(ui, |ui| draw_fn(params, ui)); + } +} + +fn draw_general_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { + ParamTable::show(ui, 0, |grid| { + grid.add_label("Stage", params.stage); + grid.add_param("Length", |ui| { + ui.vertical(|ui| { + let mut min = params.length.start().as_usize(); + let mut max = params.length.end().as_usize(); + let mut set_length = |min: usize, max: usize| { + params.length = len_range(min, max); + }; + + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut min)); + ui.label("to"); + ui.add(egui::DragValue::new(&mut max)); + ui.label("(inclusive)"); + set_length(min, max); + }); + ui.horizontal(|ui| { + if ui.button("Practice").clicked() { + set_length(0, 300); + } + if ui.button("QP").clicked() { + set_length(1250, 1350); + } + if ui.button("Half Peal").clicked() { + set_length(2500, 2600); + } + if ui.button("Peal").clicked() { + set_length(5000, 5200); + } + }); + }); + }); + grid.add_param_widget("Num comps", egui::DragValue::new(&mut params.num_comps)); + grid.add_param("Truth", |ui| { + ui.selectable_value(&mut params.require_truth, true, "Require truth"); + ui.selectable_value(&mut params.require_truth, false, "Allow falseness"); + }); + }); +} + +fn draw_method_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { + // Other params + for (used, m) in &mut params.maybe_unused_methods { + draw_method_gui(used, m, ui); + } + ui.separator(); + if !params.is_spliced() { + ui.label("(Include more than one method to show splicing options)"); + } else { + ParamTable::show(ui, 0, |grid| { + grid.add_param("Splice locations", |ui| { + ui.selectable_value( + &mut params.splice_style, + SpliceStyle::LeadLabels, + "At leads", + ); + ui.selectable_value(&mut params.splice_style, SpliceStyle::Calls, "At calls"); + }); + grid.add_param_widget( + "Score per splice", + egui::Slider::new(&mut params.splice_weight, -5.0..=5.0), + ); + grid.add_todo("Count range"); + grid.add_todo("Start indices"); + grid.add_todo("End indices"); + }); + } +} + +fn draw_method_gui(used: &mut bool, method: &mut monument::parameters::Method, ui: &mut egui::Ui) { + let heading = format!("{}: {}", method.shorthand(), method.title()); + egui::CollapsingHeader::new(heading) + .id_source(method.id) + .show(ui, |ui| { + ParamTable::show(ui, 0, |table| { + table.add_param_widget("Name", egui::TextEdit::singleline(&mut method.inner.name)); + let default_shorthand = default_shorthand(&method.title()); + table.add_param_widget( + "Shorthand", + egui::TextEdit::singleline(&mut method.custom_shorthand) + .hint_text(default_shorthand), + ); + table.add_param_widget("Used", egui::Checkbox::new(used, "")); + }); + ui.separator(); + ParamTable::show(ui, 1, |table| { + table.add_label("Class", method.inner.class()); + table.add_label("Stage", method.inner.stage()); + }); + }); +} + +fn draw_calls_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { + for (used, c) in &mut params.maybe_unused_calls { + draw_call_ui(used, c, ui); + } + /* + TODO: Display call params + ui.separator(); + ParamTable::show(ui, 0, |grid| { + grid.add_param("Calling bell", |ui| { + for b in params.stage.bells() { + ui.selectable_value(&mut params.calling_bell, b, b.name()); + } + }); + }); + */ +} + +fn draw_call_ui(used: &mut bool, call: &mut Call, ui: &mut egui::Ui) { + let label_str = if call.label_from == call.label_to { + call.label_from.clone() + } else { + format!("{}->{}", call.label_from, call.label_to) + }; + let heading = format!("{} @ {} ({})", call.symbol, label_str, call.place_notation); + + egui::CollapsingHeader::new(heading) + .id_source(call.id) + .show(ui, |ui| { + ParamTable::show(ui, 0, |table| { + table.add_param_widget("Symbol", egui::TextEdit::singleline(&mut call.symbol)); + table.add_param_widget("Used", egui::Checkbox::new(used, "")); + table.add_param_widget("Weight", egui::Slider::new(&mut call.weight, -20.0..=2.0)); + table.add_label("Place notation", &call.place_notation); + table.add_label("Lead location (from)", &call.label_from); + table.add_label("Lead location (to)", &call.label_to); + table.add_label("Calling positions", call.calling_positions.join("")); + }); + }); +} + +fn draw_music_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { + ParamTable::show(ui, 0, |grid| { + grid.add_todo("Music types"); + grid.add_param("Start stroke", |ui| { + ui.selectable_value(&mut params.start_stroke, Stroke::Hand, "Handstroke"); + ui.selectable_value(&mut params.start_stroke, Stroke::Back, "Backstroke"); + }); + }); +} + +fn draw_courses_params(_params: &mut crate::Parameters, ui: &mut egui::Ui) { + ParamTable::show(ui, 0, |grid| { + grid.add_todo("Start row"); + grid.add_todo("End row"); + grid.add_todo("Part head"); + grid.add_todo("Allowed courses"); + grid.add_todo("Course weights"); + }); +} diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs new file mode 100644 index 00000000..7327b84a --- /dev/null +++ b/monument/gui/src/project.rs @@ -0,0 +1,142 @@ +use bellframe::{Mask, PlaceNot, RowBuf, Stage, Stroke}; +use eframe::egui; +use itertools::Itertools; +use monument::{ + parameters::{Call, CallDisplayStyle, CallId, OptionalRangeInclusive, SpliceStyle}, + Composition, PartHeadGroup, +}; + +use crate::{ + search::SearchThreadHandle, + utils::{len_range, ParamTable}, +}; + +/// A 'project' is a group of `Search`es, sharing methods, calls, etc. Conceptually, these +/// searches are all iterations looking for the same compositions. +#[derive(Debug)] +pub struct Project { + name: String, + params: crate::Parameters, + compositions: Vec, + search_progress: Option, +} + +impl Project { + ///////// + // GUI // + ///////// + + #[must_use] + pub fn draw_left_panel( + &mut self, + ui: &mut egui::Ui, + search_handle: &SearchThreadHandle, + ) -> bool { + // Header section + ui.heading(format!("Monument v{}", crate::VERSION)); + ui.add_space(30.0); + self.draw_header_ui(ui); + ui.separator(); + + // Search section + let should_search = self.draw_search_ui(ui, search_handle); + ui.separator(); + + // Comp params + ui.add_space(10.0); + ui.heading("Composition Parameters"); + egui::ScrollArea::vertical().show(ui, |ui| { + crate::params_gui::draw_params_gui(&mut self.params, ui) + }); + + should_search + } + + pub fn draw_header_ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label("Project name: "); + ui.text_edit_singleline(&mut self.name); + }); + } + + #[must_use] + pub fn draw_search_ui( + &mut self, + ui: &mut egui::Ui, + search_handle: &SearchThreadHandle, + ) -> bool { + if let Some(progress) = &self.search_progress { + ParamTable::show(ui, 0, |table| { + table.add_label("Iterations", progress.iter_count); + table.add_label("Compositions generated", progress.num_comps); // TODO: Num unique + table.add_label("Prefixes in queue", progress.queue_len); // TODO: Truncating + table.add_label("Mean prefix length", progress.avg_length); + table.add_label("Max prefix length", progress.max_length); + }); + if progress.truncating_queue { + ui.label("Truncating queue..."); + } + if progress.aborting { + ui.label("Aborting..."); + } else if ui.button("Abort search").clicked() { + search_handle.signal_abort(); + } + false + } else { + // Search section. It's either a button (if no search is running) or an update display + // (if a search is running) + let generate_text = if self.has_comps() { + "Generate better compositions" + } else { + "Generate compositions" + }; + ui.vertical_centered(|ui| ui.button(generate_text).clicked()) + .inner + } + } + + pub fn draw_composition_list(&mut self, ui: &mut egui::Ui) { + if !self.has_comps() { + ui.vertical_centered(|ui| ui.label("No compositions yet")); + } else { + egui::ScrollArea::vertical() + .auto_shrink([false, false]) + .show(ui, |ui| { + for c in &self.compositions { + ui.label(c.call_string()); + } + }); + } + } + + ///////////// + // HELPERS // + ///////////// + + pub fn clone_params(&self) -> crate::Parameters { + self.params.clone() + } + + pub fn recieve_update(&mut self, update: monument::Update) { + match update { + monument::Update::Comp(comp) => self.compositions.push(comp), + monument::Update::Progress(progress) => self.search_progress = Some(progress), + monument::Update::Complete => self.search_progress = None, + } + } + + pub fn has_comps(&self) -> bool { + !self.compositions.is_empty() + } +} + +impl Default for Project { + fn default() -> Self { + Self { + name: "Yorksire S8 Peals".to_owned(), + params: yorkshire_s8_qps(), + compositions: vec![], + search_progress: None, + } + } +} diff --git a/monument/gui/src/search.rs b/monument/gui/src/search.rs new file mode 100644 index 00000000..e8757944 --- /dev/null +++ b/monument/gui/src/search.rs @@ -0,0 +1,67 @@ +//! Code for the worker thread which runs Monument's searches + +use std::sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::{Receiver, Sender}, + Arc, Mutex, +}; + +use crate::project::Project; + +use eframe::egui; + +/// Struct to handle communication with the worker thread which runs queries. +pub struct SearchThreadHandle { + search_channel_tx: Sender<(Arc>, crate::Parameters, egui::Context)>, + abort_flag: Arc, +} + +impl SearchThreadHandle { + /// Spawns a background worker thread and returns a new `SearchThreadHandle` to communicate + /// with it + pub fn new() -> Self { + let abort_flag = Arc::new(AtomicBool::new(false)); + let (search_channel_tx, search_channel_rx) = std::sync::mpsc::channel(); + + // Spawn worker thread to handle searches in the background + { + let abort_flag = Arc::clone(&abort_flag); + std::thread::spawn(move || worker_thread(abort_flag, search_channel_rx)); + } + + Self { + search_channel_tx, + abort_flag, + } + } + + pub fn signal_abort(&self) { + self.abort_flag.store(true, Ordering::SeqCst); + } + + pub fn queue_search(&self, project: Arc>, ctx: egui::Context) { + let params = project.lock().unwrap().clone_params(); + // Get the parameters now, in case they get changed while this query request is waiting in + // the queue + self.search_channel_tx.send((project, params, ctx)).unwrap(); + } +} + +fn worker_thread( + abort_flag: Arc, + search_channel_rx: Receiver<(Arc>, crate::Parameters, egui::Context)>, +) { + while let Ok((project, params, ctx)) = search_channel_rx.recv() { + println!("Got new query!"); + let search = + monument::Search::new(params.to_monument(), monument::Config::default()).unwrap(); // TODO: Good error handling + search.run( + |update| { + project.lock().unwrap().recieve_update(update); + ctx.request_repaint(); + }, + &abort_flag, + ); + println!("Finished query, waiting for next one"); + } +} diff --git a/monument/gui/src/utils.rs b/monument/gui/src/utils.rs new file mode 100644 index 00000000..77ce29c3 --- /dev/null +++ b/monument/gui/src/utils.rs @@ -0,0 +1,57 @@ +use std::{fmt::Display, hash::Hash}; + +use eframe::egui; +use monument::utils::TotalLength; + +/// Helper type to create a table of parameters. +/// +/// # Example +/// ``` +/// ParamTable::show(ui, |table| { +/// table.add_param_widget("Name", egui::TextEdit::singleline(&mut self.name)); +/// table.add_param("Age", |ui| { +/// ui.selectable_value(&mut self.is_adult, true, "Adult"); +/// ui.selectable_value(&mut self.is_adult, false, "Child"); +/// }); +/// }); +/// ``` +/// produces a table like this (note how the values are all aligned): +/// +/// Name: +/// Age: [Adult] +pub struct ParamTable<'ui> { + ui: &'ui mut egui::Ui, +} + +impl ParamTable<'_> { + pub fn show(ui: &mut egui::Ui, id_source: impl Hash, f: impl FnOnce(&mut ParamTable<'_>)) { + egui::Grid::new(id_source) + .num_columns(2) + .show(ui, |ui| f(&mut ParamTable { ui })); + } + + pub fn add_param_widget(&mut self, label: &str, rhs: impl egui::Widget) { + self.add_param(label, |ui| ui.add(rhs)); + } + + pub fn add_label(&mut self, label: &str, rhs: impl Display) { + self.add_param_widget(label, egui::Label::new(rhs.to_string())); + } + + pub fn add_todo(&mut self, label: &str) { + let text = egui::RichText::new("TODO") + .strong() + .color(egui::Color32::RED); + self.add_param_widget(label, egui::Label::new(text)); + } + + pub fn add_param(&mut self, label: &str, rhs: impl FnOnce(&mut egui::Ui) -> T) { + self.ui.label(format!("{label}:")); + self.ui.scope(rhs); + self.ui.end_row(); + } +} + +pub fn len_range(min: usize, max: usize) -> std::ops::RangeInclusive { + TotalLength::new(min)..=TotalLength::new(max) +} From 80313aa635ffd4a90b5b61a7685093832a2ac062 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Wed, 15 Nov 2023 23:01:44 +0000 Subject: [PATCH 02/29] Validate compositions/parameter pairs before getting values --- monument/cli/src/lib.rs | 7 +- monument/cli/src/logging.rs | 15 +- monument/gui/src/parameters.rs | 37 ++-- monument/gui/src/params_gui.rs | 15 +- monument/gui/src/project.rs | 20 +- monument/lib/src/composition.rs | 327 +++++++++++++++++++----------- monument/lib/src/lib.rs | 3 +- monument/lib/src/search/prefix.rs | 13 +- 8 files changed, 263 insertions(+), 174 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index 8f4aaac1..33707e6d 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -23,7 +23,7 @@ use std::{ }; use log::LevelFilter; -use monument::{Composition, Search}; +use monument::{Composition, CompositionGetter, Search}; use ordered_float::OrderedFloat; use ringing_utils::PrettyDuration; use simple_logger::SimpleLogger; @@ -119,10 +119,11 @@ pub fn run( OrderedFloat(rounded) } comps.sort_by_cached_key(|(comp, _generation_index)| { + let comp = CompositionGetter::new(comp, ¶ms).unwrap(); ( - rounded_float(comp.music_score(¶ms)), + rounded_float(comp.music_score()), rounded_float(comp.average_score()), - comp.call_string(¶ms), + comp.call_string(), ) }); Ok(Some(SearchResult { diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index e734e27d..bd43c8a5 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -6,7 +6,7 @@ use bellframe::row::ShortRow; use colored::Colorize; use itertools::Itertools; use log::log_enabled; -use monument::{Composition, Progress, Search, Update}; +use monument::{Composition, CompositionGetter, Progress, Search, Update}; use ringing_utils::BigNumInt; use crate::music::MusicDisplay; @@ -285,7 +285,7 @@ impl CompositionPrinter { } fn comp_string(&self, comp: &Composition, generation_index: usize) -> String { - let params = self.search.parameters(); + let comp = CompositionGetter::new(comp, self.search.parameters()).unwrap(); let mut s = String::new(); // Comp index @@ -297,10 +297,7 @@ impl CompositionPrinter { // Method counts (for spliced) if self.method_count_widths.len() > 1 { s.push_str(": "); - for ((width, _), count) in self - .method_count_widths - .iter() - .zip_eq(comp.method_counts(params)) + for ((width, _), count) in self.method_count_widths.iter().zip_eq(comp.method_counts()) { write!(s, "{:>width$} ", count, width = *width).unwrap(); } @@ -308,7 +305,7 @@ impl CompositionPrinter { s.push('|'); // Atw if self.print_atw { - let factor = comp.atw_factor(params); + let factor = comp.atw_factor(); if factor > 0.999999 { s.push_str(&format!(" {} |", "atw".color(colored::Color::BrightGreen))); } else { @@ -320,7 +317,7 @@ impl CompositionPrinter { write!(s, " {} |", ShortRow(comp.part_head())).unwrap(); } // Music - let (music_counts, music_score) = comp.music_counts_and_score(params); // Only call `music_counts` once! + let (music_counts, music_score) = comp.music_counts_and_score(); // Only call `music_counts` once! write!(s, " {:>7.2} ", music_score).unwrap(); if !self.music_displays.is_empty() { s.push(':'); @@ -339,7 +336,7 @@ impl CompositionPrinter { s, "| {:>9.6} | {}", comp.average_score(), - comp.call_string(params) + comp.call_string() ) .unwrap(); diff --git a/monument/gui/src/parameters.rs b/monument/gui/src/parameters.rs index 0a20436a..2016b8f2 100644 --- a/monument/gui/src/parameters.rs +++ b/monument/gui/src/parameters.rs @@ -1,15 +1,27 @@ -use std::ops::{Deref, DerefMut}; +use bellframe::{Mask, PlaceNot, RowBuf, Stage, Stroke}; +use itertools::Itertools; +use monument::{ + parameters::{Call, CallDisplayStyle, CallId, Method, OptionalRangeInclusive, SpliceStyle}, + PartHeadGroup, +}; -use monument::parameters::{Call, Method}; +use crate::utils::len_range; #[derive(Debug, Clone)] pub struct Parameters { - inner: monument::parameters::Parameters, + pub inner: monument::parameters::Parameters, pub maybe_unused_methods: Vec<(bool, Method)>, pub maybe_unused_calls: Vec<(bool, Call)>, } impl Parameters { + pub fn to_monument(&self) -> monument::Parameters { + let mut params = self.inner.clone(); + params.methods = self.used_methods().cloned().collect(); + params.calls = self.used_calls().cloned().collect(); + params + } + pub fn used_methods(&self) -> impl Iterator { self.maybe_unused_methods .iter() @@ -24,8 +36,8 @@ impl Parameters { .map(|(_, c)| c) } - pub fn to_monument(&self) -> monument::Parameters { - todo!() + pub fn is_spliced(&self) -> bool { + self.used_methods().count() > 1 } pub fn yorkshire_s8_qps() -> Self { @@ -79,6 +91,7 @@ impl Parameters { music_types: index_vec::index_vec![], start_stroke: Stroke::Hand, }; + crate::Parameters { inner: monument_params, maybe_unused_methods: vec![ @@ -90,17 +103,3 @@ impl Parameters { } } } - -impl Deref for Parameters { - type Target = monument::Parameters; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for Parameters { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/monument/gui/src/params_gui.rs b/monument/gui/src/params_gui.rs index 215ddb42..ca5fb23c 100644 --- a/monument/gui/src/params_gui.rs +++ b/monument/gui/src/params_gui.rs @@ -42,6 +42,7 @@ pub fn draw_params_gui(params: &mut crate::Parameters, ui: &mut egui::Ui) { } fn draw_general_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { + let params = &mut params.inner; ParamTable::show(ui, 0, |grid| { grid.add_label("Stage", params.stage); grid.add_param("Length", |ui| { @@ -95,15 +96,19 @@ fn draw_method_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { ParamTable::show(ui, 0, |grid| { grid.add_param("Splice locations", |ui| { ui.selectable_value( - &mut params.splice_style, + &mut params.inner.splice_style, SpliceStyle::LeadLabels, "At leads", ); - ui.selectable_value(&mut params.splice_style, SpliceStyle::Calls, "At calls"); + ui.selectable_value( + &mut params.inner.splice_style, + SpliceStyle::Calls, + "At calls", + ); }); grid.add_param_widget( "Score per splice", - egui::Slider::new(&mut params.splice_weight, -5.0..=5.0), + egui::Slider::new(&mut params.inner.splice_weight, -5.0..=5.0), ); grid.add_todo("Count range"); grid.add_todo("Start indices"); @@ -179,8 +184,8 @@ fn draw_music_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { ParamTable::show(ui, 0, |grid| { grid.add_todo("Music types"); grid.add_param("Start stroke", |ui| { - ui.selectable_value(&mut params.start_stroke, Stroke::Hand, "Handstroke"); - ui.selectable_value(&mut params.start_stroke, Stroke::Back, "Backstroke"); + ui.selectable_value(&mut params.inner.start_stroke, Stroke::Hand, "Handstroke"); + ui.selectable_value(&mut params.inner.start_stroke, Stroke::Back, "Backstroke"); }); }); } diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index 7327b84a..d0f8e67d 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,15 +1,7 @@ -use bellframe::{Mask, PlaceNot, RowBuf, Stage, Stroke}; use eframe::egui; -use itertools::Itertools; -use monument::{ - parameters::{Call, CallDisplayStyle, CallId, OptionalRangeInclusive, SpliceStyle}, - Composition, PartHeadGroup, -}; +use monument::{Composition, CompositionGetter}; -use crate::{ - search::SearchThreadHandle, - utils::{len_range, ParamTable}, -}; +use crate::{search::SearchThreadHandle, utils::ParamTable}; /// A 'project' is a group of `Search`es, sharing methods, calls, etc. Conceptually, these /// searches are all iterations looking for the same compositions. @@ -96,6 +88,8 @@ impl Project { } pub fn draw_composition_list(&mut self, ui: &mut egui::Ui) { + let monument_params = self.params.to_monument(); + if !self.has_comps() { ui.vertical_centered(|ui| ui.label("No compositions yet")); } else { @@ -103,7 +97,9 @@ impl Project { .auto_shrink([false, false]) .show(ui, |ui| { for c in &self.compositions { - ui.label(c.call_string()); + if let Some(getter) = CompositionGetter::new(c, &monument_params) { + ui.label(getter.call_string()); + } } }); } @@ -134,7 +130,7 @@ impl Default for Project { fn default() -> Self { Self { name: "Yorksire S8 Peals".to_owned(), - params: yorkshire_s8_qps(), + params: crate::Parameters::yorkshire_s8_qps(), compositions: vec![], search_progress: None, } diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index f318da5c..ae8440af 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -9,7 +9,10 @@ use bellframe::{music::AtRowPositions, Bell, Block, Row, RowBuf, Stage, Stroke}; use itertools::Itertools; use crate::{ - parameters::{CallDisplayStyle, CallId, MethodId, MethodVec, MusicTypeVec, Parameters}, + parameters::{ + Call, CallDisplayStyle, CallId, CallIdx, MethodId, MethodIdx, MethodVec, MusicTypeVec, + Parameters, + }, utils::lengths::{PerPartLength, TotalLength}, }; @@ -33,65 +36,131 @@ pub struct Composition { pub(crate) total_score: f32, } -impl Composition { - /// The number of [`Row`]s in this composition. - pub fn length(&self) -> usize { - self.length.as_usize() - } +/// A piece of a [`Composition`] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct PathElem { + pub start_row: RowBuf, + pub method_id: MethodId, + pub start_sub_lead_idx: usize, + pub length: PerPartLength, + pub call_to_end: Option, +} - /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the - /// [`Search`]. These are stored in the same order as the [`Method`]s. - pub fn method_counts(&self, params: &Parameters) -> MethodVec { - let counts_per_method_id = self - .rows(params) - .annots() - .counts_by(|(method_id, _)| *method_id); +impl PathElem { + pub fn ends_with_plain(&self) -> bool { + self.call_to_end.is_none() + } + pub(crate) fn end_sub_lead_idx(&self, params: &Parameters) -> usize { params - .methods - .iter() - .map(|m| *counts_per_method_id.get(&m.id).unwrap_or(&0)) - .collect() + .get_method(self.method_id) + .add_sub_lead_idx(self.start_sub_lead_idx, self.length) } +} - /// Compute [`Self::music_counts`] and use it to calculate [`Self::music_score`]. Calling - /// them separately would cause the music to be calculated twice. - pub fn music_counts_and_score( - &self, - params: &Parameters, - ) -> (MusicTypeVec>, f32) { - let music_counts = self.music_counts(params); - let mut music_score = 0.0; - for (count, music_type) in music_counts.iter().zip_eq(¶ms.music_types) { - music_score += music_type.as_overall_score(*count); +////////////////// +// CALCULATIONS // +////////////////// + +/// Struct which combines a [`Composition`] with a set of [`Parameters`] from which the extra data +/// is taken. +#[derive(Debug, Clone)] +pub struct CompositionGetter<'c, 'p> { + composition: &'c Composition, + params: &'p Parameters, + + method_map: HashMap, + call_map: HashMap, +} + +#[derive(Debug, Clone)] +struct MethodData { + idx: MethodIdx, + double_plain_course: Block<(MethodId, usize)>, +} + +impl<'c, 'p> CompositionGetter<'c, 'p> { + /* CONSTRUCTION/VALIDATION */ + + pub fn new(composition: &'c Composition, params: &'p Parameters) -> Option { + if composition.stage != params.stage { + return None; } - (music_counts, music_score) + + Some(CompositionGetter { + composition, + params, + + method_map: Self::method_map(composition, params)?, + call_map: Self::call_map(composition, params)?, + }) } - /// Score generated by just the [`MusicType`]s (not including calls, changes of methods, - /// etc.). - pub fn music_score(&self, params: &Parameters) -> f32 { - self.music_counts_and_score(params).1 + /// If every [`MethodId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` + /// where `map` maps [`MethodId`]s to their corresponding [`MethodIdx`]. Otherwise, returns + /// `None`. + fn method_map( + composition: &Composition, + params: &Parameters, + ) -> Option> { + let methods = composition + .path + .iter() + .map(|e| e.method_id) + .collect::>(); + + let mut method_map = HashMap::new(); + for id in methods { + let idx = params.methods.position(|m| m.id == id)?; + let mut double_plain_course = params.methods[idx] + .plain_course() + .map_annots(|annot| (id, annot.sub_lead_idx)); + double_plain_course.extend_from_within(..); + method_map.insert( + id, + MethodData { + idx, + double_plain_course, + }, + ); + } + Some(method_map) } - /// The number of *instances* of each [`MusicType`] in the [`Search`]. - pub fn music_counts(&self, params: &Parameters) -> MusicTypeVec> { - let rows = self.rows(params); - params - .music_types + /// If every [`CallId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` + /// where `map` maps [`CallId`]s to their corresponding [`CallId`]. Otherwise, returns `None`. + fn call_map( + composition: &Composition, + params: &Parameters, + ) -> Option> { + let call_ids_used = composition + .path .iter() - .map(|mt| mt.count(&rows, !self.start_stroke)) - .collect() + .filter_map(|e| e.call_to_end) + .collect::>(); + + let mut call_map = HashMap::new(); + for id in call_ids_used { + call_map.insert(id, params.calls.position(|c| c.id == id)?); + } + Some(call_map) + } + + /* GETTERS */ + + /// The number of [`Row`]s in this composition. + pub fn length(&self) -> usize { + self.composition.length.as_usize() } pub fn part_head(&self) -> &Row { - &self.part_head + &self.composition.part_head } /// The total score generated by this composition from all the different weights (music, calls, /// changes of method, handbell coursing, etc.). pub fn total_score(&self) -> f32 { - self.total_score + self.composition.total_score } /// The average score generated by each [`Row`] in this composition. This is equal to @@ -100,45 +169,22 @@ impl Composition { self.total_score() / self.length() as f32 } - /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything - /// was rung (i.e. the composition is atw) - pub fn atw_factor(&self, params: &Parameters) -> f32 { - // Determine the working bells, and store this in a bitmap - let working_bells = params.working_bells(); - let mut is_working = vec![false; self.stage.num_bells()]; - for b in &working_bells { - is_working[b.index()] = true; - } - // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible - let total_unique_row_positions = working_bells.len() // Working bells - * working_bells.len() // Working place bells - * params.methods.iter().map(|m| m.lead_len()).sum::(); - // Compute which `(bell, place, method, sub-lead-index)` quadruples have been rung in this - // composition. - // - // TODO: Use a more efficient storage method - let mut rung_place_bell_positions = HashSet::<(Bell, u8, MethodId, usize)>::new(); - for (&(method_id, sub_lead_idx), row) in self.rows(params).annot_rows() { - for (place, bell) in row.bell_iter().enumerate() { - if is_working[bell.index()] { - rung_place_bell_positions.insert((bell, place as u8, method_id, sub_lead_idx)); - } - } - } - - rung_place_bell_positions.len() as f32 / total_unique_row_positions as f32 - } - /// Generate a human-friendly [`String`] summarising the calling of this composition. For /// example, [this composition](https://complib.org/composition/87419) would have a /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. - pub fn call_string(&self, params: &Parameters) -> String { + pub fn call_string(&self) -> String { + let Self { + composition, + params, + .. + } = self; + let needs_brackets = params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional; - let is_snap_start = self.path[0].start_sub_lead_idx > 0; - let is_snap_finish = self.path.last().unwrap().end_sub_lead_idx(params) > 0; + let is_snap_start = composition.path[0].start_sub_lead_idx > 0; + let is_snap_finish = composition.path.last().unwrap().end_sub_lead_idx(params) > 0; - let mut path_iter = self.path.iter().peekable(); + let mut path_iter = composition.path.iter().peekable(); let mut s = String::new(); if params.call_display_style == CallDisplayStyle::Positional { @@ -151,7 +197,7 @@ impl Composition { // Add one shorthand for every lead *covered* (not number of lead heads reached) // // TODO: Deal with half-lead spliced - let method = params.get_method(path_elem.method_id); + let method = self.get_method(path_elem.method_id); let num_leads_covered = num_leads_covered( method.lead_len(), path_elem.start_sub_lead_idx, @@ -163,15 +209,14 @@ impl Composition { } // Call text if let Some(call_id) = path_elem.call_to_end { - let call = params.get_call(call_id); - + let call = self.get_call(call_id); s.push_str(if needs_brackets { "[" } else { "" }); // Call position match params.call_display_style { CallDisplayStyle::CallingPositions(calling_bell) => { let row_after_call = path_iter .peek() - .map_or(&self.part_head, |path_elem| &path_elem.start_row); + .map_or(&composition.part_head, |path_elem| &path_elem.start_row); let place_of_calling_bell = row_after_call.place_of(calling_bell).unwrap(); let calling_position = &call.calling_positions[place_of_calling_bell]; s.push_str(call.short_symbol()); @@ -188,6 +233,73 @@ impl Composition { s } + /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything + /// was rung (i.e. the composition is atw) + pub fn atw_factor(&self) -> f32 { + // Determine the working bells, and store this in a bitmap + let working_bells = self.params.working_bells(); + let mut is_working = vec![false; self.composition.stage.num_bells()]; + for b in &working_bells { + is_working[b.index()] = true; + } + // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible + let total_unique_row_positions = working_bells.len() // Working bells + * working_bells.len() // Working place bells + * self.params.methods.iter().map(|m| m.lead_len()).sum::(); + // Compute which `(bell, place, method, sub-lead-index)` quadruples have been rung in this + // composition. + // + // TODO: Use a more efficient storage method + let mut rung_place_bell_positions = HashSet::<(Bell, u8, MethodId, usize)>::new(); + for (&(method_id, sub_lead_idx), row) in self.rows().annot_rows() { + for (place, bell) in row.bell_iter().enumerate() { + if is_working[bell.index()] { + rung_place_bell_positions.insert((bell, place as u8, method_id, sub_lead_idx)); + } + } + } + + rung_place_bell_positions.len() as f32 / total_unique_row_positions as f32 + } + + /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the + /// [`Search`]. These are stored in the same order as the [`Method`]s. + pub fn method_counts(&self) -> MethodVec { + let mut method_counts = index_vec::index_vec![TotalLength::ZERO; self.params.methods.len()]; + for elem in &self.composition.path { + let idx = self.method_map[&elem.method_id].idx; + method_counts[idx] += elem.length.as_total(&self.params.part_head_group); + } + method_counts + } + + /// Compute [`Self::music_counts`] and use it to calculate [`Self::music_score`]. Calling + /// them separately would cause the music to be calculated twice. + pub fn music_counts_and_score(&self) -> (MusicTypeVec>, f32) { + let music_counts = self.music_counts(); + let mut music_score = 0.0; + for (count, music_type) in music_counts.iter().zip_eq(&self.params.music_types) { + music_score += music_type.as_overall_score(*count); + } + (music_counts, music_score) + } + + /// Score generated by just the [`MusicType`]s (not including calls, changes of methods, + /// etc.). + pub fn music_score(&self) -> f32 { + self.music_counts_and_score().1 + } + + /// The number of *instances* of each [`MusicType`] in the [`Search`]. + pub fn music_counts(&self) -> MusicTypeVec> { + let rows = self.rows(); + self.params + .music_types + .iter() + .map(|mt| mt.count(&rows, !self.composition.start_stroke)) + .collect() + } + /// Return a [`Block`] containing the [`Row`]s in this composition. Each [`Row`] is annotated /// with a `(method index, index within a lead)` pair. For example, splicing a lead of Bastow /// into Cambridge Major would create a [`Block`] which starts like: @@ -206,38 +318,25 @@ impl Composition { /// ... /// } /// ``` - pub fn rows(&self, params: &Parameters) -> Block<(MethodId, usize)> { - assert_eq!(self.stage, params.stage); - let mut plain_course_cache = HashMap::>::new(); - + /// + /// If this `Composition` uses methods or calls which are not specified in the [`Parameters`], + /// then `None` is returned. + pub fn rows(&self) -> Block<(MethodId, usize)> { // Generate the first part let mut first_part = - Block::<(MethodId, usize)>::with_leftover_row(params.start_row.clone()); - for elem in &self.path { + Block::<(MethodId, usize)>::with_leftover_row(self.params.start_row.clone()); + for elem in &self.composition.path { assert_eq!(first_part.leftover_row(), elem.start_row.as_row()); - // Get the plain course of the current method - let plain_course = plain_course_cache.entry(elem.method_id).or_insert_with(|| { - params - .get_method(elem.method_id) - .plain_course() - .map_annots(|annot| (elem.method_id, annot.sub_lead_idx)) - }); - // Add this elem to the first part + // Copy the corresponding part of this method's (double) plain course + let double_plain_course = &self.method_map[&elem.method_id].double_plain_course; let start_idx = elem.start_sub_lead_idx; let end_idx = start_idx + elem.length.as_usize(); - if end_idx > plain_course.len() { - // `elem` wraps over the course head, so copy it in two pieces - first_part.extend_range(plain_course, start_idx..); - first_part.extend_range(plain_course, ..end_idx - plain_course.len()); - } else { - // `elem` doesn't wrap over the course head, so copy it in one piece - first_part.extend_range(plain_course, start_idx..end_idx); - } + first_part.extend_range(double_plain_course, start_idx..end_idx); // If this PathElem ends in a call, then change the `leftover_row` to suit if let Some(call_id) = elem.call_to_end { let last_non_leftover_row = first_part.rows().next_back().unwrap(); let new_leftover_row = - last_non_leftover_row * params.get_call(call_id).place_notation.transposition(); + last_non_leftover_row * self.get_call(call_id).place_notation.transposition(); first_part.leftover_row_mut().copy_from(&new_leftover_row); } } @@ -245,34 +344,20 @@ impl Composition { // Generate the other parts from the first let part_len = first_part.len(); let mut comp = first_part; - for _ in 0..params.num_parts() - 1 { + for _ in 0..self.params.num_parts() - 1 { comp.extend_from_within(..part_len); } assert_eq!(comp.len(), self.length()); - assert_eq!(comp.leftover_row(), ¶ms.end_row); + assert_eq!(comp.leftover_row(), &self.params.end_row); comp } -} -/// A piece of a [`Composition`] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PathElem { - pub start_row: RowBuf, - pub method_id: MethodId, - pub start_sub_lead_idx: usize, - pub length: PerPartLength, - pub call_to_end: Option, -} - -impl PathElem { - pub fn ends_with_plain(&self) -> bool { - self.call_to_end.is_none() + fn get_method(&self, id: MethodId) -> &Method { + &self.params.methods[self.method_map[&id].idx] } - pub(crate) fn end_sub_lead_idx(&self, params: &Parameters) -> usize { - params - .get_method(self.method_id) - .add_sub_lead_idx(self.start_sub_lead_idx, self.length) + fn get_call(&self, id: CallId) -> &Call { + &self.params.calls[self.call_map[&id]] } } diff --git a/monument/lib/src/lib.rs b/monument/lib/src/lib.rs index 2c07eae4..c38420bc 100644 --- a/monument/lib/src/lib.rs +++ b/monument/lib/src/lib.rs @@ -50,7 +50,8 @@ mod prove_length; mod search; pub mod utils; -pub use composition::Composition; +pub use composition::{Composition, CompositionGetter}; pub use error::{Error, Result}; pub use group::{PartHead, PartHeadGroup, PhRotation}; +pub use parameters::Parameters; pub use search::{Config, Progress, Search, Update}; diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index e5f5f8f9..dc0a924e 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -10,7 +10,7 @@ use datasize::DataSize; use ordered_float::OrderedFloat; use crate::{ - composition::{Composition, PathElem}, + composition::{Composition, CompositionGetter, PathElem}, graph::LinkSide, group::PartHead, parameters::SpliceStyle, @@ -321,10 +321,15 @@ impl CompPrefix { }; // Sanity check that the composition is true if params.require_truth { - let mut rows_so_far = HashSet::<&Row>::with_capacity(comp.length()); - for row in comp.rows(params).rows() { + let comp_getter = CompositionGetter::new(&comp, params).unwrap(); + + let mut rows_so_far = HashSet::<&Row>::with_capacity(comp_getter.length()); + for row in comp_getter.rows().rows() { if !rows_so_far.insert(row) { - panic!("Generated false composition ({})", comp.call_string(params)); + panic!( + "Generated false composition ({})", + comp_getter.call_string() + ); } } } From 489bb81845f171c99f41fc92c28fe0e2b234a711 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 2 Dec 2023 17:05:21 +0000 Subject: [PATCH 03/29] Move GUI code as a submodule of params --- monument/gui/src/main.rs | 1 - monument/gui/src/parameters/gui.rs | 213 ++++++++++++++++++ .../src/{parameters.rs => parameters/mod.rs} | 6 +- monument/gui/src/params_gui.rs | 201 ----------------- monument/gui/src/project.rs | 4 +- 5 files changed, 218 insertions(+), 207 deletions(-) create mode 100644 monument/gui/src/parameters/gui.rs rename monument/gui/src/{parameters.rs => parameters/mod.rs} (97%) delete mode 100644 monument/gui/src/params_gui.rs diff --git a/monument/gui/src/main.rs b/monument/gui/src/main.rs index d54bca1a..30562adb 100644 --- a/monument/gui/src/main.rs +++ b/monument/gui/src/main.rs @@ -1,5 +1,4 @@ mod parameters; -mod params_gui; mod project; mod search; mod utils; diff --git a/monument/gui/src/parameters/gui.rs b/monument/gui/src/parameters/gui.rs new file mode 100644 index 00000000..d985385c --- /dev/null +++ b/monument/gui/src/parameters/gui.rs @@ -0,0 +1,213 @@ +use bellframe::Stroke; +use eframe::egui; +use monument::parameters::{default_shorthand, Call, SpliceStyle}; + +use crate::utils::{len_range, ParamTable}; + +impl super::Parameters { + pub fn draw_gui(&mut self, ui: &mut egui::Ui) { + #[allow(clippy::type_complexity)] + let sections: [(&str, fn(&mut crate::Parameters, &mut egui::Ui)); 5] = [ + ("General", Self::draw_general_params), + ( + &format!( + "Methods ({}/{})", + self.used_methods().count(), + self.maybe_unused_methods.len() + ), + Self::draw_method_params, + ), + ( + &format!( + "Calls ({}/{})", + self.used_calls().count(), + self.maybe_unused_calls.len() + ), + Self::draw_calls_params, + ), + ("Music", Self::draw_music_params), + ("Courses", Self::draw_courses_params), + ]; + + let mut is_first = true; + for (idx, (name, draw_fn)) in sections.into_iter().enumerate() { + if !is_first { + ui.add_space(30.0); + } + is_first = false; + egui::CollapsingHeader::new(name) + .id_source(idx) // Use index as an ID source because header names may change + .default_open(true) + .show(ui, |ui| draw_fn(self, ui)); + } + } + + fn draw_general_params(&mut self, ui: &mut egui::Ui) { + let params = &mut self.inner; + ParamTable::show(ui, 0, |grid| { + grid.add_label("Stage", params.stage); + grid.add_param("Length", |ui| { + ui.vertical(|ui| { + let mut min = params.length.start().as_usize(); + let mut max = params.length.end().as_usize(); + let mut set_length = |min: usize, max: usize| { + params.length = len_range(min, max); + }; + + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut min)); + ui.label("to"); + ui.add(egui::DragValue::new(&mut max)); + ui.label("(inclusive)"); + set_length(min, max); + }); + ui.horizontal(|ui| { + if ui.button("Practice").clicked() { + set_length(0, 300); + } + if ui.button("QP").clicked() { + set_length(1250, 1350); + } + if ui.button("Half Peal").clicked() { + set_length(2500, 2600); + } + if ui.button("Peal").clicked() { + set_length(5000, 5200); + } + }); + }); + }); + grid.add_param_widget("Num comps", egui::DragValue::new(&mut params.num_comps)); + grid.add_param("Truth", |ui| { + ui.selectable_value(&mut params.require_truth, true, "Require truth"); + ui.selectable_value(&mut params.require_truth, false, "Allow falseness"); + }); + }); + } + + fn draw_method_params(&mut self, ui: &mut egui::Ui) { + // Other params + for (used, m) in &mut self.maybe_unused_methods { + Self::draw_method_gui(used, m, ui); + } + ui.separator(); + if !self.is_spliced() { + ui.label("(Include more than one method to show splicing options)"); + } else { + ParamTable::show(ui, 0, |grid| { + grid.add_param("Splice locations", |ui| { + ui.selectable_value( + &mut self.inner.splice_style, + SpliceStyle::LeadLabels, + "At leads", + ); + ui.selectable_value( + &mut self.inner.splice_style, + SpliceStyle::Calls, + "At calls", + ); + }); + grid.add_param_widget( + "Score per splice", + egui::Slider::new(&mut self.inner.splice_weight, -5.0..=5.0), + ); + grid.add_todo("Count range"); + grid.add_todo("Start indices"); + grid.add_todo("End indices"); + }); + } + } + + fn draw_method_gui( + used: &mut bool, + method: &mut monument::parameters::Method, + ui: &mut egui::Ui, + ) { + let heading = format!("{}: {}", method.shorthand(), method.title()); + egui::CollapsingHeader::new(heading) + .id_source(method.id) + .show(ui, |ui| { + ParamTable::show(ui, 0, |table| { + table.add_param_widget( + "Name", + egui::TextEdit::singleline(&mut method.inner.name), + ); + let default_shorthand = default_shorthand(&method.title()); + table.add_param_widget( + "Shorthand", + egui::TextEdit::singleline(&mut method.custom_shorthand) + .hint_text(default_shorthand), + ); + table.add_param_widget("Used", egui::Checkbox::new(used, "")); + }); + ui.separator(); + ParamTable::show(ui, 1, |table| { + table.add_label("Class", method.inner.class()); + table.add_label("Stage", method.inner.stage()); + }); + }); + } + + fn draw_calls_params(&mut self, ui: &mut egui::Ui) { + for (used, c) in &mut self.maybe_unused_calls { + Self::draw_call_ui(used, c, ui); + } + /* + TODO: Display call params + ui.separator(); + ParamTable::show(ui, 0, |grid| { + grid.add_param("Calling bell", |ui| { + for b in params.stage.bells() { + ui.selectable_value(&mut params.calling_bell, b, b.name()); + } + }); + }); + */ + } + + fn draw_call_ui(used: &mut bool, call: &mut Call, ui: &mut egui::Ui) { + let label_str = if call.label_from == call.label_to { + call.label_from.clone() + } else { + format!("{}->{}", call.label_from, call.label_to) + }; + let heading = format!("{} @ {} ({})", call.symbol, label_str, call.place_notation); + + egui::CollapsingHeader::new(heading) + .id_source(call.id) + .show(ui, |ui| { + ParamTable::show(ui, 0, |table| { + table.add_param_widget("Symbol", egui::TextEdit::singleline(&mut call.symbol)); + table.add_param_widget("Used", egui::Checkbox::new(used, "")); + table.add_param_widget( + "Weight", + egui::Slider::new(&mut call.weight, -20.0..=2.0), + ); + table.add_label("Place notation", &call.place_notation); + table.add_label("Lead location (from)", &call.label_from); + table.add_label("Lead location (to)", &call.label_to); + table.add_label("Calling positions", call.calling_positions.join("")); + }); + }); + } + + fn draw_music_params(&mut self, ui: &mut egui::Ui) { + ParamTable::show(ui, 0, |grid| { + grid.add_todo("Music types"); + grid.add_param("Start stroke", |ui| { + ui.selectable_value(&mut self.inner.start_stroke, Stroke::Hand, "Handstroke"); + ui.selectable_value(&mut self.inner.start_stroke, Stroke::Back, "Backstroke"); + }); + }); + } + + fn draw_courses_params(&mut self, ui: &mut egui::Ui) { + ParamTable::show(ui, 0, |grid| { + grid.add_todo("Start row"); + grid.add_todo("End row"); + grid.add_todo("Part head"); + grid.add_todo("Allowed courses"); + grid.add_todo("Course weights"); + }); + } +} diff --git a/monument/gui/src/parameters.rs b/monument/gui/src/parameters/mod.rs similarity index 97% rename from monument/gui/src/parameters.rs rename to monument/gui/src/parameters/mod.rs index 2016b8f2..12edca63 100644 --- a/monument/gui/src/parameters.rs +++ b/monument/gui/src/parameters/mod.rs @@ -1,3 +1,5 @@ +mod gui; + use bellframe::{Mask, PlaceNot, RowBuf, Stage, Stroke}; use itertools::Itertools; use monument::{ @@ -10,8 +12,8 @@ use crate::utils::len_range; #[derive(Debug, Clone)] pub struct Parameters { pub inner: monument::parameters::Parameters, - pub maybe_unused_methods: Vec<(bool, Method)>, - pub maybe_unused_calls: Vec<(bool, Call)>, + maybe_unused_methods: Vec<(bool, Method)>, + maybe_unused_calls: Vec<(bool, Call)>, } impl Parameters { diff --git a/monument/gui/src/params_gui.rs b/monument/gui/src/params_gui.rs deleted file mode 100644 index ca5fb23c..00000000 --- a/monument/gui/src/params_gui.rs +++ /dev/null @@ -1,201 +0,0 @@ -use bellframe::Stroke; -use eframe::egui; -use monument::parameters::{default_shorthand, Call, SpliceStyle}; - -use crate::utils::{len_range, ParamTable}; - -pub fn draw_params_gui(params: &mut crate::Parameters, ui: &mut egui::Ui) { - #[allow(clippy::type_complexity)] - let sections: [(&str, fn(&mut crate::Parameters, &mut egui::Ui)); 5] = [ - ("General", draw_general_params), - ( - &format!( - "Methods ({}/{})", - params.used_methods().count(), - params.maybe_unused_methods.len() - ), - draw_method_params, - ), - ( - &format!( - "Calls ({}/{})", - params.used_calls().count(), - params.maybe_unused_calls.len() - ), - draw_calls_params, - ), - ("Music", draw_music_params), - ("Courses", draw_courses_params), - ]; - - let mut is_first = true; - for (idx, (name, draw_fn)) in sections.into_iter().enumerate() { - if !is_first { - ui.add_space(30.0); - } - is_first = false; - egui::CollapsingHeader::new(name) - .id_source(idx) // Use index as an ID source because header names may change - .default_open(true) - .show(ui, |ui| draw_fn(params, ui)); - } -} - -fn draw_general_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { - let params = &mut params.inner; - ParamTable::show(ui, 0, |grid| { - grid.add_label("Stage", params.stage); - grid.add_param("Length", |ui| { - ui.vertical(|ui| { - let mut min = params.length.start().as_usize(); - let mut max = params.length.end().as_usize(); - let mut set_length = |min: usize, max: usize| { - params.length = len_range(min, max); - }; - - ui.horizontal(|ui| { - ui.add(egui::DragValue::new(&mut min)); - ui.label("to"); - ui.add(egui::DragValue::new(&mut max)); - ui.label("(inclusive)"); - set_length(min, max); - }); - ui.horizontal(|ui| { - if ui.button("Practice").clicked() { - set_length(0, 300); - } - if ui.button("QP").clicked() { - set_length(1250, 1350); - } - if ui.button("Half Peal").clicked() { - set_length(2500, 2600); - } - if ui.button("Peal").clicked() { - set_length(5000, 5200); - } - }); - }); - }); - grid.add_param_widget("Num comps", egui::DragValue::new(&mut params.num_comps)); - grid.add_param("Truth", |ui| { - ui.selectable_value(&mut params.require_truth, true, "Require truth"); - ui.selectable_value(&mut params.require_truth, false, "Allow falseness"); - }); - }); -} - -fn draw_method_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { - // Other params - for (used, m) in &mut params.maybe_unused_methods { - draw_method_gui(used, m, ui); - } - ui.separator(); - if !params.is_spliced() { - ui.label("(Include more than one method to show splicing options)"); - } else { - ParamTable::show(ui, 0, |grid| { - grid.add_param("Splice locations", |ui| { - ui.selectable_value( - &mut params.inner.splice_style, - SpliceStyle::LeadLabels, - "At leads", - ); - ui.selectable_value( - &mut params.inner.splice_style, - SpliceStyle::Calls, - "At calls", - ); - }); - grid.add_param_widget( - "Score per splice", - egui::Slider::new(&mut params.inner.splice_weight, -5.0..=5.0), - ); - grid.add_todo("Count range"); - grid.add_todo("Start indices"); - grid.add_todo("End indices"); - }); - } -} - -fn draw_method_gui(used: &mut bool, method: &mut monument::parameters::Method, ui: &mut egui::Ui) { - let heading = format!("{}: {}", method.shorthand(), method.title()); - egui::CollapsingHeader::new(heading) - .id_source(method.id) - .show(ui, |ui| { - ParamTable::show(ui, 0, |table| { - table.add_param_widget("Name", egui::TextEdit::singleline(&mut method.inner.name)); - let default_shorthand = default_shorthand(&method.title()); - table.add_param_widget( - "Shorthand", - egui::TextEdit::singleline(&mut method.custom_shorthand) - .hint_text(default_shorthand), - ); - table.add_param_widget("Used", egui::Checkbox::new(used, "")); - }); - ui.separator(); - ParamTable::show(ui, 1, |table| { - table.add_label("Class", method.inner.class()); - table.add_label("Stage", method.inner.stage()); - }); - }); -} - -fn draw_calls_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { - for (used, c) in &mut params.maybe_unused_calls { - draw_call_ui(used, c, ui); - } - /* - TODO: Display call params - ui.separator(); - ParamTable::show(ui, 0, |grid| { - grid.add_param("Calling bell", |ui| { - for b in params.stage.bells() { - ui.selectable_value(&mut params.calling_bell, b, b.name()); - } - }); - }); - */ -} - -fn draw_call_ui(used: &mut bool, call: &mut Call, ui: &mut egui::Ui) { - let label_str = if call.label_from == call.label_to { - call.label_from.clone() - } else { - format!("{}->{}", call.label_from, call.label_to) - }; - let heading = format!("{} @ {} ({})", call.symbol, label_str, call.place_notation); - - egui::CollapsingHeader::new(heading) - .id_source(call.id) - .show(ui, |ui| { - ParamTable::show(ui, 0, |table| { - table.add_param_widget("Symbol", egui::TextEdit::singleline(&mut call.symbol)); - table.add_param_widget("Used", egui::Checkbox::new(used, "")); - table.add_param_widget("Weight", egui::Slider::new(&mut call.weight, -20.0..=2.0)); - table.add_label("Place notation", &call.place_notation); - table.add_label("Lead location (from)", &call.label_from); - table.add_label("Lead location (to)", &call.label_to); - table.add_label("Calling positions", call.calling_positions.join("")); - }); - }); -} - -fn draw_music_params(params: &mut crate::Parameters, ui: &mut egui::Ui) { - ParamTable::show(ui, 0, |grid| { - grid.add_todo("Music types"); - grid.add_param("Start stroke", |ui| { - ui.selectable_value(&mut params.inner.start_stroke, Stroke::Hand, "Handstroke"); - ui.selectable_value(&mut params.inner.start_stroke, Stroke::Back, "Backstroke"); - }); - }); -} - -fn draw_courses_params(_params: &mut crate::Parameters, ui: &mut egui::Ui) { - ParamTable::show(ui, 0, |grid| { - grid.add_todo("Start row"); - grid.add_todo("End row"); - grid.add_todo("Part head"); - grid.add_todo("Allowed courses"); - grid.add_todo("Course weights"); - }); -} diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index d0f8e67d..aa1af85c 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -37,9 +37,7 @@ impl Project { // Comp params ui.add_space(10.0); ui.heading("Composition Parameters"); - egui::ScrollArea::vertical().show(ui, |ui| { - crate::params_gui::draw_params_gui(&mut self.params, ui) - }); + egui::ScrollArea::vertical().show(ui, |ui| self.params.draw_gui(ui)); should_search } From 9931c7f953075cedc0d875e7826e91cc890df612 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 2 Dec 2023 17:23:06 +0000 Subject: [PATCH 04/29] Filter compositions which fail splice style --- monument/lib/src/composition.rs | 37 ++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index ae8440af..610ef42d 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use crate::{ parameters::{ Call, CallDisplayStyle, CallId, CallIdx, MethodId, MethodIdx, MethodVec, MusicTypeVec, - Parameters, + Parameters, SpliceStyle, }, utils::lengths::{PerPartLength, TotalLength}, }; @@ -56,6 +56,13 @@ impl PathElem { .get_method(self.method_id) .add_sub_lead_idx(self.start_sub_lead_idx, self.length) } + + /// Returns true if going from `self` to `next` would be considered a 'splice' + fn is_splice_with(&self, next: &Self, params: &Parameters) -> bool { + let is_continuation = self.method_id == next.method_id + && self.end_sub_lead_idx(params) == next.start_sub_lead_idx; + !is_continuation + } } ////////////////// @@ -83,16 +90,40 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { /* CONSTRUCTION/VALIDATION */ pub fn new(composition: &'c Composition, params: &'p Parameters) -> Option { + // All methods/calls used must still exist + let method_map = Self::method_map(composition, params)?; + let call_map = Self::call_map(composition, params)?; + + // Stages must match if composition.stage != params.stage { return None; } + // Splice style must be satisfied + if params.splice_style == SpliceStyle::Calls { + let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { + // PERF: use the method map to speed up the splicing check + e1.is_splice_with(e2, params) && e1.ends_with_plain() + }; + for (elem1, elem2) in composition.path.iter().tuple_windows() { + if is_invalid_splice(elem1, elem2) { + return None; // Splice but no call + } + } + if params.is_multipart() && composition.path.len() > 2 { + let first = composition.path.first().unwrap(); + let last = composition.path.last().unwrap(); + if is_invalid_splice(last, first) { + return None; // Splice but no call over part head + } + } + } Some(CompositionGetter { composition, params, - method_map: Self::method_map(composition, params)?, - call_map: Self::call_map(composition, params)?, + method_map, + call_map, }) } From 0c77cacbd30f0a158106e43476b13d0dd24b7c13 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 2 Dec 2023 17:24:36 +0000 Subject: [PATCH 05/29] Use `-1` as default splice weight --- monument/gui/src/parameters/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monument/gui/src/parameters/mod.rs b/monument/gui/src/parameters/mod.rs index 12edca63..5917a5a0 100644 --- a/monument/gui/src/parameters/mod.rs +++ b/monument/gui/src/parameters/mod.rs @@ -79,7 +79,7 @@ impl Parameters { methods: index_vec::index_vec![], splice_style: SpliceStyle::LeadLabels, - splice_weight: 0.0, + splice_weight: -1.0, calls: index_vec::index_vec![], call_display_style: CallDisplayStyle::CallingPositions(stage.tenor()), atw_weight: None, // Don't calculate atw From abd84d2ccd1222e73744ec1ab785c5a31dc40f40 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 2 Dec 2023 17:49:47 +0000 Subject: [PATCH 06/29] Filter compositions which OOB length --- monument/lib/src/composition.rs | 48 +++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 610ef42d..ab4c1bc2 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -98,24 +98,13 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { if composition.stage != params.stage { return None; } + // Length must match + if !params.length.contains(&composition.length) { + return None; + } // Splice style must be satisfied - if params.splice_style == SpliceStyle::Calls { - let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { - // PERF: use the method map to speed up the splicing check - e1.is_splice_with(e2, params) && e1.ends_with_plain() - }; - for (elem1, elem2) in composition.path.iter().tuple_windows() { - if is_invalid_splice(elem1, elem2) { - return None; // Splice but no call - } - } - if params.is_multipart() && composition.path.len() > 2 { - let first = composition.path.first().unwrap(); - let last = composition.path.last().unwrap(); - if is_invalid_splice(last, first) { - return None; // Splice but no call over part head - } - } + if !Self::is_splice_style_satisfied(composition, params) { + return None; } Some(CompositionGetter { @@ -177,6 +166,31 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { Some(call_map) } + fn is_splice_style_satisfied(composition: &Composition, params: &Parameters) -> bool { + match params.splice_style { + SpliceStyle::LeadLabels => true, // Assume all comps are still valid + SpliceStyle::Calls => { + let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { + // PERF: use the method map to speed up the splicing check + e1.is_splice_with(e2, params) && e1.ends_with_plain() + }; + for (elem1, elem2) in composition.path.iter().tuple_windows() { + if is_invalid_splice(elem1, elem2) { + return false; // Splice but no call + } + } + if params.is_multipart() && composition.path.len() > 2 { + let first = composition.path.first().unwrap(); + let last = composition.path.last().unwrap(); + if is_invalid_splice(last, first) { + return false; // Splice but no call over part head + } + } + true + } + } + } + /* GETTERS */ /// The number of [`Row`]s in this composition. From 24d7850c9a4eaf2732a9d54e75faffb4c2ab7646 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 2 Dec 2023 17:50:05 +0000 Subject: [PATCH 07/29] Show lengths and scores of compositions --- monument/cli/src/lib.rs | 2 +- monument/cli/src/logging.rs | 2 +- monument/gui/src/project.rs | 39 ++++++++++++++++++++++++++++----- monument/lib/src/composition.rs | 3 ++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index 33707e6d..9ad8ba07 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -122,7 +122,7 @@ pub fn run( let comp = CompositionGetter::new(comp, ¶ms).unwrap(); ( rounded_float(comp.music_score()), - rounded_float(comp.average_score()), + rounded_float(comp.score_per_row()), comp.call_string(), ) }); diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index bd43c8a5..e7d1267a 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -335,7 +335,7 @@ impl CompositionPrinter { write!( s, "| {:>9.6} | {}", - comp.average_score(), + comp.score_per_row(), comp.call_string() ) .unwrap(); diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index aa1af85c..4bd2022f 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,5 +1,7 @@ -use eframe::egui; +use eframe::egui::{self, RichText}; +use itertools::Itertools; use monument::{Composition, CompositionGetter}; +use ordered_float::OrderedFloat; use crate::{search::SearchThreadHandle, utils::ParamTable}; @@ -94,15 +96,40 @@ impl Project { egui::ScrollArea::vertical() .auto_shrink([false, false]) .show(ui, |ui| { - for c in &self.compositions { - if let Some(getter) = CompositionGetter::new(c, &monument_params) { - ui.label(getter.call_string()); - } - } + self.draw_comps(&monument_params, ui); }); } } + fn draw_comps(&mut self, params: &monument::Parameters, ui: &mut egui::Ui) { + // Generate getters for all comps which still match the current params + let mut comp_getters = self + .compositions + .iter() + .filter_map(|c| CompositionGetter::new(c, params)) + .collect_vec(); + // Sort them by total score + comp_getters.sort_by_cached_key(|g| OrderedFloat(-g.total_score())); + // Display them in a grid + // TODO: Custom (animated!) widget for this + egui::Grid::new("Comp grid").striped(true).show(ui, |ui| { + // Header + ui.label(RichText::new("Length").strong()); + ui.label(RichText::new("Score").strong()); + ui.label(RichText::new("Score/row").strong()); + ui.label(RichText::new("Call string").strong()); + ui.end_row(); + + for g in comp_getters { + ui.label(g.length().to_string()); + ui.label(format!("{:.2}", g.total_score())); + ui.label(format!("{:.6}", g.score_per_row())); + ui.label(g.call_string()); + ui.end_row() + } + }); + } + ///////////// // HELPERS // ///////////// diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index ab4c1bc2..bdc137aa 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -205,12 +205,13 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { /// The total score generated by this composition from all the different weights (music, calls, /// changes of method, handbell coursing, etc.). pub fn total_score(&self) -> f32 { + // TODO: Calculate this self.composition.total_score } /// The average score generated by each [`Row`] in this composition. This is equal to /// `self.total_score() / self.length() as f32`. - pub fn average_score(&self) -> f32 { + pub fn score_per_row(&self) -> f32 { self.total_score() / self.length() as f32 } From 7f2d4847765f8bfdd6e8aff431484f00a8eeb00e Mon Sep 17 00:00:00 2001 From: Kneasle Date: Thu, 7 Dec 2023 23:20:14 +0000 Subject: [PATCH 08/29] Implement all checks except method count range --- bellframe/src/block.rs | 20 +- bellframe/src/method/mod.rs | 4 +- monument/lib/src/composition.rs | 299 ++++++++++++++++------ monument/lib/src/graph/build/falseness.rs | 6 +- monument/lib/src/group.rs | 16 +- monument/lib/src/parameters.rs | 37 ++- monument/lib/src/search/prefix.rs | 21 +- 7 files changed, 295 insertions(+), 108 deletions(-) diff --git a/bellframe/src/block.rs b/bellframe/src/block.rs index bfe12648..0114cbf1 100644 --- a/bellframe/src/block.rs +++ b/bellframe/src/block.rs @@ -2,6 +2,7 @@ //! starting [`Row`] and yields a sequence of permuted [`Row`]s. use std::{ + collections::HashSet, fmt::{Debug, Display, Formatter}, iter::repeat_with, ops::RangeBounds, @@ -12,7 +13,7 @@ use itertools::Itertools; use crate::{ row::{same_stage_vec, DbgRow}, - utils, Bell, Row, RowBuf, SameStageVec, Stage, + utils, Bell, Row, RowBuf, SameStageVec, Stage, Truth, }; /// A block of [`Row`], each of which can be given an annotation of any type. Blocks can start @@ -193,6 +194,23 @@ impl Block { self.rows.last_mut().unwrap() } + #[inline] + pub fn is_true(&self) -> bool { + self.truth().is_true() + } + + /// Returns whether a row is repeated within this [`Block`] (not including the + /// [leftover row](Self::leftover_row)) + pub fn truth(&self) -> Truth { + let mut rows_so_far = HashSet::<&Row>::with_capacity(self.len()); + for row in self.rows() { + if !rows_so_far.insert(row) { + return Truth::False; // Row has repeated + } + } + Truth::True // If no rows repeated, composition is true + } + ////////////////////////////// // ITERATORS / PATH GETTERS // ////////////////////////////// diff --git a/bellframe/src/method/mod.rs b/bellframe/src/method/mod.rs index 11ecb83d..2fcbae54 100644 --- a/bellframe/src/method/mod.rs +++ b/bellframe/src/method/mod.rs @@ -213,11 +213,11 @@ impl Method { } /// An [`Iterator`] over the sub-lead indices of a particular lead label. - pub fn all_label_indices(&self) -> Vec<(&str, usize)> { + pub fn all_label_indices(&self) -> Vec<(usize, &str)> { let mut label_indices = Vec::new(); for (sub_lead_idx, labels) in self.first_lead.annots().enumerate() { for label in labels { - label_indices.push((label.as_str(), sub_lead_idx)); + label_indices.push((sub_lead_idx, label.as_str())); } } label_indices diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index bdc137aa..fccb759a 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -5,7 +5,7 @@ use std::{ hash::Hash, }; -use bellframe::{music::AtRowPositions, Bell, Block, Row, RowBuf, Stage, Stroke}; +use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke}; use itertools::Itertools; use crate::{ @@ -13,7 +13,10 @@ use crate::{ Call, CallDisplayStyle, CallId, CallIdx, MethodId, MethodIdx, MethodVec, MusicTypeVec, Parameters, SpliceStyle, }, - utils::lengths::{PerPartLength, TotalLength}, + utils::{ + lengths::{PerPartLength, TotalLength}, + Boundary, + }, }; #[allow(unused_imports)] // Used by doc comments @@ -63,6 +66,14 @@ impl PathElem { && self.end_sub_lead_idx(params) == next.start_sub_lead_idx; !is_continuation } + + fn lead_head(&self, method_map: &HashMap) -> RowBuf { + let row_in_first_lead = method_map[&self.method_id] + .double_plain_course + .get_row(self.start_sub_lead_idx) + .unwrap(); + &self.start_row * !row_in_first_lead + } } ////////////////// @@ -78,6 +89,10 @@ pub struct CompositionGetter<'c, 'p> { method_map: HashMap, call_map: HashMap, + /// Set of labels at which the composition can end. I.e. these are labels which are also a + /// valid `end_index` for some method. + valid_end_labels: HashSet, + block: Block<(MethodId, usize)>, } #[derive(Debug, Clone)] @@ -90,30 +105,55 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { /* CONSTRUCTION/VALIDATION */ pub fn new(composition: &'c Composition, params: &'p Parameters) -> Option { - // All methods/calls used must still exist + // Do cheap checks before calculating anything. This is useful because the search + // algorithm produces tons of too-short compositions, so it's worth validating length + // quickly and thus rejecting these. + if !Self::do_cheap_checks(composition, params) { + return None; + } + + // Once the cheap checks pass, compute useful cached values and build the `getter` let method_map = Self::method_map(composition, params)?; let call_map = Self::call_map(composition, params)?; + let getter = CompositionGetter { + composition, + params, - // Stages must match - if composition.stage != params.stage { + block: Self::block(composition, params, &method_map, &call_map), + valid_end_labels: params.valid_end_labels(), + method_map, + call_map, + }; + + if !getter.do_non_cheap_checks() { return None; } - // Length must match + + Some(getter) // If all checks didn't find problems, the getter is valid + } + + /// Perform cheap checks on this composition which don't involve looking up methods or calls. + /// The main reason to do this is to quickly reject compositions which are being generated by the + /// search routine + #[must_use] + fn do_cheap_checks(composition: &Composition, params: &Parameters) -> bool { + if composition.stage != params.stage { + return false; // Stage mismatch + } if !params.length.contains(&composition.length) { - return None; + return false; // Length mismatch } - // Splice style must be satisfied - if !Self::is_splice_style_satisfied(composition, params) { - return None; + if !params + .part_head_group + .is_row_generator(&composition.part_head) + { + return false; // Composition doesn't end in a valid part + } + if composition.path[0].start_row != params.start_row { + return false; // Doesn't start in the right row } - Some(CompositionGetter { - composition, - params, - - method_map, - call_map, - }) + true // Can't reject composition this easily } /// If every [`MethodId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` @@ -166,22 +206,164 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { Some(call_map) } - fn is_splice_style_satisfied(composition: &Composition, params: &Parameters) -> bool { - match params.splice_style { + /// Return a [`Block`] containing the [`Row`]s in this composition. Each [`Row`] is annotated + /// with a `(method index, index within a lead)` pair. For example, splicing a lead of Bastow + /// into Cambridge Major would create a [`Block`] which starts like: + /// + /// ```text + /// Block { + /// 12345678: (, 0), + /// 21436587: (, 1), + /// 21345678: (, 2), + /// 12436587: (, 3), + /// 14263857: (, 0), + /// 41628375: (, 1), + /// 14682735: (, 2), + /// 41867253: (, 3), + /// 48162735: (, 4), + /// ... + /// } + /// ``` + /// + /// If this `Composition` uses methods or calls which are not specified in the [`Parameters`], + /// then `None` is returned. + fn block( + composition: &Composition, + params: &Parameters, + method_map: &HashMap, + call_map: &HashMap, + ) -> Block<(MethodId, usize)> { + // Generate the first part + let mut first_part = + Block::<(MethodId, usize)>::with_leftover_row(params.start_row.clone()); + for elem in &composition.path { + assert_eq!(first_part.leftover_row(), elem.start_row.as_row()); + // Copy the corresponding part of this method's (double) plain course + let double_plain_course = &method_map[&elem.method_id].double_plain_course; + let start_idx = elem.start_sub_lead_idx; + let end_idx = start_idx + elem.length.as_usize(); + first_part.extend_range(double_plain_course, start_idx..end_idx); + // If this PathElem ends in a call, then change the `leftover_row` to suit + if let Some(call_id) = elem.call_to_end { + let call = ¶ms.calls[call_map[&call_id]]; + let last_non_leftover_row = first_part.rows().next_back().unwrap(); + let new_leftover_row = last_non_leftover_row * call.place_notation.transposition(); + first_part.leftover_row_mut().copy_from(&new_leftover_row); + } + } + + // Generate the other parts from the first + let part_len = first_part.len(); + let mut comp = first_part; + for _ in 0..params.num_parts() - 1 { + comp.extend_from_within(..part_len); + } + assert_eq!(comp.len(), composition.length.as_usize()); + assert_eq!(comp.leftover_row(), ¶ms.end_row); + comp + } + + fn do_non_cheap_checks(&self) -> bool { + if !self.are_methods_satisfied() { + return false; + } + if !self.is_splice_style_satisfied() { + return false; + } + if self.params.require_atw && !self.is_atw() { + return false; + } + if self.params.require_truth && !self.block.is_true() { + return false; // Composition is false but we needed it to be true + } + if self.block.leftover_row() != &self.params.end_row { + return false; // Comps ends on the wrong row + } + for (mt, counts) in self.params.music_types.iter().zip_eq(self.music_counts()) { + let total = mt.masked_total(counts); + if !mt.count_range.contains(total) { + return false; // Music count range isn't satisfied + } + } + // Start indices + let first_elem = &self.composition.path[0]; + let start_indices = self + .get_method(first_elem.method_id) + .wrapped_indices(Boundary::Start, self.params); + if !start_indices.contains(&first_elem.start_sub_lead_idx) { + return false; // Composition couldn't start in this way + } + // End indices + let last_elem = self.composition.path.last().unwrap(); + match last_elem.call_to_end { + None => { + // The last element ends with a plain lead, so we need to check that this chunk's + // end sub-lead index is valid + let end_indices = self + .get_method(last_elem.method_id) + .wrapped_indices(Boundary::End, self.params); + if !end_indices.contains(&last_elem.end_sub_lead_idx(self.params)) { + return false; // End index isn't valid for this method + } + } + Some(call_id) => { + // If the composition ends with a call, then the situation is more complex; we need + // to check that the call leads to a method which could end immediately + // (conceptually, this introduces an imaginary 0-length 'path-elem' at the end) + let call = &self.params.calls[self.call_map[&call_id]]; + if !self.valid_end_labels.contains(&call.label_to) { + return false; // Call's label_to can't correspond to a valid end idx + } + } + } + + true // Composition is all OK + } + + fn are_methods_satisfied(&self) -> bool { + let allowed_lead_heads: MethodVec> = self + .params + .methods + .iter() + .map(|m| m.allowed_lead_head_masks(self.params)) + .collect(); + + let mut method_counts: MethodVec<_> = + index_vec::index_vec![TotalLength::ZERO; self.params.methods.len()]; + for path_elem in &self.composition.path { + let method_idx = self.method_map[&path_elem.method_id].idx; + method_counts[method_idx] += path_elem.length.as_total(&self.params.part_head_group); + // Check if lead head is valid + let lead_head = path_elem.lead_head(&self.method_map); + if !allowed_lead_heads[method_idx] + .iter() + .any(|m| m.matches(&lead_head)) + { + return false; // If no lead heads matched, this course isn't valid anymore + } + } + + // TODO: Check method ranges using refined lengths + + true + } + + fn is_splice_style_satisfied(&self) -> bool { + match self.params.splice_style { SpliceStyle::LeadLabels => true, // Assume all comps are still valid SpliceStyle::Calls => { let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { // PERF: use the method map to speed up the splicing check - e1.is_splice_with(e2, params) && e1.ends_with_plain() + e1.is_splice_with(e2, &self.params) && e1.ends_with_plain() }; - for (elem1, elem2) in composition.path.iter().tuple_windows() { + for (elem1, elem2) in self.composition.path.iter().tuple_windows() { if is_invalid_splice(elem1, elem2) { return false; // Splice but no call } } - if params.is_multipart() && composition.path.len() > 2 { - let first = composition.path.first().unwrap(); - let last = composition.path.last().unwrap(); + if self.params.is_multipart() && self.composition.path.len() > 2 { + let first = self.composition.path.first().unwrap(); + let last = self.composition.path.last().unwrap(); if is_invalid_splice(last, first) { return false; // Splice but no call over part head } @@ -202,6 +384,10 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { &self.composition.part_head } + pub fn is_true(&self) -> bool { + self.block.is_true() + } + /// The total score generated by this composition from all the different weights (music, calls, /// changes of method, handbell coursing, etc.). pub fn total_score(&self) -> f32 { @@ -279,6 +465,10 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { s } + pub fn is_atw(&self) -> bool { + self.atw_factor() == 1.0 + } + /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything /// was rung (i.e. the composition is atw) pub fn atw_factor(&self) -> f32 { @@ -295,9 +485,9 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { // Compute which `(bell, place, method, sub-lead-index)` quadruples have been rung in this // composition. // - // TODO: Use a more efficient storage method + // TODO: Use a more efficient storage method for common cases like ringing whole leads let mut rung_place_bell_positions = HashSet::<(Bell, u8, MethodId, usize)>::new(); - for (&(method_id, sub_lead_idx), row) in self.rows().annot_rows() { + for (&(method_id, sub_lead_idx), row) in self.block.annot_rows() { for (place, bell) in row.bell_iter().enumerate() { if is_working[bell.index()] { rung_place_bell_positions.insert((bell, place as u8, method_id, sub_lead_idx)); @@ -336,68 +526,15 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { self.music_counts_and_score().1 } - /// The number of *instances* of each [`MusicType`] in the [`Search`]. + /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. pub fn music_counts(&self) -> MusicTypeVec> { - let rows = self.rows(); self.params .music_types .iter() - .map(|mt| mt.count(&rows, !self.composition.start_stroke)) + .map(|mt| mt.count(&self.block, !self.composition.start_stroke)) .collect() } - /// Return a [`Block`] containing the [`Row`]s in this composition. Each [`Row`] is annotated - /// with a `(method index, index within a lead)` pair. For example, splicing a lead of Bastow - /// into Cambridge Major would create a [`Block`] which starts like: - /// - /// ```text - /// Block { - /// 12345678: (, 0), - /// 21436587: (, 1), - /// 21345678: (, 2), - /// 12436587: (, 3), - /// 14263857: (, 0), - /// 41628375: (, 1), - /// 14682735: (, 2), - /// 41867253: (, 3), - /// 48162735: (, 4), - /// ... - /// } - /// ``` - /// - /// If this `Composition` uses methods or calls which are not specified in the [`Parameters`], - /// then `None` is returned. - pub fn rows(&self) -> Block<(MethodId, usize)> { - // Generate the first part - let mut first_part = - Block::<(MethodId, usize)>::with_leftover_row(self.params.start_row.clone()); - for elem in &self.composition.path { - assert_eq!(first_part.leftover_row(), elem.start_row.as_row()); - // Copy the corresponding part of this method's (double) plain course - let double_plain_course = &self.method_map[&elem.method_id].double_plain_course; - let start_idx = elem.start_sub_lead_idx; - let end_idx = start_idx + elem.length.as_usize(); - first_part.extend_range(double_plain_course, start_idx..end_idx); - // If this PathElem ends in a call, then change the `leftover_row` to suit - if let Some(call_id) = elem.call_to_end { - let last_non_leftover_row = first_part.rows().next_back().unwrap(); - let new_leftover_row = - last_non_leftover_row * self.get_call(call_id).place_notation.transposition(); - first_part.leftover_row_mut().copy_from(&new_leftover_row); - } - } - - // Generate the other parts from the first - let part_len = first_part.len(); - let mut comp = first_part; - for _ in 0..self.params.num_parts() - 1 { - comp.extend_from_within(..part_len); - } - assert_eq!(comp.len(), self.length()); - assert_eq!(comp.leftover_row(), &self.params.end_row); - comp - } - fn get_method(&self, id: MethodId) -> &Method { &self.params.methods[self.method_map[&id].idx] } diff --git a/monument/lib/src/graph/build/falseness.rs b/monument/lib/src/graph/build/falseness.rs index c4987678..9fa62034 100644 --- a/monument/lib/src/graph/build/falseness.rs +++ b/monument/lib/src/graph/build/falseness.rs @@ -56,10 +56,10 @@ pub(super) fn set_links( /// /// Naively iterating through every pair of chunks is far too slow, so this instead stores a set of /// false lead head transpositions between the different chunk types. This way, once the table is -/// built, the computing the falseness of a chunk is one [`HashMap`] lookup and some row +/// built, computing the falseness of a chunk costs one [`HashMap`] lookup and some row /// transpositions. Building the `FalsenessTable` is still quadratic, but it's quadratic in the -/// number of _unique chunk ranges_, which is often orders of magnitude smaller than the total -/// number of chunks. +/// number of _unique chunk ranges_, which is orders of magnitude smaller than the total number of +/// chunks and usually scales much slower. #[derive(Debug, Clone)] struct FalsenessTable { /// For each [`ChunkRange`], list the false [`ChunkRange`]s and the lead head transpositions diff --git a/monument/lib/src/group.rs b/monument/lib/src/group.rs index 07a87e46..35fa8f6d 100644 --- a/monument/lib/src/group.rs +++ b/monument/lib/src/group.rs @@ -13,8 +13,8 @@ pub struct PartHeadGroup { /// Invariant: there is some row `r` such that `part_heads[i] = r^i` for all `i`. // PERF: Make this a `SameStageVec` part_heads: Vec, - /// For each value in `part_heads`, the corresponding bit is `1` iff that part head generates - /// all the others + /// For each value in `part_heads`, the bit at index `i` is `1` iff that `part_heads[i]` is + /// a generator for this group is_generator_bitmap: u64, } @@ -123,6 +123,18 @@ impl PartHeadGroup { &self.part_heads[element.index as usize] } + pub fn get_part_head(&self, row: &Row) -> Option { + let index = self.part_heads.iter().position(|x| x == row)?; + Some(PartHead { index: index as u8 }) + } + + pub fn is_row_generator(&self, row: &Row) -> bool { + let Some(part_head) = self.get_part_head(row) else { + return false; // Can't be a generator if it isn't in the group + }; + self.is_generator(part_head) + } + /// Returns `true` iff every [`PartHead`] in this group is a power of the given `part_head` pub fn is_generator(&self, part_head: PartHead) -> bool { self.is_generator_bitmap & (1 << part_head.index) != 0 diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index b995d07c..658789f4 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -49,7 +49,6 @@ pub struct Parameters { // NOTE: Course masks are defined on each `Method` pub start_row: RowBuf, pub end_row: RowBuf, - // TODO: Explicitly compute PH group, leave user to just input rows? pub part_head_group: PartHeadGroup, /// Score applied to every row in every course containing a lead head matching the /// corresponding [`Mask`]. @@ -278,6 +277,19 @@ impl Parameters { } } + pub(crate) fn valid_end_labels(&self) -> HashSet { + let mut valid_labels = HashSet::new(); + for m in &self.methods { + let wrapped_indices = m.wrapped_indices(Boundary::End, self); + for (sub_lead_idx, label) in m.inner.all_label_indices() { + if wrapped_indices.contains(&sub_lead_idx) { + valid_labels.insert(label.to_owned()); + } + } + } + valid_labels + } + /// Returns a human-readable string representing the given methods. /// /// This is: @@ -511,14 +523,17 @@ pub fn default_shorthand(title: &str) -> String { pub struct Call { pub id: CallId, - pub symbol: String, - pub calling_positions: Vec, - + // These fields determine what 'functionally' defines a specific call. Modifying these will + // likely invalidate any existing compositions, and thus should not be modified without also + // changing the `CallId`. pub label_from: String, pub label_to: String, // TODO: Allow calls to cover multiple PNs (e.g. singles in Grandsire) pub place_notation: PlaceNot, + pub symbol: String, + pub calling_positions: Vec, + pub weight: f32, } @@ -933,6 +948,20 @@ impl OptionalRangeInclusive { max: None, }; + pub fn contains(self, v: usize) -> bool { + if let Some(min) = self.min { + if v < min { + return false; // `v` is too small + } + } + if let Some(max) = self.max { + if v > max { + return false; // `v` is too big + } + } + true // `v` is just right :D + } + /// Returns `true` if at least one of `min` or `max` is set pub fn is_set(self) -> bool { self.min.is_some() || self.max.is_some() diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index dc0a924e..925ff383 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -1,10 +1,5 @@ -use std::{ - cmp::Ordering, - collections::{BinaryHeap, HashSet}, - ops::Deref, -}; +use std::{cmp::Ordering, collections::BinaryHeap, ops::Deref}; -use bellframe::Row; use bit_vec::BitVec; use datasize::DataSize; use ordered_float::OrderedFloat; @@ -322,15 +317,11 @@ impl CompPrefix { // Sanity check that the composition is true if params.require_truth { let comp_getter = CompositionGetter::new(&comp, params).unwrap(); - - let mut rows_so_far = HashSet::<&Row>::with_capacity(comp_getter.length()); - for row in comp_getter.rows().rows() { - if !rows_so_far.insert(row) { - panic!( - "Generated false composition ({})", - comp_getter.call_string() - ); - } + if !comp_getter.is_true() { + panic!( + "Generated false composition ({})", + comp_getter.call_string() + ); } } // Finally, return the comp From 7f8950554ebcdab69663a1306dcb89cf64250947 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 9 Dec 2023 18:15:55 +0000 Subject: [PATCH 09/29] Use `CompositionGetter::new` to check candidate compositions --- monument/lib/src/search/prefix.rs | 5 ++++- monument/test/results.toml | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 925ff383..953fa520 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -314,9 +314,12 @@ impl CompPrefix { total_score: score, }; + // Validate the composition by building a `CompositionGetter`. The checks performed by + // `CompositionGetter::new` are much stricter and more correct than those we can perform + // here, so we defer entirely to it to check these candidate compositions for validity. + let comp_getter = CompositionGetter::new(&comp, params)?; // Sanity check that the composition is true if params.require_truth { - let comp_getter = CompositionGetter::new(&comp, params).unwrap(); if !comp_getter.is_true() { panic!( "Generated false composition ({})", diff --git a/monument/test/results.toml b/monument/test/results.toml index 57586706..e68f731d 100644 --- a/monument/test/results.toml +++ b/monument/test/results.toml @@ -3152,7 +3152,6 @@ len Y B | music | avg score | calling 256 : 224 32 | 0.00 | -0.017969 | YYYYYYY[sH]B[sH] 258 : 2 256 | 0.00 | -0.015891 | B[sH]B[H]BBBBBBY> 224 : 0 224 | 0.00 | 0.000000 | BBBBBBB -224 : 224 0 | 0.00 | 0.000000 | YYYYYYY --------------|---------|-----------|----------- len Y B | music | avg score | calling ''' @@ -3189,7 +3188,6 @@ len Y B | music | avg score | calling 258 : 2 256 | 0.00 | -0.015891 | B[sH]B[H]BBBBBBY> 224 : 0 224 | 0.00 | 0.000000 | 224 : 0 224 | 0.00 | 0.000000 | BBBBBBB -224 : 224 0 | 0.00 | 0.000000 | YYYYYYY --------------|---------|-----------|----------- len Y B | music | avg score | calling ''' From 020fefe93c6e3f0d5afba77776e7c9d7e1586e4c Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 9 Dec 2023 18:15:05 +0000 Subject: [PATCH 10/29] Dynamically calculate composition scores --- monument/lib/src/composition.rs | 83 +++++++++++++++++++++-------- monument/lib/src/graph/build/mod.rs | 62 ++++++++++++--------- monument/lib/src/parameters.rs | 10 ++++ monument/lib/src/search/prefix.rs | 19 ++++++- monument/test/results.toml | 42 +++++++-------- 5 files changed, 146 insertions(+), 70 deletions(-) diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index fccb759a..a3452868 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -33,10 +33,6 @@ pub struct Composition { pub(crate) path: Vec, pub(crate) length: TotalLength, pub(crate) part_head: RowBuf, - - /// The total score generated by this composition, accumulated from music, calls, coursing - /// patterns, etc. - pub(crate) total_score: f32, } /// A piece of a [`Composition`] @@ -61,7 +57,7 @@ impl PathElem { } /// Returns true if going from `self` to `next` would be considered a 'splice' - fn is_splice_with(&self, next: &Self, params: &Parameters) -> bool { + fn is_splice_when_followed_by(&self, next: &Self, params: &Parameters) -> bool { let is_continuation = self.method_id == next.method_id && self.end_sub_lead_idx(params) == next.start_sub_lead_idx; !is_continuation @@ -99,6 +95,7 @@ pub struct CompositionGetter<'c, 'p> { struct MethodData { idx: MethodIdx, double_plain_course: Block<(MethodId, usize)>, + lead_head_weights: Vec<(Mask, f32)>, } impl<'c, 'p> CompositionGetter<'c, 'p> { @@ -172,7 +169,8 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { let mut method_map = HashMap::new(); for id in methods { let idx = params.methods.position(|m| m.id == id)?; - let mut double_plain_course = params.methods[idx] + let method = ¶ms.methods[idx]; + let mut double_plain_course = method .plain_course() .map_annots(|annot| (id, annot.sub_lead_idx)); double_plain_course.extend_from_within(..); @@ -181,6 +179,7 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { MethodData { idx, double_plain_course, + lead_head_weights: method.lead_head_weights(params), }, ); } @@ -310,8 +309,8 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { // If the composition ends with a call, then the situation is more complex; we need // to check that the call leads to a method which could end immediately // (conceptually, this introduces an imaginary 0-length 'path-elem' at the end) - let call = &self.params.calls[self.call_map[&call_id]]; - if !self.valid_end_labels.contains(&call.label_to) { + let end_label = &self.get_call(call_id).label_to; + if !self.valid_end_labels.contains(end_label) { return false; // Call's label_to can't correspond to a valid end idx } } @@ -354,7 +353,7 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { SpliceStyle::Calls => { let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { // PERF: use the method map to speed up the splicing check - e1.is_splice_with(e2, &self.params) && e1.ends_with_plain() + e1.is_splice_when_followed_by(e2, &self.params) && e1.ends_with_plain() }; for (elem1, elem2) in self.composition.path.iter().tuple_windows() { if is_invalid_splice(elem1, elem2) { @@ -388,19 +387,6 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { self.block.is_true() } - /// The total score generated by this composition from all the different weights (music, calls, - /// changes of method, handbell coursing, etc.). - pub fn total_score(&self) -> f32 { - // TODO: Calculate this - self.composition.total_score - } - - /// The average score generated by each [`Row`] in this composition. This is equal to - /// `self.total_score() / self.length() as f32`. - pub fn score_per_row(&self) -> f32 { - self.total_score() / self.length() as f32 - } - /// Generate a human-friendly [`String`] summarising the calling of this composition. For /// example, [this composition](https://complib.org/composition/87419) would have a /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. @@ -465,6 +451,59 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { s } + /// The average score generated by each [`Row`] in this composition. This is equal to + /// `self.total_score() / self.length() as f32`. + pub fn score_per_row(&self) -> f32 { + self.total_score() / self.length() as f32 + } + + /// The total score generated by this composition from all the different weights (music, calls, + /// changes of method, handbell coursing, etc.). + pub fn total_score(&self) -> f32 { + let mut total_score = 0.0; + // Music + total_score += self.music_score(); + // ATW + if let Some(atw_weight) = self.params.atw_weight { + total_score += self.atw_factor() * atw_weight; + } + // Calls + for elem in &self.composition.path { + if let Some(call_id) = elem.call_to_end { + let call = self.get_call(call_id); + total_score += call.weight * self.params.num_parts() as f32; + } + } + // Splices + let mut changes_of_method = 0; + for (e1, e2) in self.composition.path.iter().tuple_windows() { + if e1.is_splice_when_followed_by(e2, self.params) { + changes_of_method += self.params.num_parts(); + } + } + let first_elem = self.composition.path.first().unwrap(); + let last_elem = self.composition.path.last().unwrap(); + if last_elem.is_splice_when_followed_by(first_elem, self.params) { + // -1 because there's no splice around the end/start of the composition + changes_of_method += self.params.num_parts() - 1; + } + total_score += changes_of_method as f32 * self.params.splice_weight; + // Course weights + for elem in &self.composition.path { + let lead_head_weights = &self.method_map[&elem.method_id].lead_head_weights; + let lead_head = elem.lead_head(&self.method_map); + for part_head in self.composition.part_head.closure() { + let lead_head_in_part = part_head * &lead_head; + for (mask, weight) in lead_head_weights { + if mask.matches(&lead_head_in_part) { + total_score += *weight * elem.length.as_usize() as f32; + } + } + } + } + total_score + } + pub fn is_atw(&self) -> bool { self.atw_factor() == 1.0 } diff --git a/monument/lib/src/graph/build/mod.rs b/monument/lib/src/graph/build/mod.rs index 991ef9a1..65803b4e 100644 --- a/monument/lib/src/graph/build/mod.rs +++ b/monument/lib/src/graph/build/mod.rs @@ -10,14 +10,12 @@ use std::{ time::Instant, }; -use bellframe::{ - method::RowAnnot, music::AtRowPositions, Block, Mask, PlaceNot, Row, RowBuf, Stroke, StrokeSet, -}; +use bellframe::{music::AtRowPositions, Block, Mask, PlaceNot, Row, RowBuf, Stroke, StrokeSet}; use itertools::Itertools; use crate::{ group::{PartHeadGroup, PhRotation}, - parameters::{Call, MethodIdx, MethodVec, Parameters}, + parameters::{Call, Method, MethodIdx, MethodVec, Parameters}, search::Config, utils::counts::Counts, }; @@ -84,20 +82,13 @@ impl Graph { return Err(crate::Error::InconsistentStroke); } // Now we know the starting strokes, count the music on each chunk - let double_plain_courses: MethodVec<_> = params + let method_caches: MethodVec = params .methods .iter() - .map(|m| { - let mut double_plain_course = m.plain_course(); - // Add another plain course. We use 'double plain courses' so that the range - // covered by a chunk is always a contiguous section of these cached plain - // courses (otherwise, a chunk could wrap over the end of one plain course). - double_plain_course.extend_from_within(..); - double_plain_course - }) + .map(|m| MethodCacheData::new(m, params)) .collect(); for (id, chunk) in &mut chunks { - count_scores(id, chunk, &double_plain_courses, &start_strokes, params); + count_scores(id, chunk, &method_caches, &start_strokes, params); } log::debug!(" Music counted in {:.2?}", start.elapsed()); @@ -131,6 +122,26 @@ impl Graph { } } +struct MethodCacheData<'params> { + double_plain_course: Block>, + lead_head_weights: Vec<(Mask, f32)>, +} + +impl<'params> MethodCacheData<'params> { + fn new(method: &'params Method, params: &'params Parameters) -> Self { + let mut double_plain_course = method.plain_course(); + // Add another plain course. We use 'double plain courses' so that the range + // covered by a chunk is always a contiguous section of these cached plain + // courses (otherwise, a chunk could wrap over the end of one plain course). + double_plain_course.extend_from_within(..); + + Self { + double_plain_course, + lead_head_weights: method.lead_head_weights(params), + } + } +} + /// Creates a blank [`Chunk`] from a [`ChunkId`] and corresponding [`PerPartLength`]. fn expand_chunk(id: &ChunkId, per_part_length: PerPartLength, params: &Parameters) -> Chunk { let total_length = per_part_length.as_total(¶ms.part_head_group); @@ -217,7 +228,7 @@ fn get_start_strokes( fn count_scores( id: &ChunkId, chunk: &mut Chunk, - double_plain_courses: &MethodVec>, + method_caches: &MethodVec, start_strokes: &Option>, params: &Parameters, ) { @@ -241,16 +252,19 @@ fn count_scores( // start stroke None => Stroke::Back, }; - let double_plain_course = &double_plain_courses[id.method]; - let lead_heads = params.methods[id.method].inner.lead_head().closure(); + let method_cache = &method_caches[id.method]; for part_head in params.part_head_group.rows() { let lead_head_in_part = part_head * id.lead_head.as_ref(); - let start_row = &lead_head_in_part * double_plain_course.get_row(id.sub_lead_idx).unwrap(); + let start_row = &lead_head_in_part + * method_cache + .double_plain_course + .get_row(id.sub_lead_idx) + .unwrap(); // Determine the rows that this chunk contains let mut rows = Block::empty(params.stage); rows.extend_range( - double_plain_course, + &method_cache.double_plain_course, id.sub_lead_idx..(id.sub_lead_idx + chunk.per_part_length.as_usize()), ); rows.pre_multiply(&start_row); @@ -267,12 +281,10 @@ fn count_scores( // methods, `xxxxxx78` will expand into masks // `[xxxxxx78, xxxxx8x7, xxx8x7xx, x8x7xxxx, x78xxxxx, xx7x8xxx, xxxx7x8x]` (every one of // those leads is included in the course for `xxxxxx78`) - for (mask, weight) in ¶ms.course_weights { - for lead_head in &lead_heads { - if (mask * lead_head).matches(&lead_head_in_part) { - // Weight applies to each row - chunk.score += *weight * chunk.per_part_length.as_usize() as f32; - } + for (mask, weight) in &method_cache.lead_head_weights { + if mask.matches(&lead_head_in_part) { + // Weight applies to each row + chunk.score += *weight * chunk.per_part_length.as_usize() as f32; } } } diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index 658789f4..c6d53d9f 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -375,6 +375,16 @@ impl Method { (sub_lead_idx + len.as_usize()) % self.lead_len() } + pub fn lead_head_weights(&self, params: &Parameters) -> Vec<(Mask, f32)> { + let mut mask_weights = Vec::new(); + for lead_head in self.lead_head().closure() { + for (mask, weight) in ¶ms.course_weights { + mask_weights.push((mask * &lead_head, *weight)); + } + } + mask_weights + } + ///////////////////////// // START/END LOCATIONS // ///////////////////////// diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 953fa520..0c34cbbf 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -311,8 +311,6 @@ impl CompPrefix { path, part_head: params.part_head_group.get_row(self.part_head).to_owned(), length: self.length, - - total_score: score, }; // Validate the composition by building a `CompositionGetter`. The checks performed by // `CompositionGetter::new` are much stricter and more correct than those we can perform @@ -327,6 +325,23 @@ impl CompPrefix { ); } } + // Validate that the composition thinks it has the same score that we gave it + let comp_score = comp_getter.total_score(); + let abs_diff = (comp_score - score).abs(); + let rel_diff = abs_diff / f32::max(comp_score, score); + let is_bad = if comp_score > 1.0 { + rel_diff > 1e-5 // Use relative difference for large numbers + } else { + abs_diff > 1e-3 // Use absolute difference for small numbers + }; + if is_bad { + panic!( + "Scores for {} differed by too much (search gives {} but comp gives {})", + comp_getter.call_string(), + score, + comp_score + ); + } // Finally, return the comp Some(comp) } diff --git a/monument/test/results.toml b/monument/test/results.toml index e68f731d..1f4a40ce 100644 --- a/monument/test/results.toml +++ b/monument/test/results.toml @@ -254,7 +254,7 @@ Note: For course mask 1xxx7856, adding extra masks for other parts: 1344 : 0 224 224 0 448 448 | 66% | 17823456 | 151.90 : 93 ( 45f 48b) 5f 4b 5f 6b 3f 4b 3 | 0.129291 | #WE[-]ES[s]YW[s] 1344 : 0 0 448 224 224 448 | 66% | 17823456 | 152.00 : 92 ( 37f 55b) 4f 5b 4f 7b 4f 3b 0 | 0.129365 | #WE[-]BS[s]SE[s] 1344 : 0 448 0 224 224 448 | 66% | 17823456 | 152.90 : 95 ( 37f 58b) 4f 5b 4f 7b 3f 3b 3 | 0.130035 | #WE[-]BY[s]YE[s] -1344 : 0 224 224 224 448 224 | 83% | 17823456 | 152.90 : 92 ( 38f 54b) 4f 4b 4f 7b 3f 4b 3 | 0.142436 | #WE[-]BS[s]YW[s] +1344 : 0 224 224 224 448 224 | 83% | 17823456 | 152.90 : 92 ( 38f 54b) 4f 4b 4f 7b 3f 4b 3 | 0.142435 | #WE[-]BS[s]YW[s] 1344 : 224 224 0 224 448 224 | 83% | 17823456 | 153.80 : 96 ( 44f 52b) 5f 4b 5f 7b 0f 4b 6 | 0.143105 | #WE[s]BY[-]CW[s] 1344 : 0 224 224 224 224 448 | 83% | 17823456 | 153.90 : 95 ( 37f 58b) 4f 5b 4f 7b 4f 3b 3 | 0.143180 | #WE[-]BS[s]YE[s] 1344 : 448 0 0 224 448 224 | 66% | 17823456 | 154.80 : 97 ( 44f 53b) 5f 4b 5f 7b 0f 4b 6 | 0.131448 | #WE[s]BC[-]CW[s] @@ -383,7 +383,7 @@ len P S | atw | music 4-bell runs 56s 65s | avg score | calling 120 : 60 60 | 52% | 18.40 : 9 ( 5f 4b) 12 0 | -0.000000 | PS[sH]S[sT]S[sW]S[sH]SP[sH]P[sW]P[sF]P[sH] 120 : 60 60 | 60% | 18.40 : 7 ( 5f 2b) 12 0 | -0.000000 | SP[sH]P[sW]P[sF]P[sH]SP[sH]S[sT]S[sW]S[sH] 120 : 60 60 | 76% | 18.40 : 5 ( 3f 2b) 7 0 | -0.000000 | SP[sH]P[sW]S[sH]P[sW]PS[sW]S[sH]P[sW]S[sH] -120 : 60 60 | 64% | 18.40 : 10 ( 5f 5b) 7 0 | 0.000000 | SP[sH]S[sT]S[sW]P[sF]SP[sT]S[sW]P[sF]P[sH] +120 : 60 60 | 64% | 18.40 : 10 ( 5f 5b) 7 0 | -0.000000 | SP[sH]S[sT]S[sW]P[sF]SP[sT]S[sW]P[sF]P[sH] 120 : 60 60 | 92% | 18.40 : 11 ( 6f 5b) 7 4 | 0.076667 | P[sW]P[sF]S[sB]SSSS[sT]PPP 120 : 60 60 | 84% | 18.60 : 8 ( 3f 5b) 3 0 | 0.040000 | P[sW]S[sH]SSS[sF]S[sB]P[sT]PPP[sH] ------------|-----|-------------------------------------|-----------|----------- @@ -494,7 +494,7 @@ len | atw | music 4-bell runs 5678s 8765s 6578s 87s | avg 384 | atw | 60.00 : 28 ( 14f 14b) 0f 12b 4f 0b 0f 0b 0 | 0.390625 | sHMMsMH 352 | atw | 60.00 : 28 ( 16f 12b) 0f 12b 4f 0b 0f 0b 0 | 0.431250 | HWsWsH 352 | atw | 60.00 : 28 ( 14f 14b) 0f 12b 4f 0b 0f 0b 0 | 0.431250 | sHsMMH -128 | 24% | 61.00 : 28 ( 12f 16b) 0f 16b 0f 0b 0f 0b 0 | 0.603827 | sHHsHH +128 | 24% | 61.00 : 28 ( 12f 16b) 0f 16b 0f 0b 0f 0b 0 | 0.603826 | sHHsHH 384 | atw | 66.00 : 30 ( 16f 14b) 0f 12b 4f 0b 0f 0b 0 | 0.406250 | sHHsWWW 384 | atw | 67.00 : 30 ( 12f 18b) 0f 16b 4f 0b 0f 0b 0 | 0.408854 | HsMMsHH 352 | atw | 68.00 : 32 ( 18f 14b) 0f 12b 4f 0b 0f 0b 0 | 0.453977 | sHHWsW @@ -525,19 +525,19 @@ len O P | atw | music | avg score | calling 120 : 40 80 | 80% | 0.00 | 0.336667 | O[H]P[F]O[I]OO[B]P[B]OO[H]P[F]O[I]O[L]OO[F]P[H] 120 : 40 80 | 80% | 0.00 | 0.336667 | P[F]O[I]P[I]OO[B]O[F]O[I]P[I]OO[B]P[B]OO[H]O[H] 120 : 40 80 | 80% | 0.00 | 0.336667 | P[F]O[I]P[I]O[L]OO[F]P[H]OO[I]P[I]O[L]O[B]OO[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | OOO[L]O[B]O[F]P[H]OO[I]P[I]O[L]P[L]O[B]O[F]P[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | OO[I]P[I]OO[B]P[B]O[F]O[I]P[I]OO[B]O[F]P[H]O[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | OO[I]P[I]O[L]O[B]OOO[I]P[I]O[L]P[L]O[B]O[F]P[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | OO[I]P[I]O[L]O[B]P[B]O[F]O[I]P[I]OOO[F]P[H]O[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]O[H]P[F]OOO[B]P[B]O[F]O[I]P[I]O[L]O[B]P[B]O -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]O[H]P[F]OO[L]P[L]OO[F]O[I]P[I]O[L]OO[F]P[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]O[H]P[F]O[I]OO[B]P[B]O[F]O[I]P[I]OO[B]P[B]O -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]O[H]P[F]O[I]O[L]P[L]OO[F]O[I]P[I]OOO[F]P[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]P[F]OOO[B]P[B]O[F]OO[L]P[L]O[B]O[F]P[H]O[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]P[F]OO[L]O[B]P[B]O[F]OO[L]P[L]OO[F]P[H]O[H] -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]P[F]O[I]O[L]P[L]O[B]P[B]OOO[I]O[L]O[B]P[B]O -120 : 40 80 | 83% | 0.00 | 0.364444 | O[H]P[F]O[I]O[L]P[L]O[B]P[B]OO[H]P[F]O[I]O[L]OO -120 : 40 80 | 83% | 0.00 | 0.364444 | P[F]OOO[B]O[F]O[I]P[I]OO[B]P[B]O[F]P[H]O[H]O[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | OOO[L]O[B]O[F]P[H]OO[I]P[I]O[L]P[L]O[B]O[F]P[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | OO[I]P[I]OO[B]P[B]O[F]O[I]P[I]OO[B]O[F]P[H]O[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | OO[I]P[I]O[L]O[B]OOO[I]P[I]O[L]P[L]O[B]O[F]P[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | OO[I]P[I]O[L]O[B]P[B]O[F]O[I]P[I]OOO[F]P[H]O[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]O[H]P[F]OOO[B]P[B]O[F]O[I]P[I]O[L]O[B]P[B]O +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]O[H]P[F]OO[L]P[L]OO[F]O[I]P[I]O[L]OO[F]P[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]O[H]P[F]O[I]OO[B]P[B]O[F]O[I]P[I]OO[B]P[B]O +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]O[H]P[F]O[I]O[L]P[L]OO[F]O[I]P[I]OOO[F]P[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]P[F]OOO[B]P[B]O[F]OO[L]P[L]O[B]O[F]P[H]O[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]P[F]OO[L]O[B]P[B]O[F]OO[L]P[L]OO[F]P[H]O[H] +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]P[F]O[I]O[L]P[L]O[B]P[B]OOO[I]O[L]O[B]P[B]O +120 : 40 80 | 83% | 0.00 | 0.364445 | O[H]P[F]O[I]O[L]P[L]O[B]P[B]OO[H]P[F]O[I]O[L]OO +120 : 40 80 | 83% | 0.00 | 0.364445 | P[F]OOO[B]O[F]O[I]P[I]OO[B]P[B]O[F]P[H]O[H]O[H] 120 : 40 80 | 83% | 0.00 | 0.364445 | P[F]OO[L]P[L]O[B]O[F]OOO[B]P[B]O[F]P[H]O[H]O[H] 120 : 40 80 | 83% | 0.00 | 0.364445 | P[F]O[I]O[L]P[L]OO[F]P[H]OO[I]O[L]P[L]O[B]OO[H] 120 : 40 80 | 83% | 0.00 | 0.364445 | P[F]O[I]O[L]P[L]O[B]O[F]P[H]OO[I]O[L]P[L]OOO[H] @@ -1160,7 +1160,7 @@ len | music 4-bell runs 5678s 8765s 6578s 87s | avg score 1584 | 312.00 : 129 ( 55f 74b) 8f 11b 6 0 0 | 0.188258 | sMsWsHsWsHsM 1584 | 315.00 : 103 ( 45f 58b) 9f 11b 12 0 0 | 0.190278 | HMsWHsMWH 1584 | 320.00 : 110 ( 48f 62b) 8f 11b 12 0 0 | 0.196212 | sMsMsHsH -1584 | 333.00 : 141 ( 61f 80b) 9f 12b 6 0 0 | 0.202146 | sMsHsMsWHW +1584 | 333.00 : 141 ( 61f 80b) 9f 12b 6 0 0 | 0.202147 | sMsHsMsWHW 1584 | 335.00 : 129 ( 55f 74b) 8f 11b 6 0 0 | 0.205682 | sWsHsHsW 1584 | 335.00 : 114 ( 50f 64b) 8f 11b 12 0 0 | 0.205682 | sWsWsHsH 1584 | 339.00 : 104 ( 46f 58b) 8f 11b 18 0 0 | 0.210606 | HHH @@ -1359,7 +1359,7 @@ len | music 4-bell runs 46s | avg score | calling 112 | 27.20 : 10 ( 5f 5b) 6 | 0.128571 | sFsMsHFMsH 112 | 27.20 : 12 ( 6f 6b) 6 | 0.128571 | sWsBIIsMsH 112 | 27.20 : 10 ( 5f 5b) 6 | 0.137500 | FMsHFMsH -140 | 27.60 : 10 ( 5f 5b) 8 | 0.138572 | sFsBMH +140 | 27.60 : 10 ( 5f 5b) 8 | 0.138571 | sFsBMH 140 | 28.00 : 10 ( 5f 5b) 10 | 0.134286 | sWsFsHsM 140 | 28.00 : 10 ( 5f 5b) 10 | 0.141429 | WFsHsM 140 | 28.60 : 8 ( 4f 4b) 3 | 0.138571 | sFsMsWsH @@ -2529,7 +2529,7 @@ len | music NMs CRUs 5678 combs | avg score | calling 272 | 46.00 : 1 1f 12b 0f 32b | 0.088971 | sMMMBsWWWsWsHHH 256 | 46.00 : 2 0f 12b 0f 32b | 0.101563 | HHMsMBsWWWsWsH 256 | 46.00 : 1 1f 12b 0f 32b | 0.101563 | HsHsMMMsMBsWWH -256 | 46.00 : 1 1f 12b 0f 32b | 0.101562 | MsMBsWWWsWsHHH +256 | 46.00 : 1 1f 12b 0f 32b | 0.101563 | MsMBsWWWsWsHHH 256 | 46.00 : 2 0f 12b 0f 32b | 0.101563 | sHsMMMsMBsWWHH 240 | 46.00 : 2 0f 12b 0f 32b | 0.120000 | HHMMsMMBWsH 240 | 46.00 : 1 1f 12b 0f 32b | 0.120000 | HsHMBWsWWWH @@ -2765,8 +2765,8 @@ len | music NMs CRUs 5678 combs | avg score | calling 288 | 69.00 : 2 0f 19b 8f 40b | 0.163889 | HsHMsMMMsWWWsHH 272 | 69.00 : 2 0f 19b 8f 40b | 0.180147 | HsHMMsMsWWWsHH 272 | 69.00 : 3 0f 18b 8f 40b | 0.180147 | sHHHsHMsMMMWsW -256 | 69.00 : 2 0f 19b 8f 40b | 0.198438 | HsHsMMsWWWsHH -256 | 69.00 : 3 0f 18b 8f 40b | 0.198438 | sHHHsHMMsMWsW +256 | 69.00 : 2 0f 19b 8f 40b | 0.198437 | HsHsMMsWWWsHH +256 | 69.00 : 3 0f 18b 8f 40b | 0.198437 | sHHHsHMMsMWsW 240 | 69.00 : 3 0f 18b 8f 40b | 0.219167 | sHHHsHsMMWsW 224 | 69.00 : 2 0f 19b 8f 40b | 0.247321 | sWWWHsHHH 208 | 69.00 : 3 0f 18b 8f 40b | 0.275000 | HsHHHWsW From 1d73e4e59e65f33f38d04d2aa2b27c92e420827b Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 9 Dec 2023 22:47:25 +0000 Subject: [PATCH 11/29] Move all comp checks into `CompositionGetter::new` --- monument/lib/src/composition.rs | 26 ++++++++-- monument/lib/src/search/prefix.rs | 86 ++++++------------------------- 2 files changed, 38 insertions(+), 74 deletions(-) diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index a3452868..c75ea8c1 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -294,6 +294,7 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { } // End indices let last_elem = self.composition.path.last().unwrap(); + let end_sub_lead_idx = last_elem.end_sub_lead_idx(self.params); match last_elem.call_to_end { None => { // The last element ends with a plain lead, so we need to check that this chunk's @@ -301,7 +302,7 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { let end_indices = self .get_method(last_elem.method_id) .wrapped_indices(Boundary::End, self.params); - if !end_indices.contains(&last_elem.end_sub_lead_idx(self.params)) { + if !end_indices.contains(&end_sub_lead_idx) { return false; // End index isn't valid for this method } } @@ -315,6 +316,25 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { } } } + // Check for continuity over the part head (this checks for cases like finishing each part + // at a snap and then starting the next part at the lead-end) + if self.params.is_multipart() { + let start_labels = self + .get_method(first_elem.method_id) + .get_labels(first_elem.start_sub_lead_idx); + let end_labels = self + .get_method(last_elem.method_id) + .get_labels(end_sub_lead_idx); + let is_splice_possible = start_labels.iter().any(|label| end_labels.contains(label)); + let is_continuous_lead = first_elem.start_sub_lead_idx == end_sub_lead_idx + && first_elem.method_id == last_elem.method_id; + if !is_splice_possible && !is_continuous_lead && last_elem.ends_with_plain() { + return false; // No way to splice over the part heads + } + if first_elem.start_sub_lead_idx != end_sub_lead_idx { + return false; // Composition isn't continuous over the part heads + } + } true // Composition is all OK } @@ -353,14 +373,14 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { SpliceStyle::Calls => { let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { // PERF: use the method map to speed up the splicing check - e1.is_splice_when_followed_by(e2, &self.params) && e1.ends_with_plain() + e1.is_splice_when_followed_by(e2, self.params) && e1.ends_with_plain() }; for (elem1, elem2) in self.composition.path.iter().tuple_windows() { if is_invalid_splice(elem1, elem2) { return false; // Splice but no call } } - if self.params.is_multipart() && self.composition.path.len() > 2 { + if self.params.is_multipart() && !self.composition.path.is_empty() { let first = self.composition.path.first().unwrap(); let last = self.composition.path.last().unwrap(); if is_invalid_splice(last, first) { diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 0c34cbbf..7f643c0d 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -8,7 +8,6 @@ use crate::{ composition::{Composition, CompositionGetter, PathElem}, graph::LinkSide, group::PartHead, - parameters::SpliceStyle, utils::{counts::Counts, div_rounding_up, lengths::TotalLength}, }; @@ -240,7 +239,6 @@ impl CompPrefix { /// Assuming that the [`CompPrefix`] has just finished the composition, check if the resulting /// composition satisfies the user's requirements. fn check_comp(&self, search: &Search, paths: &Paths) -> Option { - let params = &search.params; assert!(self.next_link_side.is_start_or_end()); if !search.refined_ranges.length.contains(&self.length) { @@ -251,95 +249,41 @@ impl CompPrefix { // (conservatively) if the range is feasible within the _maximum possible_ length // range. However, the composition is likely to be _shorter_ than this range and // removing those extra rows could make the method count infeasible. + // + // TODO: Move this into `CompGetter::new` once we calculate refined ranges before + // the graph build if !self .method_counts .is_feasible(0, search.refined_ranges.method_counts.as_raw_slice()) { return None; // Comp doesn't have the required method balance } - if !params.part_head_group.is_generator(self.part_head) { - return None; // The part head reached wouldn't generate all the parts - } - if params.require_atw && search.atw_table.atw_factor(&self.atw_bitmap) < 0.99999 { - return None; // The composition is not atw, but we were required to make it atw - } /* At this point, all checks on the composition have passed and we know it satisfies the * user's parameters */ - let path = self.flattened_path(search, paths); - let first_elem = path.first().expect("Must have at least one chunk"); - let last_elem = path.last().expect("Must have at least one chunk"); - - // Handle splices over the part head - let mut score = self.score; - let is_splice = first_elem.method_id != last_elem.method_id - || first_elem.start_sub_lead_idx != last_elem.end_sub_lead_idx(params); - let splice_over_part_head = params.is_multipart() && is_splice; - if splice_over_part_head { - // Check if this splice is actually allowed under the composition (i.e. there must be a - // common label between the start and end of the composition for a splice to be - // allowed) - let start_labels = search - .params - .get_method_by_id(first_elem.method_id) - .first_lead() - .get_annot(first_elem.start_sub_lead_idx) - .unwrap(); - let end_labels = search - .params - .get_method_by_id(last_elem.method_id) - .first_lead() - .get_annot(last_elem.end_sub_lead_idx(&search.params)) - .unwrap(); - let is_valid_splice = start_labels.iter().any(|label| end_labels.contains(label)); - if !is_valid_splice { - return None; - } - // Don't generate comp if it would violate the splice style over the part head - if params.splice_style == SpliceStyle::Calls && last_elem.ends_with_plain() { - return None; - } - // Add/subtract weights from the splices over the part head - score += params.splice_weight * (params.num_parts() - 1) as f32; - } - // Now we know the composition is valid, construct it and return + let path = self.flattened_path(search, paths); let comp = Composition { - stage: params.stage, - start_stroke: params.start_stroke, + stage: search.params.stage, + start_stroke: search.params.start_stroke, path, - part_head: params.part_head_group.get_row(self.part_head).to_owned(), + part_head: search + .params + .part_head_group + .get_row(self.part_head) + .to_owned(), length: self.length, }; // Validate the composition by building a `CompositionGetter`. The checks performed by // `CompositionGetter::new` are much stricter and more correct than those we can perform // here, so we defer entirely to it to check these candidate compositions for validity. - let comp_getter = CompositionGetter::new(&comp, params)?; + let comp_getter = CompositionGetter::new(&comp, &search.params)?; // Sanity check that the composition is true - if params.require_truth { - if !comp_getter.is_true() { - panic!( - "Generated false composition ({})", - comp_getter.call_string() - ); - } - } - // Validate that the composition thinks it has the same score that we gave it - let comp_score = comp_getter.total_score(); - let abs_diff = (comp_score - score).abs(); - let rel_diff = abs_diff / f32::max(comp_score, score); - let is_bad = if comp_score > 1.0 { - rel_diff > 1e-5 // Use relative difference for large numbers - } else { - abs_diff > 1e-3 // Use absolute difference for small numbers - }; - if is_bad { + if search.params.require_truth && !comp_getter.is_true() { panic!( - "Scores for {} differed by too much (search gives {} but comp gives {})", - comp_getter.call_string(), - score, - comp_score + "Generated false composition ({})", + comp_getter.call_string() ); } // Finally, return the comp From 909183eb68d25672edfc200f724d2c79c30dd338 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 14:07:13 +0000 Subject: [PATCH 12/29] Move music display logic into `lib/` --- bellframe/src/music.rs | 18 ++++ monument/cli/src/lib.rs | 3 +- monument/cli/src/logging.rs | 35 +++----- monument/cli/src/music/mod.rs | 151 +++++---------------------------- monument/cli/src/toml_file.rs | 15 ++-- monument/lib/src/parameters.rs | 92 ++++++++++++++++++-- 6 files changed, 140 insertions(+), 174 deletions(-) diff --git a/bellframe/src/music.rs b/bellframe/src/music.rs index 66d054ab..83480ca9 100644 --- a/bellframe/src/music.rs +++ b/bellframe/src/music.rs @@ -478,6 +478,14 @@ impl AtRowPositions { } } + pub fn as_array(self) -> [T; 4] { + [self.front, self.internal, self.back, self.wrap] + } + + pub fn as_ref_array(&self) -> [&T; 4] { + [&self.front, &self.internal, &self.back, &self.wrap] + } + pub fn total(&self) -> V where V: std::iter::Sum, @@ -496,6 +504,16 @@ impl AtRowPositions { } } +impl AtRowPositions { + pub fn any(self) -> bool { + self.as_array().into_iter().any(|x| x) + } + + pub fn all(self) -> bool { + self.as_array().into_iter().all(|x| x) + } +} + impl Add for AtRowPositions { type Output = AtRowPositions; diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index 9ad8ba07..e839fb01 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -66,7 +66,7 @@ pub fn run( // seriously beneficial - it shaves many seconds off Monument's total running time. let leak_search_memory = env == Environment::Cli; // Convert the `TomlFile` into a `Layout` and other data required for running a search - let (params, music_displays) = toml_file.to_params(toml_path)?; + let params = toml_file.to_params(toml_path)?; debug_print!(Params, params); // Build the search let search = Arc::new(Search::new( @@ -77,7 +77,6 @@ pub fn run( // Build all the data structures for the search let comp_printer = CompositionPrinter::new( - music_displays, search.clone(), toml_file.should_print_atw(), !options.dont_display_comp_numbers, diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index e7d1267a..c2957387 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -9,8 +9,6 @@ use log::log_enabled; use monument::{Composition, CompositionGetter, Progress, Search, Update}; use ringing_utils::BigNumInt; -use crate::music::MusicDisplay; - /// Struct which handles logging updates, keeping the updates to a single line which updates as the /// search progresses. pub struct SingleLineProgressLogger { @@ -127,7 +125,6 @@ impl SingleLineProgressLogger { #[derive(Debug, Clone)] pub struct CompositionPrinter { search: Arc, - music_displays: Vec, /// Counter which records how many compositions have been printed so far comps_printed: usize, @@ -149,17 +146,10 @@ pub struct CompositionPrinter { print_atw: bool, /// If a part head should be displayed, then what's its width part_head_width: Option, - /// The column widths of every `MusicDisplay` in the output - music_widths: Vec, } impl CompositionPrinter { - pub fn new( - music_displays: Vec, - search: Arc, - print_atw: bool, - print_comp_widths: bool, - ) -> Self { + pub fn new(search: Arc, print_atw: bool, print_comp_widths: bool) -> Self { Self { comp_count_width: print_comp_widths .then_some(search.parameters().num_comps.to_string().len()), @@ -178,13 +168,8 @@ impl CompositionPrinter { print_atw, part_head_width: (search.num_parts() > 2) .then(|| search.effective_part_head_stage().num_bells()), - music_widths: music_displays - .iter() - .map(|d| d.col_width(search.parameters())) - .collect_vec(), search, - music_displays, } } @@ -270,13 +255,15 @@ impl CompositionPrinter { s.push('|'); } // Music + let params = self.search.parameters(); + let music_types_to_display = params.music_types_to_show(); s.push_str(" music "); - if !self.music_displays.is_empty() { + if !music_types_to_display.is_empty() { s.push(' '); } - for (music_display, col_width) in self.music_displays.iter().zip_eq(&self.music_widths) { + for (_idx, music_type) in music_types_to_display { s.push_str(" "); - write_centered_text(&mut s, &music_display.name, *col_width); + write_centered_text(&mut s, &music_type.name, music_type.col_width(params.stage)); s.push(' '); } // Everything else @@ -317,17 +304,19 @@ impl CompositionPrinter { write!(s, " {} |", ShortRow(comp.part_head())).unwrap(); } // Music + let params = self.search.parameters(); + let music_types_to_show = params.music_types_to_show(); let (music_counts, music_score) = comp.music_counts_and_score(); // Only call `music_counts` once! write!(s, " {:>7.2} ", music_score).unwrap(); - if !self.music_displays.is_empty() { + if !music_types_to_show.is_empty() { s.push(':'); } - for (music_display, col_width) in self.music_displays.iter().zip_eq(&self.music_widths) { + for (idx, music_type) in music_types_to_show { s.push_str(" "); write_left_centered_text( &mut s, - &music_display.display_counts(&music_counts, self.search.parameters()), - *col_width, + &music_type.display_counts(music_counts[idx], params.stage), + music_type.col_width(params.stage), ); s.push(' '); } diff --git a/monument/cli/src/music/mod.rs b/monument/cli/src/music/mod.rs index 4b06847d..9bf481ce 100644 --- a/monument/cli/src/music/mod.rs +++ b/monument/cli/src/music/mod.rs @@ -1,11 +1,8 @@ -use std::fmt::Write; - use bellframe::{ music::{AtRowPositions, Pattern, RowPosition}, Stage, }; -use index_vec::IndexVec; -use monument::parameters::{IdGenerator, MusicType, MusicTypeId, MusicTypeVec, Parameters}; +use monument::parameters::{IdGenerator, MusicType, MusicTypeId, MusicTypeVec}; use serde::Deserialize; use crate::utils::OptRangeInclusive; @@ -147,7 +144,7 @@ pub fn generate_music( base_music: BaseMusic, music_file_str: Option<&str>, stage: Stage, -) -> anyhow::Result<(MusicTypeVec, Vec)> { +) -> anyhow::Result> { let mut music_builder = MusicTypeFactory::new(); // Base music @@ -170,14 +167,14 @@ pub fn generate_music( /// Struct to build a single set of `MusicType`s #[derive(Debug)] struct MusicTypeFactory { - music_types_with_displays: MusicTypeVec<(MusicType, Option)>, + music_types: MusicTypeVec, id_gen: IdGenerator, } impl MusicTypeFactory { fn new() -> Self { Self { - music_types_with_displays: MusicTypeVec::new(), + music_types: MusicTypeVec::new(), id_gen: IdGenerator::starting_at_zero(), } } @@ -193,20 +190,14 @@ impl MusicTypeFactory { stage: Stage, ) -> anyhow::Result<()> { for s in toml_musics { - self.music_types_with_displays + self.music_types .extend(s.to_music_types(&mut self.id_gen, stage)?); } Ok(()) } - fn finish(self) -> (MusicTypeVec, Vec) { - let mut music_types = IndexVec::new(); - let mut music_displays = Vec::new(); - for (ty, disp) in self.music_types_with_displays { - music_types.push(ty); - music_displays.extend(disp); - } - (music_types, music_displays) + fn finish(self) -> MusicTypeVec { + self.music_types } } @@ -316,7 +307,7 @@ impl TomlMusic { &self, id_gen: &mut IdGenerator, stage: Stage, - ) -> anyhow::Result)>> { + ) -> anyhow::Result> { // This function just delegates the work to one of `music_type_runs`, // `music_type_patterns` or `music_type_preset`. @@ -344,7 +335,7 @@ fn music_type_runs( common: &MusicCommon, id_gen: &mut IdGenerator, stage: Stage, -) -> Vec<(MusicType, Option)> { +) -> Vec { let mut music_types = Vec::new(); for &len in lengths { music_types.push(new_music_type( @@ -363,7 +354,7 @@ fn music_type_patterns( common: &MusicCommon, id_gen: &mut IdGenerator, stage: Stage, -) -> anyhow::Result)>> { +) -> anyhow::Result> { let count_range = monument::parameters::OptionalRangeInclusive::from(common.count_range); // Parse patterns let mut patterns = Vec::new(); @@ -387,7 +378,7 @@ fn music_type_patterns( if music_types.is_empty() || count_range.is_set() { // If the user gave a custom name or is not showing this type at all, we combine all the // patterns into one [`MusicType`] - let (mut music_type, mut music_display) = new_music_type( + let mut music_type = new_music_type( id_gen.next(), String::new(), bellframe::MusicType::new(patterns), @@ -395,10 +386,10 @@ fn music_type_patterns( false, ); if !music_types.is_empty() { - music_type.optional_weights = AtRowPositions::splat(None); - music_display = None; + music_type.weights = AtRowPositions::splat(0.0); + music_type.show_positions = AtRowPositions::FALSE; } - music_types.push((music_type, music_display)); + music_types.push(music_type); } // Check that a non-empty pattern string always produces a non-empty set of music types if !pattern_strings.is_empty() { @@ -412,7 +403,7 @@ fn music_type_preset( common: &MusicCommon, id_gen: &mut IdGenerator, stage: Stage, -) -> anyhow::Result)>> { +) -> anyhow::Result> { // Determine the pattern types let (music_type, positions, default_name) = match preset { MusicPreset::Combinations5678s => match stage { @@ -477,121 +468,19 @@ fn new_music_type( music_type: bellframe::MusicType, common: &MusicCommon, show_total: bool, -) -> (MusicType, Option) { +) -> MusicType { let optional_weights = AtRowPositions::>::from(common.specified_weight); // Create music types, etc. - let music_type = MusicType { + MusicType { id, inner: music_type.at_stroke(common.strokes.into()), - optional_weights, + weights: optional_weights.map(|x| x.unwrap_or(0.0)), + show_positions: optional_weights.map(|x| x.is_some() && common.should_show()), + show_total: show_total && common.should_show(), count_range: common.count_range.into(), - }; - let music_display = common.should_show().then(|| MusicDisplay { - source: music_type.id, name: match &common.name { Some(name) => name.clone(), None => default_name.to_owned(), }, - show_total, - show: optional_weights.map(|x| x.is_some()), - }); - (music_type, music_display) -} - -////////////////////////////////////// -// DETERMINING HOW TO DISPLAY MUSIC // -////////////////////////////////////// - -/// How music counts can be displayed. -/// -/// This could take its counts from multiple [`MusicType`]s, and takes a few forms: -/// 1. Just a total count: e.g. `*5678: 23` -/// 2. Just a breakdown: e.g. `5678s: 24f,81i,24b` -/// 3. A breakdown and a total count: e.g. `4-runs: 312 (73f,132i,107b)` -/// -/// Setting all sources to `None` is allowed, but will create an empty column. -#[derive(Debug, Clone)] -pub struct MusicDisplay { - pub source: MusicTypeId, - - /// The name used to identify this type of music - pub name: String, - pub show_total: bool, - pub show: AtRowPositions, -} - -impl MusicDisplay { - /// Return the width of the smallest column large enough to be guaranteed to hold (almost) - /// every instance of this [`MusicDisplay`] (assuming rows can't be repeated). - pub fn col_width(&self, params: &Parameters) -> usize { - let all_zeros = index_vec::index_vec![AtRowPositions::ZERO; params.music_types.len()]; - // We always pad the counts as much as required, so displaying a set of 0s results in a - // maximum-width string (i.e. all output strings are the same length) - let max_count_width = self.display_counts(&all_zeros, params).len(); - max_count_width.max(self.name.len()) - } - - /// Generate a compact string representing a given set of music counts - pub fn display_counts( - &self, - all_counts: &MusicTypeVec>, - params: &Parameters, - ) -> String { - let music_type_idx = params.music_type_id_to_idx(self.source); - let music_type = ¶ms.music_types[music_type_idx]; - let counts = all_counts[music_type_idx]; - let max_counts = music_type.max_possible_count(params.stage); - let num_items_to_show: usize = self.show.map(|b| b as usize).total(); - - let mut s = String::new(); - // Add total count - if self.show_total || num_items_to_show == 1 { - write_music_count( - &mut s, - counts.masked(!self.show, 0).total(), - max_counts.masked(!self.show, 0).total(), - ); - } - // Add specific counts (if there are any) - if num_items_to_show > 1 { - // Add brackets if there's a total score - if self.show_total { - s.push_str(" ("); - } - // Add every front/internal/back count for which we have a source - let mut is_first_count = true; - for (position, position_char) in [ - (RowPosition::Front, 'f'), - (RowPosition::Internal, 'i'), - (RowPosition::Back, 'b'), - (RowPosition::Wrap, 'w'), - ] { - if !self.show.get(position) { - continue; // Skip positions we're not showing - } - // Add separating comma - if !is_first_count { - s.push(' '); - } - is_first_count = false; - // Add the number - write_music_count(&mut s, *counts.get(position), *max_counts.get(position)); - s.push(position_char); - } - if self.show_total { - s.push(')'); // Add closing brackets if there's a total score - } - } - - s } } - -/// Prints the width of the largest count possible for a [`MusicType`] (assuming that rows can't be -/// repeated). -fn write_music_count(s: &mut String, count: usize, max_possible_count: usize) { - // `min(4)` because we don't expect more than 9999 instances of a music type, even - // if more are theoretically possible - let max_count_width = max_possible_count.to_string().len().min(4); - write!(s, "{:>width$}", count, width = max_count_width).unwrap(); -} diff --git a/monument/cli/src/toml_file.rs b/monument/cli/src/toml_file.rs index 9d006d1e..20ff62ac 100644 --- a/monument/cli/src/toml_file.rs +++ b/monument/cli/src/toml_file.rs @@ -24,7 +24,7 @@ use serde::Deserialize; use crate::{ calls::{BaseCalls, CustomCall}, - music::{BaseMusic, MusicDisplay, TomlMusic}, + music::{BaseMusic, TomlMusic}, utils::OptRangeInclusive, }; @@ -170,7 +170,7 @@ impl TomlFile { } /// Build a set of [`Parameters`] from this `TomlFile` - pub fn to_params(&self, toml_path: &Path) -> anyhow::Result<(Parameters, Vec)> { + pub fn to_params(&self, toml_path: &Path) -> anyhow::Result { log::debug!("Generating params"); // Error on deprecated paramaters @@ -199,7 +199,6 @@ impl TomlFile { anyhow!("No methods specified. Try something like `method = \"Bristol Surprise Major\"`.") })?; - let (music_types, music_displays) = self.music(toml_path, stage)?; let part_head = parse_row("part head", &self.part_head, stage)?; let calling_bell = match self.calling_bell { @@ -232,10 +231,10 @@ impl TomlFile { end_row: parse_row("end row", &self.end_row, stage)?, part_head_group: PartHeadGroup::new(&part_head), course_weights: self.course_weights(stage)?, - music_types, + music_types: self.music(toml_path, stage)?, start_stroke: self.start_stroke, }; - Ok((params, music_displays)) + Ok(params) } pub fn should_print_atw(&self) -> bool { @@ -306,11 +305,7 @@ impl TomlFile { )) } - fn music( - &self, - toml_path: &Path, - stage: Stage, - ) -> anyhow::Result<(MusicTypeVec, Vec)> { + fn music(&self, toml_path: &Path, stage: Stage) -> anyhow::Result> { // Load TOML for the music file let music_file_str = match &self.music_file { Some(relative_music_path) => { diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index c6d53d9f..ae6c75de 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -2,13 +2,16 @@ use std::{ collections::HashSet, + fmt::Write, marker::PhantomData, ops::{Deref, Range, RangeInclusive}, sync::atomic::AtomicBool, }; use bellframe::{ - method::LABEL_LEAD_END, music::AtRowPositions, Bell, Mask, PlaceNot, Row, RowBuf, Stage, Stroke, + method::LABEL_LEAD_END, + music::{AtRowPositions, RowPosition}, + Bell, Mask, PlaceNot, Row, RowBuf, Stage, Stroke, }; use itertools::Itertools; @@ -332,6 +335,13 @@ impl Parameters { pub fn get_music_type(&self, id: MusicTypeId) -> &MusicType { self.music_types.iter().find(|mt| mt.id == id).unwrap() } + + pub fn music_types_to_show(&self) -> Vec<(MusicTypeIdx, &MusicType)> { + self.music_types + .iter_enumerated() + .filter(|(_idx, mt)| mt.should_show()) + .collect_vec() + } } ///////////// @@ -730,29 +740,95 @@ pub fn default_calling_positions(place_not: &PlaceNot) -> Vec { pub struct MusicType { pub id: MusicTypeId, + pub show_total: bool, + pub show_positions: AtRowPositions, + pub name: String, + pub inner: bellframe::MusicType, - pub optional_weights: AtRowPositions>, + pub weights: AtRowPositions, pub count_range: OptionalRangeInclusive, // TODO: Count ranges for front/internal/back/wrap } impl MusicType { pub fn as_overall_score(&self, counts: AtRowPositions) -> f32 { - (counts.map(|x| x as f32) * self.weights()).total() + (counts.map(|x| x as f32) * self.weights).total() } /// Return the total counts, only from [`RowPosition`](bellframe::music::RowPosition)s for which /// `Self::optional_weights` are not [`None`]. pub fn masked_total(&self, counts: AtRowPositions) -> usize { - counts.masked(!self.mask(), 0).total() + counts.masked(!self.show_positions, 0).total() } - pub fn weights(&self) -> AtRowPositions { - self.optional_weights.map(|v| v.unwrap_or(0.0)) + pub fn should_show(&self) -> bool { + self.show_total || self.show_positions.any() + } + + /// Return the width of the smallest column large enough to be guaranteed to hold (almost) + /// every instance of this [`MusicDisplay`] (assuming rows can't be repeated). + pub fn col_width(&self, stage: Stage) -> usize { + // We always pad the counts as much as required, so displaying a set of 0s results in a + // maximum-width string (i.e. all output strings are the same length) + let max_count_width = self.display_counts(AtRowPositions::ZERO, stage).len(); + max_count_width.max(self.name.len()) + } + + /// Generate a compact string representing a given set of music counts + pub fn display_counts(&self, counts: AtRowPositions, stage: Stage) -> String { + let max_counts = self.max_possible_count(stage); + let num_items_to_show: usize = self.show_positions.map(|b| b as usize).total(); + + let mut s = String::new(); + // Add total count + if self.show_total || num_items_to_show == 1 { + Self::write_music_count( + &mut s, + self.masked_total(counts), + self.masked_total(max_counts), + ); + } + // Add specific counts (if there are any) + if num_items_to_show > 1 { + // Add brackets if there's a total score + if self.show_total { + s.push_str(" ("); + } + // Add every front/internal/back count for which we have a source + let mut is_first_count = true; + for (position, position_char) in [ + (RowPosition::Front, 'f'), + (RowPosition::Internal, 'i'), + (RowPosition::Back, 'b'), + (RowPosition::Wrap, 'w'), + ] { + if !self.show_positions.get(position) { + continue; // Skip positions we're not showing + } + // Add separating comma + if !is_first_count { + s.push(' '); + } + is_first_count = false; + // Add the number + Self::write_music_count(&mut s, *counts.get(position), *max_counts.get(position)); + s.push(position_char); + } + if self.show_total { + s.push(')'); // Add closing brackets if there's a total score + } + } + + s } - pub fn mask(&self) -> AtRowPositions { - self.optional_weights.map(|v| v.is_some()) + /// Prints the width of the largest count possible for a [`MusicType`] (assuming that rows can't be + /// repeated). + fn write_music_count(s: &mut String, count: usize, max_possible_count: usize) { + // `min(4)` because we don't expect more than 9999 instances of a music type, even + // if more are theoretically possible + let max_count_width = max_possible_count.to_string().len().min(4); + write!(s, "{:>width$}", count, width = max_count_width).unwrap(); } } From 27b4a46d9ff87f57f62b679a2ea3c19096aa2df7 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 15:10:46 +0000 Subject: [PATCH 13/29] Display music in GUI's composition list --- bellframe/src/music.rs | 51 +++++++++--------- monument/gui/src/parameters/mod.rs | 87 ++++++++++++++++++++++++++---- monument/gui/src/project.rs | 58 ++++++++++++++------ 3 files changed, 142 insertions(+), 54 deletions(-) diff --git a/bellframe/src/music.rs b/bellframe/src/music.rs index 83480ca9..51d4e69d 100644 --- a/bellframe/src/music.rs +++ b/bellframe/src/music.rs @@ -391,43 +391,40 @@ pub struct AtRowPositions { impl AtRowPositions<()> { pub const ZERO: AtRowPositions = AtRowPositions::splat(0); pub const ZERO_F32: AtRowPositions = AtRowPositions::splat(0.0); +} +impl AtRowPositions { /* Masks */ - pub const FALSE: AtRowPositions = AtRowPositions::splat(false); + pub const FALSE: Self = AtRowPositions::splat(false); + pub const TRUE: Self = AtRowPositions::splat(true); - pub const FRONT: AtRowPositions = AtRowPositions { - front: true, - internal: false, - back: false, - wrap: false, - }; - - pub const BACK: AtRowPositions = AtRowPositions { - front: false, - internal: false, - back: true, - wrap: false, - }; - - pub const FRONT_AND_BACK: AtRowPositions = AtRowPositions { - front: true, - internal: false, - back: true, - wrap: false, - }; + pub const FRONT: Self = Self::new(true, false, false, false); + pub const BACK: Self = Self::new(false, false, true, false); + pub const FRONT_AND_BACK: Self = Self::new(true, false, true, false); } impl AtRowPositions { + pub const fn new(front: T, internal: T, back: T, wrap: T) -> Self { + Self { + front, + internal, + back, + wrap, + } + } + + pub fn front_and_back(value: T) -> Self + where + T: Default + Clone, + { + Self::new(value.clone(), T::default(), value, T::default()) + } + pub const fn splat(value: T) -> Self where T: Copy, { - Self { - front: value, - internal: value, - back: value, - wrap: value, - } + Self::new(value, value, value, value) } pub fn get(&self, position: RowPosition) -> &T { diff --git a/monument/gui/src/parameters/mod.rs b/monument/gui/src/parameters/mod.rs index 5917a5a0..eff48045 100644 --- a/monument/gui/src/parameters/mod.rs +++ b/monument/gui/src/parameters/mod.rs @@ -1,9 +1,12 @@ mod gui; -use bellframe::{Mask, PlaceNot, RowBuf, Stage, Stroke}; +use bellframe::{music::AtRowPositions, Mask, PlaceNot, RowBuf, Stage, Stroke}; use itertools::Itertools; use monument::{ - parameters::{Call, CallDisplayStyle, CallId, Method, OptionalRangeInclusive, SpliceStyle}, + parameters::{ + Call, CallDisplayStyle, CallId, IdGenerator, Method, MusicTypeId, MusicTypeVec, + OptionalRangeInclusive, SpliceStyle, + }, PartHeadGroup, }; @@ -11,7 +14,7 @@ use crate::utils::len_range; #[derive(Debug, Clone)] pub struct Parameters { - pub inner: monument::parameters::Parameters, + inner: monument::parameters::Parameters, maybe_unused_methods: Vec<(bool, Method)>, maybe_unused_calls: Vec<(bool, Call)>, } @@ -46,6 +49,7 @@ impl Parameters { let stage = Stage::MAJOR; let cc_lib = bellframe::MethodLib::cc_lib().unwrap(); + // Methods let make_method = |title: &str, id: u16| { let mut method = cc_lib.get_by_title(title).unwrap(); method.set_lead_end_label(); @@ -60,6 +64,11 @@ impl Parameters { allowed_courses: vec![Mask::parse_with_stage("1*", stage).unwrap().into()], } }; + let maybe_unused_methods = vec![ + (false, make_method("Cambridge Surprise Major", 0)), + (true, make_method("Yorkshire Surprise Major", 1)), + (false, make_method("Superlative Surprise Major", 2)), + ]; // Calls let bob = Call::lead_end_call(CallId(0), PlaceNot::parse("14", stage).unwrap(), "-", -1.8); @@ -71,6 +80,69 @@ impl Parameters { ); let maybe_unused_calls = vec![(true, bob), (true, single)]; + // Music + let mut id_gen = IdGenerator::::starting_at_zero(); + let mut music_types = MusicTypeVec::new(); + let mut add_front_back_music_type = + |inner: bellframe::MusicType, + show_total: bool, + show_positions: bool, + weight: f32, + name: String| { + music_types.push(monument::parameters::MusicType { + id: id_gen.next(), + show_total, + show_positions: AtRowPositions::front_and_back(show_positions), + name, + inner, + weights: AtRowPositions::front_and_back(weight), + count_range: OptionalRangeInclusive::OPEN, + }); + }; + for pattern in ["5678", "8765", "6578"] { + add_front_back_music_type( + bellframe::MusicType::parse(pattern).unwrap(), + false, + true, + 1.0, + format!("{pattern}s"), + ); + } + for run_length in 4u8..=7 { + let show = run_length == 4; + add_front_back_music_type( + bellframe::MusicType::runs(run_length, stage), + show, + show, + 1.0, + format!("{run_length}-bell runs"), + ); + } + add_front_back_music_type( + bellframe::MusicType::combination_5678s_major(), + false, + false, + 0.1, + "5678 combs".to_owned(), + ); + add_front_back_music_type( + bellframe::MusicType::crus(stage), + false, + false, + 0.0, + "CRUs".to_owned(), + ); + music_types.push(monument::parameters::MusicType { + id: id_gen.next(), + show_total: true, + show_positions: AtRowPositions::BACK, + name: "87s".to_owned(), + inner: bellframe::MusicType::reversed_tenors_at_back(stage), + weights: AtRowPositions::new(0.0, 0.0, -1.0, 0.0), + count_range: OptionalRangeInclusive::OPEN, + }); + + // Construct parameters let monument_params = monument::Parameters { length: len_range(1250, 1350), stage, @@ -90,17 +162,12 @@ impl Parameters { part_head_group: PartHeadGroup::one_part(stage), course_weights: vec![], - music_types: index_vec::index_vec![], + music_types, start_stroke: Stroke::Hand, }; - crate::Parameters { inner: monument_params, - maybe_unused_methods: vec![ - (true, make_method("Cambridge Surprise Major", 0)), - (true, make_method("Yorkshire Surprise Major", 1)), - (false, make_method("Superlative Surprise Major", 2)), - ], + maybe_unused_methods, maybe_unused_calls, } } diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index 4bd2022f..d549010d 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,4 +1,4 @@ -use eframe::egui::{self, RichText}; +use eframe::egui; use itertools::Itertools; use monument::{Composition, CompositionGetter}; use ordered_float::OrderedFloat; @@ -112,22 +112,46 @@ impl Project { comp_getters.sort_by_cached_key(|g| OrderedFloat(-g.total_score())); // Display them in a grid // TODO: Custom (animated!) widget for this - egui::Grid::new("Comp grid").striped(true).show(ui, |ui| { - // Header - ui.label(RichText::new("Length").strong()); - ui.label(RichText::new("Score").strong()); - ui.label(RichText::new("Score/row").strong()); - ui.label(RichText::new("Call string").strong()); - ui.end_row(); - - for g in comp_getters { - ui.label(g.length().to_string()); - ui.label(format!("{:.2}", g.total_score())); - ui.label(format!("{:.6}", g.score_per_row())); - ui.label(g.call_string()); - ui.end_row() - } - }); + egui::Grid::new("Comp grid") + .striped(true) + .min_col_width(10.0) + .show(ui, |ui| { + /* Header */ + ui.strong("Length"); + ui.strong("|"); + ui.strong("Score"); + ui.strong("Score/row"); + + ui.strong("|"); + ui.strong("Music"); + for (_idx, mt) in params.music_types_to_show() { + ui.strong(&mt.name); + } + + ui.strong("|"); + ui.strong("Call string"); + ui.end_row(); + + /* Compositions */ + for g in comp_getters { + ui.label(g.length().to_string()); + + ui.label("|"); + ui.label(format!("{:.2}", g.total_score())); + ui.label(format!("{:.6}", g.score_per_row())); + + ui.label("|"); + let (music_counts, music_score) = g.music_counts_and_score(); + ui.label(format!("{music_score:.2}")); + for (idx, mt) in params.music_types_to_show() { + ui.label(mt.display_counts(music_counts[idx], params.stage)); + } + + ui.label("|"); + ui.label(g.call_string()); + ui.end_row() + } + }); } ///////////// From 046bc7d9417a8e3392ae240e098e31a6c4026327 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 15:31:20 +0000 Subject: [PATCH 14/29] Display music in the params --- bellframe/src/music.rs | 12 ++++++++++++ monument/gui/src/parameters/gui.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/bellframe/src/music.rs b/bellframe/src/music.rs index 51d4e69d..a594c027 100644 --- a/bellframe/src/music.rs +++ b/bellframe/src/music.rs @@ -378,6 +378,18 @@ impl RowPosition { pub const ALL: [RowPosition; 4] = [Self::Front, Self::Internal, Self::Back, Self::Wrap]; } +impl Display for RowPosition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name = match self { + RowPosition::Front => "Front", + RowPosition::Internal => "Internal", + RowPosition::Back => "Back", + RowPosition::Wrap => "Wrap", + }; + f.write_str(name) + } +} + /// A collection of data (usually counts) for each position in a [`Row`] that music can occur /// (front, internal, back, wrap) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/monument/gui/src/parameters/gui.rs b/monument/gui/src/parameters/gui.rs index d985385c..04ab6c0d 100644 --- a/monument/gui/src/parameters/gui.rs +++ b/monument/gui/src/parameters/gui.rs @@ -1,4 +1,4 @@ -use bellframe::Stroke; +use bellframe::{music::RowPosition, Stroke}; use eframe::egui; use monument::parameters::{default_shorthand, Call, SpliceStyle}; @@ -192,8 +192,33 @@ impl super::Parameters { } fn draw_music_params(&mut self, ui: &mut egui::Ui) { + egui::Grid::new("Music types").striped(true).show(ui, |ui| { + // Header + ui.label(""); + ui.strong("Total"); + for position in RowPosition::ALL { + ui.strong(position.to_string()); + } + ui.end_row(); + + // Music types + for mt in &mut self.inner.music_types { + ui.strong(&mt.name); + ui.checkbox(&mut mt.show_total, ""); + for position in RowPosition::ALL { + ui.horizontal_centered(|ui| { + ui.checkbox(mt.show_positions.get_mut(position), ""); + ui.add( + egui::DragValue::new(mt.weights.get_mut(position)) + .speed(0.1) + .min_decimals(0), + ); + }); + } + ui.end_row(); + } + }); ParamTable::show(ui, 0, |grid| { - grid.add_todo("Music types"); grid.add_param("Start stroke", |ui| { ui.selectable_value(&mut self.inner.start_stroke, Stroke::Hand, "Handstroke"); ui.selectable_value(&mut self.inner.start_stroke, Stroke::Back, "Backstroke"); From 5e53ea188645108fa31cdadf8288e38d2331372a Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 15:44:21 +0000 Subject: [PATCH 15/29] Show position char for single-position music types --- monument/cli/src/music/mod.rs | 9 ++++----- monument/gui/src/parameters/mod.rs | 2 +- monument/lib/src/parameters.rs | 5 +++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monument/cli/src/music/mod.rs b/monument/cli/src/music/mod.rs index 9bf481ce..5bbd6918 100644 --- a/monument/cli/src/music/mod.rs +++ b/monument/cli/src/music/mod.rs @@ -422,9 +422,7 @@ fn music_type_preset( ), // 5678 combinations don't make sense for any stage other than Triples and Major _ => { - return Err(anyhow::Error::msg( - "5678 combinations only make sense for Triples and Major", - )); + anyhow::bail!("5678 combinations only make sense for Triples and Major"); } }, MusicPreset::NearMisses => ( @@ -434,7 +432,7 @@ fn music_type_preset( ), MusicPreset::Crus => { if stage < Stage::TRIPLES { - return Err(anyhow::Error::msg("Can't have CRUs on less than 7 bells")); + anyhow::bail!("Can't have CRUs on less than 7 bells"); } let positions = if stage.is_even() { AtRowPositions::FRONT_AND_BACK @@ -470,13 +468,14 @@ fn new_music_type( show_total: bool, ) -> MusicType { let optional_weights = AtRowPositions::>::from(common.specified_weight); + let num_specified_weights: usize = optional_weights.map(|v| v.is_some() as usize).total(); // Create music types, etc. MusicType { id, inner: music_type.at_stroke(common.strokes.into()), weights: optional_weights.map(|x| x.unwrap_or(0.0)), show_positions: optional_weights.map(|x| x.is_some() && common.should_show()), - show_total: show_total && common.should_show(), + show_total: (show_total || num_specified_weights == 1) && common.should_show(), count_range: common.count_range.into(), name: match &common.name { Some(name) => name.clone(), diff --git a/monument/gui/src/parameters/mod.rs b/monument/gui/src/parameters/mod.rs index eff48045..be045ed9 100644 --- a/monument/gui/src/parameters/mod.rs +++ b/monument/gui/src/parameters/mod.rs @@ -134,7 +134,7 @@ impl Parameters { ); music_types.push(monument::parameters::MusicType { id: id_gen.next(), - show_total: true, + show_total: false, show_positions: AtRowPositions::BACK, name: "87s".to_owned(), inner: bellframe::MusicType::reversed_tenors_at_back(stage), diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index ae6c75de..7f07c575 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -781,7 +781,7 @@ impl MusicType { let mut s = String::new(); // Add total count - if self.show_total || num_items_to_show == 1 { + if self.show_total { Self::write_music_count( &mut s, self.masked_total(counts), @@ -789,7 +789,8 @@ impl MusicType { ); } // Add specific counts (if there are any) - if num_items_to_show > 1 { + let hide_counts = self.show_total && num_items_to_show == 1; + if !hide_counts { // Add brackets if there's a total score if self.show_total { s.push_str(" ("); From 510cbe2615d4f9ecf8067c8f168c74a9717caab8 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 16:45:37 +0000 Subject: [PATCH 16/29] Add IDs to compositions --- monument/cli/src/music/mod.rs | 5 ++- monument/cli/src/toml_file.rs | 7 +++-- monument/gui/src/parameters/mod.rs | 9 +++--- monument/gui/src/project.rs | 12 ++++++- monument/gui/src/search.rs | 5 +-- monument/lib/src/composition.rs | 19 +++++++++++- monument/lib/src/lib.rs | 2 +- monument/lib/src/parameters.rs | 50 ++++++++---------------------- monument/lib/src/search/mod.rs | 19 +++++++++--- monument/lib/src/search/prefix.rs | 2 ++ monument/lib/src/utils/mod.rs | 31 ++++++++++++++++-- 11 files changed, 104 insertions(+), 57 deletions(-) diff --git a/monument/cli/src/music/mod.rs b/monument/cli/src/music/mod.rs index 5bbd6918..72a71696 100644 --- a/monument/cli/src/music/mod.rs +++ b/monument/cli/src/music/mod.rs @@ -2,7 +2,10 @@ use bellframe::{ music::{AtRowPositions, Pattern, RowPosition}, Stage, }; -use monument::parameters::{IdGenerator, MusicType, MusicTypeId, MusicTypeVec}; +use monument::{ + parameters::{MusicType, MusicTypeId, MusicTypeVec}, + utils::IdGenerator, +}; use serde::Deserialize; use crate::utils::OptRangeInclusive; diff --git a/monument/cli/src/toml_file.rs b/monument/cli/src/toml_file.rs index 20ff62ac..f6f9b8b3 100644 --- a/monument/cli/src/toml_file.rs +++ b/monument/cli/src/toml_file.rs @@ -14,10 +14,11 @@ use index_vec::index_vec; use itertools::Itertools; use monument::{ parameters::{ - BaseCallType, CallDisplayStyle, CallId, CallVec, IdGenerator, MethodId, MethodVec, - MusicType, MusicTypeVec, OptionalRangeInclusive, Parameters, DEFAULT_BOB_WEIGHT, + BaseCallType, CallDisplayStyle, CallId, CallVec, MethodId, MethodVec, MusicType, + MusicTypeVec, OptionalRangeInclusive, Parameters, DEFAULT_BOB_WEIGHT, DEFAULT_SINGLE_WEIGHT, }, + utils::IdGenerator, Config, PartHeadGroup, }; use serde::Deserialize; @@ -417,7 +418,7 @@ impl TomlFile { /* BUILD METHODS */ - let mut id_gen = IdGenerator::::starting_at_zero(); + let id_gen = IdGenerator::::starting_at_zero(); let mut methods = MethodVec::new(); // TODO: Add dummy unused method, to make sure that Monument handles them correctly for (mut method, common) in parsed_methods { diff --git a/monument/gui/src/parameters/mod.rs b/monument/gui/src/parameters/mod.rs index be045ed9..3b523aae 100644 --- a/monument/gui/src/parameters/mod.rs +++ b/monument/gui/src/parameters/mod.rs @@ -4,9 +4,10 @@ use bellframe::{music::AtRowPositions, Mask, PlaceNot, RowBuf, Stage, Stroke}; use itertools::Itertools; use monument::{ parameters::{ - Call, CallDisplayStyle, CallId, IdGenerator, Method, MusicTypeId, MusicTypeVec, - OptionalRangeInclusive, SpliceStyle, + Call, CallDisplayStyle, CallId, Method, MusicTypeId, MusicTypeVec, OptionalRangeInclusive, + SpliceStyle, }, + utils::IdGenerator, PartHeadGroup, }; @@ -50,7 +51,7 @@ impl Parameters { let cc_lib = bellframe::MethodLib::cc_lib().unwrap(); // Methods - let make_method = |title: &str, id: u16| { + let make_method = |title: &str, id: u32| { let mut method = cc_lib.get_by_title(title).unwrap(); method.set_lead_end_label(); monument::parameters::Method { @@ -81,7 +82,7 @@ impl Parameters { let maybe_unused_calls = vec![(true, bob), (true, single)]; // Music - let mut id_gen = IdGenerator::::starting_at_zero(); + let id_gen = IdGenerator::::starting_at_zero(); let mut music_types = MusicTypeVec::new(); let mut add_front_back_music_type = |inner: bellframe::MusicType, diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index d549010d..ca5c770b 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; + use eframe::egui; use itertools::Itertools; -use monument::{Composition, CompositionGetter}; +use monument::{composition::CompositionId, utils::IdGenerator, Composition, CompositionGetter}; use ordered_float::OrderedFloat; use crate::{search::SearchThreadHandle, utils::ParamTable}; @@ -12,6 +14,8 @@ pub struct Project { name: String, params: crate::Parameters, compositions: Vec, + + comp_id_generator: Arc>, search_progress: Option, } @@ -158,6 +162,10 @@ impl Project { // HELPERS // ///////////// + pub fn comp_id_generator(&self) -> Arc> { + self.comp_id_generator.clone() + } + pub fn clone_params(&self) -> crate::Parameters { self.params.clone() } @@ -181,6 +189,8 @@ impl Default for Project { name: "Yorksire S8 Peals".to_owned(), params: crate::Parameters::yorkshire_s8_qps(), compositions: vec![], + + comp_id_generator: Arc::new(IdGenerator::starting_at_zero()), search_progress: None, } } diff --git a/monument/gui/src/search.rs b/monument/gui/src/search.rs index e8757944..d9480faf 100644 --- a/monument/gui/src/search.rs +++ b/monument/gui/src/search.rs @@ -53,8 +53,9 @@ fn worker_thread( ) { while let Ok((project, params, ctx)) = search_channel_rx.recv() { println!("Got new query!"); - let search = - monument::Search::new(params.to_monument(), monument::Config::default()).unwrap(); // TODO: Good error handling + let search = monument::Search::new(params.to_monument(), monument::Config::default()) + .unwrap() + .id_generator(project.lock().unwrap().comp_id_generator()); search.run( |update| { project.lock().unwrap().recieve_update(update); diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index c75ea8c1..61bd6161 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -28,9 +28,12 @@ use crate::{ /// A [`Composition`] generated by Monument. #[derive(Debug, Clone)] pub struct Composition { + pub(crate) id: CompositionId, pub(crate) stage: Stage, pub(crate) start_stroke: Stroke, pub(crate) path: Vec, + + // Cached values pub(crate) length: TotalLength, pub(crate) part_head: RowBuf, } @@ -72,6 +75,21 @@ impl PathElem { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CompositionId(pub u32); + +impl From for CompositionId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: CompositionId) -> Self { + value.0 + } +} + ////////////////// // CALCULATIONS // ////////////////// @@ -553,7 +571,6 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { } } } - rung_place_bell_positions.len() as f32 / total_unique_row_positions as f32 } diff --git a/monument/lib/src/lib.rs b/monument/lib/src/lib.rs index c38420bc..08bfed3e 100644 --- a/monument/lib/src/lib.rs +++ b/monument/lib/src/lib.rs @@ -41,7 +41,7 @@ #![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)] #![allow(clippy::result_large_err)] -mod composition; +pub mod composition; mod error; mod graph; mod group; diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index 7f07c575..bbd7a56a 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -3,7 +3,6 @@ use std::{ collections::HashSet, fmt::Write, - marker::PhantomData, ops::{Deref, Range, RangeInclusive}, sync::atomic::AtomicBool, }; @@ -20,7 +19,7 @@ use crate::{ group::PartHeadGroup, utils::{ lengths::{PerPartLength, TotalLength}, - Boundary, + Boundary, IdGenerator, }, Composition, Config, Search, Update, }; @@ -495,15 +494,15 @@ impl std::ops::Deref for Method { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MethodId(pub u16); +pub struct MethodId(pub u32); -impl From for MethodId { - fn from(value: u16) -> Self { +impl From for MethodId { + fn from(value: u32) -> Self { Self(value) } } -impl From for u16 { +impl From for u32 { fn from(value: MethodId) -> Self { value.0 } @@ -558,15 +557,15 @@ pub struct Call { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct CallId(pub u16); +pub struct CallId(pub u32); -impl From for CallId { - fn from(value: u16) -> Self { +impl From for CallId { + fn from(value: u32) -> Self { Self(value) } } -impl From for u16 { +impl From for u32 { fn from(value: CallId) -> Self { value.0 } @@ -842,15 +841,15 @@ impl Deref for MusicType { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MusicTypeId(pub u16); +pub struct MusicTypeId(pub u32); -impl From for MusicTypeId { - fn from(value: u16) -> Self { +impl From for MusicTypeId { + fn from(value: u32) -> Self { Self(value) } } -impl From for u16 { +impl From for u32 { fn from(value: MusicTypeId) -> Self { value.0 } @@ -1072,29 +1071,6 @@ impl OptionalRangeInclusive { } } -/// Struct which generates unique `Id`s. Can be used with any of [`MethodId`] or [`CallId`]. -#[derive(Debug, Clone)] -pub struct IdGenerator { - next_id: u16, - _id: PhantomData, -} - -impl + Into> IdGenerator { - pub fn starting_at_zero() -> Self { - Self { - next_id: 0, - _id: PhantomData, - } - } - - #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Id { - let id = Id::from(self.next_id); - self.next_id += 1; - id - } -} - index_vec::define_index_type! { pub struct MethodIdx = usize; } index_vec::define_index_type! { pub struct CallIdx = usize; } index_vec::define_index_type! { pub struct MusicTypeIdx = usize; } diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index c7fda4ad..778405d0 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -19,8 +19,10 @@ use bellframe::Stage; use itertools::Itertools; use crate::{ + composition::CompositionId, parameters::{MethodId, MusicTypeId, Parameters}, prove_length::{prove_lengths, RefinedRanges}, + utils::IdGenerator, Composition, }; @@ -37,11 +39,13 @@ use self::atw::AtwTable; #[derive(Debug)] pub struct Search { /* Data */ - params: Arc, config: Config, + params: Arc, + id_generator: Arc>, + + refined_ranges: RefinedRanges, graph: self::graph::Graph, atw_table: Arc, - refined_ranges: RefinedRanges, } impl Search { @@ -70,14 +74,21 @@ impl Search { drop(source_graph); Ok(Search { - params: Arc::new(params), - atw_table: Arc::new(atw_table), config, + params: Arc::new(params), + id_generator: Arc::new(IdGenerator::starting_at_zero()), + refined_ranges, graph, + atw_table: Arc::new(atw_table), }) } + pub fn id_generator(mut self, gen: Arc>) -> Self { + self.id_generator = gen; + self + } + /// Runs the search, **blocking the current thread** until either the search is completed or /// is aborted pub fn run(&self, update_fn: impl FnMut(Update), abort_flag: &AtomicBool) { diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 7f643c0d..2504f6fb 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -265,9 +265,11 @@ impl CompPrefix { // Now we know the composition is valid, construct it and return let path = self.flattened_path(search, paths); let comp = Composition { + id: search.id_generator.next(), stage: search.params.stage, start_stroke: search.params.start_stroke, path, + part_head: search .params .part_head_group diff --git a/monument/lib/src/utils/mod.rs b/monument/lib/src/utils/mod.rs index 2183c371..e4f76adf 100644 --- a/monument/lib/src/utils/mod.rs +++ b/monument/lib/src/utils/mod.rs @@ -1,4 +1,7 @@ -use std::cmp::Ordering; +use std::{ + marker::PhantomData, + sync::atomic::{AtomicU32, Ordering}, +}; pub(crate) mod counts; pub(crate) mod lengths; @@ -27,17 +30,39 @@ impl PartialEq for FrontierItem { impl Eq for FrontierItem {} impl PartialOrd for FrontierItem { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for FrontierItem { - fn cmp(&self, other: &Self) -> Ordering { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.distance.cmp(&other.distance) } } +/// Struct which generates unique `Id`s. Can be used with any of [`MethodId`] or [`CallId`]. +#[derive(Debug)] +pub struct IdGenerator { + next_id: AtomicU32, + _id: PhantomData, +} + +impl + Into> IdGenerator { + pub fn starting_at_zero() -> Self { + Self { + next_id: AtomicU32::new(0), + _id: PhantomData, + } + } + + #[allow(clippy::should_implement_trait)] + pub fn next(&self) -> Id { + let id = Id::from(self.next_id.fetch_add(1, Ordering::SeqCst)); + id + } +} + #[derive(Debug, Clone, Copy)] pub enum Boundary { Start, From cbf6ee36ed46a07f68f47c01ae200bf3b692120b Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 17:31:51 +0000 Subject: [PATCH 17/29] Add a cache for composition data --- monument/cli/src/lib.rs | 11 +++++++--- monument/cli/src/logging.rs | 22 +++++++++++++++---- monument/gui/src/project.rs | 28 ++++++++++++++++++------ monument/gui/src/search.rs | 6 +++++- monument/lib/src/composition.rs | 29 +++++++++++++++++++------ monument/lib/src/parameters.rs | 21 ------------------ monument/lib/src/search/best_first.rs | 16 ++++++++++---- monument/lib/src/search/mod.rs | 13 +++++++---- monument/lib/src/search/prefix.rs | 31 +++++++++++++++++---------- 9 files changed, 117 insertions(+), 60 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index e839fb01..ec412cf9 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -17,13 +17,13 @@ use std::{ str::FromStr, sync::{ atomic::{AtomicBool, Ordering}, - Arc, + Arc, Mutex, }, time::{Duration, Instant}, }; use log::LevelFilter; -use monument::{Composition, CompositionGetter, Search}; +use monument::{composition::CompositionDataCache, Composition, CompositionGetter, Search}; use ordered_float::OrderedFloat; use ringing_utils::PrettyDuration; use simple_logger::SimpleLogger; @@ -76,8 +76,10 @@ pub fn run( debug_print!(Search, search); // Build all the data structures for the search + let comp_cache = Arc::new(Mutex::new(CompositionDataCache::default())); let comp_printer = CompositionPrinter::new( search.clone(), + comp_cache.clone(), toml_file.should_print_atw(), !options.dont_display_comp_numbers, ); @@ -109,6 +111,7 @@ pub fn run( } }, &abort_flag, + &comp_cache, ); // Once the search has completed, sort the compositions and return @@ -117,14 +120,16 @@ pub fn run( let rounded = (f / FACTOR).round() * FACTOR; OrderedFloat(rounded) } + let mut cache_guard = comp_cache.lock().unwrap(); comps.sort_by_cached_key(|(comp, _generation_index)| { - let comp = CompositionGetter::new(comp, ¶ms).unwrap(); + let comp = CompositionGetter::new(comp, ¶ms, &mut cache_guard).unwrap(); ( rounded_float(comp.music_score()), rounded_float(comp.score_per_row()), comp.call_string(), ) }); + drop(cache_guard); Ok(Some(SearchResult { comps, comp_printer, diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index c2957387..5b789329 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -1,12 +1,18 @@ //! Code for handling the logging of compositions or updates provided by Monument -use std::{fmt::Write, io::Write as IoWrite, sync::Arc}; +use std::{ + fmt::Write, + io::Write as IoWrite, + sync::{Arc, Mutex}, +}; use bellframe::row::ShortRow; use colored::Colorize; use itertools::Itertools; use log::log_enabled; -use monument::{Composition, CompositionGetter, Progress, Search, Update}; +use monument::{ + composition::CompositionDataCache, Composition, CompositionGetter, Progress, Search, Update, +}; use ringing_utils::BigNumInt; /// Struct which handles logging updates, keeping the updates to a single line which updates as the @@ -125,6 +131,7 @@ impl SingleLineProgressLogger { #[derive(Debug, Clone)] pub struct CompositionPrinter { search: Arc, + cache: Arc>, /// Counter which records how many compositions have been printed so far comps_printed: usize, @@ -149,7 +156,12 @@ pub struct CompositionPrinter { } impl CompositionPrinter { - pub fn new(search: Arc, print_atw: bool, print_comp_widths: bool) -> Self { + pub fn new( + search: Arc, + cache: Arc>, + print_atw: bool, + print_comp_widths: bool, + ) -> Self { Self { comp_count_width: print_comp_widths .then_some(search.parameters().num_comps.to_string().len()), @@ -170,6 +182,7 @@ impl CompositionPrinter { .then(|| search.effective_part_head_stage().num_bells()), search, + cache, } } @@ -272,7 +285,8 @@ impl CompositionPrinter { } fn comp_string(&self, comp: &Composition, generation_index: usize) -> String { - let comp = CompositionGetter::new(comp, self.search.parameters()).unwrap(); + let mut cache = self.cache.lock().unwrap(); + let comp = CompositionGetter::new(comp, self.search.parameters(), &mut cache).unwrap(); let mut s = String::new(); // Comp index diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index ca5c770b..fb8bab7a 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,8 +1,12 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use eframe::egui; use itertools::Itertools; -use monument::{composition::CompositionId, utils::IdGenerator, Composition, CompositionGetter}; +use monument::{ + composition::{CompositionDataCache, CompositionId}, + utils::IdGenerator, + Composition, CompositionGetter, +}; use ordered_float::OrderedFloat; use crate::{search::SearchThreadHandle, utils::ParamTable}; @@ -15,6 +19,7 @@ pub struct Project { params: crate::Parameters, compositions: Vec, + comp_cache: Arc>, comp_id_generator: Arc>, search_progress: Option, } @@ -107,13 +112,17 @@ impl Project { fn draw_comps(&mut self, params: &monument::Parameters, ui: &mut egui::Ui) { // Generate getters for all comps which still match the current params - let mut comp_getters = self + let mut cache_guard = self.comp_cache.lock().unwrap(); + let mut comps_to_display = self .compositions .iter() - .filter_map(|c| CompositionGetter::new(c, params)) + .filter_map(|comp| { + let getter = CompositionGetter::new(comp, params, &mut cache_guard)?; + Some((getter.score_per_row(), comp)) + }) .collect_vec(); // Sort them by total score - comp_getters.sort_by_cached_key(|g| OrderedFloat(-g.total_score())); + comps_to_display.sort_by_cached_key(|(score, _comp)| OrderedFloat(-score)); // Display them in a grid // TODO: Custom (animated!) widget for this egui::Grid::new("Comp grid") @@ -137,7 +146,9 @@ impl Project { ui.end_row(); /* Compositions */ - for g in comp_getters { + for (_score, comp) in comps_to_display { + let g = CompositionGetter::new(comp, params, &mut cache_guard).unwrap(); + ui.label(g.length().to_string()); ui.label("|"); @@ -162,6 +173,10 @@ impl Project { // HELPERS // ///////////// + pub fn comp_cache(&self) -> Arc> { + self.comp_cache.clone() + } + pub fn comp_id_generator(&self) -> Arc> { self.comp_id_generator.clone() } @@ -190,6 +205,7 @@ impl Default for Project { params: crate::Parameters::yorkshire_s8_qps(), compositions: vec![], + comp_cache: Arc::new(Mutex::new(CompositionDataCache::default())), comp_id_generator: Arc::new(IdGenerator::starting_at_zero()), search_progress: None, } diff --git a/monument/gui/src/search.rs b/monument/gui/src/search.rs index d9480faf..f03193c6 100644 --- a/monument/gui/src/search.rs +++ b/monument/gui/src/search.rs @@ -53,15 +53,19 @@ fn worker_thread( ) { while let Ok((project, params, ctx)) = search_channel_rx.recv() { println!("Got new query!"); + let project_guard = project.lock().unwrap(); let search = monument::Search::new(params.to_monument(), monument::Config::default()) .unwrap() - .id_generator(project.lock().unwrap().comp_id_generator()); + .id_generator(project_guard.comp_id_generator()); + let comp_cache = project_guard.comp_cache(); + drop(project_guard); // Release the mutex before the search runs search.run( |update| { project.lock().unwrap().recieve_update(update); ctx.request_repaint(); }, &abort_flag, + &comp_cache, ); println!("Finished query, waiting for next one"); } diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 61bd6161..75db95b0 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -96,10 +96,11 @@ impl From for u32 { /// Struct which combines a [`Composition`] with a set of [`Parameters`] from which the extra data /// is taken. -#[derive(Debug, Clone)] -pub struct CompositionGetter<'c, 'p> { - composition: &'c Composition, - params: &'p Parameters, +#[derive(Debug)] +pub struct CompositionGetter<'a> { + composition: &'a Composition, + params: &'a Parameters, + cache: &'a mut CompositionDataCache, method_map: HashMap, call_map: HashMap, @@ -116,10 +117,14 @@ struct MethodData { lead_head_weights: Vec<(Mask, f32)>, } -impl<'c, 'p> CompositionGetter<'c, 'p> { +impl<'a> CompositionGetter<'a> { /* CONSTRUCTION/VALIDATION */ - pub fn new(composition: &'c Composition, params: &'p Parameters) -> Option { + pub fn new( + composition: &'a Composition, + params: &'a Parameters, + cache: &'a mut CompositionDataCache, + ) -> Option { // Do cheap checks before calculating anything. This is useful because the search // algorithm produces tons of too-short compositions, so it's worth validating length // quickly and thus rejecting these. @@ -133,6 +138,7 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { let getter = CompositionGetter { composition, params, + cache, block: Self::block(composition, params, &method_map, &call_map), valid_end_labels: params.valid_end_labels(), @@ -620,6 +626,17 @@ impl<'c, 'p> CompositionGetter<'c, 'p> { } } +///////////// +// CACHING // +///////////// + +/// Struct which caches expensive properties about [`Composition`]s, so that these values will be +/// calculated once and then re-used for future queries. +#[derive(Debug, Default)] +pub struct CompositionDataCache { + music_counts: HashMap<(bellframe::MusicType, CompositionId), AtRowPositions>, +} + /////////// // UTILS // /////////// diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index bbd7a56a..82a5370e 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -4,7 +4,6 @@ use std::{ collections::HashSet, fmt::Write, ops::{Deref, Range, RangeInclusive}, - sync::atomic::AtomicBool, }; use bellframe::{ @@ -21,7 +20,6 @@ use crate::{ lengths::{PerPartLength, TotalLength}, Boundary, IdGenerator, }, - Composition, Config, Search, Update, }; /// Fully built specification for which [`Composition`]s should be generated. @@ -64,25 +62,6 @@ pub struct Parameters { } impl Parameters { - /// Finish building and run the search with the default [`Config`], blocking until the required - /// compositions have been generated. - pub fn run(self) -> crate::Result> { - self.run_with_config(Config::default()) - } - - /// Finish building and run the search with a custom [`Config`], blocking until the required - /// number of [`Composition`]s have been generated. - pub fn run_with_config(self, config: Config) -> crate::Result> { - let mut comps = Vec::::new(); - let update_fn = |update| { - if let Update::Comp(comp) = update { - comps.push(comp); - } - }; - Search::new(self, config)?.run(update_fn, &AtomicBool::new(false)); - Ok(comps) - } - pub fn max_length(&self) -> TotalLength { *self.length.end() } diff --git a/monument/lib/src/search/best_first.rs b/monument/lib/src/search/best_first.rs index 2ac1dc35..ac898c85 100644 --- a/monument/lib/src/search/best_first.rs +++ b/monument/lib/src/search/best_first.rs @@ -1,12 +1,15 @@ use std::{ collections::BinaryHeap, - sync::atomic::{AtomicBool, Ordering}, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, + }, }; use datasize::DataSize; use ringing_utils::BigNumInt; -use crate::utils::lengths::TotalLength; +use crate::{composition::CompositionDataCache, utils::lengths::TotalLength}; use super::{path::Paths, prefix::CompPrefix, Progress, Search, Update}; @@ -16,7 +19,12 @@ const ITERS_BETWEEN_PATH_GCS: usize = 100_000_000; /// Searches a [`Graph`](m_gr::Graph) for compositions. This function is the core of Monument, and /// almost all of Monument's runtime will be spent in the `while` loop in this function. -pub(crate) fn search(search: &Search, mut update_fn: impl FnMut(Update), abort_flag: &AtomicBool) { +pub(crate) fn search( + search: &Search, + mut update_fn: impl FnMut(Update), + abort_flag: &AtomicBool, + cache: &Mutex, +) { let mem_limit = search .config .mem_limit @@ -57,7 +65,7 @@ pub(crate) fn search(search: &Search, mut update_fn: impl FnMut(Update), abort_f // frontier). This is best-first search (and can be A* depending on the cost function used). // This loop is the core of Monument - almost all the runtime will be spent here. while let Some(prefix) = frontier.pop() { - let maybe_comp = prefix.expand(search, &mut paths, &mut frontier); + let maybe_comp = prefix.expand(search, &mut paths, &mut frontier, cache); // Submit new compositions when they're generated if let Some(comp) = maybe_comp { diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index 778405d0..34ee1d36 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -11,7 +11,7 @@ use std::{ ops::RangeInclusive, sync::{ atomic::{AtomicBool, Ordering}, - Arc, + Arc, Mutex, }, }; @@ -19,7 +19,7 @@ use bellframe::Stage; use itertools::Itertools; use crate::{ - composition::CompositionId, + composition::{CompositionDataCache, CompositionId}, parameters::{MethodId, MusicTypeId, Parameters}, prove_length::{prove_lengths, RefinedRanges}, utils::IdGenerator, @@ -91,12 +91,17 @@ impl Search { /// Runs the search, **blocking the current thread** until either the search is completed or /// is aborted - pub fn run(&self, update_fn: impl FnMut(Update), abort_flag: &AtomicBool) { + pub fn run( + &self, + update_fn: impl FnMut(Update), + abort_flag: &AtomicBool, + comp_data_cache: &Mutex, + ) { // Make sure that `abort_flag` starts as false (so the search doesn't abort immediately). // We want this to be sequentially consistent to make sure that the worker threads don't // see the previous value (which could be 'true'). abort_flag.store(false, Ordering::SeqCst); - best_first::search(self, update_fn, abort_flag); + best_first::search(self, update_fn, abort_flag, comp_data_cache); } } diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 2504f6fb..70eb27d6 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -1,11 +1,11 @@ -use std::{cmp::Ordering, collections::BinaryHeap, ops::Deref}; +use std::{cmp::Ordering, collections::BinaryHeap, ops::Deref, sync::Mutex}; use bit_vec::BitVec; use datasize::DataSize; use ordered_float::OrderedFloat; use crate::{ - composition::{Composition, CompositionGetter, PathElem}, + composition::{Composition, CompositionDataCache, CompositionGetter, PathElem}, graph::LinkSide, group::PartHead, utils::{counts::Counts, div_rounding_up, lengths::TotalLength}, @@ -148,11 +148,12 @@ impl CompPrefix { search: &Search, paths: &mut Paths, frontier: &mut BinaryHeap, + cache: &Mutex, ) -> Option { // Determine the chunk being expanded (or if it's an end, complete the composition) let chunk_idx = match self.next_link_side { LinkSide::Chunk(chunk_idx) => chunk_idx, - LinkSide::StartOrEnd => return self.check_comp(search, paths), + LinkSide::StartOrEnd => return self.check_comp(search, paths, cache), }; let chunk = &search.graph.chunks[chunk_idx]; @@ -238,7 +239,12 @@ impl CompPrefix { impl CompPrefix { /// Assuming that the [`CompPrefix`] has just finished the composition, check if the resulting /// composition satisfies the user's requirements. - fn check_comp(&self, search: &Search, paths: &Paths) -> Option { + fn check_comp( + &self, + search: &Search, + paths: &Paths, + cache: &Mutex, + ) -> Option { assert!(self.next_link_side.is_start_or_end()); if !search.refined_ranges.length.contains(&self.length) { @@ -280,13 +286,16 @@ impl CompPrefix { // Validate the composition by building a `CompositionGetter`. The checks performed by // `CompositionGetter::new` are much stricter and more correct than those we can perform // here, so we defer entirely to it to check these candidate compositions for validity. - let comp_getter = CompositionGetter::new(&comp, &search.params)?; - // Sanity check that the composition is true - if search.params.require_truth && !comp_getter.is_true() { - panic!( - "Generated false composition ({})", - comp_getter.call_string() - ); + { + let mut cache = cache.lock().unwrap(); + let comp_getter = CompositionGetter::new(&comp, &search.params, &mut cache)?; + // Sanity check that the composition is true + if search.params.require_truth && !comp_getter.is_true() { + panic!( + "Generated false composition ({})", + comp_getter.call_string() + ); + } } // Finally, return the comp Some(comp) From 9c227545d681d4e49bc2fd35cfe078b40d325914 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 18:44:23 +0000 Subject: [PATCH 18/29] Remove `MusicTypeId` --- monument/cli/src/music/mod.rs | 45 ++++++------------------------ monument/gui/src/parameters/mod.rs | 7 +---- monument/lib/src/parameters.rs | 29 ------------------- monument/lib/src/search/mod.rs | 6 +--- 4 files changed, 11 insertions(+), 76 deletions(-) diff --git a/monument/cli/src/music/mod.rs b/monument/cli/src/music/mod.rs index 72a71696..75b0867c 100644 --- a/monument/cli/src/music/mod.rs +++ b/monument/cli/src/music/mod.rs @@ -2,10 +2,7 @@ use bellframe::{ music::{AtRowPositions, Pattern, RowPosition}, Stage, }; -use monument::{ - parameters::{MusicType, MusicTypeId, MusicTypeVec}, - utils::IdGenerator, -}; +use monument::parameters::{MusicType, MusicTypeVec}; use serde::Deserialize; use crate::utils::OptRangeInclusive; @@ -171,14 +168,12 @@ pub fn generate_music( #[derive(Debug)] struct MusicTypeFactory { music_types: MusicTypeVec, - id_gen: IdGenerator, } impl MusicTypeFactory { fn new() -> Self { Self { music_types: MusicTypeVec::new(), - id_gen: IdGenerator::starting_at_zero(), } } @@ -193,8 +188,7 @@ impl MusicTypeFactory { stage: Stage, ) -> anyhow::Result<()> { for s in toml_musics { - self.music_types - .extend(s.to_music_types(&mut self.id_gen, stage)?); + self.music_types.extend(s.to_music_types(stage)?); } Ok(()) } @@ -306,43 +300,29 @@ impl From for bellframe::StrokeSet { impl TomlMusic { /// Generates a [`MusicType`] representing `self`. - fn to_music_types( - &self, - id_gen: &mut IdGenerator, - stage: Stage, - ) -> anyhow::Result> { + fn to_music_types(&self, stage: Stage) -> anyhow::Result> { // This function just delegates the work to one of `music_type_runs`, // `music_type_patterns` or `music_type_preset`. use std::slice::from_ref; match self { Self::RunLength { length, common } => { - Ok(music_type_runs(from_ref(length), common, id_gen, stage)) - } - Self::RunLengths { lengths, common } => { - Ok(music_type_runs(lengths, common, id_gen, stage)) + Ok(music_type_runs(from_ref(length), common, stage)) } + Self::RunLengths { lengths, common } => Ok(music_type_runs(lengths, common, stage)), Self::Pattern { pattern, common } => { - music_type_patterns(from_ref(pattern), common, id_gen, stage) + music_type_patterns(from_ref(pattern), common, stage) } - Self::Patterns { patterns, common } => { - music_type_patterns(patterns, common, id_gen, stage) - } - Self::Preset { preset, common } => music_type_preset(*preset, common, id_gen, stage), + Self::Patterns { patterns, common } => music_type_patterns(patterns, common, stage), + Self::Preset { preset, common } => music_type_preset(*preset, common, stage), } } } -fn music_type_runs( - lengths: &[u8], - common: &MusicCommon, - id_gen: &mut IdGenerator, - stage: Stage, -) -> Vec { +fn music_type_runs(lengths: &[u8], common: &MusicCommon, stage: Stage) -> Vec { let mut music_types = Vec::new(); for &len in lengths { music_types.push(new_music_type( - id_gen.next(), format!("{len}-bell runs"), bellframe::MusicType::runs(len, stage), common, @@ -355,7 +335,6 @@ fn music_type_runs( fn music_type_patterns( pattern_strings: &[String], common: &MusicCommon, - id_gen: &mut IdGenerator, stage: Stage, ) -> anyhow::Result> { let count_range = monument::parameters::OptionalRangeInclusive::from(common.count_range); @@ -370,7 +349,6 @@ fn music_type_patterns( // If this is being shown but no custom name is given, we display each pattern separately for pattern in &patterns { music_types.push(new_music_type( - id_gen.next(), format!("{pattern}s"), bellframe::MusicType::from(pattern.clone()), common, @@ -382,7 +360,6 @@ fn music_type_patterns( // If the user gave a custom name or is not showing this type at all, we combine all the // patterns into one [`MusicType`] let mut music_type = new_music_type( - id_gen.next(), String::new(), bellframe::MusicType::new(patterns), common, @@ -404,7 +381,6 @@ fn music_type_patterns( fn music_type_preset( preset: MusicPreset, common: &MusicCommon, - id_gen: &mut IdGenerator, stage: Stage, ) -> anyhow::Result> { // Determine the pattern types @@ -455,7 +431,6 @@ fn music_type_preset( } // Construct a music type Ok(vec![new_music_type( - id_gen.next(), default_name.to_owned(), music_type, &common, @@ -464,7 +439,6 @@ fn music_type_preset( } fn new_music_type( - id: MusicTypeId, default_name: String, music_type: bellframe::MusicType, common: &MusicCommon, @@ -474,7 +448,6 @@ fn new_music_type( let num_specified_weights: usize = optional_weights.map(|v| v.is_some() as usize).total(); // Create music types, etc. MusicType { - id, inner: music_type.at_stroke(common.strokes.into()), weights: optional_weights.map(|x| x.unwrap_or(0.0)), show_positions: optional_weights.map(|x| x.is_some() && common.should_show()), diff --git a/monument/gui/src/parameters/mod.rs b/monument/gui/src/parameters/mod.rs index 3b523aae..26ae66f9 100644 --- a/monument/gui/src/parameters/mod.rs +++ b/monument/gui/src/parameters/mod.rs @@ -4,10 +4,8 @@ use bellframe::{music::AtRowPositions, Mask, PlaceNot, RowBuf, Stage, Stroke}; use itertools::Itertools; use monument::{ parameters::{ - Call, CallDisplayStyle, CallId, Method, MusicTypeId, MusicTypeVec, OptionalRangeInclusive, - SpliceStyle, + Call, CallDisplayStyle, CallId, Method, MusicTypeVec, OptionalRangeInclusive, SpliceStyle, }, - utils::IdGenerator, PartHeadGroup, }; @@ -82,7 +80,6 @@ impl Parameters { let maybe_unused_calls = vec![(true, bob), (true, single)]; // Music - let id_gen = IdGenerator::::starting_at_zero(); let mut music_types = MusicTypeVec::new(); let mut add_front_back_music_type = |inner: bellframe::MusicType, @@ -91,7 +88,6 @@ impl Parameters { weight: f32, name: String| { music_types.push(monument::parameters::MusicType { - id: id_gen.next(), show_total, show_positions: AtRowPositions::front_and_back(show_positions), name, @@ -134,7 +130,6 @@ impl Parameters { "CRUs".to_owned(), ); music_types.push(monument::parameters::MusicType { - id: id_gen.next(), show_total: false, show_positions: AtRowPositions::BACK, name: "87s".to_owned(), diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index 82a5370e..f6eb6d2a 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -129,10 +129,6 @@ impl Parameters { self.calls.position(|c| c.id == id).unwrap() } - pub fn music_type_id_to_idx(&self, id: MusicTypeId) -> MusicTypeIdx { - self.music_types.position(|mt| mt.id == id).unwrap() - } - pub fn get_method_by_id(&self, id: MethodId) -> &Method { &self.methods[self.method_id_to_idx(id)] } @@ -141,10 +137,6 @@ impl Parameters { &self.calls[self.call_id_to_idx(id)] } - pub fn get_music_type_by_id(&self, id: MusicTypeId) -> &MusicType { - &self.music_types[self.music_type_id_to_idx(id)] - } - ////////////////////// // HELPER FUNCTIONS // ////////////////////// @@ -310,10 +302,6 @@ impl Parameters { self.calls.iter().find(|mt| mt.id == id).unwrap() } - pub fn get_music_type(&self, id: MusicTypeId) -> &MusicType { - self.music_types.iter().find(|mt| mt.id == id).unwrap() - } - pub fn music_types_to_show(&self) -> Vec<(MusicTypeIdx, &MusicType)> { self.music_types .iter_enumerated() @@ -716,8 +704,6 @@ pub fn default_calling_positions(place_not: &PlaceNot) -> Vec { /// A class of music that Monument should care about #[derive(Debug, Clone)] pub struct MusicType { - pub id: MusicTypeId, - pub show_total: bool, pub show_positions: AtRowPositions, pub name: String, @@ -819,21 +805,6 @@ impl Deref for MusicType { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MusicTypeId(pub u32); - -impl From for MusicTypeId { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: MusicTypeId) -> Self { - value.0 - } -} - //////////////// // MISC TYPES // //////////////// diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index 34ee1d36..df99c28e 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -20,7 +20,7 @@ use itertools::Itertools; use crate::{ composition::{CompositionDataCache, CompositionId}, - parameters::{MethodId, MusicTypeId, Parameters}, + parameters::{MethodId, Parameters}, prove_length::{prove_lengths, RefinedRanges}, utils::IdGenerator, Composition, @@ -121,10 +121,6 @@ impl Search { self.params.methods.iter().map(|m| (m, m.shorthand())) } - pub fn music_type_ids(&self) -> impl Iterator + '_ { - self.params.music_types.iter().map(|ty| ty.id) - } - pub fn parameters(&self) -> &Parameters { &self.params } From 575f2b4c27e92ae77cd0d7e368eb8750d22b9bb7 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 18:45:33 +0000 Subject: [PATCH 19/29] Cache composition validity --- bellframe/src/method/mod.rs | 3 +- monument/lib/src/composition.rs | 52 +++++++++++++++++++++++++++++++-- monument/lib/src/group.rs | 2 +- monument/lib/src/parameters.rs | 12 ++++---- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/bellframe/src/method/mod.rs b/bellframe/src/method/mod.rs index 2fcbae54..d4eeb939 100644 --- a/bellframe/src/method/mod.rs +++ b/bellframe/src/method/mod.rs @@ -15,7 +15,8 @@ pub const LABEL_LEAD_END: &str = "LE"; /// locations (by their label), and thus the single lead can be modified to determine the effect of /// calls in a general way. This follows how [CompLib](https://complib.org)'s composition input /// works. -#[derive(Debug, Clone)] +// TODO: Exclude name in equality test? +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Method { pub name: String, omit_class: bool, // Set to `true` for methods like Grandsire, who's title omits the class diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 75db95b0..2a0a3f2f 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -3,6 +3,7 @@ use std::{ collections::{HashMap, HashSet}, hash::Hash, + sync::{Arc, Mutex}, }; use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke}; @@ -124,11 +125,39 @@ impl<'a> CompositionGetter<'a> { composition: &'a Composition, params: &'a Parameters, cache: &'a mut CompositionDataCache, + ) -> Option { + let validity_cache = Arc::clone(&cache.get_per_param_cache(params).is_valid); + let is_valid = validity_cache.lock().unwrap().get(&composition.id).copied(); + match is_valid { + Some(false) => None, // Known bad; reject + Some(true) => { + // Known good; construct but skip checks + let v = Self::new_inner(composition, params, cache, true); + assert!(v.is_some()); + v + } + None => { + // Unknown; construct without skipping checks and save the result + let getter = Self::new_inner(composition, params, cache, false); + validity_cache + .lock() + .unwrap() + .insert(composition.id, getter.is_some()); + getter + } + } + } + + fn new_inner( + composition: &'a Composition, + params: &'a Parameters, + cache: &'a mut CompositionDataCache, + skip_checks: bool, ) -> Option { // Do cheap checks before calculating anything. This is useful because the search // algorithm produces tons of too-short compositions, so it's worth validating length // quickly and thus rejecting these. - if !Self::do_cheap_checks(composition, params) { + if !skip_checks && !Self::do_cheap_checks(composition, params) { return None; } @@ -146,7 +175,7 @@ impl<'a> CompositionGetter<'a> { call_map, }; - if !getter.do_non_cheap_checks() { + if !skip_checks && !getter.do_non_cheap_checks() { return None; } @@ -634,9 +663,28 @@ impl<'a> CompositionGetter<'a> { /// calculated once and then re-used for future queries. #[derive(Debug, Default)] pub struct CompositionDataCache { + per_param_cache: Option<(Parameters, PerParamCache)>, music_counts: HashMap<(bellframe::MusicType, CompositionId), AtRowPositions>, } +impl CompositionDataCache { + fn get_per_param_cache(&mut self, params: &Parameters) -> &mut PerParamCache { + // Create a new empty cache if the params changed + let current_cached_params = self.per_param_cache.as_ref().map(|(params, _cache)| params); + if current_cached_params != Some(params) { + self.per_param_cache = Some((params.clone(), PerParamCache::default())); + } + // Get the (possibly replaced) cache + &mut self.per_param_cache.as_mut().unwrap().1 + } +} + +#[derive(Debug, Default)] +struct PerParamCache { + // TODO: Find a way of doing this without Arc> + is_valid: Arc>>, +} + /////////// // UTILS // /////////// diff --git a/monument/lib/src/group.rs b/monument/lib/src/group.rs index 35fa8f6d..1ee7ed33 100644 --- a/monument/lib/src/group.rs +++ b/monument/lib/src/group.rs @@ -6,7 +6,7 @@ use gcd::Gcd; /// A group of [`Row`]s, used to represent part heads. Currently limited to cyclic groups (in the /// mathematical sense of 'cyclic'). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PartHeadGroup { /// The [`Row`]s which make up the `PartHeadGroup`. `part_heads[0]` is always rounds. /// diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index f6eb6d2a..75711155 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -27,7 +27,7 @@ use crate::{ /// Compare this to [`Config`], which determines _how_ those /// [`Composition`]s are generated (and therefore determines how quickly the /// results are generated). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Parameters { // GENERAL pub length: RangeInclusive, @@ -315,7 +315,7 @@ impl Parameters { ///////////// /// A `Method` used in a [`Search`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Method { pub id: MethodId, pub inner: bellframe::Method, @@ -505,7 +505,7 @@ pub fn default_shorthand(title: &str) -> String { /////////// /// A type of call (e.g. bob or single) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Call { pub id: CallId, @@ -702,7 +702,7 @@ pub fn default_calling_positions(place_not: &PlaceNot) -> Vec { /////////// /// A class of music that Monument should care about -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct MusicType { pub show_total: bool, pub show_positions: AtRowPositions, @@ -810,7 +810,7 @@ impl Deref for MusicType { //////////////// /// Convenient description of a set of courses via [`Mask`]s. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CourseSet { /// List of [`Mask`]s, of which courses should match at least one. pub masks: Vec, @@ -970,7 +970,7 @@ fn try_fixing_bells(mut mask: Mask, fixed_bells: &[(Bell, usize)]) -> Option, pub max: Option, From 0089339c5a61575bf4891cf79b8971b34a95be9a Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 19:50:00 +0000 Subject: [PATCH 20/29] Cache music counts --- monument/cli/src/lib.rs | 8 ++++---- monument/cli/src/logging.rs | 2 +- monument/gui/src/main.rs | 10 +++++----- monument/gui/src/project.rs | 4 ++-- monument/lib/src/composition.rs | 29 +++++++++++++++++------------ 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index ec412cf9..770bbd53 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -122,11 +122,11 @@ pub fn run( } let mut cache_guard = comp_cache.lock().unwrap(); comps.sort_by_cached_key(|(comp, _generation_index)| { - let comp = CompositionGetter::new(comp, ¶ms, &mut cache_guard).unwrap(); + let mut getter = CompositionGetter::new(comp, ¶ms, &mut cache_guard).unwrap(); ( - rounded_float(comp.music_score()), - rounded_float(comp.score_per_row()), - comp.call_string(), + rounded_float(getter.music_score()), + rounded_float(getter.score_per_row()), + getter.call_string(), ) }); drop(cache_guard); diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index 5b789329..12074aea 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -286,7 +286,7 @@ impl CompositionPrinter { fn comp_string(&self, comp: &Composition, generation_index: usize) -> String { let mut cache = self.cache.lock().unwrap(); - let comp = CompositionGetter::new(comp, self.search.parameters(), &mut cache).unwrap(); + let mut comp = CompositionGetter::new(comp, self.search.parameters(), &mut cache).unwrap(); let mut s = String::new(); // Comp index diff --git a/monument/gui/src/main.rs b/monument/gui/src/main.rs index 30562adb..0d8e8e1e 100644 --- a/monument/gui/src/main.rs +++ b/monument/gui/src/main.rs @@ -56,11 +56,11 @@ impl eframe::App for MonumentApp { }) .inner; - egui::SidePanel::right("Comps panel") - .default_width(300.0) - .show(ctx, |ui| { - ui.heading("Composition display"); - }); + // egui::SidePanel::right("Comps panel") + // .default_width(300.0) + // .show(ctx, |ui| { + // ui.heading("Composition display"); + // }); egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Compositions"); diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index fb8bab7a..b5886cdd 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -117,7 +117,7 @@ impl Project { .compositions .iter() .filter_map(|comp| { - let getter = CompositionGetter::new(comp, params, &mut cache_guard)?; + let mut getter = CompositionGetter::new(comp, params, &mut cache_guard)?; Some((getter.score_per_row(), comp)) }) .collect_vec(); @@ -147,7 +147,7 @@ impl Project { /* Compositions */ for (_score, comp) in comps_to_display { - let g = CompositionGetter::new(comp, params, &mut cache_guard).unwrap(); + let mut g = CompositionGetter::new(comp, params, &mut cache_guard).unwrap(); ui.label(g.length().to_string()); diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 2a0a3f2f..8df5a278 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -164,7 +164,7 @@ impl<'a> CompositionGetter<'a> { // Once the cheap checks pass, compute useful cached values and build the `getter` let method_map = Self::method_map(composition, params)?; let call_map = Self::call_map(composition, params)?; - let getter = CompositionGetter { + let mut getter = CompositionGetter { composition, params, cache, @@ -315,7 +315,7 @@ impl<'a> CompositionGetter<'a> { comp } - fn do_non_cheap_checks(&self) -> bool { + fn do_non_cheap_checks(&mut self) -> bool { if !self.are_methods_satisfied() { return false; } @@ -526,13 +526,13 @@ impl<'a> CompositionGetter<'a> { /// The average score generated by each [`Row`] in this composition. This is equal to /// `self.total_score() / self.length() as f32`. - pub fn score_per_row(&self) -> f32 { + pub fn score_per_row(&mut self) -> f32 { self.total_score() / self.length() as f32 } /// The total score generated by this composition from all the different weights (music, calls, /// changes of method, handbell coursing, etc.). - pub fn total_score(&self) -> f32 { + pub fn total_score(&mut self) -> f32 { let mut total_score = 0.0; // Music total_score += self.music_score(); @@ -622,7 +622,7 @@ impl<'a> CompositionGetter<'a> { /// Compute [`Self::music_counts`] and use it to calculate [`Self::music_score`]. Calling /// them separately would cause the music to be calculated twice. - pub fn music_counts_and_score(&self) -> (MusicTypeVec>, f32) { + pub fn music_counts_and_score(&mut self) -> (MusicTypeVec>, f32) { let music_counts = self.music_counts(); let mut music_score = 0.0; for (count, music_type) in music_counts.iter().zip_eq(&self.params.music_types) { @@ -633,17 +633,22 @@ impl<'a> CompositionGetter<'a> { /// Score generated by just the [`MusicType`]s (not including calls, changes of methods, /// etc.). - pub fn music_score(&self) -> f32 { + pub fn music_score(&mut self) -> f32 { self.music_counts_and_score().1 } /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. - pub fn music_counts(&self) -> MusicTypeVec> { - self.params - .music_types - .iter() - .map(|mt| mt.count(&self.block, !self.composition.start_stroke)) - .collect() + pub fn music_counts(&mut self) -> MusicTypeVec> { + let mut counts = MusicTypeVec::new(); + for mt in &self.params.music_types { + let count = self + .cache + .music_counts + .entry((mt.inner.clone(), self.composition.id)) + .or_insert_with(|| mt.count(&self.block, !self.composition.start_stroke)); + counts.push(*count); + } + counts } fn get_method(&self, id: MethodId) -> &Method { From bcf1e020f5214b8e09da56962d850b7459abdbf6 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 10 Dec 2023 21:10:39 +0000 Subject: [PATCH 21/29] Ignore profiling files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b0ab527a..fbf0a839 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ monument/test/.last-benches.toml # Where I store comps I'm working on that I don't want to show to everyone */comps/ -# All callgrind files +# Profiling files **/callgrind* +**/profile*.json From 9492ed6fdf8f8ad85d3a8dc2c9211b98d552795a Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 16 Dec 2023 16:52:50 +0000 Subject: [PATCH 22/29] Better composition value caching --- monument/cli/src/lib.rs | 9 +- monument/cli/src/logging.rs | 15 +- monument/gui/src/project.rs | 8 +- monument/lib/src/composition.rs | 866 +++++++++++++++----------- monument/lib/src/lib.rs | 2 +- monument/lib/src/search/best_first.rs | 4 +- monument/lib/src/search/mod.rs | 4 +- monument/lib/src/search/prefix.rs | 43 +- 8 files changed, 527 insertions(+), 424 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index 770bbd53..3418ee8d 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -23,7 +23,7 @@ use std::{ }; use log::LevelFilter; -use monument::{composition::CompositionDataCache, Composition, CompositionGetter, Search}; +use monument::{composition::CompositionCache, Composition, Search}; use ordered_float::OrderedFloat; use ringing_utils::PrettyDuration; use simple_logger::SimpleLogger; @@ -76,7 +76,7 @@ pub fn run( debug_print!(Search, search); // Build all the data structures for the search - let comp_cache = Arc::new(Mutex::new(CompositionDataCache::default())); + let comp_cache = Arc::new(Mutex::new(CompositionCache::default())); let comp_printer = CompositionPrinter::new( search.clone(), comp_cache.clone(), @@ -121,12 +121,13 @@ pub fn run( OrderedFloat(rounded) } let mut cache_guard = comp_cache.lock().unwrap(); + let cache_with_params = cache_guard.with_params(¶ms); comps.sort_by_cached_key(|(comp, _generation_index)| { - let mut getter = CompositionGetter::new(comp, ¶ms, &mut cache_guard).unwrap(); + let getter = cache_with_params.get_comp_values(comp).unwrap(); ( rounded_float(getter.music_score()), rounded_float(getter.score_per_row()), - getter.call_string(), + getter.call_string().to_owned(), ) }); drop(cache_guard); diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index 12074aea..351787f2 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -10,9 +10,7 @@ use bellframe::row::ShortRow; use colored::Colorize; use itertools::Itertools; use log::log_enabled; -use monument::{ - composition::CompositionDataCache, Composition, CompositionGetter, Progress, Search, Update, -}; +use monument::{composition::CompositionCache, Composition, Progress, Search, Update}; use ringing_utils::BigNumInt; /// Struct which handles logging updates, keeping the updates to a single line which updates as the @@ -131,7 +129,7 @@ impl SingleLineProgressLogger { #[derive(Debug, Clone)] pub struct CompositionPrinter { search: Arc, - cache: Arc>, + cache: Arc>, /// Counter which records how many compositions have been printed so far comps_printed: usize, @@ -158,7 +156,7 @@ pub struct CompositionPrinter { impl CompositionPrinter { pub fn new( search: Arc, - cache: Arc>, + cache: Arc>, print_atw: bool, print_comp_widths: bool, ) -> Self { @@ -286,7 +284,8 @@ impl CompositionPrinter { fn comp_string(&self, comp: &Composition, generation_index: usize) -> String { let mut cache = self.cache.lock().unwrap(); - let mut comp = CompositionGetter::new(comp, self.search.parameters(), &mut cache).unwrap(); + let cache_with_params = cache.with_params(&self.search.parameters()); + let comp = cache_with_params.get_comp_values(comp).unwrap(); let mut s = String::new(); // Comp index @@ -320,8 +319,8 @@ impl CompositionPrinter { // Music let params = self.search.parameters(); let music_types_to_show = params.music_types_to_show(); - let (music_counts, music_score) = comp.music_counts_and_score(); // Only call `music_counts` once! - write!(s, " {:>7.2} ", music_score).unwrap(); + let music_counts = comp.music_counts(); + write!(s, " {:>7.2} ", comp.music_score()).unwrap(); if !music_types_to_show.is_empty() { s.push(':'); } diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index b5886cdd..67a4ad79 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use eframe::egui; use itertools::Itertools; use monument::{ - composition::{CompositionDataCache, CompositionId}, + composition::{CompositionCache, CompositionId}, utils::IdGenerator, Composition, CompositionGetter, }; @@ -19,7 +19,7 @@ pub struct Project { params: crate::Parameters, compositions: Vec, - comp_cache: Arc>, + comp_cache: Arc>, comp_id_generator: Arc>, search_progress: Option, } @@ -173,7 +173,7 @@ impl Project { // HELPERS // ///////////// - pub fn comp_cache(&self) -> Arc> { + pub fn comp_cache(&self) -> Arc> { self.comp_cache.clone() } @@ -205,7 +205,7 @@ impl Default for Project { params: crate::Parameters::yorkshire_s8_qps(), compositions: vec![], - comp_cache: Arc::new(Mutex::new(CompositionDataCache::default())), + comp_cache: Arc::new(Mutex::new(CompositionCache::default())), comp_id_generator: Arc::new(IdGenerator::starting_at_zero()), search_progress: None, } diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 8df5a278..6645996c 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -1,12 +1,14 @@ //! Representation of a [`Composition`] generated by Monument. use std::{ + cell::RefCell, collections::{HashMap, HashSet}, hash::Hash, + rc::Rc, sync::{Arc, Mutex}, }; -use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke}; +use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke, Truth}; use itertools::Itertools; use crate::{ @@ -18,6 +20,7 @@ use crate::{ lengths::{PerPartLength, TotalLength}, Boundary, }, + PartHead, }; #[allow(unused_imports)] // Used by doc comments @@ -34,8 +37,11 @@ pub struct Composition { pub(crate) start_stroke: Stroke, pub(crate) path: Vec, - // Cached values + // Cached values which don't change, even if a different set of params are used + pub(crate) unique_place_bell_rows_per_bell: Vec, // TODO: Type-safe bell-vec + pub(crate) truth: Truth, pub(crate) length: TotalLength, + pub(crate) end_row: RowBuf, pub(crate) part_head: RowBuf, } @@ -49,6 +55,45 @@ pub(crate) struct PathElem { pub call_to_end: Option, } +impl Composition { + pub(crate) fn new( + id: CompositionId, + path: Vec, + part_head: PartHead, + cache: &CompCacheWithParams, + ) -> Self { + let block = cache.block(&path); + Self { + id, + stage: cache.params.stage, + start_stroke: cache.params.start_stroke, + path, + + truth: block.truth(), + part_head: cache.params.part_head_group.get_row(part_head).to_owned(), + unique_place_bell_rows_per_bell: Self::unique_place_bell_rows_per_bell(&block), + end_row: cache.params.end_row.clone(), + length: TotalLength::new(block.len()), + } + } + + fn unique_place_bell_rows_per_bell(block: &Block<(MethodId, usize)>) -> Vec { + // Collect which (place bell, method, sub-lead-idx) triples are rung by each bell + let mut place_bell_rows: Vec> = + vec![HashSet::new(); block.stage().num_bells()]; + for (&(method_id, sub_lead_idx), row) in block.annot_rows() { + for (place, bell) in row.bell_iter().enumerate() { + place_bell_rows[bell.index()].insert((place as u8, method_id, sub_lead_idx)); + } + } + // Return the sizes of these sets + place_bell_rows + .into_iter() + .map(|set| set.len()) + .collect_vec() + } +} + impl PathElem { pub fn ends_with_plain(&self) -> bool { self.call_to_end.is_none() @@ -91,24 +136,46 @@ impl From for u32 { } } -////////////////// -// CALCULATIONS // -////////////////// +///////////// +// CACHING // +///////////// -/// Struct which combines a [`Composition`] with a set of [`Parameters`] from which the extra data -/// is taken. +type PerMusicTypeCache = Rc>>>; + +/// Struct which caches expensive properties about [`Composition`]s, so that these values will be +/// calculated once and then re-used for future queries. +#[derive(Debug, Default)] +pub struct CompositionCache { + per_param_cache: Option<(Parameters, PerParamCache)>, + music_counts: HashMap, +} + +#[derive(Debug, Default)] +struct PerParamCache { + // TODO: Find a way of doing this without Arc> + is_valid: Arc>>, +} + +/// A variant of [`CompositionCache`] with extra cached data specific to the [`Parameters`]. +/// +/// Constructing a `CompCacheWithParams` is much more expensive than using it, so it is intended +/// to be constructed once and then applied to many [`Composition`]s. #[derive(Debug)] -pub struct CompositionGetter<'a> { - composition: &'a Composition, - params: &'a Parameters, - cache: &'a mut CompositionDataCache, +pub struct CompCacheWithParams<'params, 'cache> { + params: &'params Parameters, + /* DATA CACHED FROM PARAMS */ method_map: HashMap, call_map: HashMap, + working_bells: Vec, /// Set of labels at which the composition can end. I.e. these are labels which are also a /// valid `end_index` for some method. valid_end_labels: HashSet, - block: Block<(MethodId, usize)>, + + /* DATA BORROWED FROM COMP CACHE */ + per_param_cache: &'cache PerParamCache, + // PERF: Push the RefCell out of here, so we don't have to keep checking for valid borrows + music_counts: MusicTypeVec, } #[derive(Debug, Clone)] @@ -118,117 +185,71 @@ struct MethodData { lead_head_weights: Vec<(Mask, f32)>, } -impl<'a> CompositionGetter<'a> { - /* CONSTRUCTION/VALIDATION */ - - pub fn new( - composition: &'a Composition, - params: &'a Parameters, - cache: &'a mut CompositionDataCache, - ) -> Option { - let validity_cache = Arc::clone(&cache.get_per_param_cache(params).is_valid); - let is_valid = validity_cache.lock().unwrap().get(&composition.id).copied(); - match is_valid { - Some(false) => None, // Known bad; reject - Some(true) => { - // Known good; construct but skip checks - let v = Self::new_inner(composition, params, cache, true); - assert!(v.is_some()); - v - } - None => { - // Unknown; construct without skipping checks and save the result - let getter = Self::new_inner(composition, params, cache, false); - validity_cache - .lock() - .unwrap() - .insert(composition.id, getter.is_some()); - getter - } - } +impl CompositionCache { + pub fn with_params<'params, 'cache>( + &'cache mut self, + params: &'params Parameters, + ) -> CompCacheWithParams<'params, 'cache> { + CompCacheWithParams::new(self, params) } +} - fn new_inner( - composition: &'a Composition, - params: &'a Parameters, - cache: &'a mut CompositionDataCache, - skip_checks: bool, - ) -> Option { - // Do cheap checks before calculating anything. This is useful because the search - // algorithm produces tons of too-short compositions, so it's worth validating length - // quickly and thus rejecting these. - if !skip_checks && !Self::do_cheap_checks(composition, params) { - return None; - } - - // Once the cheap checks pass, compute useful cached values and build the `getter` - let method_map = Self::method_map(composition, params)?; - let call_map = Self::call_map(composition, params)?; - let mut getter = CompositionGetter { - composition, - params, - cache, +impl<'params, 'cache> CompCacheWithParams<'params, 'cache> { + fn new(cache: &'cache mut CompositionCache, params: &'params Parameters) -> Self { + let CompositionCache { + per_param_cache, + music_counts, + } = cache; - block: Self::block(composition, params, &method_map, &call_map), + Self { + method_map: Self::method_map(params), + call_map: Self::call_map(params), valid_end_labels: params.valid_end_labels(), - method_map, - call_map, - }; + working_bells: params.working_bells(), - if !skip_checks && !getter.do_non_cheap_checks() { - return None; + per_param_cache: Self::get_or_reset_per_param_cache(per_param_cache, params), + music_counts: Self::get_music_count_caches(music_counts, params), + params, } - - Some(getter) // If all checks didn't find problems, the getter is valid } - /// Perform cheap checks on this composition which don't involve looking up methods or calls. - /// The main reason to do this is to quickly reject compositions which are being generated by the - /// search routine - #[must_use] - fn do_cheap_checks(composition: &Composition, params: &Parameters) -> bool { - if composition.stage != params.stage { - return false; // Stage mismatch - } - if !params.length.contains(&composition.length) { - return false; // Length mismatch - } - if !params - .part_head_group - .is_row_generator(&composition.part_head) - { - return false; // Composition doesn't end in a valid part - } - if composition.path[0].start_row != params.start_row { - return false; // Doesn't start in the right row + fn get_or_reset_per_param_cache( + cache: &'cache mut Option<(Parameters, PerParamCache)>, + params: &Parameters, + ) -> &'cache mut PerParamCache { + // Create a new empty cache if the params changed + let current_cached_params = cache.as_ref().map(|(params, _cache)| params); + if current_cached_params != Some(params) { + *cache = Some((params.clone(), PerParamCache::default())); } + // Get the (possibly replaced) cache + &mut cache.as_mut().unwrap().1 + } - true // Can't reject composition this easily + fn get_music_count_caches( + counts: &mut HashMap, + params: &Parameters, + ) -> MusicTypeVec { + let mut caches = MusicTypeVec::new(); + for mt in ¶ms.music_types { + let count_cache = Rc::clone(counts.entry(mt.inner.clone()).or_default()); + caches.push(count_cache); + } + caches } /// If every [`MethodId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` /// where `map` maps [`MethodId`]s to their corresponding [`MethodIdx`]. Otherwise, returns /// `None`. - fn method_map( - composition: &Composition, - params: &Parameters, - ) -> Option> { - let methods = composition - .path - .iter() - .map(|e| e.method_id) - .collect::>(); - + fn method_map(params: &Parameters) -> HashMap { let mut method_map = HashMap::new(); - for id in methods { - let idx = params.methods.position(|m| m.id == id)?; - let method = ¶ms.methods[idx]; + for (idx, method) in params.methods.iter_enumerated() { let mut double_plain_course = method .plain_course() - .map_annots(|annot| (id, annot.sub_lead_idx)); + .map_annots(|annot| (method.id, annot.sub_lead_idx)); double_plain_course.extend_from_within(..); method_map.insert( - id, + method.id, MethodData { idx, double_plain_course, @@ -236,26 +257,15 @@ impl<'a> CompositionGetter<'a> { }, ); } - Some(method_map) + method_map } - /// If every [`CallId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` - /// where `map` maps [`CallId`]s to their corresponding [`CallId`]. Otherwise, returns `None`. - fn call_map( - composition: &Composition, - params: &Parameters, - ) -> Option> { - let call_ids_used = composition - .path - .iter() - .filter_map(|e| e.call_to_end) - .collect::>(); - - let mut call_map = HashMap::new(); - for id in call_ids_used { - call_map.insert(id, params.calls.position(|c| c.id == id)?); - } - Some(call_map) + fn call_map(params: &Parameters) -> HashMap { + params + .calls + .iter_enumerated() + .map(|(idx, call)| (call.id, idx)) + .collect() } /// Return a [`Block`] containing the [`Row`]s in this composition. Each [`Row`] is annotated @@ -279,27 +289,22 @@ impl<'a> CompositionGetter<'a> { /// /// If this `Composition` uses methods or calls which are not specified in the [`Parameters`], /// then `None` is returned. - fn block( - composition: &Composition, - params: &Parameters, - method_map: &HashMap, - call_map: &HashMap, - ) -> Block<(MethodId, usize)> { + pub(crate) fn block(&self, path: &[PathElem]) -> Block<(MethodId, usize)> { // Generate the first part let mut first_part = - Block::<(MethodId, usize)>::with_leftover_row(params.start_row.clone()); - for elem in &composition.path { + Block::<(MethodId, usize)>::with_leftover_row(self.params.start_row.clone()); + for elem in path { assert_eq!(first_part.leftover_row(), elem.start_row.as_row()); // Copy the corresponding part of this method's (double) plain course - let double_plain_course = &method_map[&elem.method_id].double_plain_course; + let double_plain_course = &self.method_map[&elem.method_id].double_plain_course; let start_idx = elem.start_sub_lead_idx; let end_idx = start_idx + elem.length.as_usize(); first_part.extend_range(double_plain_course, start_idx..end_idx); // If this PathElem ends in a call, then change the `leftover_row` to suit if let Some(call_id) = elem.call_to_end { - let call = ¶ms.calls[call_map[&call_id]]; let last_non_leftover_row = first_part.rows().next_back().unwrap(); - let new_leftover_row = last_non_leftover_row * call.place_notation.transposition(); + let new_leftover_row = + last_non_leftover_row * self.get_call(call_id).place_notation.transposition(); first_part.leftover_row_mut().copy_from(&new_leftover_row); } } @@ -307,54 +312,351 @@ impl<'a> CompositionGetter<'a> { // Generate the other parts from the first let part_len = first_part.len(); let mut comp = first_part; - for _ in 0..params.num_parts() - 1 { + for _ in 0..self.params.num_parts() - 1 { comp.extend_from_within(..part_len); } - assert_eq!(comp.len(), composition.length.as_usize()); - assert_eq!(comp.leftover_row(), ¶ms.end_row); + assert_eq!(comp.leftover_row(), &self.params.end_row); comp } - fn do_non_cheap_checks(&mut self) -> bool { - if !self.are_methods_satisfied() { + fn get_method(&self, id: MethodId) -> &Method { + &self.params.methods[self.method_map[&id].idx] + } + + fn get_call(&self, id: CallId) -> &Call { + &self.params.calls[self.call_map[&id]] + } +} + +////////////////////// +// CONSTRUCT GETTER // +////////////////////// + +/// Struct which combines a [`Composition`] with a set of [`Parameters`] from which the extra data +/// is taken. +#[derive(Debug)] +pub struct CompositionValues<'comp> { + composition: &'comp Composition, + + call_string: String, + method_counts: MethodVec, + music_counts: MusicTypeVec>, + music_score: f32, + total_score: f32, + atw_factor: f32, +} + +////////////////// +// CONSTRUCTION // +////////////////// + +impl CompCacheWithParams<'_, '_> { + pub fn get_comp_values<'comp>( + &self, + comp: &'comp Composition, + ) -> Option> { + CompositionValues::new(comp, self) + } +} + +impl<'comp> CompositionValues<'comp> { + fn new(composition: &'comp Composition, cache: &CompCacheWithParams) -> Option { + let validity_cache = Arc::clone(&cache.per_param_cache.is_valid); + // Sanity check that there should only be two `Arc`s for the validity cache (the one in + // `cache`, and the one we just cloned). If this is the case, then we are the only thread + // to have access to the contained `Mutex` and therefore, since we drop one guard before + // taking another, no deadlocks are possible. + assert_eq!(Arc::strong_count(&validity_cache), 2); + let is_valid = validity_cache.lock().unwrap().get(&composition.id).copied(); + match is_valid { + Some(false) => None, // Known bad; reject + Some(true) => { + // Known good; construct but skip checks + let v = Self::new_inner(composition, cache, true); + assert!(v.is_some()); + v + } + None => { + // Unknown; construct without skipping checks and save the result + let getter = Self::new_inner(composition, cache, false); + validity_cache + .lock() + .unwrap() + .insert(composition.id, getter.is_some()); + getter + } + } + } + + fn new_inner( + composition: &'comp Composition, + cache: &CompCacheWithParams, + known_good: bool, + ) -> Option { + // Do cheap checks before calculating anything. This is useful because the search + // algorithm produces tons of too-short compositions, so it's worth validating length + // quickly and thus rejecting these. + if !known_good && !Self::do_cheap_checks(composition, &cache.params) { + return None; + } + + // Once the cheap checks pass, compute useful cached values and build the `getter` + let music_counts = composition.music_counts(cache); + let music_score = music_counts_to_score(&music_counts, cache.params); + // TODO: Cache these + let call_string = composition.call_string(cache); + let atw_factor = composition.atw_factor(cache); + let total_score = composition.total_score(music_score, atw_factor, cache); + let method_counts = composition.method_counts(cache); + let mut values = Self { + composition, + + call_string, + method_counts, + music_counts, + music_score, + atw_factor, + total_score, + }; + + if !known_good && !values.do_non_cheap_checks(cache) { + return None; + } + + Some(values) // If all checks didn't find problems, the getter is valid + } +} + +impl Composition { + /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything + /// was rung (i.e. the composition is atw) + fn atw_factor(&self, cache: &CompCacheWithParams) -> f32 { + // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible + let total_unique_row_positions = cache.working_bells.len() // Working bells + * cache.working_bells.len() // Working place bells + * cache.params.methods.iter().map(|m| m.lead_len()).sum::(); + // Determine how many we actuall rang + let run_unique_row_positions = cache + .working_bells + .iter() + .map(|b| self.unique_place_bell_rows_per_bell[b.index()]) + .sum::(); + + run_unique_row_positions as f32 / total_unique_row_positions as f32 + } + + /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. + fn music_counts<'a>(&self, cache: &CompCacheWithParams) -> MusicTypeVec> { + let mut cached_block: Option> = None; + + let mut counts = MusicTypeVec::new(); + for (idx, mt) in cache.params.music_types.iter_enumerated() { + let count = *cache.music_counts[idx] + .borrow_mut() + .entry(self.id) + .or_insert_with(|| { + if cached_block.is_none() { + cached_block = Some(cache.block(&self.path)); + } + let block = cached_block.as_ref().unwrap(); + + mt.count(block, !self.start_stroke) + }); + counts.push(count); + } + counts + } + + /// The total score generated by this composition from all the different weights (music, calls, + /// changes of method, handbell coursing, etc.). + fn total_score(&self, music_score: f32, atw_factor: f32, cache: &CompCacheWithParams) -> f32 { + let mut total_score = 0.0; + // Music + total_score += music_score; + // ATW + if let Some(atw_weight) = cache.params.atw_weight { + total_score += atw_factor * atw_weight; + } + // Calls + for elem in &self.path { + if let Some(call_id) = elem.call_to_end { + let call = cache.get_call(call_id); + total_score += call.weight * cache.params.num_parts() as f32; + } + } + // Splices + let mut changes_of_method = 0; + for (e1, e2) in self.path.iter().tuple_windows() { + if e1.is_splice_when_followed_by(e2, cache.params) { + changes_of_method += cache.params.num_parts(); + } + } + let first_elem = self.path.first().unwrap(); + let last_elem = self.path.last().unwrap(); + if last_elem.is_splice_when_followed_by(first_elem, cache.params) { + // -1 because there's no splice around the end/start of the composition + changes_of_method += cache.params.num_parts() - 1; + } + total_score += changes_of_method as f32 * cache.params.splice_weight; + // Course weights + // TODO: Cache this + for elem in &self.path { + let lead_head_weights = &cache.method_map[&elem.method_id].lead_head_weights; + let lead_head = elem.lead_head(&cache.method_map); + for part_head in self.part_head.closure() { + let lead_head_in_part = part_head * &lead_head; + for (mask, weight) in lead_head_weights { + if mask.matches(&lead_head_in_part) { + total_score += *weight * elem.length.as_usize() as f32; + } + } + } + } + total_score + } + + /// Generate a human-friendly [`String`] summarising the calling of this composition. For + /// example, [this composition](https://complib.org/composition/87419) would have a + /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. + pub fn call_string(&self, cache: &CompCacheWithParams) -> String { + let params = &cache.params; + + let needs_brackets = + params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional; + let is_snap_start = self.path[0].start_sub_lead_idx > 0; + let is_snap_finish = self.path.last().unwrap().end_sub_lead_idx(params) > 0; + + let mut path_iter = self.path.iter().peekable(); + + let mut s = String::new(); + if params.call_display_style == CallDisplayStyle::Positional { + s.push('#'); + } + s.push_str(if is_snap_start { "<" } else { "" }); + while let Some(path_elem) = path_iter.next() { + // Method text + if params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional { + // Add one shorthand for every lead *covered* (not number of lead heads reached) + // + // TODO: Deal with half-lead spliced + let method = cache.get_method(path_elem.method_id); + let num_leads_covered = num_leads_covered( + method.lead_len(), + path_elem.start_sub_lead_idx, + path_elem.length, + ); + for _ in 0..num_leads_covered { + s.push_str(&method.shorthand()); + } + } + // Call text + if let Some(call_id) = path_elem.call_to_end { + let call = cache.get_call(call_id); + s.push_str(if needs_brackets { "[" } else { "" }); + // Call position + match params.call_display_style { + CallDisplayStyle::CallingPositions(calling_bell) => { + let row_after_call = path_iter + .peek() + .map_or(&self.part_head, |path_elem| &path_elem.start_row); + let place_of_calling_bell = row_after_call.place_of(calling_bell).unwrap(); + let calling_position = &call.calling_positions[place_of_calling_bell]; + s.push_str(call.short_symbol()); + s.push_str(calling_position); + } + // TODO: Compute actual counts for positional calls + CallDisplayStyle::Positional => s.push_str(&call.symbol), + } + s.push_str(if needs_brackets { "]" } else { "" }); + } + } + s.push_str(if is_snap_finish { ">" } else { "" }); + + s + } + + /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the + /// [`Search`]. These are stored in the same order as the [`Method`]s. + fn method_counts(&self, cache: &CompCacheWithParams) -> MethodVec { + let mut method_counts = + index_vec::index_vec![TotalLength::ZERO; cache.params.methods.len()]; + for elem in &self.path { + let idx = cache.method_map[&elem.method_id].idx; + method_counts[idx] += elem.length.as_total(&cache.params.part_head_group); + } + method_counts + } +} + +//////////////// +// VALIDATION // +//////////////// + +impl<'comp> CompositionValues<'comp> { + /// Perform cheap checks on this composition which don't involve looking up methods or calls. + /// The main reason to do this is to quickly reject compositions which are being generated by the + /// search routine + #[must_use] + fn do_cheap_checks(composition: &Composition, params: &Parameters) -> bool { + if composition.stage != params.stage { + return false; // Stage mismatch + } + if !params.length.contains(&composition.length) { + return false; // Length mismatch + } + if !params + .part_head_group + .is_row_generator(&composition.part_head) + { + return false; // Composition doesn't end in a valid part + } + if composition.path[0].start_row != params.start_row { + return false; // Doesn't start in the right row + } + + true // Can't reject composition this easily + } + + fn do_non_cheap_checks(&mut self, cache: &CompCacheWithParams) -> bool { + if !self.are_methods_satisfied(cache) { return false; } - if !self.is_splice_style_satisfied() { + if !self.is_splice_style_satisfied(cache) { return false; } - if self.params.require_atw && !self.is_atw() { + if cache.params.require_atw && !self.is_atw() { return false; } - if self.params.require_truth && !self.block.is_true() { + if cache.params.require_truth && !self.is_true() { return false; // Composition is false but we needed it to be true } - if self.block.leftover_row() != &self.params.end_row { + if &self.composition.end_row != &cache.params.end_row { return false; // Comps ends on the wrong row } - for (mt, counts) in self.params.music_types.iter().zip_eq(self.music_counts()) { - let total = mt.masked_total(counts); + for (mt, counts) in cache.params.music_types.iter().zip_eq(&self.music_counts) { + let total = mt.masked_total(*counts); if !mt.count_range.contains(total) { return false; // Music count range isn't satisfied } } // Start indices let first_elem = &self.composition.path[0]; - let start_indices = self + let start_indices = cache .get_method(first_elem.method_id) - .wrapped_indices(Boundary::Start, self.params); + .wrapped_indices(Boundary::Start, cache.params); if !start_indices.contains(&first_elem.start_sub_lead_idx) { return false; // Composition couldn't start in this way } // End indices let last_elem = self.composition.path.last().unwrap(); - let end_sub_lead_idx = last_elem.end_sub_lead_idx(self.params); + let end_sub_lead_idx = last_elem.end_sub_lead_idx(cache.params); match last_elem.call_to_end { None => { // The last element ends with a plain lead, so we need to check that this chunk's // end sub-lead index is valid - let end_indices = self + let end_indices = cache .get_method(last_elem.method_id) - .wrapped_indices(Boundary::End, self.params); + .wrapped_indices(Boundary::End, cache.params); if !end_indices.contains(&end_sub_lead_idx) { return false; // End index isn't valid for this method } @@ -363,19 +665,19 @@ impl<'a> CompositionGetter<'a> { // If the composition ends with a call, then the situation is more complex; we need // to check that the call leads to a method which could end immediately // (conceptually, this introduces an imaginary 0-length 'path-elem' at the end) - let end_label = &self.get_call(call_id).label_to; - if !self.valid_end_labels.contains(end_label) { + let end_label = &cache.get_call(call_id).label_to; + if !cache.valid_end_labels.contains(end_label) { return false; // Call's label_to can't correspond to a valid end idx } } } // Check for continuity over the part head (this checks for cases like finishing each part // at a snap and then starting the next part at the lead-end) - if self.params.is_multipart() { - let start_labels = self + if cache.params.is_multipart() { + let start_labels = cache .get_method(first_elem.method_id) .get_labels(first_elem.start_sub_lead_idx); - let end_labels = self + let end_labels = cache .get_method(last_elem.method_id) .get_labels(end_sub_lead_idx); let is_splice_possible = start_labels.iter().any(|label| end_labels.contains(label)); @@ -392,21 +694,18 @@ impl<'a> CompositionGetter<'a> { true // Composition is all OK } - fn are_methods_satisfied(&self) -> bool { - let allowed_lead_heads: MethodVec> = self + fn are_methods_satisfied(&self, cache: &CompCacheWithParams) -> bool { + let allowed_lead_heads: MethodVec> = cache .params .methods .iter() - .map(|m| m.allowed_lead_head_masks(self.params)) + .map(|m| m.allowed_lead_head_masks(cache.params)) .collect(); - let mut method_counts: MethodVec<_> = - index_vec::index_vec![TotalLength::ZERO; self.params.methods.len()]; for path_elem in &self.composition.path { - let method_idx = self.method_map[&path_elem.method_id].idx; - method_counts[method_idx] += path_elem.length.as_total(&self.params.part_head_group); + let method_idx = cache.method_map[&path_elem.method_id].idx; // Check if lead head is valid - let lead_head = path_elem.lead_head(&self.method_map); + let lead_head = path_elem.lead_head(&cache.method_map); if !allowed_lead_heads[method_idx] .iter() .any(|m| m.matches(&lead_head)) @@ -420,20 +719,20 @@ impl<'a> CompositionGetter<'a> { true } - fn is_splice_style_satisfied(&self) -> bool { - match self.params.splice_style { + fn is_splice_style_satisfied(&self, cache: &CompCacheWithParams) -> bool { + match cache.params.splice_style { SpliceStyle::LeadLabels => true, // Assume all comps are still valid SpliceStyle::Calls => { let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { // PERF: use the method map to speed up the splicing check - e1.is_splice_when_followed_by(e2, self.params) && e1.ends_with_plain() + e1.is_splice_when_followed_by(e2, cache.params) && e1.ends_with_plain() }; for (elem1, elem2) in self.composition.path.iter().tuple_windows() { if is_invalid_splice(elem1, elem2) { return false; // Splice but no call } } - if self.params.is_multipart() && !self.composition.path.is_empty() { + if cache.params.is_multipart() && !self.composition.path.is_empty() { let first = self.composition.path.first().unwrap(); let last = self.composition.path.last().unwrap(); if is_invalid_splice(last, first) { @@ -445,255 +744,70 @@ impl<'a> CompositionGetter<'a> { } } - /* GETTERS */ + ///////////// + // GETTERS // + ///////////// /// The number of [`Row`]s in this composition. pub fn length(&self) -> usize { self.composition.length.as_usize() } + pub fn call_string(&self) -> &str { + &self.call_string + } + pub fn part_head(&self) -> &Row { &self.composition.part_head } pub fn is_true(&self) -> bool { - self.block.is_true() - } - - /// Generate a human-friendly [`String`] summarising the calling of this composition. For - /// example, [this composition](https://complib.org/composition/87419) would have a - /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. - pub fn call_string(&self) -> String { - let Self { - composition, - params, - .. - } = self; - - let needs_brackets = - params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional; - let is_snap_start = composition.path[0].start_sub_lead_idx > 0; - let is_snap_finish = composition.path.last().unwrap().end_sub_lead_idx(params) > 0; - - let mut path_iter = composition.path.iter().peekable(); - - let mut s = String::new(); - if params.call_display_style == CallDisplayStyle::Positional { - s.push('#'); - } - s.push_str(if is_snap_start { "<" } else { "" }); - while let Some(path_elem) = path_iter.next() { - // Method text - if params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional { - // Add one shorthand for every lead *covered* (not number of lead heads reached) - // - // TODO: Deal with half-lead spliced - let method = self.get_method(path_elem.method_id); - let num_leads_covered = num_leads_covered( - method.lead_len(), - path_elem.start_sub_lead_idx, - path_elem.length, - ); - for _ in 0..num_leads_covered { - s.push_str(&method.shorthand()); - } - } - // Call text - if let Some(call_id) = path_elem.call_to_end { - let call = self.get_call(call_id); - s.push_str(if needs_brackets { "[" } else { "" }); - // Call position - match params.call_display_style { - CallDisplayStyle::CallingPositions(calling_bell) => { - let row_after_call = path_iter - .peek() - .map_or(&composition.part_head, |path_elem| &path_elem.start_row); - let place_of_calling_bell = row_after_call.place_of(calling_bell).unwrap(); - let calling_position = &call.calling_positions[place_of_calling_bell]; - s.push_str(call.short_symbol()); - s.push_str(calling_position); - } - // TODO: Compute actual counts for positional calls - CallDisplayStyle::Positional => s.push_str(&call.symbol), - } - s.push_str(if needs_brackets { "]" } else { "" }); - } - } - s.push_str(if is_snap_finish { ">" } else { "" }); - - s + self.composition.truth.is_true() } /// The average score generated by each [`Row`] in this composition. This is equal to /// `self.total_score() / self.length() as f32`. - pub fn score_per_row(&mut self) -> f32 { + pub fn score_per_row(&self) -> f32 { self.total_score() / self.length() as f32 } - /// The total score generated by this composition from all the different weights (music, calls, - /// changes of method, handbell coursing, etc.). - pub fn total_score(&mut self) -> f32 { - let mut total_score = 0.0; - // Music - total_score += self.music_score(); - // ATW - if let Some(atw_weight) = self.params.atw_weight { - total_score += self.atw_factor() * atw_weight; - } - // Calls - for elem in &self.composition.path { - if let Some(call_id) = elem.call_to_end { - let call = self.get_call(call_id); - total_score += call.weight * self.params.num_parts() as f32; - } - } - // Splices - let mut changes_of_method = 0; - for (e1, e2) in self.composition.path.iter().tuple_windows() { - if e1.is_splice_when_followed_by(e2, self.params) { - changes_of_method += self.params.num_parts(); - } - } - let first_elem = self.composition.path.first().unwrap(); - let last_elem = self.composition.path.last().unwrap(); - if last_elem.is_splice_when_followed_by(first_elem, self.params) { - // -1 because there's no splice around the end/start of the composition - changes_of_method += self.params.num_parts() - 1; - } - total_score += changes_of_method as f32 * self.params.splice_weight; - // Course weights - for elem in &self.composition.path { - let lead_head_weights = &self.method_map[&elem.method_id].lead_head_weights; - let lead_head = elem.lead_head(&self.method_map); - for part_head in self.composition.part_head.closure() { - let lead_head_in_part = part_head * &lead_head; - for (mask, weight) in lead_head_weights { - if mask.matches(&lead_head_in_part) { - total_score += *weight * elem.length.as_usize() as f32; - } - } - } - } - total_score + pub fn total_score(&self) -> f32 { + self.total_score } pub fn is_atw(&self) -> bool { self.atw_factor() == 1.0 } - /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything - /// was rung (i.e. the composition is atw) pub fn atw_factor(&self) -> f32 { - // Determine the working bells, and store this in a bitmap - let working_bells = self.params.working_bells(); - let mut is_working = vec![false; self.composition.stage.num_bells()]; - for b in &working_bells { - is_working[b.index()] = true; - } - // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible - let total_unique_row_positions = working_bells.len() // Working bells - * working_bells.len() // Working place bells - * self.params.methods.iter().map(|m| m.lead_len()).sum::(); - // Compute which `(bell, place, method, sub-lead-index)` quadruples have been rung in this - // composition. - // - // TODO: Use a more efficient storage method for common cases like ringing whole leads - let mut rung_place_bell_positions = HashSet::<(Bell, u8, MethodId, usize)>::new(); - for (&(method_id, sub_lead_idx), row) in self.block.annot_rows() { - for (place, bell) in row.bell_iter().enumerate() { - if is_working[bell.index()] { - rung_place_bell_positions.insert((bell, place as u8, method_id, sub_lead_idx)); - } - } - } - rung_place_bell_positions.len() as f32 / total_unique_row_positions as f32 + self.atw_factor } - /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the - /// [`Search`]. These are stored in the same order as the [`Method`]s. - pub fn method_counts(&self) -> MethodVec { - let mut method_counts = index_vec::index_vec![TotalLength::ZERO; self.params.methods.len()]; - for elem in &self.composition.path { - let idx = self.method_map[&elem.method_id].idx; - method_counts[idx] += elem.length.as_total(&self.params.part_head_group); - } - method_counts + pub fn music_score(&self) -> f32 { + self.music_score } - /// Compute [`Self::music_counts`] and use it to calculate [`Self::music_score`]. Calling - /// them separately would cause the music to be calculated twice. - pub fn music_counts_and_score(&mut self) -> (MusicTypeVec>, f32) { - let music_counts = self.music_counts(); - let mut music_score = 0.0; - for (count, music_type) in music_counts.iter().zip_eq(&self.params.music_types) { - music_score += music_type.as_overall_score(*count); - } - (music_counts, music_score) + pub fn music_counts(&self) -> &MusicTypeVec> { + &self.music_counts } - /// Score generated by just the [`MusicType`]s (not including calls, changes of methods, - /// etc.). - pub fn music_score(&mut self) -> f32 { - self.music_counts_and_score().1 - } - - /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. - pub fn music_counts(&mut self) -> MusicTypeVec> { - let mut counts = MusicTypeVec::new(); - for mt in &self.params.music_types { - let count = self - .cache - .music_counts - .entry((mt.inner.clone(), self.composition.id)) - .or_insert_with(|| mt.count(&self.block, !self.composition.start_stroke)); - counts.push(*count); - } - counts - } - - fn get_method(&self, id: MethodId) -> &Method { - &self.params.methods[self.method_map[&id].idx] - } - - fn get_call(&self, id: CallId) -> &Call { - &self.params.calls[self.call_map[&id]] + pub fn method_counts(&self) -> &MethodVec { + &self.method_counts } } -///////////// -// CACHING // -///////////// - -/// Struct which caches expensive properties about [`Composition`]s, so that these values will be -/// calculated once and then re-used for future queries. -#[derive(Debug, Default)] -pub struct CompositionDataCache { - per_param_cache: Option<(Parameters, PerParamCache)>, - music_counts: HashMap<(bellframe::MusicType, CompositionId), AtRowPositions>, -} - -impl CompositionDataCache { - fn get_per_param_cache(&mut self, params: &Parameters) -> &mut PerParamCache { - // Create a new empty cache if the params changed - let current_cached_params = self.per_param_cache.as_ref().map(|(params, _cache)| params); - if current_cached_params != Some(params) { - self.per_param_cache = Some((params.clone(), PerParamCache::default())); - } - // Get the (possibly replaced) cache - &mut self.per_param_cache.as_mut().unwrap().1 - } -} - -#[derive(Debug, Default)] -struct PerParamCache { - // TODO: Find a way of doing this without Arc> - is_valid: Arc>>, -} - /////////// // UTILS // /////////// +fn music_counts_to_score(counts: &MusicTypeVec>, params: &Parameters) -> f32 { + let mut music_score = 0.0; + for (count, music_type) in counts.iter().zip_eq(¶ms.music_types) { + music_score += music_type.as_overall_score(*count); + } + music_score +} + /// Return the number of leads covered by some [`Chunk`] fn num_leads_covered(lead_len: usize, start_sub_lead_idx: usize, length: PerPartLength) -> usize { assert_ne!(length, PerPartLength::ZERO); // 0-length chunks shouldn't exist diff --git a/monument/lib/src/lib.rs b/monument/lib/src/lib.rs index 08bfed3e..ee379e5d 100644 --- a/monument/lib/src/lib.rs +++ b/monument/lib/src/lib.rs @@ -50,7 +50,7 @@ mod prove_length; mod search; pub mod utils; -pub use composition::{Composition, CompositionGetter}; +pub use composition::Composition; pub use error::{Error, Result}; pub use group::{PartHead, PartHeadGroup, PhRotation}; pub use parameters::Parameters; diff --git a/monument/lib/src/search/best_first.rs b/monument/lib/src/search/best_first.rs index ac898c85..633ce86f 100644 --- a/monument/lib/src/search/best_first.rs +++ b/monument/lib/src/search/best_first.rs @@ -9,7 +9,7 @@ use std::{ use datasize::DataSize; use ringing_utils::BigNumInt; -use crate::{composition::CompositionDataCache, utils::lengths::TotalLength}; +use crate::{composition::CompositionCache, utils::lengths::TotalLength}; use super::{path::Paths, prefix::CompPrefix, Progress, Search, Update}; @@ -23,7 +23,7 @@ pub(crate) fn search( search: &Search, mut update_fn: impl FnMut(Update), abort_flag: &AtomicBool, - cache: &Mutex, + cache: &Mutex, ) { let mem_limit = search .config diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index df99c28e..47b58ecd 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -19,7 +19,7 @@ use bellframe::Stage; use itertools::Itertools; use crate::{ - composition::{CompositionDataCache, CompositionId}, + composition::{CompositionCache, CompositionId}, parameters::{MethodId, Parameters}, prove_length::{prove_lengths, RefinedRanges}, utils::IdGenerator, @@ -95,7 +95,7 @@ impl Search { &self, update_fn: impl FnMut(Update), abort_flag: &AtomicBool, - comp_data_cache: &Mutex, + comp_data_cache: &Mutex, ) { // Make sure that `abort_flag` starts as false (so the search doesn't abort immediately). // We want this to be sequentially consistent to make sure that the worker threads don't diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 70eb27d6..49bebbd5 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -5,7 +5,7 @@ use datasize::DataSize; use ordered_float::OrderedFloat; use crate::{ - composition::{Composition, CompositionDataCache, CompositionGetter, PathElem}, + composition::{Composition, CompositionCache, PathElem}, graph::LinkSide, group::PartHead, utils::{counts::Counts, div_rounding_up, lengths::TotalLength}, @@ -148,7 +148,7 @@ impl CompPrefix { search: &Search, paths: &mut Paths, frontier: &mut BinaryHeap, - cache: &Mutex, + cache: &Mutex, ) -> Option { // Determine the chunk being expanded (or if it's an end, complete the composition) let chunk_idx = match self.next_link_side { @@ -243,7 +243,7 @@ impl CompPrefix { &self, search: &Search, paths: &Paths, - cache: &Mutex, + cache: &Mutex, ) -> Option { assert!(self.next_link_side.is_start_or_end()); @@ -268,37 +268,26 @@ impl CompPrefix { /* At this point, all checks on the composition have passed and we know it satisfies the * user's parameters */ + let mut binding = cache.lock().unwrap(); + let cache = binding.with_params(&search.params); + // Now we know the composition is valid, construct it and return let path = self.flattened_path(search, paths); - let comp = Composition { - id: search.id_generator.next(), - stage: search.params.stage, - start_stroke: search.params.start_stroke, - path, - - part_head: search - .params - .part_head_group - .get_row(self.part_head) - .to_owned(), - length: self.length, - }; + let composition = + Composition::new(search.id_generator.next(), path, self.part_head, &cache); // Validate the composition by building a `CompositionGetter`. The checks performed by // `CompositionGetter::new` are much stricter and more correct than those we can perform // here, so we defer entirely to it to check these candidate compositions for validity. - { - let mut cache = cache.lock().unwrap(); - let comp_getter = CompositionGetter::new(&comp, &search.params, &mut cache)?; - // Sanity check that the composition is true - if search.params.require_truth && !comp_getter.is_true() { - panic!( - "Generated false composition ({})", - comp_getter.call_string() - ); - } + let comp_values = cache.get_comp_values(&composition)?; + // Sanity check that the composition is true + if search.params.require_truth && !comp_values.is_true() { + panic!( + "Generated false composition ({})", + comp_values.call_string() + ); } // Finally, return the comp - Some(comp) + Some(composition) } /// Create a sequence of [`ChunkId`]/[`LinkId`]s by traversing the [`Graph`] following the From 1c346f3ef2d4ef949c1160ebc1d4518b39bb0fab Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 24 Dec 2023 18:22:42 +0000 Subject: [PATCH 23/29] Temporarily remove caching code --- monument/cli/src/lib.rs | 15 +- monument/cli/src/logging.rs | 59 ++-- monument/gui/src/search.rs | 1 - monument/lib/src/composition.rs | 478 ++++++++++---------------- monument/lib/src/search/atw.rs | 1 + monument/lib/src/search/best_first.rs | 25 +- monument/lib/src/search/graph.rs | 3 + monument/lib/src/search/mod.rs | 42 +-- monument/lib/src/search/prefix.rs | 21 +- 9 files changed, 245 insertions(+), 400 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index 3418ee8d..a043197d 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -17,13 +17,13 @@ use std::{ str::FromStr, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Arc, }, time::{Duration, Instant}, }; use log::LevelFilter; -use monument::{composition::CompositionCache, Composition, Search}; +use monument::{composition::ParamsData, Composition, Search}; use ordered_float::OrderedFloat; use ringing_utils::PrettyDuration; use simple_logger::SimpleLogger; @@ -76,10 +76,8 @@ pub fn run( debug_print!(Search, search); // Build all the data structures for the search - let comp_cache = Arc::new(Mutex::new(CompositionCache::default())); let comp_printer = CompositionPrinter::new( - search.clone(), - comp_cache.clone(), + &search, toml_file.should_print_atw(), !options.dont_display_comp_numbers, ); @@ -111,7 +109,6 @@ pub fn run( } }, &abort_flag, - &comp_cache, ); // Once the search has completed, sort the compositions and return @@ -120,17 +117,15 @@ pub fn run( let rounded = (f / FACTOR).round() * FACTOR; OrderedFloat(rounded) } - let mut cache_guard = comp_cache.lock().unwrap(); - let cache_with_params = cache_guard.with_params(¶ms); + let params_data = ParamsData::new(¶ms); comps.sort_by_cached_key(|(comp, _generation_index)| { - let getter = cache_with_params.get_comp_values(comp).unwrap(); + let getter = comp.values(¶ms_data).unwrap(); ( rounded_float(getter.music_score()), rounded_float(getter.score_per_row()), getter.call_string().to_owned(), ) }); - drop(cache_guard); Ok(Some(SearchResult { comps, comp_printer, diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index 351787f2..8197b2b2 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -1,16 +1,12 @@ //! Code for handling the logging of compositions or updates provided by Monument -use std::{ - fmt::Write, - io::Write as IoWrite, - sync::{Arc, Mutex}, -}; +use std::{fmt::Write, io::Write as IoWrite}; use bellframe::row::ShortRow; use colored::Colorize; use itertools::Itertools; use log::log_enabled; -use monument::{composition::CompositionCache, Composition, Progress, Search, Update}; +use monument::{composition::ParamsData, Composition, Parameters, Progress, Search, Update}; use ringing_utils::BigNumInt; /// Struct which handles logging updates, keeping the updates to a single line which updates as the @@ -128,8 +124,7 @@ impl SingleLineProgressLogger { #[derive(Debug, Clone)] pub struct CompositionPrinter { - search: Arc, - cache: Arc>, + params: Parameters, // TODO: Make this `ParamsData` /// Counter which records how many compositions have been printed so far comps_printed: usize, @@ -154,19 +149,16 @@ pub struct CompositionPrinter { } impl CompositionPrinter { - pub fn new( - search: Arc, - cache: Arc>, - print_atw: bool, - print_comp_widths: bool, - ) -> Self { + pub fn new(search: &Search, print_atw: bool, print_comp_widths: bool) -> Self { + let params = search.parameters().clone(); Self { - comp_count_width: print_comp_widths - .then_some(search.parameters().num_comps.to_string().len()), - length_width: search.parameters().max_length().to_string().len().max(3), - method_count_widths: search - .methods() - .map(|(method, shorthand)| { + comp_count_width: print_comp_widths.then_some(params.num_comps.to_string().len()), + length_width: params.max_length().to_string().len().max(3), + method_count_widths: params + .methods + .iter() + .map(|method| { + let shorthand = method.shorthand(); let max_count_width = search.method_count_range(method.id).end().to_string().len(); let max_width = max_count_width.max(shorthand.len()); @@ -176,11 +168,10 @@ impl CompositionPrinter { comps_printed: 0, print_atw, - part_head_width: (search.num_parts() > 2) - .then(|| search.effective_part_head_stage().num_bells()), + part_head_width: (params.num_parts() > 2) + .then(|| params.part_head_group.effective_stage().num_bells()), - search, - cache, + params, } } @@ -266,15 +257,18 @@ impl CompositionPrinter { s.push('|'); } // Music - let params = self.search.parameters(); - let music_types_to_display = params.music_types_to_show(); + let music_types_to_display = self.params.music_types_to_show(); s.push_str(" music "); if !music_types_to_display.is_empty() { s.push(' '); } for (_idx, music_type) in music_types_to_display { s.push_str(" "); - write_centered_text(&mut s, &music_type.name, music_type.col_width(params.stage)); + write_centered_text( + &mut s, + &music_type.name, + music_type.col_width(self.params.stage), + ); s.push(' '); } // Everything else @@ -283,9 +277,7 @@ impl CompositionPrinter { } fn comp_string(&self, comp: &Composition, generation_index: usize) -> String { - let mut cache = self.cache.lock().unwrap(); - let cache_with_params = cache.with_params(&self.search.parameters()); - let comp = cache_with_params.get_comp_values(comp).unwrap(); + let comp = comp.values(&ParamsData::new(&self.params)).unwrap(); let mut s = String::new(); // Comp index @@ -317,8 +309,7 @@ impl CompositionPrinter { write!(s, " {} |", ShortRow(comp.part_head())).unwrap(); } // Music - let params = self.search.parameters(); - let music_types_to_show = params.music_types_to_show(); + let music_types_to_show = self.params.music_types_to_show(); let music_counts = comp.music_counts(); write!(s, " {:>7.2} ", comp.music_score()).unwrap(); if !music_types_to_show.is_empty() { @@ -328,8 +319,8 @@ impl CompositionPrinter { s.push_str(" "); write_left_centered_text( &mut s, - &music_type.display_counts(music_counts[idx], params.stage), - music_type.col_width(params.stage), + &music_type.display_counts(music_counts[idx], self.params.stage), + music_type.col_width(self.params.stage), ); s.push(' '); } diff --git a/monument/gui/src/search.rs b/monument/gui/src/search.rs index f03193c6..02573bac 100644 --- a/monument/gui/src/search.rs +++ b/monument/gui/src/search.rs @@ -65,7 +65,6 @@ fn worker_thread( ctx.request_repaint(); }, &abort_flag, - &comp_cache, ); println!("Finished query, waiting for next one"); } diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 6645996c..91b50091 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -1,11 +1,8 @@ //! Representation of a [`Composition`] generated by Monument. use std::{ - cell::RefCell, collections::{HashMap, HashSet}, - hash::Hash, - rc::Rc, - sync::{Arc, Mutex}, + ops::Deref, }; use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke, Truth}; @@ -60,19 +57,20 @@ impl Composition { id: CompositionId, path: Vec, part_head: PartHead, - cache: &CompCacheWithParams, + param_data: &ParamsData, ) -> Self { - let block = cache.block(&path); + let params = ¶m_data.params; + let block = param_data.get_block(&path); Self { id, - stage: cache.params.stage, - start_stroke: cache.params.start_stroke, + stage: params.stage, + start_stroke: params.start_stroke, path, truth: block.truth(), - part_head: cache.params.part_head_group.get_row(part_head).to_owned(), + part_head: params.part_head_group.get_row(part_head).to_owned(), unique_place_bell_rows_per_bell: Self::unique_place_bell_rows_per_bell(&block), - end_row: cache.params.end_row.clone(), + end_row: params.end_row.clone(), length: TotalLength::new(block.len()), } } @@ -105,10 +103,10 @@ impl PathElem { .add_sub_lead_idx(self.start_sub_lead_idx, self.length) } - /// Returns true if going from `self` to `next` would be considered a 'splice' - fn is_splice_when_followed_by(&self, next: &Self, params: &Parameters) -> bool { - let is_continuation = self.method_id == next.method_id - && self.end_sub_lead_idx(params) == next.start_sub_lead_idx; + /// Returns true if going from `from` to `to` would be considered a 'splice' + fn is_splice_between(from: &Self, to: &Self, params: &Parameters) -> bool { + let is_continuation = from.method_id == to.method_id + && from.end_sub_lead_idx(params) == to.start_sub_lead_idx; !is_continuation } @@ -136,46 +134,23 @@ impl From for u32 { } } -///////////// -// CACHING // -///////////// - -type PerMusicTypeCache = Rc>>>; - -/// Struct which caches expensive properties about [`Composition`]s, so that these values will be -/// calculated once and then re-used for future queries. -#[derive(Debug, Default)] -pub struct CompositionCache { - per_param_cache: Option<(Parameters, PerParamCache)>, - music_counts: HashMap, -} +//////////////// +// PARAM DATA // +//////////////// -#[derive(Debug, Default)] -struct PerParamCache { - // TODO: Find a way of doing this without Arc> - is_valid: Arc>>, -} +// TODO: Move this section to `crate::params` -/// A variant of [`CompositionCache`] with extra cached data specific to the [`Parameters`]. -/// -/// Constructing a `CompCacheWithParams` is much more expensive than using it, so it is intended -/// to be constructed once and then applied to many [`Composition`]s. +/// Data generated and cached purely from [`Parameters`] #[derive(Debug)] -pub struct CompCacheWithParams<'params, 'cache> { +pub struct ParamsData<'params> { params: &'params Parameters, - /* DATA CACHED FROM PARAMS */ method_map: HashMap, call_map: HashMap, working_bells: Vec, /// Set of labels at which the composition can end. I.e. these are labels which are also a /// valid `end_index` for some method. valid_end_labels: HashSet, - - /* DATA BORROWED FROM COMP CACHE */ - per_param_cache: &'cache PerParamCache, - // PERF: Push the RefCell out of here, so we don't have to keep checking for valid borrows - music_counts: MusicTypeVec, } #[derive(Debug, Clone)] @@ -185,57 +160,16 @@ struct MethodData { lead_head_weights: Vec<(Mask, f32)>, } -impl CompositionCache { - pub fn with_params<'params, 'cache>( - &'cache mut self, - params: &'params Parameters, - ) -> CompCacheWithParams<'params, 'cache> { - CompCacheWithParams::new(self, params) - } -} - -impl<'params, 'cache> CompCacheWithParams<'params, 'cache> { - fn new(cache: &'cache mut CompositionCache, params: &'params Parameters) -> Self { - let CompositionCache { - per_param_cache, - music_counts, - } = cache; +impl<'params> ParamsData<'params> { + pub fn new(params: &'params Parameters) -> Self { + ParamsData { + params, - Self { method_map: Self::method_map(params), call_map: Self::call_map(params), valid_end_labels: params.valid_end_labels(), working_bells: params.working_bells(), - - per_param_cache: Self::get_or_reset_per_param_cache(per_param_cache, params), - music_counts: Self::get_music_count_caches(music_counts, params), - params, - } - } - - fn get_or_reset_per_param_cache( - cache: &'cache mut Option<(Parameters, PerParamCache)>, - params: &Parameters, - ) -> &'cache mut PerParamCache { - // Create a new empty cache if the params changed - let current_cached_params = cache.as_ref().map(|(params, _cache)| params); - if current_cached_params != Some(params) { - *cache = Some((params.clone(), PerParamCache::default())); } - // Get the (possibly replaced) cache - &mut cache.as_mut().unwrap().1 - } - - fn get_music_count_caches( - counts: &mut HashMap, - params: &Parameters, - ) -> MusicTypeVec { - let mut caches = MusicTypeVec::new(); - for mt in ¶ms.music_types { - let count_cache = Rc::clone(counts.entry(mt.inner.clone()).or_default()); - caches.push(count_cache); - } - caches } /// If every [`MethodId`] in this `Composition` is in the [`Parameters`], returns `Some(map)` @@ -289,7 +223,7 @@ impl<'params, 'cache> CompCacheWithParams<'params, 'cache> { /// /// If this `Composition` uses methods or calls which are not specified in the [`Parameters`], /// then `None` is returned. - pub(crate) fn block(&self, path: &[PathElem]) -> Block<(MethodId, usize)> { + fn get_block(&self, path: &[PathElem]) -> Block<(MethodId, usize)> { // Generate the first part let mut first_part = Block::<(MethodId, usize)>::with_leftover_row(self.params.start_row.clone()); @@ -328,9 +262,17 @@ impl<'params, 'cache> CompCacheWithParams<'params, 'cache> { } } -////////////////////// -// CONSTRUCT GETTER // -////////////////////// +impl<'p> Deref for ParamsData<'p> { + type Target = Parameters; + + fn deref(&self) -> &Self::Target { + self.params + } +} + +///////////////// +// COMP VALUES // +///////////////// /// Struct which combines a [`Composition`] with a set of [`Parameters`] from which the extra data /// is taken. @@ -342,185 +284,50 @@ pub struct CompositionValues<'comp> { method_counts: MethodVec, music_counts: MusicTypeVec>, music_score: f32, - total_score: f32, atw_factor: f32, + // TODO: Replace this with a simple getter + total_score: f32, } -////////////////// -// CONSTRUCTION // -////////////////// +impl Deref for CompositionValues<'_> { + type Target = Composition; -impl CompCacheWithParams<'_, '_> { - pub fn get_comp_values<'comp>( - &self, - comp: &'comp Composition, - ) -> Option> { - CompositionValues::new(comp, self) + fn deref(&self) -> &Self::Target { + self.composition } } -impl<'comp> CompositionValues<'comp> { - fn new(composition: &'comp Composition, cache: &CompCacheWithParams) -> Option { - let validity_cache = Arc::clone(&cache.per_param_cache.is_valid); - // Sanity check that there should only be two `Arc`s for the validity cache (the one in - // `cache`, and the one we just cloned). If this is the case, then we are the only thread - // to have access to the contained `Mutex` and therefore, since we drop one guard before - // taking another, no deadlocks are possible. - assert_eq!(Arc::strong_count(&validity_cache), 2); - let is_valid = validity_cache.lock().unwrap().get(&composition.id).copied(); - match is_valid { - Some(false) => None, // Known bad; reject - Some(true) => { - // Known good; construct but skip checks - let v = Self::new_inner(composition, cache, true); - assert!(v.is_some()); - v - } - None => { - // Unknown; construct without skipping checks and save the result - let getter = Self::new_inner(composition, cache, false); - validity_cache - .lock() - .unwrap() - .insert(composition.id, getter.is_some()); - getter - } - } - } - - fn new_inner( - composition: &'comp Composition, - cache: &CompCacheWithParams, - known_good: bool, - ) -> Option { - // Do cheap checks before calculating anything. This is useful because the search - // algorithm produces tons of too-short compositions, so it's worth validating length - // quickly and thus rejecting these. - if !known_good && !Self::do_cheap_checks(composition, &cache.params) { +impl Composition { + pub fn values(&self, params: &ParamsData) -> Option { + if !self.do_cheap_checks(params) { return None; } - // Once the cheap checks pass, compute useful cached values and build the `getter` - let music_counts = composition.music_counts(cache); - let music_score = music_counts_to_score(&music_counts, cache.params); - // TODO: Cache these - let call_string = composition.call_string(cache); - let atw_factor = composition.atw_factor(cache); - let total_score = composition.total_score(music_score, atw_factor, cache); - let method_counts = composition.method_counts(cache); - let mut values = Self { - composition, - - call_string, - method_counts, + let music_counts = self.music_counts(params); + let music_score = music_counts_to_score(&music_counts, params); + let atw_factor = self.atw_factor(params); + let comp_values = CompositionValues { + composition: self, + + call_string: self.call_string(params), + method_counts: self.method_counts(params), music_counts, music_score, atw_factor, - total_score, + total_score: self.total_score(music_score, atw_factor, params), }; - if !known_good && !values.do_non_cheap_checks(cache) { + if !comp_values.do_non_cheap_checks(params) { return None; } - Some(values) // If all checks didn't find problems, the getter is valid - } -} - -impl Composition { - /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything - /// was rung (i.e. the composition is atw) - fn atw_factor(&self, cache: &CompCacheWithParams) -> f32 { - // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible - let total_unique_row_positions = cache.working_bells.len() // Working bells - * cache.working_bells.len() // Working place bells - * cache.params.methods.iter().map(|m| m.lead_len()).sum::(); - // Determine how many we actuall rang - let run_unique_row_positions = cache - .working_bells - .iter() - .map(|b| self.unique_place_bell_rows_per_bell[b.index()]) - .sum::(); - - run_unique_row_positions as f32 / total_unique_row_positions as f32 - } - - /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. - fn music_counts<'a>(&self, cache: &CompCacheWithParams) -> MusicTypeVec> { - let mut cached_block: Option> = None; - - let mut counts = MusicTypeVec::new(); - for (idx, mt) in cache.params.music_types.iter_enumerated() { - let count = *cache.music_counts[idx] - .borrow_mut() - .entry(self.id) - .or_insert_with(|| { - if cached_block.is_none() { - cached_block = Some(cache.block(&self.path)); - } - let block = cached_block.as_ref().unwrap(); - - mt.count(block, !self.start_stroke) - }); - counts.push(count); - } - counts - } - - /// The total score generated by this composition from all the different weights (music, calls, - /// changes of method, handbell coursing, etc.). - fn total_score(&self, music_score: f32, atw_factor: f32, cache: &CompCacheWithParams) -> f32 { - let mut total_score = 0.0; - // Music - total_score += music_score; - // ATW - if let Some(atw_weight) = cache.params.atw_weight { - total_score += atw_factor * atw_weight; - } - // Calls - for elem in &self.path { - if let Some(call_id) = elem.call_to_end { - let call = cache.get_call(call_id); - total_score += call.weight * cache.params.num_parts() as f32; - } - } - // Splices - let mut changes_of_method = 0; - for (e1, e2) in self.path.iter().tuple_windows() { - if e1.is_splice_when_followed_by(e2, cache.params) { - changes_of_method += cache.params.num_parts(); - } - } - let first_elem = self.path.first().unwrap(); - let last_elem = self.path.last().unwrap(); - if last_elem.is_splice_when_followed_by(first_elem, cache.params) { - // -1 because there's no splice around the end/start of the composition - changes_of_method += cache.params.num_parts() - 1; - } - total_score += changes_of_method as f32 * cache.params.splice_weight; - // Course weights - // TODO: Cache this - for elem in &self.path { - let lead_head_weights = &cache.method_map[&elem.method_id].lead_head_weights; - let lead_head = elem.lead_head(&cache.method_map); - for part_head in self.part_head.closure() { - let lead_head_in_part = part_head * &lead_head; - for (mask, weight) in lead_head_weights { - if mask.matches(&lead_head_in_part) { - total_score += *weight * elem.length.as_usize() as f32; - } - } - } - } - total_score + Some(comp_values) } /// Generate a human-friendly [`String`] summarising the calling of this composition. For /// example, [this composition](https://complib.org/composition/87419) would have a /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. - pub fn call_string(&self, cache: &CompCacheWithParams) -> String { - let params = &cache.params; - + fn call_string(&self, params: &ParamsData) -> String { let needs_brackets = params.is_spliced() || params.call_display_style == CallDisplayStyle::Positional; let is_snap_start = self.path[0].start_sub_lead_idx > 0; @@ -539,7 +346,7 @@ impl Composition { // Add one shorthand for every lead *covered* (not number of lead heads reached) // // TODO: Deal with half-lead spliced - let method = cache.get_method(path_elem.method_id); + let method = params.get_method(path_elem.method_id); let num_leads_covered = num_leads_covered( method.lead_len(), path_elem.start_sub_lead_idx, @@ -551,7 +358,7 @@ impl Composition { } // Call text if let Some(call_id) = path_elem.call_to_end { - let call = cache.get_call(call_id); + let call = params.get_call(call_id); s.push_str(if needs_brackets { "[" } else { "" }); // Call position match params.call_display_style { @@ -577,86 +384,159 @@ impl Composition { /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the /// [`Search`]. These are stored in the same order as the [`Method`]s. - fn method_counts(&self, cache: &CompCacheWithParams) -> MethodVec { - let mut method_counts = - index_vec::index_vec![TotalLength::ZERO; cache.params.methods.len()]; + fn method_counts(&self, param_data: &ParamsData) -> MethodVec { + let mut method_counts = index_vec::index_vec![TotalLength::ZERO; param_data.methods.len()]; for elem in &self.path { - let idx = cache.method_map[&elem.method_id].idx; - method_counts[idx] += elem.length.as_total(&cache.params.part_head_group); + let idx = param_data.method_map[&elem.method_id].idx; + method_counts[idx] += elem.length.as_total(¶m_data.part_head_group); } method_counts } + + /// Returns a factor in `0.0..=1.0` where 0.0 means nothing was rung and 1.0 means everything + /// was rung (i.e. the composition is atw) + fn atw_factor(&self, params: &ParamsData) -> f32 { + // Determine how many `(bell, place, method, sub-lead-idx)` quadruples are actually possible + let total_unique_row_positions = params.working_bells.len() // Working bells + * params.working_bells.len() // Working place bells + * params.methods.iter().map(|m| m.lead_len()).sum::(); + // Determine how many we actuall rang + let run_unique_row_positions = params + .working_bells + .iter() + .map(|b| self.unique_place_bell_rows_per_bell[b.index()]) + .sum::(); + + run_unique_row_positions as f32 / total_unique_row_positions as f32 + } + + /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. + fn music_counts<'a>(&self, params: &ParamsData) -> MusicTypeVec> { + let block = params.get_block(&self.path); + params + .music_types + .iter() + .map(|mt| mt.count(&block, !self.start_stroke)) + .collect() + } + + /// The total score generated by this composition from all the different weights (music, calls, + /// changes of method, handbell coursing, etc.). + fn total_score(&self, music_score: f32, atw_factor: f32, params: &ParamsData) -> f32 { + let mut total_score = 0.0; + // Music + total_score += music_score; + // ATW + if let Some(atw_weight) = params.atw_weight { + total_score += atw_factor * atw_weight; + } + // Calls + for elem in &self.path { + if let Some(call_id) = elem.call_to_end { + let call = params.get_call(call_id); + total_score += call.weight * params.num_parts() as f32; + } + } + // Splices + let mut changes_of_method = 0; + for (e1, e2) in self.path.iter().tuple_windows() { + if PathElem::is_splice_between(e1, e2, params) { + changes_of_method += params.num_parts(); + } + } + let first_elem = self.path.first().unwrap(); + let last_elem = self.path.last().unwrap(); + if PathElem::is_splice_between(last_elem, first_elem, params) { + // -1 because there's no splice around the end/start of the composition + changes_of_method += params.num_parts() - 1; + } + total_score += changes_of_method as f32 * params.splice_weight; + // Course weights + // TODO: Cache this + for elem in &self.path { + let lead_head_weights = ¶ms.method_map[&elem.method_id].lead_head_weights; + let lead_head = elem.lead_head(¶ms.method_map); + for part_head in self.part_head.closure() { + let lead_head_in_part = part_head * &lead_head; + for (mask, weight) in lead_head_weights { + if mask.matches(&lead_head_in_part) { + total_score += *weight * elem.length.as_usize() as f32; + } + } + } + } + total_score + } } -//////////////// -// VALIDATION // -//////////////// +///////////////////// +// VALIDITY CHECKS // +///////////////////// -impl<'comp> CompositionValues<'comp> { +impl Composition { /// Perform cheap checks on this composition which don't involve looking up methods or calls. /// The main reason to do this is to quickly reject compositions which are being generated by the /// search routine #[must_use] - fn do_cheap_checks(composition: &Composition, params: &Parameters) -> bool { - if composition.stage != params.stage { + fn do_cheap_checks(&self, params: &ParamsData) -> bool { + if self.stage != params.stage { return false; // Stage mismatch } - if !params.length.contains(&composition.length) { + if !params.length.contains(&self.length) { return false; // Length mismatch } - if !params - .part_head_group - .is_row_generator(&composition.part_head) - { + if !params.part_head_group.is_row_generator(&self.part_head) { return false; // Composition doesn't end in a valid part } - if composition.path[0].start_row != params.start_row { + if self.path[0].start_row != params.start_row { return false; // Doesn't start in the right row } true // Can't reject composition this easily } +} - fn do_non_cheap_checks(&mut self, cache: &CompCacheWithParams) -> bool { - if !self.are_methods_satisfied(cache) { +impl CompositionValues<'_> { + fn do_non_cheap_checks(&self, params: &ParamsData) -> bool { + if !self.are_methods_satisfied(params) { return false; } - if !self.is_splice_style_satisfied(cache) { + if !self.is_splice_style_satisfied(params) { return false; } - if cache.params.require_atw && !self.is_atw() { + if params.require_atw && !self.is_atw() { return false; } - if cache.params.require_truth && !self.is_true() { + if params.require_truth && !self.is_true() { return false; // Composition is false but we needed it to be true } - if &self.composition.end_row != &cache.params.end_row { + if &self.end_row != ¶ms.end_row { return false; // Comps ends on the wrong row } - for (mt, counts) in cache.params.music_types.iter().zip_eq(&self.music_counts) { + for (mt, counts) in params.music_types.iter().zip_eq(&self.music_counts) { let total = mt.masked_total(*counts); if !mt.count_range.contains(total) { return false; // Music count range isn't satisfied } } // Start indices - let first_elem = &self.composition.path[0]; - let start_indices = cache + let first_elem = &self.path[0]; + let start_indices = params .get_method(first_elem.method_id) - .wrapped_indices(Boundary::Start, cache.params); + .wrapped_indices(Boundary::Start, params); if !start_indices.contains(&first_elem.start_sub_lead_idx) { return false; // Composition couldn't start in this way } // End indices - let last_elem = self.composition.path.last().unwrap(); - let end_sub_lead_idx = last_elem.end_sub_lead_idx(cache.params); + let last_elem = self.path.last().unwrap(); + let end_sub_lead_idx = last_elem.end_sub_lead_idx(params); match last_elem.call_to_end { None => { // The last element ends with a plain lead, so we need to check that this chunk's // end sub-lead index is valid - let end_indices = cache + let end_indices = params .get_method(last_elem.method_id) - .wrapped_indices(Boundary::End, cache.params); + .wrapped_indices(Boundary::End, params); if !end_indices.contains(&end_sub_lead_idx) { return false; // End index isn't valid for this method } @@ -665,19 +545,19 @@ impl<'comp> CompositionValues<'comp> { // If the composition ends with a call, then the situation is more complex; we need // to check that the call leads to a method which could end immediately // (conceptually, this introduces an imaginary 0-length 'path-elem' at the end) - let end_label = &cache.get_call(call_id).label_to; - if !cache.valid_end_labels.contains(end_label) { + let end_label = ¶ms.get_call(call_id).label_to; + if !params.valid_end_labels.contains(end_label) { return false; // Call's label_to can't correspond to a valid end idx } } } // Check for continuity over the part head (this checks for cases like finishing each part // at a snap and then starting the next part at the lead-end) - if cache.params.is_multipart() { - let start_labels = cache + if params.is_multipart() { + let start_labels = params .get_method(first_elem.method_id) .get_labels(first_elem.start_sub_lead_idx); - let end_labels = cache + let end_labels = params .get_method(last_elem.method_id) .get_labels(end_sub_lead_idx); let is_splice_possible = start_labels.iter().any(|label| end_labels.contains(label)); @@ -694,18 +574,18 @@ impl<'comp> CompositionValues<'comp> { true // Composition is all OK } - fn are_methods_satisfied(&self, cache: &CompCacheWithParams) -> bool { - let allowed_lead_heads: MethodVec> = cache + fn are_methods_satisfied(&self, params: &ParamsData) -> bool { + let allowed_lead_heads: MethodVec> = params .params .methods .iter() - .map(|m| m.allowed_lead_head_masks(cache.params)) + .map(|m| m.allowed_lead_head_masks(params)) .collect(); for path_elem in &self.composition.path { - let method_idx = cache.method_map[&path_elem.method_id].idx; + let method_idx = params.method_map[&path_elem.method_id].idx; // Check if lead head is valid - let lead_head = path_elem.lead_head(&cache.method_map); + let lead_head = path_elem.lead_head(¶ms.method_map); if !allowed_lead_heads[method_idx] .iter() .any(|m| m.matches(&lead_head)) @@ -719,20 +599,20 @@ impl<'comp> CompositionValues<'comp> { true } - fn is_splice_style_satisfied(&self, cache: &CompCacheWithParams) -> bool { - match cache.params.splice_style { + fn is_splice_style_satisfied(&self, params: &ParamsData) -> bool { + match params.splice_style { SpliceStyle::LeadLabels => true, // Assume all comps are still valid SpliceStyle::Calls => { let is_invalid_splice = |e1: &PathElem, e2: &PathElem| -> bool { // PERF: use the method map to speed up the splicing check - e1.is_splice_when_followed_by(e2, cache.params) && e1.ends_with_plain() + PathElem::is_splice_between(e1, e2, params) && e1.ends_with_plain() }; for (elem1, elem2) in self.composition.path.iter().tuple_windows() { if is_invalid_splice(elem1, elem2) { return false; // Splice but no call } } - if cache.params.is_multipart() && !self.composition.path.is_empty() { + if params.is_multipart() && !self.composition.path.is_empty() { let first = self.composition.path.first().unwrap(); let last = self.composition.path.last().unwrap(); if is_invalid_splice(last, first) { @@ -743,11 +623,13 @@ impl<'comp> CompositionValues<'comp> { } } } +} - ///////////// - // GETTERS // - ///////////// +///////////// +// GETTERS // +///////////// +impl<'comp> CompositionValues<'comp> { /// The number of [`Row`]s in this composition. pub fn length(&self) -> usize { self.composition.length.as_usize() diff --git a/monument/lib/src/search/atw.rs b/monument/lib/src/search/atw.rs index 29ca9375..55e9736c 100644 --- a/monument/lib/src/search/atw.rs +++ b/monument/lib/src/search/atw.rs @@ -51,6 +51,7 @@ impl AtwFlag { impl AtwTable { pub fn new(params: &Parameters, chunk_lengths: &[(ChunkId, PerPartLength)]) -> Self { + log::debug!("Building ATW table"); let atw_weight = match params.atw_weight { Some(w) => w, None if params.require_atw => 0.0, diff --git a/monument/lib/src/search/best_first.rs b/monument/lib/src/search/best_first.rs index 633ce86f..23b50a3c 100644 --- a/monument/lib/src/search/best_first.rs +++ b/monument/lib/src/search/best_first.rs @@ -1,15 +1,12 @@ use std::{ collections::BinaryHeap, - sync::{ - atomic::{AtomicBool, Ordering}, - Mutex, - }, + sync::atomic::{AtomicBool, Ordering}, }; use datasize::DataSize; use ringing_utils::BigNumInt; -use crate::{composition::CompositionCache, utils::lengths::TotalLength}; +use crate::{composition::ParamsData, utils::lengths::TotalLength}; use super::{path::Paths, prefix::CompPrefix, Progress, Search, Update}; @@ -19,18 +16,15 @@ const ITERS_BETWEEN_PATH_GCS: usize = 100_000_000; /// Searches a [`Graph`](m_gr::Graph) for compositions. This function is the core of Monument, and /// almost all of Monument's runtime will be spent in the `while` loop in this function. -pub(crate) fn search( - search: &Search, - mut update_fn: impl FnMut(Update), - abort_flag: &AtomicBool, - cache: &Mutex, -) { +pub(crate) fn search(search: &Search, mut update_fn: impl FnMut(Update), abort_flag: &AtomicBool) { let mem_limit = search .config .mem_limit .unwrap_or_else(super::default_mem_limit); log::info!("Limiting memory usage to {}B", BigNumInt(mem_limit)); + let param_data = ParamsData::new(&search.params); + // Initialise the frontier to just the start chunks let mut paths = Paths::new(); let mut frontier: BinaryHeap = CompPrefix::starts(search, &mut paths); @@ -58,6 +52,8 @@ pub(crate) fn search( }; } + log::debug!("Sending first progress update!"); + // Send 'empty' update before search starts send_progress_update!(truncating_queue = false); @@ -65,10 +61,15 @@ pub(crate) fn search( // frontier). This is best-first search (and can be A* depending on the cost function used). // This loop is the core of Monument - almost all the runtime will be spent here. while let Some(prefix) = frontier.pop() { - let maybe_comp = prefix.expand(search, &mut paths, &mut frontier, cache); + let maybe_comp = prefix.expand(search, &mut paths, &mut frontier, ¶m_data); // Submit new compositions when they're generated if let Some(comp) = maybe_comp { + log::debug!( + "Found composition (len {}): {}", + comp.length.as_usize(), + comp.values(¶m_data).unwrap().call_string() + ); update_fn(Update::Comp(comp)); num_comps += 1; diff --git a/monument/lib/src/search/graph.rs b/monument/lib/src/search/graph.rs index 65e2b117..99ab0a99 100644 --- a/monument/lib/src/search/graph.rs +++ b/monument/lib/src/search/graph.rs @@ -62,6 +62,7 @@ impl Graph { params: &Parameters, atw_table: &AtwTable, ) -> Self { + log::debug!("Lowering graph"); let num_chunks = source_graph.chunks.len(); // Assign each chunk ID to a unique `ChunkIdx`, and vice versa. This way, we can now label @@ -141,6 +142,8 @@ impl Graph { } } + log::debug!("Finished lowering graph"); + Graph { starts, chunks } } } diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index 47b58ecd..b7c99a11 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -11,15 +11,14 @@ use std::{ ops::RangeInclusive, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Arc, }, }; -use bellframe::Stage; use itertools::Itertools; use crate::{ - composition::{CompositionCache, CompositionId}, + composition::CompositionId, parameters::{MethodId, Parameters}, prove_length::{prove_lengths, RefinedRanges}, utils::IdGenerator, @@ -91,25 +90,18 @@ impl Search { /// Runs the search, **blocking the current thread** until either the search is completed or /// is aborted - pub fn run( - &self, - update_fn: impl FnMut(Update), - abort_flag: &AtomicBool, - comp_data_cache: &Mutex, - ) { + pub fn run(&self, update_fn: impl FnMut(Update), abort_flag: &AtomicBool) { // Make sure that `abort_flag` starts as false (so the search doesn't abort immediately). // We want this to be sequentially consistent to make sure that the worker threads don't // see the previous value (which could be 'true'). abort_flag.store(false, Ordering::SeqCst); - best_first::search(self, update_fn, abort_flag, comp_data_cache); + log::debug!("Starting search"); + best_first::search(self, update_fn, abort_flag); } } impl Search { - // TODO: Most of these functions just call the corresponding function in `parameters`, and at - // that point, we may as well just force the consumer of the API to call `Self::parameters()` - // first. - + // TODO: Remove this once `refined_ranges` are cheaper to compute /// Gets the range of counts required of the given [`MethodId`]. pub fn method_count_range(&self, id: MethodId) -> RangeInclusive { let idx = self.params.method_id_to_idx(id); @@ -117,28 +109,9 @@ impl Search { range.start().as_usize()..=range.end().as_usize() } - pub fn methods(&self) -> impl Iterator { - self.params.methods.iter().map(|m| (m, m.shorthand())) - } - pub fn parameters(&self) -> &Parameters { &self.params } - - pub fn num_parts(&self) -> usize { - self.params.num_parts() - } - - /// Does this `Parameters` generate [`Composition`](crate::Composition)s with more than one part? - pub fn is_multipart(&self) -> bool { - self.params.is_multipart() - } - - /// Gets the [`effective_stage`](bellframe::Row::effective_stage) of the part heads used in - /// this `Parameters`. The short form of every possible part head will be exactly this length. - pub fn effective_part_head_stage(&self) -> Stage { - self.params.part_head_group.effective_stage() - } } /// Update message from an in-progress [`Search`]. @@ -233,6 +206,8 @@ impl Default for Config { /// Return the memory limit for this search, if not specified by the user's [`Config`]. On most /// systems, this will return 80% of available memory. fn default_mem_limit() -> usize { + log::debug!("Getting memory usage"); + // Use as a memory limit either 80% of available memory or 5GB if we can't access // availability let ideal_mem_limit = if sysinfo::IS_SUPPORTED_SYSTEM { @@ -240,6 +215,7 @@ fn default_mem_limit() -> usize { } else { 5_000_000_000u64 }; + log::debug!("Got memory usage"); // However, always use 500MB less than the memory that's accessible by the system (i.e. if // we're running in 32-bit environments like WASM, we can't fill available memory so we // just default to `2*32 - 500MB ~= 3.5GB`) diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index 49bebbd5..bc8bc32d 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -1,11 +1,11 @@ -use std::{cmp::Ordering, collections::BinaryHeap, ops::Deref, sync::Mutex}; +use std::{cmp::Ordering, collections::BinaryHeap, ops::Deref}; use bit_vec::BitVec; use datasize::DataSize; use ordered_float::OrderedFloat; use crate::{ - composition::{Composition, CompositionCache, PathElem}, + composition::{Composition, ParamsData, PathElem}, graph::LinkSide, group::PartHead, utils::{counts::Counts, div_rounding_up, lengths::TotalLength}, @@ -148,12 +148,12 @@ impl CompPrefix { search: &Search, paths: &mut Paths, frontier: &mut BinaryHeap, - cache: &Mutex, + param_data: &ParamsData, ) -> Option { // Determine the chunk being expanded (or if it's an end, complete the composition) let chunk_idx = match self.next_link_side { LinkSide::Chunk(chunk_idx) => chunk_idx, - LinkSide::StartOrEnd => return self.check_comp(search, paths, cache), + LinkSide::StartOrEnd => return self.check_comp(search, paths, param_data), }; let chunk = &search.graph.chunks[chunk_idx]; @@ -243,7 +243,7 @@ impl CompPrefix { &self, search: &Search, paths: &Paths, - cache: &Mutex, + param_data: &ParamsData, ) -> Option { assert!(self.next_link_side.is_start_or_end()); @@ -268,17 +268,14 @@ impl CompPrefix { /* At this point, all checks on the composition have passed and we know it satisfies the * user's parameters */ - let mut binding = cache.lock().unwrap(); - let cache = binding.with_params(&search.params); - // Now we know the composition is valid, construct it and return let path = self.flattened_path(search, paths); let composition = - Composition::new(search.id_generator.next(), path, self.part_head, &cache); - // Validate the composition by building a `CompositionGetter`. The checks performed by - // `CompositionGetter::new` are much stricter and more correct than those we can perform + Composition::new(search.id_generator.next(), path, self.part_head, param_data); + // Validate the composition by attempting to get its values (as would happen in the GUI). + // The checks performed here are much stricter and more correct than those we can perform // here, so we defer entirely to it to check these candidate compositions for validity. - let comp_values = cache.get_comp_values(&composition)?; + let comp_values = composition.values(param_data)?; // Sanity check that the composition is true if search.params.require_truth && !comp_values.is_true() { panic!( From ef739224a54f6a85f6adb9a337e25490d553cc5f Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sun, 24 Dec 2023 18:26:41 +0000 Subject: [PATCH 24/29] Clean up some getters --- monument/cli/src/lib.rs | 4 +- monument/cli/src/logging.rs | 21 ++----- monument/lib/src/composition.rs | 79 ++++++++++++++++----------- monument/lib/src/search/best_first.rs | 2 +- monument/lib/src/search/prefix.rs | 5 +- 5 files changed, 56 insertions(+), 55 deletions(-) diff --git a/monument/cli/src/lib.rs b/monument/cli/src/lib.rs index a043197d..a1623fdd 100644 --- a/monument/cli/src/lib.rs +++ b/monument/cli/src/lib.rs @@ -121,9 +121,9 @@ pub fn run( comps.sort_by_cached_key(|(comp, _generation_index)| { let getter = comp.values(¶ms_data).unwrap(); ( - rounded_float(getter.music_score()), + rounded_float(getter.music_score), rounded_float(getter.score_per_row()), - getter.call_string().to_owned(), + getter.call_string.clone(), ) }); Ok(Some(SearchResult { diff --git a/monument/cli/src/logging.rs b/monument/cli/src/logging.rs index 8197b2b2..77931cd4 100644 --- a/monument/cli/src/logging.rs +++ b/monument/cli/src/logging.rs @@ -289,19 +289,17 @@ impl CompositionPrinter { // Method counts (for spliced) if self.method_count_widths.len() > 1 { s.push_str(": "); - for ((width, _), count) in self.method_count_widths.iter().zip_eq(comp.method_counts()) - { + for ((width, _), count) in self.method_count_widths.iter().zip_eq(&comp.method_counts) { write!(s, "{:>width$} ", count, width = *width).unwrap(); } } s.push('|'); // Atw if self.print_atw { - let factor = comp.atw_factor(); - if factor > 0.999999 { + if comp.atw_factor > 0.999999 { s.push_str(&format!(" {} |", "atw".color(colored::Color::BrightGreen))); } else { - write!(s, " {:>2}% |", (factor * 100.0).floor() as usize).unwrap(); + write!(s, " {:>2}% |", (comp.atw_factor * 100.0).floor() as usize).unwrap(); } } // Part head (if >2 parts; up to 2-parts must always have the same part head) @@ -310,8 +308,7 @@ impl CompositionPrinter { } // Music let music_types_to_show = self.params.music_types_to_show(); - let music_counts = comp.music_counts(); - write!(s, " {:>7.2} ", comp.music_score()).unwrap(); + write!(s, " {:>7.2} ", comp.music_score).unwrap(); if !music_types_to_show.is_empty() { s.push(':'); } @@ -319,19 +316,13 @@ impl CompositionPrinter { s.push_str(" "); write_left_centered_text( &mut s, - &music_type.display_counts(music_counts[idx], self.params.stage), + &music_type.display_counts(comp.music_counts[idx], self.params.stage), music_type.col_width(self.params.stage), ); s.push(' '); } // avg score, call string - write!( - s, - "| {:>9.6} | {}", - comp.score_per_row(), - comp.call_string() - ) - .unwrap(); + write!(s, "| {:>9.6} | {}", comp.score_per_row(), comp.call_string).unwrap(); s } diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 91b50091..3446856b 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -270,6 +270,36 @@ impl<'p> Deref for ParamsData<'p> { } } +///////////// +// CACHING // +///////////// + +/// Struct which caches expensive properties about [`Composition`]s, so that these values will be +/// calculated once and then re-used for future queries. +#[derive(Debug, Default)] +pub struct CompositionCache { + per_param_cache: Option<(Parameters, PerParamCache)>, + music_counts: HashMap, +} + +type PerMusicTypeCache = HashMap>; + +#[derive(Debug, Default)] +struct PerParamCache { + is_valid: HashMap, +} + +#[derive(Debug)] +pub struct CacheWithParams<'cache> { + cache: &'cache mut CompositionCache, +} + +impl CompositionCache { + pub fn with_params<'cache>(&'cache mut self, _params: &Parameters) -> CacheWithParams<'cache> { + CacheWithParams { cache: self } + } +} + ///////////////// // COMP VALUES // ///////////////// @@ -278,15 +308,15 @@ impl<'p> Deref for ParamsData<'p> { /// is taken. #[derive(Debug)] pub struct CompositionValues<'comp> { - composition: &'comp Composition, + pub composition: &'comp Composition, - call_string: String, - method_counts: MethodVec, - music_counts: MusicTypeVec>, - music_score: f32, - atw_factor: f32, + pub call_string: String, + pub method_counts: MethodVec, + pub music_counts: MusicTypeVec>, + pub music_score: f32, + pub atw_factor: f32, // TODO: Replace this with a simple getter - total_score: f32, + pub total_score: f32, } impl Deref for CompositionValues<'_> { @@ -324,6 +354,13 @@ impl Composition { Some(comp_values) } + pub fn values_with_cache<'comp>( + &'comp self, + cache: &mut CacheWithParams, + ) -> Option> { + todo!() + } + /// Generate a human-friendly [`String`] summarising the calling of this composition. For /// example, [this composition](https://complib.org/composition/87419) would have a /// `call_string` of `D[B]BL[W]N[M]SE[sH]NCYW[sH]`. @@ -635,10 +672,6 @@ impl<'comp> CompositionValues<'comp> { self.composition.length.as_usize() } - pub fn call_string(&self) -> &str { - &self.call_string - } - pub fn part_head(&self) -> &Row { &self.composition.part_head } @@ -650,31 +683,11 @@ impl<'comp> CompositionValues<'comp> { /// The average score generated by each [`Row`] in this composition. This is equal to /// `self.total_score() / self.length() as f32`. pub fn score_per_row(&self) -> f32 { - self.total_score() / self.length() as f32 - } - - pub fn total_score(&self) -> f32 { - self.total_score + self.total_score / self.length() as f32 } pub fn is_atw(&self) -> bool { - self.atw_factor() == 1.0 - } - - pub fn atw_factor(&self) -> f32 { - self.atw_factor - } - - pub fn music_score(&self) -> f32 { - self.music_score - } - - pub fn music_counts(&self) -> &MusicTypeVec> { - &self.music_counts - } - - pub fn method_counts(&self) -> &MethodVec { - &self.method_counts + self.atw_factor == 1.0 } } diff --git a/monument/lib/src/search/best_first.rs b/monument/lib/src/search/best_first.rs index 23b50a3c..b5179e28 100644 --- a/monument/lib/src/search/best_first.rs +++ b/monument/lib/src/search/best_first.rs @@ -68,7 +68,7 @@ pub(crate) fn search(search: &Search, mut update_fn: impl FnMut(Update), abort_f log::debug!( "Found composition (len {}): {}", comp.length.as_usize(), - comp.values(¶m_data).unwrap().call_string() + comp.values(¶m_data).unwrap().call_string ); update_fn(Update::Comp(comp)); num_comps += 1; diff --git a/monument/lib/src/search/prefix.rs b/monument/lib/src/search/prefix.rs index bc8bc32d..0c4c4fbf 100644 --- a/monument/lib/src/search/prefix.rs +++ b/monument/lib/src/search/prefix.rs @@ -278,10 +278,7 @@ impl CompPrefix { let comp_values = composition.values(param_data)?; // Sanity check that the composition is true if search.params.require_truth && !comp_values.is_true() { - panic!( - "Generated false composition ({})", - comp_values.call_string() - ); + panic!("Generated false composition ({})", comp_values.call_string); } // Finally, return the comp Some(composition) From 38ef597754989877f9d6ec4e4a879a110ddfb192 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Wed, 27 Dec 2023 15:43:05 +0000 Subject: [PATCH 25/29] Undo patch for broken OS --- monument/lib/src/search/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index b7c99a11..1e36fa4d 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -210,7 +210,7 @@ fn default_mem_limit() -> usize { // Use as a memory limit either 80% of available memory or 5GB if we can't access // availability - let ideal_mem_limit = if sysinfo::IS_SUPPORTED_SYSTEM { + let ideal_mem_limit = if sysinfo::System::IS_SUPPORTED { (sysinfo::System::new_all().available_memory() as f32 * 0.8) as u64 } else { 5_000_000_000u64 From d6aea639c40db9e1f90e8d6d90190e984fd7bc90 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Wed, 27 Dec 2023 15:51:32 +0000 Subject: [PATCH 26/29] Reject compositions with unused methods/calls --- monument/lib/src/composition.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index 3446856b..ad653746 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -528,6 +528,16 @@ impl Composition { if self.path[0].start_row != params.start_row { return false; // Doesn't start in the right row } + for elem in &self.path { + if !params.method_map.contains_key(&elem.method_id) { + return false; // Composition uses a method not in params + } + if let Some(call_id) = elem.call_to_end { + if !params.call_map.contains_key(&call_id) { + return false; // Composition uses a call not in params + } + } + } true // Can't reject composition this easily } From 28c44743c33ca78dd7fdae51bae5bafce9b94645 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Wed, 27 Dec 2023 17:19:37 +0000 Subject: [PATCH 27/29] Implement a cache for expensive values --- Cargo.lock | 7 ++ monument/gui/src/project.rs | 42 ++++----- monument/gui/src/search.rs | 1 - monument/lib/Cargo.toml | 1 + monument/lib/src/composition.rs | 157 +++++++++++++++++++++++++++++--- monument/lib/src/search/mod.rs | 2 +- 6 files changed, 168 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59ad4d49..69703ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1468,6 +1468,12 @@ dependencies = [ "arrayvec 0.7.4", ] +[[package]] +name = "lazy-st" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b428d1a595e7ea6e853401f52e452c7afc281a2335b6115525e1a3002aa3a4f6" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1614,6 +1620,7 @@ dependencies = [ "index_vec", "itertools 0.12.1", "kneasle_ringing_utils", + "lazy-st", "log", "num_cpus", "ordered-float 4.2.0", diff --git a/monument/gui/src/project.rs b/monument/gui/src/project.rs index 67a4ad79..14acbb24 100644 --- a/monument/gui/src/project.rs +++ b/monument/gui/src/project.rs @@ -1,11 +1,11 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use eframe::egui; use itertools::Itertools; use monument::{ - composition::{CompositionCache, CompositionId}, + composition::{CompositionCache, CompositionId, ParamsData}, utils::IdGenerator, - Composition, CompositionGetter, + Composition, }; use ordered_float::OrderedFloat; @@ -19,7 +19,7 @@ pub struct Project { params: crate::Parameters, compositions: Vec, - comp_cache: Arc>, + pub comp_cache: CompositionCache, comp_id_generator: Arc>, search_progress: Option, } @@ -111,18 +111,17 @@ impl Project { } fn draw_comps(&mut self, params: &monument::Parameters, ui: &mut egui::Ui) { + let per_param_values = ParamsData::new(params); + let mut cache_with_params = self.comp_cache.with_params(params); + // Generate getters for all comps which still match the current params - let mut cache_guard = self.comp_cache.lock().unwrap(); let mut comps_to_display = self .compositions .iter() - .filter_map(|comp| { - let mut getter = CompositionGetter::new(comp, params, &mut cache_guard)?; - Some((getter.score_per_row(), comp)) - }) + .filter_map(|comp| comp.values_with_cache(&per_param_values, &mut cache_with_params)) .collect_vec(); // Sort them by total score - comps_to_display.sort_by_cached_key(|(score, _comp)| OrderedFloat(-score)); + comps_to_display.sort_by_cached_key(|values| OrderedFloat(-values.score_per_row())); // Display them in a grid // TODO: Custom (animated!) widget for this egui::Grid::new("Comp grid") @@ -146,24 +145,21 @@ impl Project { ui.end_row(); /* Compositions */ - for (_score, comp) in comps_to_display { - let mut g = CompositionGetter::new(comp, params, &mut cache_guard).unwrap(); - - ui.label(g.length().to_string()); + for comp_values in comps_to_display { + ui.label(comp_values.length().to_string()); ui.label("|"); - ui.label(format!("{:.2}", g.total_score())); - ui.label(format!("{:.6}", g.score_per_row())); + ui.label(format!("{:.2}", comp_values.total_score)); + ui.label(format!("{:.6}", comp_values.score_per_row())); ui.label("|"); - let (music_counts, music_score) = g.music_counts_and_score(); - ui.label(format!("{music_score:.2}")); + ui.label(format!("{:.2}", comp_values.music_score)); for (idx, mt) in params.music_types_to_show() { - ui.label(mt.display_counts(music_counts[idx], params.stage)); + ui.label(mt.display_counts(comp_values.music_counts[idx], params.stage)); } ui.label("|"); - ui.label(g.call_string()); + ui.label(&comp_values.call_string); ui.end_row() } }); @@ -173,10 +169,6 @@ impl Project { // HELPERS // ///////////// - pub fn comp_cache(&self) -> Arc> { - self.comp_cache.clone() - } - pub fn comp_id_generator(&self) -> Arc> { self.comp_id_generator.clone() } @@ -205,7 +197,7 @@ impl Default for Project { params: crate::Parameters::yorkshire_s8_qps(), compositions: vec![], - comp_cache: Arc::new(Mutex::new(CompositionCache::default())), + comp_cache: CompositionCache::default(), comp_id_generator: Arc::new(IdGenerator::starting_at_zero()), search_progress: None, } diff --git a/monument/gui/src/search.rs b/monument/gui/src/search.rs index 02573bac..d05185bf 100644 --- a/monument/gui/src/search.rs +++ b/monument/gui/src/search.rs @@ -57,7 +57,6 @@ fn worker_thread( let search = monument::Search::new(params.to_monument(), monument::Config::default()) .unwrap() .id_generator(project_guard.comp_id_generator()); - let comp_cache = project_guard.comp_cache(); drop(project_guard); // Release the mutex before the search runs search.run( |update| { diff --git a/monument/lib/Cargo.toml b/monument/lib/Cargo.toml index a0867a9b..569310b7 100644 --- a/monument/lib/Cargo.toml +++ b/monument/lib/Cargo.toml @@ -17,6 +17,7 @@ gcd = "2.3.0" hmap = "0.1.0" index_vec = "0.1.3" itertools = "0.12.1" +lazy-st = "0.2.2" log = "0.4.20" num_cpus = "1.16.0" ordered-float = "4.2.0" diff --git a/monument/lib/src/composition.rs b/monument/lib/src/composition.rs index ad653746..65c11039 100644 --- a/monument/lib/src/composition.rs +++ b/monument/lib/src/composition.rs @@ -7,6 +7,7 @@ use std::{ use bellframe::{music::AtRowPositions, Bell, Block, Mask, Row, RowBuf, Stage, Stroke, Truth}; use itertools::Itertools; +use lazy_st::lazy; use crate::{ parameters::{ @@ -278,25 +279,78 @@ impl<'p> Deref for ParamsData<'p> { /// calculated once and then re-used for future queries. #[derive(Debug, Default)] pub struct CompositionCache { - per_param_cache: Option<(Parameters, PerParamCache)>, + per_param_cache: Option<(Parameters, HashMap)>, music_counts: HashMap, } -type PerMusicTypeCache = HashMap>; +#[derive(Debug)] +enum PerParamCacheEntry { + Invalid, + Valid(ValidPerParamCache), +} -#[derive(Debug, Default)] -struct PerParamCache { - is_valid: HashMap, +#[derive(Debug)] +struct ValidPerParamCache { + // TODO: Add values here } #[derive(Debug)] pub struct CacheWithParams<'cache> { - cache: &'cache mut CompositionCache, + param_wide_values: &'cache mut HashMap, + music_type_cache: MusicTypeCache<'cache>, +} + +#[derive(Debug)] +pub struct MusicTypeCache<'cache> { + /// Map to which the contents of `music_type_caches` should be returned + cache_source: &'cache mut HashMap, + /// Set of [`PerMusicTypeCache`]s, to be added back to the cache once this is dropped + values: Vec<(bellframe::MusicType, PerMusicTypeCache)>, + /// For each of Monument's music types, which index from `music_type_caches` should be used + /// to access the cache? + indices: MusicTypeVec, +} + +type PerMusicTypeCache = HashMap>; + +impl Drop for MusicTypeCache<'_> { + fn drop(&mut self) { + for (mt, cache) in self.values.drain(..) { + self.cache_source.insert(mt, cache); + } + } } impl CompositionCache { - pub fn with_params<'cache>(&'cache mut self, _params: &Parameters) -> CacheWithParams<'cache> { - CacheWithParams { cache: self } + pub fn with_params<'cache>(&'cache mut self, params: &Parameters) -> CacheWithParams<'cache> { + // Get per-param values, preserving the cache if the params haven't changed + if self.per_param_cache.as_ref().map(|(p, _)| p) != Some(params) { + self.per_param_cache = Some((params.clone(), HashMap::new())); + } + let param_wide_values = &mut self.per_param_cache.as_mut().unwrap().1; + + // Get the music type caches + let mut values = Vec::new(); + let mut indices = MusicTypeVec::new(); + for music_type in ¶ms.music_types { + let music_type = &music_type.inner; + if let Some(idx) = values.iter().position(|(t, _)| t == music_type) { + indices.push(idx); + } else { + indices.push(values.len()); + let cache_entry = self.music_counts.remove(music_type).unwrap_or_default(); + values.push((music_type.clone(), cache_entry)); + } + } + + CacheWithParams { + param_wide_values, + music_type_cache: MusicTypeCache { + cache_source: &mut self.music_counts, + values, + indices, + }, + } } } @@ -333,7 +387,7 @@ impl Composition { return None; } - let music_counts = self.music_counts(params); + let music_counts = self.calculate_music_counts(params); let music_score = music_counts_to_score(&music_counts, params); let atw_factor = self.atw_factor(params); let comp_values = CompositionValues { @@ -356,9 +410,74 @@ impl Composition { pub fn values_with_cache<'comp>( &'comp self, + params: &ParamsData, cache: &mut CacheWithParams, ) -> Option> { - todo!() + use PerParamCacheEntry::*; + + let per_param_cache = match cache.param_wide_values.get(&self.id) { + Some(Invalid) => return None, // Known invalid + Some(Valid(e)) => Some(e), // Known good, with values + None => None, // We just don't know yet + }; + let is_known_good = per_param_cache.is_some(); + + if !is_known_good { + if !self.do_cheap_checks(params) { + cache.param_wide_values.insert(self.id, Invalid); + return None; + } + } + + let music_counts = + self.calculate_music_counts_with_cache(params, &mut cache.music_type_cache); + let music_score = music_counts_to_score(&music_counts, params); + let atw_factor = self.atw_factor(params); + let values = CompositionValues { + composition: self, + + call_string: self.call_string(params), + method_counts: self.method_counts(params), + music_score: music_counts_to_score(&music_counts, params), + music_counts, + atw_factor, + total_score: self.total_score(music_score, atw_factor, params), + }; + + if !is_known_good { + if self.do_cheap_checks(params) { + cache + .param_wide_values + .insert(self.id, Valid(ValidPerParamCache {})); + } else { + cache.param_wide_values.insert(self.id, Invalid); + return None; + } + } + + Some(values) + } + + fn calculate_music_counts_with_cache( + &self, + params: &ParamsData, + cache: &mut MusicTypeCache, + ) -> MusicTypeVec> { + let block = lazy!({ + println!("Calculating block"); + params.get_block(&self.path) + }); + + params + .music_types + .iter_enumerated() + .map(|(idx, music_type)| { + let (_mt, comp_id_map) = &mut cache.values[cache.indices[idx]]; + *comp_id_map + .entry(self.id) + .or_insert_with(|| music_type.count_block(&*block, self.start_stroke)) + }) + .collect() } /// Generate a human-friendly [`String`] summarising the calling of this composition. For @@ -421,11 +540,11 @@ impl Composition { /// A slice containing the number of [`Row`]s generated for each [`Method`] used in the /// [`Search`]. These are stored in the same order as the [`Method`]s. - fn method_counts(&self, param_data: &ParamsData) -> MethodVec { - let mut method_counts = index_vec::index_vec![TotalLength::ZERO; param_data.methods.len()]; + fn method_counts(&self, params: &ParamsData) -> MethodVec { + let mut method_counts = index_vec::index_vec![TotalLength::ZERO; params.methods.len()]; for elem in &self.path { - let idx = param_data.method_map[&elem.method_id].idx; - method_counts[idx] += elem.length.as_total(¶m_data.part_head_group); + let idx = params.method_map[&elem.method_id].idx; + method_counts[idx] += elem.length.as_total(¶ms.part_head_group); } method_counts } @@ -448,7 +567,15 @@ impl Composition { } /// The number of *instances* of each [`MusicType`] in the [`Parameters`]. - fn music_counts<'a>(&self, params: &ParamsData) -> MusicTypeVec> { + /// + /// This function computes all the rows of the composition, and therefore is quite expensive to + /// call. It's fine to call it every so often (e.g. every time a valid composition is + /// generated), but not if there is a lot of time pressure (e.g. recomputing it for every + /// composition on every GUI frame). + fn calculate_music_counts<'a>( + &self, + params: &ParamsData, + ) -> MusicTypeVec> { let block = params.get_block(&self.path); params .music_types diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index 1e36fa4d..b7c99a11 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -210,7 +210,7 @@ fn default_mem_limit() -> usize { // Use as a memory limit either 80% of available memory or 5GB if we can't access // availability - let ideal_mem_limit = if sysinfo::System::IS_SUPPORTED { + let ideal_mem_limit = if sysinfo::IS_SUPPORTED_SYSTEM { (sysinfo::System::new_all().available_memory() as f32 * 0.8) as u64 } else { 5_000_000_000u64 From 449f441757048bbad74bcdf026714366b508da89 Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 13 Apr 2024 12:32:40 +0100 Subject: [PATCH 28/29] Fix documentation errors --- monument/lib/src/parameters.rs | 9 ++++++--- monument/lib/src/search/mod.rs | 6 +----- monument/lib/src/utils/mod.rs | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monument/lib/src/parameters.rs b/monument/lib/src/parameters.rs index 75711155..036c19c6 100644 --- a/monument/lib/src/parameters.rs +++ b/monument/lib/src/parameters.rs @@ -22,6 +22,9 @@ use crate::{ }, }; +#[allow(unused_imports)] // Used for doc links +use crate::{Composition, Config}; + /// Fully built specification for which [`Composition`]s should be generated. /// /// Compare this to [`Config`], which determines _how_ those @@ -314,7 +317,7 @@ impl Parameters { // METHODS // ///////////// -/// A `Method` used in a [`Search`]. +/// A `Method` used in a [`Search`](crate::Search). #[derive(Debug, Clone, PartialEq)] pub struct Method { pub id: MethodId, @@ -719,7 +722,7 @@ impl MusicType { (counts.map(|x| x as f32) * self.weights).total() } - /// Return the total counts, only from [`RowPosition`](bellframe::music::RowPosition)s for which + /// Return the total counts, only from [`RowPosition`]s for which /// `Self::optional_weights` are not [`None`]. pub fn masked_total(&self, counts: AtRowPositions) -> usize { counts.masked(!self.show_positions, 0).total() @@ -730,7 +733,7 @@ impl MusicType { } /// Return the width of the smallest column large enough to be guaranteed to hold (almost) - /// every instance of this [`MusicDisplay`] (assuming rows can't be repeated). + /// every instance of this `MusicType` (assuming rows can't be repeated). pub fn col_width(&self, stage: Stage) -> usize { // We always pad the counts as much as required, so displaying a set of 0s results in a // maximum-width string (i.e. all output strings are the same length) diff --git a/monument/lib/src/search/mod.rs b/monument/lib/src/search/mod.rs index b7c99a11..9c622853 100644 --- a/monument/lib/src/search/mod.rs +++ b/monument/lib/src/search/mod.rs @@ -30,11 +30,7 @@ use self::atw::AtwTable; /// Handle to a search being run by Monument. /// /// This is used if you want to keep control over searches as they are running, for example -/// to receive [`Update`]s on their [`Progress`]. If you just -/// want to run a (hopefully quick) search, use [`Parameters::run`] or -/// [`Parameters::run_with_config`]. Both of those will deal with handling the [`Search`] for -/// you. -// TODO: Rename all instances from `data` to `search` +/// to receive [`Update`]s on their [`Progress`]. #[derive(Debug)] pub struct Search { /* Data */ diff --git a/monument/lib/src/utils/mod.rs b/monument/lib/src/utils/mod.rs index e4f76adf..4ddfda8c 100644 --- a/monument/lib/src/utils/mod.rs +++ b/monument/lib/src/utils/mod.rs @@ -41,7 +41,8 @@ impl Ord for FrontierItem { } } -/// Struct which generates unique `Id`s. Can be used with any of [`MethodId`] or [`CallId`]. +/// Struct which generates unique `Id`s. Can be used with any of +/// [`MethodId`](crate::parameters::MethodId) or [`CallId`](crate::parameters::CallId). #[derive(Debug)] pub struct IdGenerator { next_id: AtomicU32, From c561c6f3be1a6d7f0ea215f988b24d471e34163f Mon Sep 17 00:00:00 2001 From: Kneasle Date: Sat, 13 Apr 2024 12:34:05 +0100 Subject: [PATCH 29/29] Run all PR checks on `monument_gui` --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 72b635cf..fc13e5f4 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -19,6 +19,6 @@ jobs: - name: Run tests run: cargo test --all --verbose - name: Check formatting - run: cargo fmt -- --check + run: cargo fmt --all -- --check - name: Check docs - run: cargo doc + run: cargo doc --all