diff options
40 files changed, 3863 insertions, 2616 deletions
@@ -1,23 +1,39 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] [[package]] name = "android-activity" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.6.0", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", - "num_enum 0.6.1", + "num_enum", + "thiserror", ] [[package]] @@ -33,26 +49,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "bedrock_core" +name = "bedrock-asm" version = "4.0.0" -source = "git+git://benbridle.com/bedrock_core?tag=v4.0.0#f5986d0a37dd8c2526432e5e5e3af7083840447d" +source = "git+git://benbridle.com/bedrock_asm?tag=v4.0.0#db2c08220e95729c1880953d00aaded693dc43c7" +dependencies = [ + "vagabond", + "xflags", +] [[package]] -name = "bedrock_emu" -version = "0.5.0" +name = "bedrock-core" +version = "5.0.0" +source = "git+git://benbridle.com/bedrock_core?tag=v5.0.0#179bd6a13d91f0a1137ee8ed6aebb7e226e99b5d" + +[[package]] +name = "bedrock-pc" +version = "1.0.0-alpha1" dependencies = [ - "bedrock_core", + "bedrock-asm", + "bedrock-core", "geometry", - "mini_paste", "phosphor", "time", "windows", + "xflags", ] [[package]] @@ -63,39 +95,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - -[[package]] -name = "block" -version = "0.1.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys", -] +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block2" -version = "0.2.0-alpha.6" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "block-sys", - "objc2-encode", + "objc2", ] [[package]] name = "buffer" -version = "1.0.0" -source = "git+git://benbridle.com/buffer?tag=v1.0.0#2322c4c2b335ca398ec039503257e20d139a699f" +version = "1.1.0" +source = "git+git://benbridle.com/buffer?tag=v1.1.0#492cfb25742a2a6ca96671e6a9f3f3bfccf65b3e" dependencies = [ "colour", "geometry", @@ -124,21 +140,39 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] name = "calloop" -version = "0.10.6" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "log", - "nix 0.25.1", - "slotmap", + "polling", + "rustix", + "slab", "thiserror", - "vec_map", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", ] [[package]] @@ -152,6 +186,12 @@ dependencies = [ ] [[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -159,46 +199,35 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +name = "colour" +version = "2.0.0" +source = "git+git://benbridle.com/colour?tag=v2.0.0#0ae93bec309d37cda72e137da83127bb48b22d65" dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics 0.23.1", - "foreign-types 0.5.0", - "libc", - "objc", + "proportion", ] [[package]] -name = "cocoa-foundation" -version = "0.1.2" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "bytes", + "memchr", ] [[package]] -name = "colour" -version = "1.0.0" -source = "git+git://benbridle.com/colour?tag=v1.0.0#8875d9d6dd48400a9ca1cf8c64889d860700d40c" +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "proportion", + "crossbeam-utils", ] [[package]] @@ -212,34 +241,44 @@ dependencies = [ ] [[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types 0.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", "libc", ] [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types 0.5.0", + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", "libc", ] @@ -250,21 +289,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] [[package]] +name = "core-graphics-types" +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", + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] name = "ctor" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.42", + "syn", ] [[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] name = "deranged" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -285,7 +347,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading", ] [[package]] @@ -295,26 +357,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + +[[package]] name = "drm" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb1b703ffbc7ebd216eba7900008049a56ace55580ecb2ee7fa801e8d8be87" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "bytemuck", "drm-ffi", "drm-fourcc", - "nix 0.27.1", + "rustix", ] [[package]] name = "drm-ffi" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7d1c19c4b6270e89d59fb27dc6d02a317c658a8a54e54781e1db9b5947595d" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" dependencies = [ "drm-sys", - "nix 0.27.1", + "rustix", ] [[package]] @@ -325,9 +393,13 @@ checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "drm-sys" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f1c0468062a56cd5705f1e3b5409eb286d5596a2028ec8e947595d7e715ae" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] [[package]] name = "equivalent" @@ -346,6 +418,11 @@ dependencies = [ ] [[package]] +name = "event_queue" +version = "1.1.0" +source = "git+git://benbridle.com/event_queue?tag=v1.1.0#b3dfdf2edf96d267c050e1c6521372479b5884c8" + +[[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -353,21 +430,12 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -378,17 +446,11 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" @@ -400,12 +462,23 @@ source = "git+git://benbridle.com/geometry?tag=v1.0.0#8547ad9c2f2867da9b0e655dd4 [[package]] name = "gethostname" -version = "0.3.0" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ + "cfg-if", "libc", - "winapi", + "wasi", ] [[package]] @@ -415,6 +488,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -425,15 +504,19 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ + "cesu8", "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -461,26 +544,10 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] name = "libc" -version = "0.2.151" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" @@ -498,31 +565,28 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "libc", "redox_syscall 0.4.1", ] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "log" -version = "0.4.20" +name = "linux-raw-sys" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" [[package]] -name = "malloc_buf" -version = "0.0.6" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -532,228 +596,274 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" dependencies = [ "libc", ] [[package]] -name = "memmap2" -version = "0.9.3" +name = "ndk" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "libc", + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", ] [[package]] -name = "memoffset" -version = "0.6.5" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "memoffset" -version = "0.7.1" +name = "ndk-sys" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "autocfg", + "jni-sys", ] [[package]] -name = "mini_paste" -version = "0.1.11" +name = "num_enum" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2499b7bd9834270bf24cfc4dd96be59020ba6fd7f3276b772aee2de66e82b63" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "mini_paste-proc_macro", + "num_enum_derive", ] [[package]] -name = "mini_paste-proc_macro" -version = "0.1.11" +name = "num_enum_derive" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c5f1f52e39b728e73af4b454f1b29173d4544607bd395dafe1918fd149db67" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "mio" -version = "0.8.10" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", - "log", - "wasi", - "windows-sys 0.48.0", ] [[package]] -name = "ndk" -version = "0.7.0" +name = "objc-sys" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum 0.5.11", - "raw-window-handle", - "thiserror", -] +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] -name = "ndk-context" -version = "0.1.1" +name = "objc2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] [[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" +name = "objc2-app-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "jni-sys", + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", ] [[package]] -name = "nix" -version = "0.24.3" +name = "objc2-cloud-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", ] [[package]] -name = "nix" -version = "0.25.1" +name = "objc2-contacts" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", + "block2", + "objc2", + "objc2-foundation", ] [[package]] -name = "nix" -version = "0.26.4" +name = "objc2-core-data" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", ] [[package]] -name = "nix" -version = "0.27.1" +name = "objc2-core-image" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "libc", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", ] [[package]] -name = "num_enum" -version = "0.5.11" +name = "objc2-core-location" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "num_enum_derive 0.5.11", + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", ] [[package]] -name = "num_enum" -version = "0.6.1" +name = "objc2-encode" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "num_enum_derive 0.6.1", + "bitflags 2.6.0", + "block2", + "dispatch", + "libc", + "objc2", ] [[package]] -name = "num_enum_derive" -version = "0.5.11" +name = "objc2-link-presentation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] -name = "num_enum_derive" -version = "0.6.1" +name = "objc2-metal" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.42", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "objc2-quartz-core" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "libc", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "malloc_buf", + "objc2", + "objc2-foundation", ] [[package]] -name = "objc-sys" -version = "0.2.0-beta.2" +name = "objc2-ui-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] [[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +name = "objc2-uniform-type-identifiers" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", - "objc-sys", - "objc2-encode", + "objc2", + "objc2-foundation", ] [[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" +name = "objc2-user-notifications" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "objc-sys", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", ] [[package]] @@ -779,16 +889,37 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phosphor" -version = "2.0.0" -source = "git+git://benbridle.com/phosphor?tag=v2.0.0#d598b9b0b404b386c460a09933bcfce4683b8f7a" +version = "3.0.0" +source = "git+git://benbridle.com/phosphor?tag=v3.0.0#47a754a7beb8741bfe0469236036cb8023043cd8" dependencies = [ "buffer", + "event_queue", "geometry", "softbuffer", "winit", ] [[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -801,6 +932,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -832,9 +978,9 @@ source = "git+git://benbridle.com/proportion?tag=v1.0.0#291bb4de2a2e5940fbebd8d4 [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] @@ -850,42 +996,51 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -908,16 +1063,16 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] -name = "slotmap" -version = "1.0.7" +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "version_check", + "autocfg", ] [[package]] @@ -928,67 +1083,72 @@ checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smithay-client-toolkit" -version = "0.16.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "calloop", - "dlib", - "lazy_static", + "calloop-wayland-source", + "cursor-icon", + "libc", "log", - "memmap2 0.5.10", - "nix 0.24.3", - "pkg-config", - "wayland-client 0.29.5", + "memmap2", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", ] [[package]] name = "softbuffer" -version = "0.3.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826da2ead8e85d1b4ea579fae3d58ec10c81a77d61deab8918c4a4f7514b2948" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "as-raw-xcb-connection", "bytemuck", "cfg_aliases", - "cocoa", - "core-graphics 0.23.1", + "core-graphics 0.24.0", "drm", "fastrand", - "foreign-types 0.5.0", + "foreign-types", "js-sys", "log", - "memmap2 0.9.3", - "objc", + "memmap2", + "objc2", + "objc2-foundation", + "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.4.1", + "redox_syscall 0.5.7", "rustix", "tiny-xlib", "wasm-bindgen", "wayland-backend", - "wayland-client 0.31.1", - "wayland-sys 0.31.1", + "wayland-client", + "wayland-sys", "web-sys", - "windows-sys 0.48.0", + "windows-sys 0.59.0", "x11rb", ] [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" @@ -1015,7 +1175,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -1046,7 +1206,7 @@ checksum = "d4098d49269baa034a8d1eae9bd63e9fa532148d772121dace3bcd6a6c98eb6d" dependencies = [ "as-raw-xcb-connection", "ctor", - "libloading 0.8.1", + "libloading", "tracing", ] @@ -1090,10 +1250,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "vec_map" -version = "0.8.2" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "vagabond" +version = "1.0.1" +source = "git+git://benbridle.com/vagabond?tag=v1.0.1#08f3153fea62ea81a42438347eeee058f5bec199" [[package]] name = "version_check" @@ -1102,6 +1267,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1128,11 +1303,23 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.42", + "syn", "wasm-bindgen-shared", ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1150,7 +1337,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1163,97 +1350,95 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wayland-backend" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "nix 0.26.4", + "rustix", "scoped-tls", "smallvec", - "wayland-sys 0.31.1", + "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.29.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ - "bitflags 1.3.2", - "downcast-rs", - "libc", - "nix 0.24.3", - "scoped-tls", - "wayland-commons", - "wayland-scanner 0.29.5", - "wayland-sys 0.29.5", + "bitflags 2.6.0", + "rustix", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "wayland-client" -version = "0.31.1" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.1", - "nix 0.26.4", + "bitflags 2.6.0", + "cursor-icon", "wayland-backend", - "wayland-scanner 0.31.0", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-cursor" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys 0.29.5", + "rustix", + "wayland-client", + "xcursor", ] [[package]] -name = "wayland-cursor" -version = "0.29.5" +name = "wayland-protocols" +version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ - "nix 0.24.3", - "wayland-client 0.29.5", - "xcursor", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "wayland-protocols" -version = "0.29.5" +name = "wayland-protocols-plasma" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" dependencies = [ - "bitflags 1.3.2", - "wayland-client 0.29.5", - "wayland-commons", - "wayland-scanner 0.29.5", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "wayland-scanner" -version = "0.29.5" +name = "wayland-protocols-wlr" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ - "proc-macro2", - "quote", - "xml-rs", + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.31.0" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", "quick-xml", @@ -1262,19 +1447,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" -dependencies = [ - "dlib", - "pkg-config", -] - -[[package]] -name = "wayland-sys" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -1293,37 +1468,25 @@ dependencies = [ ] [[package]] -name = "winapi" -version = "0.3.9" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "js-sys", + "wasm-bindgen", ] [[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-wsapoll" -version = "0.1.1" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[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" @@ -1354,7 +1517,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -1365,7 +1528,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -1415,6 +1578,15 @@ dependencies = [ ] [[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1594,36 +1766,53 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.28.7" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.6.0", + "block2", + "bytemuck", + "calloop", "cfg_aliases", - "core-foundation", - "core-graphics 0.22.3", - "dispatch", - "instant", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.1", + "cursor-icon", + "dpi", + "js-sys", "libc", - "log", - "mio", + "memmap2", "ndk", "objc2", - "once_cell", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", + "rustix", "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", "wasm-bindgen", - "wayland-client 0.29.5", - "wayland-commons", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", "wayland-protocols", - "wayland-scanner 0.29.5", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.52.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] @@ -1648,29 +1837,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.7.4", - "nix 0.26.4", + "libloading", "once_cell", - "winapi", - "winapi-wsapoll", + "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix 0.26.4", -] +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" @@ -1679,7 +1863,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" [[package]] -name = "xml-rs" -version = "0.8.19" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d18ac1a136311770ed587356f8a828c9b86261f68761f34e6cdc6d5b4c435c" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.6.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] @@ -1,24 +1,22 @@ [package] -name = "bedrock_emu" -version = "0.5.0" +name = "bedrock-pc" +version = "1.0.0-alpha1" authors = ["Ben Bridle"] edition = "2021" description = "Emulator for running Bedrock programs" - [dependencies] -bedrock_core = { git = "git://benbridle.com/bedrock_core", tag = "v4.0.0" } -phosphor = { git = "git://benbridle.com/phosphor", tag = "v2.0.0" } +bedrock-asm = { git = "git://benbridle.com/bedrock_asm", tag = "v4.0.0" } +bedrock-core = { git = "git://benbridle.com/bedrock_core", tag = "v5.0.0" } +phosphor = { git = "git://benbridle.com/phosphor", tag = "v3.0.0" } geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" } -mini_paste = "0.1.11" time = { version = "0.3.30", features = [ "local-offset" ] } - +xflags = "0.4.0-pre.1" [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = ["Win32_Storage_FileSystem"] } - [profile.release] lto=true opt-level="s" diff --git a/src/bin/br.rs b/src/bin/br.rs new file mode 100644 index 0000000..b4cf19d --- /dev/null +++ b/src/bin/br.rs @@ -0,0 +1,448 @@ +use bedrock_asm::*; +use bedrock_pc::*; +use phosphor::*; + +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + + +static mut VERBOSE: bool = false; + +macro_rules! verbose { + ($($tokens:tt)*) => { if unsafe { VERBOSE } { + eprint!("[INFO] "); eprintln!($($tokens)*); + } }; +} +macro_rules! error { + ($($tokens:tt)*) => {{ + eprint!("[ERROR] "); eprintln!($($tokens)*); std::process::exit(1); + }}; +} + + +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) { + if args.debug { + unsafe { VERBOSE = true; } + } + + let bytecode = load_bytecode(args.program.as_ref().map(|p| p.as_path())); + let metadata = parse_metadata(&bytecode); + if metadata.is_none() { + verbose!("Could not read program metadata"); + } + // TODO: Load and parse symbols file into debug state, use nearest symbol + // path when debugging. + + let config = EmulatorConfig { + dimensions: args.dimensions().unwrap_or(ScreenDimensions::ZERO), + fullscreen: args.fullscreen, + scale: args.scale(), + debug_palette: args.palette(), + initial_transmission: None, + encode_stdin: args.encode_stdin, + encode_stdout: args.encode_stdout, + }; + let phosphor = Phosphor::new(); + + if phosphor.is_ok() && args.dimensions().is_some() { + verbose!("Starting graphical emulator"); + let mut phosphor = phosphor.unwrap(); + let mut graphical = GraphicalEmulator::new(&config, args.debug); + 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), + icon: None, + cursor: 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); + headless.load_program(&bytecode); + headless.run(args.debug); + }; + + std::process::exit(0); +} + +fn load_bytecode(path: Option<&Path>) -> Vec<u8> { + // TODO: Etch file location into bytecode. + if let Some(path) = path { + if let Ok(bytecode) = load_bytecode_from_file(path) { + verbose!("Loaded program from {path:?} ({} bytes)", bytecode.len()); + return bytecode; + } else if let Some((bytecode, path)) = load_bytecode_from_bedrock_path(path) { + verbose!("Loaded program from {path:?} ({} bytes)", bytecode.len()); + return bytecode; + } else { + eprintln!("Could not read program from {path:?}, exiting"); + std::process::exit(1); + } + } else { + verbose!("Reading program from standard input..."); + if let Ok(bytecode) = load_bytecode_from_stdin() { + verbose!("Loaded program from standard input ({} bytes)", bytecode.len()); + return bytecode; + } else { + eprintln!("Could not read program from standard input, exiting"); + std::process::exit(1); + } + } +} + +/// Attempt to load bytecode from a file path. +fn load_bytecode_from_file(path: &Path) -> Result<Vec<u8>, std::io::Error> { + load_bytecode_from_readable_source(std::fs::File::open(path)?) +} + +/// Attempt to load bytecode from a directory in the BEDROCK_PATH environment variable. +fn load_bytecode_from_bedrock_path(path: &Path) -> Option<(Vec<u8>, PathBuf)> { + 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, base_path)); + } + 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, base_path)); + } + } + } + return None; +} + +/// Attempt to load bytecode from standard input. +fn load_bytecode_from_stdin() -> Result<Vec<u8>, std::io::Error> { + load_bytecode_from_readable_source(std::io::stdin()) +} + +/// Attempt to load bytecode from a source that implements std::io::Read. +fn load_bytecode_from_readable_source(source: impl Read) -> Result<Vec<u8>, std::io::Error> { + let mut bytecode = Vec::<u8>::new(); + source.take(65536).read_to_end(&mut bytecode)?; + return Ok(bytecode); +} + + +fn main_asm(args: Asm) { + // ----------------------------------------------------------------------- + // RESOLVE syntactic symbols + let ext = args.ext.unwrap_or(String::from("brc")); + let mut resolver = if let Some(path) = &args.source { + 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:?}") + } + } + } 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) { + eprintln!("Could not read from standard input, exiting."); + eprintln!("({err:?})"); + std::process::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) = &args.source { + 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 = resolver.get_merged_source_code(); + 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); + // let symbols = generate_symbols_file(&semantic_tokens); + + 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) { + eprintln!("Could not write to path {:?}, exiting.", path.as_ref()); + eprintln!("({err:?})"); + std::process::exit(1); + } + } else { + if let Err(err) = std::io::stdout().write_all(bytes) { + eprintln!("Could not write to standard output, exiting."); + eprintln!("({err:?})"); + std::process::exit(1); + } + } + std::process::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 + /// Encode standard input + optional -i, --encode-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 + } + } +} + + +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, + }; + 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/debug.rs b/src/debug.rs new file mode 100644 index 0000000..d19dbec --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,54 @@ +use bedrock_core::*; + +use std::time::Instant; + +macro_rules! yellow { () => { eprint!("\x1b[33m") };} +macro_rules! normal { () => { eprint!("\x1b[0m" ) };} + + +pub struct DebugState { + pub enabled: bool, + last_cycle: usize, + last_mark: Instant, +} + +impl DebugState { + pub fn new(enabled: bool) -> Self { + Self { + enabled, + last_cycle: 0, + last_mark: Instant::now(), + } + } + + pub fn print(&self, string: &str) { + if self.enabled { + println!("{}", string); + } + } + + pub fn debug_summary(&mut self, core: &BedrockCore) { + eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", + core.mem.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); + + self.last_cycle = core.cycle; + self.last_mark = Instant::now(); + } +} + + +fn debug_stack(stack: &Stack, len: usize) { + for i in 0..len { + if i == stack.sp as usize { yellow!(); } + eprint!("{:02x} ", stack.mem[i]); + } + normal!(); + eprintln!(); +} diff --git a/src/devices.rs b/src/devices.rs index 1d69cc7..2221152 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -1,366 +1,20 @@ -mod system; -mod memory; -mod math; -mod clock; -mod input; -mod screen; -mod stream; -mod file; +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; -use bedrock_core::*; -pub use system::ReadOnlyTextBuffer; -pub use screen::{ScreenDimensions, ScreenPosition}; - -const LEN_PROGRAM_MEMORY: u16 = 0xffff; -const LEN_WORKING_STACK: u8 = 0xff; -const LEN_RETURN_STACK: u8 = 0xff; -const CONNECTED_DEVICES: u16 = 0b_1111_1100_1100_0000; - - -pub struct StandardDevices { - pub memory: memory::MemoryDevice, - pub math: math::MathDevice, - pub clock: clock::ClockDevice, - pub input: input::InputDevice, - pub screen: screen::ScreenDevice, - pub stream: stream::StreamDevice, - pub file: file::FileDevice, - - pub name: ReadOnlyTextBuffer, - - pub wake_mask: u16, - pub wake_id: u8, -} - -impl StandardDevices { - pub fn new() -> Self { - Self { - memory: memory::MemoryDevice::new(), - math: math::MathDevice::new(), - clock: clock::ClockDevice::new(), - input: input::InputDevice::new(), - screen: screen::ScreenDevice::new(), - stream: stream::StreamDevice::new(), - file: file::FileDevice::new(), - - name: ReadOnlyTextBuffer::from_text("Bedrock for PC"), - - wake_mask: 0x0000, - wake_id: 0x00, - } - } - - pub fn can_wake(&mut self) -> bool { - macro_rules! test_wake { - ($flag:expr, $id:expr, $mask:expr) => { - if $flag && self.wake_mask & $mask != 0 { - $flag = false; self.wake_id = $id; return true } - }; - } - self.clock.update_timers(); - // The order here is important. If clock comes first, a fast - // enough recurring timer could block out all other events. - // It might be preferable to implement a queue system, so that - // the flags are tested in reverse order to how recently - // they were matched. - test_wake!(self.input.wake_flag, 0x4, 0x0800); - test_wake!(self.screen.wake_flag, 0x5, 0x0400); - test_wake!(self.stream.wake_flag, 0x8, 0x0080); - test_wake!(self.clock.wake_flag, 0x3, 0x1000); - return false; - } -} - -impl DeviceBus for StandardDevices { - fn read_u8(&mut self, port: u8) -> u8 { - macro_rules! read_hh { ($v:expr) => { ($v>>24) as u8 }; } - macro_rules! read_hl { ($v:expr) => { ($v>>16) as u8 }; } - macro_rules! read_lh { ($v:expr) => { ($v>>8) as u8 }; } - macro_rules! read_ll { ($v:expr) => { $v as u8 }; } - macro_rules! read_h { ($v:expr) => { ($v>>8) as u8 }; } - macro_rules! read_l { ($v:expr) => { $v as u8 }; } - macro_rules! read_b { ($b:expr) => { if $b { 0xff } else { 0x00 } }; } - macro_rules! no_read { () => { 0x00 }; } - - match port { - // System - 0x00 => self.name.read_byte(), - 0x01 => self.wake_id, - 0x02 => no_read!(), - 0x03 => no_read!(), - 0x04 => 0x00, - 0x05 => 0x00, - 0x06 => 0x00, - 0x07 => 0x00, - 0x08 => read_h!(LEN_PROGRAM_MEMORY), - 0x09 => read_l!(LEN_PROGRAM_MEMORY), - 0x0A => LEN_WORKING_STACK, - 0x0B => LEN_RETURN_STACK, - 0x0C => read_h!(CONNECTED_DEVICES), - 0x0D => read_l!(CONNECTED_DEVICES), - 0x0E => no_read!(), - 0x0F => no_read!(), - // Memory - 0x10 => read_h!(self.memory.page_1), - 0x11 => read_l!(self.memory.page_1), - 0x12 => read_h!(self.memory.address_1), - 0x13 => read_l!(self.memory.address_1), - 0x14 => self.memory.read_from_head_1(), - 0x15 => self.memory.read_from_head_1(), - 0x16 => read_h!(self.memory.page_limit), - 0x17 => read_l!(self.memory.page_limit), - 0x18 => read_h!(self.memory.page_2), - 0x19 => read_l!(self.memory.page_2), - 0x1A => read_h!(self.memory.address_2), - 0x1B => read_l!(self.memory.address_2), - 0x1C => self.memory.read_from_head_2(), - 0x1D => self.memory.read_from_head_2(), - 0x1E => no_read!(), - 0x1F => no_read!(), - // Math - 0x20 => no_read!(), - 0x21 => no_read!(), - 0x22 => no_read!(), - 0x23 => no_read!(), - 0x24 => no_read!(), - 0x25 => no_read!(), - 0x26 => no_read!(), - 0x27 => no_read!(), - 0x28 => read_h!(self.math.multiply_high()), - 0x29 => read_l!(self.math.multiply_high()), - 0x2A => read_h!(self.math.multiply_low()), - 0x2B => read_l!(self.math.multiply_low()), - 0x2C => read_h!(self.math.divide()), - 0x2D => read_l!(self.math.divide()), - 0x2E => read_h!(self.math.modulo()), - 0x2F => read_l!(self.math.modulo()), - // Clock - 0x30 => self.clock.year(), - 0x31 => self.clock.month(), - 0x32 => self.clock.day(), - 0x33 => self.clock.hour(), - 0x34 => self.clock.minute(), - 0x35 => self.clock.second(), - 0x36 => read_h!(self.clock.update_uptime()), - 0x37 => read_l!(self.clock.uptime), - 0x38 => read_h!(self.clock.update_timer_1()), - 0x39 => read_l!(self.clock.timer_1), - 0x3A => read_h!(self.clock.update_timer_2()), - 0x3B => read_l!(self.clock.timer_2), - 0x3C => read_h!(self.clock.update_timer_3()), - 0x3D => read_l!(self.clock.timer_3), - 0x3E => read_h!(self.clock.update_timer_4()), - 0x3F => read_l!(self.clock.timer_4), - // Input - 0x40 => read_h!(self.input.pointer_position.x), - 0x41 => read_l!(self.input.pointer_position.x), - 0x42 => read_h!(self.input.pointer_position.y), - 0x43 => read_l!(self.input.pointer_position.y), - 0x44 => read_b!(self.input.pointer_active), - 0x45 => self.input.pointer_buttons, - 0x46 => self.input.read_horizontal_scroll(), - 0x47 => self.input.read_vertical_scroll(), - 0x48 => 0xff, - 0x49 => self.input.text_queue.pop_front().unwrap_or(0), - 0x4A => self.input.modifiers, - 0x4B => self.input.navigation, - 0x4C => self.input.controller_1, - 0x4D => self.input.controller_2, - 0x4E => self.input.controller_3, - 0x4F => self.input.controller_4, - // Screen - 0x50 => read_h!(self.screen.cursor.x), - 0x51 => read_l!(self.screen.cursor.x), - 0x52 => read_h!(self.screen.cursor.y), - 0x53 => read_l!(self.screen.cursor.y), - 0x54 => read_h!(self.screen.dimensions.width), - 0x55 => read_l!(self.screen.dimensions.width), - 0x56 => read_h!(self.screen.dimensions.height), - 0x57 => read_l!(self.screen.dimensions.height), - 0x58 => no_read!(), - 0x59 => no_read!(), - 0x5A => no_read!(), - 0x5B => no_read!(), - 0x5C => no_read!(), - 0x5D => no_read!(), - 0x5E => no_read!(), - 0x5F => no_read!(), - // Stream - // 0x80 => todo!(), - // 0x81 => todo!(), - // 0x82 => todo!(), - // 0x83 => todo!(), - // 0x84 => todo!(), - // 0x85 => todo!(), - // 0x86 => todo!(), - // 0x87 => todo!(), - // 0x88 => todo!(), - // 0x89 => todo!(), - // 0x8A => todo!(), - // 0x8B => todo!(), - // 0x8C => todo!(), - // 0x8D => todo!(), - // 0x8E => todo!(), - // 0x8F => todo!(), - // File - 0x90 => read_b!(self.file.entry.is_some()), - 0x91 => read_b!(self.file.success), - 0x92 => self.file.name_buffer.read_byte(), - 0x93 => read_b!(self.file.entry_type()), - 0x94 => self.file.read_byte(), - 0x95 => self.file.read_byte(), - 0x96 => self.file.read_child_name(), - 0x97 => read_b!(self.file.child_type()), - 0x98 => read_hh!(self.file.pointer()), - 0x99 => read_hl!(self.file.pointer()), - 0x9A => read_lh!(self.file.pointer()), - 0x9B => read_ll!(self.file.pointer()), - 0x9C => read_hh!(self.file.length()), - 0x9D => read_hl!(self.file.length()), - 0x9E => read_lh!(self.file.length()), - 0x9F => read_ll!(self.file.length()), - - _ => unimplemented!("Reading from device port 0x{port:02x}"), - } - } - - fn write_u8(&mut self, val: u8, port: u8) -> Option<Signal> { - macro_rules! write_hh { ($v:expr) => { $v = $v & 0x00ffffff | ((val as u32) << 24) }; } - macro_rules! write_hl { ($v:expr) => { $v = $v & 0xff00ffff | ((val as u32) << 16) }; } - macro_rules! write_lh { ($v:expr) => { $v = $v & 0x00ff | ((val as u32) << 8) }; } - macro_rules! write_ll { ($v:expr) => { $v = $v & 0xff00 | (val as u32) }; } - macro_rules! write_h { ($v:expr) => { $v = $v & 0x00ff | ((val as u16) << 8) }; } - macro_rules! write_l { ($v:expr) => { $v = $v & 0xff00 | (val as u16) }; } - macro_rules! no_write { () => { () }; } - - match port { - // System - 0x00 => self.name.reset_pointer(), - 0x01 => no_write!(), - 0x02 => write_h!(self.wake_mask), - 0x03 => { write_l!(self.wake_mask); return Some(Signal::Sleep) }, - 0x04 => no_write!(), - 0x05 => no_write!(), - 0x06 => no_write!(), - 0x07 => no_write!(), - 0x08 => no_write!(), - 0x09 => no_write!(), - 0x0A => no_write!(), - 0x0B => no_write!(), - 0x0C => no_write!(), - 0x0D => no_write!(), - 0x0E => no_write!(), - 0x0F => no_write!(), - // Memory - 0x10 => write_h!(self.memory.page_1), - 0x11 => write_l!(self.memory.page_1), - 0x12 => write_h!(self.memory.address_1), - 0x13 => write_l!(self.memory.address_1), - 0x14 => self.memory.write_to_head_1(val), - 0x15 => self.memory.write_to_head_1(val), - 0x16 => no_write!(), - 0x17 => no_write!(), - 0x18 => write_h!(self.memory.page_2), - 0x19 => write_l!(self.memory.page_2), - 0x1A => write_h!(self.memory.address_2), - 0x1B => write_l!(self.memory.address_2), - 0x1C => self.memory.write_to_head_2(val), - 0x1D => self.memory.write_to_head_2(val), - 0x1E => write_h!(self.memory.copy_length), - 0x1F => { write_l!(self.memory.copy_length); self.memory.copy() }, - // Math - 0x20 => write_h!(self.math.operand_1), - 0x21 => write_l!(self.math.operand_1), - 0x22 => write_h!(self.math.operand_2), - 0x23 => write_l!(self.math.operand_2), - 0x24 => no_write!(), - 0x25 => no_write!(), - 0x26 => no_write!(), - 0x27 => no_write!(), - 0x28 => no_write!(), - 0x29 => no_write!(), - 0x2A => no_write!(), - 0x2B => no_write!(), - 0x2C => no_write!(), - 0x2D => no_write!(), - 0x2E => no_write!(), - 0x2F => no_write!(), - // Clock - 0x30 => no_write!(), - 0x31 => no_write!(), - 0x32 => no_write!(), - 0x33 => no_write!(), - 0x34 => no_write!(), - 0x35 => no_write!(), - 0x36 => no_write!(), - 0x37 => no_write!(), - 0x38 => write_h!(self.clock.timer_1), - 0x39 => { write_l!(self.clock.timer_1); self.clock.set_timer_1() }, - 0x3A => write_h!(self.clock.timer_2), - 0x3B => { write_l!(self.clock.timer_2); self.clock.set_timer_2() }, - 0x3C => write_h!(self.clock.timer_3), - 0x3D => { write_l!(self.clock.timer_3); self.clock.set_timer_3() }, - 0x3E => write_h!(self.clock.timer_4), - 0x3F => { write_l!(self.clock.timer_4); self.clock.set_timer_4() }, - // Input - 0x40 => no_write!(), - 0x41 => no_write!(), - 0x42 => no_write!(), - 0x43 => no_write!(), - 0x44 => no_write!(), - 0x45 => no_write!(), - 0x46 => no_write!(), - 0x47 => no_write!(), - 0x48 => no_write!(), - 0x49 => self.input.text_queue.clear(), - 0x4A => no_write!(), - 0x4B => no_write!(), - 0x4C => no_write!(), - 0x4D => no_write!(), - 0x4E => no_write!(), - 0x4F => no_write!(), - // Screen - 0x50 => write_h!(self.screen.cursor.x), - 0x51 => write_l!(self.screen.cursor.x), - 0x52 => write_h!(self.screen.cursor.y), - 0x53 => write_l!(self.screen.cursor.y), - 0x54 => write_h!(self.screen.dimensions.width), - 0x55 => { write_l!(self.screen.dimensions.width); self.screen.set_size() }, - 0x56 => write_h!(self.screen.dimensions.height), - 0x57 => { write_l!(self.screen.dimensions.height); self.screen.set_size() }, - 0x58 => self.screen.set_palette_high(val), - 0x59 => self.screen.set_palette_low(val), - 0x5A => self.screen.sprite_buffer.set_colour_high(val), - 0x5B => self.screen.sprite_buffer.set_colour_low(val), - 0x5C => self.screen.sprite_buffer.push(val), - 0x5D => self.screen.sprite_buffer.push(val), - 0x5E => self.screen.draw(val), - 0x5F => self.screen.shunt(val), - // Stream - 0x86 => self.stream.write_stdout(val), - 0x87 => self.stream.write_stdout(val), - // File - 0x90 => self.file.write_to_open_port(val), - 0x91 => self.file.write_to_move_port(val), - 0x92 => self.file.set_name_pointer(val), - 0x93 => self.file.ascend_to_parent(), - 0x94 => self.file.write_byte(val), - 0x95 => self.file.write_byte(val), - 0x96 => self.file.set_child_name_pointer(val), - 0x97 => self.file.descend_to_child(), - 0x98 => write_hh!(self.file.pointer), - 0x99 => write_hl!(self.file.pointer), - 0x9A => write_lh!(self.file.pointer), - 0x9B => { write_ll!(self.file.pointer); self.file.commit_pointer() }, - 0x9C => write_hh!(self.file.length), - 0x9D => write_hl!(self.file.length), - 0x9E => write_lh!(self.file.length), - 0x9F => { write_ll!(self.file.length); self.file.commit_length() }, - - _ => unimplemented!("Writing to device port 0x{port:02x}"), - }; - - return None; - - } -} diff --git a/src/devices/clock.rs b/src/devices/clock.rs deleted file mode 100644 index 60b1cad..0000000 --- a/src/devices/clock.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::time::{Duration, Instant}; - -macro_rules! to_ticks { ($dur:expr) => {($dur.as_millis() / 4) as u16}; } -macro_rules! from_ticks { ($ticks:expr) => {Duration::from_millis(($ticks * 4).into())}; } -macro_rules! now { () => { - time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc())};} - -/// Create a method to set the instant of a timer. -macro_rules! generate_set_timer_method { - ($i:tt) => { mini_paste::item!{ - pub fn [< set_timer_ $i >] (&mut self) { - let ticks = &mut self. [< timer_ $i >]; - let instant = &mut self. [< timer_ $i _end >]; - - *instant = Instant::now() + from_ticks!(*ticks); - } - }}; -} - -/// Create a method to update the value of a timer. -macro_rules! generate_update_timer_method { - ($i:tt) => { mini_paste::item!{ - pub fn [< update_timer_ $i >] (&mut self) -> u16 { - let ticks = &mut self. [< timer_ $i >]; - let instant = &mut self. [< timer_ $i _end >]; - - if *ticks > 0 { - *ticks = to_ticks!(instant.duration_since(Instant::now())); - if *ticks == 0 { self.wake_flag = true; } - } - return *ticks; - } - }}; -} - -pub struct ClockDevice { - pub wake_flag: bool, - - pub program_start: Instant, - pub uptime: u16, - - pub timer_1_end: Instant, - pub timer_2_end: Instant, - pub timer_3_end: Instant, - pub timer_4_end: Instant, - pub timer_1: u16, - pub timer_2: u16, - pub timer_3: u16, - pub timer_4: u16, -} - -impl ClockDevice { - pub fn new() -> Self { - Self { - wake_flag: false, - - program_start: Instant::now(), - uptime: 0, - - timer_1_end: Instant::now(), - timer_2_end: Instant::now(), - timer_3_end: Instant::now(), - timer_4_end: Instant::now(), - timer_1: 0, - timer_2: 0, - timer_3: 0, - timer_4: 0, - } - } - - generate_set_timer_method!{1} - generate_set_timer_method!{2} - generate_set_timer_method!{3} - generate_set_timer_method!{4} - - generate_update_timer_method!{1} - generate_update_timer_method!{2} - generate_update_timer_method!{3} - generate_update_timer_method!{4} - - pub fn update_timers(&mut self) { - self.update_timer_1(); - self.update_timer_2(); - self.update_timer_3(); - self.update_timer_4(); - } - - pub fn update_uptime(&mut self) -> u16 { - self.uptime = to_ticks!(self.program_start.elapsed()); - return self.uptime; - } - - pub fn year(&self) -> u8 { - now!().year().saturating_sub(2000).try_into().unwrap_or(u8::MAX) - } - - pub fn month(&self) -> u8 { - now!().month() as u8 - 1 - } - - pub fn day(&self) -> u8 { - now!().day() as u8 - 1 - } - - pub fn hour(&self) -> u8 { - now!().hour() - } - - pub fn minute(&self) -> u8 { - now!().minute() - } - - pub fn second(&self) -> u8 { - now!().second() - } - - pub fn time_to_next_wake(&self) -> Option<Duration> { - [self.timer_1, self.timer_2, self.timer_3, self.timer_4] - .iter() - .filter(|t| **t > 0) - .min() - .and_then(|t| Some(from_ticks!(t))) - } -} diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs new file mode 100644 index 0000000..494e0c7 --- /dev/null +++ b/src/devices/clock_device.rs @@ -0,0 +1,185 @@ +use bedrock_core::*; + +use std::time::{Duration, Instant}; + + +macro_rules! now { () => { + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()) +}; } + +fn current_year() -> u8 { now!().year().saturating_sub(2000) as u8 } +fn current_month() -> u8 { now!().month() as u8 - 1 } +fn current_day() -> u8 { now!().day() as u8 - 1 } +fn current_hour() -> u8 { now!().hour() } +fn current_minute() -> u8 { now!().minute() } +fn current_second() -> u8 { now!().second() } + +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 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, +} + +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 { + match port { + 0x0 => current_year(), + 0x1 => current_month(), + 0x2 => current_day(), + 0x3 => current_hour(), + 0x4 => current_minute(), + 0x5 => current_second(), + 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), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 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() }, + _ => 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; + } + }; + } + 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.rs b/src/devices/file.rs deleted file mode 100644 index 0bea2f4..0000000 --- a/src/devices/file.rs +++ /dev/null @@ -1,288 +0,0 @@ -mod bedrock_file_path; -mod buffered_file; -mod circular_path_buffer; -mod directory_listing; -mod entry; -mod operations; - -pub use bedrock_file_path::*; -pub use buffered_file::*; -pub use circular_path_buffer::*; -pub use directory_listing::*; -pub use entry::*; -use operations::*; - -use std::path::{Component, Path, PathBuf}; -use std::mem::take; - - -pub struct FileDevice { - pub base_path: PathBuf, - pub default_path: PathBuf, - - pub open_buffer: CircularPathBuffer, - pub move_buffer: CircularPathBuffer, - pub name_buffer: CircularPathBuffer, - - pub entry: Option<(Entry, BedrockFilePath)>, - pub cached_dir: Option<(Entry, BedrockFilePath)>, - - pub success: bool, - pub pointer: u32, - pub length: u32, - - pub enable_read: bool, - pub enable_write: bool, - pub enable_create: bool, - pub enable_move: bool, - pub enable_delete: bool, -} - -impl FileDevice { - pub fn new() -> Self { - #[cfg(target_family = "unix")] - let default_base: PathBuf = PathBuf::from("/"); - #[cfg(target_family = "windows")] - let default_base: PathBuf = PathBuf::from(""); - - Self { - base_path: default_base, - default_path: match std::env::current_dir() { - Ok(dir) => PathBuf::from(dir), - Err(_) => PathBuf::from(""), - }, - - open_buffer: CircularPathBuffer::new(), - move_buffer: CircularPathBuffer::new(), - name_buffer: CircularPathBuffer::new(), - - entry: None, - cached_dir: None, - - success: false, - pointer: 0, - length: 0, - - enable_read: true, - enable_write: true, - enable_create: true, - enable_move: true, - enable_delete: false, - } - } - - /// Commit any pending writes to the currently-open file. - pub fn flush_entry(&mut self) { - if let Some((Entry::File(buffered_file), _)) = &mut self.entry { - buffered_file.flush(); - } - } - - /// Safely close the currently-open entry, cleaning up entry variables. - pub fn close_entry(&mut self) { - self.open_buffer.clear(); - self.move_buffer.clear(); - self.name_buffer.clear(); - self.flush_entry(); - self.pointer = 0; - self.length = 0; - if let Some((Entry::Directory(dir), path)) = take(&mut self.entry) { - self.cached_dir = Some((Entry::Directory(dir), path)); - } - } - - /// Process a byte received from the OPEN port. - pub fn write_to_open_port(&mut self, byte: u8) { - if let Some(buffer) = self.open_buffer.push_byte(byte) { - self.close_entry(); - if let Some(path) = BedrockFilePath::from_buffer(buffer, &self.base_path) { - self.success = self.open_entry(path).is_ok(); - } - } - } - - /// Opens the entry at the given path. - pub fn open_entry(&mut self, path: BedrockFilePath) -> Result<(), ()> { - match path.entry_type() { - Some(EntryType::File) => { - let open_result = std::fs::OpenOptions::new() - .read(self.enable_read) - .write(self.enable_write) - .open(path.as_path()); - // Keep the current entry open if we can't open the new path. - if let Ok(file) = open_result { - self.close_entry(); - self.name_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::File(BufferedFile::new(file)), path)); - return Ok(()); - }; - } - Some(EntryType::Directory) => { - // Attempt to use the cached directory. - if let Some((dir, cached_path)) = take(&mut self.cached_dir) { - if cached_path == path { - self.close_entry(); - self.name_buffer.populate(cached_path.as_buffer()); - self.entry = Some((dir, cached_path)); - return Ok(()); - } - } - // Keep the current entry open if we can't open the new path. - if let Some(listing) = DirectoryListing::from_path(&path) { - self.close_entry(); - self.name_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::Directory(listing), path)); - return Ok(()); - }; - } - // The entry either doesn't exist or is not a file or directory. - None => (), - } - return Err(()); - } - - pub fn write_to_move_port(&mut self, byte: u8) { - if let Some(buffer) = self.move_buffer.push_byte(byte) { - let blank_destination = buffer[0] == 0x00; - let destination = BedrockFilePath::from_buffer(buffer, &self.base_path); - self.success = false; - - if let Some((_, source)) = &self.entry { - if blank_destination { - if self.enable_delete { - self.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()); - } - } - } else if let Some(dest) = destination { - if self.enable_create { - self.success = create_file(&dest.as_path()); - } - } - - self.close_entry(); - } - } - - /// Attempt to open the parent directory of the current entry. - pub fn ascend_to_parent(&mut self) { - self.success = false; - if let Some((_, path)) = &self.entry { - if let Some(parent_path) = path.parent() { - self.success = self.open_entry(parent_path).is_ok(); - } - } else { - if let Some(default) = BedrockFilePath::from_path(&self.default_path, &self.base_path) { - self.success = self.open_entry(default).is_ok(); - } - } - } - - /// Attempt to open the currently-selected child of the current directory. - pub fn descend_to_child(&mut self) { - self.success = false; - if let Some((Entry::Directory(listing), _)) = &self.entry { - if let Some(child_path) = listing.child_path() { - self.success = self.open_entry(child_path).is_ok(); - }; - } - } - - pub fn set_name_pointer(&mut self, value: u8) { - self.name_buffer.set_pointer(value); - } - - /// Returns true if the currently-open entry is a directory. - pub fn entry_type(&self) -> bool { - match self.entry { - Some((Entry::Directory(_), _)) => true, - _ => false, - } - } - - /// Reads a byte from the name buffer of the currently-selected child. - pub fn read_child_name(&mut self) -> u8 { - if let Some((Entry::Directory(listing), _)) = &mut self.entry { - listing.child_name().read_byte() - } else { - 0 - } - } - - pub fn set_child_name_pointer(&mut self, byte: u8) { - if let Some((Entry::Directory(listing), _)) = &mut self.entry { - listing.child_name().set_pointer(byte); - } - } - - /// Returns true if the currently-selected child is a directory. - pub fn child_type(&self) -> bool { - if let Some((Entry::Directory(listing), _)) = &self.entry { - match listing.child_type() { - Some(EntryType::Directory) => true, - Some(EntryType::File) => false, - None => false, - } - } else { - false - } - } - - /// Reads a byte from the currently-open file. - pub fn read_byte(&mut self) -> u8 { - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.read_byte(), - _ => 0, - } - } - - /// Writes a byte to the currently-open file. - pub fn write_byte(&mut self, byte: u8) { - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.write_byte(byte), - _ => (), - } - } - - pub fn pointer(&mut self) -> u32 { - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.pointer(), - Some((Entry::Directory(listing), _)) => listing.selected(), - _ => 0, - } - } - - pub fn commit_pointer(&mut self) { - let pointer = take(&mut self.pointer); - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.set_pointer(pointer), - Some((Entry::Directory(listing), _)) => listing.set_selected(pointer), - _ => (), - } - } - - pub fn length(&mut self) -> u32 { - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.length(), - Some((Entry::Directory(listing), _)) => listing.length(), - _ => 0, - } - } - - pub fn commit_length(&mut self) { - let length = take(&mut self.length); - match &mut self.entry { - Some((Entry::File(buffered_file), _)) => buffered_file.set_length(length), - _ => (), - } - } -} - -impl Drop for FileDevice { - fn drop(&mut self) { - self.close_entry(); - } -} diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs new file mode 100644 index 0000000..4859053 --- /dev/null +++ b/src/devices/file_device.rs @@ -0,0 +1,335 @@ +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}; + + +pub struct FileDevice { + pub base_path: PathBuf, + pub default_path: PathBuf, + + pub entry_buffer: BedrockPathBuffer, + pub action_buffer: BedrockPathBuffer, + pub path_buffer: BedrockPathBuffer, + + pub entry: Option<(Entry, BedrockFilePath)>, + pub cached_dir: Option<(Entry, BedrockFilePath)>, + + pub success: bool, + pub pointer_write: u32, + pub length_write: u32, + + pub enable_read: bool, + pub enable_write: bool, + pub enable_create: bool, + pub enable_move: bool, + pub enable_delete: bool, +} + +impl FileDevice { + pub fn new() -> Self { + #[cfg(target_family = "unix")] + let default_base: PathBuf = PathBuf::from("/"); + #[cfg(target_family = "windows")] + let default_base: PathBuf = PathBuf::from(""); + + // 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(""), + }, + + entry_buffer: BedrockPathBuffer::new(), + action_buffer: BedrockPathBuffer::new(), + path_buffer: BedrockPathBuffer::new(), + + entry: None, + cached_dir: None, + + success: false, + pointer_write: 0, + length_write: 0, + + enable_read: true, + enable_write: true, + enable_create: true, + enable_move: true, + enable_delete: false, + } + } + + /// Safely close the current entry, cleaning up entry variables. + pub fn close(&mut self) { + self.entry_buffer.clear(); + self.action_buffer.clear(); + self.path_buffer.clear(); + self.flush(); + + if let Some((Entry::Directory(dir), path)) = std::mem::take(&mut self.entry) { + self.cached_dir = Some((Entry::Directory(dir), path)); + } + } + + /// Open the entry at the given Bedrock path. + pub fn open(&mut self, path: BedrockFilePath) -> Result<(), ()> { + match path.entry_type() { + Some(EntryType::File) => { + let open_result = std::fs::OpenOptions::new() + .read(self.enable_read) + .write(self.enable_write) + .open(path.as_path()); + // Keep the current entry open if we can't open the new path. + if let Ok(file) = open_result { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::File(BufferedFile::new(file)), path)); + 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 { + self.close(); + self.path_buffer.populate(cached_path.as_buffer()); + self.entry = Some((dir, cached_path)); + return Ok(()); + } + } + // Keep the current entry open if we can't open the new path. + if let Some(listing) = DirectoryListing::from_path(&path) { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::Directory(listing), path)); + return Ok(()); + }; + } + // The entry either doesn't exist or is not a file or directory. + None => (), + } + return Err(()); + } + + /// Process a byte received from the entry port. + 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, + }; + } + } + + /// Process a byte received from the action port. + pub fn write_to_action_port(&mut self, byte: u8) { + 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 destination_blank { + if self.enable_delete { + self.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()); + } + } + } else if let Some(dest) = destination { + if self.enable_create { + self.success = create_file(&dest.as_path()); + } + } + self.close(); + } + } + + /// 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, + }; + } else { + match BedrockFilePath::from_path(&self.default_path, &self.base_path) { + Some(default) => self.success = self.open(default).is_ok(), + None => self.success = false, + }; + } + } + + /// 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, + }; + } else { + self.success = false; + } + } + + /// Return true if the current entry is a directory. + pub fn entry_type(&self) -> bool { + match self.entry { + Some((Entry::Directory(_), _)) => true, + _ => false, + } + } + + /// 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(), + _ => 0, + } + } + + pub fn set_child_path(&mut self, byte: u8) { + if let Some((Entry::Directory(dir), _)) = &mut self.entry { + dir.child_path_buffer().set_pointer(byte); + } + } + + /// 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(EntryType::Directory) => true, + _ => false, + } + _ => false, + } + } + + /// Read a byte from the current file. + pub fn read_byte(&mut self) -> u8 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.read(), + _ => 0, + } + } + + /// 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), + _ => (), + } + } + + pub fn pointer(&mut self) -> u32 { + match &mut self.entry { + 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), + _ => (), + } + } + + pub fn length(&mut self) -> u32 { + match &mut self.entry { + 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), + _ => (), + } + } + + pub fn flush(&mut self) { + if let Some((Entry::File(buffered_file), _)) = &mut self.entry { + buffered_file.flush(); + } + } +} + +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!(), + } + } + + 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), + 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), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } +} diff --git a/src/devices/file/bedrock_file_path.rs b/src/devices/file_device/bedrock_file_path.rs index 30aa803..fdd8f79 100644 --- a/src/devices/file/bedrock_file_path.rs +++ b/src/devices/file_device/bedrock_file_path.rs @@ -6,7 +6,9 @@ use std::ffi::OsString; #[derive(Clone)] pub struct BedrockFilePath { + /// Sandbox directory base: PathBuf, + /// Path relative to sandbox directory relative: PathBuf, bytes: Vec<u8>, entry_type: Option<EntryType>, @@ -249,7 +251,7 @@ impl Ord for BedrockFilePath { } } -// Compare two ASCII byte-slices in case-agnostic alphabetic order. +/// Compare two ASCII byte-slices in case-agnostic alphabetic order. fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering { let l = std::cmp::min(left.len(), right.len()); let lhs = &left[..l]; @@ -267,10 +269,13 @@ fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering { left.len().cmp(&right.len()) } -// Remap ASCII values so that they sort in case-agnostic alphabetic order: -// !"#$%&'()*+,-./0123456789:;<=>? -// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo -// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_ +/// Remap ASCII values so that they sort in case-agnostic alphabetic order: +/// +/// ```text +/// !"#$%&'()*+,-./0123456789:;<=>? +/// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo +/// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_ +/// ``` fn remap_ascii(c: u8) -> u8 { if 0x40 <= c && c <= 0x5F { (c - 0x40) * 2 + 0x40 diff --git a/src/devices/file/circular_path_buffer.rs b/src/devices/file_device/bedrock_path_buffer.rs index 9d1dea6..d6a0861 100644 --- a/src/devices/file/circular_path_buffer.rs +++ b/src/devices/file_device/bedrock_path_buffer.rs @@ -1,9 +1,9 @@ -pub struct CircularPathBuffer { +pub struct BedrockPathBuffer { buffer: [u8; 256], pointer: u8, } -impl CircularPathBuffer { +impl BedrockPathBuffer { pub fn new() -> Self { Self { buffer: [0; 256] , pointer: 0 } } @@ -20,7 +20,7 @@ impl CircularPathBuffer { self.buffer = buffer; } - /// Move internal pointer to either the start of the path or the file name. + /// Move internal pointer to the start of the path or file name. /// /// If value is non-zero, the pointer will be moved to the byte /// directly following the final forward-slash. @@ -39,7 +39,7 @@ impl CircularPathBuffer { } /// Read a single byte from the buffer. - pub fn read_byte(&mut self) -> u8 { + pub fn read(&mut self) -> u8 { let pointer = self.pointer as usize; self.pointer = self.pointer.wrapping_add(1); self.buffer[pointer] @@ -48,12 +48,12 @@ impl CircularPathBuffer { /// Write a single byte to the buffer. /// /// If a null-byte is written, the buffer will be cleared and returned. - pub fn push_byte(&mut self, byte: u8) -> Option<[u8; 256]> { + pub fn write(&mut self, byte: u8) -> Option<[u8; 256]> { if byte == 0x00 { Some(self.clear()) } else { self.buffer[self.pointer as usize] = byte; - self.pointer = self.pointer.wrapping_add(1); + self.pointer = self.pointer.saturating_add(1); None } } diff --git a/src/devices/file/buffered_file.rs b/src/devices/file_device/buffered_file.rs index 73d3536..3487d54 100644 --- a/src/devices/file/buffered_file.rs +++ b/src/devices/file_device/buffered_file.rs @@ -3,6 +3,8 @@ use std::io::{BufReader, BufWriter}; use std::io::{Read, Write}; use std::io::{ErrorKind, Seek, SeekFrom}; +use crate::*; + pub struct BufferedFile { file: AccessMode, @@ -15,17 +17,11 @@ impl BufferedFile { } } - pub fn flush(&mut self) { - if let AccessMode::Write(writer) = &mut self.file { - writer.flush().unwrap(); - } - } - pub fn close(&mut self) { self.file = AccessMode::None; } - pub fn read_byte(&mut self) -> u8 { + pub fn read(&mut self) -> u8 { let mut buffer = [0u8; 1]; let read_result = match &mut self.file { @@ -46,12 +42,12 @@ impl BufferedFile { Ok(_) => buffer[0], Err(error) => match error.kind() { ErrorKind::UnexpectedEof => 0, - _ => panic!("{error:?}"), + _ => { error!("BufferedFile::read", "{error:?}"); 0 }, } } } - pub fn write_byte(&mut self, byte: u8) { + pub fn write(&mut self, byte: u8) { let mut buffer = [byte; 1]; let write_result = match &mut self.file { @@ -113,8 +109,19 @@ impl BufferedFile { AccessMode::None => unreachable!(), }; } + + pub fn flush(&mut self) { + if let AccessMode::Write(writer) = &mut self.file { + writer.flush().unwrap(); + } + } } +impl Drop for BufferedFile { + fn drop(&mut self) { + self.flush() + } +} enum AccessMode { Read(BufReader<File>), diff --git a/src/devices/file/directory_listing.rs b/src/devices/file_device/directory_listing.rs index febc5c2..1d7ddd2 100644 --- a/src/devices/file/directory_listing.rs +++ b/src/devices/file_device/directory_listing.rs @@ -5,7 +5,7 @@ pub struct DirectoryListing { children: Vec<BedrockFilePath>, length: u32, selected: Option<u32>, - name_buffer: CircularPathBuffer, + child_path_buffer: BedrockPathBuffer, } @@ -46,8 +46,8 @@ impl DirectoryListing { children.sort(); let length = u32::try_from(children.len()).ok()?; let selected = None; - let name_buffer = CircularPathBuffer::new(); - Some( Self { children, length, selected, name_buffer } ) + let child_path_buffer = BedrockPathBuffer::new(); + Some( Self { children, length, selected, child_path_buffer } ) } /// Generate entries for a virtual root directory. @@ -70,8 +70,8 @@ impl DirectoryListing { let length = children.len() as u32; let selected = None; - let name_buffer = CircularPathBuffer::new(); - Self { children, length, selected, name_buffer } + let child_path_buffer = BedrockPathBuffer::new(); + Self { children, length, selected, child_path_buffer } } /// Attempts to return a directory child by index. @@ -92,16 +92,16 @@ impl DirectoryListing { pub fn set_selected(&mut self, index: u32) { if let Some(child) = self.get(index) { let buffer = child.as_buffer(); - self.name_buffer.populate(buffer); + self.child_path_buffer.populate(buffer); self.selected = Some(index); } else { - self.name_buffer.clear(); + self.child_path_buffer.clear(); self.selected = None; } } - pub fn child_name(&mut self) -> &mut CircularPathBuffer { - &mut self.name_buffer + pub fn child_path_buffer(&mut self) -> &mut BedrockPathBuffer { + &mut self.child_path_buffer } pub fn child_type(&self) -> Option<EntryType> { diff --git a/src/devices/file/entry.rs b/src/devices/file_device/entry.rs index d604bb7..d604bb7 100644 --- a/src/devices/file/entry.rs +++ b/src/devices/file_device/entry.rs diff --git a/src/devices/file/operations.rs b/src/devices/file_device/operations.rs index 0593ac8..3a3f81b 100644 --- a/src/devices/file/operations.rs +++ b/src/devices/file_device/operations.rs @@ -2,9 +2,25 @@ use std::io::ErrorKind; use std::path::Path; -/// Returns true if an entry already exists at the given path. -pub fn entry_exists(source: &Path) -> bool { - std::fs::metadata(source).is_ok() +/// 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. @@ -25,23 +41,7 @@ pub fn delete_entry(source: &Path) -> bool { } } -/// 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() -} - -/// 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() - } +/// 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.rs b/src/devices/input.rs deleted file mode 100644 index 462f569..0000000 --- a/src/devices/input.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::*; -use phosphor::*; -use std::collections::VecDeque; - -const CONTROL: u8 = 0x80; -const ALT: u8 = 0x40; -const SHIFT: u8 = 0x20; - -const UP: u8 = 0x80; -const DOWN: u8 = 0x40; -const LEFT: u8 = 0x20; -const RIGHT: u8 = 0x10; -const CONFIRM: u8 = 0x08; -const CANCEL: u8 = 0x04; -const NEXT: u8 = 0x02; -const PREVIOUS: u8 = 0x01; - -macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } - - -pub struct InputDevice { - pub wake_flag: bool, - - pub pointer_position: ScreenPosition, - pub pointer_buttons: u8, - pub pointer_active: bool, - - pub horizontal_scroll: i8, - pub vertical_scroll: i8, - pub horizontal_scroll_delta: f64, - pub vertical_scroll_delta: f64, - - pub text_queue: VecDeque<u8>, - pub modifiers: u8, - pub navigation: u8, - - pub controller_1: u8, - pub controller_2: u8, - pub controller_3: u8, - pub controller_4: u8, -} - -impl InputDevice { - pub fn new() -> Self { - Self { - wake_flag: false, - - pointer_position: ScreenPosition::ZERO, - pointer_buttons: 0x00, - pointer_active: false, - - horizontal_scroll: 0x00, - vertical_scroll: 0x00, - horizontal_scroll_delta: 0.0, - vertical_scroll_delta: 0.0, - - text_queue: VecDeque::new(), - modifiers: 0x00, - navigation: 0x00, - - controller_1: 0x00, - controller_2: 0x00, - controller_3: 0x00, - controller_4: 0x00, - } - } - - pub fn read_horizontal_scroll(&mut self) -> u8 { - std::mem::take(&mut self.horizontal_scroll) as u8 - } - - pub fn read_vertical_scroll(&mut self) -> u8 { - std::mem::take(&mut self.vertical_scroll) as u8 - } - - pub fn on_pointer_button(&mut self, mask: u8, action: Action) { - let new_buttons = match action { - Action::Pressed => self.pointer_buttons | mask, - Action::Released => self.pointer_buttons & !mask, - }; - if new_buttons != self.pointer_buttons { - self.pointer_buttons = new_buttons; - self.wake_flag = true; - } - } - - pub fn on_pointer_move(&mut self, position: ScreenPosition) { - if position != self.pointer_position { - self.pointer_position = position; - self.wake_flag = true; - } - } - - pub fn on_scroll_horizontal(&mut self, delta: f64) { - self.horizontal_scroll_delta += delta; - while self.horizontal_scroll_delta >= 1.0 { - self.horizontal_scroll = self.horizontal_scroll.saturating_add(1); - self.horizontal_scroll_delta -= 1.0; - self.wake_flag = true; - } - while self.horizontal_scroll_delta <= -1.0 { - self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1); - self.horizontal_scroll_delta += 1.0; - self.wake_flag = true; - } - } - - pub fn on_scroll_vertical(&mut self, delta: f64) { - self.vertical_scroll_delta += delta; - while self.vertical_scroll_delta >= 1.0 { - self.vertical_scroll = self.vertical_scroll.saturating_add(1); - self.vertical_scroll_delta -= 1.0; - self.wake_flag = true; - } - while self.vertical_scroll_delta <= -1.0 { - self.vertical_scroll = self.vertical_scroll.saturating_sub(1); - self.vertical_scroll_delta += 1.0; - self.wake_flag = true; - } - } - - pub fn on_character_input(&mut self, input: char) { - if let Ok(byte) = u8::try_from(u32::from(input)) { - self.text_queue.push_back(byte); - self.wake_flag = true; - } - } - - pub fn on_keyboard_input(&mut self, input: KeyboardInput) { - let mask = match input.key { - KeyCode::Up => UP, - KeyCode::Down => DOWN, - KeyCode::Left => LEFT, - KeyCode::Right => RIGHT, - KeyCode::Return => CONFIRM, - KeyCode::Escape => CANCEL, - KeyCode::Tab => match test!(self.modifiers, SHIFT) { - false => NEXT, - true => PREVIOUS - }, - _ => return, - }; - let navigation = match input.action { - Action::Pressed => self.navigation | mask, - Action::Released => self.navigation & !mask, - }; - if navigation != self.navigation { - self.navigation = navigation; - self.wake_flag = true; - } - } - - pub fn on_modifier_change(&mut self, modifiers: ModifiersState) { - let mut new_modifiers = 0x00; - if modifiers.ctrl() { new_modifiers |= CONTROL } - if modifiers.alt() { new_modifiers |= ALT } - if modifiers.shift() { new_modifiers |= SHIFT } - if new_modifiers != self.modifiers { - self.modifiers = new_modifiers; - self.wake_flag = true; - } - - } -} diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs new file mode 100644 index 0000000..638c277 --- /dev/null +++ b/src/devices/input_device.rs @@ -0,0 +1,233 @@ +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 x_read: u16, + pub y_read: u16, + + 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 gamepad_1: u8, + pub gamepad_2: u8, + pub gamepad_3: u8, + pub gamepad_4: u8, +} + +impl InputDevice { + pub fn new() -> Self { + Self { + wake: false, + accessed: false, + + pointer_active: false, + pointer_buttons: 0, + + position: ScreenPosition::ZERO, + x_read: 0, + y_read: 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, + + gamepad_1: 0, + gamepad_2: 0, + gamepad_3: 0, + gamepad_4: 0, + } + } + + pub fn on_cursor_enter(&mut self) { + self.pointer_active = true; + self.wake = true; + } + + pub fn on_cursor_exit(&mut self) { + self.pointer_active = false; + self.wake = true; + } + + pub fn on_cursor_move(&mut self, position: Position) { + let screen_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; + self.wake = true; + } + } + + pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) { + let mask = match button { + MouseButton::Left => 0x80, + MouseButton::Middle => 0x40, + MouseButton::Right => 0x20, + }; + let pointer_buttons = match action { + Action::Pressed => self.pointer_buttons | mask, + Action::Released => self.pointer_buttons & !mask, + }; + if self.pointer_buttons != pointer_buttons { + self.pointer_buttons = pointer_buttons; + self.wake = true; + } + } + + fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta)); + fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta)); + + pub fn read_horizontal_scroll(&mut self) -> u8 { + std::mem::take(&mut self.h_scroll) as u8 + } + + pub fn read_vertical_scroll(&mut self) -> u8 { + std::mem::take(&mut self.v_scroll) as u8 + } + + pub fn on_character(&mut self, character: char) { + let character = match character { + '\r' => '\n', + _ => character, + }; + let mut bytes = [0; 4]; + let string = character.encode_utf8(&mut bytes); + for byte in string.bytes() { + self.characters.push_back(byte); + } + self.wake = true; + } + + pub fn on_keypress(&mut self, key: KeyCode, action: Action) { + let shift = self.modifiers & 0x40 != 0; + let mask = match key { + KeyCode::ArrowUp => 0x80, // up + KeyCode::ArrowDown => 0x40, // down + KeyCode::ArrowLeft => 0x20, // left + KeyCode::ArrowRight => 0x10, // right + KeyCode::Enter => 0x08, // confirm + KeyCode::Escape => 0x04, // cancel + KeyCode::Tab => match shift { // shift + false => 0x02, // next + true => 0x01 // previous + }, + _ => return, + }; + let navigation = match action { + Action::Pressed => self.navigation | mask, + Action::Released => self.navigation & !mask, + }; + if self.navigation != navigation { + self.navigation = navigation; + self.wake = true; + } + } + + pub fn on_modifier(&mut self, state: ModifiersState) { + let mut modifiers = 0; + if state.control_key() { modifiers |= 0x80 } + if state.shift_key() { modifiers |= 0x40 } + if state.alt_key() { modifiers |= 0x20 } + if state.super_key() { modifiers |= 0x10 } + if self.modifiers != modifiers { + self.modifiers = modifiers; + self.wake = true; + } + } +} + +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/local_device.rs b/src/devices/local_device.rs new file mode 100644 index 0000000..14a8f71 --- /dev/null +++ b/src/devices/local_device.rs @@ -0,0 +1,230 @@ +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, + + stdin_connected: bool, + stdin_control: bool, + stdin_rx: mpsc::Receiver<Vec<u8>>, + stdin_queue: VecDeque<u8>, + stdin_excess: VecDeque<u8>, + stdout: Stdout, + + stdout_connected: bool, + + decode_stdin: bool, + encode_stdout: bool, + decode_buffer: Option<u8>, +} + +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) } + } + // Spawn a thread to enable non-blocking reads of stdin. + let (stdin_tx, stdin_rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || loop { + let mut stdin = std::io::stdin().lock(); + match stdin.fill_buf() { + Ok(buf) if !buf.is_empty() => { + let length = buf.len(); + stdin_tx.send(buf.to_vec()).unwrap(); + stdin.consume(length); + } + _ => break, + }; + }); + + Self { + wake: true, + + stdin_connected: true, + stdin_control: false, + stdin_rx, + stdin_queue, + stdin_excess: VecDeque::new(), + stdout: std::io::stdout(), + + stdout_connected: true, + + decode_stdin: config.encode_stdin, + encode_stdout: config.encode_stdout, + decode_buffer: None, + } + } + + pub fn stdin_length(&mut self) -> u8 { + self.fetch_stdin_data(); + self.stdin_queue.len().try_into().unwrap_or(u8::MAX) + } + + pub fn stdin_enable(&mut self) { + if !self.stdin_control { + self.stdin_queue.clear(); + self.stdin_control = true; + } + } + + pub fn stdin_read(&mut self) -> u8 { + self.fetch_stdin_data(); + self.stdin_queue.pop_front().unwrap_or(0) + } + + pub fn stdout_write(&mut self, value: u8) { + 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), + } }; + } + if self.encode_stdout { + let encoded = [hex!(value >> 4), hex!(value & 0xf), b' ']; + self.stdout_write_raw(&encoded); + } else { + self.stdout_write_raw(&[value]); + }; + } + + fn stdout_write_raw(&mut self, bytes: &[u8]) { + if let Err(_) = self.stdout.write_all(bytes) { + if self.stdout_connected { + self.stdout_connected = false; + self.wake = true; // wake because stdout was disconnected. + } + } + } + + pub fn stdout_disable(&mut self) { + if self.encode_stdout { + self.stdout_write_raw(&[b'\n']); + } + } + + pub fn fetch_stdin_data(&mut self) { + while self.stdin_control { + match self.stdin_excess.pop_front() { + Some(byte) => self.fetch_byte(byte), + 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), + } + } + } + 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. + } + } + } + } + + fn fetch_byte(&mut self, byte: u8) { + 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'\n' => { + self.decode_buffer = None; + self.stdin_control = false; + self.wake = true; // wake because a transmission ended. + return; + }, + _ => return, + }; + if let Some(high) = std::mem::take(&mut self.decode_buffer) { + self.stdin_queue.push_back((high << 4) | decoded); + self.wake = true; // wake because a byte was received. + } else { + self.decode_buffer = Some(decoded); + } + } else { + self.stdin_queue.push_back(byte); + self.wake = true; // wake because a byte was received. + } + } + + pub fn flush(&mut self) { + self.stdout.flush().unwrap(); + } +} + + +impl Drop for LocalDevice { + 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/math.rs b/src/devices/math.rs deleted file mode 100644 index cefb572..0000000 --- a/src/devices/math.rs +++ /dev/null @@ -1,31 +0,0 @@ -pub struct MathDevice { - pub operand_1: u16, - pub operand_2: u16, -} - -impl MathDevice { - pub fn new() -> Self { - Self { - operand_1: 0x0000, - operand_2: 0x0000, - } - } - - pub fn multiply_high(&mut self) -> u16 { - let (_, high) = self.operand_1.widening_mul(self.operand_2); - return high; - } - - pub fn multiply_low(&mut self) -> u16 { - let (low, _) = self.operand_1.widening_mul(self.operand_2); - return low; - } - - pub fn divide(&mut self) -> u16 { - self.operand_1.checked_div(self.operand_2).unwrap_or(0) - } - - pub fn modulo(&mut self) -> u16 { - self.operand_1.checked_rem(self.operand_2).unwrap_or(0) - } -} diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs new file mode 100644 index 0000000..015545e --- /dev/null +++ b/src/devices/math_device.rs @@ -0,0 +1,141 @@ +use bedrock_core::*; + + +pub struct MathDevice { + pub op1: u16, + pub op2: u16, + + pub sqrt: Option<u16>, + pub atan: Option<u16>, + pub prod: Option<(u16, u16)>, // (low, high) + pub quot: Option<u16>, + pub rem: Option<u16>, +} + +impl MathDevice { + pub fn new() -> Self { + Self { + op1: 0, + op2: 0, + + sqrt: None, + atan: None, + prod: None, + quot: None, + rem: None, + } + } + + pub fn clear(&mut self) { + self.sqrt = None; + self.atan = None; + self.prod = None; + self.quot = None; + self.rem = None; + } + + pub fn atan(&mut self) -> u16 { + match self.atan { + Some(atan) => atan, + 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() + } + } + } + + pub fn sqrt(&mut self) -> u16 { + match self.sqrt { + Some(sqrt) => sqrt, + None => { + let input = ((self.op1 as u32) << 16) | (self.op2 as u32); + self.sqrt = Some((input as f64).sqrt() as u16); + self.sqrt.unwrap() + } + } + } + + pub fn prod(&mut self) -> (u16, u16) { + match self.prod { + Some(prod) => prod, + None => { + self.prod = Some(self.op1.widening_mul(self.op2)); + self.prod.unwrap() + } + } + } + + pub fn quot(&mut self) -> u16 { + match self.quot { + Some(quot) => quot, + None => { + self.quot = Some(self.op1.checked_div(self.op2).unwrap_or(0)); + self.quot.unwrap() + } + } + } + + pub fn rem(&mut self) -> u16 { + match self.rem { + Some(rem) => rem, + None => { + self.rem = Some(self.op1.checked_rem(self.op2).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.rs b/src/devices/memory.rs deleted file mode 100644 index 56125d2..0000000 --- a/src/devices/memory.rs +++ /dev/null @@ -1,91 +0,0 @@ -macro_rules! then_inc { ($v:expr) => {{ $v = $v.wrapping_add(1); $v as usize }}; } - -type Page = [u8; 65536]; -fn new_blank_page() -> [u8; 65536] { [0; 65536] } - - -pub struct MemoryDevice { - // Hard limit on the number of pages which can be allocated - pub page_limit: u16, - // Pages which have actually been allocated - pub pages: Vec<Page>, - - pub page_1: u16, - pub address_1: u16, - pub page_2: u16, - pub address_2: u16, - pub copy_length: u16, -} - -impl MemoryDevice { - pub fn new() -> Self { - Self { - page_limit: 0, - pages: Vec::new(), - - page_1: 0, - address_1: 0, - page_2: 0, - address_2: 0, - copy_length: 0, - } - } - - pub fn copy(&mut self) { - let count = std::cmp::max(self.page_1, self.page_2) as usize + 1; - if count <= self.page_limit as usize { - if self.pages.len() < count { - self.pages.resize_with(count, new_blank_page); - } - let p1 = self.page_1 as usize; - let p2 = self.page_2 as usize; - let a1 = self.address_1; - let a2 = self.address_2; - - for i in 0..=self.copy_length { - let byte = self.pages[p2][a2.wrapping_add(i) as usize]; - self.pages[p1][a1.wrapping_add(i) as usize] = byte; - } - } - } - - pub fn read_from_head_1(&mut self) -> u8 { - let address = then_inc!(self.address_1); - if let Some(page) = self.pages.get(self.page_1 as usize) { - page[address] - } else { - 0x00 - } - } - - pub fn read_from_head_2(&mut self) -> u8 { - let address = then_inc!(self.address_2); - if let Some(page) = self.pages.get(self.page_2 as usize) { - page[address] - } else { - 0x00 - } - } - - pub fn write_to_head_1(&mut self, byte: u8) { - let address = then_inc!(self.address_1); - let page = self.page_1 as usize; - if self.page_limit as usize > page { - if self.pages.len() <= page { - self.pages.resize_with(page + 1, new_blank_page); - } - self.pages[page][address] = byte; - } - } - - pub fn write_to_head_2(&mut self, byte: u8) { - let address = then_inc!(self.address_1); - let page = self.page_2 as usize; - if self.page_limit as usize > page { - if self.pages.len() <= page { - self.pages.resize_with(page + 1, new_blank_page); - } - self.pages[page][address] = byte; - } - } -} diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs new file mode 100644 index 0000000..7c1aeda --- /dev/null +++ b/src/devices/memory_device.rs @@ -0,0 +1,152 @@ +use bedrock_core::*; + +type Page = [u8; 256]; + +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; + } + } + } + }; +} + + +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 pages: Vec<Page>, // all allocated pages + + pub offset_1: u16, + pub address_1: u16, + pub offset_2: u16, + pub address_2: u16, + + pub copy_length: u16, +} + +impl MemoryDevice { + pub fn new() -> Self { + Self { + limit: 0, + requested: 0, + provisioned: 0, + pages: Vec::new(), + + offset_1: 0, + address_1: 0, + offset_2: 0, + address_2: 0, + + copy_length: 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 provision(&mut self) { + self.provisioned = std::cmp::min(self.requested, self.limit) as usize; + // Defer allocation of new pages. + self.pages.truncate(self.provisioned 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; + + // 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]); + } + + for i in 0..count { + let src_page = match self.pages.get(src + i) { + Some(src_page) => src_page.to_owned(), + None => [0; 256], + }; + match self.pages.get_mut(dest + i) { + Some(dest) => *dest = src_page, + None => break, + }; + } + } +} + +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!(), + } + } + + 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; + } + + fn wake(&mut self) -> bool { + false + } +} diff --git a/src/devices/remote_device.rs b/src/devices/remote_device.rs new file mode 100644 index 0000000..f50ac7a --- /dev/null +++ b/src/devices/remote_device.rs @@ -0,0 +1,35 @@ +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.rs b/src/devices/screen.rs deleted file mode 100644 index a61d8b3..0000000 --- a/src/devices/screen.rs +++ /dev/null @@ -1,258 +0,0 @@ -mod sprite_data; -mod draw_line; -mod draw_rect; -mod draw_sprite; - -pub use sprite_data::*; -use geometry::HasDimensions; -use phosphor::*; -use std::cmp::{min, max, Ordering}; -use std::iter::zip; - -pub type ScreenDimensions = geometry::Dimensions<u16>; -pub type ScreenPosition = geometry::Point<u16>; -pub type Plane = [u8; 8]; -pub type Sprite = [[u8; 8]; 8]; - -const TRANSPARENT: u8 = 0x08; -const FLIP_DIAGONAL: u8 = 0x04; -const FLIP_VERTICAL: u8 = 0x02; -const FLIP_HORIZONTAL: u8 = 0x01; -const NEGATIVE: u8 = 0x80; -const VERTICAL: u8 = 0x40; - - -#[derive(Copy, Clone)] -pub enum ScreenLayer { Background, Foreground } - -macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } - - -pub struct ScreenDevice { - pub wake_flag: bool, - - /// Each byte represents a screen pixel, left-to-right and top-to-bottom. - // Only the bottom four bits of each byte are used. - pub foreground: Vec<u8>, - pub background: Vec<u8>, - pub dirty: bool, - pub resizable: bool, - - pub cursor: ScreenPosition, - pub vector: ScreenPosition, - pub dimensions: ScreenDimensions, - - pub palette_high: u8, - pub palette: [Colour; 16], - pub sprite_buffer: SpriteBuffer, -} - -impl ScreenDevice { - pub fn new() -> Self { - Self { - wake_flag: false, - - foreground: Vec::new(), - background: Vec::new(), - dirty: false, - resizable: true, - - cursor: ScreenPosition::ZERO, - vector: ScreenPosition::ZERO, - dimensions: ScreenDimensions::ZERO, - - palette_high: 0, - palette: [Colour::BLACK; 16], - sprite_buffer: SpriteBuffer::new(), - } - } - - pub fn set_size(&mut self) { - self.resizable = false; - self.resize(self.dimensions); - } - - // Resize the screen buffers while preserving the current content. - pub fn resize(&mut self, dimensions: ScreenDimensions) { - let old_width = self.dimensions.width as usize; - let old_height = self.dimensions.height as usize; - let new_width = dimensions.width as usize; - let new_height = dimensions.height as usize; - let new_area = dimensions.area_usize(); - let y_range = 0..min(old_height, new_height); - - match new_width.cmp(&old_width) { - Ordering::Less => { - for y in y_range { - let from = y * old_width; - let to = y * new_width; - let len = new_width; - self.foreground.copy_within(from..from+len, to); - self.background.copy_within(from..from+len, to); - } - self.foreground.resize(new_area, 0); - self.background.resize(new_area, 0); - }, - Ordering::Greater => { - self.foreground.resize(new_area, 0); - self.background.resize(new_area, 0); - for y in y_range.rev() { - let from = y * old_width; - let to = y * new_width; - let len = old_width; - self.foreground.copy_within(from..from+len, to); - self.background.copy_within(from..from+len, to); - self.foreground[to+len..to+new_width].fill(0); - self.background[to+len..to+new_width].fill(0); - } - }, - Ordering::Equal => { - self.foreground.resize(new_area, 0); - self.background.resize(new_area, 0); - }, - }; - - self.dimensions = dimensions; - self.dirty = true; - self.wake_flag = true; - } - - pub fn render(&mut self, buffer: &mut Buffer) { - // Pre-calculate a lookup table for the colour palette - let mut lookup = [Colour::BLACK; 256]; - for (i, c) in lookup.iter_mut().enumerate() { - match i > 0x0f { - true => *c = self.palette[i >> 4], - false => *c = self.palette[i & 0x0f], - } - }; - // Prepare values - let b_width = buffer.width() as usize; - let b_height = buffer.height() as usize; - let s_width = self.dimensions.width() as usize; - let s_height = self.dimensions.height() as usize; - - // Write colours to the buffer - if b_width == s_width && b_height == s_height { - let screen_iter = zip(&self.background, &self.foreground); - let buffer_iter = buffer.as_mut_slice(); - for (b, (bg, fg)) in zip(buffer_iter, screen_iter) { - *b = lookup[(fg << 4 | bg) as usize]; - } - // Write colours to the buffer when the size of the buffer is wrong - } else { - let width = min(b_width, s_width); - let height = min(b_height, s_height); - let width_excess = b_width.saturating_sub(width); - let b_slice = &mut buffer.as_mut_slice(); - let mut bi = 0; - let mut si = 0; - for _ in 0..height { - let b_iter = &mut b_slice[bi..bi+width]; - let s_iter = zip( - &self.background[si..si+width], - &self.foreground[si..si+width], - ); - for (b, (bg, fg)) in zip(b_iter, s_iter) { - *b = lookup[(fg << 4 | bg) as usize]; - } - b_slice[bi+width..bi+width+width_excess].fill(lookup[0]); - bi += b_width; - si += s_width; - } - b_slice[bi..].fill(lookup[0]); - } - self.dirty = false; - } - - pub fn set_palette_high(&mut self, val: u8) { - self.palette_high = val; - } - - pub fn set_palette_low(&mut self, val: u8) { - let index = (self.palette_high >> 4) as usize; - let red = (self.palette_high & 0x0f) * 17; - let green = (val >> 4) * 17; - let blue = (val & 0x0f) * 17; - self.palette[index] = Colour::from_rgb(red, green, blue); - self.dirty = true; - } - - pub fn shunt(&mut self, val: u8) { - let negative = test!(val, NEGATIVE); - let vertical = test!(val, VERTICAL); - let dist = (val & 0x3f) as u16; - match (negative, vertical) { - (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist), - (false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist), - ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist), - ( true, true) => self.cursor.y = self.cursor.y.wrapping_sub(dist), - }; - } - - pub fn draw(&mut self, val: u8) { - let operation = val & 0x70; - let parameters = val & 0x0f; - let layer = match val & 0x80 != 0 { - true => ScreenLayer::Foreground, - false => ScreenLayer::Background - }; - match operation { - 0x00 => self.draw_pixel(parameters, layer, self.cursor), - 0x10 => self.draw_sprite_1bit(parameters, layer), - 0x20 => self.fill_layer(parameters, layer), - 0x30 => self.draw_sprite_2bit(parameters, layer), - 0x40 => self.draw_line(parameters, layer), - 0x50 => self.draw_line_1bit(parameters, layer), - 0x60 => self.draw_rect(parameters, layer), - 0x70 => self.draw_rect_1bit(parameters, layer), - _ => unreachable!(), - }; - - self.dirty = true; - self.vector = self.cursor; - } - - // Draw a single pixel of a single colour - fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) { - let dim = self.dimensions; - if !dim.contains_point(point) || colour > 0xf { return } - let index = point.x as usize + ((dim.width as usize) * (point.y as usize)); - match layer { - ScreenLayer::Background => self.background[index] = colour, - ScreenLayer::Foreground => self.foreground[index] = colour, - }; - } - - // Fill an entire screen layer with a single colour - fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) { - match layer { - ScreenLayer::Background => self.background.fill(colour), - ScreenLayer::Foreground => self.foreground.fill(colour), - } - } - - /// Returns [x0, y0, x1, y1], ensuring that x0 <= x1 and y0 <= y1 - fn find_vector_bounding_box(&self) -> Option<[usize; 4]> { - macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};} - macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};} - - let [p0, p1] = [self.cursor, self.vector]; - let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ]; - let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ]; - let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ]; - let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ]; - let right = self.dimensions.width.saturating_sub(1); - let bottom = self.dimensions.height.saturating_sub(1); - if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 { - None - } else { - Some([ - if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize, - if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize, - if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize, - if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize, - ]) - } - } -} diff --git a/src/devices/screen/draw_line.rs b/src/devices/screen/draw_line.rs deleted file mode 100644 index 94066f4..0000000 --- a/src/devices/screen/draw_line.rs +++ /dev/null @@ -1,223 +0,0 @@ -use super::*; - -impl ScreenDevice { - pub fn draw_line(&mut self, colour: u8, layer: ScreenLayer) { - let [p0, p1] = [self.cursor, self.vector]; - match (p0.x == p1.x, p0.y == p1.y) { - (false, false) => self.draw_diagonal_line(colour, layer), - (false, true) => self.draw_horizontal_line(colour, layer), - ( true, false) => self.draw_vertical_line(colour, layer), - ( true, true) => self.draw_pixel(colour, layer, p0), - }; - } - - pub fn draw_line_1bit(&mut self, params: u8, layer: ScreenLayer) { - let [p0, p1] = [self.cursor, self.vector]; - match (p0.x == p1.x, p0.y == p1.y) { - (false, false) => self.draw_diagonal_line_1bit(params, layer), - (false, true) => self.draw_horizontal_line_1bit(params, layer), - ( true, false) => self.draw_vertical_line_1bit(params, layer), - ( true, true) => self.draw_pixel_1bit(params, layer, p0), - }; - } - - pub fn draw_pixel_1bit(&mut self, params: u8, layer: ScreenLayer, point: ScreenPosition) { - let dim = self.dimensions; - let sprite = self.sprite_buffer.get_1bit_sprite(params); - let colour = sprite[point.y as usize % 8][point.x as usize % 8]; - if !dim.contains_point(point) || colour == 0xff { return } - let index = point.x as usize + ((dim.width as usize) * (point.y as usize)); - match layer { - ScreenLayer::Background => self.background[index] = colour, - ScreenLayer::Foreground => self.foreground[index] = colour, - }; - } - - fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer) { - if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let i = screen_width * y; - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - buffer[i+x0..=i+x1].fill(colour); - } - } - - fn draw_horizontal_line_1bit(&mut self, params: u8, layer: ScreenLayer) { - if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let i = screen_width * y; - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - let sprite = self.sprite_buffer.get_1bit_sprite(params); - let row = sprite[y % 8]; - for x in x0..=x1 { - let colour = row[x % 8]; - if colour != 0xff { buffer[i+x] = colour }; - } - } - } - - fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) { - if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let mut i = (screen_width * y0) + x; - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - buffer[i] = colour; - i += screen_width; - } - } - } - - fn draw_vertical_line_1bit(&mut self, params: u8, layer: ScreenLayer) { - if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let mut i = (screen_width * y0) + x; - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - let sprite = self.sprite_buffer.get_1bit_sprite(params); - let mut column = [0u8; 8]; - for y in 0..8 { column[y] = sprite[y][x % 8] } - for y in y0..=y1 { - let colour = column[y % 8]; - if colour != 0xff { buffer[i] = colour }; - i += screen_width; - } - } - } - - fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) { - fn abs_diff(v0: u16, v1: u16) -> u16 { - let v = v1.wrapping_sub(v0); - if v > 0x8000 { !v + 1 } else { v } - } - let [p0, p1] = [self.cursor, self.vector]; - - // If the slope of the line is greater than 1. - if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) { - // Swap points 0 and 1 such that y0 is always smaller than y1. - let (x0, y0, x1, y1) = match p0.y > p1.y { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.y), - }; - let dy = y1 - y0; - let (dx, xi) = match x0 > x1 { - true => (x0 - x1, 0xffff), - false => (x1 - x0, 0x0001), - }; - let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2); - let dx2 = dx * 2; - let mut d = dx2.wrapping_sub(dy); - let mut x = x0; - - for y in y0..=y1 { - self.draw_pixel(colour, layer, ScreenPosition::new(x, y)); - if d < 0x8000 { - x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2); - } else { - d = d.wrapping_add(dx2); - } - } - // If the slope of the line is less than or equal to 1. - } else { - // Swap points 0 and 1 so that x0 is always smaller than x1. - let (x0, y0, x1, y1) = match p0.x > p1.x { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.y), - }; - let dx = x1 - x0; - let (dy, yi) = match y0 > y1 { - true => (y0 - y1, 0xffff), - false => (y1 - y0, 0x0001), - }; - let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2); - let dy2 = dy * 2; - let mut d = dy2.wrapping_sub(dx); - let mut y = y0; - - for x in x0..=x1 { - self.draw_pixel(colour, layer, ScreenPosition::new(x, y)); - if d < 0x8000 { - y = y.wrapping_add(yi); - d = d.wrapping_add(dydx2); - } else { - d = d.wrapping_add(dy2); - } - } - } - } - - fn draw_diagonal_line_1bit(&mut self, params: u8, layer: ScreenLayer) { - fn abs_diff(v0: u16, v1: u16) -> u16 { - let v = v1.wrapping_sub(v0); - if v > 0x8000 { !v + 1 } else { v } - } - let [p0, p1] = [self.cursor, self.vector]; - - // If the slope of the line is greater than 1. - if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) { - // Swap points 0 and 1 such that y0 is always smaller than y1. - let (x0, y0, x1, y1) = match p0.y > p1.y { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.y), - }; - let dy = y1 - y0; - let (dx, xi) = match x0 > x1 { - true => (x0 - x1, 0xffff), - false => (x1 - x0, 0x0001), - }; - let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2); - let dx2 = dx * 2; - let mut d = dx2.wrapping_sub(dy); - let mut x = x0; - - for y in y0..=y1 { - self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y)); - if d < 0x8000 { - x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2); - } else { - d = d.wrapping_add(dx2); - } - } - // If the slope of the line is less than or equal to 1. - } else { - // Swap points 0 and 1 so that x0 is always smaller than x1. - let (x0, y0, x1, y1) = match p0.x > p1.x { - true => (p1.x, p1.y, p0.x, p0.y), - false => (p0.x, p0.y, p1.x, p1.y), - }; - let dx = x1 - x0; - let (dy, yi) = match y0 > y1 { - true => (y0 - y1, 0xffff), - false => (y1 - y0, 0x0001), - }; - let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2); - let dy2 = dy * 2; - let mut d = dy2.wrapping_sub(dx); - let mut y = y0; - - for x in x0..=x1 { - self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y)); - if d < 0x8000 { - y = y.wrapping_add(yi); - d = d.wrapping_add(dydx2); - } else { - d = d.wrapping_add(dy2); - } - } - } - } - - - -} diff --git a/src/devices/screen/draw_rect.rs b/src/devices/screen/draw_rect.rs deleted file mode 100644 index 265a87f..0000000 --- a/src/devices/screen/draw_rect.rs +++ /dev/null @@ -1,42 +0,0 @@ -use super::*; - -impl ScreenDevice { - pub fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) { - if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * y0); - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - buffer[i..i+rect_width].fill(colour); - i += screen_width; - } - } - } - - pub fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) { - if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() { - let screen_width = self.dimensions.width as usize; - let rect_width = x1 - x0 + 1; - let mut i = x0 + (screen_width * y0); - let sprite = self.sprite_buffer.get_1bit_sprite(params); - let buffer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - - for y in y0..=y1 { - let row = sprite[y % 8]; - for x in x0..=x1 { - let colour = row[x % 8]; - if colour != 0xff { buffer[i] = colour } - i += 1; - } - i += screen_width - rect_width; - } - }; - } -} diff --git a/src/devices/screen/draw_sprite.rs b/src/devices/screen/draw_sprite.rs deleted file mode 100644 index 5676335..0000000 --- a/src/devices/screen/draw_sprite.rs +++ /dev/null @@ -1,25 +0,0 @@ -use super::*; - -impl ScreenDevice { - pub fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) { - let sprite = self.sprite_buffer.get_1bit_sprite(params); - self.draw_sprite(sprite, layer); - } - - pub fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) { - let sprite = self.sprite_buffer.get_2bit_sprite(params); - self.draw_sprite(sprite, layer); - } - - fn draw_sprite(&mut self, sprite: Sprite, layer: ScreenLayer) { - let mut pos = self.cursor; - for row in sprite { - for colour in row { - self.draw_pixel(colour, layer, pos); - pos.x = pos.x.wrapping_add(1); - } - pos.x = pos.x.wrapping_sub(8); - pos.y = pos.y.wrapping_add(1); - } - } -} diff --git a/src/devices/screen/sprite_data.rs b/src/devices/screen/sprite_data.rs deleted file mode 100644 index 0a1d3c2..0000000 --- a/src/devices/screen/sprite_data.rs +++ /dev/null @@ -1,109 +0,0 @@ -use super::*; - -macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } - - -pub struct SpriteBuffer { - data: [u8; 16], - pointer: usize, - colours: [u8; 4], -} - -impl SpriteBuffer { - pub fn new() -> Self { - Self { data: [0; 16], pointer: 0, colours: [0; 4] } - } - - pub fn push(&mut self, val: u8) { - self.data[self.pointer] = val; - self.pointer = (self.pointer + 1) % 16; - } - - pub fn set_colour_high(&mut self, val: u8) { - self.colours[0] = val >> 4; - self.colours[1] = val & 0x0f; - } - - pub fn set_colour_low(&mut self, val: u8) { - self.colours[2] = val >> 4; - self.colours[3] = val & 0x0f; - } - - // Return the 64 transformed pixels of the current 1-bit sprite. - // Each pixel is the palette index of that pixel, or 0xff if transparent. - pub fn get_1bit_sprite(&self, params: u8) -> Sprite { - let mut sprite = [[0u8; 8]; 8]; - let plane = self.get_low_plane(params); - let colours = self.get_colours(params); - for (y, row) in plane.into_iter().enumerate() { - for x in (0..8).rev() { - sprite[y][7-x] = colours[(row >> x & 0x1) as usize]; - } - } - return sprite; - } - - // Return the 64 transformed pixels of the current 2-bit sprite. - // Each pixel is the palette index of that pixel, or 0xff if transparent. - pub fn get_2bit_sprite(&self, params: u8) -> Sprite { - let mut sprite = [[0u8; 8]; 8]; - let high_plane = self.get_high_plane(params); - let low_plane = self.get_low_plane(params); - let colours = self.get_colours(params); - for (y, (row_h, row_l)) in zip(high_plane, low_plane).enumerate() { - for x in (0..8).rev() { - let bit_h = (row_h >> x) & 0x1; - let bit_l = (row_l >> x) & 0x1; - sprite[y][7-x] = colours[(bit_h << 1 | bit_l) as usize]; - } - } - return sprite; - } - - fn get_high_plane(&self, params: u8) -> Plane { - let mut plane = [0u8; 8]; - for (i, row) in plane.iter_mut().enumerate() { - *row = self.data[(self.pointer + i) % 16] - } - transform_plane(plane, params) - } - - fn get_low_plane(&self, params: u8) -> Plane { - let mut plane = [0u8; 8]; - for (i, row) in plane.iter_mut().enumerate() { - *row = self.data[(self.pointer + i + 8) % 16] - } - transform_plane(plane, params) - } - - fn get_colours(&self, params: u8) -> [u8; 4] { - let mut colours = self.colours; - if test!(params, TRANSPARENT) { - colours[0] = 0xff; - } - return colours; - } -} - -fn transform_plane(mut plane: Plane, params: u8) -> Plane { - if test!(params, FLIP_DIAGONAL) { - let mut flipped = [0u8; 8]; - for mut row in plane { - for y in 0..8 { - flipped[y] = flipped[y] << 1 | row >> 7; - row <<= 1; - } - } - plane = flipped; - } - if test!(params, FLIP_VERTICAL) { - plane.reverse(); - } - if test!(params, FLIP_HORIZONTAL) { - for row in plane.iter_mut() { - *row = row.reverse_bits(); - } - } - return plane; -} - diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs new file mode 100644 index 0000000..64f3815 --- /dev/null +++ b/src/devices/screen_device.rs @@ -0,0 +1,485 @@ +use crate::*; + +use bedrock_core::*; +use geometry::*; +use phosphor::*; + +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, + + pub cursor: ScreenPosition, + pub vector: ScreenPosition, + + pub dimensions: ScreenDimensions, + pub dirty_dimensions: bool, + pub width_write: u16, + pub height_write: u16, + pub fixed_width: Option<u16>, + pub fixed_height: Option<u16>, + + pub palette_write: u16, + pub palette: [Colour; 16], + pub colours: u16, + pub sprite: SpriteBuffer, +} + +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, + + cursor: ScreenPosition::ZERO, + vector: ScreenPosition::ZERO, + + dimensions: config.dimensions, + dirty_dimensions: true, + width_write: 0, + height_write: 0, + fixed_width: None, + fixed_height: None, + + palette_write: 0, + palette: [Colour::BLACK; 16], + colours: 0, + sprite: SpriteBuffer::new(), + } + } + + /// External resize. + pub fn resize(&mut self, dimensions: phosphor::Dimensions) { + // Replace dimensions with fixed dimensions. + let screen_dimensions = ScreenDimensions { + width: match self.fixed_width { + Some(fixed_width) => fixed_width, + None => dimensions.width as u16, + }, + height: match self.fixed_height { + Some(fixed_height) => fixed_height, + None => dimensions.height as u16, + }, + }; + let old_dimensions = self.dimensions; + if self.dimensions != screen_dimensions { + self.dimensions = screen_dimensions; + self.resize_layers(old_dimensions); + self.wake = true; + } + } + + /// Internal resize. + fn resize_width(&mut self) { + self.fixed_width = Some(self.width_write); + self.dirty_dimensions = true; + let old_dimensions = self.dimensions; + if self.dimensions.width != self.width_write { + self.dimensions.width = self.width_write; + self.resize_layers(old_dimensions); + } + } + + /// Internal resize. + fn resize_height(&mut self) { + self.fixed_height = Some(self.height_write); + self.dirty_dimensions = true; + let old_dimensions = self.dimensions; + if self.dimensions.height != self.height_write { + self.dimensions.height = self.height_write; + self.resize_layers(old_dimensions); + } + } + + fn resize_layers(&mut self, old_dimensions: ScreenDimensions) { + use std::cmp::{min, Ordering}; + + let old_width = old_dimensions.width as usize; + let old_height = old_dimensions.height as usize; + let new_width = self.dimensions.width as usize; + let new_height = self.dimensions.height as usize; + let new_area = self.dimensions.area_usize(); + let y_range = 0..min(old_height, new_height); + let new_colour = match self.fg.last() { + None | Some(0) => *self.bg.last().unwrap_or(&0), + Some(colour) => *colour, + }; + + match new_width.cmp(&old_width) { + Ordering::Less => { + for y in y_range { + let src = y * old_width; + let dest = y * new_width; + let len = new_width; + self.fg.copy_within(src..src+len, dest); + self.bg.copy_within(src..src+len, dest); + } + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + }, + Ordering::Greater => { + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + for y in y_range.rev() { + let src = y * old_width; + let dest = y * new_width; + let len = old_width; + self.fg.copy_within(src..src+len, dest); + self.bg.copy_within(src..src+len, dest); + self.fg[dest+len..dest+new_width].fill(0); + self.bg[dest+len..dest+new_width].fill(new_colour); + } + }, + Ordering::Equal => { + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + }, + }; + + self.dirty = true; + } + + 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 colour = Colour::from_rgb(r, g, b); + if self.palette[i] != colour { + self.palette[i] = colour; + self.dirty = true; + } + } + + pub fn draw_dispatch(&mut self, draw: u8) { + match draw >> 4 { + 0x0 => self.op_draw_pixel(Layer::Bg, draw), + 0x1 => self.op_draw_sprite(Layer::Bg, draw), + 0x2 => self.op_fill_layer(Layer::Bg, draw), + 0x3 => self.op_draw_sprite(Layer::Bg, draw), + 0x4 => self.op_draw_line(Layer::Bg, draw), + 0x5 => self.op_draw_line(Layer::Bg, draw), + 0x6 => self.op_draw_rect(Layer::Bg, draw), + 0x7 => self.op_draw_rect(Layer::Bg, draw), + 0x8 => self.op_draw_pixel(Layer::Fg, draw), + 0x9 => self.op_draw_sprite(Layer::Fg, draw), + 0xA => self.op_fill_layer(Layer::Fg, draw), + 0xB => self.op_draw_sprite(Layer::Fg, draw), + 0xC => self.op_draw_line(Layer::Fg, draw), + 0xD => self.op_draw_line(Layer::Fg, draw), + 0xE => self.op_draw_rect(Layer::Fg, draw), + 0xF => self.op_draw_rect(Layer::Fg, draw), + _ => unreachable!(), + } + self.vector = self.cursor; + self.dirty = true; + } + + pub fn move_cursor(&mut self, value: u8) { + 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), + 0b10 => self.cursor.x = self.cursor.x.wrapping_sub(distance), + 0b11 => self.cursor.y = self.cursor.y.wrapping_sub(distance), + _ => unreachable!(), + }; + } + + /// 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); + match layer { + Layer::Fg => self.fg[index] = colour, + Layer::Bg => self.bg[index] = colour, + }; + } + } + + fn op_draw_pixel(&mut self, layer: Layer, draw: u8) { + 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), + } + } + + fn op_draw_sprite(&mut self, layer: Layer, draw: u8) { + let sprite = match draw & 0x20 != 0 { + true => self.sprite.read_2bit_sprite(draw), + 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, + ]; + let cx = self.cursor.x; + let cy = self.cursor.y; + + if draw & 0x08 != 0 { + // Draw sprite with transparent background + for y in 0..8 { + for x in 0..8 { + let index = sprite[y as usize][x as usize] as usize; + if index != 0 { + let px = cx.wrapping_add(x); + let py = cy.wrapping_add(y); + self.draw_pixel(layer, px, py, colours[index]); + } + } + } + } else { + // Draw sprite with opaque background + for y in 0..8 { + for x in 0..8 { + let index = sprite[y as usize][x as usize] as usize; + let px = cx.wrapping_add(x); + let py = cy.wrapping_add(y); + self.draw_pixel(layer, px, py, colours[index]); + } + } + } + } + + fn op_draw_line(&mut self, layer: Layer, draw: u8) { + let mut x: i16 = self.cursor.x as i16; + let mut y: i16 = self.cursor.y as i16; + let x_end: i16 = self.vector.x as i16; + let y_end: i16 = self.vector.y as i16; + + let dx: i16 = (x_end - x).abs(); + let dy: i16 = -(y_end - y).abs(); + let sx: i16 = if x < x_end { 1 } else { -1 }; + let sy: i16 = if y < y_end { 1 } else { -1 }; + let mut e1: i16 = dx + dy; + + 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 opaque = draw & 0x08 == 0; + loop { + let sprite_pixel = sprite[(y % 8) as usize][(x % 8) as usize]; + if sprite_pixel != 0 { self.draw_pixel(layer, x as u16, y as u16, c1); } + else if opaque { self.draw_pixel(layer, x as u16, y as u16, c0); } + if x == x_end && y == y_end { break; } + let e2 = e1 << 1; + if e2 >= dy { e1 += dy; x += sx; } + if e2 <= dx { e1 += dx; y += sy; } + } + } else { + // Draw solid line. + let colour = draw & 0xf; + loop { + self.draw_pixel(layer, x as u16, y as u16, colour); + if x == x_end && y == y_end { break; } + let e2 = e1 << 1; + if e2 >= dy { e1 += dy; x += sx; } + if e2 <= dx { e1 += dx; y += sy; } + } + } + } + + 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 } + }; + } + macro_rules! out_of_bounds { + ($axis:ident, $max:expr) => {{ + let c = self.cursor.$axis; + let v = self.vector.$axis; + c >= $max && v >= $max && (c >= 0x8000) == (v >= 0x8000) + }}; + } + + let out_of_bounds_x = out_of_bounds!(x, self.dimensions.width); + let out_of_bounds_y = out_of_bounds!(y, self.dimensions.height); + if out_of_bounds_x || out_of_bounds_y { return; } + + // Get bounding box. + let mut l = clamp!(self.vector.x, self.dimensions.width -1); + let mut r = clamp!(self.cursor.x, self.dimensions.width -1); + let mut t = clamp!(self.vector.y, self.dimensions.height -1); + let mut b = clamp!(self.cursor.y, self.dimensions.height -1); + if l > r { std::mem::swap(&mut l, &mut r) }; + if t > b { std::mem::swap(&mut t, &mut b) }; + + 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 opaque = draw & 0x08 == 0; + for y in t..=b { + for x in l..=r { + let sprite_colour = sprite[(y % 8) as usize][(x % 8) as usize]; + if sprite_colour != 0 { self.draw_pixel(layer, x, y, c1); } + else if opaque { self.draw_pixel(layer, x, y, c0); } + } + } + } else { + // Draw solid rectangle. + let colour = draw & 0xf; + for y in t..=b { + for x in l..=r { + self.draw_pixel(layer, x, y, colour); + } + } + } + } +} + +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, +} + +impl SpriteBuffer { + pub fn new() -> Self { + Self { + mem: [0; 16], + pointer: 0, + } + } + + pub fn push_byte(&mut self, byte: u8) { + self.mem[self.pointer] = byte; + self.pointer = (self.pointer + 1) % 16; + } + + pub fn read_1bit_sprite(&self, draw: u8) -> 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!(), + } + return sprite; + } + + pub fn read_2bit_sprite(&self, draw: u8) -> 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!(), + } + return sprite; + } +} diff --git a/src/devices/stream.rs b/src/devices/stream.rs deleted file mode 100644 index 532df58..0000000 --- a/src/devices/stream.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::io::{Read, Write}; -use std::io::{BufReader, BufWriter}; -use std::io::{Stdin, Stdout}; - -pub struct StreamDevice { - pub wake_flag: bool, - - pub input_control: bool, - pub output_control: bool, - - pub stdin: BufReader<Stdin>, - pub stdout: BufWriter<Stdout>, -} - -impl StreamDevice { - pub fn new() -> Self { - Self { - wake_flag: false, - - input_control: true, - output_control: true, - - stdin: BufReader::new(std::io::stdin()), - stdout: BufWriter::new(std::io::stdout()), - } - } - - pub fn flush_local(&mut self) { - self.stdout.flush().unwrap(); - } - - pub fn read_queue_len(&self) -> usize { - self.stdin.buffer().len() - } - - pub fn read_stdin(&mut self) -> u8 { - let mut buffer = [0; 1]; - match self.stdin.read_exact(&mut buffer) { - Ok(_) => buffer[0], - Err(_) => 0, - } - } - - pub fn write_stdout(&mut self, val: u8) { - self.stdout.write_all(&[val]).unwrap(); - } -} - -impl Drop for StreamDevice { - fn drop(&mut self) { - self.flush_local(); - } -} diff --git a/src/devices/system.rs b/src/devices/system.rs deleted file mode 100644 index 6eb9510..0000000 --- a/src/devices/system.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod read_only_text_buffer; - -pub use read_only_text_buffer::ReadOnlyTextBuffer; - diff --git a/src/devices/system/read_only_text_buffer.rs b/src/devices/system/read_only_text_buffer.rs deleted file mode 100644 index dae1024..0000000 --- a/src/devices/system/read_only_text_buffer.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub struct ReadOnlyTextBuffer { - chars: Vec<u8>, - pointer: usize, -} - -impl ReadOnlyTextBuffer { - pub fn from_text(text: &str) -> Self { - Self { - chars: text.bytes().collect(), - pointer: 0, - } - } - - pub fn read_byte(&mut self) -> u8 { - let option = self.chars.get(self.pointer); - self.pointer += 1; - *option.unwrap_or(&0) - } - - pub fn reset_pointer(&mut self) { - self.pointer = 0; - } -} diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs new file mode 100644 index 0000000..bcffb86 --- /dev/null +++ b/src/devices/system_device.rs @@ -0,0 +1,114 @@ +use bedrock_core::*; + + +pub struct SystemDevice { + pub name: ReadBuffer, + pub authors: ReadBuffer, + pub can_wake: u16, + pub wake_id: u8, + pub asleep: bool, +} + +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 mut authors_str = String::new(); + for author in pkg_authors.split(":") { + authors_str.push_str(&format!("{author}, 2024\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, + 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, + _ => 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); + self.asleep = true; + return Some(Signal::Sleep); + }, + 0xa => (), + 0xb => return Some(Signal::Fork), + 0xc => (), + 0xd => (), + 0xe => (), + 0xf => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + true + } +} + + +pub struct ReadBuffer { + pub bytes: Vec<u8>, + pub pointer: usize, +} + +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, + } + } +} + diff --git a/src/emulator.rs b/src/emulator.rs deleted file mode 100644 index d983378..0000000 --- a/src/emulator.rs +++ /dev/null @@ -1,293 +0,0 @@ -use crate::*; - -use bedrock_core::*; -use phosphor::*; - -use std::cmp::{min, max}; -use std::time::*; -use std::thread::sleep; - -const FRAME: Duration = Duration::from_micros(8_000); -const RENDER_WAIT: Duration = Duration::from_micros(500_000); -const LINE_HEIGHT: f64 = 20.0; - -macro_rules! u16 { ($u32:expr) => { $u32.try_into().unwrap_or(u16::MAX) }; } - - -pub struct BedrockEmulator { - vm: Processor<StandardDevices>, - title: String, - initialising: bool, - sleeping: bool, - pixel_scale: u32, - fullscreen: bool, - start_of_process: Instant, - end_of_render: Instant, - debug_mark: Instant, - cycles_elapsed: usize, -} - -impl BedrockEmulator { - pub fn new(bytecode: &[u8]) -> Self { - let mut vm = Processor::new(StandardDevices::new()); - vm.dev.screen.resize(ScreenDimensions::new(256, 192)); - vm.dev.memory.page_limit = 256; - vm.load_program(bytecode); - - Self { - vm, - title: String::from("Bedrock emulator"), - initialising: true, - sleeping: false, - pixel_scale: 3, - fullscreen: false, - start_of_process: Instant::now(), - end_of_render: Instant::now(), - debug_mark: Instant::now(), - cycles_elapsed: 0, - } - } - - pub fn with_title(mut self, title: &str) -> Self { - self.title = title.to_string(); - self - } - - pub fn run(self) -> ! { - let mut wm = WindowManager::new(); - wm.add_window(Box::new(self)); - wm.run(); - } - - pub fn debug(&mut self, variant: DebugVariant) { - self.vm.dev.stream.flush_local(); - - macro_rules! yellow {()=>{eprint!("\x1b[33m")};} - macro_rules! normal {()=>{eprint!("\x1b[0m")};} - macro_rules! print_stack { - ($stack:expr, $len:expr) => { - for i in 0..$len { - if i == $stack.sp as usize { yellow!(); } else { normal!(); } - eprint!("{:02x} ", $stack.mem[i]); - } - normal!(); - }; - } - match variant { - DebugVariant::DB1 => { - eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", - self.vm.mem.pc, self.vm.cycles, - self.vm.cycles - self.cycles_elapsed, - self.debug_mark.elapsed()); - eprint!("WST: "); - print_stack!(self.vm.wst, 0x10); - eprint!("\nRST: "); - print_stack!(self.vm.rst, 0x10); - eprintln!(); - } - DebugVariant::DB2 => { - eprintln!("{:>8.2?}ms ({:>8} cycles)", - self.debug_mark.elapsed().as_micros() as f64 / 1000.0, - self.vm.cycles - self.cycles_elapsed) - } - DebugVariant::DB3 => { - // Only resets the debug timer - } - DebugVariant::DB4 => { - if self.vm.wst.sp == 1 - && self.vm.rst.sp == 0 - && self.vm.wst.mem[0] == 0xff { - print!(".") - } else { - print!("X") - } - } - _ => (), - } - self.cycles_elapsed = self.vm.cycles; - self.debug_mark = Instant::now(); - } -} - -impl WindowController for BedrockEmulator { - fn title(&self) -> String { - self.title.clone() - } - - fn exact_size(&self) -> Option<Dimensions> { - match self.vm.dev.screen.resizable { - true => None, - false => Some(Dimensions::new( - self.vm.dev.screen.dimensions.width as u32, - self.vm.dev.screen.dimensions.height as u32, - )), - } - } - - fn fullscreen(&self) -> bool { - self.fullscreen - } - - fn cursor(&mut self) -> Option<CursorIcon> { - let pos = self.vm.dev.input.pointer_position; - let dim = self.vm.dev.screen.dimensions; - match pos.x >= dim.width || pos.y >= dim.height { - true => Some(CursorIcon::Default), - false => None, - } - } - - fn pixel_scale(&self) -> NonZeroU32 { - NonZeroU32::new(self.pixel_scale).unwrap() - } - - fn on_resize(&mut self, dimensions: Dimensions) { - let width = u16!(dimensions.width); - let height = u16!(dimensions.height); - self.vm.dev.screen.resize(ScreenDimensions { width, height }); - self.initialising = false; - } - - fn on_cursor_move(&mut self, position: Point) { - let x = position.x as u16; - let y = position.y as u16; - self.vm.dev.input.on_pointer_move(ScreenPosition::new(x, y)); - self.vm.dev.input.pointer_active = true; - } - - fn on_cursor_enter(&mut self) { - self.vm.dev.input.pointer_active = true; - self.vm.dev.input.wake_flag = true; - } - - fn on_cursor_exit(&mut self) { - self.vm.dev.input.pointer_active = false; - self.vm.dev.input.wake_flag = true; - } - - fn on_left_mouse_button(&mut self, action: Action) { - self.vm.dev.input.on_pointer_button(0x80, action); - } - - fn on_middle_mouse_button(&mut self, action: Action) { - self.vm.dev.input.on_pointer_button(0x20, action); - } - - fn on_right_mouse_button(&mut self, action: Action) { - self.vm.dev.input.on_pointer_button(0x40, action); - } - - fn on_line_scroll_horizontal(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_horizontal(delta); - } - - fn on_line_scroll_vertical(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_vertical(delta); - } - - fn on_pixel_scroll_horizontal(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_horizontal(delta / LINE_HEIGHT); - } - - fn on_pixel_scroll_vertical(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_vertical(delta / LINE_HEIGHT); - } - - fn on_character_input(&mut self, input: char) { - self.vm.dev.input.on_character_input(input); - } - - fn on_keyboard_input(&mut self, input: KeyboardInput) { - self.vm.dev.input.on_keyboard_input(input); - if input.action.is_pressed() { - match input.key { - KeyCode::F5 => self.pixel_scale = max(1, self.pixel_scale - 1), - KeyCode::F6 => self.pixel_scale = min(8, self.pixel_scale + 1), - KeyCode::F11 => self.fullscreen = !self.fullscreen, - _ => (), - } - } - } - - fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) { - self.vm.dev.input.on_modifier_change(modifiers); - } - - fn on_process(&mut self) { - // Prevent program starting before the window receives an initial size. - if self.initialising { - sleep(FRAME); - return; - } - - if self.sleeping { - // Pause the processor until the current frame is rendered. This - // prevents the current frame from being overdrawn before rendering. - if self.vm.dev.screen.dirty { - sleep(FRAME); - return; - } - // Ensure a minimum delay of FRAME between the start of consecutive - // process frames when asleep, unless a timer has expired. - let frame_start = self.start_of_process + FRAME; - let time_to_frame_start = frame_start.duration_since(Instant::now()); - if !time_to_frame_start.is_zero() { - let time_to_sleep = match self.vm.dev.clock.time_to_next_wake() { - Some(ms) => min(ms, time_to_frame_start), - None => time_to_frame_start, - }; - sleep(time_to_sleep); - return; - } - // Stay asleep if there are no pending wake events. - if !self.vm.dev.can_wake() { - sleep(FRAME); - return; - } - } - - // Run the processor for the remainder of the frame. - self.start_of_process = Instant::now(); - self.sleeping = false; - let frame_end = Instant::now() + FRAME; - - while Instant::now() < frame_end { - if let Some(signal) = self.vm.evaluate(1000) { - match signal { - Signal::Debug(var) => self.debug(var), - Signal::Sleep => { - self.sleeping = true; - break; - }, - Signal::Halt => { - self.vm.dev.stream.flush_local(); - self.vm.dev.file.flush_entry(); - exit(0); - }, - } - } - } - self.vm.dev.stream.flush_local(); - self.vm.dev.file.flush_entry(); - } - - fn render_request(&mut self) -> RenderRequest { - if self.vm.dev.screen.dirty { - match self.sleeping { - true => RenderRequest::UPDATE, - false => match self.end_of_render.elapsed() >= RENDER_WAIT { - true => RenderRequest::UPDATE, - false => RenderRequest::NONE, - } - } - } else { - self.end_of_render = Instant::now(); - RenderRequest::NONE - } - } - - fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) { - self.vm.dev.screen.render(buffer); - self.end_of_render = Instant::now(); - } -} diff --git a/src/emulators.rs b/src/emulators.rs new file mode 100644 index 0000000..68bf0ce --- /dev/null +++ b/src/emulators.rs @@ -0,0 +1,27 @@ +mod headless_emulator; +mod graphical_emulator; + +pub use headless_emulator::{HeadlessEmulator, HeadlessDeviceBus}; +pub use graphical_emulator::{GraphicalEmulator, GraphicalDeviceBus}; + +use crate::*; + +use phosphor::Colour; + + +pub enum EmulatorSignal { + Promote, + Halt, +} + + +pub struct EmulatorConfig { + pub dimensions: ScreenDimensions, + pub fullscreen: bool, + pub scale: u32, + pub debug_palette: Option<[Colour; 16]>, + + pub initial_transmission: Option<Vec<u8>>, + pub encode_stdin: bool, + pub encode_stdout: bool, +} diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs new file mode 100644 index 0000000..1e048de --- /dev/null +++ b/src/emulators/graphical_emulator.rs @@ -0,0 +1,374 @@ +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, + pub fullscreen: bool, + pub scale: u32, + pub render_mark: Instant, + pub debug_palette: Option<[Colour; 16]>, + pub show_debug_palette: bool, +} + +impl GraphicalEmulator { + pub fn new(config: &EmulatorConfig, debug: bool) -> Self { + let devices = GraphicalDeviceBus::new(config); + Self { + br: BedrockEmulator::new(devices), + debug: DebugState::new(debug), + dimensions: config.dimensions, + fullscreen: config.fullscreen, + scale: config.scale, + render_mark: Instant::now(), + debug_palette: config.debug_palette, + show_debug_palette: config.debug_palette.is_some(), + } + } + + pub fn load_program(&mut self, bytecode: &[u8]) { + 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.debug.print("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 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_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), + }, + + } + } + + pub fn dimensions(&self) -> Dimensions { + Dimensions { + width: u32::from(self.br.dev.scr.dimensions.width), + height: u32::from(self.br.dev.scr.dimensions.height), + } + } +} + + +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::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_horizontal_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_horizontal_scroll(distance / 20.0), + } + Event::FileDrop(_path) => todo!("FileDrop"), + + Event::Close => (), + Event::KeyboardInput { key, action } => { + self.br.dev.inp.on_keypress(key, action); + if action == Action::Pressed { + match key { + KeyCode::F2 => { + self.show_debug_palette = !self.show_debug_palette; + r.write(Request::Redraw); + }, + KeyCode::F5 => { + self.scale = std::cmp::max(1, self.scale.saturating_sub(1)); + r.write(Request::SetPixelScale(self.scale)); + }, + KeyCode::F6 => { + self.scale = self.scale.saturating_add(1); + r.write(Request::SetPixelScale(self.scale)); + }, + KeyCode::F11 => { + self.fullscreen = !self.fullscreen; + r.write(Request::SetFullscreen(self.fullscreen)); + }, + _ => (), + } + } + } + } + } + + fn process(&mut self, requests: &mut EventWriter<Request>) { + self.br.dev.loc.flush(); + + if self.br.dev.sys.asleep { + // Stay asleep if there are no pending wake events. + if !self.br.dev.wake() { + if self.br.dev.scr.dirty { + requests.write(Request::Redraw); + } + std::thread::sleep(MIN_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); + return; + } + } + + // Run the processor for the remainder of the frame. + let frame_end = Instant::now() + MIN_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.debug.print("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 std::mem::take(&mut self.br.dev.scr.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 { + requests.write(Request::Redraw); + } else if elapsed > MAX_FRAME_DURATION { + requests.write(Request::Redraw); + } + } else { + self.render_mark = Instant::now(); + } + } + + fn render(&mut self, buffer: &mut Buffer, _full: bool) { + let screen = &mut self.br.dev.scr; + + // 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, + false => screen.palette, + } + None => screen.palette, + }; + table[0..16].clone_from_slice(&palette); + for i in 1..16 { table[i*16..(i+1)*16].fill(palette[i]); } + + // Copy pixels to buffer when it is the same size as the screen. + if buffer.area_usize() == screen.area_usize() { + for (i, colour) in buffer.iter_mut().enumerate() { + let fg = screen.fg[i]; + 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. + } else { + let buffer_width = buffer.width() as usize; + let buffer_height = buffer.height() as usize; + let screen_width = screen.width() as usize; + let screen_height = screen.height() as usize; + let width = std::cmp::min(buffer_width, screen_width ); + let height = std::cmp::min(buffer_height, screen_height); + + let mut bi = 0; + let mut si = 0; + for _ in 0..height { + let bi_next = bi + buffer_width; + let si_next = si + screen_width; + for _ in 0..width { + let fg = screen.fg[si]; + let bg = screen.bg[si]; + let index = unsafe { fg.unchecked_shl(4) | bg }; + buffer[bi] = table[index as usize]; + bi += 1; + si += 1; + } + // Fill remaining right edge with background colour. + buffer[bi..bi_next].fill(table[0]); + bi = bi_next; + si = si_next; + } + // Fill remaining bottom edge with background colour. + buffer[bi..].fill(table[0]); + } + + screen.dirty = false; + self.render_mark = Instant::now(); + } +} diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs new file mode 100644 index 0000000..418ea9c --- /dev/null +++ b/src/emulators/headless_emulator.rs @@ -0,0 +1,124 @@ +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, +} + +impl HeadlessDeviceBus { + pub fn new(config: &EmulatorConfig) -> 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(), + } + } +} + +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 + } + } + + 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 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!(0x3, clk); + rouse!(0x2, mat); + rouse!(0x1, mem); + rouse!(0x0, sys); + return false; + } +} + + +pub struct HeadlessEmulator { + pub br: BedrockEmulator<HeadlessDeviceBus>, + pub debug: DebugState, +} + +impl HeadlessEmulator { + pub fn new(config: &EmulatorConfig, debug: bool) -> Self { + Self { + br: BedrockEmulator::new(HeadlessDeviceBus::new(config)), + debug: DebugState::new(debug), + } + } + + pub fn load_program(&mut self, bytecode: &[u8]) { + self.br.core.mem.load_program(bytecode); + } + + 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.debug.print("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); + } + _ => (), + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4ff35eb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,34 @@ +#![feature(bigint_helper_methods)] +#![feature(unchecked_shifts)] +#![feature(seek_stream_len)] +#![feature(io_error_more)] + +mod debug; +mod devices; +mod emulators; +mod metadata; + +pub use debug::DebugState; +pub use devices::*; +pub use emulators::*; +pub use metadata::*; + +use std::num::NonZeroU32; +use std::time::Duration; +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 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) }; + +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)*); + }}; +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 4416f7d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![feature(bigint_helper_methods)] -#![feature(io_error_more)] -#![feature(split_array)] -#![feature(seek_stream_len)] - -use std::io::Read; -use std::process::exit; - -mod devices; -mod emulator; - -pub use devices::*; -pub use emulator::*; - -fn main() { - // Read bytecode from standard input - let mut bytecode: Vec<u8> = Vec::new(); - match std::io::stdin().take(64*1024).read_to_end(&mut bytecode) { - Ok(len) => eprintln!("Loaded {len} bytes of bytecode."), - Err(err) => { - eprintln!("Could not read from standard input, quitting."); - eprintln!("({err:?})"); - exit(1); - } - }; - BedrockEmulator::new(&bytecode).run(); -} - diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..7130517 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,136 @@ +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_string(self.string(self.double(0x10))); + let authors = self.string(self.double(0x12)).map(|string| { + string.lines().map(|line| { + let (name, year) = split_string(Some(line.to_string())); + Author { name: name.unwrap(), year } + }).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_string(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<Author>>, + 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>, +} + + +pub struct Author { + pub name: String, + pub year: Option<String>, +} |