diff options
-rw-r--r-- | Cargo.lock | 1026 | ||||
-rw-r--r-- | Cargo.toml | 23 | ||||
-rw-r--r-- | rust-toolchain.toml | 2 | ||||
-rw-r--r-- | src/bin/br.rs | 521 | ||||
-rw-r--r-- | src/bin/br/config.rs | 122 | ||||
-rw-r--r-- | src/bin/br/load.rs | 81 | ||||
-rw-r--r-- | src/bin/br/main.rs | 233 | ||||
-rw-r--r-- | src/debug.rs | 127 | ||||
-rw-r--r-- | src/devices.rs | 20 | ||||
-rw-r--r-- | src/devices/clock_device.rs | 251 | ||||
-rw-r--r-- | src/devices/file_device.rs | 300 | ||||
-rw-r--r-- | src/devices/file_device/operations.rs | 47 | ||||
-rw-r--r-- | src/devices/input_device.rs | 265 | ||||
-rw-r--r-- | src/devices/math_device.rs | 224 | ||||
-rw-r--r-- | src/devices/memory_device.rs | 240 | ||||
-rw-r--r-- | src/devices/mod.rs | 17 | ||||
-rw-r--r-- | src/devices/remote_device.rs | 35 | ||||
-rw-r--r-- | src/devices/screen_device.rs | 296 | ||||
-rw-r--r-- | src/devices/stream_device.rs (renamed from src/devices/local_device.rs) | 198 | ||||
-rw-r--r-- | src/devices/system_device.rs | 160 | ||||
-rw-r--r-- | src/emulators.rs | 32 | ||||
-rw-r--r-- | src/emulators/graphical_emulator.rs | 396 | ||||
-rw-r--r-- | src/emulators/headless_emulator.rs | 164 | ||||
-rw-r--r-- | src/emulators/mod.rs | 24 | ||||
-rw-r--r-- | src/lib.rs | 47 | ||||
-rw-r--r-- | src/metadata.rs | 127 | ||||
-rw-r--r-- | src/types/buffered_file.rs (renamed from src/devices/file_device/buffered_file.rs) | 7 | ||||
-rw-r--r-- | src/types/controller.rs | 166 | ||||
-rw-r--r-- | src/types/directory_listing.rs (renamed from src/devices/file_device/directory_listing.rs) | 2 | ||||
-rw-r--r-- | src/types/entry_type.rs (renamed from src/devices/file_device/entry.rs) | 3 | ||||
-rw-r--r-- | src/types/file_path.rs (renamed from src/devices/file_device/bedrock_file_path.rs) | 3 | ||||
-rw-r--r-- | src/types/mod.rs | 19 | ||||
-rw-r--r-- | src/types/path_buffer.rs (renamed from src/devices/file_device/bedrock_path_buffer.rs) | 0 | ||||
-rw-r--r-- | src/types/sprite_buffer.rs | 85 | ||||
-rw-r--r-- | src/types/string_buffer.rs | 37 | ||||
-rw-r--r-- | src/types/wake_queue.rs | 51 |
36 files changed, 2989 insertions, 2362 deletions
@@ -4,12 +4,12 @@ version = 4 [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -22,13 +22,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.6.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", "jni-sys", "libc", - "log", + "log 0.4.27", "ndk", "ndk-context", "ndk-sys", @@ -64,6 +64,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] +name = "assembler" +version = "2.3.0" +source = "git+git://benbridle.com/assembler?tag=v2.3.0#a9640fce1aaa5e80170ce4d2ac700f66cfffbb4b" +dependencies = [ + "inked", + "log 2.0.0", + "vagabond", +] + +[[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -71,35 +81,42 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bedrock-asm" -version = "4.0.6" -source = "git+git://benbridle.com/bedrock_asm?tag=v4.0.6#c211280e077703903d968f14e8c6699d9f728aa2" +version = "1.0.2" +source = "git+git://benbridle.com/bedrock-asm?tag=v1.0.2#c4f161b44f3eb2811438b17d55d3d36767aee790" dependencies = [ - "vagabond", - "xflags", + "assembler", + "indexmap", + "log 2.0.0", + "switchboard", ] [[package]] name = "bedrock-core" -version = "5.0.0" -source = "git+git://benbridle.com/bedrock_core?tag=v5.0.0#179bd6a13d91f0a1137ee8ed6aebb7e226e99b5d" +version = "1.0.0" +source = "git+git://benbridle.com/bedrock-core?tag=v1.0.0#9e6a17a24c5dd5748270d07e59c13eca7810ba04" [[package]] name = "bedrock-pc" -version = "1.0.0-alpha3" +version = "1.2.0" dependencies = [ "bedrock-asm", "bedrock-core", "chrono", + "dirs-next", "geometry", + "gilrs", + "inked", + "log 2.0.0", "phosphor", - "windows", - "xflags", + "switchboard", + "vagabond", + "windows 0.58.0", ] [[package]] @@ -110,9 +127,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block2" @@ -134,24 +151,24 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", @@ -160,9 +177,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.7.2" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -170,10 +187,10 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.6.0", - "log", + "bitflags 2.9.1", + "log 0.4.27", "polling", - "rustix", + "rustix 0.38.44", "slab", "thiserror", ] @@ -185,19 +202,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.0.83" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -208,9 +226,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -220,16 +238,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -271,9 +289,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -287,9 +305,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", @@ -304,8 +322,8 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.6.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types", "libc", @@ -328,32 +346,49 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.6.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "libc", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "ctor" -version = "0.2.6" +name = "ctor-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dirs-next" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" dependencies = [ - "quote", - "syn", + "cfg-if", + "dirs-sys-next", ] [[package]] -name = "cursor-icon" -version = "1.1.0" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] [[package]] name = "dispatch" @@ -372,15 +407,15 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "drm" @@ -388,11 +423,11 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "bytemuck", "drm-ffi", "drm-fourcc", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -402,7 +437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" dependencies = [ "drm-sys", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -423,30 +458,36 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] -name = "event_queue" -version = "1.1.0" -source = "git+git://benbridle.com/event_queue?tag=v1.1.0#b3dfdf2edf96d267c050e1c6521372479b5884c8" +name = "event-queue" +version = "1.2.0" +source = "git+git://benbridle.com/event-queue?tag=v1.2.0#f595c3757788a230b31396f32980c20fe0c092cd" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" @@ -492,39 +533,86 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gilrs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f" +dependencies = [ + "fnv", + "gilrs-core", + "log 0.4.27", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d95ae10ce5aa99543a28cf74e41c11f3b9e3c14f0452bbde46024753cd683e" +dependencies = [ + "core-foundation 0.10.1", + "inotify", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log 0.4.27", + "nix", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.61.3", ] [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log 0.4.27", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -538,15 +626,53 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", ] [[package]] +name = "inked" +version = "1.0.0" +source = "git+git://benbridle.com/inked?tag=v1.0.0#2954d37b638fa2c1dd3d51ff53f08f475aea6ea3" +dependencies = [ + "termcolor", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -556,7 +682,7 @@ dependencies = [ "cfg-if", "combine", "jni-sys", - "log", + "log 0.4.27", "thiserror", "walkdir", "windows-sys 0.45.0", @@ -570,54 +696,66 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.159" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.53.2", ] [[package]] name = "libredox" -version = "0.0.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.13", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" @@ -626,22 +764,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" [[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] name = "log" -version = "0.4.20" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "log" +version = "1.1.1" +source = "git+git://benbridle.com/log?tag=v1.1.1#930f3d0e2b82df1243f423c092a38546ea7533c3" + +[[package]] +name = "log" +version = "2.0.0" +source = "git+git://benbridle.com/log?tag=v2.0.0#a38d3dd487594f41151db57625410d1b786bebe4" +dependencies = [ + "inked", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -652,9 +818,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "jni-sys", - "log", + "log 0.4.27", "ndk-sys", "num_enum", "raw-window-handle", @@ -677,6 +843,18 @@ dependencies = [ ] [[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -687,18 +865,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -728,7 +907,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "libc", "objc2", @@ -744,7 +923,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-core-location", @@ -768,7 +947,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -800,9 +979,9 @@ dependencies = [ [[package]] name = "objc2-encode" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" @@ -810,7 +989,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "dispatch", "libc", @@ -835,7 +1014,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -847,7 +1026,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -870,7 +1049,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-cloud-kit", @@ -902,7 +1081,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-core-location", @@ -911,20 +1090,26 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" dependencies = [ "libredox", ] [[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -932,11 +1117,11 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phosphor" -version = "3.2.1" -source = "git+git://benbridle.com/phosphor?tag=v3.2.1#34217d9e35fb3f4abfa0f94184b0fa739f3c36c2" +version = "3.3.2" +source = "git+git://benbridle.com/phosphor?tag=v3.3.2#37d89379b77ea9032c83da570e4382834ae148c6" dependencies = [ "buffer", - "event_queue", + "event-queue", "geometry", "softbuffer", "winit", @@ -944,18 +1129,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -964,46 +1149,45 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" -version = "3.7.3" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1015,23 +1199,29 @@ source = "git+git://benbridle.com/proportion?tag=v1.0.0#291bb4de2a2e5940fbebd8d4 [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1048,27 +1238,57 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "bitflags 2.6.0", + "getrandom 0.2.16", + "libredox", + "thiserror", ] [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", ] [[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1085,18 +1305,18 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1104,19 +1324,22 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.11.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -1124,14 +1347,14 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", "libc", - "log", + "log 0.4.27", "memmap2", - "rustix", + "rustix 0.38.44", "thiserror", "wayland-backend", "wayland-client", @@ -1166,14 +1389,14 @@ dependencies = [ "fastrand", "foreign-types", "js-sys", - "log", + "log 0.4.27", "memmap2", "objc2", "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.7", - "rustix", + "redox_syscall 0.5.13", + "rustix 0.38.44", "tiny-xlib", "wasm-bindgen", "wayland-backend", @@ -1185,10 +1408,19 @@ dependencies = [ ] [[package]] +name = "switchboard" +version = "2.1.0" +source = "git+git://benbridle.com/switchboard?tag=v2.1.0#e6435712ba5b3ca36e99fc8cbe7755940f8b1f3f" +dependencies = [ + "log 1.1.1", + "paste", +] + +[[package]] name = "syn" -version = "2.0.42" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1196,19 +1428,28 @@ dependencies = [ ] [[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1217,27 +1458,28 @@ dependencies = [ [[package]] name = "tiny-xlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4098d49269baa034a8d1eae9bd63e9fa532148d772121dace3bcd6a6c98eb6d" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" dependencies = [ "as-raw-xcb-connection", - "ctor", + "ctor-lite", "libloading", + "pkg-config", "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -1246,9 +1488,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", @@ -1256,15 +1498,15 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -1273,9 +1515,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "vagabond" -version = "1.0.1" -source = "git+git://benbridle.com/vagabond?tag=v1.0.1#08f3153fea62ea81a42438347eeee058f5bec199" +version = "1.1.1" +source = "git+git://benbridle.com/vagabond?tag=v1.1.1#b190582517e6008ad1deff1859f15988e4efaa26" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" @@ -1295,29 +1553,39 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", - "log", - "once_cell", + "log 0.4.27", "proc-macro2", "quote", "syn", @@ -1326,21 +1594,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1348,9 +1617,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -1361,19 +1630,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.44", "scoped-tls", "smallvec", "wayland-sys", @@ -1381,12 +1653,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.6" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.6.0", - "rustix", + "bitflags 2.9.1", + "rustix 0.38.44", "wayland-backend", "wayland-scanner", ] @@ -1397,29 +1669,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.6" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ - "rustix", + "rustix 0.38.44", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.4" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -1427,11 +1699,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1440,11 +1712,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1453,9 +1725,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", "quick-xml", @@ -1464,21 +1736,21 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ "dlib", - "log", + "log 0.4.27", "once_cell", "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1495,6 +1767,22 @@ dependencies = [ ] [[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1504,6 +1792,12 @@ dependencies = [ ] [[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] name = "windows" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1514,12 +1808,25 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -1528,14 +1835,38 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] [[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", +] + +[[package]] name = "windows-implement" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1547,6 +1878,17 @@ dependencies = [ ] [[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "windows-interface" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1558,6 +1900,33 @@ dependencies = [ ] [[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] + +[[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1567,31 +1936,40 @@ dependencies = [ ] [[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-strings" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-targets 0.42.2", + "windows-link", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -1613,6 +1991,15 @@ dependencies = [ ] [[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1651,7 +2038,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -1659,6 +2046,31 @@ dependencies = [ ] [[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1677,6 +2089,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1695,6 +2113,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1713,12 +2137,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1737,6 +2173,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1755,6 +2197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1773,6 +2221,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1791,22 +2245,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] name = "winit" -version = "0.30.5" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.6.0", + "bitflags 2.9.1", "block2", "bytemuck", "calloop", "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics 0.23.1", + "core-graphics 0.23.2", "cursor-icon", "dpi", "js-sys", @@ -1822,7 +2282,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.44", "smithay-client-toolkit", "smol_str", "tracing", @@ -1843,14 +2303,23 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.30" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] [[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1872,7 +2341,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix", + "rustix 0.38.44", "x11rb-protocol", ] @@ -1884,24 +2353,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" - -[[package]] -name = "xflags" -version = "0.4.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4697c0db52cfb7277cf997ed334c92c739fafc7c5d44a948a906a5bf4b41a63f" -dependencies = [ - "xflags-macros", -] - -[[package]] -name = "xflags-macros" -version = "0.4.0-pre.1" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d18ac1a136311770ed587356f8a828c9b86261f68761f34e6cdc6d5b4c435c" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -1909,9 +2363,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "dlib", - "log", + "log 0.4.27", "once_cell", "xkeysym", ] @@ -1924,18 +2378,18 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -1,22 +1,31 @@ [package] name = "bedrock-pc" -version = "1.0.0-alpha3" +version = "1.2.0" authors = ["Ben Bridle"] -edition = "2021" -description = "Emulator for running Bedrock programs" +edition = "2024" +description = "Bedrock emulator" [dependencies] -bedrock-asm = { git = "git://benbridle.com/bedrock_asm", tag = "v4.0.6" } -bedrock-core = { git = "git://benbridle.com/bedrock_core", tag = "v5.0.0" } -phosphor = { git = "git://benbridle.com/phosphor", tag = "v3.2.1" } +bedrock-asm = { git = "git://benbridle.com/bedrock-asm", tag = "v1.0.2" } +bedrock-core = { git = "git://benbridle.com/bedrock-core", tag = "v1.0.0" } +phosphor = { git = "git://benbridle.com/phosphor", tag = "v3.3.2" } geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" } +inked = { git = "git://benbridle.com/inked", tag = "v1.0.0" } +log = { git = "git://benbridle.com/log", tag = "v2.0.0" } +switchboard = { git = "git://benbridle.com/switchboard", tag = "v2.1.0" } +vagabond = { git = "git://benbridle.com/vagabond", tag = "v1.1.1" } chrono = { version = "0.4.38" } -xflags = "0.4.0-pre.1" +gilrs = { version = "0.11.0", optional = true } +dirs-next = "1.0.2" [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = ["Win32_Storage_FileSystem"] } +[features] +default = ["gamepad"] +gamepad = ["dep:gilrs"] + [profile.release] lto=true opt-level="s" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/bin/br.rs b/src/bin/br.rs deleted file mode 100644 index 431ae39..0000000 --- a/src/bin/br.rs +++ /dev/null @@ -1,521 +0,0 @@ -use bedrock_asm::*; -use bedrock_pc::*; -use phosphor::*; - -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::exit; - - -const NORMAL: &str = "\x1b[0m"; -const BOLD: &str = "\x1b[1m"; -const WHITE: &str = "\x1b[37m"; -const RED: &str = "\x1b[31m"; -const BLUE: &str = "\x1b[34m"; - -static mut VERBOSE: bool = false; - -macro_rules! verbose { - ($($tokens:tt)*) => { if unsafe { VERBOSE } { - eprint!("{BOLD}{BLUE}[INFO]{NORMAL}: "); eprint!($($tokens)*); - eprintln!("{NORMAL}"); - } }; -} -macro_rules! error { - ($($tokens:tt)*) => {{ - eprint!("{BOLD}{RED}[ERROR]{WHITE}: "); eprint!($($tokens)*); - eprintln!("{NORMAL}"); - }}; -} - - -fn main() { - let args = Arguments::from_env_or_exit(); - if args.version { - let name = env!("CARGO_PKG_NAME"); - let version = env!("CARGO_PKG_VERSION"); - eprintln!("{name} v{version}"); - eprintln!("Written by Ben Bridle."); - std::process::exit(0); - } - if args.verbose { - unsafe { VERBOSE = true; } - } - match args.subcommand { - ArgumentsCmd::Run(run) => main_run(run), - ArgumentsCmd::Asm(asm) => main_asm(asm), - } -} - -fn main_run(args: Run) { - let program_path = args.program.as_ref().map(|p| p.as_path()); - let Bytecode { bytes: bytecode, path } = load_bytecode(program_path); - let symbols_path = path.as_ref().map(|p| { - let mut path = p.to_path_buf(); - path.set_extension("br.sym"); - path - }); - - let metadata = parse_metadata(&bytecode); - if metadata.is_none() { - verbose!("Could not read program metadata"); - } - - let mut config = EmulatorConfig { - dimensions: ScreenDimensions::ZERO, - fullscreen: args.fullscreen, - scale: args.scale(), - debug_palette: args.palette(), - show_cursor: args.show_cursor, - initial_transmission: None, - decode_stdin: args.decode_stdin, - encode_stdout: args.encode_stdout, - symbols_path, - }; - let phosphor = Phosphor::new(); - - if phosphor.is_ok() && args.dimensions().is_some() { - verbose!("Starting graphical emulator"); - let mut phosphor = phosphor.unwrap(); - config.dimensions = args.dimensions().unwrap(); - let cursor = match config.show_cursor { - true => Some(CursorIcon::Default), - false => None, - }; - - let mut graphical = GraphicalEmulator::new(&config, args.debug, unsafe {VERBOSE}); - graphical.load_program(&bytecode); - if let EmulatorSignal::Promote = graphical.run() { - let program_name = match &metadata { - Some(metadata) => match &metadata.name { - Some(name) => name.to_string(), - None => String::from("Bedrock"), - } - None => String::from("Bedrock"), - }; - let window = WindowBuilder { - dimensions: Some(graphical.dimensions()), - size_bounds: Some(graphical.size_bounds()), - fullscreen: graphical.fullscreen, - scale: graphical.scale, - title: Some(program_name), - cursor, - icon: None, - program: Box::new(graphical), - }; - - phosphor.create_window(window); - phosphor.run().unwrap(); - } - } else { - verbose!("Starting headless emulator"); - let mut headless = HeadlessEmulator::new(&config, args.debug, unsafe {VERBOSE}); - headless.load_program(&bytecode); - headless.run(args.debug); - }; - - std::process::exit(0); -} - -fn load_bytecode(path: Option<&Path>) -> Bytecode { - // TODO: Etch file location into bytecode. - if let Some(path) = path { - if let Ok(bytecode) = load_bytecode_from_file(path) { - let length = bytecode.bytes.len(); - let path = bytecode.path(); - verbose!("Loaded program from {path:?} ({length} bytes)"); - return bytecode; - } else if let Some(bytecode) = load_bytecode_from_bedrock_path(path) { - let length = bytecode.bytes.len(); - let path = bytecode.path(); - verbose!("Loaded program from {path:?} ({length} bytes)"); - return bytecode; - } else { - error!("Could not read program from {path:?}, exiting"); - exit(1); - } - } else { - verbose!("Reading program from standard input..."); - if let Ok(bytecode) = load_bytecode_from_stdin() { - let length = bytecode.bytes.len(); - verbose!("Loaded program from standard input ({length} bytes)"); - return bytecode; - } else { - error!("Could not read program from standard input, exiting"); - exit(1); - } - } -} - -/// Attempt to load bytecode from a directory in the BEDROCK_PATH environment variable. -fn load_bytecode_from_bedrock_path(path: &Path) -> Option<Bytecode> { - if path.is_relative() && path.components().count() == 1 { - for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') { - let mut base_path = PathBuf::from(base_path); - if !base_path.is_absolute() { continue; } - base_path.push(path); - verbose!("Attempting to load program from {base_path:?}"); - if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some(bytecode); - } - if path.extension().is_some() { continue; } - base_path.set_extension("br"); - verbose!("Attempting to load program from {base_path:?}"); - if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some(bytecode); - } - } - } - return None; -} - -/// Attempt to load bytecode from a file path. -fn load_bytecode_from_file(path: &Path) -> Result<Bytecode, std::io::Error> { - // Canonicalize paths so that symbolic links to program files resolve to - // the real program directory, which could contain a symbols file. - let path = match path.canonicalize() { - Ok(canonical) => canonical, - Err(_) => path.to_path_buf(), - }; - load_bytecode_from_readable_source(std::fs::File::open(&path)?, Some(&path)) -} - -/// Attempt to load bytecode from standard input. -fn load_bytecode_from_stdin() -> Result<Bytecode, std::io::Error> { - load_bytecode_from_readable_source(std::io::stdin(), None) -} - -/// Attempt to load bytecode from a source that implements std::io::Read. -fn load_bytecode_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<Bytecode, std::io::Error> { - let mut bytes = Vec::<u8>::new(); - source.take(65536).read_to_end(&mut bytes)?; - return Ok(Bytecode { bytes, path: path.map(|p| p.to_path_buf()) }); -} - -struct Bytecode { - bytes: Vec<u8>, - path: Option<PathBuf>, -} - -impl Bytecode { - fn path(&self) -> String { - match &self.path { - Some(path) => path.as_os_str().to_string_lossy().to_string(), - None => String::from("<unknown>"), - } - } -} - - -fn main_asm(args: Asm) { - // ----------------------------------------------------------------------- - // RESOLVE syntactic symbols - let ext = args.ext.unwrap_or(String::from("brc")); - let source_path = args.source.clone().map(|p| { - p.canonicalize().unwrap_or(p) - }); - - let mut resolver = if let Some(path) = &source_path { - match SourceUnit::from_path(&path, &ext) { - Ok(source_unit) => SymbolResolver::from_source_unit(source_unit), - Err(err) => { - match err { - ParseError::InvalidExtension => error!( - "File {path:?} has invalid extension, must be '.{ext}'"), - ParseError::NotFound => error!( - "File {path:?} was not found"), - ParseError::InvalidUtf8 => error!( - "File {path:?} does not contain valid UTF-8 text"), - ParseError::NotReadable => error!( - "File {path:?} is not readable"), - ParseError::IsADirectory => error!( - "File {path:?} is a directory"), - ParseError::Unknown => error!( - "Unknown error while attempting to read from {path:?}") - }; - exit(1); - } - } - } else { - let mut source_code = String::new(); - verbose!("Reading program source from standard input"); - if let Err(err) = std::io::stdin().read_to_string(&mut source_code) { - error!("Could not read from standard input"); - eprintln!("{err:?}"); - exit(1); - } - let path = "<standard input>"; - let source_unit = SourceUnit::from_source_code(source_code, path); - SymbolResolver::from_source_unit(source_unit) - }; - // Load project libraries. - if let Some(path) = &source_path { - if !args.no_libs && !args.no_project_libs { - let project_library = gather_project_libraries(path, &ext); - resolver.add_library_units(project_library); - } - } - // Load environment libraries. - if !args.no_libs && !args.no_env_libs { - for env_library in gather_environment_libraries(&ext) { - resolver.add_library_units(env_library); - } - } - resolver.resolve(); - - // ----------------------------------------------------------------------- - // PRINT information, generate merged source code - if args.tree { - print_source_tree(&resolver); - } - if print_resolver_errors(&resolver) { - std::process::exit(1); - }; - let merged_source = match resolver.get_merged_source_code() { - Ok(merged_source) => merged_source, - Err(ids) => { - print_cyclic_source_units(&ids, &resolver); - std::process::exit(1); - }, - }; - if args.resolve && !args.check { - write_bytes_and_exit(merged_source.as_bytes(), args.output.as_ref()); - } - - // ----------------------------------------------------------------------- - // PARSE semantic tokens from merged source code - let path = Some("<merged source>"); - let mut semantic_tokens = generate_semantic_tokens(&merged_source, path); - if print_semantic_errors(&semantic_tokens, &merged_source) { - std::process::exit(1); - }; - - // ----------------------------------------------------------------------- - // GENERATE symbols file and bytecode - let bytecode = generate_bytecode(&mut semantic_tokens); - - if args.symbols && !args.check { - if let Some(path) = &args.output { - let mut symbols_path = path.to_path_buf(); - symbols_path.set_extension("br.sym"); - let symbols = generate_symbols_file(&semantic_tokens); - if let Err(err) = std::fs::write(&symbols_path, symbols) { - verbose!("Could not write to symbols path {symbols_path:?}"); - eprintln!("{err:?}"); - } else { - verbose!("Saved debug symbols to {symbols_path:?}"); - } - } - } - - let length = bytecode.len(); - let percentage = (length as f32 / 65536.0 * 100.0).round() as u16; - verbose!("Assembled program in {length} bytes ({percentage}% of maximum)"); - - if !args.check { - write_bytes_and_exit(&bytecode, args.output.as_ref()); - } -} - -fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! { - if let Some(path) = path { - if let Err(err) = std::fs::write(path, bytes) { - error!("Could not write to path {:?}", path.as_ref()); - eprintln!("{err:?}"); - exit(1); - } - } else { - if let Err(err) = std::io::stdout().write_all(bytes) { - error!("Could not write to standard output"); - eprintln!("{err:?}"); - exit(1); - } - } - exit(0); -} - - -xflags::xflags! { - /// Integrated Bedrock assembler and emulator. - /// - /// The arguments and options shown are for running a Bedrock program. - /// To see the documentation for the assembler, run `br asm --help` - /// - /// Usage: - /// To load a Bedrock program from a file, run `br [path]`, where - /// [path] is the path of an assembled Bedrock program. - /// - /// To load a Bedrock program from piped input, run `[command] | br`, - /// where [command] is a command that generates Bedrock bytecode. - /// - /// To assemble a Bedrock program from a source file and write to an - /// output file, run `br asm [source] [output]`, where [source] is the - /// path of the source file and [output] is the path to write to. - /// - /// To assemble and run a Bedrock program without saving to a file, - /// run `br asm [source] | br`, where [source] is the path of the - /// source file. - /// - /// Environment variables: - /// BEDROCK_PATH - /// A list of colon-separated paths which will be searched to find - /// a Bedrock program when the program doesn't exist in the current - /// directory. This allows the user to run frequently-used programs - /// from any directory. - /// BEDROCK_LIBS - /// A list of colon-separated paths which will be searched to find - /// Bedrock source code files to use as libraries when assembling a - /// Bedrock program. If a library file resolves an unresolved symbol - /// in the program being assembled, the library file will be merged - /// into the program. - cmd arguments { - /// Print additional debug information - optional -v, --verbose - /// Print the assembler version and exit - optional --version - - /// Run a Bedrock program (this is the default command) - default cmd run { - /// Path to a Bedrock program to run - optional program: PathBuf - - /// Show debug information while the program is running - optional -d, --debug - /// Start the program in fullscreen mode (toggle with F11) - optional -f, --fullscreen - /// Set the initial window size in the format <width>x<height> - optional -s, --screen size: ParsedDimensions - /// Set the pixel size for the screen (change with F5/F6) - optional -z, --zoom factor: u32 - /// Set a debug colour palette in the format <rgb>,... (toggle with F2) - optional --palette colours: ParsedPalette - /// Show the operating system cursor over the window - optional --show-cursor - /// Decode standard input - optional -i, --decode-stdin - /// Encode standard output - optional -o, --encode-stdout - } - - /// Assemble a Bedrock program from source code - cmd asm { - /// Bedrock source code file to assemble. - optional source: PathBuf - /// Destination path for assembler output. - optional output: PathBuf - /// File extension to identify source files. - optional ext: String - - /// Don't include libraries or resolve references. - optional --no-libs - /// Don't include project libraries - optional --no-project-libs - /// Don't include environment libraries. - optional --no-env-libs - - /// Show the resolved source file heirarchy - optional --tree - /// Assemble the program without saving any output - optional --check - /// Only return resolved source code. - optional --resolve - /// Generate debug symbols with file extension '.br.sym' - optional --symbols - } - } -} - - -impl Run { - pub fn dimensions(&self) -> Option<geometry::Dimensions<u16>> { - let size = match &self.screen { - Some(parsed) => geometry::Dimensions::new(parsed.width, parsed.height), - None => DEFAULT_SCREEN_SIZE / (self.scale() as u16), - }; - match size.is_zero() { - true => None, - false => Some(size), - } - } - - pub fn scale(&self) -> u32 { - std::cmp::max(1, self.zoom.unwrap_or(1)) - } - - pub fn palette(&self) -> Option<[Colour; 16]> { - self.palette.as_ref().map(|p| p.palette) - } -} - - -#[derive(Debug)] -struct ParsedDimensions { width: u16, height: u16 } -impl std::str::FromStr for ParsedDimensions { - type Err = ParsedDimensionsError; - fn from_str(s: &str) -> Result<Self, Self::Err> { - if s == "none" { - Ok( Self { width: 0, height: 0 } ) - } else { - let (w_str, h_str) = s.split_once('x').ok_or(ParsedDimensionsError)?; - Ok( Self { - width: u16::from_str(w_str).or(Err(ParsedDimensionsError))?, - height: u16::from_str(h_str).or(Err(ParsedDimensionsError))?, - } ) - } - } -} - -pub struct ParsedDimensionsError; -impl std::fmt::Display for ParsedDimensionsError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - f.write_str("try giving a value like 800x600") - } -} - - -#[derive(Debug)] -pub struct ParsedPalette { palette: [Colour; 16] } -impl std::str::FromStr for ParsedPalette { - type Err = ParsedPaletteError; - fn from_str(s: &str) -> Result<Self, Self::Err> { - fn decode_ascii_hex_digit(ascii: u8) -> Result<u8, ParsedPaletteError> { - match ascii { - b'0'..=b'9' => Ok(ascii - b'0'), - b'a'..=b'f' => Ok(ascii - b'a' + 10), - b'A'..=b'F' => Ok(ascii - b'A' + 10), - _ => { Err(ParsedPaletteError)} - } - } - let mut colours = Vec::new(); - for token in s.split(',') { - let mut bytes = token.bytes(); - if bytes.len() != 3 { return Err(ParsedPaletteError); } - let r = decode_ascii_hex_digit(bytes.next().unwrap())?; - let g = decode_ascii_hex_digit(bytes.next().unwrap())?; - let b = decode_ascii_hex_digit(bytes.next().unwrap())?; - colours.push(Colour::from_rgb(r*17, g*17, b*17)); - } - let c = &colours; - let palette = match colours.len() { - 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], - c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], - 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], - c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], - 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], - c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], - 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], - c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], - 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], - c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], - _ => return Err(ParsedPaletteError), - }; - Ok( Self { palette }) - } -} - -pub struct ParsedPaletteError; -impl std::fmt::Display for ParsedPaletteError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - f.write_str("try giving a value like 000,fff,880,808") - } -} diff --git a/src/bin/br/config.rs b/src/bin/br/config.rs new file mode 100644 index 0000000..56d5190 --- /dev/null +++ b/src/bin/br/config.rs @@ -0,0 +1,122 @@ +use crate::*; + + +#[derive(Copy, Clone, PartialEq)] +pub enum Mode { + Graphical, + Headless, + Dynamic, +} + +impl Mode { + pub fn from_str(string: &str) -> Self { + match string { + "g" | "graphical" => Self::Graphical, + "h" | "headless" => Self::Headless, + "d" | "dynamic" => Self::Dynamic, + _ => fatal!("Invalid mode string '{string}'"), + } + } +} + + +pub fn parse_dimensions(string: &str) -> ScreenDimensions { + fn parse_inner(string: &str) -> Option<ScreenDimensions> { + let (w_str, h_str) = string.trim().split_once('x')?; + Some( ScreenDimensions { + width: w_str.parse().ok()?, + height: h_str.parse().ok()?, + } ) + } + let dimensions = parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid dimensions string '{string}'"); + }); + if dimensions.is_zero() { + fatal!("Screen dimensions must be greater than zero"); + } + return dimensions; +} + + +pub fn parse_palette(string: &str) -> [Colour; 16] { + fn decode_ascii_hex_digit(ascii: u8) -> Option<u8> { + match ascii { + b'0'..=b'9' => Some(ascii - b'0'), + b'a'..=b'f' => Some(ascii - b'a' + 10), + b'A'..=b'F' => Some(ascii - b'A' + 10), + _ => { None } + } + } + fn parse_inner(string: &str) -> Option<[Colour; 16]> { + let mut c = Vec::new(); + for token in string.split(',') { + let mut bytes = token.bytes(); + if bytes.len() != 3 { return None; } + let r = decode_ascii_hex_digit(bytes.next().unwrap())?; + let g = decode_ascii_hex_digit(bytes.next().unwrap())?; + let b = decode_ascii_hex_digit(bytes.next().unwrap())?; + c.push(Colour::from_rgb(r*17, g*17, b*17)); + } + Some(match c.len() { + 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], + c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], + 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], + c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], + 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], + c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], + 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], + 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], + _ => return None, + }) + } + parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid palette string '{string}'"); + }) +} + + +pub fn parse_metadata_colour(colour: Option<MetadataColour>) -> Option<Colour> { + let c = colour?; + Some(Colour::from_rgb(c.red, c.green, c.blue)) +} + + +pub fn parse_small_icon(bytes: Option<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> { + let rgba = sprite_data_to_rgb(&bytes?, 3, bg, fg); + match Icon::from_rgba(rgba, 24, 24) { + Ok(icon) => Some(icon), + Err(err) => unreachable!("Error while parsing small icon data: {err}"), + } +} + +pub fn parse_large_icon(bytes: Option<Vec<u8>>, bg: Colour, fg: Colour) -> Option<Icon> { + let rgba = sprite_data_to_rgb(&bytes?, 8, bg, fg); + match Icon::from_rgba(rgba, 64, 64) { + Ok(icon) => Some(icon), + Err(err) => unreachable!("Error while parsing large icon data: {err}"), + } +} + +fn sprite_data_to_rgb(bytes: &[u8], size: usize, bg: Colour, fg: Colour) -> Vec<u8> { + let sprites: Vec<&[u8]> = bytes.chunks_exact(8).collect(); + let mut rgba = Vec::new(); + for sprite_row in 0..size { + for pixel_row in 0..8 { + for sprite_column in 0..size { + let sprite = &sprites[sprite_column + (sprite_row * size)]; + let row = &sprite[pixel_row]; + for bit in 0..8 { + let state = row & (0x80 >> bit); + let colour = match state != 0 { + true => fg, + false => bg, + }; + rgba.extend_from_slice(&colour.as_rgba_array()); + } + } + } + } + return rgba; +} diff --git a/src/bin/br/load.rs b/src/bin/br/load.rs new file mode 100644 index 0000000..93a748c --- /dev/null +++ b/src/bin/br/load.rs @@ -0,0 +1,81 @@ +use crate::*; + +use std::io::Read; +use std::path::{Path, PathBuf}; + + +pub struct LoadedProgram { + pub bytecode: Vec<u8>, + pub path: Option<PathBuf>, +} + +/// Load program from path or standard input. +pub fn load_program(path: Option<&PathBuf>) -> LoadedProgram { + if let Some(path) = path { + if let Ok(program) = load_program_from_file(path) { + let length = program.bytecode.len(); + info!("Loaded program from {path:?} ({length} bytes)"); + return program; + } else if let Some(program) = load_program_from_env(path) { + let length = program.bytecode.len(); + info!("Loaded program from {path:?} ({length} bytes)"); + return program; + } else { + fatal!("Could not read program from {path:?}"); + } + } else { + info!("Reading program from standard input..."); + if let Ok(program) = load_program_from_stdin() { + let length = program.bytecode.len(); + info!("Loaded program from standard input ({length} bytes)"); + return program; + } else { + fatal!("Could not read program from standard input"); + } + } +} + +/// Attempt to load program from a directory in the BEDROCK_PATH environment variable. +fn load_program_from_env(path: &Path) -> Option<LoadedProgram> { + // Check that the path is a bare program name. + if path.is_relative() && path.components().count() == 1 { + for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') { + let mut base_path = PathBuf::from(base_path); + // Skip relative paths. + if !base_path.is_absolute() { continue; } + base_path.push(path); + if let Ok(program) = load_program_from_file(&base_path) { + return Some(program); + } + if path.extension().is_some() { continue; } + base_path.set_extension("br"); + if let Ok(program) = load_program_from_file(&base_path) { + return Some(program); + } + } + } + return None; +} + +/// Attempt to load program from a file path. +fn load_program_from_file(path: &Path) -> Result<LoadedProgram, std::io::Error> { + // Canonicalize paths so that symbolic links to program files resolve to + // the real program directory, which could contain a symbols file. + let path = match path.canonicalize() { + Ok(canonical) => canonical, + Err(_) => path.to_path_buf(), + }; + load_program_from_readable_source(std::fs::File::open(&path)?, Some(&path)) +} + +/// Attempt to load program from standard input. +fn load_program_from_stdin() -> Result<LoadedProgram, std::io::Error> { + load_program_from_readable_source(std::io::stdin(), None) +} + +/// Attempt to load program from a source that implements std::io::Read. +fn load_program_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<LoadedProgram, std::io::Error> { + let mut bytecode = Vec::<u8>::new(); + source.take(65536).read_to_end(&mut bytecode)?; + return Ok(LoadedProgram { bytecode, path: path.map(|p| p.to_path_buf()) }); +} diff --git a/src/bin/br/main.rs b/src/bin/br/main.rs new file mode 100644 index 0000000..da11a18 --- /dev/null +++ b/src/bin/br/main.rs @@ -0,0 +1,233 @@ +#![feature(path_add_extension)] + +mod config; +mod load; +pub use config::*; +pub use load::*; + +use bedrock_asm::*; +use bedrock_core::*; +use bedrock_pc::*; +use log::*; +use switchboard::*; +use phosphor::*; + +use std::cmp::{min, max}; +use std::num::NonZeroU32; + + +fn main() { + let mut args = Switchboard::from_env(); + if let Some("asm") = args.peek() { + args.pop(); + assemble(args, "br asm"); + } + + // ----------------------------------------------------------------------- + + args.named("help").short('h'); + args.named("version"); + args.named("verbose").short('v'); + + if args.get("help").as_bool() { + print_help(); + std::process::exit(0); + } + if args.get("version").as_bool() { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + eprintln!("{name} v{version}"); + eprintln!("Written by Ben Bridle."); + std::process::exit(0); + } + if args.get("verbose").as_bool() { + log::set_log_level(log::LogLevel::Info); + } + + args.positional("source"); + args.named("debug").short('d'); + args.named("mode").default("dynamic"); + + args.named("palette"); + args.named("fullscreen").short('f'); + args.named("show-cursor").short('c'); + args.named("zoom").short('z').quick("3").default("1"); + args.named("size").short('s'); + args.named("decode-stdin").short('i'); + args.named("encode-stdout").short('o'); + args.named("trust-files"); + args.raise_errors(); + + let source = args.get("source").as_path_opt(); + let debug = args.get("debug").as_bool(); + let mode = Mode::from_str(args.get("mode").as_str()); + let palette = args.get("palette").as_str_opt().map(parse_palette); + let fullscreen = args.get("fullscreen").as_bool(); + let show_cursor = args.get("show-cursor").as_bool(); + let zoom_raw = min(10, max(1, args.get("zoom").as_u32())); + let zoom = unsafe { NonZeroU32::new_unchecked(zoom_raw) }; + let dimensions = match args.get("size").as_str_opt() { + Some(string) => parse_dimensions(string), + None => DEFAULT_SCREEN_SIZE / (zoom_raw as u16), + }; + let decode_stdin = args.get("decode-stdin").as_bool(); + let encode_stdout = args.get("encode-stdout").as_bool(); + let trust_files = args.get("trust-files").as_bool(); + + // ----------------------------------------------------------------------- + + let LoadedProgram { bytecode, path } = load_program(source.as_ref()); + let mut title = String::from("Bedrock program"); + let mut icon = None; + + let metadata = Metadata::from(&bytecode); + + if let Some(ref metadata) = metadata { + let name = metadata.name().unwrap_or("unnamed".to_string()); + let authors = metadata.authors().unwrap_or_else(Vec::new); + let mut metadata_string = format!("Program is '{name}'"); + if authors.len() > 0 { + metadata_string.push_str(&format!(", by {}", authors[0])); } + if authors.len() > 1 { + metadata_string.push_str(" and others"); } + info!("{metadata_string}"); + + match name.split_once('/') { + Some((name, _version)) if !name.is_empty() => title = name.to_string(), + _ => title = name.to_string(), + } + let bg = parse_metadata_colour(metadata.bg_colour()).unwrap_or(Colour::rgb(0x1E1C26)); + let fg = parse_metadata_colour(metadata.fg_colour()).unwrap_or(Colour::rgb(0xED614F)); + match parse_large_icon(metadata.large_icon(), bg, fg) { + Some(large_icon) => icon = Some(large_icon), + None => match parse_small_icon(metadata.small_icon(), bg, fg) { + Some(small_icon) => icon = Some(small_icon), + None => (), + } + } + } else { + info!("Program does not contain metadata"); + } + + let symbols_path = path.as_ref().map(|p| { + let mut path = p.to_path_buf(); + path.add_extension("sym"); path + }); + + let name = metadata.and_then(|m| m.name()).and_then(|n| match n.split_once('/') { + Some((name, _)) => Some(name.to_string()), + None => Some(n), + }); + let identifier = name.as_ref().and_then( + |n| Some(n.to_lowercase().chars().filter_map( + |c| c.is_alphanumeric().then_some(c) + ).collect()) + ); + + let config = EmulatorConfig { + dimensions, fullscreen, zoom, palette, show_cursor, + decode_stdin, encode_stdout, trust_files, + symbols_path, name, identifier, title, icon, + }; + + match Phosphor::new() { + Ok(phosphor) => match mode { + Mode::Dynamic => { + info!("Starting graphical emulator (hidden)"); + let mut emulator = GraphicalEmulator::new(config, debug); + emulator.load_program(&bytecode); + emulator.run(phosphor, false); + } + Mode::Graphical => { + info!("Starting graphical emulator"); + let mut emulator = GraphicalEmulator::new(config, debug); + emulator.load_program(&bytecode); + emulator.run(phosphor, true); + } + Mode::Headless => { + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.load_program(&bytecode); + emulator.run(); + } + } + Err(err) => match mode { + Mode::Dynamic => { + eprintln!("EventLoopError: {err:?}"); + info!("Could not start graphical event loop"); + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.load_program(&bytecode); + emulator.run(); + } + Mode::Graphical => { + eprintln!("EventLoopError: {err:?}"); + fatal!("Could not start graphical event loop"); + } + Mode::Headless => { + info!("Starting headless emulator"); + let mut emulator = HeadlessEmulator::new(&config, debug); + emulator.load_program(&bytecode); + emulator.run(); + } + } + } +} + + +fn print_help() { + eprintln!("\ +Usage: br [source] + br asm [source] [destination] + +Emulator and assembler for the Bedrock computer system. + +To access the assembler, run `br asm`. To learn how to use the +assembler, run `br asm --help`. + +Usage: + To load a Bedrock program from a file, run `br <path>`, where + <path> is the path to an assembled Bedrock program. + + To load a Bedrock program from piped input, run `<command> | br`, + where <command> is a command that generates Bedrock bytecode. + + To assemble a Bedrock program from a source file and write to an + output file, run `br asm <source> <output>`, where <source> is the + path of the source file and <output> is the path to write to. + + To assemble and run a Bedrock program without saving to a file, + run `br asm <source> | br`, where <source> is the path of the + source file. + +Environment variables: + BEDROCK_PATH + A list of colon-separated paths that will be searched to find + a Bedrock program when the program doesn't exist in the current + directory. This allows the user to run frequently-used programs + from any directory. + BEDROCK_LIBS + A list of colon-separated paths that will be searched to find + Bedrock source code files to use as libraries when assembling a + Bedrock program. If a library file resolves an unresolved symbol + in the program being assembled, the library file will be merged + into the program. + +Arguments: + [program] Path to a Bedrock program to run + +Switches: + --fullscreen (-f) Start the program in fullscreen mode (toggle with F11) + --size=<dim> (-s) Set the initial window size in the format <width>x<height> + --zoom=<scale> (-z) Set the pixel size for the screen (change with F5/F6) + --show-cursor (-c) Show the operating system cursor over the window + --palette=<pal> Set a debug colour palette in the format <rgb>,... (toggle with F2) + --debug, (-d) Show debug information while the program is running + --decode-stdin (-i) Decode transmissions on standard input from text lines. + --encode-stdout (-o) Encode transmissions on standard output as text lines. + --trust-files Give the program unrestricted access to the file system. + --help (-h) Print this help information + --verbose, (-v) Print additional information + --version Print the program version and exit +"); +} diff --git a/src/debug.rs b/src/debug.rs index 6270948..a0746e7 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,86 +1,85 @@ -use bedrock_core::*; +use crate::*; -use std::path::Path; -use std::time::Instant; - - -const NORMAL: &str = "\x1b[0m"; -const BOLD: &str = "\x1b[1m"; -const DIM: &str = "\x1b[2m"; -const YELLOW: &str = "\x1b[33m"; -const BLUE: &str = "\x1b[34m"; +use inked::{ink, InkedString}; pub struct DebugState { pub enabled: bool, - pub verbose: bool, last_cycle: usize, - last_mark: Instant, + mark: Instant, symbols: DebugSymbols, } impl DebugState { - pub fn new<P: AsRef<Path>>(enabled: bool, verbose: bool, symbols_path: Option<P>) -> Self { + pub fn new<P: AsRef<Path>>(enabled: bool, symbols_path: Option<P>) -> Self { Self { enabled, - verbose, last_cycle: 0, - last_mark: Instant::now(), - symbols: DebugSymbols::from_path_opt(symbols_path), - } - } - - pub fn info(&self, string: &str) { - if self.verbose { - eprintln!("{BOLD}{BLUE}[INFO]{NORMAL}: {string}{NORMAL}"); + mark: Instant::now(), + symbols: DebugSymbols::from_path(symbols_path), } } - pub fn debug_summary(&mut self, core: &BedrockCore) { + pub fn debug_full(&mut self, core: &BedrockCore) { if self.enabled { let prev_pc = core.mem.pc.wrapping_sub(1); - eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", - prev_pc, core.cycle, - core.cycle.saturating_sub(self.last_cycle), - self.last_mark.elapsed(), - ); - eprint!("WST: "); - debug_stack(&core.wst, 0x10); - eprint!("RST: "); - debug_stack(&core.rst, 0x10); - // Print information about the closest symbol. + let cycle = core.cycle; + let delta = core.cycle.saturating_sub(self.last_cycle); + let elapsed = self.mark.elapsed(); + + eprintln!(" PC: 0x{prev_pc:04x} Cycle: {cycle} (+{delta} in {elapsed:.2?})"); + eprint!("WST: "); debug_stack(&core.wst); + eprint!("RST: "); debug_stack(&core.rst); + // Print information about the nearest symbol. if let Some(symbol) = self.symbols.for_address(prev_pc) { let name = &symbol.name; let address = &symbol.address; - eprint!("SYM: {BLUE}@{name}{NORMAL} 0x{address:04x}"); + let mut string = InkedString::new(); + string.push(ink!("SYM: ")); + string.push(ink!("@{name}").blue()); + string.push(ink!(" 0x{address:04X}")); if let Some(location) = &symbol.location { - eprint!(" {DIM}{location}{NORMAL}"); + string.push(ink!(" ")); + string.push(ink!("{location}").dim()); } - eprintln!(); + string.eprintln(); } + eprintln!(); + } + self.last_cycle = core.cycle; + self.mark = Instant::now(); + } + + pub fn debug_timing(&mut self, core: &BedrockCore) { + if self.enabled { + let cycle = core.cycle; + let delta = core.cycle.saturating_sub(self.last_cycle); + let elapsed = self.mark.elapsed(); + + eprintln!("Cycle: {cycle} (+{delta} in {elapsed:.2?})"); } self.last_cycle = core.cycle; - self.last_mark = Instant::now(); + self.mark = Instant::now(); } } -fn debug_stack(stack: &Stack, len: usize) { - for i in 0..len { - if i == stack.sp as usize { eprint!("{YELLOW}"); } - eprint!("{:02x} ", stack.mem[i]); +fn debug_stack(stack: &Stack) { + for i in 0..(stack.sp as usize) { + eprint!("{:02X} ", stack.mem[i]); } - eprintln!("{NORMAL}"); + eprintln!(); } -struct DebugSymbols { - symbols: Vec<DebugSymbol> + +pub struct DebugSymbols { + pub symbols: Vec<DebugSymbol> } impl DebugSymbols { /// Load debug symbols from a symbols file. - pub fn from_path_opt<P: AsRef<Path>>(path: Option<P>) -> Self { + pub fn from_path<P: AsRef<Path>>(path: Option<P>) -> Self { let mut symbols = Vec::new(); if let Some(path) = path { if let Ok(string) = std::fs::read_to_string(path) { @@ -95,36 +94,34 @@ impl DebugSymbols { Self { symbols } } + /// Return the symbol matching a given program address. pub fn for_address(&self, address: u16) -> Option<&DebugSymbol> { if self.symbols.is_empty() { return None; } - let symbol = match self.symbols.binary_search_by_key(&address, |s| s.address) { - Ok(index) => self.symbols.get(index)?, - Err(index) => self.symbols.get(index.checked_sub(1)?)?, - }; - Some(&symbol) + match self.symbols.binary_search_by_key(&address, |s| s.address) { + Ok(index) => self.symbols.get(index), + Err(index) => self.symbols.get(index.checked_sub(1)?), + } } } -struct DebugSymbol { - address: u16, - name: String, - location: Option<String>, + +pub struct DebugSymbol { + pub address: u16, + pub name: String, + pub location: Option<String>, } impl DebugSymbol { pub fn from_line(line: &str) -> Option<Self> { - if let Some((address, line)) = line.split_once(' ') { - let address = u16::from_str_radix(address, 16).ok()?; - if let Some((name, location)) = line.split_once(' ') { - let name = name.to_string(); - let location = Some(location.to_string()); - Some( DebugSymbol { address, name, location } ) - } else { - let name = line.to_string(); - Some( DebugSymbol { address, name, location: None } ) - } + let (address, line) = line.split_once(' ')?; + let address = u16::from_str_radix(address, 16).ok()?; + if let Some((name, location)) = line.split_once(' ') { + let name = name.to_string(); + let location = Some(location.to_string()); + Some( DebugSymbol { address, name, location } ) } else { - None + let name = line.to_string(); + Some( DebugSymbol { address, name, location: None } ) } } } diff --git a/src/devices.rs b/src/devices.rs deleted file mode 100644 index 2221152..0000000 --- a/src/devices.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod system_device; -mod memory_device; -mod math_device; -mod clock_device; -mod input_device; -mod screen_device; -mod local_device; -mod remote_device; -mod file_device; - -pub use system_device::SystemDevice; -pub use memory_device::MemoryDevice; -pub use math_device::MathDevice; -pub use clock_device::ClockDevice; -pub use input_device::InputDevice; -pub use screen_device::ScreenDevice; -pub use local_device::LocalDevice; -pub use remote_device::RemoteDevice; -pub use file_device::FileDevice; - diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs index 7cc877e..d06a92c 100644 --- a/src/devices/clock_device.rs +++ b/src/devices/clock_device.rs @@ -1,113 +1,18 @@ -use bedrock_core::*; +use crate::*; use chrono::prelude::*; -use std::time::{Duration, Instant}; - - -macro_rules! fn_read_timer { - ($fn_name:ident($read:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$end > uptime { - self.$read = (self.$end.saturating_sub(uptime)) as u16; - } else { - if self.$end > 0 { - self.$end = 0; - self.wake = true; - } - self.$read = 0; - } - } - }; -} - -macro_rules! fn_set_timer { - ($fn_name:ident($write:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$write > 0 { - self.$end = uptime.saturating_add(self.$write as u32); - } else { - self.$end = 0; - } - } - }; -} pub struct ClockDevice { - pub wake: bool, - - pub start: Instant, + pub epoch: Instant, pub uptime_read: u16, - pub t1_end: u32, - pub t2_end: u32, - pub t3_end: u32, - pub t4_end: u32, - pub t1_read: u16, - pub t2_read: u16, - pub t3_read: u16, - pub t4_read: u16, - pub t1_write: u16, - pub t2_write: u16, - pub t3_write: u16, - pub t4_write: u16, + pub t1: CountdownTimer, + pub t2: CountdownTimer, + pub t3: CountdownTimer, + pub t4: CountdownTimer, } -impl ClockDevice { - pub fn new() -> Self { - Self { - start: Instant::now(), - uptime_read: 0, - wake: false, - - t1_end: 0, - t2_end: 0, - t3_end: 0, - t4_end: 0, - t1_read: 0, - t2_read: 0, - t3_read: 0, - t4_read: 0, - t1_write: 0, - t2_write: 0, - t3_write: 0, - t4_write: 0, - } - } - - pub fn uptime(&self) -> u32 { - (self.start.elapsed().as_millis() / 4) as u32 - } - - pub fn read_uptime(&mut self) { - self.uptime_read = self.uptime() as u16; - } - - fn_read_timer!{ read_t1(t1_read, t1_end) } - fn_read_timer!{ read_t2(t2_read, t2_end) } - fn_read_timer!{ read_t3(t3_read, t3_end) } - fn_read_timer!{ read_t4(t4_read, t4_end) } - - fn_set_timer!{ set_t1(t1_write, t1_end) } - fn_set_timer!{ set_t2(t2_write, t2_end) } - fn_set_timer!{ set_t3(t3_write, t3_end) } - fn_set_timer!{ set_t4(t4_write, t4_end) } - - - pub fn time_remaining(&mut self) -> Option<Duration> { - use std::cmp::max; - let uptime = self.uptime(); - - let end = max(self.t1_end, max(self.t2_end, max(self.t3_end, self.t4_end))); - let remaining = end.saturating_sub(uptime); - match remaining > 0 { - true => Some(Duration::from_millis(remaining as u64) * 4), - false => None, - } - } -} impl Device for ClockDevice { fn read(&mut self, port: u8) -> u8 { @@ -118,16 +23,17 @@ impl Device for ClockDevice { 0x3 => Local::now().hour() as u8, 0x4 => Local::now().minute() as u8, 0x5 => Local::now().second() as u8, - 0x6 => { self.read_uptime(); read_h!(self.uptime_read) }, - 0x7 => read_l!(self.uptime_read), - 0x8 => { self.read_t1(); read_h!(self.t1_read) }, - 0x9 => read_l!(self.t1_read), - 0xa => { self.read_t2(); read_h!(self.t2_read) }, - 0xb => read_l!(self.t2_read), - 0xc => { self.read_t3(); read_h!(self.t3_read) }, - 0xd => read_l!(self.t3_read), - 0xe => { self.read_t4(); read_h!(self.t4_read) }, - 0xf => read_l!(self.t4_read), + 0x6 => { self.uptime_read = self.uptime() as u16; + read_h!(self.uptime_read) }, + 0x7 => read_l!(self.uptime_read), + 0x8 => { self.t1.update(); read_h!(self.t1.read) }, + 0x9 => read_l!(self.t1.read), + 0xA => { self.t2.update(); read_h!(self.t2.read) }, + 0xB => read_l!(self.t2.read), + 0xC => { self.t3.update(); read_h!(self.t3.read) }, + 0xD => read_l!(self.t3.read), + 0xE => { self.t4.update(); read_h!(self.t4.read) }, + 0xF => read_l!(self.t4.read), _ => unreachable!(), } } @@ -142,33 +48,114 @@ impl Device for ClockDevice { 0x5 => (), 0x6 => (), 0x7 => (), - 0x8 => write_h!(self.t1_write, value), - 0x9 => { write_l!(self.t1_write, value); self.set_t1() }, - 0xa => write_h!(self.t2_write, value), - 0xb => { write_l!(self.t2_write, value); self.set_t2() }, - 0xc => write_h!(self.t3_write, value), - 0xd => { write_l!(self.t3_write, value); self.set_t3() }, - 0xe => write_h!(self.t4_write, value), - 0xf => { write_l!(self.t4_write, value); self.set_t4() }, + 0x8 => write_h!(self.t1.write, value), + 0x9 => { write_l!(self.t1.write, value); self.t1.commit() }, + 0xA => write_h!(self.t2.write, value), + 0xB => { write_l!(self.t2.write, value); self.t2.commit() }, + 0xC => write_h!(self.t3.write, value), + 0xD => { write_l!(self.t3.write, value); self.t3.commit() }, + 0xE => write_h!(self.t4.write, value), + 0xF => { write_l!(self.t4.write, value); self.t4.commit() }, _ => unreachable!(), }; return None; } fn wake(&mut self) -> bool { - let uptime = self.uptime(); - macro_rules! check_timer { - ($end:ident) => { - if self.$end > 0 && self.$end <= uptime { - self.$end = 0; - self.wake = true; - } - }; + let t1 = self.t1.wake(); + let t2 = self.t2.wake(); + let t3 = self.t3.wake(); + let t4 = self.t4.wake(); + return t1 | t2 | t3 | t4; + } + + fn reset(&mut self) { + self.epoch = Instant::now(); + self.uptime_read = 0; + self.t1.reset(); + self.t2.reset(); + self.t3.reset(); + self.t4.reset(); + } +} + + +impl ClockDevice { + pub fn new() -> Self { + Self { + epoch: Instant::now(), + uptime_read: 0, + + t1: CountdownTimer::new(), + t2: CountdownTimer::new(), + t3: CountdownTimer::new(), + t4: CountdownTimer::new(), + } + } + + pub fn uptime(&self) -> u64 { + (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64 + } +} + + + +pub struct CountdownTimer { + pub end: Option<Instant>, + pub read: u16, + pub write: u16, + pub wake: bool, +} + +impl CountdownTimer { + pub fn new() -> Self { + Self { + end: None, + read: 0, + write: 0, + wake: false, + } + } + + pub fn reset(&mut self) { + self.end = None; + self.read = 0; + self.write = 0; + self.wake = false; + } + + pub fn wake(&mut self) -> bool { + if let Some(end) = self.end { + if end <= Instant::now() { + self.end = None; + self.wake = true; + } + } + std::mem::take(&mut self.wake) + } + + pub fn update(&mut self) { + if let Some(end) = self.end { + let now = Instant::now(); + if end > now { + let nanos = (end - now).as_nanos(); + self.read = (nanos * 256 / 1_000_000_000) as u16; + } else { + self.read = 0; + self.end = None; + self.wake = true; + } + } else { + self.read = 0; + } + } + + pub fn commit(&mut self) { + if self.write > 0 { + let nanos = (self.write as u64) * 1_000_000_000 / 256; + self.end = Some(Instant::now() + Duration::from_nanos(nanos)); + } else { + self.end = None; } - check_timer!(t1_end); - check_timer!(t2_end); - check_timer!(t3_end); - check_timer!(t4_end); - return std::mem::take(&mut self.wake); } } diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs index 61966b1..83f0a56 100644 --- a/src/devices/file_device.rs +++ b/src/devices/file_device.rs @@ -1,20 +1,4 @@ -mod bedrock_file_path; -mod bedrock_path_buffer; -mod buffered_file; -mod directory_listing; -mod entry; -mod operations; - -use buffered_file::BufferedFile; -use bedrock_file_path::BedrockFilePath; -use bedrock_path_buffer::BedrockPathBuffer; -use directory_listing::DirectoryListing; -use entry::{Entry, EntryType}; -use operations::{create_file, move_entry, delete_entry}; - -use bedrock_core::*; - -use std::path::{Component, Path, PathBuf}; +use crate::*; pub struct FileDevice { @@ -25,13 +9,14 @@ pub struct FileDevice { pub action_buffer: BedrockPathBuffer, pub path_buffer: BedrockPathBuffer, - pub entry: Option<(Entry, BedrockFilePath)>, - pub cached_dir: Option<(Entry, BedrockFilePath)>, + pub entry: Option<(Entry, BedrockFilePath, Instant)>, + pub cached_dir: Option<(Entry, BedrockFilePath, Instant)>, - pub success: bool, + pub error: bool, pub pointer_write: u32, pub length_write: u32, + pub enable: bool, pub enable_read: bool, pub enable_write: bool, pub enable_create: bool, @@ -39,21 +24,95 @@ pub struct FileDevice { pub enable_delete: bool, } + +impl Device for FileDevice { + fn read(&mut self, port: u8) -> u8 { + if !self.enable { return 0x00; } + match port { + 0x0 => read_b!(self.entry.is_some()), + 0x1 => read_b!(std::mem::take(&mut self.error)), + 0x2 => self.read_byte(), + 0x3 => self.read_byte(), + 0x4 => self.path_buffer.read(), + 0x5 => read_b!(self.entry_type()), + 0x6 => self.read_child_path(), + 0x7 => read_b!(self.child_type()), + 0x8 => read_hh!(self.pointer()), + 0x9 => read_hl!(self.pointer()), + 0xA => read_lh!(self.pointer()), + 0xB => read_ll!(self.pointer()), + 0xC => read_hh!(self.length()), + 0xD => read_hl!(self.length()), + 0xE => read_lh!(self.length()), + 0xF => read_ll!(self.length()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + if !self.enable { return None; } + match port { + 0x0 => self.write_to_entry_port(value), + 0x1 => self.write_to_action_port(value), + 0x2 => self.write_byte(value), + 0x3 => self.write_byte(value), + 0x4 => self.path_buffer.set_pointer(value), + 0x5 => self.ascend_to_parent(), + 0x6 => self.set_child_path(value), + 0x7 => self.descend_to_child(), + 0x8 => write_hh!(self.pointer_write, value), + 0x9 => write_hl!(self.pointer_write, value), + 0xA => write_lh!(self.pointer_write, value), + 0xB => {write_ll!(self.pointer_write, value); self.commit_pointer()}, + 0xC => write_hh!(self.length_write, value), + 0xD => write_hl!(self.length_write, value), + 0xE => write_lh!(self.length_write, value), + 0xF => {write_ll!(self.length_write, value); self.commit_length()}, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + todo!() + } +} + + impl FileDevice { - pub fn new() -> Self { + pub fn new(config: &EmulatorConfig) -> Self { #[cfg(target_family = "unix")] let default_base: PathBuf = PathBuf::from("/"); #[cfg(target_family = "windows")] let default_base: PathBuf = PathBuf::from(""); + let current_dir = match std::env::current_dir() { + Ok(dir) => PathBuf::from(dir), + Err(_) => PathBuf::from(""), + }; + + let (enable, base_path, default_path) = if config.trust_files { + (true, default_base, current_dir) + } else if let Some(config_dir) = dirs_next::config_dir() { + let bedrock_dir = config_dir.join("bedrock"); + let identifier = config.identifier.clone().unwrap_or("default".to_string()); + let sandbox_dir = bedrock_dir.join(identifier); + vagabond::make_directory(&sandbox_dir).unwrap(); + (true, sandbox_dir.clone(), sandbox_dir) + } else { + error!("Could not determine sandbox path for file device"); + (false, default_base, current_dir) + }; + // TODO: I'm not at all confident that the default path is correct // when not being set as the current directory. Self { - base_path: default_base, - default_path: match std::env::current_dir() { - Ok(dir) => PathBuf::from(dir), - Err(_) => PathBuf::from(""), - }, + base_path, + default_path, entry_buffer: BedrockPathBuffer::new(), action_buffer: BedrockPathBuffer::new(), @@ -62,10 +121,11 @@ impl FileDevice { entry: None, cached_dir: None, - success: false, + error: false, pointer_write: 0, length_write: 0, + enable, enable_read: true, enable_write: true, enable_create: true, @@ -74,6 +134,12 @@ impl FileDevice { } } + pub fn check_success(&mut self, success: bool) { + if !success { + self.error = true; + } + } + /// Safely close the current entry, cleaning up entry variables. pub fn close(&mut self) { self.entry_buffer.clear(); @@ -81,10 +147,10 @@ impl FileDevice { self.path_buffer.clear(); self.flush(); - if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) { + if let Some((Entry::Directory(mut dir), path, time)) = std::mem::take(&mut self.entry) { // Prevent the selected child from persisting when loading from cache. dir.deselect_child(); - self.cached_dir = Some((Entry::Directory(dir), path)); + self.cached_dir = Some((Entry::Directory(dir), path, time)); } } @@ -100,17 +166,17 @@ impl FileDevice { if let Ok(file) = open_result { self.close(); self.path_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::File(BufferedFile::new(file)), path)); + self.entry = Some((Entry::File(BufferedFile::new(file)), path, Instant::now())); return Ok(()); }; } Some(EntryType::Directory) => { - // Attempt to use the cached directory. - if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) { - if cached_path == path { + // Attempt to use the cached directory if not too old. + if let Some((dir, cached_path, time)) = std::mem::take(&mut self.cached_dir) { + if cached_path == path && time.elapsed() < Duration::from_secs(1) { self.close(); self.path_buffer.populate(cached_path.as_buffer()); - self.entry = Some((dir, cached_path)); + self.entry = Some((dir, cached_path, time)); return Ok(()); } } @@ -118,7 +184,7 @@ impl FileDevice { if let Some(listing) = DirectoryListing::from_path(&path) { self.close(); self.path_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::Directory(listing), path)); + self.entry = Some((Entry::Directory(listing), path, Instant::now())); return Ok(()); }; } @@ -132,10 +198,16 @@ impl FileDevice { pub fn write_to_entry_port(&mut self, byte: u8) { if let Some(buffer) = self.entry_buffer.write(byte) { self.close(); - match BedrockFilePath::from_buffer(buffer, &self.base_path) { - Some(path) => self.success = self.open(path).is_ok(), - None => self.success = false, - }; + // Attempt to open file if buffer was not empty. + if buffer[0] != 0 { + let success = match BedrockFilePath::from_buffer(buffer, &self.base_path) { + Some(path) => self.open(path).is_ok(), + None => false, + }; + self.check_success(success); + } else { + self.check_success(true); + } } } @@ -144,22 +216,23 @@ impl FileDevice { if let Some(buffer) = self.action_buffer.write(byte) { let destination_blank = buffer[0] == 0x00; let destination = BedrockFilePath::from_buffer(buffer, &self.base_path); - self.success = false; - if let Some((_, source)) = &self.entry { + if let Some((_, source, _)) = &self.entry { if destination_blank { if self.enable_delete { - self.success = delete_entry(&source.as_path()); + self.check_success(delete_entry(&source.as_path())); } } else if let Some(dest) = destination { if self.enable_move { - self.success = move_entry(&source.as_path(), &dest.as_path()); + self.check_success(move_entry(&source.as_path(), &dest.as_path())); } } } else if let Some(dest) = destination { if self.enable_create { - self.success = create_file(&dest.as_path()); + self.check_success(create_file(&dest.as_path())); } + } else { + self.check_success(false); } self.close(); } @@ -167,35 +240,38 @@ impl FileDevice { /// Attempt to open the parent directory of the current entry. pub fn ascend_to_parent(&mut self) { - if let Some((_, path)) = &self.entry { - match path.parent() { - Some(parent) => self.success = self.open(parent).is_ok(), - None => self.success = false, + if let Some((_, path, _)) = &self.entry { + let success = match path.parent() { + Some(parent) => self.open(parent).is_ok(), + None => false, }; + self.check_success(success); } else { - match BedrockFilePath::from_path(&self.default_path, &self.base_path) { - Some(default) => self.success = self.open(default).is_ok(), - None => self.success = false, + let success = match BedrockFilePath::from_path(&self.default_path, &self.base_path) { + Some(default) => self.open(default).is_ok(), + None => false, }; + self.check_success(success); } } /// Attempt to open the selected child of the current directory. pub fn descend_to_child(&mut self) { - if let Some((Entry::Directory(dir), _)) = &self.entry { - match dir.child_path() { - Some(child) => self.success = self.open(child).is_ok(), - None => self.success = false, + if let Some((Entry::Directory(dir), _, _)) = &self.entry { + let success = match dir.child_path() { + Some(child) => self.open(child).is_ok(), + None => false, }; + self.check_success(success); } else { - self.success = false; + self.check_success(false); } } /// Return true if the current entry is a directory. pub fn entry_type(&self) -> bool { match self.entry { - Some((Entry::Directory(_), _)) => true, + Some((Entry::Directory(_), _, _)) => true, _ => false, } } @@ -203,13 +279,13 @@ impl FileDevice { /// Read a byte from the path buffer of the selected child. pub fn read_child_path(&mut self) -> u8 { match &mut self.entry { - Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(), + Some((Entry::Directory(dir), _, _)) => dir.child_path_buffer().read(), _ => 0, } } pub fn set_child_path(&mut self, byte: u8) { - if let Some((Entry::Directory(dir), _)) = &mut self.entry { + if let Some((Entry::Directory(dir), _, _)) = &mut self.entry { dir.child_path_buffer().set_pointer(byte); } } @@ -217,7 +293,7 @@ impl FileDevice { /// Return true if the selected child is a directory. pub fn child_type(&self) -> bool { match &self.entry { - Some((Entry::Directory(dir), _)) => match dir.child_type() { + Some((Entry::Directory(dir), _, _)) => match dir.child_type() { Some(EntryType::Directory) => true, _ => false, } @@ -228,7 +304,7 @@ impl FileDevice { /// Read a byte from the current file. pub fn read_byte(&mut self) -> u8 { match &mut self.entry { - Some((Entry::File(file), _)) => file.read(), + Some((Entry::File(file), _, _)) => file.read(), _ => 0, } } @@ -236,102 +312,100 @@ impl FileDevice { /// Writes a byte to the currently-open file. pub fn write_byte(&mut self, byte: u8) { match &mut self.entry { - Some((Entry::File(file), _)) => file.write(byte), + Some((Entry::File(file), _, _)) => file.write(byte), _ => (), } } pub fn pointer(&mut self) -> u32 { match &mut self.entry { - Some((Entry::File(file), _)) => file.pointer(), - Some((Entry::Directory(dir), _)) => dir.selected(), + Some((Entry::File(file), _, _)) => file.pointer(), + Some((Entry::Directory(dir), _, _)) => dir.selected(), _ => 0, } } pub fn commit_pointer(&mut self) { match &mut self.entry { - Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write), - Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write), + Some((Entry::File(file), _, _)) => file.set_pointer(self.pointer_write), + Some((Entry::Directory(dir), _, _)) => dir.set_selected(self.pointer_write), _ => (), } } pub fn length(&mut self) -> u32 { match &mut self.entry { - Some((Entry::File(file), _)) => file.length(), - Some((Entry::Directory(dir), _)) => dir.length(), + Some((Entry::File(file), _, _)) => file.length(), + Some((Entry::Directory(dir), _, _)) => dir.length(), _ => 0, } } pub fn commit_length(&mut self) { match &mut self.entry { - Some((Entry::File(file), _)) => file.set_length(self.length_write), + Some((Entry::File(file), _, _)) => file.set_length(self.length_write), _ => (), } } pub fn flush(&mut self) { - if let Some((Entry::File(buffered_file), _)) = &mut self.entry { + if let Some((Entry::File(buffered_file), _, _)) = &mut self.entry { let _ = buffered_file; } } } + impl Drop for FileDevice { fn drop(&mut self) { self.flush(); } } -impl Device for FileDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_b!(self.entry.is_some()), - 0x1 => read_b!(self.success), - 0x2 => self.path_buffer.read(), - 0x3 => read_b!(self.entry_type()), - 0x4 => self.read_byte(), - 0x5 => self.read_byte(), - 0x6 => self.read_child_path(), - 0x7 => read_b!(self.child_type()), - 0x8 => read_hh!(self.pointer()), - 0x9 => read_hl!(self.pointer()), - 0xa => read_lh!(self.pointer()), - 0xb => read_ll!(self.pointer()), - 0xc => read_hh!(self.length()), - 0xd => read_hl!(self.length()), - 0xe => read_lh!(self.length()), - 0xf => read_ll!(self.length()), - _ => unreachable!(), + +/// Create a new file if it doesn't already exist, returning true if successful. +pub fn create_file(destination: &Path) -> bool { + if entry_exists(destination) { + false + } else { + if let Some(parent_path) = destination.parent() { + let _ = std::fs::create_dir_all(parent_path); } + std::fs::OpenOptions::new().write(true).create_new(true) + .open(destination).is_ok() } +} - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => self.write_to_entry_port(value), - 0x1 => self.write_to_action_port(value), - 0x2 => self.path_buffer.set_pointer(value), - 0x3 => self.ascend_to_parent(), - 0x4 => self.write_byte(value), - 0x5 => self.write_byte(value), - 0x6 => self.set_child_path(value), - 0x7 => self.descend_to_child(), - 0x8 => write_hh!(self.pointer_write, value), - 0x9 => write_hl!(self.pointer_write, value), - 0xa => write_lh!(self.pointer_write, value), - 0xb => {write_ll!(self.pointer_write, value); self.commit_pointer()}, - 0xc => write_hh!(self.length_write, value), - 0xd => write_hl!(self.length_write, value), - 0xe => write_lh!(self.length_write, value), - 0xf => {write_ll!(self.length_write, value); self.commit_length()}, - _ => unreachable!(), - }; - return None; +/// Move an entry from one location to another, returning true if successful. +pub fn move_entry(source: &Path, destination: &Path) -> bool { + if !entry_exists(source) || entry_exists(destination) { + return false; } + std::fs::rename(source, destination).is_ok() +} - fn wake(&mut self) -> bool { - false +/// Delete an entry, returning true if successful. +pub fn delete_entry(source: &Path) -> bool { + use std::fs::{remove_file, remove_dir_all}; + use std::io::ErrorKind; + + match remove_file(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + ErrorKind::IsADirectory => match remove_dir_all(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + _ => false, + } + } + _ => false, + } } } + +/// Returns true if an entry already exists at the given path. +fn entry_exists(source: &Path) -> bool { + std::fs::metadata(source).is_ok() +} diff --git a/src/devices/file_device/operations.rs b/src/devices/file_device/operations.rs deleted file mode 100644 index 3a3f81b..0000000 --- a/src/devices/file_device/operations.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io::ErrorKind; -use std::path::Path; - - -/// Create a new file if it doesn't already exist, returning true if successful. -pub fn create_file(destination: &Path) -> bool { - if entry_exists(destination) { - false - } else { - if let Some(parent_path) = destination.parent() { - let _ = std::fs::create_dir_all(parent_path); - } - std::fs::OpenOptions::new().write(true).create_new(true) - .open(destination).is_ok() - } -} - -/// Move an entry from one location to another, returning true if successful. -pub fn move_entry(source: &Path, destination: &Path) -> bool { - if !entry_exists(source) || entry_exists(destination) { - return false; - } - std::fs::rename(source, destination).is_ok() -} - -/// Delete an entry, returning true if successful. -pub fn delete_entry(source: &Path) -> bool { - match std::fs::remove_file(source) { - Ok(_) => true, - Err(e) => match e.kind() { - ErrorKind::NotFound => true, - ErrorKind::IsADirectory => match std::fs::remove_dir_all(source) { - Ok(_) => true, - Err(e) => match e.kind() { - ErrorKind::NotFound => true, - _ => false, - } - } - _ => false, - } - } -} - -/// Returns true if an entry already exists at the given path. -fn entry_exists(source: &Path) -> bool { - std::fs::metadata(source).is_ok() -} diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs index 9b7038c..3ebeb4c 100644 --- a/src/devices/input_device.rs +++ b/src/devices/input_device.rs @@ -1,84 +1,149 @@ use crate::*; -use bedrock_core::*; -use phosphor::*; use std::collections::VecDeque; -macro_rules! fn_on_scroll { - ($fn_name:ident($value:ident, $delta:ident)) => { - pub fn $fn_name(&mut self, delta: f32) { - self.$delta += delta; - while self.$delta >= 1.0 { - self.$value = self.$value.saturating_add(1); - self.$delta -= 1.0; - self.wake = true; - } - while self.$delta <= -1.0 { - self.$value = self.$value.saturating_sub(1); - self.$delta += 1.0; - self.wake = true; - } - } - }; -} - pub struct InputDevice { - pub wake: bool, - pub accessed: bool, - - pub pointer_active: bool, - pub pointer_buttons: u8, - pub position: ScreenPosition, + pub cursor: ScreenPosition, pub x_read: u16, pub y_read: u16, + pub h_scroll_read: i16, + pub v_scroll_read: i16, + pub h_scroll: f32, + pub v_scroll: f32, + pub pointer_buttons: u8, + pub pointer_active: bool, - pub h_scroll: i8, - pub v_scroll: i8, - pub h_scroll_delta: f32, - pub v_scroll_delta: f32, - - pub keyboard_active: bool, - pub characters: VecDeque<u8>, pub navigation: u8, pub modifiers: u8, + pub characters: VecDeque<u8>, + pub gamepad_1: OwnedGamepad, + pub gamepad_2: OwnedGamepad, + pub gamepad_3: OwnedGamepad, + pub gamepad_4: OwnedGamepad, + + pub accessed: bool, + pub wake: bool, +} + + +impl Device for InputDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => { self.x_read = self.cursor.x; read_h!(self.x_read) }, + 0x1 => read_l!(self.cursor.x), + 0x2 => { self.y_read = self.cursor.y; read_h!(self.y_read) }, + 0x3 => read_l!(self.cursor.y), + 0x4 => { self.update_horizontal_scroll(); read_h!(self.h_scroll_read) }, + 0x5 => read_l!(self.h_scroll_read), + 0x6 => { self.update_vertical_scroll(); read_h!(self.v_scroll_read) }, + 0x7 => read_l!(self.v_scroll_read), + 0x8 => read_b!(self.pointer_active), + 0x9 => self.pointer_buttons, + 0xA => self.characters.pop_front().unwrap_or(0), + 0xB => self.modifiers, + 0xC => self.gamepad_1.state() | self.navigation, + 0xD => self.gamepad_2.state(), + 0xE => self.gamepad_3.state(), + 0xF => self.gamepad_4.state(), + _ => unreachable!(), + } + } - pub gamepad_1: u8, - pub gamepad_2: u8, - pub gamepad_3: u8, - pub gamepad_4: u8, + fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { + let signal = if self.accessed { None } else { Some(Signal::Break) }; + self.accessed = true; + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 0x5 => (), + 0x6 => (), + 0x7 => (), + 0x8 => (), + 0x9 => (), + 0xA => self.characters.clear(), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), + _ => unreachable!(), + }; + return signal; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } + + fn reset(&mut self) { + self.cursor = ScreenPosition::ZERO; + self.x_read = 0; + self.y_read = 0; + self.h_scroll_read = 0; + self.v_scroll_read = 0; + self.h_scroll = 0.0; + self.v_scroll = 0.0; + self.pointer_active = false; + self.pointer_buttons = 0; + + self.navigation = 0; + self.modifiers = 0; + self.characters.clear(); + self.gamepad_1.reset(); + self.gamepad_2.reset(); + self.gamepad_3.reset(); + self.gamepad_4.reset(); + + self.accessed = false; + self.wake = false; + } } + impl InputDevice { pub fn new() -> Self { Self { - wake: false, - accessed: false, - - pointer_active: false, - pointer_buttons: 0, - - position: ScreenPosition::ZERO, + cursor: ScreenPosition::ZERO, x_read: 0, y_read: 0, + h_scroll_read: 0, + v_scroll_read: 0, + h_scroll: 0.0, + v_scroll: 0.0, + pointer_active: false, + pointer_buttons: 0, - h_scroll: 0, - v_scroll: 0, - h_scroll_delta: 0.0, - v_scroll_delta: 0.0, - - keyboard_active: true, - characters: VecDeque::new(), - modifiers: 0, navigation: 0, + modifiers: 0, + characters: VecDeque::new(), + gamepad_1: OwnedGamepad::new(1), + gamepad_2: OwnedGamepad::new(2), + gamepad_3: OwnedGamepad::new(3), + gamepad_4: OwnedGamepad::new(4), - gamepad_1: 0, - gamepad_2: 0, - gamepad_3: 0, - gamepad_4: 0, + accessed: false, + wake: false, } } + #[cfg(feature = "gamepad")] + pub fn on_gamepad_event(&mut self, event: gilrs::Event) { + if let Some(g) = self.gamepad_1.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_2.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_3.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_4.register(event.id) { + self.wake |= g.process_event(&event); return; } + } + pub fn on_cursor_enter(&mut self) { self.pointer_active = true; self.wake = true; @@ -90,12 +155,13 @@ impl InputDevice { } pub fn on_cursor_move(&mut self, position: Position) { - let screen_position = ScreenPosition { + self.pointer_active = true; + let cursor_position = ScreenPosition { x: position.x as i16 as u16, y: position.y as i16 as u16, }; - if self.position != screen_position { - self.position = screen_position; + if self.cursor != cursor_position { + self.cursor = cursor_position; self.wake = true; } } @@ -103,8 +169,8 @@ impl InputDevice { pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) { let mask = match button { MouseButton::Left => 0x80, - MouseButton::Middle => 0x40, - MouseButton::Right => 0x20, + MouseButton::Right => 0x40, + MouseButton::Middle => 0x20, _ => return, }; let pointer_buttons = match action { @@ -117,15 +183,26 @@ impl InputDevice { } } - fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta)); - fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta)); + pub fn on_horizontal_scroll(&mut self, delta: f32) { + self.h_scroll += delta; + self.h_scroll = self.h_scroll.clamp(-32768.0, 32767.0); + if self.h_scroll.abs() >= 1.0 { self.wake = true; } + } - pub fn read_horizontal_scroll(&mut self) -> u8 { - std::mem::take(&mut self.h_scroll) as u8 + pub fn on_vertical_scroll(&mut self, delta: f32) { + self.v_scroll += delta; + self.v_scroll = self.v_scroll.clamp(i16::MIN as f32, i16::MAX as f32); + if self.v_scroll.abs() >= 1.0 { self.wake = true; } } - pub fn read_vertical_scroll(&mut self) -> u8 { - std::mem::take(&mut self.v_scroll) as u8 + pub fn update_horizontal_scroll(&mut self) { + self.h_scroll_read = self.h_scroll.trunc() as i16; + self.h_scroll -= self.h_scroll.trunc(); + } + + pub fn update_vertical_scroll(&mut self) { + self.v_scroll_read = self.v_scroll.trunc() as i16; + self.v_scroll -= self.v_scroll.trunc(); } pub fn on_character(&mut self, character: char) { @@ -178,57 +255,3 @@ impl InputDevice { } } } - -impl Device for InputDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_b!(self.pointer_active), - 0x1 => self.pointer_buttons, - 0x2 => self.read_horizontal_scroll(), - 0x3 => self.read_vertical_scroll(), - 0x4 => { self.x_read = self.position.x as u16; read_h!(self.x_read) }, - 0x5 => read_l!(self.position.x), - 0x6 => { self.y_read = self.position.y as u16; read_h!(self.y_read) }, - 0x7 => read_l!(self.position.y), - 0x8 => read_b!(self.keyboard_active), - 0x9 => self.characters.pop_front().unwrap_or(0), - 0xa => self.navigation, - 0xb => self.modifiers, - 0xc => self.gamepad_1, - 0xd => self.gamepad_2, - 0xe => self.gamepad_3, - 0xf => self.gamepad_4, - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { - self.accessed = true; - match port { - 0x0 => (), - 0x1 => (), - 0x2 => (), - 0x3 => (), - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => self.characters.clear(), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs index 015545e..e7043b9 100644 --- a/src/devices/math_device.rs +++ b/src/devices/math_device.rs @@ -1,59 +1,168 @@ -use bedrock_core::*; +use crate::*; +const ANGLE_SCALE: f64 = 10430.378350470453; // 65536 / 2Ï€ -pub struct MathDevice { - pub op1: u16, - pub op2: u16, - pub sqrt: Option<u16>, - pub atan: Option<u16>, +pub struct MathDevice { + pub x: u16, + pub y: u16, + pub r: u16, + pub t: u16, + pub x_read: Option<u16>, + pub y_read: Option<u16>, + pub r_read: Option<u16>, + pub t_read: Option<u16>, pub prod: Option<(u16, u16)>, // (low, high) pub quot: Option<u16>, pub rem: Option<u16>, } + +impl Device for MathDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_h!(self.x()), + 0x1 => read_l!(self.x()), + 0x2 => read_h!(self.y()), + 0x3 => read_l!(self.y()), + 0x4 => read_h!(self.r()), + 0x5 => read_l!(self.r()), + 0x6 => read_h!(self.t()), + 0x7 => read_l!(self.t()), + 0x8 => read_h!(self.prod().1), + 0x9 => read_l!(self.prod().1), + 0xA => read_h!(self.prod().0), + 0xB => read_l!(self.prod().0), + 0xC => read_h!(self.quot()), + 0xD => read_l!(self.quot()), + 0xE => read_h!(self.rem()), + 0xF => read_l!(self.rem()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => { write_h!(self.x, value); self.clear_polar(); }, + 0x1 => { write_l!(self.x, value); self.clear_polar(); }, + 0x2 => { write_h!(self.y, value); self.clear_polar(); }, + 0x3 => { write_l!(self.y, value); self.clear_polar(); }, + 0x4 => { write_h!(self.r, value); self.clear_cartesian(); }, + 0x5 => { write_l!(self.r, value); self.clear_cartesian(); }, + 0x6 => { write_h!(self.t, value); self.clear_cartesian(); }, + 0x7 => { write_l!(self.t, value); self.clear_cartesian(); }, + 0x8 => (), + 0x9 => (), + 0xA => (), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + self.x = 0; + self.y = 0; + self.r = 0; + self.t = 0; + self.clear_cartesian(); + self.clear_polar(); + } +} + + impl MathDevice { pub fn new() -> Self { Self { - op1: 0, - op2: 0, - - sqrt: None, - atan: None, + x: 0, + y: 0, + r: 0, + t: 0, + x_read: None, + y_read: None, + r_read: None, + t_read: None, prod: None, quot: None, rem: None, } } - pub fn clear(&mut self) { - self.sqrt = None; - self.atan = None; + pub fn clear_cartesian(&mut self) { + self.x_read = None; + self.y_read = None; + } + + pub fn clear_polar(&mut self) { + self.r_read = None; + self.t_read = None; self.prod = None; self.quot = None; - self.rem = None; + self.rem = None; } - pub fn atan(&mut self) -> u16 { - match self.atan { - Some(atan) => atan, + pub fn x(&mut self) -> u16 { + match self.x_read { + Some(x) => x, None => { - let x = self.op1 as i16 as f64; - let y = self.op2 as i16 as f64; - const SCALE: f64 = 10430.378350470453; // PI * 32768 - self.atan = Some((f64::atan2(x, y) * SCALE) as i16 as u16); - self.atan.unwrap() + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let x = angle.cos() * r; + self.x_read = match x > (i16::MIN as f64) && x < (i16::MAX as f64) { + true => Some(x as i16 as u16), + false => Some(0), + }; + self.x_read.unwrap() } } } - pub fn sqrt(&mut self) -> u16 { - match self.sqrt { - Some(sqrt) => sqrt, + pub fn y(&mut self) -> u16 { + match self.y_read { + Some(y) => y, None => { - let input = ((self.op1 as u32) << 16) | (self.op2 as u32); - self.sqrt = Some((input as f64).sqrt() as u16); - self.sqrt.unwrap() + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let y = angle.sin() * r; + self.y_read = match y > (i16::MIN as f64) && y < (i16::MAX as f64) { + true => Some(y as i16 as u16), + false => Some(0), + }; + self.y_read.unwrap() + } + } + } + + pub fn r(&mut self) -> u16 { + match self.r_read { + Some(r) => r, + None => { + let sum = (self.x as f64).powi(2) + (self.y as f64).powi(2); + self.r_read = Some(sum.sqrt() as u16); + self.r_read.unwrap() + } + } + } + + pub fn t(&mut self) -> u16 { + match self.t_read { + Some(t) => t, + None => { + let x = self.x as i16 as f64; + let y = self.x as i16 as f64; + let angle = f64::atan2(y, x) * ANGLE_SCALE; + self.t_read = Some(angle as i16 as u16); + self.t_read.unwrap() } } } @@ -62,7 +171,7 @@ impl MathDevice { match self.prod { Some(prod) => prod, None => { - self.prod = Some(self.op1.widening_mul(self.op2)); + self.prod = Some(self.x.widening_mul(self.y)); self.prod.unwrap() } } @@ -72,7 +181,7 @@ impl MathDevice { match self.quot { Some(quot) => quot, None => { - self.quot = Some(self.op1.checked_div(self.op2).unwrap_or(0)); + self.quot = Some(self.x.checked_div(self.y).unwrap_or(0)); self.quot.unwrap() } } @@ -82,60 +191,9 @@ impl MathDevice { match self.rem { Some(rem) => rem, None => { - self.rem = Some(self.op1.checked_rem(self.op2).unwrap_or(0)); + self.rem = Some(self.x.checked_rem(self.y).unwrap_or(0)); self.rem.unwrap() } } } } - -impl Device for MathDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_h!(self.op1), - 0x1 => read_l!(self.op1), - 0x2 => read_h!(self.op2), - 0x3 => read_l!(self.op2), - 0x4 => read_h!(self.sqrt()), - 0x5 => read_l!(self.sqrt()), - 0x6 => read_h!(self.atan()), - 0x7 => read_l!(self.atan()), - 0x8 => read_h!(self.prod().1), - 0x9 => read_l!(self.prod().1), - 0xa => read_h!(self.prod().0), - 0xb => read_l!(self.prod().0), - 0xc => read_h!(self.quot()), - 0xd => read_l!(self.quot()), - 0xe => read_h!(self.rem()), - 0xf => read_l!(self.rem()), - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => { write_h!(self.op1, value); self.clear(); }, - 0x1 => { write_l!(self.op1, value); self.clear(); }, - 0x2 => { write_h!(self.op2, value); self.clear(); }, - 0x3 => { write_l!(self.op2, value); self.clear(); }, - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => (), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs index 0128d55..d116ca7 100644 --- a/src/devices/memory_device.rs +++ b/src/devices/memory_device.rs @@ -1,93 +1,151 @@ -use bedrock_core::*; +use crate::*; -type Page = [u8; 256]; +use std::cmp::min; -macro_rules! fn_read_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self) -> u8 { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get(page_i) { - Some(page) => page[byte_i], - None => 0, - } - } - }; -} -macro_rules! fn_write_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self, byte: u8) { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get_mut(page_i) { - Some(page) => page[byte_i] = byte, - None => if page_i < self.provisioned { - self.pages.resize(page_i + 1, [0; 256]); - self.pages[page_i][byte_i] = byte; - } - } - } - }; -} +type Page = [u8; 256]; pub struct MemoryDevice { - pub limit: u16, // maximum provisionable number of pages - pub requested: u16, // number of pages requested by program - pub provisioned: usize, // number of pages provisioned for use + pub limit: u16, // maximum allocateable number of pages pub pages: Vec<Page>, // all allocated pages + pub count_write: u16, // number of pages requested by program + pub count: usize, // number of pages allocated for use + pub copy_write: u16, + pub head_1: HeadAddress, + pub head_2: HeadAddress, +} - pub offset_1: u16, - pub address_1: u16, - pub offset_2: u16, - pub address_2: u16, - pub copy_length: u16, +impl Device for MemoryDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_h!(self.count), + 0x1 => read_l!(self.count), + 0x2 => read_h!(self.head_1.page), + 0x3 => read_l!(self.head_1.page), + 0x4 => read_h!(self.head_1.address), + 0x5 => read_l!(self.head_1.address), + 0x6 => self.read_head_1(), + 0x7 => self.read_head_1(), + 0x8 => 0x00, + 0x9 => 0x00, + 0xA => read_h!(self.head_2.page), + 0xB => read_l!(self.head_2.page), + 0xC => read_h!(self.head_2.address), + 0xD => read_l!(self.head_2.address), + 0xE => self.read_head_2(), + 0xF => self.read_head_2(), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => write_h!(self.count_write, value), + 0x1 => { write_l!(self.count_write, value); self.allocate(); }, + 0x2 => write_h!(self.head_1.page, value), + 0x3 => write_l!(self.head_1.page, value), + 0x4 => write_h!(self.head_1.address, value), + 0x5 => write_l!(self.head_1.address, value), + 0x6 => self.write_head_1(value), + 0x7 => self.write_head_1(value), + 0x8 => write_h!(self.copy_write, value), + 0x9 => { write_l!(self.copy_write, value); self.copy(); }, + 0xA => write_h!(self.head_2.page, value), + 0xB => write_l!(self.head_2.page, value), + 0xC => write_h!(self.head_2.address, value), + 0xD => write_l!(self.head_2.address, value), + 0xE => self.write_head_2(value), + 0xF => self.write_head_2(value), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + self.pages.clear(); + self.count_write = 0; + self.count = 0; + self.copy_write = 0; + self.head_1.reset(); + self.head_2.reset(); + } } + impl MemoryDevice { pub fn new() -> Self { Self { limit: u16::MAX, - requested: 0, - provisioned: 0, pages: Vec::new(), + count_write: 0, + count: 0, + copy_write: 0, + head_1: HeadAddress::new(), + head_2: HeadAddress::new(), + } + } - offset_1: 0, - address_1: 0, - offset_2: 0, - address_2: 0, + pub fn read_head_1(&mut self) -> u8 { + let (page_i, byte_i) = self.head_1.get_indices(); + self.read_byte(page_i, byte_i) + } - copy_length: 0, + pub fn read_head_2(&mut self) -> u8 { + let (page_i, byte_i) = self.head_2.get_indices(); + self.read_byte(page_i, byte_i) + } + + fn read_byte(&self, page_i: usize, byte_i: usize) -> u8 { + match self.pages.get(page_i) { + Some(page) => page[byte_i], + None => 0, } } - fn_read_head! { read_head_1( offset_1, address_1) } - fn_read_head! { read_head_2( offset_2, address_2) } - fn_write_head!{ write_head_1(offset_1, address_1) } - fn_write_head!{ write_head_2(offset_2, address_2) } + pub fn write_head_1(&mut self, value: u8) { + let (page_i, byte_i) = self.head_1.get_indices(); + self.write_byte(page_i, byte_i, value); + } + + pub fn write_head_2(&mut self, value: u8) { + let (page_i, byte_i) = self.head_2.get_indices(); + self.write_byte(page_i, byte_i, value); + } - pub fn provision(&mut self) { - self.provisioned = std::cmp::min(self.requested, self.limit) as usize; + fn write_byte(&mut self, page_i: usize, byte_i: usize, value: u8) { + match self.pages.get_mut(page_i) { + Some(page) => page[byte_i] = value, + None => if page_i < self.count { + self.pages.resize(page_i + 1, [0; 256]); + self.pages[page_i][byte_i] = value; + } + } + } + + pub fn allocate(&mut self) { + self.count = min(self.count_write, self.limit) as usize; // Defer allocation of new pages. - self.pages.truncate(self.provisioned as usize); + self.pages.truncate(self.count as usize); } pub fn copy(&mut self) { - let src = self.offset_2 as usize; - let dest = self.offset_1 as usize; - let count = self.copy_length as usize; + let src = self.head_2.page as usize; + let dest = self.head_1.page as usize; + let n = self.copy_write as usize; // Pre-allocate destination pages as needed. - let pages_needed = std::cmp::min(dest + count, self.provisioned); - if pages_needed > self.pages.len() { - self.pages.resize(pages_needed, [0; 256]); + let allocate = min(dest + n, self.count); + if allocate > self.pages.len() { + self.pages.resize(allocate, [0; 256]); } - for i in 0..count { + for i in 0..n { let src_page = match self.pages.get(src + i) { Some(src_page) => src_page.to_owned(), None => [0; 256], @@ -100,53 +158,29 @@ impl MemoryDevice { } } -impl Device for MemoryDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => self.read_head_1(), - 0x1 => self.read_head_1(), - 0x2 => read_h!(self.offset_1), - 0x3 => read_l!(self.offset_1), - 0x4 => read_h!(self.address_1), - 0x5 => read_l!(self.address_1), - 0x6 => read_h!(self.provisioned), - 0x7 => read_l!(self.provisioned), - 0x8 => self.read_head_2(), - 0x9 => self.read_head_2(), - 0xa => read_h!(self.offset_2), - 0xb => read_l!(self.offset_2), - 0xc => read_h!(self.address_2), - 0xd => read_l!(self.address_2), - 0xe => 0x00, - 0xf => 0x00, - _ => unreachable!(), + +pub struct HeadAddress { + pub page: u16, + pub address: u16, +} + +impl HeadAddress { + pub fn new() -> Self { + Self { + page: 0, + address: 0, } } - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => self.write_head_1(value), - 0x1 => self.write_head_1(value), - 0x2 => write_h!(self.offset_1, value), - 0x3 => write_l!(self.offset_1, value), - 0x4 => write_h!(self.address_1, value), - 0x5 => write_l!(self.address_1, value), - 0x6 => write_h!(self.requested, value), - 0x7 => { write_l!(self.requested, value); self.provision(); }, - 0x8 => self.write_head_2(value), - 0x9 => self.write_head_2(value), - 0xa => write_h!(self.offset_2, value), - 0xb => write_l!(self.offset_2, value), - 0xc => write_h!(self.address_2, value), - 0xd => write_l!(self.address_2, value), - 0xe => write_h!(self.copy_length, value), - 0xf => { write_l!(self.copy_length, value); self.copy(); }, - _ => unreachable!(), - }; - return None; + pub fn reset(&mut self) { + self.page = 0; + self.address = 0; } - fn wake(&mut self) -> bool { - false + pub fn get_indices(&mut self) -> (usize, usize) { + let page_i = (self.page + (self.address / 256)) as usize; + let byte_i = (self.address % 256) as usize; + self.address = self.address.wrapping_add(1); + (page_i, byte_i) } } diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..aa98a49 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,17 @@ +mod system_device; +mod memory_device; +mod math_device; +mod clock_device; +mod input_device; +mod screen_device; +mod stream_device; +mod file_device; + +pub use system_device::*; +pub use memory_device::*; +pub use math_device::*; +pub use clock_device::*; +pub use input_device::*; +pub use screen_device::*; +pub use stream_device::*; +pub use file_device::*; diff --git a/src/devices/remote_device.rs b/src/devices/remote_device.rs deleted file mode 100644 index f50ac7a..0000000 --- a/src/devices/remote_device.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bedrock_core::*; - - -pub struct RemoteDevice { - -} - -impl RemoteDevice { - pub fn new() -> Self { - Self { - - } - } -} - -impl Device for RemoteDevice { - fn read(&mut self, _port: u8) -> u8 { - todo!() - // match port { - // _ => unreachable!(), - // } - } - - fn write(&mut self, _port: u8, _value: u8) -> Option<Signal> { - todo!() - // match port { - // _ => unreachable!(), - // }; - // return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs index a10ab20..483bcca 100644 --- a/src/devices/screen_device.rs +++ b/src/devices/screen_device.rs @@ -1,21 +1,18 @@ use crate::*; -use bedrock_core::*; use geometry::*; use phosphor::*; -type Sprite = [[u8; 8]; 8]; + +pub type Sprite = [[u8; 8]; 8]; + #[derive(Clone, Copy)] pub enum Layer { Fg, Bg } pub struct ScreenDevice { - pub wake: bool, - pub accessed: bool, - /// Each byte represents a screen pixel, left-to-right and top-to-bottom. // Only the bottom four bits of each byte are used. - // TODO: Consider using the high bit of each pixel byte as a dirty bit. pub fg: Vec<u8>, pub bg: Vec<u8>, pub dirty: bool, @@ -32,18 +29,110 @@ pub struct ScreenDevice { pub palette_write: u16, pub palette: [Colour; 16], - pub colours: u16, + pub sprite_colours: u16, pub sprite: SpriteBuffer, + + pub accessed: bool, + pub wake: bool, +} + + +impl HasDimensions<u16> for ScreenDevice { + fn dimensions(&self) -> ScreenDimensions { + self.dimensions + } +} + + +impl Device for ScreenDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => read_h!(self.cursor.x), + 0x1 => read_l!(self.cursor.x), + 0x2 => read_h!(self.cursor.y), + 0x3 => read_l!(self.cursor.y), + 0x4 => read_h!(self.dimensions.width), + 0x5 => read_l!(self.dimensions.width), + 0x6 => read_h!(self.dimensions.height), + 0x7 => read_l!(self.dimensions.height), + 0x8 => 0, + 0x9 => 0, + 0xA => 0, + 0xB => 0, + 0xC => 0, + 0xD => 0, + 0xE => 0, + 0xF => 0, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + let signal = if self.accessed { None } else { Some(Signal::Break) }; + self.accessed = true; + match port { + 0x0 => write_h!(self.cursor.x, value), + 0x1 => write_l!(self.cursor.x, value), + 0x2 => write_h!(self.cursor.y, value), + 0x3 => write_l!(self.cursor.y, value), + 0x4 => write_h!(self.width_write, value), + 0x5 => { write_l!(self.width_write, value); self.resize_width() } + 0x6 => write_h!(self.height_write, value), + 0x7 => { write_l!(self.height_write, value); self.resize_height() } + 0x8 => write_h!(self.palette_write, value), + 0x9 => { write_l!(self.palette_write, value); self.set_palette() } + 0xA => write_h!(self.sprite_colours, value), + 0xB => write_l!(self.sprite_colours, value), + 0xC => self.sprite.push_byte(value), + 0xD => self.sprite.push_byte(value), + 0xE => self.draw_dispatch(value), + 0xF => self.move_cursor(value), + _ => unreachable!(), + }; + return signal; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } + + fn reset(&mut self) { + self.fg.clear(); + self.bg.clear(); + self.dirty = false; + + self.cursor = ScreenPosition::ZERO; + self.vector = ScreenPosition::ZERO; + + self.dirty_dimensions = true; + self.width_write = 0; + self.height_write = 0; + self.fixed_width = None; + self.fixed_height = None; + + self.palette_write = 0; + self.palette = [ + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + ]; + self.sprite_colours = 0; + self.sprite = SpriteBuffer::new(); + + self.accessed = false; + self.wake = false; + } } + impl ScreenDevice { pub fn new(config: &EmulatorConfig) -> Self { let area = config.dimensions.area_usize(); Self { - wake: false, - accessed: false, - fg: vec![0; area], bg: vec![0; area], dirty: false, @@ -60,12 +149,15 @@ impl ScreenDevice { palette_write: 0, palette: [Colour::BLACK; 16], - colours: 0, + sprite_colours: 0, sprite: SpriteBuffer::new(), + + accessed: false, + wake: false, } } - /// External resize. + /// Resize screen to match window dimensions. pub fn resize(&mut self, dimensions: phosphor::Dimensions) { // Replace dimensions with fixed dimensions. let screen_dimensions = ScreenDimensions { @@ -158,9 +250,9 @@ impl ScreenDevice { pub fn set_palette(&mut self) { let i = (self.palette_write >> 12 ) as usize; - let r = (self.palette_write >> 8 & 0xf) as u8 * 17; - let g = (self.palette_write >> 4 & 0xf) as u8 * 17; - let b = (self.palette_write & 0xf) as u8 * 17; + let r = (self.palette_write >> 8 & 0xF) as u8 * 17; + let g = (self.palette_write >> 4 & 0xF) as u8 * 17; + let b = (self.palette_write & 0xF) as u8 * 17; let colour = Colour::from_rgb(r, g, b); if self.palette[i] != colour { self.palette[i] = colour; @@ -193,7 +285,7 @@ impl ScreenDevice { } pub fn move_cursor(&mut self, value: u8) { - let distance = (value & 0x3f) as u16; + let distance = (value & 0x3F) as u16; match value >> 6 { 0b00 => self.cursor.x = self.cursor.x.wrapping_add(distance), 0b01 => self.cursor.y = self.cursor.y.wrapping_add(distance), @@ -203,7 +295,7 @@ impl ScreenDevice { }; } - /// Colour must already be masked by 0xf. + /// Colour must already be masked by 0xF. pub fn draw_pixel(&mut self, layer: Layer, x: u16, y: u16, colour: u8) { if x < self.dimensions.width && y < self.dimensions.height { let index = x as usize + (self.dimensions.width as usize * y as usize); @@ -215,13 +307,13 @@ impl ScreenDevice { } fn op_draw_pixel(&mut self, layer: Layer, draw: u8) { - self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xf); + self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xF); } fn op_fill_layer(&mut self, layer: Layer, draw: u8) { match layer { - Layer::Fg => self.fg.fill(draw & 0xf), - Layer::Bg => self.bg.fill(draw & 0xf), + Layer::Fg => self.fg.fill(draw & 0xF), + Layer::Bg => self.bg.fill(draw & 0xF), } } @@ -231,10 +323,10 @@ impl ScreenDevice { false => self.sprite.read_1bit_sprite(draw), }; let colours = [ - (self.colours >> 12 & 0x000f) as u8, - (self.colours >> 8 & 0x000f) as u8, - (self.colours >> 4 & 0x000f) as u8, - (self.colours & 0x000f) as u8, + (self.sprite_colours >> 12 & 0x000F) as u8, + (self.sprite_colours >> 8 & 0x000F) as u8, + (self.sprite_colours >> 4 & 0x000F) as u8, + (self.sprite_colours & 0x000F) as u8, ]; let cx = self.cursor.x; let cy = self.cursor.y; @@ -279,8 +371,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured line. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + let c1 = (self.sprite_colours >> 8 & 0xF) as u8; + let c0 = (self.sprite_colours >> 12 & 0xF) as u8; let opaque = draw & 0x08 == 0; loop { let sprite_pixel = sprite[(y as usize) % 8][(x as usize) % 8]; @@ -293,7 +385,7 @@ impl ScreenDevice { } } else { // Draw solid line. - let colour = draw & 0xf; + let colour = draw & 0xF; loop { self.draw_pixel(layer, x as u16, y as u16, colour); if x == x_end && y == y_end { break; } @@ -307,7 +399,7 @@ impl ScreenDevice { fn op_draw_rect(&mut self, layer: Layer, draw: u8) { macro_rules! clamp { ($v:expr, $max:expr) => { - if $v > 0x7fff { 0 } else if $v > $max { $max } else { $v } + if $v > 0x7FFF { 0 } else if $v > $max { $max } else { $v } }; } macro_rules! out_of_bounds { @@ -333,8 +425,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured rectangle. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + let c1 = (self.sprite_colours >> 8 & 0xF) as u8; + let c0 = (self.sprite_colours >> 12 & 0xF) as u8; let opaque = draw & 0x08 == 0; for y in t..=b { for x in l..=r { @@ -345,7 +437,7 @@ impl ScreenDevice { } } else { // Draw solid rectangle. - let colour = draw & 0xf; + let colour = draw & 0xF; for y in t..=b { for x in l..=r { self.draw_pixel(layer, x, y, colour); @@ -355,146 +447,4 @@ impl ScreenDevice { } } -impl Device for ScreenDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_h!(self.dimensions.width), - 0x1 => read_l!(self.dimensions.width), - 0x2 => read_h!(self.dimensions.height), - 0x3 => read_l!(self.dimensions.height), - 0x4 => read_h!(self.cursor.x), - 0x5 => read_l!(self.cursor.x), - 0x6 => read_h!(self.cursor.y), - 0x7 => read_l!(self.cursor.y), - 0x8 => 0, - 0x9 => 0, - 0xa => 0, - 0xb => 0, - 0xc => 0, - 0xd => 0, - 0xe => 0, - 0xf => 0, - _ => unreachable!(), - } - } - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - self.accessed = true; - match port { - 0x0 => write_h!(self.width_write, value), - 0x1 => { write_l!(self.width_write, value); self.resize_width(); }, - 0x2 => write_h!(self.height_write, value), - 0x3 => { write_l!(self.height_write, value); self.resize_height(); }, - 0x4 => write_h!(self.cursor.x, value), - 0x5 => write_l!(self.cursor.x, value), - 0x6 => write_h!(self.cursor.y, value), - 0x7 => write_l!(self.cursor.y, value), - 0x8 => write_h!(self.palette_write, value), - 0x9 => { write_l!(self.palette_write, value); self.set_palette(); }, - 0xa => write_h!(self.colours, value), - 0xb => write_l!(self.colours, value), - 0xc => self.sprite.push_byte(value), - 0xd => self.sprite.push_byte(value), - 0xe => self.draw_dispatch(value), - 0xf => self.move_cursor(value), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} - -impl HasDimensions<u16> for ScreenDevice { - fn dimensions(&self) -> ScreenDimensions { - self.dimensions - } -} - - -pub struct SpriteBuffer { - pub mem: [u8; 16], - pub pointer: usize, - pub cached: Option<(Sprite, u8)>, -} - -impl SpriteBuffer { - pub fn new() -> Self { - Self { - mem: [0; 16], - pointer: 0, - cached: None, - } - } - - pub fn push_byte(&mut self, byte: u8) { - self.mem[self.pointer] = byte; - self.pointer = (self.pointer + 1) % 16; - self.cached = None; - } - - pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - match draw & 0x07 { - 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } - - pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - let mut s = p + 8; - match draw & 0x07 { - 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } -} diff --git a/src/devices/local_device.rs b/src/devices/stream_device.rs index c6456de..1e67166 100644 --- a/src/devices/local_device.rs +++ b/src/devices/stream_device.rs @@ -1,36 +1,95 @@ use crate::*; -use bedrock_core::*; - use std::collections::VecDeque; use std::io::{BufRead, Stdout, Write}; use std::sync::mpsc::{self, TryRecvError}; -pub struct LocalDevice { - wake: bool, - +pub struct StreamDevice { + /// True if a source is connected to stdin. stdin_connected: bool, + /// True if a transmission is in progress. stdin_control: bool, stdin_rx: mpsc::Receiver<Vec<u8>>, + /// Bytes received in the current transmission. stdin_queue: VecDeque<u8>, + /// Bytes received since stdin end-of-transmission. stdin_excess: VecDeque<u8>, - stdout: Stdout, + stdout: Stdout, + /// True if a sink is connected to stdout. stdout_connected: bool, + /// True if stdin is transmission-encoded. decode_stdin: bool, + /// True if stdout should be transmission-encoded. encode_stdout: bool, + /// Half-byte buffer for decoding stdin. decode_buffer: Option<u8>, + + wake: bool, } -impl LocalDevice { - pub fn new(config: &EmulatorConfig) -> Self { - // Fill input queue with initial transmission. - let mut stdin_queue = VecDeque::new(); - if let Some(bytes) = &config.initial_transmission { - for byte in bytes { stdin_queue.push_front(*byte) } + +impl Device for StreamDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.stdin_connected), + 0x1 => read_b!(self.stdout_connected), + 0x2 => read_b!(self.stdin_control), + 0x3 => read_b!(true), + 0x4 => self.stdin_length(), + 0x5 => read_b!(true), + 0x6 => self.stdin_read(), + 0x7 => self.stdin_read(), + 0x8 => todo!(), + 0x9 => todo!(), + 0xA => todo!(), + 0xB => todo!(), + 0xC => todo!(), + 0xD => todo!(), + 0xE => todo!(), + 0xF => todo!(), + _ => unreachable!(), } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => (), + 0x1 => (), + 0x2 => self.stdin_start_transmission(), + 0x3 => self.stdout_end_transmission(), + 0x4 => (), + 0x5 => (), + 0x6 => self.stdout_write(value), + 0x7 => self.stdout_write(value), + 0x8 => todo!(), + 0x9 => todo!(), + 0xA => todo!(), + 0xB => todo!(), + 0xC => todo!(), + 0xD => todo!(), + 0xE => todo!(), + 0xF => todo!(), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + self.fetch_stdin_data(); + std::mem::take(&mut self.wake) + } + + fn reset(&mut self) { + todo!() + } +} + + +impl StreamDevice { + pub fn new(config: &EmulatorConfig) -> Self { // Spawn a thread to enable non-blocking reads of stdin. let (stdin_tx, stdin_rx) = std::sync::mpsc::channel(); std::thread::spawn(move || loop { @@ -46,20 +105,20 @@ impl LocalDevice { }); Self { - wake: true, - stdin_connected: true, stdin_control: false, stdin_rx, - stdin_queue, + stdin_queue: VecDeque::new(), stdin_excess: VecDeque::new(), - stdout: std::io::stdout(), + stdout: std::io::stdout(), stdout_connected: true, decode_stdin: config.decode_stdin, encode_stdout: config.encode_stdout, decode_buffer: None, + + wake: true, } } @@ -68,7 +127,8 @@ impl LocalDevice { self.stdin_queue.len().try_into().unwrap_or(u8::MAX) } - pub fn stdin_enable(&mut self) { + /// Start a transmission on stdin. + pub fn stdin_start_transmission(&mut self) { self.stdin_control = true; } @@ -81,12 +141,12 @@ impl LocalDevice { macro_rules! hex { ($value:expr) => { match $value { 0x0..=0x9 => $value + b'0', - 0xa..=0xf => $value - 0x0a + b'a', - _ => unreachable!("Cannot encode value as hex digit: 0x{:02x}", $value), + 0xA..=0xF => $value - 0x0A + b'A', + _ => unreachable!("Cannot encode value as hex digit: 0x{:02X}", $value), } }; } if self.encode_stdout { - let encoded = [hex!(value >> 4), hex!(value & 0xf), b' ']; + let encoded = [hex!(value >> 4), hex!(value & 0xF), b' ']; self.stdout_write_raw(&encoded); } else { self.stdout_write_raw(&[value]); @@ -102,12 +162,12 @@ impl LocalDevice { } } - pub fn stdout_disable(&mut self) { - if self.encode_stdout { - self.stdout_write_raw(&[b'\n']); - } + /// End the current transmission on stdout. + pub fn stdout_end_transmission(&mut self) { + self.stdout_write_raw(&[b'\n']); } + /// Fetch all pending data from stdin. pub fn fetch_stdin_data(&mut self) { while self.stdin_control { match self.stdin_excess.pop_front() { @@ -115,21 +175,26 @@ impl LocalDevice { None => break, } } - match self.stdin_rx.try_recv() { - Ok(tx) => { - for byte in tx { - match self.stdin_control { - true => self.fetch_byte(byte), - false => self.stdin_excess.push_back(byte), + loop { + match self.stdin_rx.try_recv() { + Ok(tx) => { + for byte in tx { + match self.stdin_control { + true => self.fetch_byte(byte), + false => self.stdin_excess.push_back(byte), + } } } - } - Err(TryRecvError::Empty) => (), - Err(TryRecvError::Disconnected) => { - self.stdin_control = false; - if self.stdin_connected { - self.stdin_connected = false; - self.wake = true; // wake because stdin was disconnected. + Err(TryRecvError::Empty) => { + break; + } + Err(TryRecvError::Disconnected) => { + self.stdin_control = false; + if self.stdin_connected { + self.stdin_connected = false; + self.wake = true; // wake because stdin was disconnected. + } + break; } } } @@ -139,8 +204,8 @@ impl LocalDevice { if self.decode_stdin { let decoded = match byte { b'0'..=b'9' => byte - b'0', - b'a'..=b'f' => byte - b'a' + 0x0a, - b'A'..=b'F' => byte - b'A' + 0x0a, + b'a'..=b'f' => byte - b'a' + 0x0A, + b'A'..=b'F' => byte - b'A' + 0x0A, b'\n' => { self.decode_buffer = None; self.stdin_control = false; @@ -167,61 +232,8 @@ impl LocalDevice { } -impl Drop for LocalDevice { +impl Drop for StreamDevice { fn drop(&mut self) { self.flush(); } } - - -impl Device for LocalDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_b!(self.stdin_connected), - 0x1 => 0xff, - 0x2 => read_b!(self.stdin_control), - 0x3 => 0xff, - 0x4 => self.stdin_length(), - 0x5 => 0xff, - 0x6 => self.stdin_read(), - 0x7 => self.stdin_read(), - 0x8 => todo!(), - 0x9 => todo!(), - 0xa => todo!(), - 0xb => todo!(), - 0xc => todo!(), - 0xd => todo!(), - 0xe => todo!(), - 0xf => todo!(), - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => (), - 0x1 => (), - 0x2 => self.stdin_enable(), - 0x3 => self.stdout_disable(), - 0x4 => self.stdin_queue.clear(), - 0x5 => (), - 0x6 => self.stdout_write(value), - 0x7 => self.stdout_write(value), - 0x8 => todo!(), - 0x9 => todo!(), - 0xa => todo!(), - 0xb => todo!(), - 0xc => todo!(), - 0xd => todo!(), - 0xe => todo!(), - 0xf => todo!(), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.fetch_stdin_data(); - std::mem::take(&mut self.wake) - } -} diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs index 383bb08..097c616 100644 --- a/src/devices/system_device.rs +++ b/src/devices/system_device.rs @@ -1,80 +1,78 @@ -use bedrock_core::*; +use crate::*; pub struct SystemDevice { - pub name: ReadBuffer, - pub authors: ReadBuffer, - pub can_wake: u16, - pub wake_id: u8, + /// Name and version of this system. + pub name: StringBuffer, + /// Authors of this system. + pub authors: StringBuffer, + /// Mask of all devices permitted to wake from sleep. + pub wake_mask: u16, + /// Slot number of device that most recently woke the system. + pub wake_slot: u8, + /// True if the system has been put to sleep. pub asleep: bool, + /// Mask of all connected devices. + pub connected_devices: u16, + /// Name of the first custom device. + pub custom1: StringBuffer, + /// Name of the second custom device. + pub custom2: StringBuffer, + /// Name of the third custom device. + pub custom3: StringBuffer, + /// Name of the fourth custom device. + pub custom4: StringBuffer, } -impl SystemDevice { - pub fn new() -> Self { - let pkg_version = env!("CARGO_PKG_VERSION"); - let pkg_name = env!("CARGO_PKG_NAME"); - let pkg_authors = env!("CARGO_PKG_AUTHORS"); - let name_str = format!("{pkg_name}/{pkg_version}"); - let authors_str = pkg_authors.replace(":", "\n"); - Self { - name: ReadBuffer::from_str(&name_str), - authors: ReadBuffer::from_str(&authors_str), - can_wake: 0, - wake_id: 0, - asleep: false, - } - } - - pub fn can_wake(&self, dev_id: u8) -> bool { - test_bit!(self.can_wake, 0x8000 >> dev_id) - } -} impl Device for SystemDevice { fn read(&mut self, port: u8) -> u8 { match port { - 0x0 => self.name.read(), - 0x1 => self.authors.read(), - 0x2 => 0x00, + 0x0 => 0x00, + 0x1 => 0x00, + 0x2 => self.wake_slot, 0x3 => 0x00, - 0x4 => 0x00, - 0x5 => 0x00, - 0x6 => 0b1111_1100, - 0x7 => 0b0000_0000, // TODO: Update when fs and stream implemented - 0x8 => 0x00, - 0x9 => 0x00, - 0xa => self.wake_id, - 0xb => 0x00, - 0xc => 0x00, - 0xd => 0x00, - 0xe => 0x00, - 0xf => 0x00, + 0x4 => self.custom1.read(), + 0x5 => self.custom2.read(), + 0x6 => self.custom3.read(), + 0x7 => self.custom4.read(), + 0x8 => self.name.read(), + 0x9 => self.authors.read(), + 0xA => 0x00, + 0xB => 0x00, + 0xC => 0x00, + 0xD => 0x00, + 0xE => read_h!(self.connected_devices), + 0xF => read_l!(self.connected_devices), _ => unreachable!(), } } fn write(&mut self, port: u8, value: u8) -> Option<Signal> { match port { - 0x0 => self.name.pointer = 0, - 0x1 => self.authors.pointer = 0, - 0x2 => (), - 0x3 => (), - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => write_h!(self.can_wake, value), - 0x9 => { - write_l!(self.can_wake, value); + 0x0 => write_h!(self.wake_mask, value), + 0x1 => { + write_l!(self.wake_mask, value); self.asleep = true; return Some(Signal::Sleep); - }, - 0xa => (), - 0xb => return Some(Signal::Fork), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), + } + 0x2 => (), + 0x3 => match value { + 0 => return Some(Signal::Reset), + _ => return Some(Signal::Fork), + } + 0x4 => self.custom1.restart(), + 0x5 => self.custom2.restart(), + 0x6 => self.custom3.restart(), + 0x7 => self.custom4.restart(), + 0x8 => self.name.restart(), + 0x9 => self.authors.restart(), + 0xA => (), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), _ => unreachable!(), }; return None; @@ -83,29 +81,39 @@ impl Device for SystemDevice { fn wake(&mut self) -> bool { true } + + fn reset(&mut self) { + self.wake_mask = 0; + self.wake_slot = 0; + self.custom1.restart(); + self.custom2.restart(); + self.custom3.restart(); + self.custom4.restart(); + self.name.restart(); + self.authors.restart(); + } } -pub struct ReadBuffer { - pub bytes: Vec<u8>, - pub pointer: usize, -} +impl SystemDevice { + pub fn new(connected_devices: u16) -> Self { + let pkg_name = env!("CARGO_PKG_NAME"); + let pkg_version = env!("CARGO_PKG_VERSION"); + let pkg_authors = env!("CARGO_PKG_AUTHORS"); + let name = format!("{pkg_name}/{pkg_version}"); + let authors = pkg_authors.replace(':', "\n"); -impl ReadBuffer { - pub fn from_str(text: &str) -> Self { Self { - bytes: text.bytes().collect(), - pointer: 0, - } - } - - pub fn read(&mut self) -> u8 { - let pointer = self.pointer; - self.pointer += 1; - match self.bytes.get(pointer) { - Some(byte) => *byte, - None => 0, + name: StringBuffer::from_str(&name), + authors: StringBuffer::from_str(&authors), + wake_mask: 0, + wake_slot: 0, + asleep: false, + connected_devices, + custom1: StringBuffer::new(), + custom2: StringBuffer::new(), + custom3: StringBuffer::new(), + custom4: StringBuffer::new(), } } } - diff --git a/src/emulators.rs b/src/emulators.rs deleted file mode 100644 index 56f7181..0000000 --- a/src/emulators.rs +++ /dev/null @@ -1,32 +0,0 @@ -mod headless_emulator; -mod graphical_emulator; - -pub use headless_emulator::{HeadlessEmulator, HeadlessDeviceBus}; -pub use graphical_emulator::{GraphicalEmulator, GraphicalDeviceBus}; - -use crate::*; - -use phosphor::Colour; - -use std::path::PathBuf; - - -pub enum EmulatorSignal { - Promote, - Halt, -} - - -pub struct EmulatorConfig { - pub dimensions: ScreenDimensions, - pub fullscreen: bool, - pub scale: u32, - pub debug_palette: Option<[Colour; 16]>, - pub show_cursor: bool, - - pub initial_transmission: Option<Vec<u8>>, - pub decode_stdin: bool, - pub encode_stdout: bool, - - pub symbols_path: Option<PathBuf>, -} diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs index 14848c6..503a8f4 100644 --- a/src/emulators/graphical_emulator.rs +++ b/src/emulators/graphical_emulator.rs @@ -1,128 +1,42 @@ use crate::*; -use bedrock_core::*; - -use phosphor::*; - -use std::time::Instant; - - -pub struct GraphicalDeviceBus { - pub sys: SystemDevice, - pub mem: MemoryDevice, - pub mat: MathDevice, - pub clk: ClockDevice, - pub inp: InputDevice, - pub scr: ScreenDevice, - pub loc: LocalDevice, - pub rem: RemoteDevice, - pub fs1: FileDevice, - pub fs2: FileDevice, -} - -impl GraphicalDeviceBus { - pub fn new(config: &EmulatorConfig) -> Self { - Self { - sys: SystemDevice::new(), - mem: MemoryDevice::new(), - mat: MathDevice::new(), - clk: ClockDevice::new(), - inp: InputDevice::new(), - scr: ScreenDevice::new(config), - loc: LocalDevice::new(config), - rem: RemoteDevice::new(), - fs1: FileDevice::new(), - fs2: FileDevice::new(), - } - } - - pub fn graphical(&self) -> bool { - self.inp.accessed || self.scr.accessed - } -} - -impl DeviceBus for GraphicalDeviceBus { - fn read(&mut self, port: u8) -> u8 { - match port & 0xf0 { - 0x00 => self.sys.read(port & 0x0f), - 0x10 => self.mem.read(port & 0x0f), - 0x20 => self.mat.read(port & 0x0f), - 0x30 => self.clk.read(port & 0x0f), - 0x40 => self.inp.read(port & 0x0f), - 0x50 => self.scr.read(port & 0x0f), - 0x80 => self.loc.read(port & 0x0f), - 0x90 => self.rem.read(port & 0x0f), - 0xa0 => self.fs1.read(port & 0x0f), - 0xb0 => self.fs2.read(port & 0x0f), - _ => 0 - } - } - - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port & 0xf0 { - 0x00 => self.sys.write(port & 0x0f, value), - 0x10 => self.mem.write(port & 0x0f, value), - 0x20 => self.mat.write(port & 0x0f, value), - 0x30 => self.clk.write(port & 0x0f, value), - 0x40 => self.inp.write(port & 0x0f, value), - 0x50 => self.scr.write(port & 0x0f, value), - 0x80 => self.loc.write(port & 0x0f, value), - 0x90 => self.rem.write(port & 0x0f, value), - 0xa0 => self.fs1.write(port & 0x0f, value), - 0xb0 => self.fs2.write(port & 0x0f, value), - _ => None - } - } - - fn wake(&mut self) -> bool { - macro_rules! rouse { - ($id:expr, $dev:ident) => { - if self.sys.can_wake($id) && self.$dev.wake() { - self.sys.wake_id = $id; - self.sys.asleep = false; - return true; - } - }; - } - rouse!(0xb, fs2); - rouse!(0xa, fs1); - rouse!(0x9, rem); - rouse!(0x8, loc); - rouse!(0x5, scr); - rouse!(0x4, inp); - rouse!(0x3, clk); - rouse!(0x2, mat); - rouse!(0x1, mem); - rouse!(0x0, sys); - return false; - } -} - pub struct GraphicalEmulator { pub br: BedrockEmulator<GraphicalDeviceBus>, pub debug: DebugState, - pub dimensions: ScreenDimensions, + #[cfg(feature = "gamepad")] + pub gilrs: Option<Gilrs>, + pub fullscreen: bool, + pub visible: bool, pub scale: u32, - pub render_mark: Instant, - pub debug_palette: Option<[Colour; 16]>, - pub show_debug_palette: bool, - pub show_cursor: bool, + pub render_mark: Instant, // last time screen was rendered + pub frame_mark: Instant, // refreshes when clean + pub replace_palette: bool, + pub config: EmulatorConfig, } impl GraphicalEmulator { - pub fn new(config: &EmulatorConfig, debug: bool, verbose: bool) -> Self { - let devices = GraphicalDeviceBus::new(config); + pub fn new(config: EmulatorConfig, debug: bool) -> Self { Self { - br: BedrockEmulator::new(devices), - debug: DebugState::new(debug, verbose, config.symbols_path.as_ref()), - dimensions: config.dimensions, + br: BedrockEmulator::new(GraphicalDeviceBus::new(&config)), + debug: DebugState::new(debug, config.symbols_path.as_ref()), + + #[cfg(feature = "gamepad")] + gilrs: match Gilrs::new() { + Ok(gilrs) => Some(gilrs), + Err(err) => { + info!("Could not start gamepad listener: {}", err); + None + } + }, + fullscreen: config.fullscreen, - scale: config.scale, + scale: config.zoom.into(), + replace_palette: config.palette.is_some(), render_mark: Instant::now(), - debug_palette: config.debug_palette, - show_debug_palette: config.debug_palette.is_some(), - show_cursor: config.show_cursor, + frame_mark: Instant::now(), + config, + visible: false, } } @@ -130,70 +44,108 @@ impl GraphicalEmulator { self.br.core.mem.load_program(bytecode); } - pub fn run(&mut self) -> EmulatorSignal { - loop { - match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { - Some(Signal::Fork) => { - self.br.core.mem.pc = 0; - self.br.core.wst.sp = 0; - self.br.core.rst.sp = 0; - } - Some(Signal::Sleep) => loop { - if self.br.dev.graphical() { - return EmulatorSignal::Promote; - } - if self.br.dev.wake() { break; } - std::thread::sleep(MIN_TICK_DURATION); - } - Some(Signal::Halt) => { - self.br.dev.loc.flush(); - self.debug.info("Program halted, exiting."); - self.debug.debug_summary(&self.br.core); - return EmulatorSignal::Halt; - } - Some(Signal::Debug1) => { - self.debug.debug_summary(&self.br.core); - } - _ => (), - } - - if self.br.dev.graphical() { - return EmulatorSignal::Promote; - } - } + pub fn run(mut self, mut phosphor: Phosphor, visible: bool) { + self.visible = visible; + let window = WindowBuilder { + dimensions: Some(self.dimensions()), + size_bounds: Some(self.size_bounds()), + fullscreen: self.fullscreen, + scale: self.scale, + title: Some(self.config.title.clone()), + cursor: match self.config.show_cursor { + true => Some(CursorIcon::Default), + false => None, + }, + icon: self.config.icon.clone(), + program: Box::new(self), + visible, + }; + phosphor.add_window(window); + phosphor.run().unwrap(); } pub fn size_bounds(&self) -> SizeBounds { - macro_rules! to_u32 { - ($opt:expr) => { - match $opt { - Some(a) => Some(u32::from(a)), - None => None, - } - }; - } match self.fullscreen { true => SizeBounds { - min_width: None, - max_width: None, + min_width: None, + max_width: None, min_height: None, max_height: None, }, false => SizeBounds { - min_width: to_u32!(self.br.dev.scr.fixed_width), - max_width: to_u32!(self.br.dev.scr.fixed_width), - min_height: to_u32!(self.br.dev.scr.fixed_height), - max_height: to_u32!(self.br.dev.scr.fixed_height), + min_width: self.br.dev.screen.fixed_width.map(u32::from), + max_width: self.br.dev.screen.fixed_width.map(u32::from), + min_height: self.br.dev.screen.fixed_height.map(u32::from), + max_height: self.br.dev.screen.fixed_height.map(u32::from), }, - } } pub fn dimensions(&self) -> Dimensions { Dimensions { - width: u32::from(self.br.dev.scr.dimensions.width), - height: u32::from(self.br.dev.scr.dimensions.height), + width: self.br.dev.screen.dimensions.width.into(), + height: self.br.dev.screen.dimensions.height.into(), + } + } +} + + +pub struct GraphicalDeviceBus { + pub system: SystemDevice, + pub memory: MemoryDevice, + pub math: MathDevice, + pub clock: ClockDevice, + pub input: InputDevice, + pub screen: ScreenDevice, + pub stream: StreamDevice, + pub file: FileDevice, + pub wake_queue: WakeQueue, +} + +impl GraphicalDeviceBus { + pub fn new(config: &EmulatorConfig) -> Self { + Self { + system: SystemDevice::new(0b1111_1100_1100_0000), + memory: MemoryDevice::new(), + math: MathDevice::new(), + clock: ClockDevice::new(), + input: InputDevice::new(), + screen: ScreenDevice::new(&config), + stream: StreamDevice::new(&config), + file: FileDevice::new(&config), + wake_queue: WakeQueue::new(), + } + } +} + + +impl DeviceBus for GraphicalDeviceBus { + fn get_device(&mut self, slot: u8) -> Option<&mut dyn Device> { + match slot { + 0x0 => Some(&mut self.system), + 0x1 => Some(&mut self.memory), + 0x2 => Some(&mut self.math ), + 0x3 => Some(&mut self.clock ), + 0x4 => Some(&mut self.input ), + 0x5 => Some(&mut self.screen), + 0x8 => Some(&mut self.stream), + 0x9 => Some(&mut self.file ), + _ => None + } + } + + fn wake(&mut self) -> bool { + for slot in self.wake_queue.iter(self.system.wake_mask) { + if let Some(device) = self.get_device(slot) { + if device.wake() { + self.system.wake_slot = slot; + self.system.asleep = false; + self.wake_queue.wake(slot); + return true; + } + } } + return false; } } @@ -202,33 +154,31 @@ impl WindowProgram for GraphicalEmulator { fn handle_event(&mut self, event: Event, r: &mut EventWriter<Request>) { match event { Event::CloseRequest => r.write(Request::CloseWindow), - Event::CursorEnter => self.br.dev.inp.on_cursor_enter(), - Event::CursorExit => self.br.dev.inp.on_cursor_exit(), - Event::CursorMove(p) => self.br.dev.inp.on_cursor_move(p), - Event::Resize(d) => self.br.dev.scr.resize(d), - Event::CharacterInput(c) => self.br.dev.inp.on_character(c), - Event::ModifierChange(m) => self.br.dev.inp.on_modifier(m), - Event::MouseButton { button, action } => - self.br.dev.inp.on_mouse_button(button, action), + Event::CursorEnter => self.br.dev.input.on_cursor_enter(), + Event::CursorExit => self.br.dev.input.on_cursor_exit(), + Event::CursorMove(p) => self.br.dev.input.on_cursor_move(p), + Event::Resize(d) => self.br.dev.screen.resize(d), + Event::CharacterInput(c) => self.br.dev.input.on_character(c), + Event::ModifierChange(m) => self.br.dev.input.on_modifier(m), + Event::MouseButton { button, action } => self.br.dev.input.on_mouse_button(button, action), Event::FocusChange(_) => (), Event::Initialise => (), Event::ScrollLines { axis, distance } => match axis { - Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance), - Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance), + Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance), + Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance), } Event::ScrollPixels { axis, distance } => match axis { - Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance / 20.0), - Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance / 20.0), + Axis::Horizontal => self.br.dev.input.on_horizontal_scroll(distance / 20.0), + Axis::Vertical => self.br.dev.input.on_vertical_scroll(distance / 20.0), } - Event::FileDrop(_path) => todo!("FileDrop"), Event::Close => (), Event::KeyboardInput { key, action } => { - self.br.dev.inp.on_keypress(key, action); + self.br.dev.input.on_keypress(key, action); if action == Action::Pressed { match key { KeyCode::F2 => { - self.show_debug_palette = !self.show_debug_palette; + self.replace_palette = !self.replace_palette; r.write(Request::Redraw); }, KeyCode::F5 => { @@ -247,83 +197,101 @@ impl WindowProgram for GraphicalEmulator { } } } + _ => (), } } fn process(&mut self, requests: &mut EventWriter<Request>) { - self.br.dev.loc.flush(); + self.br.dev.stream.flush(); + + #[cfg(feature = "gamepad")] + if let Some(gilrs) = &mut self.gilrs { + while let Some(event) = gilrs.next_event() { + self.br.dev.input.on_gamepad_event(event); + } + } - if self.br.dev.sys.asleep { + if self.br.dev.system.asleep { // Stay asleep if there are no pending wake events. if !self.br.dev.wake() { - if self.br.dev.scr.dirty { + if self.br.dev.screen.dirty { requests.write(Request::Redraw); } - std::thread::sleep(MIN_TICK_DURATION); + std::thread::sleep(TICK_DURATION); return; } // Wait for the current frame to be rendered. - if self.br.dev.scr.dirty { - if self.render_mark.elapsed() > MIN_FRAME_DURATION { - requests.write(Request::Redraw); - } - std::thread::sleep(MIN_TICK_DURATION); + if self.br.dev.screen.dirty { + requests.write(Request::Redraw); + std::thread::sleep(TICK_DURATION); return; } } // Run the processor for the remainder of the frame. - let frame_end = Instant::now() + MIN_TICK_DURATION; + let frame_end = Instant::now() + TICK_DURATION; while Instant::now() < frame_end { - match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { - Some(Signal::Fork) => { - todo!("Fork") - } - Some(Signal::Sleep) => { - self.br.dev.sys.asleep = true; - break; - } - Some(Signal::Halt) => { - self.br.dev.loc.flush(); - self.debug.info("Program halted, exiting."); - self.debug.debug_summary(&self.br.core); - requests.write(Request::CloseWindow); - break; - } - Some(Signal::Debug1) => { - self.debug.debug_summary(&self.br.core); + if let Some(signal) = self.br.evaluate(BATCH_SIZE, self.debug.enabled) { + match signal { + Signal::Break => { + } + Signal::Fork | Signal::Reset => { + self.br.reset(); + } + Signal::Sleep => { + self.br.dev.system.asleep = true; + break; + } + Signal::Halt => { + self.br.dev.stream.flush(); + info!("Program halted, exiting."); + self.debug.debug_full(&self.br.core); + requests.write(Request::CloseWindow); + break; + } + Signal::Debug(debug_signal) => match debug_signal { + Debug::Debug1 => self.debug.debug_full(&self.br.core), + Debug::Debug2 => self.debug.debug_timing(&self.br.core), + _ => (), + } } - _ => (), } } - if std::mem::take(&mut self.br.dev.scr.dirty_dimensions) { + if !self.visible { + if self.br.dev.input.accessed || self.br.dev.screen.accessed { + info!("Making window visible"); + requests.write(Request::SetVisible(true)); + self.visible = true; + } + } + + if std::mem::take(&mut self.br.dev.screen.dirty_dimensions) { requests.write(Request::SetSizeBounds(self.size_bounds())); } - if self.br.dev.scr.dirty { - let elapsed = self.render_mark.elapsed(); - if self.br.dev.sys.asleep && elapsed > MIN_FRAME_DURATION { + if self.br.dev.screen.dirty { + if self.br.dev.system.asleep { requests.write(Request::Redraw); - } else if elapsed > MAX_FRAME_DURATION { + } else if self.frame_mark.elapsed() > MAX_FRAME_DURATION { requests.write(Request::Redraw); } } else { - self.render_mark = Instant::now(); + self.frame_mark = Instant::now(); } } fn render(&mut self, buffer: &mut Buffer, _full: bool) { - let screen = &mut self.br.dev.scr; + let screen = &mut self.br.dev.screen; // Generate table for calculating pixel colours from layer values. // A given screen pixel will be rendered as the colour given by // table[fg][bg], where fg and bg are the corresponding layer values. let mut table = [Colour::BLACK; 256]; - let palette = match self.debug_palette { - Some(debug_palette) => match self.show_debug_palette { - true => debug_palette, + let palette = match self.config.palette { + Some(palette) => match self.replace_palette { + true => palette, false => screen.palette, } None => screen.palette, @@ -338,7 +306,6 @@ impl WindowProgram for GraphicalEmulator { let bg = screen.bg[i]; let index = unsafe { fg.unchecked_shl(4) | bg }; *colour = table[index as usize]; - // TODO: merge fg and bg: *colour = table[screen.bg[i] as usize]; } // Copy pixels to buffer when it is a different size to the screen. @@ -374,5 +341,6 @@ impl WindowProgram for GraphicalEmulator { screen.dirty = false; self.render_mark = Instant::now(); + self.frame_mark = Instant::now(); } } diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs index f215db3..770bae3 100644 --- a/src/emulators/headless_emulator.rs +++ b/src/emulators/headless_emulator.rs @@ -1,125 +1,105 @@ use crate::*; -use bedrock_core::*; -pub struct HeadlessDeviceBus { - pub sys: SystemDevice, - pub mem: MemoryDevice, - pub mat: MathDevice, - pub clk: ClockDevice, - pub loc: LocalDevice, - pub rem: RemoteDevice, - pub fs1: FileDevice, - pub fs2: FileDevice, +pub struct HeadlessEmulator { + pub br: BedrockEmulator<HeadlessDeviceBus>, + pub debug: DebugState, } -impl HeadlessDeviceBus { - pub fn new(config: &EmulatorConfig) -> Self { +impl HeadlessEmulator { + pub fn new(config: &EmulatorConfig, debug: bool) -> Self { Self { - sys: SystemDevice::new(), - mem: MemoryDevice::new(), - mat: MathDevice::new(), - clk: ClockDevice::new(), - loc: LocalDevice::new(config), - rem: RemoteDevice::new(), - fs1: FileDevice::new(), - fs2: FileDevice::new(), + br: BedrockEmulator::new(HeadlessDeviceBus::new(config)), + debug: DebugState::new(debug, config.symbols_path.as_ref()), } } -} -impl DeviceBus for HeadlessDeviceBus { - fn read(&mut self, port: u8) -> u8 { - match port & 0xf0 { - 0x00 => self.sys.read(port & 0x0f), - 0x10 => self.mem.read(port & 0x0f), - 0x20 => self.mat.read(port & 0x0f), - 0x30 => self.clk.read(port & 0x0f), - 0x80 => self.loc.read(port & 0x0f), - 0x90 => self.rem.read(port & 0x0f), - 0xa0 => self.fs1.read(port & 0x0f), - 0xb0 => self.fs2.read(port & 0x0f), - _ => 0 - } + pub fn load_program(&mut self, bytecode: &[u8]) { + self.br.core.mem.load_program(bytecode); } - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port & 0xf0 { - 0x00 => self.sys.write(port & 0x0f, value), - 0x10 => self.mem.write(port & 0x0f, value), - 0x20 => self.mat.write(port & 0x0f, value), - 0x30 => self.clk.write(port & 0x0f, value), - 0x80 => self.loc.write(port & 0x0f, value), - 0x90 => self.rem.write(port & 0x0f, value), - 0xa0 => self.fs1.write(port & 0x0f, value), - 0xb0 => self.fs2.write(port & 0x0f, value), - _ => None + fn sleep(&mut self) { + loop { + if self.br.dev.wake() { break; } + std::thread::sleep(TICK_DURATION); } } - fn wake(&mut self) -> bool { - macro_rules! rouse { - ($id:expr, $dev:ident) => { - if self.sys.can_wake($id) && self.$dev.wake() { - self.sys.wake_id = $id; - self.sys.asleep = false; - return true; + fn halt(&mut self) { + self.br.dev.stream.flush(); + info!("Program halted, exiting."); + self.debug.debug_full(&self.br.core); + std::process::exit(0); + } + + pub fn run(&mut self) -> ! { + loop { + if let Some(signal) = self.br.evaluate(BATCH_SIZE, self.debug.enabled) { + match signal { + Signal::Fork | Signal::Reset => self.br.reset(), + Signal::Sleep => self.sleep(), + Signal::Halt => self.halt(), + Signal::Debug(debug_signal) => match debug_signal { + Debug::Debug1 => self.debug.debug_full(&self.br.core), + Debug::Debug2 => self.debug.debug_timing(&self.br.core), + _ => (), + } + _ => (), } - }; + } } - rouse!(0xb, fs2); - rouse!(0xa, fs1); - rouse!(0x9, rem); - rouse!(0x8, loc); - rouse!(0x3, clk); - rouse!(0x2, mat); - rouse!(0x1, mem); - rouse!(0x0, sys); - return false; } } -pub struct HeadlessEmulator { - pub br: BedrockEmulator<HeadlessDeviceBus>, - pub debug: DebugState, +pub struct HeadlessDeviceBus { + pub system: SystemDevice, + pub memory: MemoryDevice, + pub math: MathDevice, + pub clock: ClockDevice, + pub stream: StreamDevice, + pub file: FileDevice, + pub wake_queue: WakeQueue, } -impl HeadlessEmulator { - pub fn new(config: &EmulatorConfig, debug: bool, verbose: bool) -> Self { +impl HeadlessDeviceBus { + pub fn new(config: &EmulatorConfig) -> Self { Self { - br: BedrockEmulator::new(HeadlessDeviceBus::new(config)), - debug: DebugState::new(debug, verbose, config.symbols_path.as_ref()), + system: SystemDevice::new(0b1111_0000_1100_0000), + memory: MemoryDevice::new(), + math: MathDevice::new(), + clock: ClockDevice::new(), + stream: StreamDevice::new(&config), + file: FileDevice::new(&config), + wake_queue: WakeQueue::new(), } } +} - pub fn load_program(&mut self, bytecode: &[u8]) { - self.br.core.mem.load_program(bytecode); +impl DeviceBus for HeadlessDeviceBus { + fn get_device(&mut self, slot: u8) -> Option<&mut dyn Device> { + match slot { + 0x0 => Some(&mut self.system), + 0x1 => Some(&mut self.memory), + 0x2 => Some(&mut self.math ), + 0x3 => Some(&mut self.clock ), + 0x8 => Some(&mut self.stream), + 0x9 => Some(&mut self.file ), + _ => None + } } - pub fn run(&mut self, debug: bool) -> EmulatorSignal { - loop { - match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { - Some(Signal::Fork) => { - self.br.core.mem.pc = 0; - self.br.core.wst.sp = 0; - self.br.core.rst.sp = 0; - } - Some(Signal::Sleep) => loop { - if self.br.dev.wake() { break; } - std::thread::sleep(MIN_TICK_DURATION); - } - Some(Signal::Halt) => { - self.br.dev.loc.flush(); - self.debug.info("Program halted, exiting."); - self.debug.debug_summary(&self.br.core); - return EmulatorSignal::Halt; - } - Some(Signal::Debug1) => if debug { - self.debug.debug_summary(&self.br.core); + fn wake(&mut self) -> bool { + for slot in self.wake_queue.iter(self.system.wake_mask) { + if let Some(device) = self.get_device(slot) { + if device.wake() { + self.system.wake_slot = slot; + self.system.asleep = false; + self.wake_queue.wake(slot); + return true; } - _ => (), } } + return false; } } diff --git a/src/emulators/mod.rs b/src/emulators/mod.rs new file mode 100644 index 0000000..d4a58f9 --- /dev/null +++ b/src/emulators/mod.rs @@ -0,0 +1,24 @@ +mod headless_emulator; +mod graphical_emulator; + +pub use headless_emulator::*; +pub use graphical_emulator::*; + +use crate::*; + + +pub struct EmulatorConfig { + pub dimensions: ScreenDimensions, + pub fullscreen: bool, + pub zoom: NonZeroU32, + pub palette: Option<[Colour; 16]>, + pub show_cursor: bool, + pub decode_stdin: bool, + pub encode_stdout: bool, + pub trust_files: bool, + pub symbols_path: Option<PathBuf>, + pub name: Option<String>, + pub identifier: Option<String>, + pub title: String, + pub icon: Option<Icon>, +} @@ -1,23 +1,28 @@ #![feature(bigint_helper_methods)] -#![feature(unchecked_shifts)] #![feature(seek_stream_len)] -#![feature(io_error_more)] +#![feature(unchecked_shifts)] mod debug; mod devices; mod emulators; -mod metadata; +mod types; -pub use debug::DebugState; +pub use debug::*; pub use devices::*; pub use emulators::*; -pub use metadata::*; +pub use types::*; + +use bedrock_core::*; +use log::*; +use phosphor::*; use std::num::NonZeroU32; -use std::time::Duration; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; + pub const BATCH_SIZE: usize = 1000; -pub const MIN_TICK_DURATION: Duration = Duration::from_millis( 4 ); -pub const MIN_FRAME_DURATION: Duration = Duration::from_millis( 14 ); +pub const TICK_DURATION: Duration = Duration::from_nanos( 1_000_000_000/256 ); +pub const MIN_FRAME_DURATION: Duration = Duration::from_millis( 10 ); pub const MAX_FRAME_DURATION: Duration = Duration::from_millis( 500 ); pub const DEFAULT_SCREEN_SIZE: ScreenDimensions = ScreenDimensions::new(800,600); pub const DEFAULT_SCREEN_SCALE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) }; @@ -25,10 +30,24 @@ pub const DEFAULT_SCREEN_SCALE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked( pub type ScreenPosition = geometry::Point<u16>; pub type ScreenDimensions = geometry::Dimensions<u16>; -#[macro_export] -macro_rules! error { - ($source:expr, $($tokens:tt)*) => {{ - eprint!("[ERROR] [{}]: ", $source); - eprintln!($($tokens)*); - }}; + +pub fn run_program(bytecode: &[u8], config: EmulatorConfig) { + let mut args = switchboard::Switchboard::from_env(); + args.named("verbose").short('v'); + if args.get("verbose").as_bool() { + log::set_log_level(log::LogLevel::Info); + } + + match Phosphor::new() { + Ok(phosphor) => { + info!("Starting graphical emulator"); + let mut emulator = GraphicalEmulator::new(config, false); + emulator.load_program(&bytecode); + emulator.run(phosphor, true); + } + Err(err) => { + eprintln!("EventLoopError: {err:?}"); + fatal!("Could not start graphical event loop"); + } + } } diff --git a/src/metadata.rs b/src/metadata.rs deleted file mode 100644 index 7692434..0000000 --- a/src/metadata.rs +++ /dev/null @@ -1,127 +0,0 @@ -use phosphor::Colour; - - -pub fn parse_metadata(bytecode: &[u8]) -> Option<ProgramMetadata> { - MetadataParser::from_bytecode(bytecode).parse() -} - - -struct MetadataParser<'a> { - bytecode: &'a [u8], -} - -impl<'a> MetadataParser<'a> { - pub fn from_bytecode(bytecode: &'a [u8]) -> Self { - Self { bytecode } - } - - pub fn parse(self) -> Option<ProgramMetadata> { - macro_rules! array { - ($len:expr, $slice:expr) => {{ - let mut array = [0; $len]; - for i in 0..$len { array[i] = *$slice.get(i).unwrap_or(&0); } - array - }}; - } - - if self.range(0x00, 3) != &[0x41, 0x00, 0x20] { - return None; - } - - let (name, version) = split_name_version(self.string(self.double(0x10))); - let authors = self.string(self.double(0x12)).map(|string| { - string.lines().map(|line| line.to_string()).collect() - }); - - Some( ProgramMetadata { - bedrock_string: array!(7, self.range(0x03, 7)), - program_memory_size: match self.double(0x0a) { - 0 => 65536, double => double as usize }, - working_stack_size: match self.byte(0x0c) { - 0 => 256, byte => byte as usize }, - return_stack_size: match self.byte(0x0d) { - 0 => 256, byte => byte as usize }, - required_devices: self.double(0x0e), - name, - version, - authors, - description: self.string(self.double(0x14)), - path_buffer: match self.double(0x16) { - 0 => None, addr => Some(addr as usize) }, - small_icon: match self.double(0x18) { - 0 => None, addr => Some(array!(72, self.range(addr, 72))) }, - large_icon: match self.double(0x1a) { - 0 => None, addr => Some(array!(512, self.range(addr, 512))) }, - bg_colour: parse_colour(self.double(0x1c)), - fg_colour: parse_colour(self.double(0x1e)), - } ) - } - - fn byte(&self, address: u16) -> u8 { - *self.bytecode.get(address as usize).unwrap_or(&0) - } - - fn double(&self, address: u16) -> u16 { - u16::from_be_bytes([ - *self.bytecode.get(address as usize).unwrap_or(&0), - *self.bytecode.get(address as usize + 1).unwrap_or(&0), - ]) - } - - fn range(&self, address: u16, length: usize) -> &[u8] { - let start = address as usize; - let end = start + length; - self.bytecode.get(start..end).unwrap_or(&[]) - } - - fn string(&self, address: u16) -> Option<String> { - if address == 0 { return None; } - let start = address as usize; - let mut end = start; - while let Some(byte) = self.bytecode.get(end) { - match byte { 0 => break, _ => end += 1 } - } - let range = self.bytecode.get(start..end)?; - Some( String::from_utf8_lossy(range).to_string() ) - } -} - -fn split_name_version(string: Option<String>) -> (Option<String>, Option<String>) { - if let Some(string) = string { - match string.split_once('/') { - Some((left, right)) => (Some(left.to_string()), Some(right.to_string())), - None => (Some(string), None), - } - } else { - (None, None) - } -} - -fn parse_colour(double: u16) -> Option<Colour> { - let i = (double >> 12 ) as usize; - let r = (double >> 8 & 0xf) as u8 * 17; - let g = (double >> 4 & 0xf) as u8 * 17; - let b = (double & 0xf) as u8 * 17; - match i { - 0 => Some(Colour::from_rgb(r, g, b)), - _ => None, - } -} - - -pub struct ProgramMetadata { - pub bedrock_string: [u8; 7], - pub program_memory_size: usize, - pub working_stack_size: usize, - pub return_stack_size: usize, - pub required_devices: u16, - pub name: Option<String>, - pub version: Option<String>, - pub authors: Option<Vec<String>>, - pub description: Option<String>, - pub path_buffer: Option<usize>, - pub small_icon: Option<[u8; 72]>, - pub large_icon: Option<[u8; 512]>, - pub bg_colour: Option<Colour>, - pub fg_colour: Option<Colour>, -} diff --git a/src/devices/file_device/buffered_file.rs b/src/types/buffered_file.rs index f965950..5cdf0ea 100644 --- a/src/devices/file_device/buffered_file.rs +++ b/src/types/buffered_file.rs @@ -1,10 +1,7 @@ use std::fs::File; -use std::io::{BufReader, BufWriter}; -use std::io::{Read, Write}; +use std::io::{BufReader, BufWriter, Read, Write}; use std::io::{ErrorKind, Seek, SeekFrom}; -use crate::*; - pub struct BufferedFile { file: AccessMode, @@ -42,7 +39,7 @@ impl BufferedFile { Ok(_) => buffer[0], Err(error) => match error.kind() { ErrorKind::UnexpectedEof => 0, - _ => { error!("BufferedFile::read", "{error:?}"); 0 }, + _ => { log::error!("BufferedFile::read: {error:?}"); 0 }, } } } diff --git a/src/types/controller.rs b/src/types/controller.rs new file mode 100644 index 0000000..42d3f8c --- /dev/null +++ b/src/types/controller.rs @@ -0,0 +1,166 @@ +use crate::*; + +#[cfg(feature = "gamepad")] +pub use gilrs::{Gilrs, GamepadId}; + + +pub struct OwnedGamepad { + tag: usize, + #[cfg(feature = "gamepad")] + id: Option<GamepadId>, + #[cfg(not(feature = "gamepad"))] + id: Option<()>, + gamepad: Gamepad, +} + +impl OwnedGamepad { + pub fn new(tag: usize) -> Self { + Self { tag, id: None, gamepad: Gamepad::new() } + } + + /// Returns Some if the ID owns this gamepad. + #[cfg(feature = "gamepad")] + pub fn register(&mut self, new_id: GamepadId) -> Option<&mut Gamepad> { + if let Some(id) = self.id { + match id == new_id { + true => Some(&mut self.gamepad), + false => None, + } + } else { + self.id = Some(new_id); + info!("Registered gamepad {}", self.tag); + Some(&mut self.gamepad) + } + } + + pub fn state(&self) -> u8 { + self.gamepad.state + } + + pub fn reset(&mut self) { + self.gamepad.reset(); + } +} + + +pub struct Gamepad { + pub state: u8, + l_up: bool, + l_down: bool, + l_left: bool, + l_right: bool, + r_up: bool, + r_down: bool, + r_left: bool, + r_right: bool, + d_up: bool, + d_down: bool, + d_left: bool, + d_right: bool, + a: bool, + b: bool, + x: bool, + y: bool, +} + +impl Gamepad { + pub fn new() -> Self { + Self { + state: 0, + l_up: false, + l_down: false, + l_left: false, + l_right: false, + r_up: false, + r_down: false, + r_left: false, + r_right: false, + d_up: false, + d_down: false, + d_left: false, + d_right: false, + a: false, + b: false, + x: false, + y: false, + } + } + + pub fn reset(&mut self) { + self.state = 0; + self.l_up = false; + self.l_down = false; + self.l_left = false; + self.l_right = false; + self.r_up = false; + self.r_down = false; + self.r_left = false; + self.r_right = false; + self.d_up = false; + self.d_down = false; + self.d_left = false; + self.d_right = false; + self.a = false; + self.b = false; + self.x = false; + self.y = false; + } + + // Returns true if the state changed. + #[cfg(feature = "gamepad")] + pub fn process_event(&mut self, event: &gilrs::Event) -> bool { + macro_rules! schmitt { + ($name_neg:ident, $name_pos:ident, $v:expr) => {{ + if self.$name_neg { if $v > -0.40 { self.$name_neg = false; } } + else { if $v < -0.50 { self.$name_neg = true; } } + if self.$name_pos { if $v < 0.40 { self.$name_pos = false; } } + else { if $v > 0.50 { self.$name_pos = true; } } + }}; + } + + match event.event { + gilrs::EventType::ButtonPressed(button, _) => match button { + gilrs::Button::South => self.a = true, + gilrs::Button::East => self.b = true, + gilrs::Button::West => self.x = true, + gilrs::Button::North => self.y = true, + gilrs::Button::DPadUp => self.d_up = true, + gilrs::Button::DPadDown => self.d_down = true, + gilrs::Button::DPadLeft => self.d_left = true, + gilrs::Button::DPadRight => self.d_right = true, + _ => (), + } + gilrs::EventType::ButtonReleased(button, _) => match button { + gilrs::Button::South => self.a = false, + gilrs::Button::East => self.b = false, + gilrs::Button::West => self.x = false, + gilrs::Button::North => self.y = false, + gilrs::Button::DPadUp => self.d_up = false, + gilrs::Button::DPadDown => self.d_down = false, + gilrs::Button::DPadLeft => self.d_left = false, + gilrs::Button::DPadRight => self.d_right = false, + _ => (), + } + gilrs::EventType::AxisChanged(axis, v, _) => match axis { + gilrs::Axis::LeftStickX => schmitt!(l_left, l_right, v), + gilrs::Axis::LeftStickY => schmitt!(l_down, l_up, v), + gilrs::Axis::RightStickX => schmitt!(r_left, r_right, v), + gilrs::Axis::RightStickY => schmitt!(r_down, r_up, v), + _ => (), + } + _ => (), + } + + let old_state = self.state; + self.state = 0; + if self.l_up | self.r_up | self.d_up { self.state |= 0x80; } + if self.l_down | self.r_down | self.d_down { self.state |= 0x40; } + if self.l_left | self.r_left | self.d_left { self.state |= 0x20; } + if self.l_right | self.r_right | self.d_right { self.state |= 0x10; } + if self.a { self.state |= 0x08; } + if self.b { self.state |= 0x04; } + if self.x { self.state |= 0x02; } + if self.y { self.state |= 0x01; } + old_state != self.state + } +} diff --git a/src/devices/file_device/directory_listing.rs b/src/types/directory_listing.rs index 465efc7..f079217 100644 --- a/src/devices/file_device/directory_listing.rs +++ b/src/types/directory_listing.rs @@ -1,4 +1,4 @@ -use super::*; +use crate::*; pub struct DirectoryListing { diff --git a/src/devices/file_device/entry.rs b/src/types/entry_type.rs index d604bb7..6a9ac2d 100644 --- a/src/devices/file_device/entry.rs +++ b/src/types/entry_type.rs @@ -1,7 +1,8 @@ -use super::*; +use crate::*; use std::cmp::Ordering; + pub enum Entry { File(BufferedFile), Directory(DirectoryListing), diff --git a/src/devices/file_device/bedrock_file_path.rs b/src/types/file_path.rs index fdd8f79..7e6dbe8 100644 --- a/src/devices/file_device/bedrock_file_path.rs +++ b/src/types/file_path.rs @@ -1,7 +1,8 @@ -use super::*; +use crate::*; use std::cmp::Ordering; use std::ffi::OsString; +use std::path::Component; #[derive(Clone)] diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..1cc90d3 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,19 @@ +mod buffered_file; +mod controller; +mod directory_listing; +mod entry_type; +mod file_path; +mod path_buffer; +mod sprite_buffer; +mod string_buffer; +mod wake_queue; + +pub use buffered_file::*; +pub use controller::*; +pub use directory_listing::*; +pub use entry_type::*; +pub use file_path::*; +pub use path_buffer::*; +pub use sprite_buffer::*; +pub use string_buffer::*; +pub use wake_queue::*; diff --git a/src/devices/file_device/bedrock_path_buffer.rs b/src/types/path_buffer.rs index d6a0861..d6a0861 100644 --- a/src/devices/file_device/bedrock_path_buffer.rs +++ b/src/types/path_buffer.rs diff --git a/src/types/sprite_buffer.rs b/src/types/sprite_buffer.rs new file mode 100644 index 0000000..74c7b55 --- /dev/null +++ b/src/types/sprite_buffer.rs @@ -0,0 +1,85 @@ +use crate::*; + + +pub struct SpriteBuffer { + pub mem: [u8; 16], + pub pointer: usize, + pub cached: Option<(Sprite, u8)>, +} + +impl SpriteBuffer { + pub fn new() -> Self { + Self { + mem: [0; 16], + pointer: 0, + cached: None, + } + } + + pub fn push_byte(&mut self, byte: u8) { + self.mem[self.pointer] = byte; + self.pointer = (self.pointer + 1) % 16; + self.cached = None; + } + + pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + match draw & 0x07 { + 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } + + pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + let mut s = p + 8; + match draw & 0x07 { + 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } +} diff --git a/src/types/string_buffer.rs b/src/types/string_buffer.rs new file mode 100644 index 0000000..7751d9f --- /dev/null +++ b/src/types/string_buffer.rs @@ -0,0 +1,37 @@ +pub struct StringBuffer { + pub bytes: Vec<u8>, + pub pointer: usize, +} + +impl StringBuffer { + pub fn new() -> Self { + Self { + bytes: Vec::new(), + pointer: 0, + } + } + + pub fn from_str(text: &str) -> Self { + let mut new = Self::new(); + new.set_str(text); + new + } + + pub fn set_str(&mut self, text: &str) { + self.bytes = text.bytes().collect(); + self.pointer = 0; + } + + pub fn read(&mut self) -> u8 { + if let Some(byte) = self.bytes.get(self.pointer) { + self.pointer += 1; + *byte + } else { + 0 + } + } + + pub fn restart(&mut self) { + self.pointer = 0; + } +} diff --git a/src/types/wake_queue.rs b/src/types/wake_queue.rs new file mode 100644 index 0000000..41d815b --- /dev/null +++ b/src/types/wake_queue.rs @@ -0,0 +1,51 @@ +pub struct WakeQueue { + queue: Vec<u8>, +} + + +impl WakeQueue { + pub fn new() -> Self { + Self { + queue: [0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF].into(), + } + } + + /// Iterate over all masked devices in last-woken order. + pub fn iter(&self, mask: u16) -> WakeIterator { + let mut queue = Vec::new(); + for i in &self.queue { + if mask & (0x8000 >> i) != 0 { + queue.push(*i); + } + } + // Add system device last. + if mask & 0x8000 != 0 { + queue.push(0); + } + WakeIterator { queue, pointer: 0 } + } + + /// Push a device to the back of the queue. + pub fn wake(&mut self, id: u8) { + if let Some(index) = self.queue.iter().position(|e| *e == id) { + self.queue.remove(index); + self.queue.push(id); + } + } +} + + +pub struct WakeIterator { + queue: Vec<u8>, + pointer: usize, +} + +impl Iterator for WakeIterator { + type Item = u8; + + fn next(&mut self) -> Option<u8> { + let pointer = self.pointer; + self.pointer += 1; + self.queue.get(pointer).copied() + } +} |