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