diff --git a/Cargo.lock b/Cargo.lock index d1fe1c7..0059edf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,12 +153,43 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + [[package]] name = "cc" version = "1.0.72" @@ -178,6 +209,12 @@ dependencies = [ "sacabase", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.4.0" @@ -228,7 +265,7 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -243,6 +280,16 @@ dependencies = [ "cc", ] +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-random" version = "0.1.13" @@ -432,6 +479,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cty" version = "0.2.2" @@ -451,6 +508,41 @@ dependencies = [ "wasmparser 0.81.0", ] +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.9.0" @@ -460,6 +552,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + [[package]] name = "directories-next" version = "2.0.0" @@ -549,6 +651,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "file-per-thread-logger" version = "0.1.4" @@ -583,6 +694,22 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fsevent" version = "0.4.0" @@ -618,6 +745,48 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -662,12 +831,56 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "h2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1 0.10.0", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" @@ -686,6 +899,40 @@ dependencies = [ "libc", ] +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "1.3.0" @@ -701,12 +948,53 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "id-arena" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.8.0" @@ -738,6 +1026,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -756,6 +1053,32 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.24" @@ -804,9 +1127,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.113" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" [[package]] name = "libloading" @@ -836,6 +1159,12 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.4.1" @@ -851,6 +1180,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minifb" version = "0.20.0" @@ -889,12 +1234,25 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", ] +[[package]] +name = "mio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + [[package]] name = "mio-extras" version = "2.0.6" @@ -903,7 +1261,7 @@ checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", "log", - "mio", + "mio 0.6.23", "slab", ] @@ -919,12 +1277,95 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "more-asserts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5cc68637e21fe8f077f6a1c9e0b9ca495bb74895226b476310f613325884" + +[[package]] +name = "ndk-glue" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ffb7443daba48349d545028777ca98853b018b4c16624aa01223bc29e078da" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + [[package]] name = "net2" version = "0.2.37" @@ -958,12 +1399,21 @@ dependencies = [ "fsevent-sys", "inotify", "libc", - "mio", + "mio 0.6.23", "mio-extras", "walkdir", "winapi 0.3.9", ] +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -983,6 +1433,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "object" version = "0.26.2" @@ -1048,12 +1519,50 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pico-args" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.24" @@ -1066,6 +1575,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-crate" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dada8c9981fcf32929c3c0f0cd796a9284aca335565227ed88c83babb1d43dc" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1248,6 +1767,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1260,6 +1788,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "sacabase" version = "2.0.0" @@ -1269,6 +1803,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -1278,6 +1818,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1332,16 +1878,63 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -1363,6 +1956,16 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1375,6 +1978,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 = "syn" version = "1.0.86" @@ -1403,6 +2012,20 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1460,6 +2083,75 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio 0.8.0", + "pin-project-lite", + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.8" @@ -1469,12 +2161,97 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1 0.9.8", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.8.0" @@ -1515,6 +2292,24 @@ dependencies = [ "pico-args", ] +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uw8" version = "0.1.1" @@ -1525,9 +2320,13 @@ dependencies = [ "notify", "pico-args", "same-file", + "tokio", + "tokio-stream", "uw8-tool", + "warp", "wasmtime", "wat", + "webbrowser", ] [[package]] @@ -1598,6 +2397,46 @@ dependencies = [ "syn", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -1885,6 +2724,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c28b6b6a78440b02647358625e3febc90724126480b9da6a967b5f674b3554" +dependencies = [ + "jni", + "ndk-glue", + "url", + "web-sys", + "widestring", + "winapi 0.3.9", +] + [[package]] name = "which" version = "3.1.1" @@ -1894,6 +2747,12 @@ dependencies = [ "libc", ] +[[package]] +name = "widestring" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 42f0780..4195385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["native", "browser"] +native = ["wasmtime"] +browser = ["warp", "tokio", "tokio-stream", "webbrowser"] + [dependencies] -wasmtime = "0.30" +wasmtime = { version = "0.30", optional = true } anyhow = "1" minifb = { version = "0.20", default-features = false, features = ["x11"] } notify = "4" @@ -14,4 +19,8 @@ pico-args = "0.4" curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "196719b" } wat = "1" uw8-tool = { path = "uw8-tool" } -same-file = "1" \ No newline at end of file +same-file = "1" +warp = { version = "0.3.2", optional = true } +tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true } +tokio-stream = { version = "0.1.8", features = ["sync"], optional = true } +webbrowser = { version = "0.6.0", optional = true } \ No newline at end of file diff --git a/examples/curlywas/technotunnel.cwa b/examples/curlywas/technotunnel.cwa index fc28f69..785b373 100644 --- a/examples/curlywas/technotunnel.cwa +++ b/examples/curlywas/technotunnel.cwa @@ -1,25 +1,29 @@ import "env.memory" memory(4); import "env.sin" fn sin(f32) -> f32; import "env.time" fn time() -> f32; +import "env.setPixel" fn setPixel(i32, i32, i32); export fn upd() { - let i: i32; + let x: i32; + let y: i32; loop screen { let inline t = time() / 2 as f32; - let lazy o = sin(t) * 0.8; - let lazy q = (i % 320) as f32 - 160.1; - let lazy w = (i / 320 - 120) as f32; + let lazy o = sin(t) * 0.75; + let inline q = x as f32 - 160.5; + let inline w = (y - 120) as f32; let lazy r = sqrt(q*q + w*w); let lazy z = q / r; let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o); - let lazy q2 = (z * s - o) * 10 as f32 + t; - let lazy w2 = w / r * s * 10 as f32 + t; - let lazy s2 = s * 100 as f32 / r; - i?120 = max( + let inline q2 = (z * s - o) * 10 as f32 + t; + let inline w2 = w / r * s * 10 as f32 + t; + let inline s2 = s * 100 as f32 / r; + let inline color = max( 0 as f32, ((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 * (4 as f32 - s2) as f32 ) as i32 - 32; - branch_if (i := i + 1) < 320*240: screen + setPixel(x, y, color); + branch_if x := (x + 1) % 320: screen; + branch_if y := (y + 1) % 320: screen; } } \ No newline at end of file diff --git a/site/content/_index.md b/site/content/_index.md index c904583..732f72d 100644 --- a/site/content/_index.md +++ b/site/content/_index.md @@ -29,6 +29,24 @@ Examplers for older versions: ## Versions +### v0.1.1 + +* [Web runtime](v0.1.1) +* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz) +* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz) +* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip) + +Changes: + +* implement more robust file watcher +* add basic video recording on F10 in web runtime +* add screenshot on F9 +* add watchdog to interrupt hanging update in native runtime +* add devkit mode to web runtime +* add unpack and compile commands to uw8 +* add support for table/element section in pack command +* disable wayland support (caused missing window decorations in gnome) + ### v0.1.0 * [Web runtime](v0.1.0) diff --git a/src/lib.rs b/src/lib.rs index 6d6417f..dc9c18c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,270 +1,22 @@ -use std::io::prelude::*; -use std::path::Path; -use std::sync::{Arc, Mutex}; -use std::time::Duration; -use std::{fs::File, thread, time::Instant}; - -use anyhow::Result; -use minifb::{Key, Window, WindowOptions}; -use wasmtime::{ - Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, -}; - mod filewatcher; +#[cfg(feature = "native")] +mod run_native; +#[cfg(feature = "browser")] +mod run_web; pub use filewatcher::FileWatcher; +#[cfg(feature = "native")] +pub use run_native::MicroW8; +#[cfg(feature = "browser")] +pub use run_web::RunWebServer; -static GAMEPAD_KEYS: &'static [Key] = &[ - Key::Up, - Key::Down, - Key::Left, - Key::Right, - Key::Z, - Key::X, - Key::A, - Key::S, -]; +use anyhow::Result; -pub struct MicroW8 { - engine: Engine, - loader_module: Module, - window: Window, - window_buffer: Vec, - instance: Option, - timeout: u32, -} - -struct UW8Instance { - store: Store<()>, - memory: Memory, - end_frame: TypedFunc<(), ()>, - update: TypedFunc<(), ()>, - start_time: Instant, - module: Vec, - watchdog: Arc>, -} - -impl Drop for UW8Instance { - fn drop(&mut self) { - if let Ok(mut watchdog) = self.watchdog.lock() { - watchdog.stop = true; - } +pub trait Runtime { + fn is_open(&self) -> bool; + fn set_timeout(&mut self, _timeout: u32) { + eprintln!("Warning: runtime doesn't support timeout"); } -} - -struct UW8WatchDog { - interupt: wasmtime::InterruptHandle, - timeout: u32, - stop: bool, -} - -impl MicroW8 { - pub fn new() -> Result { - let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?; - - let loader_module = - wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; - - let mut options = WindowOptions::default(); - options.scale = minifb::Scale::X2; - options.scale_mode = minifb::ScaleMode::AspectRatioStretch; - options.resize = true; - let mut window = Window::new("MicroW8", 320, 240, options)?; - window.limit_update_rate(Some(std::time::Duration::from_micros(16666))); - - Ok(MicroW8 { - engine, - loader_module, - window, - window_buffer: vec![0u32; 320 * 240], - instance: None, - timeout: 30, - }) - } - - pub fn is_open(&self) -> bool { - self.window.is_open() && !self.window.is_key_down(Key::Escape) - } - - pub fn set_timeout(&mut self, timeout: u32) { - self.timeout = timeout; - } - - fn reset(&mut self) { - self.instance = None; - for v in &mut self.window_buffer { - *v = 0; - } - } - - pub fn load_from_file>(&mut self, path: P) -> Result<()> { - self.reset(); - - let mut module = vec![]; - File::open(path)?.read_to_end(&mut module)?; - self.load_from_memory(&module) - } - - pub fn load_from_memory(&mut self, module_data: &[u8]) -> Result<()> { - self.reset(); - - let mut store = wasmtime::Store::new(&self.engine, ()); - - let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?; - - let mut linker = wasmtime::Linker::new(&self.engine); - linker.define("env", "memory", memory.clone())?; - - let loader_instance = linker.instantiate(&mut store, &self.loader_module)?; - let load_uw8 = loader_instance.get_typed_func::(&mut store, "load_uw8")?; - - let platform_data = include_bytes!("../platform/bin/platform.uw8"); - memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data); - let platform_length = - load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize; - let platform_module = - wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?; - - memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data); - let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize; - let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?; - - linker.func_wrap("env", "acos", |v: f32| v.acos())?; - linker.func_wrap("env", "asin", |v: f32| v.asin())?; - linker.func_wrap("env", "atan", |v: f32| v.atan())?; - linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?; - linker.func_wrap("env", "cos", |v: f32| v.cos())?; - linker.func_wrap("env", "exp", |v: f32| v.exp())?; - linker.func_wrap("env", "log", |v: f32| v.ln())?; - linker.func_wrap("env", "sin", |v: f32| v.sin())?; - linker.func_wrap("env", "tan", |v: f32| v.tan())?; - linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?; - for i in 9..64 { - linker.func_wrap("env", &format!("reserved{}", i), || {})?; - } - for i in 0..16 { - linker.define( - "env", - &format!("g_reserved{}", i), - wasmtime::Global::new( - &mut store, - GlobalType::new(ValType::I32, Mutability::Const), - 0.into(), - )?, - )?; - } - - let platform_instance = linker.instantiate(&mut store, &platform_module)?; - - for export in platform_instance.exports(&mut store) { - linker.define( - "env", - export.name(), - export - .into_func() - .expect("platform surely only exports functions"), - )?; - } - - let watchdog = Arc::new(Mutex::new(UW8WatchDog { - interupt: store.interrupt_handle()?, - timeout: self.timeout, - stop: false, - })); - - { - let watchdog = watchdog.clone(); - thread::spawn(move || loop { - thread::sleep(Duration::from_millis(17)); - if let Ok(mut watchdog) = watchdog.lock() { - if watchdog.stop { - break; - } - if watchdog.timeout > 0 { - watchdog.timeout -= 1; - if watchdog.timeout == 0 { - watchdog.interupt.interrupt(); - } - } - } else { - break; - } - }); - } - - let instance = linker.instantiate(&mut store, &module)?; - if let Ok(mut watchdog) = watchdog.lock() { - watchdog.timeout = 0; - } - let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; - let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?; - - self.instance = Some(UW8Instance { - store, - memory, - end_frame, - update, - start_time: Instant::now(), - module: module_data.into(), - watchdog, - }); - - Ok(()) - } - - pub fn run_frame(&mut self) -> Result<()> { - let mut result = Ok(()); - if let Some(mut instance) = self.instance.take() { - { - let time = instance.start_time.elapsed().as_millis() as i32; - let mut gamepad: u32 = 0; - for key in self.window.get_keys() { - if let Some(index) = GAMEPAD_KEYS - .iter() - .enumerate() - .find(|(_, &k)| k == key) - .map(|(i, _)| i) - { - gamepad |= 1 << index; - } - } - - let mem = instance.memory.data_mut(&mut instance.store); - mem[64..68].copy_from_slice(&time.to_le_bytes()); - mem[68..72].copy_from_slice(&gamepad.to_le_bytes()); - } - - if let Ok(mut watchdog) = instance.watchdog.lock() { - watchdog.timeout = self.timeout; - } - result = instance.update.call(&mut instance.store, ()); - if let Ok(mut watchdog) = instance.watchdog.lock() { - watchdog.timeout = 0; - } - instance.end_frame.call(&mut instance.store, ())?; - - let memory = instance.memory.data(&instance.store); - let framebuffer = &memory[120..]; - let palette = &memory[0x13000..]; - for i in 0..320 * 240 { - let offset = framebuffer[i] as usize * 4; - self.window_buffer[i] = 0xff000000 - | ((palette[offset + 0] as u32) << 16) - | ((palette[offset + 1] as u32) << 8) - | palette[offset + 2] as u32; - } - - if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) { - self.load_from_memory(&instance.module)?; - } else if result.is_ok() { - self.instance = Some(instance); - } - } - - self.window - .update_with_buffer(&self.window_buffer, 320, 240)?; - - result?; - Ok(()) - } -} + fn load(&mut self, module_data: &[u8]) -> Result<()>; + fn run_frame(&mut self) -> Result<()>; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dc19e3c..3a6e185 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,26 @@ use std::fs::File; use std::io::prelude::*; +use std::path::{Path, PathBuf}; use std::process; -use std::{ - path::{Path, PathBuf}, - process::exit, -}; use anyhow::Result; use pico_args::Arguments; -use uw8::{FileWatcher, MicroW8}; +#[cfg(feature = "native")] +use uw8::MicroW8; +#[cfg(feature = "browser")] +use uw8::RunWebServer; +#[cfg(any(feature = "native", feature = "browser"))] +use uw8::Runtime; fn main() -> Result<()> { let mut args = Arguments::from_env(); - match args.subcommand()?.as_ref().map(|s| s.as_str()) { + match args.subcommand()?.as_deref() { Some("version") => { println!("{}", env!("CARGO_PKG_VERSION")); Ok(()) } + #[cfg(any(feature = "native", feature = "browser"))] Some("run") => run(args), Some("pack") => pack(args), Some("unpack") => unpack(args), @@ -27,7 +30,8 @@ fn main() -> Result<()> { println!("uw8 {}", env!("CARGO_PKG_VERSION")); println!(); println!("Usage:"); - println!(" uw8 run [-t/--timeout ] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); + #[cfg(any(feature = "native", feature = "browser"))] + println!(" uw8 run [-t/--timeout ] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); println!(" uw8 pack [-u/--uncompressed] [-l/--level] "); println!(" uw8 unpack "); println!(" uw8 compile [-d/--debug] "); @@ -41,6 +45,7 @@ fn main() -> Result<()> { } } +#[cfg(any(feature = "native", feature = "browser"))] fn run(mut args: Arguments) -> Result<()> { let watch_mode = args.contains(["-w", "--watch"]); let timeout: Option = args.opt_value_from_str(["-t", "--timeout"])?; @@ -65,15 +70,14 @@ fn run(mut args: Arguments) -> Result<()> { config.output_path = Some(path); } + #[cfg(feature = "native")] + let run_browser = args.contains(["-b", "--browser"]); + #[cfg(not(feature = "native"))] + let run_browser = args.contains(["-b", "--browser"]) || true; + let filename = args.free_from_os_str::(|s| Ok(s.into()))?; - let mut uw8 = MicroW8::new()?; - - if let Some(timeout) = timeout { - uw8.set_timeout(timeout); - } - - let mut watcher = FileWatcher::builder(); + let mut watcher = uw8::FileWatcher::builder(); if watch_mode { watcher.add_file(&filename); @@ -81,21 +85,39 @@ fn run(mut args: Arguments) -> Result<()> { let watcher = watcher.build()?; - if let Err(err) = start_cart(&filename, &mut uw8, &config) { + use std::process::exit; + + let mut runtime: Box = if !run_browser { + #[cfg(not(feature = "native"))] + unimplemented!(); + #[cfg(feature = "native")] + Box::new(MicroW8::new()?) + } else { + #[cfg(not(feature = "browser"))] + unimplemented!(); + #[cfg(feature = "browser")] + Box::new(RunWebServer::new()) + }; + + if let Some(timeout) = timeout { + runtime.set_timeout(timeout); + } + + if let Err(err) = start_cart(&filename, &mut *runtime, &config) { eprintln!("Load error: {}", err); if !watch_mode { exit(1); } } - while uw8.is_open() { + while runtime.is_open() { if watcher.poll_changed_file()?.is_some() { - if let Err(err) = start_cart(&filename, &mut uw8, &config) { + if let Err(err) = start_cart(&filename, &mut *runtime, &config) { eprintln!("Load error: {}", err); } } - if let Err(err) = uw8.run_frame() { + if let Err(err) = runtime.run_frame() { eprintln!("Runtime error: {}", err); if !watch_mode { exit(1); @@ -112,7 +134,7 @@ struct Config { output_path: Option, } -fn load_cart(filename: &Path, pack: &Option) -> Result> { +fn load_cart(filename: &Path, config: &Config) -> Result> { let mut cart = vec![]; File::open(filename)?.read_to_end(&mut cart)?; @@ -125,22 +147,23 @@ fn load_cart(filename: &Path, pack: &Option) -> Result Result<()> { - let cart = load_cart(filename, &config.pack)?; - if let Some(ref path) = config.output_path { File::create(path)?.write_all(&cart)?; } - if let Err(err) = uw8.load_from_memory(&cart) { + Ok(cart) +} + +#[cfg(any(feature = "native", feature = "browser"))] +fn start_cart(filename: &Path, runtime: &mut dyn Runtime, config: &Config) -> Result<()> { + let cart = load_cart(filename, config)?; + + if let Err(err) = runtime.load(&cart) { eprintln!("Load error: {}", err); Err(err) } else { @@ -163,7 +186,13 @@ fn pack(mut args: Arguments) -> Result<()> { let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; - let cart = load_cart(&in_file, &Some(pack_config))?; + let cart = load_cart( + &in_file, + &Config { + pack: Some(pack_config), + output_path: None, + }, + )?; File::create(out_file)?.write_all(&cart)?; @@ -173,8 +202,8 @@ fn pack(mut args: Arguments) -> Result<()> { fn unpack(mut args: Arguments) -> Result<()> { let in_file = args.free_from_os_str::(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; - - uw8_tool::unpack_file(&in_file, &out_file).into() + + uw8_tool::unpack_file(&in_file, &out_file) } fn compile(mut args: Arguments) -> Result<()> { diff --git a/src/run-web.html b/src/run-web.html new file mode 100644 index 0000000..7d86e72 --- /dev/null +++ b/src/run-web.html @@ -0,0 +1 @@ +uw8-run
\ No newline at end of file diff --git a/src/run_native.rs b/src/run_native.rs new file mode 100644 index 0000000..d310aa8 --- /dev/null +++ b/src/run_native.rs @@ -0,0 +1,260 @@ +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use std::{thread, time::Instant}; + +use anyhow::Result; +use minifb::{Key, Window, WindowOptions}; +use wasmtime::{ + Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, +}; + +static GAMEPAD_KEYS: &[Key] = &[ + Key::Up, + Key::Down, + Key::Left, + Key::Right, + Key::Z, + Key::X, + Key::A, + Key::S, +]; + +pub struct MicroW8 { + engine: Engine, + loader_module: Module, + window: Window, + window_buffer: Vec, + instance: Option, + timeout: u32, +} + +struct UW8Instance { + store: Store<()>, + memory: Memory, + end_frame: TypedFunc<(), ()>, + update: TypedFunc<(), ()>, + start_time: Instant, + module: Vec, + watchdog: Arc>, +} + +impl Drop for UW8Instance { + fn drop(&mut self) { + if let Ok(mut watchdog) = self.watchdog.lock() { + watchdog.stop = true; + } + } +} + +struct UW8WatchDog { + interupt: wasmtime::InterruptHandle, + timeout: u32, + stop: bool, +} + +impl MicroW8 { + pub fn new() -> Result { + let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?; + + let loader_module = + wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; + + let options = WindowOptions { + scale: minifb::Scale::X2, + scale_mode: minifb::ScaleMode::AspectRatioStretch, + resize: true, + ..Default::default() + }; + let mut window = Window::new("MicroW8", 320, 240, options)?; + window.limit_update_rate(Some(std::time::Duration::from_micros(16666))); + + Ok(MicroW8 { + engine, + loader_module, + window, + window_buffer: vec![0u32; 320 * 240], + instance: None, + timeout: 30, + }) + } + + fn reset(&mut self) { + self.instance = None; + for v in &mut self.window_buffer { + *v = 0; + } + } +} + +impl super::Runtime for MicroW8 { + fn is_open(&self) -> bool { + self.window.is_open() && !self.window.is_key_down(Key::Escape) + } + + fn set_timeout(&mut self, timeout: u32) { + self.timeout = timeout; + } + + fn load(&mut self, module_data: &[u8]) -> Result<()> { + self.reset(); + + let mut store = wasmtime::Store::new(&self.engine, ()); + + let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?; + + let mut linker = wasmtime::Linker::new(&self.engine); + linker.define("env", "memory", memory)?; + + let loader_instance = linker.instantiate(&mut store, &self.loader_module)?; + let load_uw8 = loader_instance.get_typed_func::(&mut store, "load_uw8")?; + + let platform_data = include_bytes!("../platform/bin/platform.uw8"); + memory.data_mut(&mut store)[..platform_data.len()].copy_from_slice(platform_data); + let platform_length = + load_uw8.call(&mut store, platform_data.len() as i32)? as u32 as usize; + let platform_module = + wasmtime::Module::new(&self.engine, &memory.data(&store)[..platform_length])?; + + memory.data_mut(&mut store)[..module_data.len()].copy_from_slice(module_data); + let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize; + let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?; + + linker.func_wrap("env", "acos", |v: f32| v.acos())?; + linker.func_wrap("env", "asin", |v: f32| v.asin())?; + linker.func_wrap("env", "atan", |v: f32| v.atan())?; + linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?; + linker.func_wrap("env", "cos", |v: f32| v.cos())?; + linker.func_wrap("env", "exp", |v: f32| v.exp())?; + linker.func_wrap("env", "log", |v: f32| v.ln())?; + linker.func_wrap("env", "sin", |v: f32| v.sin())?; + linker.func_wrap("env", "tan", |v: f32| v.tan())?; + linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?; + for i in 9..64 { + linker.func_wrap("env", &format!("reserved{}", i), || {})?; + } + for i in 0..16 { + linker.define( + "env", + &format!("g_reserved{}", i), + wasmtime::Global::new( + &mut store, + GlobalType::new(ValType::I32, Mutability::Const), + 0.into(), + )?, + )?; + } + + let platform_instance = linker.instantiate(&mut store, &platform_module)?; + + for export in platform_instance.exports(&mut store) { + linker.define( + "env", + export.name(), + export + .into_func() + .expect("platform surely only exports functions"), + )?; + } + + let watchdog = Arc::new(Mutex::new(UW8WatchDog { + interupt: store.interrupt_handle()?, + timeout: self.timeout, + stop: false, + })); + + { + let watchdog = watchdog.clone(); + thread::spawn(move || loop { + thread::sleep(Duration::from_millis(17)); + if let Ok(mut watchdog) = watchdog.lock() { + if watchdog.stop { + break; + } + if watchdog.timeout > 0 { + watchdog.timeout -= 1; + if watchdog.timeout == 0 { + watchdog.interupt.interrupt(); + } + } + } else { + break; + } + }); + } + + let instance = linker.instantiate(&mut store, &module)?; + if let Ok(mut watchdog) = watchdog.lock() { + watchdog.timeout = 0; + } + let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?; + let update = instance.get_typed_func::<(), (), _>(&mut store, "upd")?; + + self.instance = Some(UW8Instance { + store, + memory, + end_frame, + update, + start_time: Instant::now(), + module: module_data.into(), + watchdog, + }); + + Ok(()) + } + + fn run_frame(&mut self) -> Result<()> { + let mut result = Ok(()); + if let Some(mut instance) = self.instance.take() { + { + let time = instance.start_time.elapsed().as_millis() as i32; + let mut gamepad: u32 = 0; + for key in self.window.get_keys() { + if let Some(index) = GAMEPAD_KEYS + .iter() + .enumerate() + .find(|(_, &k)| k == key) + .map(|(i, _)| i) + { + gamepad |= 1 << index; + } + } + + let mem = instance.memory.data_mut(&mut instance.store); + mem[64..68].copy_from_slice(&time.to_le_bytes()); + mem[68..72].copy_from_slice(&gamepad.to_le_bytes()); + } + + if let Ok(mut watchdog) = instance.watchdog.lock() { + watchdog.timeout = self.timeout; + } + result = instance.update.call(&mut instance.store, ()); + if let Ok(mut watchdog) = instance.watchdog.lock() { + watchdog.timeout = 0; + } + instance.end_frame.call(&mut instance.store, ())?; + + let memory = instance.memory.data(&instance.store); + let framebuffer = &memory[120..(120 + 320 * 240)]; + let palette = &memory[0x13000..]; + for (i, &color_index) in framebuffer.iter().enumerate() { + let offset = color_index as usize * 4; + self.window_buffer[i] = 0xff000000 + | ((palette[offset] as u32) << 16) + | ((palette[offset + 1] as u32) << 8) + | palette[offset + 2] as u32; + } + + if self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No) { + self.load(&instance.module)?; + } else if result.is_ok() { + self.instance = Some(instance); + } + } + + self.window + .update_with_buffer(&self.window_buffer, 320, 240)?; + + result?; + Ok(()) + } +} diff --git a/src/run_web.rs b/src/run_web.rs new file mode 100644 index 0000000..4eede6b --- /dev/null +++ b/src/run_web.rs @@ -0,0 +1,89 @@ +use anyhow::Result; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex}, + thread, +}; +use tokio::sync::broadcast; +use tokio_stream::{wrappers::BroadcastStream, Stream, StreamExt}; +use warp::{http::Response, Filter}; + +pub struct RunWebServer { + cart: Arc>>, + tx: broadcast::Sender<()>, +} + +impl RunWebServer { + pub fn new() -> RunWebServer { + let cart = Arc::new(Mutex::new(Vec::new())); + let (tx, _) = broadcast::channel(1); + + let server_cart = cart.clone(); + let server_tx = tx.clone(); + thread::spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .expect("Failed to create tokio runtime"); + rt.block_on(async { + let html = warp::path::end().map(|| { + Response::builder() + .header("Content-Type", "text/html") + .body(include_str!("run-web.html")) + }); + + let cart = warp::path("cart") + .map(move || server_cart.lock().map_or(Vec::new(), |c| c.clone())); + + let events = warp::path("events").and(warp::get()).map(move || { + fn event_stream( + tx: &broadcast::Sender<()>, + ) -> impl Stream> + { + BroadcastStream::new(tx.subscribe()) + .map(|_| Ok(warp::sse::Event::default().data("L"))) + } + warp::sse::reply(warp::sse::keep_alive().stream(event_stream(&server_tx))) + }); + + let socket_addr = "127.0.0.1:3030" + .parse::() + .expect("Failed to parse socket address"); + + let server_future = warp::serve(html.or(cart).or(events)).bind(socket_addr); + println!("Point browser at http://{}", socket_addr); + let _ignore_result = webbrowser::open(&format!("http://{}", socket_addr)); + server_future.await + }); + }); + + RunWebServer { cart, tx } + } +} + +impl super::Runtime for RunWebServer { + fn load(&mut self, module_data: &[u8]) -> Result<()> { + if let Ok(mut lock) = self.cart.lock() { + lock.clear(); + lock.extend_from_slice(module_data); + } + let _ignore_result = self.tx.send(()); + Ok(()) + } + + fn is_open(&self) -> bool { + true + } + + fn run_frame(&mut self) -> Result<()> { + std::thread::sleep(std::time::Duration::from_millis(100)); + Ok(()) + } +} + +impl Default for RunWebServer { + fn default() -> RunWebServer { + RunWebServer::new() + } +} \ No newline at end of file diff --git a/test.cwa b/test.cwa new file mode 100644 index 0000000..6e5984b --- /dev/null +++ b/test.cwa @@ -0,0 +1,13 @@ +import "env.memory" memory(4); +import "env.printString" fn print(i32); + +export fn upd() { +} + +start fn start() { + print(0); +} + +data 0 { + "Press " i8(0xe0) " and " i8(0xe1) " to adjust, " i8(0xcc) " to commit." i8(0) +} \ No newline at end of file diff --git a/test.wasm b/test.wasm new file mode 100644 index 0000000..6d572c6 Binary files /dev/null and b/test.wasm differ diff --git a/web/build-run-web b/web/build-run-web new file mode 100755 index 0000000..cc0cc6d --- /dev/null +++ b/web/build-run-web @@ -0,0 +1,2 @@ +#!/bin/bash +rm -rf .parcel-cache && yarn parcel build src/run-web.html && cp dist/run-web.html ../src/ diff --git a/web/src/main.js b/web/src/main.js index 423d076..536951d 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -1,5 +1,4 @@ -import loaderUrl from "data-url:../../platform/bin/loader.wasm"; -import platformUrl from "data-url:../../platform/bin/platform.uw8"; +import MicroW8 from './microw8.js'; function setMessage(size, error) { let html = size ? `${size} bytes` : 'Insert cart'; @@ -9,314 +8,27 @@ function setMessage(size, error) { document.getElementById('message').innerHTML = html; } -let screen = document.getElementById('screen'); -let canvasCtx = screen.getContext('2d'); -let imageData = canvasCtx.createImageData(320, 240); - -let devkitMode; - -let cancelFunction; - -let currentData; - -let U8 = (d) => new Uint8Array(d); -let U32 = (d) => new Uint32Array(d); - -let pad = 0; -let keyHandler = (e) => { - let isKeyDown = e.type == 'keydown'; - let mask; - switch (e.code) { - case 'ArrowUp': - mask = 1; - break; - case 'ArrowDown': - mask = 2; - break; - case 'ArrowLeft': - mask = 4; - break; - case 'ArrowRight': - mask = 8; - break; - case 'KeyZ': - mask = 16; - break; - case 'KeyX': - mask = 32; - break; - case 'KeyA': - mask = 64; - break; - case 'KeyS': - mask = 128; - break; - case 'KeyR': - if (isKeyDown) { - runModule(currentData, true); - } - break; - case 'F9': - if(isKeyDown) { - screen.toBlob(blob => { - downloadBlob(blob, '.png'); - }); - } - e.preventDefault(); - break; - case 'F10': - if(isKeyDown) { - recordVideo(); - } - e.preventDefault(); - break; - } - - if (isKeyDown) { - pad |= mask; - } else { - pad &= ~mask; - } -}; -window.onkeydown = keyHandler; -window.onkeyup = keyHandler; - -async function runModule(data, keepUrl) { - if (cancelFunction) { - cancelFunction(); - cancelFunction = null; - } - - let cartridgeSize = data.byteLength; - - setMessage(cartridgeSize); - if (cartridgeSize == 0) { - return; - } - - currentData = data; - - let newURL = window.location.pathname; - if (cartridgeSize <= 1024 && !keepUrl) { - let dataString = ''; - for (let byte of U8(data)) { - dataString += String.fromCharCode(byte); - } - newURL += '#' + btoa(dataString); - - if (newURL != window.location.pathname + window.location.hash) { - history.pushState(null, null, newURL); - } - } - - screen.width = screen.width; - - try { - let memSize = { initial: 4 }; - if(!devkitMode) { - memSize.maximum = 4; - } - let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 }); - let memU8 = U8(memory.buffer); - - let importObject = { - env: { - memory - }, - }; - - let loader; - - let loadModuleData = (data) => { - if (loader && (!devkitMode || U8(data)[0] != 0)) { - memU8.set(U8(data)); - let length = loader.exports.load_uw8(data.byteLength); - data = new ArrayBuffer(length); - U8(data).set(memU8.slice(0, length)); - } - return data; - } - - let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance; - - let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer())); - - loader = await loadModuleURL(loaderUrl); - - for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) { - importObject.env[n] = Math[n]; - } - - for (let i = 9; i < 64; ++i) { - importObject.env['reserved' + i] = () => { }; - } - - for (let i = 0; i < 16; ++i) { - importObject.env['g_reserved' + i] = 0; - } - - data = loadModuleData(data); - - let platform_instance = await loadModuleURL(platformUrl); - - for (let name in platform_instance.exports) { - importObject.env[name] = platform_instance.exports[name] - } - - let instance = await instantiate(data); - - let buffer = U32(imageData.data.buffer); - - let startTime = Date.now(); - - let keepRunning = true; - cancelFunction = () => keepRunning = false; - - const timePerFrame = 1000 / 60; - let nextFrame = startTime; - - function mainloop() { - if (!keepRunning) { - return; - } - - try { - let now = Date.now(); - let restart = false; - if (now >= nextFrame) { - let gamepads = navigator.getGamepads(); - let gamepad = 0; - for (let i = 0; i < 4; ++i) { - let pad = gamepads[i]; - if (!pad) { - continue; - } - for (let j = 0; j < 8; ++j) { - let buttonIdx = (j + 12) % 16; - if (pad.buttons.length > buttonIdx && pad.buttons[buttonIdx].pressed) { - gamepad |= 1 << (i * 8 + j); - } - } - if (pad.axes.length > 1) { - for (let j = 0; j < 4; ++j) { - let v = pad.axes[1 - (j >> 1)]; - if (((j & 1) ? v : -v) > 0.5) { - gamepad |= 1 << (i * 8 + j); - } - } - } - if (pad.buttons.length > 9 && pad.buttons[9].pressed) { - restart = true; - } - } - - let u32Mem = U32(memory.buffer); - u32Mem[16] = now - startTime; - u32Mem[17] = pad | gamepad; - instance.exports.upd(); - platform_instance.exports.endFrame(); - - let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024)); - for (let i = 0; i < 320 * 240; ++i) { - buffer[i] = palette[memU8[i + 120]] | 0xff000000; - } - canvasCtx.putImageData(imageData, 0, 0); - nextFrame = Math.max(nextFrame + timePerFrame, now); - } - - if (restart) { - runModule(currentData); - } else { - window.requestAnimationFrame(mainloop); - } - } catch (err) { - setMessage(cartridgeSize, err.toString()); - } - } - - mainloop(); - } catch (err) { - setMessage(cartridgeSize, err.toString()); - } -} - -function downloadBlob(blob, ext) { - let a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = 'microw8_' + new Date().toISOString() + ext; - a.click(); - URL.revokeObjectURL(a.href); -} - -let videoRecorder; -let videoStartTime; -function recordVideo() { - if(videoRecorder) { - videoRecorder.stop(); - videoRecorder = null; - return; - } - - videoRecorder = new MediaRecorder(screen.captureStream(), { - mimeType: 'video/webm', - videoBitsPerSecond: 25000000 - }); - - let chunks = []; - videoRecorder.ondataavailable = e => { - chunks.push(e.data); - }; - - let timer = document.getElementById("timer"); - timer.hidden = false; - timer.innerText = "00:00"; - - videoRecorder.onstop = () => { - timer.hidden = true; - downloadBlob(new Blob(chunks, {type: 'video/webm'}), '.webm'); - }; - - videoRecorder.start(); - videoStartTime = Date.now(); - - function updateTimer() { - if(!videoStartTime) { - return; - } - - let duration = Math.floor((Date.now() - videoStartTime) / 1000); - timer.innerText = Math.floor(duration / 60).toString().padStart(2, '0') + ':' + (duration % 60).toString().padStart(2, '0'); - - setTimeout(updateTimer, 1000); - } - - setTimeout(updateTimer, 1000); -} - -async function runModuleFromURL(url, keepUrl) { - let response = await fetch(url); - let type = response.headers.get('Content-Type'); - if(type && type.includes('html')) { - throw false; - } - runModule(await response.arrayBuffer(), keepUrl || devkitMode); -} +let uw8 = MicroW8(document.getElementById('screen'), { + setMessage, + keyboardElement: window, + timerElement: document.getElementById("timer"), +}); function runModuleFromHash() { let hash = window.location.hash.slice(1); if(hash == 'devkit') { - devkitMode = true; + uw8.setDevkitMode(true); return; } - devkitMode = false; + uw8.setDevkitMode(false); if (hash.length > 0) { if (hash.startsWith("url=")) { - runModuleFromURL(hash.slice(4), true); + uw8.runModuleFromURL(hash.slice(4), true); } else { - runModuleFromURL('data:;base64,' + hash); + uw8.runModuleFromURL('data:;base64,' + hash); } } else { - runModule(new ArrayBuffer(0)); + uw8.runModule(new ArrayBuffer(0)); } } @@ -331,7 +43,7 @@ let setupLoad = () => { fileInput.accept = '.wasm,.uw8,application/wasm'; fileInput.onchange = () => { if (fileInput.files.length > 0) { - runModuleFromURL(URL.createObjectURL(fileInput.files[0])); + uw8.runModuleFromURL(URL.createObjectURL(fileInput.files[0])); } }; fileInput.click(); @@ -345,7 +57,7 @@ let setupLoad = () => { let files = e.dataTransfer && e.dataTransfer.files; if(files && files.length == 1) { e.preventDefault(); - runModuleFromURL(URL.createObjectURL(e.dataTransfer.files[0])); + uw8.runModuleFromURL(URL.createObjectURL(e.dataTransfer.files[0])); } } @@ -367,7 +79,7 @@ if(location.hash.length != 0) { url += 'cart.uw8'; } try { - await runModuleFromURL(url, true); + await uw8.runModuleFromURL(url, true); } catch(e) { setupLoad(); } diff --git a/web/src/microw8.js b/web/src/microw8.js new file mode 100644 index 0000000..26f347c --- /dev/null +++ b/web/src/microw8.js @@ -0,0 +1,319 @@ +import loaderUrl from "data-url:../../platform/bin/loader.wasm"; +import platformUrl from "data-url:../../platform/bin/platform.uw8"; + +export default function MicroW8(screen, config = {}) { + if(!config.setMessage) { + config.setMessage = (s, e) => { + if(e) { + console.log('error: ' + e); + } + } + } + let canvasCtx = screen.getContext('2d'); + let imageData = canvasCtx.createImageData(320, 240); + + let devkitMode = config.devkitMode; + + let cancelFunction; + + let currentData; + + let U8 = (d) => new Uint8Array(d); + let U32 = (d) => new Uint32Array(d); + + let pad = 0; + let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement; + if(keyboardElement) { + let keyHandler = (e) => { + let isKeyDown = e.type == 'keydown'; + let mask; + switch (e.code) { + case 'ArrowUp': + mask = 1; + break; + case 'ArrowDown': + mask = 2; + break; + case 'ArrowLeft': + mask = 4; + break; + case 'ArrowRight': + mask = 8; + break; + case 'KeyZ': + mask = 16; + break; + case 'KeyX': + mask = 32; + break; + case 'KeyA': + mask = 64; + break; + case 'KeyS': + mask = 128; + break; + case 'KeyR': + if (isKeyDown) { + runModule(currentData, true); + } + break; + case 'F9': + if(isKeyDown) { + screen.toBlob(blob => { + downloadBlob(blob, '.png'); + }); + } + e.preventDefault(); + break; + case 'F10': + if(isKeyDown) { + recordVideo(); + } + e.preventDefault(); + break; + } + + if (isKeyDown) { + pad |= mask; + } else { + pad &= ~mask; + } + }; + + keyboardElement.onkeydown = keyHandler; + keyboardElement.onkeyup = keyHandler; + } + + async function runModule(data, keepUrl) { + if (cancelFunction) { + cancelFunction(); + cancelFunction = null; + } + + let cartridgeSize = data.byteLength; + + config.setMessage(cartridgeSize); + if (cartridgeSize == 0) { + return; + } + + currentData = data; + + let newURL = window.location.pathname; + if (cartridgeSize <= 1024 && !keepUrl) { + let dataString = ''; + for (let byte of U8(data)) { + dataString += String.fromCharCode(byte); + } + newURL += '#' + btoa(dataString); + + if (newURL != window.location.pathname + window.location.hash) { + history.pushState(null, null, newURL); + } + } + + screen.width = screen.width; + + try { + let memSize = { initial: 4 }; + if(!devkitMode) { + memSize.maximum = 4; + } + let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 }); + let memU8 = U8(memory.buffer); + + let importObject = { + env: { + memory + }, + }; + + let loader; + + let loadModuleData = (data) => { + if (loader && (!devkitMode || U8(data)[0] != 0)) { + memU8.set(U8(data)); + let length = loader.exports.load_uw8(data.byteLength); + data = new ArrayBuffer(length); + U8(data).set(memU8.slice(0, length)); + } + return data; + } + + let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance; + + let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer())); + + loader = await loadModuleURL(loaderUrl); + + for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) { + importObject.env[n] = Math[n]; + } + + for (let i = 9; i < 64; ++i) { + importObject.env['reserved' + i] = () => { }; + } + + for (let i = 0; i < 16; ++i) { + importObject.env['g_reserved' + i] = 0; + } + + data = loadModuleData(data); + + let platform_instance = await loadModuleURL(platformUrl); + + for (let name in platform_instance.exports) { + importObject.env[name] = platform_instance.exports[name] + } + + let instance = await instantiate(data); + + let buffer = U32(imageData.data.buffer); + + let startTime = Date.now(); + + let keepRunning = true; + cancelFunction = () => keepRunning = false; + + const timePerFrame = 1000 / 60; + let nextFrame = startTime; + + function mainloop() { + if (!keepRunning) { + return; + } + + try { + let now = Date.now(); + let restart = false; + if (now >= nextFrame) { + let gamepads = navigator.getGamepads(); + let gamepad = 0; + for (let i = 0; i < 4; ++i) { + let pad = gamepads[i]; + if (!pad) { + continue; + } + for (let j = 0; j < 8; ++j) { + let buttonIdx = (j + 12) % 16; + if (pad.buttons.length > buttonIdx && pad.buttons[buttonIdx].pressed) { + gamepad |= 1 << (i * 8 + j); + } + } + if (pad.axes.length > 1) { + for (let j = 0; j < 4; ++j) { + let v = pad.axes[1 - (j >> 1)]; + if (((j & 1) ? v : -v) > 0.5) { + gamepad |= 1 << (i * 8 + j); + } + } + } + if (pad.buttons.length > 9 && pad.buttons[9].pressed) { + restart = true; + } + } + + let u32Mem = U32(memory.buffer); + u32Mem[16] = now - startTime; + u32Mem[17] = pad | gamepad; + instance.exports.upd(); + platform_instance.exports.endFrame(); + + let palette = U32(memory.buffer.slice(0x13000, 0x13000 + 1024)); + for (let i = 0; i < 320 * 240; ++i) { + buffer[i] = palette[memU8[i + 120]] | 0xff000000; + } + canvasCtx.putImageData(imageData, 0, 0); + nextFrame = Math.max(nextFrame + timePerFrame, now); + } + + if (restart) { + runModule(currentData); + } else { + window.requestAnimationFrame(mainloop); + } + } catch (err) { + config.setMessage(cartridgeSize, err.toString()); + } + } + + mainloop(); + } catch (err) { + config.setMessage(cartridgeSize, err.toString()); + } + } + + function downloadBlob(blob, ext) { + let a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = 'microw8_' + new Date().toISOString() + ext; + a.click(); + URL.revokeObjectURL(a.href); + } + + let videoRecorder; + let videoStartTime; + function recordVideo() { + if(videoRecorder) { + videoRecorder.stop(); + videoRecorder = null; + return; + } + + videoRecorder = new MediaRecorder(screen.captureStream(), { + mimeType: 'video/webm', + videoBitsPerSecond: 25000000 + }); + + let chunks = []; + videoRecorder.ondataavailable = e => { + chunks.push(e.data); + }; + + let timer = config.timerElement; + if(timer) { + timer.hidden = false; + timer.innerText = "00:00"; + } + + videoRecorder.onstop = () => { + if(timer) { + timer.hidden = true; + } + downloadBlob(new Blob(chunks, {type: 'video/webm'}), '.webm'); + }; + + videoRecorder.start(); + videoStartTime = Date.now(); + + function updateTimer() { + if(!videoStartTime) { + return; + } + + if(timer) { + let duration = Math.floor((Date.now() - videoStartTime) / 1000); + timer.innerText = Math.floor(duration / 60).toString().padStart(2, '0') + ':' + (duration % 60).toString().padStart(2, '0'); + } + + setTimeout(updateTimer, 1000); + } + + setTimeout(updateTimer, 1000); + } + + async function runModuleFromURL(url, keepUrl) { + let response = await fetch(url); + let type = response.headers.get('Content-Type'); + if(type && type.includes('html')) { + throw false; + } + runModule(await response.arrayBuffer(), keepUrl || devkitMode); + } + + return { + runModule, + runModuleFromURL, + setDevkitMode: (m) => devkitMode = m, + }; +} diff --git a/web/src/run-web.css b/web/src/run-web.css new file mode 100644 index 0000000..ca6d48e --- /dev/null +++ b/web/src/run-web.css @@ -0,0 +1,46 @@ +html, body, canvas { + padding: 0; + margin: 0; + background-color: #202024; +} + +html { + height: 100%; +} + +body { + height: 100%; + display: grid; + grid-template-rows: 1fr; +} + +#screen { + align-self: center; + justify-self: center; + image-rendering: pixelated; + border: 4px solid #303040; +} + +#message { + position: absolute; + width: calc(100% - 16px); + background-color: rgba(0, 0, 0, 0.4); + color: #c64; + padding: 8px; + font: bold 12pt sans-serif; + z-index: 2; +} + +@media (min-width: 648px) and (min-height: 488px) { + #screen { + width: 640px; + height: 480px; + } +} + +@media (min-width: 968px) and (min-height: 728px) { + #screen { + width: 960px; + height: 720px; + } +} diff --git a/web/src/run-web.html b/web/src/run-web.html new file mode 100644 index 0000000..9799736 --- /dev/null +++ b/web/src/run-web.html @@ -0,0 +1,18 @@ + + + + + uw8-run + + + + + +
+ + + \ No newline at end of file diff --git a/web/src/run-web.js b/web/src/run-web.js new file mode 100644 index 0000000..6f1c552 --- /dev/null +++ b/web/src/run-web.js @@ -0,0 +1,19 @@ +import MicroW8 from './microw8.js'; + +let uw8 = MicroW8(document.getElementById('screen'), { + setMessage: (_, err) => { + let elem = document.getElementById('message'); + if(err) { + elem.innerText = err; + } + elem.hidden = !err; + } +}); +let events = new EventSource('events'); +events.onmessage = event => { + console.log(event.data); + if(event.data == 'L') { + uw8.runModuleFromURL('cart', true); + } +}; +uw8.runModuleFromURL('cart', true);