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