diff --git a/Cargo.lock b/Cargo.lock index 2aa7c881..f903de93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,18 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-compression" version = "0.4.5" @@ -251,6 +263,16 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -263,12 +285,50 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -286,7 +346,7 @@ dependencies = [ "cached_proc_macro", "cached_proc_macro_types", "futures", - "hashbrown", + "hashbrown 0.14.0", "instant", "once_cell", "thiserror", @@ -398,6 +458,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf79624de24decb4374e9c770bee822c7a55320537633aa6ef6655afdca554c" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -414,6 +480,7 @@ dependencies = [ "compiler-macros", "js-sys", "map-macro", + "once_cell", "regen-lang", "serde", "serde-wasm-bindgen 0.6.3", @@ -423,6 +490,7 @@ dependencies = [ "tokio", "tsify", "uni-path", + "urlencoding", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -438,6 +506,7 @@ dependencies = [ "flate2", "futures", "instant", + "livesplit-core", "log", "map-macro", "roman", @@ -539,6 +608,15 @@ dependencies = [ "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]] name = "derivative" version = "2.2.0" @@ -550,6 +628,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -606,6 +690,28 @@ dependencies = [ "libc", ] +[[package]] +name = "evdev" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bed59fcc8cfd6b190814a509018388462d3b203cf6dd10db5c00087e72a83f3" +dependencies = [ + "bitvec", + "cfg-if", + "libc", + "nix 0.23.2", + "thiserror", +] + +[[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" @@ -631,6 +737,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.28" @@ -764,6 +876,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.0" @@ -967,6 +1088,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "2.0.0" @@ -974,7 +1110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.0", ] [[package]] @@ -1024,6 +1160,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.66" @@ -1045,12 +1187,75 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "livesplit-core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c4e0ad5e245efe688896515ecf8a554f23c0337faced69cb306d51498b9827" +dependencies = [ + "base64-simd", + "bytemuck", + "cfg-if", + "hashbrown 0.13.2", + "image", + "itoa", + "libc", + "libm", + "livesplit-hotkey", + "livesplit-title-abbreviations", + "memchr", + "rustybuzz", + "serde", + "serde_json", + "simdutf8", + "smallstr", + "snafu", + "time", + "tiny-skia", + "unicase", + "winapi", +] + +[[package]] +name = "livesplit-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b7f15d5cd8cd2bcc4458c6e1f6527c46630abb8904cc3d4677bddbbd692c47" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "evdev", + "mio", + "nix 0.26.4", + "objc", + "promising-future", + "serde", + "snafu", + "winapi", + "x11-dl", +] + +[[package]] +name = "livesplit-title-abbreviations" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89841ea02874b53d1be920ce42acb5d4491b3beb2c85c0d570c8a67e49b6aa55" +dependencies = [ + "unicase", +] + [[package]] name = "lock_api" version = "0.4.10" @@ -1067,6 +1272,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "map-macro" version = "0.2.6" @@ -1085,6 +1299,24 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1108,6 +1340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1121,6 +1354,32 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1131,6 +1390,21 @@ 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.15.0" @@ -1141,6 +1415,24 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "object" version = "0.31.1" @@ -1152,9 +1444,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "outref" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "overload" @@ -1223,6 +1521,31 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -1241,6 +1564,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "promising-future" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44ba461c1b8785e502867026d893fa52801faccfbfe59efdae7da4b9094b4ce2" +dependencies = [ + "threadpool", +] + [[package]] name = "quote" version = "1.0.35" @@ -1250,6 +1582,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1426,6 +1764,23 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rustybuzz" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9e34ecf6900625412355a61bda0bd68099fe674de707c67e5e4aed2c05e489" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.13" @@ -1556,6 +1911,18 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slab" version = "0.4.8" @@ -1565,12 +1932,43 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d" +dependencies = [ + "smallvec", +] + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "socket2" version = "0.4.9" @@ -1597,6 +1995,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strsim" version = "0.10.0" @@ -1652,6 +2056,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.51" @@ -1682,6 +2092,72 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "libm", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1911,6 +2387,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "ttf-parser" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" + [[package]] name = "uni-path" version = "1.51.1" @@ -1932,6 +2414,24 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + [[package]] name = "unicode-ident" version = "1.0.9" @@ -1947,6 +2447,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + [[package]] name = "unsafe-libyaml" version = "0.2.10" @@ -1970,6 +2476,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8-width" version = "0.1.6" @@ -1994,6 +2506,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "want" version = "0.3.1" @@ -2091,6 +2609,12 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2198,6 +2722,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[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 = "zerocopy" version = "0.7.32" diff --git a/compiler-base/Cargo.toml b/compiler-base/Cargo.toml index e3e76983..003a114a 100644 --- a/compiler-base/Cargo.toml +++ b/compiler-base/Cargo.toml @@ -31,6 +31,8 @@ regen-lang = "0.0.7" tokio = { version = "1.36.0", features = [ "macros", ], optional = true} +once_cell = "1.19.0" +urlencoding = "2.1.3" # native dependencies diff --git a/compiler-base/src/env/env_native.rs b/compiler-base/src/env/env_native.rs index 38480994..8579afac 100644 --- a/compiler-base/src/env/env_native.rs +++ b/compiler-base/src/env/env_native.rs @@ -51,6 +51,18 @@ impl From> for RefCounted<[T]> { } } +impl From> for RefCounted +where + T: ?Sized, +{ + #[inline] + fn from(v: Box) -> Self { + Self { + inner: Arc::from(v), + } + } +} + pub async fn yield_budget(limit: u32) { if super::coop_tick_increment(limit) { tokio::task::yield_now().await; @@ -59,3 +71,24 @@ pub async fn yield_budget(limit: u32) { /// Wait for multiple futures to complete pub use tokio::join as join_futures; + +/// Spawn futures and collect the results in a vec in the same order +pub async fn join_future_vec(v: Vec) -> Vec> +where + TFuture: std::future::Future + Send + 'static, + TFuture::Output: Send + 'static, +{ + let len = v.len(); + let mut handles = Vec::with_capacity(len); + for future in v { + handles.push(tokio::spawn(future)); + } + let mut results = Vec::with_capacity(len); + for handle in handles { + match handle.await { + Ok(res) => results.push(Ok(res)), + Err(e) => results.push(Err(e.to_string())), + } + } + results +} diff --git a/compiler-base/src/env/env_wasm.rs b/compiler-base/src/env/env_wasm.rs index da06a8eb..9c1ed02e 100644 --- a/compiler-base/src/env/env_wasm.rs +++ b/compiler-base/src/env/env_wasm.rs @@ -47,6 +47,16 @@ impl From> for RefCounted<[T]> { } } +impl From> for RefCounted +where + T: ?Sized, +{ + #[inline] + fn from(v: Box) -> Self { + Self { inner: Rc::from(v) } + } +} + pub async fn yield_budget(limit: u32) { // on wasm we don't need to yield too often // multiply the limit by 4 to reduce the number of times we need to yield @@ -88,3 +98,17 @@ macro_rules! join_futures { }; } pub use join_futures; + +/// Spawn futures and collect the results in a vec in the same order +pub async fn join_future_vec(v: Vec) -> Vec> +where + TFuture: std::future::Future, +{ + // on wasm, since there is only one thread + // we will just run the futures sequentially + let mut results = Vec::with_capacity(v.len()); + for f in v { + results.push(Ok(f.await)); + } + results +} diff --git a/compiler-base/src/env/mod.rs b/compiler-base/src/env/mod.rs index f3e0785c..72d251a0 100644 --- a/compiler-base/src/env/mod.rs +++ b/compiler-base/src/env/mod.rs @@ -1,8 +1,11 @@ //! Environment setup or things that depend on the execution environment (server vs WASM). use std::cell::RefCell; +use std::fmt::Display; use std::ops::Deref; -use std::sync::OnceLock; + +use crate::macros::late_global; +use crate::res::Loader; #[cfg(feature = "wasm")] pub mod env_wasm; @@ -14,30 +17,40 @@ pub mod env_native; #[cfg(not(feature = "wasm"))] pub use env_native::*; -static SITE_ORIGIN: OnceLock = OnceLock::new(); - -/// Set the site origin globally if not already set -pub fn init_site_origin(origin: String) -> Result<(), String> { - SITE_ORIGIN.set(origin) -} +/// Site origin global configuration +#[late_global(str)] +pub mod site { + #[inline] + pub fn set_origin(origin: &str) { + let _ = set(RefCounted::from(origin)); + } -/// Get the site origin, or default to empty string -pub fn get_site_origin() -> &'static str { - match SITE_ORIGIN.get() { - Some(origin) => origin, - None => "", + #[inline] + pub fn get_origin() -> RefCounted { + match get() { + Some(origin) => origin, + None => RefCounted::from(""), + } } -} -/// Get the site domain (origin without url scheme) -pub fn get_site_domain() -> &'static str { - let origin = get_site_origin(); - match origin.strip_prefix("https://") { - Some(domain) => domain, - None => origin.strip_prefix("http://").unwrap_or(origin), + /// Get the site domain (origin without url scheme) + pub fn get_domain() -> RefCounted { + let origin = get_origin(); + match origin.strip_prefix("https://") { + Some(domain) => RefCounted::from(domain), + None => match origin.strip_prefix("http://") { + Some(domain) => RefCounted::from(domain), + None => origin, + }, + } } } +/// Global loader instance that can be used to load resources +/// outside of the usual compilation cycle. For example, in plugins +#[late_global(dyn Loader)] +pub mod global_loader {} + impl Deref for RefCounted where T: ?Sized, @@ -50,6 +63,16 @@ where } } +impl Display for RefCounted +where + T: Display + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + thread_local! { /// Current number of ticks ran without yielding in cooperative multitasking static COOP_TICKS: RefCell = RefCell::new(0); diff --git a/compiler-base/src/lang/diagnostics.rs b/compiler-base/src/lang/diagnostics.rs index ea4a4409..862fca37 100644 --- a/compiler-base/src/lang/diagnostics.rs +++ b/compiler-base/src/lang/diagnostics.rs @@ -86,7 +86,7 @@ where fn into_diagnostic(self) -> DocDiagnostic { let message = match self.help_path() { Some(path) => { - let site_origin = env::get_site_origin(); + let site_origin: &str = &env::site::get_origin(); let mut msg = format!("{self} See {site_origin}"); if !path.starts_with('/') { msg.push('/'); diff --git a/compiler-base/src/res/mod.rs b/compiler-base/src/res/mod.rs index 637dada4..4e2d2f4f 100644 --- a/compiler-base/src/res/mod.rs +++ b/compiler-base/src/res/mod.rs @@ -1,11 +1,9 @@ //! Resource resolving and loading -use std::ops::Deref; - -use base64::Engine; use serde_json::Value; use crate::env::RefCounted; use crate::macros::async_trait; +use crate::util; mod path; pub use path::*; @@ -156,12 +154,10 @@ where Some(x) if x.is_image() => x.media_type(), _ => return Err(ResError::UnknownImageFormat(self.path.to_string())), }; - // prepare the beginning of the data url - let mut data_url = format!("data:{media_type};base64,"); // load the bytes let bytes = self.loader.load_raw(&self.path).await?; - // encode the bytes and append to the data url - base64::engine::general_purpose::STANDARD.encode_string(bytes.deref(), &mut data_url); + // encode the bytes as a data url + let data_url = util::to_data_url_base64(media_type, &bytes); Ok(data_url) } diff --git a/compiler-base/src/res/path.rs b/compiler-base/src/res/path.rs index a48c38da..301e7764 100644 --- a/compiler-base/src/res/path.rs +++ b/compiler-base/src/res/path.rs @@ -21,7 +21,7 @@ pub enum ResPath<'u> { /// Local path, represented as a relative path from the "root" /// (i.e. without the leading "/") Local(PathBuf), - /// Remote path, represented as a URL prefix (with a trailing "/") + /// Remote path, represented as a URL prefix (empty, or ends with a trailing "/") /// and a relative path (without the leading "/") Remote(Cow<'u, str>, PathBuf), } @@ -68,9 +68,11 @@ impl<'u> ResPath<'u> { P: Into, { let url = url.into(); - debug_assert!(url.ends_with('/')); + debug_assert!(url.is_empty() || url.ends_with('/')); let path = path.into(); - debug_assert!(path.is_relative()); + if !url.is_empty() { + debug_assert!(path.is_relative()); + } Self::Remote(url, path) } diff --git a/compiler-base/src/util/data_url.rs b/compiler-base/src/util/data_url.rs new file mode 100644 index 00000000..cf21040d --- /dev/null +++ b/compiler-base/src/util/data_url.rs @@ -0,0 +1,41 @@ +//! Conversion between data url and bytes + +use std::borrow::Cow; + +use base64::Engine; + +pub fn to_data_url_base64(mime: &str, data: &[u8]) -> String { + let mut data_url = format!("data:{mime};base64,"); + base64::engine::general_purpose::STANDARD.encode_string(data, &mut data_url); + data_url +} + +#[derive(Debug, thiserror::Error)] +pub enum DataUrlParseError { + #[error("Data url should have `data:` prefix")] + InvalidPrefix, + #[error("Cannot determine data type from data url")] + InvalidType, + #[error("Cannot find data in data url")] + NoData, + #[error("Error decoding base64 from data url: {0}")] + InvalidBase64(#[from] base64::DecodeError), +} + +/// Decode data url to bytes. Supports base64 and URL encoding. +pub fn bytes_from_data_url(data_url: &str) -> Result, DataUrlParseError> { + let data = match data_url.strip_prefix("data:") { + Some(data) => data, + None => return Err(DataUrlParseError::InvalidPrefix), + }; + let mut parts = data.splitn(2, ','); + let type_and_encoding = parts.next().ok_or(DataUrlParseError::InvalidType)?; + let data = parts.next().ok_or(DataUrlParseError::NoData)?; + if type_and_encoding.ends_with(";base64") { + let data = base64::engine::general_purpose::STANDARD.decode(data)?; + return Ok(Cow::Owned(data)); + } + + let data = urlencoding::decode_binary(data.as_bytes()); + Ok(data) +} diff --git a/compiler-base/src/util/mod.rs b/compiler-base/src/util/mod.rs index 0d378cec..14ddfb4c 100644 --- a/compiler-base/src/util/mod.rs +++ b/compiler-base/src/util/mod.rs @@ -4,6 +4,8 @@ mod string_map; pub use string_map::*; mod escape; pub use escape::*; +mod data_url; +pub use data_url::*; // re-exports pub use uni_path::{Component, Path, PathBuf}; diff --git a/compiler-core/Cargo.toml b/compiler-core/Cargo.toml index 080d2fa2..275bc592 100644 --- a/compiler-core/Cargo.toml +++ b/compiler-core/Cargo.toml @@ -24,6 +24,7 @@ log = "0.4.20" roman = "0.1.6" flate2 = "1.0.28" base64 = "0.21.7" +livesplit-core = "0.13.0" [dev-dependencies] map-macro = "0.2.6" diff --git a/compiler-core/src/comp/iterator.rs b/compiler-core/src/comp/iterator.rs new file mode 100644 index 00000000..3e0d822b --- /dev/null +++ b/compiler-core/src/comp/iterator.rs @@ -0,0 +1,265 @@ +//! Iterators for CompDoc + +use crate::lang::DocRichTextBlock; + +use super::{CompDoc, CompLine, DocNote}; + +impl<'p> CompDoc<'p> { + /// Iterate over each CompLine of the CompDoc + pub fn lines(&self) -> impl Iterator { + self.route.iter().flat_map(|section| section.lines.iter()) + } + + /// Iterate over each CompLine of the CompDoc + pub fn lines_mut(&mut self) -> impl Iterator { + self.route + .iter_mut() + .flat_map(|section| section.lines.iter_mut()) + } + + /// Iterate over each DocRichText of the CompDoc. + /// + /// Preface and counter are excluded by default. Use the builder options + /// in the returned object to configure them + pub fn rich_texts(&self) -> RichTextIter<'_, 'p> { + RichTextIter::new(self) + } + + /// Iterate over each DocRichText of the CompDoc, except for the counter + /// + /// Preface and counter are excluded by default. Use the builder options + /// in the returned object to configure them + pub fn rich_texts_mut(&mut self) -> RichTextIterMut<'_, 'p> { + RichTextIterMut::new(self) + } +} + +impl CompLine { + /// Iterate over each DocRichText of the CompLine + /// + /// Counter is excluded by default. Use the builder options + /// in the returned object to configure them + pub fn rich_texts(&self) -> LineRichTextIter { + LineRichTextIter::new(self) + } + + /// Iterate over each DocRichText of the CompLine, except for the counter + /// + /// Counter is excluded by default. Use the builder options + /// in the returned object to configure them + pub fn rich_texts_mut(&mut self) -> LineRichTextIterMut { + LineRichTextIterMut::new(self) + } +} + +/// Implementation of the rich text iterators +pub struct RichTextIter<'c, 'p> { + doc: &'c CompDoc<'p>, + with_preface: bool, + with_counter: bool, +} +pub struct RichTextIterMut<'c, 'p> { + doc: &'c mut CompDoc<'p>, + with_preface: bool, + with_counter: bool, +} +pub struct LineRichTextIter<'c> { + line: &'c CompLine, + with_counter: bool, +} +pub struct LineRichTextIterMut<'c> { + line: &'c mut CompLine, + with_counter: bool, +} + +impl<'c, 'p> RichTextIter<'c, 'p> { + pub fn new(doc: &'c CompDoc<'p>) -> Self { + Self { + doc, + with_preface: false, + with_counter: false, + } + } + pub fn with_preface(mut self) -> Self { + self.with_preface = true; + self + } + pub fn with_counter(mut self) -> Self { + self.with_counter = true; + self + } +} +impl<'c, 'p> RichTextIterMut<'c, 'p> { + pub fn new(doc: &'c mut CompDoc<'p>) -> Self { + Self { + doc, + with_preface: false, + with_counter: false, + } + } + pub fn with_preface(mut self) -> Self { + self.with_preface = true; + self + } + pub fn with_counter(mut self) -> Self { + self.with_counter = true; + self + } +} +impl<'c> LineRichTextIter<'c> { + pub fn new(line: &'c CompLine) -> Self { + Self { + line, + with_counter: false, + } + } + pub fn with_counter(mut self) -> Self { + self.with_counter = true; + self + } +} +impl<'c> LineRichTextIterMut<'c> { + pub fn new(line: &'c mut CompLine) -> Self { + Self { + line, + with_counter: false, + } + } + pub fn with_counter(mut self) -> Self { + self.with_counter = true; + self + } +} + +impl<'c, 'p> IntoIterator for RichTextIter<'c, 'p> { + type Item = &'c DocRichTextBlock; + type IntoIter = DynIter<'c, Self::Item>; + fn into_iter(self) -> Self::IntoIter { + let iter = self.doc.route.iter().flat_map(move |section| { + section + .lines + .iter() + .flat_map(move |line| rich_text_iter(line, self.with_counter)) + }); + if self.with_preface { + let preface_iter = self.doc.preface.iter().flat_map(|text| text.iter()); + return DynIter::new(preface_iter.chain(iter)); + } + DynIter::new(iter) + } +} +impl<'c, 'p> IntoIterator for RichTextIterMut<'c, 'p> { + type Item = &'c mut DocRichTextBlock; + type IntoIter = DynIter<'c, Self::Item>; + fn into_iter(self) -> Self::IntoIter { + let iter = self.doc.route.iter_mut().flat_map(move |section| { + section + .lines + .iter_mut() + .flat_map(move |line| rich_text_iter_mut(line, self.with_counter)) + }); + if self.with_preface { + let preface_iter = self.doc.preface.iter_mut().flat_map(|text| text.iter_mut()); + return DynIter::new(preface_iter.chain(iter)); + } + DynIter::new(iter) + } +} +impl<'c> IntoIterator for LineRichTextIter<'c> { + type Item = &'c DocRichTextBlock; + type IntoIter = DynIter<'c, Self::Item>; + fn into_iter(self) -> Self::IntoIter { + let iter = rich_text_iter(self.line, self.with_counter); + DynIter::new(iter) + } +} +impl<'c> IntoIterator for LineRichTextIterMut<'c> { + type Item = &'c mut DocRichTextBlock; + type IntoIter = DynIter<'c, Self::Item>; + fn into_iter(self) -> Self::IntoIter { + let iter = rich_text_iter_mut(self.line, self.with_counter); + DynIter::new(iter) + } +} + +fn rich_text_iter(line: &CompLine, with_counter: bool) -> impl Iterator { + let iter = line + .text + .iter() + .chain(line.secondary_text.iter()) + .chain(line.notes.iter().flat_map(|note| match note { + DocNote::Text { content } => DynIter::new(content.iter()), + _ => DynIter::new(std::iter::empty()), + })) + .chain({ + if !with_counter { + DynIter::new(std::iter::empty()) + } else if let Some(counter) = &line.counter_text { + DynIter::new(std::iter::once(counter)) + } else { + DynIter::new(std::iter::empty()) + } + }) + .chain({ + if let Some(split_name) = &line.split_name { + DynIter::new(split_name.iter()) + } else { + DynIter::new(std::iter::empty()) + } + }); + + iter +} + +fn rich_text_iter_mut( + line: &mut CompLine, + with_counter: bool, +) -> impl Iterator { + let iter = line + .text + .iter_mut() + .chain(line.secondary_text.iter_mut()) + .chain(line.notes.iter_mut().flat_map(|note| match note { + DocNote::Text { content } => DynIter::new(content.iter_mut()), + _ => DynIter::new(std::iter::empty()), + })) + .chain({ + if !with_counter { + DynIter::new(std::iter::empty()) + } else if let Some(counter) = &mut line.counter_text { + DynIter::new(std::iter::once(counter)) + } else { + DynIter::new(std::iter::empty()) + } + }) + .chain({ + if let Some(split_name) = &mut line.split_name { + DynIter::new(split_name.iter_mut()) + } else { + DynIter::new(std::iter::empty()) + } + }); + + iter +} + +pub struct DynIter<'c, T>(Box + 'c + Send + Sync>) +where + T: 'c; + +impl<'c, T> DynIter<'c, T> { + fn new(iter: I) -> Self + where + I: Iterator + 'c + Send + Sync, + { + Self(Box::new(iter)) + } +} + +impl<'c, T> Iterator for DynIter<'c, T> { + type Item = T; + + fn next(&mut self) -> Option { + self.0.next() + } +} diff --git a/compiler-core/src/comp/mod.rs b/compiler-core/src/comp/mod.rs index ec8cd65c..8c1d3943 100644 --- a/compiler-core/src/comp/mod.rs +++ b/compiler-core/src/comp/mod.rs @@ -30,6 +30,8 @@ mod comp_section; pub use comp_section::*; mod comp_line; pub use comp_line::*; +mod iterator; +pub use iterator::*; #[cfg(test)] mod test_utils; @@ -55,14 +57,14 @@ impl<'p> Compiler<'p> { /// Entry point for the comp phase pub async fn compile(mut self) -> CompDoc<'p> { for plugin in &mut self.plugin_runtimes { - if let Err(e) = plugin.on_before_compile(&mut self.ctx) { + if let Err(e) = plugin.on_before_compile(&mut self.ctx).await { return CompDoc::from_diagnostic(CompError::PluginBeforeCompileError(e), self.ctx); } } let mut plugins = std::mem::take(&mut self.plugin_runtimes); let mut comp_doc = self.compile_document().await; for plugin in &mut plugins { - if let Err(e) = plugin.on_after_compile(&mut comp_doc) { + if let Err(e) = plugin.on_after_compile(&mut comp_doc).await { let diag = CompError::PluginAfterCompileError(e).into_diagnostic(); comp_doc.diagnostics.push(diag); } diff --git a/compiler-core/src/exec/mod.rs b/compiler-core/src/exec/mod.rs index 716e3f1b..f646b493 100644 --- a/compiler-core/src/exec/mod.rs +++ b/compiler-core/src/exec/mod.rs @@ -63,7 +63,7 @@ impl<'p> CompDoc<'p> { let plugin_metadata = std::mem::take(&mut self.plugin_meta); let mut exec_doc = self.execute_document().await; for plugin in &mut plugins { - if let Err(e) = plugin.on_after_execute(&mut exec_doc) { + if let Err(e) = plugin.on_after_execute(&mut exec_doc).await { let diag = e.into_diagnostic(); exec_doc.diagnostics.push(diag); } diff --git a/compiler-core/src/expo/mod.rs b/compiler-core/src/expo/mod.rs index 02707be4..cff4d823 100644 --- a/compiler-core/src/expo/mod.rs +++ b/compiler-core/src/expo/mod.rs @@ -113,10 +113,10 @@ macro_rules! export_error { } impl<'p> ExecContext<'p> { - pub fn prepare_exports(mut self) -> ExpoContext<'p> { + pub async fn prepare_exports(mut self) -> ExpoContext<'p> { let mut result = Vec::new(); for plugin in &mut self.plugin_runtimes { - if let Ok(Some(meta)) = plugin.on_prepare_export() { + if let Ok(Some(meta)) = plugin.on_prepare_export().await { result.extend(meta); } } @@ -132,12 +132,15 @@ impl<'p> CompDoc<'p> { /// /// Returning `Some` means the export was successful. Returning `None` means the export is /// pending and needed to run in the exec phase - pub fn run_exporter(&mut self, req: &ExportRequest) -> Option { + pub async fn run_exporter(&mut self, req: &ExportRequest) -> Option { let mut plugins = std::mem::take(&mut self.plugin_runtimes); for plugin in &mut plugins { if req.plugin_id == plugin.get_id() { - let result = match plugin.on_export_comp_doc(&req.export_id, &req.payload, self) { + let result = match plugin + .on_export_comp_doc(&req.export_id, &req.payload, self) + .await + { Ok(None) => None, Ok(Some(expo_doc)) => Some(expo_doc), Err(e) => Some(ExpoDoc::Error(e.to_string())), @@ -157,16 +160,18 @@ impl<'p> CompDoc<'p> { impl<'p> ExecContext<'p> { /// Run the export request on this document after the exec phase - pub fn run_exporter(self, req: ExportRequest) -> ExpoDoc { + pub async fn run_exporter(self, req: ExportRequest) -> ExpoDoc { let mut plugins = self.plugin_runtimes; for plugin in &mut plugins { if req.plugin_id == plugin.get_id() { - let result = - match plugin.on_export_exec_doc(&req.export_id, req.payload, &self.exec_doc) { - Ok(expo_doc) => expo_doc, - Err(e) => ExpoDoc::Error(e.to_string()), - }; + let result = match plugin + .on_export_exec_doc(&req.export_id, req.payload, &self.exec_doc) + .await + { + Ok(expo_doc) => expo_doc, + Err(e) => ExpoDoc::Error(e.to_string()), + }; return result; } } diff --git a/compiler-core/src/plugin/builtin/botw_unstable.rs b/compiler-core/src/plugin/builtin/botw_unstable.rs index f913125d..9dac36a4 100644 --- a/compiler-core/src/plugin/builtin/botw_unstable.rs +++ b/compiler-core/src/plugin/builtin/botw_unstable.rs @@ -5,9 +5,11 @@ use std::borrow::Cow; use serde_json::Value; use crate::comp::{CompDoc, CompLine, CompMovement}; +use crate::env::yield_budget; use crate::json::Coerce; use crate::lang::{DocDiagnostic, DocRichText, DocRichTextBlock}; -use crate::plugin::{operation, PluginResult, PluginRuntime}; +use crate::macros::async_trait; +use crate::plugin::{PluginResult, PluginRuntime}; const FURY: &str = "fury"; const GALE: &str = "gale"; @@ -220,9 +222,15 @@ impl BotwAbilityUnstablePlugin { self.set_in_castle(x); } - operation::for_each_rich_text_except_counter!(block in line { - self.process_block(block, &gale_override, &fury_override, &mut line.diagnostics); - }); + // take diagnostics out due to borrow restrictions + let mut diagnostics = std::mem::take(&mut line.diagnostics); + + for block in line.rich_texts_mut() { + self.process_block(block, &gale_override, &fury_override, &mut diagnostics); + } + + // put diagnostics back + std::mem::swap(&mut line.diagnostics, &mut diagnostics); } fn process_block( @@ -393,11 +401,13 @@ fn estimate_time(text: &DocRichText) -> u32 { movement_count * 14 + 6 // (approximately) same timing as old celer } +#[async_trait(auto)] impl PluginRuntime for BotwAbilityUnstablePlugin { - fn on_after_compile(&mut self, comp_doc: &mut CompDoc) -> PluginResult<()> { - operation::for_each_line!(line in comp_doc { + async fn on_after_compile<'p>(&mut self, comp_doc: &mut CompDoc<'p>) -> PluginResult<()> { + for line in comp_doc.lines_mut() { + yield_budget(64).await; self.process_line(line); - }); + } Ok(()) } diff --git a/compiler-core/src/plugin/builtin/export_livesplit.rs b/compiler-core/src/plugin/builtin/export_livesplit.rs index 6d1f2675..5e6d79d1 100644 --- a/compiler-core/src/plugin/builtin/export_livesplit.rs +++ b/compiler-core/src/plugin/builtin/export_livesplit.rs @@ -1,24 +1,29 @@ //! Exporter plugin for LiveSplit split files use std::borrow::Cow; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; +use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::comp::{CompDoc, CompLine}; +use crate::comp::{CompDoc, CompLine, CompSection}; +use crate::env::{self, yield_budget, RefCounted}; use crate::expo::{ExpoBlob, ExpoDoc, ExportIcon, ExportMetadata}; +use crate::export_error; use crate::json::Coerce; +use crate::macros::async_trait; use crate::plugin::{PluginResult, PluginRuntime}; -use crate::{export_error, util}; +use crate::res::ResPath; pub struct ExportLiveSplitPlugin; +#[async_trait(auto)] impl PluginRuntime for ExportLiveSplitPlugin { fn get_id(&self) -> Cow<'static, str> { Cow::Owned(super::BuiltInPlugin::ExportLiveSplit.id()) } - fn on_prepare_export(&mut self) -> PluginResult>> { + async fn on_prepare_export(&mut self) -> PluginResult>> { let metadata = ExportMetadata { plugin_id: self.get_id().into_owned(), name: "LiveSplit".to_string(), @@ -32,11 +37,11 @@ impl PluginRuntime for ExportLiveSplitPlugin { Ok(Some(vec![metadata])) } - fn on_export_comp_doc( + async fn on_export_comp_doc<'p>( &mut self, _: &str, payload: &Value, - doc: &CompDoc, + doc: &CompDoc<'p>, ) -> PluginResult> { let payload = match payload.as_object() { Some(payload) => payload, @@ -50,11 +55,10 @@ impl PluginRuntime for ExportLiveSplitPlugin { .get("subsplits") .map(|x| x.coerce_truthy()) .unwrap_or(false); - - // TODO #190: encode icon - if icon { - return export_error!("Icon export is not supported yet."); - } + let webp_compat = match payload.get("webp-compat") { + None => WebpCompat::Error, + Some(x) => serde_json::from_value(x.clone()).unwrap_or(WebpCompat::Error), + }; // build relevant split types let mut split_types = BTreeSet::new(); @@ -79,16 +83,41 @@ impl PluginRuntime for ExportLiveSplitPlugin { return export_error!("No splits to export. Make sure you selected at least one split type in the settings."); } - let mut segments_xml = String::new(); + // build lines to split + let mut split_sections = Vec::with_capacity(doc.route.len()); for section in &doc.route { let mut split_lines = vec![]; for line in §ion.lines { + yield_budget(64).await; if should_split_on(line, &split_types) { split_lines.push(line); } } + if !split_lines.is_empty() { + split_sections.push((section, split_lines)); + } + } + + if split_sections.is_empty() { + return export_error!("No splits to export. Make sure you selected at least one split type in the settings."); + } + + // build icon cache + let icon_cache = if icon { + match build_icon_cache(doc, &split_sections, webp_compat).await { + Ok(cache) => cache, + Err(e) => return export_error!(e), + } + } else { + BTreeMap::new() + }; + + let mut run = livesplit_core::Run::new(); + + for (section, split_lines) in split_sections { let length = split_lines.len(); for (i, line) in split_lines.iter().enumerate() { + yield_budget(64).await; let mut name = match &line.split_name { Some(name) => name.to_string(), None => line.text.to_string(), @@ -100,39 +129,16 @@ impl PluginRuntime for ExportLiveSplitPlugin { name = format!("-{name}"); } } - append_segment(&mut segments_xml, &name, line); + let segment = create_segment(line, &name, icon, &icon_cache); + run.push_segment(segment); } } - if segments_xml.is_empty() { - return export_error!("No splits to export. Make sure you selected at least one split type in the settings."); + let mut file_content = String::new(); + if let Err(e) = livesplit_core::run::saver::livesplit::save_run(&run, &mut file_content) { + return export_error!(format!("Failed to export to split file: {e}")); } - let file_content = format!( - "\ -\ -\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - 00:00:00\ - 0\ - \ - \ - {segments_xml}\ - \ - \ -\ - " - ); - let file_name = format!("{}.lss", doc.config.meta.title); Ok(Some(ExpoDoc::Success { @@ -155,20 +161,98 @@ fn should_split_on(line: &CompLine, split_types: &BTreeSet) -> bool { split_types.contains(tag) } -fn append_segment(output: &mut String, name: &str, _line: &CompLine) { - output.push_str( - " \ - ", - ); - output.push_str(util::xml_escape(name).as_ref()); - output.push_str(""); - // TODO #190: encode icon - output.push_str( - "\ - \ - \ - \ - \ - ", - ); +async fn build_icon_cache( + doc: &CompDoc<'_>, + split_sections: &[(&CompSection, Vec<&CompLine>)], + webp_compat: WebpCompat, +) -> Result>, String> { + let mut icon_seen = BTreeSet::new(); + let mut icon_futures = vec![]; + for (_, lines) in split_sections { + for line in lines { + yield_budget(64).await; + let icon_id = match &line.doc_icon { + Some(icon_id) => icon_id, + None => continue, + }; + if !icon_seen.insert(icon_id) { + continue; + } + if let Some(icon_url) = doc.config.icons.get(icon_id) { + icon_futures.push(load_icon( + icon_id.to_string(), + icon_url.to_string(), + webp_compat, + )) + } + } + } + let results = env::join_future_vec(icon_futures).await; + let mut cache = BTreeMap::new(); + for result in results { + match result { + Ok(Ok((id, bytes))) => { + cache.insert(id, bytes.to_vec()); + } + Ok(Err(e)) | Err(e) => return Err(e), + } + } + Ok(cache) +} + +fn create_segment( + line: &CompLine, + name: &str, + include_icon: bool, + icon_cache: &BTreeMap>, +) -> livesplit_core::Segment { + let mut segment = livesplit_core::Segment::new(name); + if include_icon { + if let Some(icon_id) = &line.doc_icon { + if let Some(icon_bytes) = icon_cache.get(icon_id) { + segment.set_icon(icon_bytes); + } + } + } + + segment +} + +/// Compability mode for WebP +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +enum WebpCompat { + /// Emit error. This is the default + Error, + /// Skip the icon + Skip, +} + +async fn load_icon( + icon_id: String, + icon_url: String, + webp_compat: WebpCompat, +) -> Result<(String, RefCounted<[u8]>), String> { + let loader = match env::global_loader::get() { + None => { + return Err( + "No global loader available to load the icons for split export!".to_string(), + ) + } + Some(loader) => loader, + }; + + let path = ResPath::new_remote_unchecked("", &icon_url); + let data = match loader.load_raw(&path).await { + Ok(data) => data, + Err(e) => return Err(format!("Failed to load icon `{icon_url}`: {e}")), + }; + + if data.starts_with(b"RIFF") { + if let WebpCompat::Error = webp_compat { + return Err(format!("Failed to load icon `{icon_url}`: RIFF (webp) icons are not supported by LiveSplit. Set the option `webp-compat: skip` to skip invalid webp icons.")); + } + } + + Ok((icon_id, data)) } diff --git a/compiler-core/src/plugin/builtin/link.rs b/compiler-core/src/plugin/builtin/link.rs index 6d944dd1..122ec6cd 100644 --- a/compiler-core/src/plugin/builtin/link.rs +++ b/compiler-core/src/plugin/builtin/link.rs @@ -5,16 +5,20 @@ use std::borrow::Cow; use crate::comp::CompDoc; +use crate::env::yield_budget; use crate::lang::DocRichTextBlock; +use crate::macros::async_trait; use crate::pack::CompileContext; use crate::prep::{DocTag, DocTagColor}; use crate::prop; -use crate::plugin::{operation, PluginResult, PluginRuntime}; +use crate::plugin::{PluginResult, PluginRuntime}; pub struct LinkPlugin; + +#[async_trait(auto)] impl PluginRuntime for LinkPlugin { - fn on_before_compile(&mut self, ctx: &mut CompileContext) -> PluginResult<()> { + async fn on_before_compile<'p>(&mut self, ctx: &mut CompileContext<'p>) -> PluginResult<()> { // add the link tag if not defined already let link_tag = DocTag { color: Some(DocTagColor::LightDark { @@ -35,20 +39,12 @@ impl PluginRuntime for LinkPlugin { .or_insert(link_tag); Ok(()) } - fn on_after_compile(&mut self, comp_doc: &mut CompDoc) -> PluginResult<()> { - for preface in comp_doc.preface.iter_mut() { - for block in preface.iter_mut() { - transform_link_tag(block); - } + + async fn on_after_compile<'p>(&mut self, comp_doc: &mut CompDoc<'p>) -> PluginResult<()> { + for tag in comp_doc.rich_texts_mut().with_preface().with_counter() { + yield_budget(256).await; + transform_link_tag(tag); } - operation::for_each_line!(line in comp_doc { - operation::for_each_rich_text_except_counter!(rich_text in line { - transform_link_tag(rich_text); - }); - if let Some(t) = line.counter_text.as_mut() { - transform_link_tag(t); - } - }); Ok(()) } diff --git a/compiler-core/src/plugin/builtin/metrics.rs b/compiler-core/src/plugin/builtin/metrics.rs index c0df8599..00477d3f 100644 --- a/compiler-core/src/plugin/builtin/metrics.rs +++ b/compiler-core/src/plugin/builtin/metrics.rs @@ -6,6 +6,7 @@ use serde_json::Value; use crate::comp::CompDoc; use crate::exec::ExecDoc; use crate::json::Coerce; +use crate::macros::async_trait; use crate::prop; use crate::plugin::{PluginResult, PluginRuntime}; @@ -50,8 +51,9 @@ impl MetricsPlugin { } } +#[async_trait(auto)] impl PluginRuntime for MetricsPlugin { - fn on_after_compile(&mut self, _: &mut CompDoc) -> PluginResult<()> { + async fn on_after_compile<'p>(&mut self, _: &mut CompDoc<'p>) -> PluginResult<()> { // measure time since plugin created = comp phase time if self.detailed { self.comp_time_ms = self.last_start_time.elapsed().as_millis() as u64; @@ -59,7 +61,7 @@ impl PluginRuntime for MetricsPlugin { } Ok(()) } - fn on_after_execute(&mut self, doc: &mut ExecDoc) -> PluginResult<()> { + async fn on_after_execute<'p>(&mut self, doc: &mut ExecDoc<'p>) -> PluginResult<()> { // measure time since comp finished = exec time let exec_time_ms = self.last_start_time.elapsed().as_millis() as u64; let project = doc.project.to_mut(); diff --git a/compiler-core/src/plugin/builtin/split_format.rs b/compiler-core/src/plugin/builtin/split_format.rs index aa407505..e9c7bad9 100644 --- a/compiler-core/src/plugin/builtin/split_format.rs +++ b/compiler-core/src/plugin/builtin/split_format.rs @@ -8,9 +8,11 @@ use std::collections::BTreeMap; use serde_json::Value; use crate::comp::{CompDoc, CompLine}; +use crate::env::yield_budget; use crate::json::Coerce; use crate::lang::{self, DocRichText}; -use crate::plugin::{operation, PluginResult, PluginRuntime}; +use crate::macros::async_trait; +use crate::plugin::{PluginResult, PluginRuntime}; pub struct SplitFormatPlugin { formats: BTreeMap, @@ -28,11 +30,13 @@ impl SplitFormatPlugin { } } +#[async_trait(auto)] impl PluginRuntime for SplitFormatPlugin { fn get_id(&self) -> Cow<'static, str> { Cow::Owned(super::BuiltInPlugin::SplitFormat.id()) } - fn on_after_compile(&mut self, comp_doc: &mut CompDoc) -> PluginResult<()> { + + async fn on_after_compile<'p>(&mut self, comp_doc: &mut CompDoc<'p>) -> PluginResult<()> { let mut tag_to_format = BTreeMap::new(); for (tag_name, tag) in comp_doc.config.tags.iter() { if let Some(split_type) = &tag.split_type { @@ -41,7 +45,8 @@ impl PluginRuntime for SplitFormatPlugin { } } } - operation::for_each_line!(line in comp_doc { + for line in comp_doc.lines_mut() { + yield_budget(64).await; let mut format = None; if let Some(counter) = &line.counter_text { if let Some(tag) = &counter.tag { @@ -57,7 +62,7 @@ impl PluginRuntime for SplitFormatPlugin { transform_format(&mut format, line); line.split_name = Some(format); } - }); + } Ok(()) } diff --git a/compiler-core/src/plugin/builtin/variables/mod.rs b/compiler-core/src/plugin/builtin/variables/mod.rs index 838cd51e..0b722fbb 100644 --- a/compiler-core/src/plugin/builtin/variables/mod.rs +++ b/compiler-core/src/plugin/builtin/variables/mod.rs @@ -6,10 +6,12 @@ use std::collections::HashMap; use serde_json::{json, Map, Value}; use crate::comp::CompDoc; +use crate::env::yield_budget; use crate::json::Coerce; use crate::lang::{self, DocDiagnostic, DocRichTextBlock}; +use crate::macros::async_trait; use crate::pack::CompileContext; -use crate::plugin::{operation, PluginResult, PluginRuntime}; +use crate::plugin::{PluginResult, PluginRuntime}; use crate::prep::{DocTag, DocTagColor}; use crate::prop; @@ -274,8 +276,9 @@ impl VariablesPlugin { } } +#[async_trait(auto)] impl PluginRuntime for VariablesPlugin { - fn on_before_compile(&mut self, ctx: &mut CompileContext) -> PluginResult<()> { + async fn on_before_compile<'p>(&mut self, ctx: &mut CompileContext<'p>) -> PluginResult<()> { // add the val tag if not defined already let tag = DocTag { color: Some(DocTagColor::LightDark { @@ -292,32 +295,36 @@ impl PluginRuntime for VariablesPlugin { Ok(()) } - fn on_after_compile(&mut self, comp_doc: &mut CompDoc) -> PluginResult<()> { + async fn on_after_compile<'p>(&mut self, comp_doc: &mut CompDoc<'p>) -> PluginResult<()> { comp_doc.known_props.insert(prop::VARS.to_string()); comp_doc.known_props.insert(prop::VALS.to_string()); for preface in comp_doc.preface.iter_mut() { for block in preface.iter_mut() { + yield_budget(256).await; self.transform_text(&mut comp_doc.diagnostics, block, VAL); } } - operation::for_each_line!(line in comp_doc { + for line in comp_doc.lines_mut() { + let mut diagnostics = std::mem::take(&mut line.diagnostics); if let Some(vars) = line.properties.get(prop::VARS) { - self.update_vars(&mut line.diagnostics, vars); + self.update_vars(&mut diagnostics, vars); } if let Some(t) = line.counter_text.as_mut() { let tag = t.text.to_string(); self.increment(&tag); - self.transform_text(&mut line.diagnostics, t, &tag); + self.transform_text(&mut diagnostics, t, &tag); } - operation::for_each_rich_text_except_counter!(block in line { - self.transform_text(&mut line.diagnostics, block, VAL); - }); + for block in line.rich_texts_mut() { + self.transform_text(&mut diagnostics, block, VAL); + } + std::mem::swap(&mut line.diagnostics, &mut diagnostics); if self.expose { - line.properties.insert(prop::VALS.to_string(), self.get_vals()); + line.properties + .insert(prop::VALS.to_string(), self.get_vals()); } self.clear_temporary(); - }); + } Ok(()) } diff --git a/compiler-core/src/plugin/mod.rs b/compiler-core/src/plugin/mod.rs index c5667427..91dc84dd 100644 --- a/compiler-core/src/plugin/mod.rs +++ b/compiler-core/src/plugin/mod.rs @@ -5,14 +5,13 @@ use serde_json::Value; use crate::comp::CompDoc; use crate::exec::ExecDoc; use crate::expo::{ExpoDoc, ExportMetadata}; -use crate::macros::derive_wasm; +use crate::macros::{async_trait, derive_wasm}; use crate::pack::CompileContext; mod error; pub use error::*; mod builtin; mod js; -mod operation; mod option; pub use option::*; mod parse; @@ -38,7 +37,8 @@ pub struct PluginMetadata { /// /// A runtime of a plugin can store states that the plugin needs during the compilation. /// Each compilation will spawn a new runtime with [`PluginInstance::create_runtime`] -pub trait PluginRuntime: Send + Sync { +#[async_trait(auto)] +pub trait PluginRuntime { /// Get the id of the plugin /// /// This is used to identify the plugin, and should be either the name of @@ -53,22 +53,22 @@ pub trait PluginRuntime: Send + Sync { } /// Called before route is compiled, to make changes to the compiler - fn on_before_compile(&mut self, _ctx: &mut CompileContext) -> PluginResult<()> { + async fn on_before_compile<'p>(&mut self, _ctx: &mut CompileContext<'p>) -> PluginResult<()> { Ok(()) } /// Called after the route is compiled, to transform the route - fn on_after_compile(&mut self, _doc: &mut CompDoc) -> PluginResult<()> { + async fn on_after_compile<'p>(&mut self, _doc: &mut CompDoc<'p>) -> PluginResult<()> { Ok(()) } /// Called after the route is turned into ExecDoc - fn on_after_execute(&mut self, _doc: &mut ExecDoc) -> PluginResult<()> { + async fn on_after_execute<'p>(&mut self, _doc: &mut ExecDoc<'p>) -> PluginResult<()> { Ok(()) } /// Called at the end of compilation to check what exports are available - fn on_prepare_export(&mut self) -> PluginResult>> { + async fn on_prepare_export(&mut self) -> PluginResult>> { Ok(None) } @@ -77,11 +77,11 @@ pub trait PluginRuntime: Send + Sync { /// If the exporter needs to access the ExecDoc as well, it should return `None`. /// Otherwise, the returned export data will be used and the exporter will not be called /// with the ExecDoc - fn on_export_comp_doc( + async fn on_export_comp_doc<'p>( &mut self, _export_id: &str, _payload: &Value, - _doc: &CompDoc, + _doc: &CompDoc<'p>, ) -> PluginResult> { Ok(None) } @@ -89,7 +89,7 @@ pub trait PluginRuntime: Send + Sync { /// Called only in export workflow, to let the exporter access the ExecDoc /// /// The exporter must return the export data or throw an error - fn on_export_exec_doc( + async fn on_export_exec_doc( &mut self, _export_id: &str, _payload: Value, diff --git a/compiler-core/src/plugin/operation.rs b/compiler-core/src/plugin/operation.rs deleted file mode 100644 index dc53020f..00000000 --- a/compiler-core/src/plugin/operation.rs +++ /dev/null @@ -1,39 +0,0 @@ -/// Transform all [`CompLine`] in a document -macro_rules! for_each_line { - ($line:ident in $comp_doc:ident $fun:block) => { - for section in $comp_doc.route.iter_mut() { - #[allow(unused_mut)] - for $line in section.lines.iter_mut() { - $fun; - } - } - }; -} -pub(crate) use for_each_line; - -macro_rules! for_each_rich_text_except_counter { - ($t:ident in $comp_line:ident $fun:block) => { - for $t in $comp_line.text.iter_mut() { - $fun; - } - for $t in $comp_line.secondary_text.iter_mut() { - $fun; - } - for note in $comp_line.notes.iter_mut() { - match note { - $crate::comp::DocNote::Text { content } => { - for $t in content.iter_mut() { - $fun; - } - } - _ => {} - } - } - if let Some(v) = $comp_line.split_name.as_mut() { - for $t in v.iter_mut() { - $fun; - } - } - }; -} -pub(crate) use for_each_rich_text_except_counter; diff --git a/compiler-macros/src/late_global_impl.rs b/compiler-macros/src/late_global_impl.rs new file mode 100644 index 00000000..1e92f64b --- /dev/null +++ b/compiler-macros/src/late_global_impl.rs @@ -0,0 +1,77 @@ +//! Implementation for expanding `late_global` + +use proc_macro::TokenStream; +use syn::ItemMod; + +use crate::util; + +type TokenStream2 = proc_macro2::TokenStream; + +pub fn expand(attr: TokenStream, input: TokenStream) -> TokenStream { + let celerc = util::compiler_crate_ident(); + let typ: TokenStream2 = attr.into(); + let parsed_mod = syn::parse_macro_input!(input as ItemMod); + + let vis = &parsed_mod.vis; + let name = &parsed_mod.ident; + let attrs = parsed_mod + .attrs + .into_iter() + .map(|attr| { + quote::quote! { + #attr + } + }) + .collect::(); + + let content = match parsed_mod.content { + Some((_, content)) => content + .into_iter() + .map(|item| { + quote::quote! { + #item + } + }) + .collect::(), + None => quote::quote!(), + }; + + // ref counting is used because thread_local does not have static lifetime + + let expanded = quote::quote! { + #[automatically_derived] + #attrs + #vis mod #name { + use super::*; + use #celerc::env::RefCounted; + #[cfg(not(feature = "wasm"))] + static GLOBAL: std::sync::OnceLock> = std::sync::OnceLock::new(); + #[cfg(not(feature = "wasm"))] + /// Get the late global value + pub fn get() -> Option> { + GLOBAL.get().map(RefCounted::clone) + } + #[cfg(not(feature = "wasm"))] + /// Set the late global value + pub fn set(val: RefCounted<#typ>) -> Result<(), RefCounted<#typ>> { GLOBAL.set(val) } + + #[cfg(feature = "wasm")] + thread_local! { + static GLOBAL: once_cell::unsync::OnceCell> = once_cell::unsync::OnceCell::new(); + } + #[cfg(feature = "wasm")] + /// Get the late global value (cloned) + pub fn get() -> Option> { + GLOBAL.with(|cell| cell.get().map(RefCounted::clone)) + } + #[cfg(feature = "wasm")] + /// Set the late global value + pub fn set(val: RefCounted<#typ>) -> Result<(), RefCounted<#typ>> { + GLOBAL.with(|cell| cell.set(val)) + } + #content + } + }; + + expanded.into() +} diff --git a/compiler-macros/src/lib.rs b/compiler-macros/src/lib.rs index 788ee48c..44d170eb 100644 --- a/compiler-macros/src/lib.rs +++ b/compiler-macros/src/lib.rs @@ -111,3 +111,20 @@ pub fn derive_opaque(attr: TokenStream, input: TokenStream) -> TokenStream { derive_opaque_impl::expand(attr, input) } mod derive_opaque_impl; + +/// A wrapper to put globals that are not `Send + Sync` in a `thread_local!` wrapper +/// when feature = "wasm" +/// +/// # Example +/// ```ignore +/// #[late_global(String)] +/// mod my_global; +/// +/// my_global::set("hello".to_string()); +/// my_global::get(); // returns Option<&String> +/// ``` +#[proc_macro_attribute] +pub fn late_global(attr: TokenStream, input: TokenStream) -> TokenStream { + late_global_impl::expand(attr, input) +} +mod late_global_impl; diff --git a/compiler-wasm/src/compiler/export.rs b/compiler-wasm/src/compiler/export.rs index 3a2fe00c..97d7b3a6 100644 --- a/compiler-wasm/src/compiler/export.rs +++ b/compiler-wasm/src/compiler/export.rs @@ -67,9 +67,9 @@ fn export_with_pack_error(error: PackError) -> ExpoDoc { async fn export_with_compiler(compiler: Compiler<'_>, req: ExportRequest) -> ExpoDoc { let mut comp_doc = compiler.compile().await; - if let Some(expo_doc) = comp_doc.run_exporter(&req) { + if let Some(expo_doc) = comp_doc.run_exporter(&req).await { return expo_doc; } let exec_ctx = comp_doc.execute().await; - exec_ctx.run_exporter(req) + exec_ctx.run_exporter(req).await } diff --git a/compiler-wasm/src/compiler/mod.rs b/compiler-wasm/src/compiler/mod.rs index 311bf655..6c97a4b0 100644 --- a/compiler-wasm/src/compiler/mod.rs +++ b/compiler-wasm/src/compiler/mod.rs @@ -30,7 +30,7 @@ pub async fn compile_document( error!("{message}"); let diagnostic = DocDiagnostic::error(&message, "web-editor"); let exec_ctx = ExecContext::from_diagnostic(diagnostic); - return OpaqueExpoContext::try_from(exec_ctx.prepare_exports()); + return OpaqueExpoContext::try_from(exec_ctx.prepare_exports().await); } }; @@ -51,7 +51,7 @@ pub async fn compile_document( Err(e) => { let comp_doc = CompDoc::from_prep_error(e, start_time); let exec_context = comp_doc.execute().await; - return OpaqueExpoContext::try_from(exec_context.prepare_exports()); + return OpaqueExpoContext::try_from(exec_context.prepare_exports().await); } }; let guard = CachedContextGuard::new(prep_ctx); @@ -88,13 +88,13 @@ async fn compile_with_pack_error( ) -> Result { let comp_doc = CompDoc::from_diagnostic(error, context); let exec_ctx = comp_doc.execute().await; - OpaqueExpoContext::try_from(exec_ctx.prepare_exports()) + OpaqueExpoContext::try_from(exec_ctx.prepare_exports().await) } async fn compile_with_compiler(compiler: Compiler<'_>) -> Result { let comp_doc = compiler.compile().await; let exec_ctx = comp_doc.execute().await; - OpaqueExpoContext::try_from(exec_ctx.prepare_exports()) + OpaqueExpoContext::try_from(exec_ctx.prepare_exports().await) } /// Create a context builder that corresponds to the root project.yaml diff --git a/compiler-wasm/src/lib.rs b/compiler-wasm/src/lib.rs index 8fea0680..2ba92d53 100644 --- a/compiler-wasm/src/lib.rs +++ b/compiler-wasm/src/lib.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use celerc::env::RefCounted; use celerc::prep::EntryPointsSorted; -use celerc::res::{ResPath, Resource}; +use celerc::res::{Loader, ResPath, Resource}; mod interop; use interop::OpaqueExpoContext; @@ -29,7 +29,9 @@ pub fn init( let _ = logger::bind(info_fn, warn_fn, error_fn); info!("initializing compiler..."); loader::bind(load_file, load_url); - let _ = celerc::env::init_site_origin(site_origin); + celerc::env::site::set_origin(&site_origin); + let loader: Box = Box::new(LoaderInWasm); + let _ = celerc::env::global_loader::set(RefCounted::from(loader)); info!("compiler initialized"); } diff --git a/compiler-wasm/src/loader.rs b/compiler-wasm/src/loader.rs index 71b954e4..4ae820f3 100644 --- a/compiler-wasm/src/loader.rs +++ b/compiler-wasm/src/loader.rs @@ -5,6 +5,7 @@ use std::cell::RefCell; +use celerc::util; use js_sys::{Array, Function, Reflect, Uint8Array}; use log::info; use wasm_bindgen::prelude::*; @@ -62,6 +63,19 @@ impl Loader for LoaderInWasm { } pub async fn load_url(url: &str) -> ResResult> { + if url.starts_with("data:") { + let data = match util::bytes_from_data_url(url) { + Ok(data) => data.into_owned(), + Err(e) => { + return Err(ResError::FailToLoadUrl( + url.to_string(), + format!("Failed to parse data URL: {e}"), + )); + } + }; + return Ok(data); + } + // this is essentially try { Ok(await load_url(url)) } catch (e) { Err(e) } let result = async { LOAD_URL diff --git a/docs/src/plugin/export-livesplit.md b/docs/src/plugin/export-livesplit.md index f40ab9a7..aa71ae64 100644 --- a/docs/src/plugin/export-livesplit.md +++ b/docs/src/plugin/export-livesplit.md @@ -18,9 +18,15 @@ config: The plugin provides extra configuration when exporting. ### Icons -:::info -Coming Soon. Currently it will give an error if `icons` is `true` -::: +Setting `icons: true` will export the splits with the icons. +The icon from the document (i.e. `icon-doc`) is used, if the icon from the +document is different from the one from the map. + +Currently, Webp icons are not supported by LiveSplit. The plugin will give +and error if webp icons are used in splits if icons are enabled. +You can set `webp-compat: skip` in the export configuration to skip those icons. +If you want to use a webp icon, you could convert it to a PNG or GIF with image +editing software or online tools. ### Subsplits Setting `subsplits: true` in the option will divide the splits into subsplits diff --git a/docs/src/route/config/icons.md b/docs/src/route/config/icons.md index 2a9e06d9..733c139d 100644 --- a/docs/src/route/config/icons.md +++ b/docs/src/route/config/icons.md @@ -26,4 +26,4 @@ from the extension, so make sure your icon file name or URL has one of the suppo |`image/png`|`.png`|Full Support| |`image/jpeg`|`.jpg`, `.jpeg`|Full Support| |`image/gif`|`.gif`|Animated in document. Not animated in map| -|`image/webp`|`.webp`|Animated in document. Not animated in map. Converted to GIF in LiveSplit export| +|`image/webp`|`.webp`|Animated in document. Not animated in map. Not supported in LiveSplit export| diff --git a/server/Cargo.toml b/server/Cargo.toml index 5e081552..b76ee315 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -34,7 +34,7 @@ tokio = { version = "1.36.0", features=["macros", "rt-multi-thread"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["ansi"] } flate2 = "1.0.28" -once_cell = "1.18.0" +once_cell = "1.19.0" reqwest = { version = "0.11.22", features = ["rustls-tls", "gzip", "deflate"], default-features=false } cached = { version = "0.46.1", features = ["async"] } instant = "0.1.12" diff --git a/server/src/api/view.rs b/server/src/api/view.rs index 3d7f3e4c..91eaf749 100644 --- a/server/src/api/view.rs +++ b/server/src/api/view.rs @@ -183,7 +183,7 @@ async fn view_internal( }; let view_url = { - let mut url = format!("{}/view/{owner}/{repo}", env::get_site_origin()); + let mut url = format!("{}/view/{owner}/{repo}", &env::site::get_origin()); if !path.is_empty() { url.push('/'); url.push_str(path); diff --git a/server/src/boot.rs b/server/src/boot.rs index 64b55c9c..daa13a5f 100644 --- a/server/src/boot.rs +++ b/server/src/boot.rs @@ -1,15 +1,16 @@ //! Things to do on server boot +use std::io::Write; +use std::path::{Path, PathBuf}; -use celerc::env; -use celerc::macros::async_recursion; use flate2::write::GzEncoder; use flate2::Compression; use futures::future; -use std::io::Write; -use std::path::{Path, PathBuf}; use tokio::{io, join}; use tracing::{debug, info}; +use celerc::env::{self, RefCounted}; +use celerc::macros::async_recursion; + /// Setup site origin in static html files pub async fn setup_site_origin( docs_dir: PathBuf, @@ -17,12 +18,11 @@ pub async fn setup_site_origin( origin: &str, ) -> io::Result<()> { debug!("setting up site origin to {origin}"); - env::init_site_origin(origin.to_string()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let origin = env::get_site_origin(); - let domain = env::get_site_domain(); + env::site::set_origin(origin); + let origin = env::site::get_origin(); + let domain = env::site::get_domain(); let (r1, r2) = join!( - process_site_origin_for_path(docs_dir, origin, domain), + process_site_origin_for_path(docs_dir, origin.clone(), domain.clone()), process_site_origin_for_path(app_dir, origin, domain) ); r1?; @@ -33,24 +33,29 @@ pub async fn setup_site_origin( #[async_recursion] async fn process_site_origin_for_path( path: PathBuf, - origin: &'static str, - domain: &'static str, + origin: RefCounted, + domain: RefCounted, ) -> io::Result<()> { if path.is_dir() { let mut dir = tokio::fs::read_dir(path).await?; let mut futures = vec![]; while let Some(entry) = dir.next_entry().await? { + let origin = origin.clone(); + let domain = domain.clone(); futures.push(process_site_origin_for_path(entry.path(), origin, domain)); } for result in future::join_all(futures).await { result?; } - } else if path + return Ok(()); + } + + if path .extension() .map(|ext| ext == "html" || ext == "js" || ext == "css") .unwrap_or(false) { - process_site_origin_in_file(path.as_ref(), origin, domain).await?; + process_site_origin_in_file(path.as_ref(), &origin, &domain).await?; } Ok(()) } diff --git a/server/src/compiler/export.rs b/server/src/compiler/export.rs index efc17929..045e42b7 100644 --- a/server/src/compiler/export.rs +++ b/server/src/compiler/export.rs @@ -27,9 +27,9 @@ fn export_with_pack_error(error: PackError) -> ExpoDoc { async fn export_with_compiler(compiler: Compiler<'_>, req: ExportRequest) -> ExpoDoc { let mut comp_doc = compiler.compile().await; - if let Some(expo_doc) = comp_doc.run_exporter(&req) { + if let Some(expo_doc) = comp_doc.run_exporter(&req).await { return expo_doc; } let exec_ctx = comp_doc.execute().await; - exec_ctx.run_exporter(req) + exec_ctx.run_exporter(req).await } diff --git a/server/src/compiler/loader.rs b/server/src/compiler/loader.rs index 6224765d..34f4c51c 100644 --- a/server/src/compiler/loader.rs +++ b/server/src/compiler/loader.rs @@ -16,6 +16,14 @@ const MAX_RESOURCE_SIZE: usize = 1024 * 1024 * 10; // 10 MB static LOADER: Lazy> = Lazy::new(|| RefCounted::new(ServerResourceLoader::default())); +pub fn setup_global_loader() { + info!("setting up global loader..."); + let loader: Box = Box::::default(); + if celerc::env::global_loader::set(RefCounted::from(loader)).is_err() { + error!("failed to set global loader because it is already set!"); + } +} + pub fn get_loader() -> RefCounted { RefCounted::clone(&LOADER) } @@ -38,12 +46,33 @@ impl ServerResourceLoader { let cache = Mutex::new(TimedSizedCache::with_size_and_lifespan(128, 301)); Self { http_client, cache } } - /// Load a resource from Url or cache. On error, returns an additional should_retry flag. + /// Load a resource from Url or cache. + /// + /// On error, returns an additional should_retry flag. async fn load_url(&self, url: &str) -> Result, (ResError, bool)> { let mut cache = self.cache.lock().await; if let Some(data) = cache.cache_get(url) { return Ok(RefCounted::clone(data)); } + + if url.starts_with("data:") { + let data = match celerc::util::bytes_from_data_url(url) { + Ok(data) => data.into_owned(), + Err(e) => { + return Err(( + ResError::FailToLoadUrl( + url.to_string(), + format!("Failed to parse data URL: {e}"), + ), + false, + )); + } + }; + let data = RefCounted::from(data); + cache.cache_set(url.to_string(), RefCounted::clone(&data)); + return Ok(data); + } + let response = self .http_client .get(url) diff --git a/server/src/compiler/mod.rs b/server/src/compiler/mod.rs index 631d940d..364be5c1 100644 --- a/server/src/compiler/mod.rs +++ b/server/src/compiler/mod.rs @@ -56,11 +56,11 @@ pub async fn compile( async fn compile_with_pack_error(context: CompileContext<'_>, error: PackError) -> ExpoContext { let comp_doc = CompDoc::from_diagnostic(error, context); let exec_ctx = comp_doc.execute().await; - exec_ctx.prepare_exports() + exec_ctx.prepare_exports().await } async fn compile_with_compiler(compiler: Compiler<'_>) -> ExpoContext { let comp_doc = compiler.compile().await; let exec_ctx = comp_doc.execute().await; - exec_ctx.prepare_exports() + exec_ctx.prepare_exports().await } diff --git a/server/src/main.rs b/server/src/main.rs index be502a2a..3513e683 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -37,6 +37,7 @@ async fn main() -> Result<(), Box> { &env.site_origin, ) .await?; + compiler::setup_global_loader(); if env.gzip { info!("compressing assets..."); boot::gzip_static_assets(PathBuf::from(&env.docs_dir), PathBuf::from(&env.app_dir)).await?;