summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-10-28 20:25:01 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2024-10-28 20:29:12 +1300
commit1a830a3d1b9d99653322d5ae49ea8165de7ed9d0 (patch)
tree798e77b6fcf2438b1c2538a67efe856a2f7cb979
parent03c4b069e1806af256730639cefdae115b24401a (diff)
downloadbedrock-pc-1.0.0-alpha1.zip
Rewrite emulatorv1.0.0-alpha1
This is a complete rewrite and restructure of the entire emulator project, as part of the effort in locking down the Bedrock specification and in creating much better tooling for creating and using Bedrock programs. This commit adds a command-line argument scheme, an embedded assembler, a headless emulator for use in non-graphical environments, deferred window creation for programs that do not access the screen device, and new versions of phosphor and bedrock-core. The new version of phosphor supports multi-window programs, which will make it possible to implement program forking in the system device later on, and the new version of bedrock-core implements the final core specification.
-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>,
+}