diff --git a/.ignore b/.ignore new file mode 100644 index 0000000000..d16386367f --- /dev/null +++ b/.ignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 35c2f6ea2c..2f2dab80ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,16 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" dependencies = [ - "gimli", + "gimli 0.25.0", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli 0.28.0", ] [[package]] @@ -163,14 +172,22 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" name = "async_channel" version = "0.1.0" dependencies = [ - "core2", - "crossbeam-utils", - "debugit", - "log", + "async_wait_queue", + "dreadnought", + "futures", "mpmc", "sync", "sync_spin", - "wait_queue", +] + +[[package]] +name = "async_wait_queue" +version = "0.1.0" +dependencies = [ + "dreadnought", + "mpmc_queue", + "sync", + "sync_spin", ] [[package]] @@ -211,13 +228,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "backtrace" version = "0.3.64" dependencies = [ - "addr2line", + "addr2line 0.16.0", "cc", "cfg-if 1.0.0", "libc", "memory", - "miniz_oxide", - "object", + "miniz_oxide 0.4.4", + "object 0.28.4", "rustc-demangle", "spin 0.9.4", "stack_trace", @@ -226,6 +243,21 @@ dependencies = [ "thread_local_macro", ] +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line 0.21.0", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide 0.7.1", + "object 0.32.1", + "rustc-demangle", +] + [[package]] name = "bare-metal" version = "0.2.5" @@ -320,7 +352,6 @@ version = "0.1.0" dependencies = [ "apic", "app_io", - "async_channel", "cpu", "fs_node", "getopts", @@ -330,6 +361,7 @@ dependencies = [ "log", "memory", "mod_mgmt", + "mpmc_channel", "path", "pmu_x86", "rendezvous", @@ -376,6 +408,7 @@ dependencies = [ "acpi", "app_io", "cls_allocator", + "compositor", "console", "cpu", "device_manager", @@ -384,6 +417,7 @@ dependencies = [ "early_printer", "exceptions_full", "first_application", + "graphics", "interrupt_controller", "interrupts", "irq_safety", @@ -406,7 +440,6 @@ dependencies = [ "time", "tlb_shootdown", "tsc", - "window_manager", ] [[package]] @@ -509,7 +542,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.32", ] [[package]] @@ -520,8 +553,20 @@ version = "0.1.0" name = "compositor" version = "0.1.0" dependencies = [ - "framebuffer", - "shapes", + "async_channel", + "dreadnought", + "futures", + "graphics", + "hashbrown", + "keyboard", + "log", + "memory", + "mouse", + "spin 0.9.4", + "sync_spin", + "time", + "waker", + "zerocopy", ] [[package]] @@ -529,12 +574,12 @@ name = "console" version = "0.1.0" dependencies = [ "app_io", - "async_channel", "core2", "hull", "io", "log", "mod_mgmt", + "mpmc_channel", "path", "serial_port", "spawn", @@ -812,7 +857,7 @@ dependencies = [ "by_address", "crate_metadata", "fs_node", - "gimli", + "gimli 0.25.0", "goblin", "hashbrown", "log", @@ -915,11 +960,11 @@ version = "0.1.0" dependencies = [ "acpi", "apic", + "async_channel", "console", "core2", "derive_more", "e1000", - "event_types", "fatfs", "io", "iommu", @@ -930,7 +975,6 @@ dependencies = [ "memory", "mlx5", "mouse", - "mpmc", "net", "pci", "ps2", @@ -953,16 +997,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "displayable" -version = "0.1.0" -dependencies = [ - "color", - "framebuffer", - "shapes", - "spin 0.9.4", -] - [[package]] name = "dmar" version = "0.1.0" @@ -980,6 +1014,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "draw" +version = "0.1.0" +dependencies = [ + "font", + "geometry", + "graphics", + "log", +] + [[package]] name = "dreadnought" version = "0.1.0" @@ -1065,15 +1109,6 @@ dependencies = [ "root", ] -[[package]] -name = "event_types" -version = "0.1.0" -dependencies = [ - "keycodes_ascii", - "mouse_data", - "shapes", -] - [[package]] name = "example" version = "0.1.0" @@ -1212,48 +1247,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "framebuffer" -version = "0.1.0" -dependencies = [ - "color", - "early_printer", - "log", - "memory", - "multicore_bringup", - "page_attribute_table", - "shapes", - "zerocopy", -] - -[[package]] -name = "framebuffer_compositor" -version = "0.1.0" -dependencies = [ - "compositor", - "framebuffer", - "hashbrown", - "shapes", - "spin 0.9.4", -] - -[[package]] -name = "framebuffer_drawer" -version = "0.1.0" -dependencies = [ - "framebuffer", - "shapes", -] - -[[package]] -name = "framebuffer_printer" -version = "0.1.0" -dependencies = [ - "font", - "framebuffer", - "shapes", -] - [[package]] name = "fs_node" version = "0.1.0" @@ -1267,9 +1260,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1281,9 +1274,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1291,44 +1284,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.98", + "syn 2.0.32", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-macro", @@ -1375,6 +1368,10 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "geometry" +version = "0.1.0" + [[package]] name = "getopts" version = "0.2.21" @@ -1413,6 +1410,12 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "goblin" version = "0.0.19" @@ -1423,6 +1426,19 @@ dependencies = [ "scroll", ] +[[package]] +name = "graphics" +version = "0.1.0" +dependencies = [ + "color", + "geometry", + "log", + "memory", + "page_attribute_table", + "time", + "zerocopy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1740,11 +1756,10 @@ version = "0.1.0" name = "keyboard" version = "0.1.0" dependencies = [ - "event_types", + "async_channel", "interrupts", "keycodes_ascii", "log", - "mpmc", "once_cell", "ps2", "spin 0.9.4", @@ -1797,8 +1812,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.127" -source = "git+https://github.com/theseus-os/libc?branch=theseus#5e1da08f39d9b25c649f1152e0084585b0adf725" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libm" @@ -1811,21 +1827,15 @@ name = "libterm" version = "0.1.0" dependencies = [ "color", + "compositor", "dfqueue", - "displayable", + "draw", "environment", - "event_types", "font", - "framebuffer", - "framebuffer_drawer", - "framebuffer_printer", + "geometry", "log", "root", - "shapes", - "text_display", "time", - "window", - "window_manager", ] [[package]] @@ -2106,6 +2116,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mkdir" version = "0.1.0" @@ -2218,11 +2237,10 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" name = "mouse" version = "0.1.0" dependencies = [ - "event_types", + "async_channel", "interrupts", "log", "mouse_data", - "mpmc", "ps2", "spin 0.9.4", "x86_64", @@ -2241,6 +2259,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf78b1242a953be96e01b5f8ed8ffdfc8055c0a2b779899b3835e5d27a69dced" +[[package]] +name = "mpmc_channel" +version = "0.1.0" +dependencies = [ + "core2", + "crossbeam-utils", + "debugit", + "log", + "mpmc", + "sync", + "sync_spin", + "wait_queue", +] + [[package]] name = "mpmc_queue" version = "0.1.0" @@ -2494,6 +2526,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.9.0" @@ -2970,12 +3011,12 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" dependencies = [ "bitflags 1.3.2", - "core2", "libc", "mach", - "memory", "winapi", ] @@ -3267,11 +3308,11 @@ dependencies = [ name = "serial_port" version = "0.1.0" dependencies = [ - "async_channel", "core2", "deferred_interrupt_tasks", "interrupts", "log", + "mpmc_channel", "serial_port_basic", "spin 0.9.4", "sync_irq", @@ -3298,19 +3339,13 @@ dependencies = [ "keccak", ] -[[package]] -name = "shapes" -version = "0.1.0" - [[package]] name = "shell" version = "0.1.0" dependencies = [ "app_io", "core2", - "dfqueue", "environment", - "event_types", "fs_node", "keycodes_ascii", "lazy_static", @@ -3323,7 +3358,7 @@ dependencies = [ "spin 0.9.4", "stdio", "task", - "window_manager", + "time", ] [[package]] @@ -3594,9 +3629,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -3661,8 +3696,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.5" -source = "git+https://github.com/theseus-os/target-lexicon?branch=theseus#75d36cc66df0ac4569df1b20a16ca914f417b85a" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "task" @@ -3740,7 +3776,7 @@ name = "test_backtrace" version = "0.1.0" dependencies = [ "app_io", - "backtrace", + "backtrace 0.3.64", "log", "task", ] @@ -3763,10 +3799,10 @@ name = "test_channel" version = "0.1.0" dependencies = [ "app_io", - "async_channel", "cpu", "getopts", "log", + "mpmc_channel", "rendezvous", "scheduler", "spawn", @@ -3944,19 +3980,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "text_display" -version = "0.1.0" -dependencies = [ - "color", - "displayable", - "font", - "framebuffer", - "framebuffer_printer", - "shapes", - "spin 0.9.4", -] - [[package]] name = "text_terminal" version = "0.1.0" @@ -3964,7 +3987,6 @@ dependencies = [ "bitflags 2.4.1", "core2", "derive_more", - "event_types", "log", "unicode-width", "vte", @@ -4128,8 +4150,8 @@ dependencies = [ name = "tty" version = "0.1.0" dependencies = [ - "async_channel", "core2", + "mpmc_channel", "sync_block", ] @@ -4182,8 +4204,8 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" name = "unified_channel" version = "0.1.0" dependencies = [ - "async_channel", "cfg-if 0.1.10", + "mpmc_channel", "rendezvous", ] @@ -4202,7 +4224,7 @@ version = "0.1.0" dependencies = [ "external_unwind_info", "fallible-iterator", - "gimli", + "gimli 0.25.0", "interrupts", "log", "memory", @@ -4462,17 +4484,15 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.81.0" -source = "git+https://github.com/theseus-os/wasm-tools?branch=no-std-wasmparser#7b0eb0d074606c8a49027e60e452862f5fe183b4" -dependencies = [ - "hashbrown", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" [[package]] name = "wasmtime" version = "0.30.0" dependencies = [ "anyhow", - "backtrace", + "backtrace 0.3.69", "bincode", "catch_unwind", "cfg-if 1.0.0", @@ -4483,7 +4503,7 @@ dependencies = [ "lazy_static", "libc", "log", - "object", + "object 0.28.4", "paste", "psm", "region", @@ -4507,12 +4527,12 @@ dependencies = [ "cfg-if 1.0.0", "core2", "cranelift-entity", - "gimli", + "gimli 0.25.0", "hashbrown", "indexmap", "log", "more-asserts", - "object", + "object 0.28.4", "serde", "target-lexicon", "thiserror_core2", @@ -4524,16 +4544,16 @@ dependencies = [ name = "wasmtime-jit" version = "0.30.0" dependencies = [ - "addr2line", + "addr2line 0.16.0", "anyhow", "bincode", "cfg-if 1.0.0", "core2", "external_unwind_info", - "gimli", + "gimli 0.25.0", "log", "more-asserts", - "object", + "object 0.28.4", "region", "serde", "target-lexicon", @@ -4550,7 +4570,7 @@ name = "wasmtime-runtime" version = "0.30.0" dependencies = [ "anyhow", - "backtrace", + "backtrace 0.3.69", "catch_unwind", "cc", "cfg-if 1.0.0", @@ -4610,61 +4630,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window" -version = "0.1.0" -dependencies = [ - "color", - "dereffer", - "event_types", - "framebuffer", - "framebuffer_drawer", - "log", - "mouse", - "mpmc", - "path", - "shapes", - "spawn", - "spin 0.9.4", - "window_inner", - "window_manager", -] - -[[package]] -name = "window_inner" -version = "0.1.0" -dependencies = [ - "event_types", - "framebuffer", - "mpmc", - "shapes", -] - -[[package]] -name = "window_manager" -version = "0.1.0" -dependencies = [ - "color", - "compositor", - "event_types", - "font", - "framebuffer", - "framebuffer_compositor", - "framebuffer_drawer", - "keycodes_ascii", - "lazy_static", - "log", - "mod_mgmt", - "mouse_data", - "mpmc", - "path", - "scheduler", - "shapes", - "spawn", - "spin 0.9.4", - "window_inner", -] - [[package]] name = "x86_64" version = "0.14.9" @@ -4710,3 +4675,8 @@ dependencies = [ "syn 1.0.98", "synstructure", ] + +[[patch.unused]] +name = "libc" +version = "0.2.127" +source = "git+https://github.com/theseus-os/libc?branch=theseus#5e1da08f39d9b25c649f1152e0084585b0adf725" diff --git a/Cargo.toml b/Cargo.toml index 6ac877dcce..2481a4f016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,15 +112,7 @@ libc = { git = "https://github.com/theseus-os/libc", branch = "theseus" } core2 = { path = "libs/core2" } ### Patch `bincode` because the version on crates.io doesn't handle no_std features correctly. bincode = { git = "https://github.com/bincode-org/bincode" } - -############################################################################################## -#################### Below are patches for wasmtime-related crates. ########################## -############################################################################################## -wasmparser = { git = "https://github.com/theseus-os/wasm-tools", branch = "no-std-wasmparser" } -backtrace = { path = "ports/backtrace" } -region = { path = "ports/region" } noline = { git = "https://github.com/theseus-os/noline", branch = "history-dedup" } -target-lexicon = { git = "https://github.com/theseus-os/target-lexicon", branch = "theseus" } ### These profiles fix the new rustc behavior of splitting one crate into many object files. ### That messes up our module loading, which is bad! diff --git a/applications/bm/Cargo.toml b/applications/bm/Cargo.toml index b18fa0d227..3848dffb8c 100644 --- a/applications/bm/Cargo.toml +++ b/applications/bm/Cargo.toml @@ -53,8 +53,8 @@ path = "../../kernel/memory" [dependencies.rendezvous] path = "../../kernel/rendezvous" -[dependencies.async_channel] -path = "../../kernel/async_channel" +[dependencies.mpmc_channel] +path = "../../kernel/mpmc_channel" [dependencies.simple_ipc] path = "../../kernel/simple_ipc" diff --git a/applications/bm/src/lib.rs b/applications/bm/src/lib.rs index a75331dd34..209f762149 100644 --- a/applications/bm/src/lib.rs +++ b/applications/bm/src/lib.rs @@ -23,7 +23,7 @@ extern crate scheduler; extern crate libtest; extern crate memory; extern crate rendezvous; -extern crate async_channel; +extern crate mpmc_channel; extern crate simple_ipc; extern crate getopts; extern crate pmu_x86; @@ -89,10 +89,10 @@ pub fn main(args: Vec) -> isize { opts.optflag("", "fs_delete", "file delete"); opts.optflag("", "fs", "test code for checking FS' ability"); - opts.optflag("a", "async", "Run IPC bm for the async channel"); + opts.optflag("m", "mpmc", "Run IPC bm for the mpmc channel"); opts.optflag("r", "rendezvous", "Run IPC bm for the rendezvous channel"); opts.optflag("p", "pinned", "Sender and Receiver should be pinned to the same core in the IPC bm"); - opts.optflag("b", "blocking", "Sender and Receiver should use blocking versions in the async IPC bm"); + opts.optflag("b", "blocking", "Sender and Receiver should use blocking versions in the mpmc IPC bm"); opts.optflag("c", "cycles", "Measure the IPC times in reference cycles (need to have a PMU for this option)"); @@ -157,8 +157,8 @@ pub fn main(args: Vec) -> isize { println!("RENDEZVOUS IPC"); do_ipc_rendezvous(pinned, cycles) } else if matches.opt_present("a") { - println!("ASYNC IPC"); - do_ipc_async(pinned, blocking, cycles) + println!("MPMC IPC"); + do_ipc_mpmc(pinned, blocking, cycles) } else { Err("Specify channel type to use") } @@ -720,9 +720,9 @@ fn rendezvous_task_receiver((sender, receiver): (rendezvous::Sender, rendezv } } -/// Measures the round trip time to send a 1-byte message on an async channel. -/// Calls `do_ipc_async_inner` multiple times to perform the actual operation -fn do_ipc_async(pinned: bool, blocking: bool, cycles: bool) -> Result<(), &'static str> { +/// Measures the round trip time to send a 1-byte message on an mpmc channel. +/// Calls `do_ipc_mpmc_inner` multiple times to perform the actual operation +fn do_ipc_mpmc(pinned: bool, blocking: bool, cycles: bool) -> Result<(), &'static str> { let child_core = if pinned { Some(CPU_ID!()) } else { @@ -738,9 +738,9 @@ fn do_ipc_async(pinned: bool, blocking: bool, cycles: bool) -> Result<(), &'stat for i in 0..TRIES { let lat = if cycles { - do_ipc_async_inner_cycles(i+1, TRIES, child_core, blocking)? + do_ipc_mpmc_inner_cycles(i+1, TRIES, child_core, blocking)? } else { - do_ipc_async_inner(i+1, TRIES, child_core, blocking)? + do_ipc_mpmc_inner(i+1, TRIES, child_core, blocking)? }; tries += lat; vec.push(lat); @@ -754,14 +754,14 @@ fn do_ipc_async(pinned: bool, blocking: bool, cycles: bool) -> Result<(), &'stat // We expect the maximum and minimum to be within 10*THRESHOLD_ERROR_RATIO % of the mean value let err = (lat * 10 * THRESHOLD_ERROR_RATIO) / 100; if max - lat > err || lat - min > err { - printlnwarn!("ipc_async_test diff is too big: {} ({} - {})", max-min, max, min); + printlnwarn!("ipc_mpmc_test diff is too big: {} ({} - {})", max-min, max, min); } let stats = calculate_stats(&vec).ok_or("couldn't calculate stats")?; if cycles { - printlninfo!("IPC ASYNC result: Round Trip Time: (cycles)",); + printlninfo!("IPC MPMC result: Round Trip Time: (cycles)",); } else { - printlninfo!("IPC ASYNC result: Round Trip Time: ({})", T_UNIT); + printlninfo!("IPC MPMC result: Round Trip Time: ({})", T_UNIT); } printlninfo!("{:?}", stats); printlninfo!("This test is equivalent to `lat_pipe` in LMBench when run with the pinned flag enabled"); @@ -772,13 +772,13 @@ fn do_ipc_async(pinned: bool, blocking: bool, cycles: bool) -> Result<(), &'stat /// Internal function that actually calculates the round trip time to send a message between two threads. /// This is measured by creating a child task, and sending messages between the parent and child. /// Overhead is measured by creating a task that just returns. -fn do_ipc_async_inner(th: usize, nr: usize, child_core: Option, blocking: bool) -> Result { +fn do_ipc_mpmc_inner(th: usize, nr: usize, child_core: Option, blocking: bool) -> Result { let hpet = get_hpet().ok_or("Could not retrieve hpet counter")?; - let (sender_task, receiver_task): (fn((async_channel::Sender, async_channel::Receiver)), fn((async_channel::Sender, async_channel::Receiver))) = if blocking { - (async_task_sender, async_task_receiver) + let (sender_task, receiver_task): (fn((mpmc_channel::Sender, mpmc_channel::Receiver)), fn((mpmc_channel::Sender, mpmc_channel::Receiver))) = if blocking { + (mpmc_task_sender, mpmc_task_receiver) } else { - (async_task_sender_nonblocking, async_task_receiver_nonblocking) + (mpmc_task_sender_nonblocking, mpmc_task_receiver_nonblocking) }; // we first spawn one task to get the overhead of creating and joining the task @@ -808,8 +808,8 @@ fn do_ipc_async_inner(th: usize, nr: usize, child_core: Option, blocking: // which is 16 4 KiB-pages, or 65,536 bytes. const CAPACITY: usize = 65536; - let (sender1, receiver1) = async_channel::new_channel(CAPACITY); - let (sender2, receiver2) = async_channel::new_channel(CAPACITY); + let (sender1, receiver1) = mpmc_channel::new_channel(CAPACITY); + let (sender2, receiver2) = mpmc_channel::new_channel(CAPACITY); let taskref1; @@ -845,14 +845,14 @@ fn do_ipc_async_inner(th: usize, nr: usize, child_core: Option, blocking: /// Internal function that actually calculates the round trip time to send a message between two threads. /// This is measured by creating a child task, and sending messages between the parent and child. /// Overhead is measured by creating a task that just returns. -fn do_ipc_async_inner_cycles(th: usize, nr: usize, child_core: Option, blocking: bool) -> Result { +fn do_ipc_mpmc_inner_cycles(th: usize, nr: usize, child_core: Option, blocking: bool) -> Result { pmu_x86::init()?; let mut counter = start_counting_reference_cycles()?; - let (sender_task, receiver_task): (fn((async_channel::Sender, async_channel::Receiver)), fn((async_channel::Sender, async_channel::Receiver))) = if blocking { - (async_task_sender, async_task_receiver) + let (sender_task, receiver_task): (fn((mpmc_channel::Sender, mpmc_channel::Receiver)), fn((mpmc_channel::Sender, mpmc_channel::Receiver))) = if blocking { + (mpmc_task_sender, mpmc_task_receiver) } else { - (async_task_sender_nonblocking, async_task_receiver_nonblocking) + (mpmc_task_sender_nonblocking, mpmc_task_receiver_nonblocking) }; // we first spawn one task to get the overhead of creating and joining the task @@ -883,8 +883,8 @@ fn do_ipc_async_inner_cycles(th: usize, nr: usize, child_core: Option, bl // which is 16 4 KiB-pages, or 65,536 bytes. const CAPACITY: usize = 65536; - let (sender1, receiver1) = async_channel::new_channel(CAPACITY); - let (sender2, receiver2) = async_channel::new_channel(CAPACITY); + let (sender1, receiver1) = mpmc_channel::new_channel(CAPACITY); + let (sender2, receiver2) = mpmc_channel::new_channel(CAPACITY); let taskref1; @@ -916,25 +916,25 @@ fn do_ipc_async_inner_cycles(th: usize, nr: usize, child_core: Option, bl } /// A task which sends and then receives a message for a number of iterations -fn async_task_sender((sender, receiver): (async_channel::Sender, async_channel::Receiver)) { +fn mpmc_task_sender((sender, receiver): (mpmc_channel::Sender, mpmc_channel::Receiver)) { let mut msg = 0; for _ in 0..ITERATIONS{ - sender.send(msg).expect("async channel task: could not send message!"); - msg = receiver.receive().expect("async channel task: could not receive message"); + sender.send(msg).expect("mpmc channel task: could not send message!"); + msg = receiver.receive().expect("mpmc channel task: could not receive message"); } } /// A task which receives and then sends a message for a number of iterations -fn async_task_receiver((sender, receiver): (async_channel::Sender, async_channel::Receiver)) { +fn mpmc_task_receiver((sender, receiver): (mpmc_channel::Sender, mpmc_channel::Receiver)) { let mut msg; for _ in 0..ITERATIONS{ - msg = receiver.receive().expect("async channel task: could not receive message"); - sender.send(msg).expect("async channel task: could not send message!"); + msg = receiver.receive().expect("mpmc channel task: could not receive message"); + sender.send(msg).expect("mpmc channel task: could not send message!"); } } /// A task which sends and then receives a message for a number of iterations -fn async_task_sender_nonblocking((sender, receiver): (async_channel::Sender, async_channel::Receiver)) { +fn mpmc_task_sender_nonblocking((sender, receiver): (mpmc_channel::Sender, mpmc_channel::Receiver)) { let mut msg = Ok(0); for _ in 0..ITERATIONS{ while sender.try_send(*msg.as_ref().unwrap()).is_err() {} @@ -946,7 +946,7 @@ fn async_task_sender_nonblocking((sender, receiver): (async_channel::Sender, } /// A task which receives and then sends a message for a number of iterations -fn async_task_receiver_nonblocking((sender, receiver): (async_channel::Sender, async_channel::Receiver)) { +fn mpmc_task_receiver_nonblocking((sender, receiver): (mpmc_channel::Sender, mpmc_channel::Receiver)) { let mut msg; for _ in 0..ITERATIONS{ msg = receiver.try_receive(); diff --git a/applications/loadc/src/lib.rs b/applications/loadc/src/lib.rs index e715705f7a..e2137a5912 100644 --- a/applications/loadc/src/lib.rs +++ b/applications/loadc/src/lib.rs @@ -16,10 +16,10 @@ extern crate rustc_demangle; extern crate mod_mgmt; extern crate task; extern crate xmas_elf; -extern crate libc; // for basic C types/typedefs used in libc use core::{ cmp::{min, max}, + ffi::c_int, ops::{AddAssign, SubAssign, Range}, }; use alloc::{collections::BTreeSet, string::{String, ToString}, sync::Arc, vec::Vec}; @@ -118,7 +118,6 @@ fn rmain(matches: Matches) -> Result { } /// Corresponds to C function: `int foo()` -use libc::c_int; type StartFunction = fn(args: &[&str], env: &[&str]) -> c_int; diff --git a/applications/shell/Cargo.toml b/applications/shell/Cargo.toml index 575a16a3bc..83e7bff83e 100644 --- a/applications/shell/Cargo.toml +++ b/applications/shell/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Andrew Pham ", "Zhiyao Ma "] [dependencies] spin = "0.9.4" core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } +time = { path = "../../kernel/time" } [dependencies.log] version = "0.4.8" @@ -15,22 +16,12 @@ version = "0.4.8" [dependencies.keycodes_ascii] path = "../../libs/keycodes_ascii" -[dependencies.dfqueue] -path = "../../libs/dfqueue" -version = "0.1.0" - -[dependencies.event_types] -path = "../../kernel/event_types" - [dependencies.spawn] path = "../../kernel/spawn" [dependencies.task] path = "../../kernel/task" -[dependencies.window_manager] -path = "../../kernel/window_manager" - [dependencies.environment] path = "../../kernel/environment" diff --git a/applications/shell/src/lib.rs b/applications/shell/src/lib.rs index ea8197dd84..f6169c2ce2 100644 --- a/applications/shell/src/lib.rs +++ b/applications/shell/src/lib.rs @@ -1,17 +1,14 @@ //! Shell with event-driven architecture //! Commands that can be run are the names of the crates in the applications directory -//! +//! //! The shell has the following responsibilities: handles key events delivered from terminal, manages terminal display, //! spawns and manages tasks, and records the history of executed user commands. #![no_std] extern crate keycodes_ascii; extern crate spin; -extern crate dfqueue; extern crate spawn; extern crate task; -extern crate event_types; -extern crate window_manager; extern crate path; extern crate root; extern crate scheduler; @@ -25,14 +22,12 @@ extern crate libterm; #[macro_use] extern crate alloc; #[macro_use] extern crate log; -use event_types::Event; use keycodes_ascii::{Keycode, KeyAction, KeyEvent}; use alloc::string::{String, ToString}; use alloc::vec::Vec; use path::Path; use task::{ExitValue, KillReason, JoinableTaskRef}; -use libterm::Terminal; -use dfqueue::{DFQueue, DFQueueConsumer, DFQueueProducer}; +use libterm::{Event, Terminal}; use alloc::sync::Arc; use spin::Mutex; use environment::Environment; @@ -98,7 +93,7 @@ pub fn main(_args: Vec) -> isize { Err(err) => { error!("{}", err); error!("failed to spawn shell"); - return -1; + return -1; } }; } @@ -123,12 +118,12 @@ pub fn main(_args: Vec) -> isize { // return 0; } -/// Errors when attempting to invoke an application from the terminal. +/// Errors when attempting to invoke an application from the terminal. enum AppErr { - /// The command does not match the name of any existing application in the - /// application namespace directory. + /// The command does not match the name of any existing application in the + /// application namespace directory. NotFound(String), - /// The terminal could not find the application namespace due to a filesystem error. + /// The terminal could not find the application namespace due to a filesystem error. NamespaceErr, /// The terminal could not spawn a new task to run the new application. /// Includes the String error returned from the task spawn function. @@ -158,11 +153,6 @@ struct Shell { /// When someone enters some commands, but before pressing `enter` it presses `up` to see previous commands, /// we must push it to command_history. We don't want to push it twice. buffered_cmd_recorded: bool, - /// The consumer to the terminal's print dfqueue - print_consumer: DFQueueConsumer, - /// The producer to the terminal's print dfqueue - #[allow(dead_code)] - print_producer: DFQueueProducer, /// The terminal's current environment env: Arc>, /// the terminal that is bind with the shell instance @@ -173,13 +163,6 @@ impl Shell { /// Create a new shell. Currently the shell will bind to the default terminal instance provided /// by the `app_io` crate. fn new() -> Result { - // Initialize a dfqueue for the terminal object to handle printing from applications. - // Note that this is only to support legacy output. Newly developed applications should - // turn to use `stdio` provided by the `stdio` crate together with the support of `app_io`. - let terminal_print_dfq: DFQueue = DFQueue::new(); - let print_consumer = terminal_print_dfq.into_consumer(); - let print_producer = print_consumer.obtain_producer(); - let key_event_queue: KeyEventQueue = KeyEventQueue::new(); let key_event_producer = key_event_queue.get_writer(); let key_event_consumer = key_event_queue.get_reader(); @@ -199,15 +182,13 @@ impl Shell { command_history: Vec::new(), history_index: 0, buffered_cmd_recorded: false, - print_consumer, - print_producer, env: Arc::new(Mutex::new(env)), terminal }) } /// Insert a character to the command line buffer in the shell. - /// The position to insert is determined by the position of the cursor in the terminal. + /// The position to insert is determined by the position of the cursor in the terminal. /// `sync_terminal` indicates whether the terminal screen will be synchronically updated. fn insert_char_to_cmdline(&mut self, c: char, sync_terminal: bool) -> Result<(), &'static str> { let mut terminal = self.terminal.lock(); @@ -237,7 +218,7 @@ impl Shell { if sync_terminal { self.terminal.lock().remove_char(cursor_offset_from_end)?; } - if !erase_left { + if !erase_left { self.update_cursor_pos(cursor_offset_from_end - 1)?; } Ok(()) @@ -320,7 +301,7 @@ impl Shell { self.update_cursor_pos(offset_from_end - 1)?; } self.terminal.lock().cursor.enable(); - + Ok(()) } @@ -335,7 +316,7 @@ impl Shell { terminal.update_cursor_pos(offset_from_end, self.cmdline.as_bytes()[self.cmdline.len() - offset_from_end]); } terminal.cursor.enable(); - + Ok(()) } @@ -381,10 +362,10 @@ impl Shell { Ok(()) } - fn handle_key_event(&mut self, keyevent: KeyEvent) -> Result<(), &'static str> { + fn handle_key_event(&mut self, keyevent: KeyEvent) -> Result<(), &'static str> { // EVERYTHING BELOW HERE WILL ONLY OCCUR ON A KEY PRESS (not key release) if keyevent.action != KeyAction::Pressed { - return Ok(()); + return Ok(()); } // Ctrl+C signals the shell to exit the job @@ -433,7 +414,7 @@ impl Shell { self.redisplay_prompt(); return Ok(()); } - + return Ok(()); } @@ -501,7 +482,7 @@ impl Shell { return Ok(()); } - // Attempts to run the command whenever the user presses enter and updates the cursor tracking variables + // Attempts to run the command whenever the user presses enter and updates the cursor tracking variables if keyevent.keycode == Keycode::Enter && keyevent.keycode.to_ascii(keyevent.modifiers).is_some() { let cmdline = self.cmdline.clone(); if cmdline.is_empty() && self.fg_job_num.is_none() { @@ -550,7 +531,7 @@ impl Shell { return Ok(()); } - // handle navigation keys: home, end, page up, page down, up arrow, down arrow + // handle navigation keys: home, end, page up, page down, up arrow, down arrow if keyevent.keycode == Keycode::Home && keyevent.modifiers.is_control() { return self.terminal.lock().move_screen_to_begin(); } @@ -636,7 +617,7 @@ impl Shell { let cmd_crate_name = format!("{cmd}-"); let mut matching_apps = namespace_dir.get_files_starting_with(&cmd_crate_name).into_iter(); let app_file = matching_apps.next(); - let second_match = matching_apps.next(); // return an error if there are multiple matching apps + let second_match = matching_apps.next(); // return an error if there are multiple matching apps let app_path = app_file.xor(second_match) .map(|f| f.lock().get_absolute_path()) .ok_or(AppErr::NotFound(cmd))?; @@ -647,7 +628,7 @@ impl Shell { .block() .spawn() .map_err(|e| AppErr::SpawnErr(e.to_string()))?; - + taskref.set_env(self.env.clone()); // Set environment variable of application to the same as terminal task // Gets the task id so we can reference this task if we need to kill it with Ctrl+C @@ -874,7 +855,7 @@ impl Shell { // Try to match the name of the file. let locked_working_dir = curr_wd.lock(); - let mut child_list = locked_working_dir.list(); + let mut child_list = locked_working_dir.list(); child_list.reverse(); for child in child_list.iter() { if child.starts_with(incomplete_node) { @@ -1172,16 +1153,6 @@ impl Shell { fn check_and_print_app_output(&mut self) -> bool { let mut need_refresh = false; - // Support for legacy output by `terminal_print`. - if let Some(print_event) = self.print_consumer.peek() { - if let Event::OutputEvent(ref s) = print_event.deref() { - self.terminal.lock().print_to_terminal(s.clone()); - } - print_event.mark_completed(); - // Goes to the next iteration of the loop after processing print event to ensure that printing is handled before keypresses - need_refresh = true; - } - let mut buf: [u8; 256] = [0; 256]; // iterate through all jobs to see if they have something to print @@ -1228,15 +1199,15 @@ impl Shell { /// This main loop is the core component of the shell's event-driven architecture. The shell receives events /// from two queues - /// + /// /// 1) The print queue handles print events from applications. The producer to this queue /// is any EXTERNAL application that prints to the terminal. - /// + /// /// 2) The input queue (provided by the window manager when the temrinal request a window) gives key events /// and resize event to the application. - /// + /// /// The print queue is handled first inside the loop iteration, which means that all print events in the print - /// queue will always be printed to the text display before input events or any other managerial functions are handled. + /// queue will always be printed to the text display before input events or any other managerial functions are handled. /// This allows for clean appending to the scrollback buffer and prevents interleaving of text. fn start(mut self) -> Result<(), &'static str> { let mut need_refresh = false; @@ -1270,41 +1241,25 @@ impl Shell { locked_terminal.get_event() } { match ev { - // Returns from the main loop. - Event::ExitEvent => { - trace!("exited terminal"); - return Ok(()); - } - - Event::WindowResizeEvent(new_position) => { + Event::Resize(new_position) => { self.terminal.lock().resize(new_position)?; // the above function also refreshes the terminal display } // Handles ordinary keypresses - Event::KeyboardEvent(ref input_event) => { - self.key_event_producer.write_one(input_event.key_event); + Event::Keyboard(input_event) => { + self.key_event_producer.write_one(input_event); } - - _unhandled => { + _unhandled => { // trace!("Shell is ignoring unhandled event: {:?}", _unhandled); } }; - } + } if need_refresh || need_refresh_on_task_event { // update if there are outputs from applications self.terminal.lock().refresh_display()?; } - let is_active = { - let term = self.terminal.lock(); - term.window.is_active() - }; - - if is_active { - self.terminal.lock().display_cursor()?; - } - // handle inputs need_refresh = false; loop { @@ -1324,8 +1279,10 @@ impl Shell { } } if need_refresh { + let start = time::Instant::now(); // update if there are inputs self.terminal.lock().refresh_display()?; + log::info!("total took: {:?}", time::Instant::now().duration_since(start)); } else { scheduler::schedule(); // yield the CPU if nothing to do } diff --git a/applications/test_channel/Cargo.toml b/applications/test_channel/Cargo.toml index 5b6e97354b..0bb7ae8e26 100644 --- a/applications/test_channel/Cargo.toml +++ b/applications/test_channel/Cargo.toml @@ -29,5 +29,5 @@ path = "../../kernel/spawn" [dependencies.rendezvous] path = "../../kernel/rendezvous" -[dependencies.async_channel] -path = "../../kernel/async_channel" +[dependencies.mpmc_channel] +path = "../../kernel/mpmc_channel" diff --git a/applications/test_channel/src/lib.rs b/applications/test_channel/src/lib.rs index b93cd9a261..56aefcd9a8 100644 --- a/applications/test_channel/src/lib.rs +++ b/applications/test_channel/src/lib.rs @@ -9,7 +9,7 @@ extern crate task; extern crate spawn; extern crate scheduler; extern crate rendezvous; -extern crate async_channel; +extern crate mpmc_channel; extern crate cpu; @@ -117,13 +117,13 @@ fn rmain(matches: Matches) -> Result<(), &'static str> { println!("Running rendezvous channel test in multiple mode."); rendezvous_test_multiple(send_count!(), receive_count!(), send_panic_point, receive_panic_point)?; - println!("Running asynchronous channel test in oneshot mode."); + println!("Running mpmc channel test in oneshot mode."); for _i in 0 .. iterations!() { - asynchronous_test_oneshot()?; + mpmc_test_oneshot()?; } - println!("Running asynchronous channel test in multiple mode."); - asynchronous_test_multiple(send_count!(), receive_count!(), send_panic_point, receive_panic_point)?; + println!("Running mpmc channel test in multiple mode."); + mpmc_test_multiple(send_count!(), receive_count!(), send_panic_point, receive_panic_point)?; Ok(()) } @@ -250,84 +250,84 @@ fn rendezvous_sender_task ((sender, iterations, panic_point): (rendezvous::Sende /// A simple test that spawns a sender & receiver task to send a single message /// Optionally can set panics at `send_panic` and `receive_panic` locations -fn asynchronous_test_oneshot() -> Result<(), &'static str> { +fn mpmc_test_oneshot() -> Result<(), &'static str> { let my_cpu = cpu::current_cpu(); - let (sender, receiver) = async_channel::new_channel(2); + let (sender, receiver) = mpmc_channel::new_channel(2); let t1 = spawn::new_task_builder(|_: ()| -> Result<(), &'static str> { - warn!("asynchronous_test_oneshot(): Entered sender task!"); + warn!("mpmc_test_oneshot(): Entered sender task!"); sender.send("hello").map_err(|error| { warn!("Sender task failed due to : {:?}", error); return "Sender task failed"; })?; Ok(()) }, ()) - .name(String::from("sender_task_asynchronous_oneshot")) + .name(String::from("sender_task_mpmc_oneshot")) .block(); let t1 = pin_task!(t1, my_cpu).spawn()?; let t2 = spawn::new_task_builder(|_: ()| -> Result<(), &'static str> { - warn!("asynchronous_test_oneshot(): Entered receiver task!"); + warn!("mpmc_test_oneshot(): Entered receiver task!"); let msg = receiver.receive().map_err(|error| { warn!("Receiver task failed due to : {:?}", error); return "Receiver task failed" })?; - warn!("asynchronous_test_oneshot(): Receiver got msg: {:?}", msg); + warn!("mpmc_test_oneshot(): Receiver got msg: {:?}", msg); Ok(()) }, ()) - .name(String::from("receiver_task_asynchronous_oneshot")) + .name(String::from("receiver_task_mpmc_oneshot")) .block(); let t2 = pin_task!(t2, my_cpu).spawn()?; - warn!("asynchronous_test_oneshot(): Finished spawning the sender and receiver tasks"); + warn!("mpmc_test_oneshot(): Finished spawning the sender and receiver tasks"); t2.unblock().unwrap(); t1.unblock().unwrap(); t1.join()?; t2.join()?; - warn!("asynchronous_test_oneshot(): Joined the sender and receiver tasks."); + warn!("mpmc_test_oneshot(): Joined the sender and receiver tasks."); Ok(()) } /// A simple test that spawns a sender & receiver task to send `send_count` and receive `receive_count` messages. -fn asynchronous_test_multiple(send_count: usize, receive_count: usize, send_panic: Option, receive_panic: Option) -> Result<(), &'static str> { +fn mpmc_test_multiple(send_count: usize, receive_count: usize, send_panic: Option, receive_panic: Option) -> Result<(), &'static str> { let my_cpu = cpu::current_cpu(); - let (sender, receiver) = async_channel::new_channel(2); + let (sender, receiver) = mpmc_channel::new_channel(2); - let t1 = spawn::new_task_builder(asynchronous_sender_task, (sender, send_count, send_panic)) - .name(String::from("sender_task_asynchronous")) + let t1 = spawn::new_task_builder(mpmc_sender_task, (sender, send_count, send_panic)) + .name(String::from("sender_task_mpmc")) .block(); let t1 = pin_task!(t1, my_cpu).spawn()?; - let t2 = spawn::new_task_builder(asynchronous_receiver_task, (receiver, receive_count, receive_panic)) - .name(String::from("receiver_task_asynchronous")) + let t2 = spawn::new_task_builder(mpmc_receiver_task, (receiver, receive_count, receive_panic)) + .name(String::from("receiver_task_mpmc")) .block(); let t2 = pin_task!(t2, my_cpu).spawn()?; - warn!("asynchronous_test_multiple(): Finished spawning the sender and receiver tasks"); + warn!("mpmc_test_multiple(): Finished spawning the sender and receiver tasks"); t2.unblock().unwrap(); t1.unblock().unwrap(); t1.join()?; t2.join()?; - warn!("asynchronous_test_multiple(): Joined the sender and receiver tasks."); + warn!("mpmc_test_multiple(): Joined the sender and receiver tasks."); Ok(()) } /// A simple receiver receiving `iterations` messages /// Optionally may panic after sending `panic_pont` messages -fn asynchronous_receiver_task ((receiver, iterations, panic_point): (async_channel::Receiver, usize, Option)) -> Result<(), &'static str> { - warn!("asynchronous_test(): Entered receiver task! Expecting to receive {} messages", iterations); +fn mpmc_receiver_task ((receiver, iterations, panic_point): (mpmc_channel::Receiver, usize, Option)) -> Result<(), &'static str> { + warn!("mpmc_test(): Entered receiver task! Expecting to receive {} messages", iterations); if panic_point.is_some(){ - warn!("asynchronous_test(): Panic will occur in receiver task at message {}",panic_point.unwrap()); + warn!("mpmc_test(): Panic will occur in receiver task at message {}",panic_point.unwrap()); } for i in 0..iterations { @@ -335,23 +335,23 @@ fn asynchronous_receiver_task ((receiver, iterations, panic_point): (async_chann warn!("Receiver task failed due to : {:?}", error); return "Receiver task failed" })?; - warn!("asynchronous_test(): Receiver got {:?} ({:03})", msg, i); + warn!("mpmc_test(): Receiver got {:?} ({:03})", msg, i); if panic_point == Some(i) { panic!("rendezvous_test() : User specified panic in receiver"); } } - warn!("asynchronous_test(): Done receiver task!"); + warn!("mpmc_test(): Done receiver task!"); Ok(()) } /// A simple sender sending `iterations` messages /// Optionally may panic after sending `panic_pont` messages -fn asynchronous_sender_task ((sender, iterations, panic_point): (async_channel::Sender, usize, Option)) -> Result<(), &'static str> { - warn!("asynchronous_test(): Entered sender task! Expecting to send {} messages", iterations); +fn mpmc_sender_task ((sender, iterations, panic_point): (mpmc_channel::Sender, usize, Option)) -> Result<(), &'static str> { + warn!("mpmc_test(): Entered sender task! Expecting to send {} messages", iterations); if panic_point.is_some(){ - warn!("asynchronous_test(): Panic will occur in sender task at message {}",panic_point.unwrap()); + warn!("mpmc_test(): Panic will occur in sender task at message {}",panic_point.unwrap()); } for i in 0..iterations { @@ -359,14 +359,14 @@ fn asynchronous_sender_task ((sender, iterations, panic_point): (async_channel:: warn!("Sender task failed due to : {:?}", error); return "Sender task failed"; })?; - warn!("asynchronous_test(): Sender sent message {:03}", i); + warn!("mpmc_test(): Sender sent message {:03}", i); if panic_point == Some(i) { panic!("rendezvous_test() : User specified panic in receiver"); } } - warn!("asynchronous_test(): Done sender task!"); + warn!("mpmc_test(): Done sender task!"); Ok(()) } diff --git a/kernel/_doc_root.rs b/kernel/_doc_root.rs index 7da2edb759..a3e8b1b975 100644 --- a/kernel/_doc_root.rs +++ b/kernel/_doc_root.rs @@ -24,7 +24,6 @@ //! * `ata_pio`: Support for ATA hard disks (IDE/PATA) using PIO (not DMA), and not SATA. //! * `captain`: The main driver of Theseus. Controls the loading and initialization of all subsystems and other crates. //! * `compositor`: The trait of a compositor. It composites a list of buffers to a final buffer. -//! * `event_types`: The types used for passing input and output events across the system. //! * `device_manager`: Code for handling the sequence required to initialize each driver. //! * `displayable`: Defines a displayable trait. A displayable can display itself in a framebuffer. //! * `text_display`: A text display is a displayable. It contains a block of text and can display in a framebuffer. diff --git a/kernel/async_channel/Cargo.toml b/kernel/async_channel/Cargo.toml index 8c2867dba3..6d152f01fe 100644 --- a/kernel/async_channel/Cargo.toml +++ b/kernel/async_channel/Cargo.toml @@ -1,32 +1,12 @@ [package] -authors = ["Kevin Boos "] name = "async_channel" -description = "Channel for asynchronous Inter-Task Communication via a bounded buffer" version = "0.1.0" +edition = "2021" [dependencies] -crossbeam-utils = { version = "0.8.12", default-features = false } +async_wait_queue = { path = "../async_wait_queue" } +dreadnought = { path = "../dreadnought" } +futures = { version = "0.3.28", default-features = false } mpmc = "0.1.6" - -[dependencies.log] -version = "0.4.8" - -[dependencies.debugit] -path = "../../libs/debugit" - -[dependencies.wait_queue] -path = "../wait_queue" - -[dependencies.sync] -path = "../../libs/sync" - -[dependencies.sync_spin] -path = "../../libs/sync_spin" - -[dependencies.core2] -version = "0.4.0" -default-features = false -features = ["alloc", "nightly"] - -[lib] -crate-type = ["rlib"] +sync = { path = "../../libs/sync" } +sync_spin = { path = "../../libs/sync_spin" } \ No newline at end of file diff --git a/kernel/async_channel/src/lib.rs b/kernel/async_channel/src/lib.rs index ae82aca872..9c6813a0e5 100644 --- a/kernel/async_channel/src/lib.rs +++ b/kernel/async_channel/src/lib.rs @@ -1,523 +1,161 @@ -//! An asynchronous channel for Inter-Task Communication (ITC) with an internal queue for buffering messages. -//! -//! This crate offers an asynchronous channel that allows multiple tasks -//! to exchange messages through the use of a bounded-capacity intermediate buffer. -//! Unlike the `rendezvous` channel, the sender and receiver do not need to rendezvous to send or receive data. -//! -//! Only `Send` types can be sent or received through the channel. -//! -//! This is not a zero-copy channel; to avoid copying large messages, -//! use a reference type like `Box` or another layer of indirection. - #![no_std] -extern crate alloc; -#[cfg(trace_channel)] #[macro_use] extern crate log; -#[cfg(trace_channel)] #[macro_use] extern crate debugit; -extern crate wait_queue; -extern crate mpmc; -extern crate crossbeam_utils; -extern crate core2; -extern crate sync; -extern crate sync_spin; +use core::{ + pin::Pin, + task::{Context, Poll}, +}; -use alloc::sync::Arc; -use mpmc::Queue as MpmcQueue; -use wait_queue::WaitQueue; -use crossbeam_utils::atomic::AtomicCell; -use core::sync::atomic::{AtomicUsize, Ordering}; +use async_wait_queue::WaitQueue; +use futures::stream::{FusedStream, Stream}; +use mpmc::Queue; use sync::DeadlockPrevention; use sync_spin::Spin; -/// Create a new channel that allows senders and receivers to -/// asynchronously exchange messages via an internal intermediary buffer. -/// -/// This channel's buffer has a bounded capacity of minimum size 2 messages, -/// and it must be a power of 2 due to the restrictions of the current MPMC queue type that is used. -/// The given `minimum_capacity` will be rounded up to the next largest power of 2, with a minimum value of 2. -/// -/// When the number of pending (buffered) messages is larger than the capacity, -/// the channel is considered full. -/// Depending on whether a non-blocking or blocking send function is invoked, -/// future attempts to send another message will either block or return a `Full` error -/// until the channel's buffer is drained by a receiver and space in the buffer becomes available. -/// -/// For the vast majority of use cases, this function is recommended way to create -/// a new channel, because there is no need to specify a deadlock prevention method. -/// To create a channel with different deadlock prevention, see [`new_channel_with()`]. -pub fn new_channel(minimum_capacity: usize) -> (Sender, Receiver) { - new_channel_with(minimum_capacity) -} - -/// Creates a new asynchronous channel with the specified deadlock prevention method. +/// A bounded, multi-producer, multi-consumer asynchronous channel. /// -/// See [`new_channel()`] for more details. +/// The channel can also be used outside of an asynchronous runtime with the +/// [`blocking_send`], and [`blocking_recv`] methods. /// -/// The asynchronous channel uses a wait queue internally and hence exposes a -/// deadlock prevention type parameter `P` that is [`Spin`] by default. -/// See [`WaitQueue`]'s documentation for more info on setting this type parameter. -pub fn new_channel_with( - minimum_capacity: usize, -) -> (Sender, Receiver) { - let channel = Arc::new(Channel { - queue: MpmcQueue::with_capacity(minimum_capacity), - waiting_senders: WaitQueue::new(), - waiting_receivers: WaitQueue::new(), - channel_status: AtomicCell::new(ChannelStatus::Connected), - sender_count: AtomicUsize::new(1), - receiver_count: AtomicUsize::new(1), - }); - ( - Sender { channel: channel.clone() }, - Receiver { channel }, - ) -} - -/// Indicates whether channel is Connected or Disconnected -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ChannelStatus { - /// Channel is working. Initially channel is created with Connected status. - Connected, - /// Set to Disconnected when Sender end is dropped. - SenderDisconnected, - /// Set to Disconnected when Receiver end is dropped. - ReceiverDisconnected, +/// [`blocking_send`]: Self::blocking_send +/// [`blocking_recv`]: Self::blocking_recv +#[derive(Clone)] +pub struct Channel +where + T: Send, + P: DeadlockPrevention, +{ + inner: Queue, + senders: WaitQueue

, + receivers: WaitQueue

, } -/// Error type for tracking different type of errors sender and receiver -/// can encounter. -#[derive(Debug, PartialEq)] -pub enum Error { - /// Occurs when a "try" operation would need to block to complete. +impl Channel +where + T: Send, + P: DeadlockPrevention, +{ + /// Creates a new channel. /// - /// I.e. `try_send` is performed on a full channel or `try_receive` is - /// performed on an empty channel. - WouldBlock, - /// Occurs when one end of channel is dropped - ChannelDisconnected, -} - -impl From for core2::io::Error { - fn from(e: Error) -> Self { - match e { - Error::WouldBlock => core2::io::ErrorKind::WouldBlock, - Error::ChannelDisconnected => core2::io::ErrorKind::BrokenPipe, - } - .into() - } -} - -/// The inner channel for asynchronous communication between `Sender`s and `Receiver`s. -/// -/// This struct is effectively a wrapper around a MPMC queue -/// with waitqueues for senders (producers) and receivers (consumers). -/// -/// This channel object is not Send/Sync or cloneable itself; -/// it can be shared across tasks using an `Arc`. -struct Channel { - queue: MpmcQueue, - waiting_senders: WaitQueue

, - waiting_receivers: WaitQueue

, - channel_status: AtomicCell, - sender_count: AtomicUsize, - receiver_count: AtomicUsize, -} - -// Ensure that `AtomicCell` is actually a lock-free atomic. -const _: () = assert!(AtomicCell::::is_lock_free()); - -impl Channel { - /// Returns true if the channel is disconnected. - #[inline(always)] - fn is_disconnected(&self) -> bool { - self.get_channel_status() != ChannelStatus::Connected - } - - /// Returns the channel's current status. - #[inline(always)] - fn get_channel_status(&self) -> ChannelStatus { - self.channel_status.load() - } - - /// Returns another `Sender` endpoint connected to the given channel. + /// The provided capacity dictates how many messages can be stored in the + /// queue before the sender blocks. /// - /// This increments the channel's sender count. - /// If there were previously no senders, the channel status is updated to `Connected`. - fn add_sender(channel: &Arc) -> Sender { - if channel.sender_count.fetch_add(1, Ordering::SeqCst) == 0 { - channel.channel_status.store(ChannelStatus::Connected); - } - Sender { channel: channel.clone() } - } - - /// Returns another `Receiver` endpoint connected to the given channel. + /// # Examples + /// + /// ``` + /// use async_channel::Channel; + /// + /// let channel = Channel::new(2); + /// + /// assert!(channel.try_send(1).is_ok()); + /// assert!(channel.try_send(2).is_ok()); + /// // The channel is full. + /// assert!(channel.try_send(3).is_err()); /// - /// This increments the channel's receiver count. - /// If there were previously no receivers, the channel status is updated to `Connected`. - fn add_receiver(channel: &Arc) -> Receiver { - if channel.receiver_count.fetch_add(1, Ordering::SeqCst) == 0 { - channel.channel_status.store(ChannelStatus::Connected); + /// assert_eq!(channel.try_recv(), Some(1)); + /// assert_eq!(channel.try_recv(), Some(2)); + /// assert!(channel.try_recv().is_none()); + /// ``` + // TODO: Is a capacity of 0 = rendezvous? + pub fn new(capacity: usize) -> Self { + Self { + inner: Queue::with_capacity(capacity), + senders: WaitQueue::new(), + receivers: WaitQueue::new(), } - Receiver { channel: channel.clone() } } -} - -/// The sender (transmit) side of a channel. -pub struct Sender { - channel: Arc>, -} -impl Clone for Sender { - /// Clones this `Sender`, returning another `Sender` connected to the same channel. + /// Sends `value`. /// - /// This increments the channel's sender count. - /// If there were previously no senders, the channel status is updated to `Connected`. - fn clone(&self) -> Self { - Channel::add_sender( &self.channel ) - } -} - -impl Sender { - /// Send a message, blocking until space in the channel's buffer is available. - /// - /// Returns `Ok(())` if the message was sent successfully, - /// otherwise returns an [`Error`]. - pub fn send(&self, msg: T) -> Result<(), Error> { - #[cfg(trace_channel)] - trace!("async_channel: sending msg: {:?}", debugit!(msg)); - // Fast path: attempt to send the message, assuming the buffer isn't full - let msg = match self.try_send(msg) { - // if successful return ok - Ok(()) => return Ok(()), - // if unsunccessful check whether it fails due to any other reason than channel being full - Err((returned_msg, channel_error)) => { - if channel_error != Error::WouldBlock { - return Err(channel_error); - } - returned_msg - }, - }; - - // Slow path: the buffer was full, so now we need to block until space becomes available. - // The code can move to this point only if fast path failed due to channel being full - // trace!("waiting for space to send..."); - - // Here we use an option to store the un-sent message outside of the `closure` - // so that we can repeatedly try to re-send it upon the next invocation of the `closure` - // (which happens when this sender task is notified in the future). - let mut msg = Some(msg); - - // This closure is invoked from within a locked context, so we cannot just call `try_send()` here - // because it will notify the receivers which can cause deadlock. - // Therefore, we need to perform the nofity action outside of this closure after it returns. - let mut closure = || { - let owned_msg = msg.take(); - let result = owned_msg.and_then(|m| match self.channel.queue.push(m) { + /// # Cancel safety + /// + /// This method is cancel safe, in that if it is dropped prior to + /// completion, `value` is guaranteed to have not been set. However, in that + /// case `value` will be dropped. + pub async fn send(&self, value: T) { + let mut temp = Some(value); + + self.senders + .wait_until(|| match self.inner.push(temp.take().unwrap()) { Ok(()) => { - // trace!("Sending in closure"); - // We wrap the result in Some() since `wait_until` progresses only when `Some` is returned. - Some(Ok(())) - }, - Err(returned_msg) => { - // Here: we (the sender) woke up and failed to send, - // so we save the returned message outside of the closure to retry later. - // trace!("try_send() failed, saving message {:?} for next retry.", debugit!(returned_msg)); - msg = Some(returned_msg); - None + self.receivers.notify_one(); + Some(()) } - }); - - if self.channel.is_disconnected() { - // trace!("Receiver Endpoint is dropped"); - // Here the receiver end has dropped. - // So we don't wait anymore in the waitqueue - Some(Err(Error::ChannelDisconnected)) - } else { - result - } - - }; - - // When `wait_until_mut` returns it can be either a successful send marked as Ok(Ok()), - // Error in the condition (channel disconnection) marked as Ok(Err()), - // or the wait_until runs into error (Err()) - let res = self.channel.waiting_senders.wait_until(&mut closure); - - // trace!("... sending space became available."); - - // If we successfully sent a message, we need to notify any waiting receivers. - // As stated above, to avoid deadlock, this must be done here rather than in the above closure. - if res.is_ok() { - // trace!("successful send() is notifying receivers."); - self.channel.waiting_receivers.notify_one(); - } - res - } - - /// Sends a slice of objects through the channel, returning how many objects were sent. - /// - /// This method only blocks on the first object being sent. - pub fn send_buf(&self, buf: &[T]) -> Result - where - T: Copy - { - for (idx, item) in buf.iter().enumerate() { - if idx == 0 { - self.send(*item)?; - } else { - match self.try_send(*item) { - Ok(_) => {}, - Err((_, Error::WouldBlock)) => return Ok(idx), - Err((_, e)) => return Err(e), + Err(value) => { + temp = Some(value); + None } - } - } - Ok(buf.len()) + }) + .await } - /// Attempts to send an entire slice of objects through the channel. - pub fn send_all(&self, buf: &[T]) -> Result<(), Error> - where - T: Copy - { - for item in buf.iter() { - self.send(*item)?; - } - Ok(()) - } - - /// Tries to send the message, only succeeding if buffer space is available. - /// - /// If no buffer space is available, it returns the `msg` with `Error` back to the caller without blocking. - pub fn try_send(&self, msg: T) -> Result<(), (T, Error)> { - // first we'll check whether the channel is active - match self.channel.get_channel_status() { - ChannelStatus::SenderDisconnected => { - self.channel.channel_status.store(ChannelStatus::Connected); - }, - ChannelStatus::ReceiverDisconnected => { - return Err((msg, Error::ChannelDisconnected)); - }, - _ => {}, - } - - match self.channel.queue.push(msg) { - // successfully sent - Ok(()) => { - // trace!("successful try_send() is notifying receivers."); - self.channel.waiting_receivers.notify_one(); - Ok(()) - } - // queue was full, return message back to caller - Err(returned_msg) => Err((returned_msg, Error::WouldBlock)), - } - } - - /// Sends a slice of objects through the channel, returning how many objects were sent. + /// Tries to send `value`. /// - /// This method does not block. - pub fn try_send_buf(&self, buf: &[T]) -> Result - where - T: Copy - { - for (idx, item) in buf.iter().enumerate() { - if idx == 0 { - self.try_send(*item).map_err(|(_, e)| e)?; - } else { - match self.try_send(*item) { - Ok(_) => {}, - Err((_, Error::WouldBlock)) => return Ok(idx), - Err((_, e)) => return Err(e), - } - } - } - Ok(buf.len()) - } - - /// Returns true if the channel is disconnected. - pub fn is_disconnected(&self) -> bool { - self.channel.is_disconnected() - } - - /// Obtain a `Receiver` endpoint connected to the same channel as this `Sender`. - pub fn receiver(&self) -> Receiver { - Channel::add_receiver( &self.channel ) - } -} - -/// The receiver side of a channel. -pub struct Receiver { - channel: Arc>, -} - -impl Clone for Receiver { - /// Clones this `Receiver`, returning another `Receiver` connected to the same channel. + /// # Errors /// - /// This increments the channel's receiver count. - /// If there were previously no receivers, the channel status is updated to `Connected`. - fn clone(&self) -> Self { - Channel::add_receiver( &self.channel ) + /// Returns an error containing `value` if the channel was full. + pub fn try_send(&self, value: T) -> Result<(), T> { + self.inner.push(value)?; + self.receivers.notify_one(); + Ok(()) } -} -impl Receiver { - /// Receive a message, blocking until a message is available in the buffer. - /// - /// Returns the message if it was received properly, otherwise returns an [`Error`]. - pub fn receive(&self) -> Result { - // trace!("async_channel: receive() entry"); - // Fast path: attempt to receive a message, assuming the buffer isn't empty - // The code progresses beyond this match only if try_receive fails due to - // empty channel - match self.try_receive() { - Err(Error::WouldBlock) => {}, - x => { - #[cfg(trace_channel)] - trace!("async_channel: received msg: {:?}", debugit!(x)); - return x; - } - }; - - // Slow path: the buffer was empty, so we need to block until a message is sent. - // trace!("waiting to receive a message..."); - - // This closure is invoked from within a locked context, so we cannot just call `try_receive()` here - // because it will notify the receivers which can cause deadlock. - // Therefore, we need to perform the nofity action outside of this closure after it returns - // Closure would output the message if received or an error if channel is disconnected. - // It would output `None` if neither happens, resulting in waiting in the queue. - let closure = || { - match self.channel.queue.pop() { - Some(msg) => Some(Ok(msg)), - _ => { - if self.channel.is_disconnected() { - Some(Err(Error::ChannelDisconnected)) - } else { - None - } - }, - } - }; - - // When wait returns it can be either a successful receiver marked as Ok(Ok(msg)), - // Error in wait condition marked as Ok(Err(error)), - // or the wait_until runs into error (Err()) - let res = self.channel.waiting_receivers.wait_until(&closure); - // trace!("... received msg."); - - // If we successfully received a message, we need to notify any waiting senders. - // As stated above, to avoid deadlock, this must be done here rather than in the above closure. - if let Ok(ref _msg) = res { - // trace!("async_channel: successful receive() is notifying senders."); - self.channel.waiting_senders.notify_one(); - } - - #[cfg(trace_channel)] - trace!("async_channel: received msg: {:?}", debugit!(res)); - - res + /// Blocks the current thread until `value` is sent. + pub fn blocking_send(&self, value: T) { + dreadnought::block_on(self.send(value)) } - /// Receives objects placing them in a buffer and returning the number of objects received. + /// Receives the next value. /// - /// This method only blocks on the first object being received. - pub fn receive_buf(&self, buf: &mut [T]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - let mut byte = self.receive()?; - let mut read = 0; - - loop { - buf[read] = byte; - read += 1; - - if read == buf.len() { - return Ok(read); - } - - byte = match self.try_receive() { - Ok(b) => b, - Err(Error::WouldBlock) => return Ok(read), - Err(e) => return Err(e), - }; - } - } - - /// Tries to receive a message, only succeeding if a message is already available in the buffer. - /// - /// If receive succeeds returns `Some(Ok(T))`. - /// If an endpoint is disconnected returns `Some(Err(ChannelStatus::Disconnected))`. - /// If no such message exists, it returns `None` without blocking - pub fn try_receive(&self) -> Result { - if let Some(msg) = self.channel.queue.pop() { - // trace!("successful try_receive() is notifying senders."); - self.channel.waiting_senders.notify_one(); - Ok(msg) - } else { - // We check whether the channel is disconnected - match self.channel.get_channel_status() { - ChannelStatus::ReceiverDisconnected => { - self.channel.channel_status.store(ChannelStatus::Connected); - Err(Error::WouldBlock) - }, - ChannelStatus::SenderDisconnected => { - Err(Error::ChannelDisconnected) - }, - _ => { - Err(Error::WouldBlock) - }, - } - } - } - - /// Receives objects placing them in a buffer and returning the number of objects received. + /// # Cancel safety /// - /// This method does not block. - pub fn try_receive_buf(&self, buf: &mut [T]) -> Result { - for (idx, item) in buf.iter_mut().enumerate() { - *item = match self.try_receive() { - Ok(byte) => byte, - Err(Error::WouldBlock) => return Ok(idx + 1), - Err(e) => return Err(e), - }; - } - Ok(buf.len()) + /// This method is cancel safe. + pub async fn recv(&self) -> T { + let value = self.receivers.wait_until(|| self.inner.pop()).await; + self.senders.notify_one(); + value } - /// Returns true if the channel is disconnected. - pub fn is_disconnected(&self) -> bool { - self.channel.is_disconnected() + /// Tries to receive the next value. + pub fn try_recv(&self) -> Option { + let value = self.inner.pop()?; + self.senders.notify_one(); + Some(value) } - /// Obtain a `Sender` endpoint connected to the same channel as this `Receiver`. - pub fn sender(&self) -> Sender { - Channel::add_sender( &self.channel ) + /// Blocks the current thread until a value is received. + pub fn blocking_recv(&self) -> T { + dreadnought::block_on(self.recv()) } } - -/// When the only remaining `Receiver` is dropped, we mark the channel as disconnected -/// and notify all of the `Senders` -impl Drop for Receiver { - fn drop(&mut self) { - // trace!("Dropping a receiver"); - if self.channel.receiver_count.fetch_sub(1, Ordering::SeqCst) == 1 { - self.channel.channel_status.store(ChannelStatus::ReceiverDisconnected); - self.channel.waiting_senders.notify_all(); +impl Stream for Channel +where + T: Send, + P: DeadlockPrevention, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + match self + .receivers + .poll_wait_until(ctx, &mut || self.inner.pop()) + { + Poll::Ready(value) => { + self.senders.notify_one(); + Poll::Ready(Some(value)) + } + Poll::Pending => Poll::Pending, } } } -/// When the only remaining `Sender` is dropped, we mark the channel as disconnected -/// and notify all of the `Receivers` -impl Drop for Sender { - fn drop(&mut self) { - // trace!("Dropping a sender"); - if self.channel.sender_count.fetch_sub(1, Ordering::SeqCst) == 1 { - self.channel.channel_status.store(ChannelStatus::SenderDisconnected); - self.channel.waiting_receivers.notify_all(); - } +impl FusedStream for Channel +where + T: Send, + P: DeadlockPrevention, +{ + fn is_terminated(&self) -> bool { + // NOTE: If we ever implement disconnections, this will need to be modified. + false } } diff --git a/kernel/async_wait_queue/Cargo.toml b/kernel/async_wait_queue/Cargo.toml new file mode 100644 index 0000000000..a599ce2b5e --- /dev/null +++ b/kernel/async_wait_queue/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "async_wait_queue" +version = "0.1.0" +edition = "2021" + +[dependencies] +dreadnought = { path = "../dreadnought" } +mpmc_queue = { path = "../../libs/mpmc_queue" } +sync = { path = "../../libs/sync" } +sync_spin = { path = "../../libs/sync_spin" } diff --git a/kernel/async_wait_queue/src/lib.rs b/kernel/async_wait_queue/src/lib.rs new file mode 100644 index 0000000000..9a290903f1 --- /dev/null +++ b/kernel/async_wait_queue/src/lib.rs @@ -0,0 +1,88 @@ +#![allow(clippy::new_without_default)] +#![no_std] + +extern crate alloc; + +use core::{ + future::poll_fn, + task::{Context, Poll, Waker}, +}; + +use alloc::sync::Arc; +use mpmc_queue::Queue; +use sync::DeadlockPrevention; +use sync_spin::Spin; + +#[derive(Clone)] +pub struct WaitQueue

+where + P: DeadlockPrevention, +{ + inner: Arc>, +} + +impl

WaitQueue

+where + P: DeadlockPrevention, +{ + /// Creates a new empty wait queue. + pub fn new() -> Self { + Self { + inner: Arc::new(Queue::new()), + } + } + + pub async fn wait_until(&self, mut condition: F) -> T + where + F: FnMut() -> Option, + { + poll_fn(move |context| self.poll_wait_until(context, &mut condition)).await + } + + pub fn poll_wait_until(&self, ctx: &mut Context, condition: &mut F) -> Poll + where + F: FnMut() -> Option, + { + let wrapped_condition = || { + if let Some(value) = condition() { + Ok(value) + } else { + Err(()) + } + }; + + match self + .inner + // TODO: Lazy clone + .push_if_fail(ctx.waker().clone(), wrapped_condition) + { + Ok(value) => Poll::Ready(value), + Err(()) => Poll::Pending, + } + } + + pub fn blocking_wait_until(&self, condition: F) -> T + where + F: FnMut() -> Option, + { + dreadnought::block_on(self.wait_until(condition)) + } + + /// Notifies the first task in the wait queue. + /// + /// Returns whether or not a task was awoken. + pub fn notify_one(&self) -> bool { + match self.inner.pop() { + Some(waker) => { + waker.wake(); + true + } + None => false, + } + } + + /// Notifies all the tasks in the wait queue. + pub fn notify_all(&self) { + while self.notify_one() {} + } +} diff --git a/kernel/boot_info/src/lib.rs b/kernel/boot_info/src/lib.rs index 2ccb07cc4f..2aff57bc03 100644 --- a/kernel/boot_info/src/lib.rs +++ b/kernel/boot_info/src/lib.rs @@ -130,6 +130,7 @@ pub struct FramebufferInfo { /// The format of the framebuffer and its pixels or characters. pub format: FramebufferFormat, } + impl FramebufferInfo { /// Returns `true` if the bootloader mapped the framebuffer and /// can provide its virtual address. diff --git a/kernel/captain/Cargo.toml b/kernel/captain/Cargo.toml index b6c7113237..5393c9b989 100644 --- a/kernel/captain/Cargo.toml +++ b/kernel/captain/Cargo.toml @@ -17,8 +17,10 @@ early_printer = { path = "../early_printer" } tlb_shootdown = { path = "../tlb_shootdown" } cls_allocator = { path = "../cls_allocator" } kernel_config = { path = "../kernel_config" } +compositor = { path = "../compositor" } interrupts = { path = "../interrupts" } scheduler = { path = "../scheduler" } +graphics = { path = "../graphics" } mod_mgmt = { path = "../mod_mgmt" } no_drop = { path = "../no_drop" } console = { path = "../console" } @@ -32,7 +34,7 @@ cpu = { path = "../cpu" } first_application = { path = "../first_application" } [target.'cfg(target_arch = "x86_64")'.dependencies] -window_manager = { path = "../window_manager" } +compositor = { path = "../compositor" } exceptions_full = { path = "../exceptions_full" } multiple_heaps = { path = "../multiple_heaps" } time = { path = "../time" } diff --git a/kernel/captain/src/lib.rs b/kernel/captain/src/lib.rs index e65ad2fd62..bfe38069ec 100644 --- a/kernel/captain/src/lib.rs +++ b/kernel/captain/src/lib.rs @@ -181,12 +181,52 @@ pub fn init( // arch-gate: no windowing/input support on aarch64 at the moment #[cfg(target_arch = "x86_64")] - match window_manager::init() { - Ok((key_producer, mouse_producer)) => { - device_manager::init(key_producer, mouse_producer)?; - }, - Err(error) => { - error!("Failed to init window manager (expected if using nographic): {error}"); + { + // Attempt to get the below 3 items of graphic mode info. + let mut width_height_paddr: Option<(usize, usize, PhysicalAddress)> = None; + // Take possession of the early framebuffer and obtain graphic info from it. + if let Some(early_fb) = early_printer::take() { + width_height_paddr = Some(( + early_fb.width as usize, + early_fb.height as usize, + early_fb.paddr, + )); + // Here: the early framebuffer's underlying mapping is dropped. + } + + // If we booted up other CPUs, we may have switched to a better graphics mode. + if let Some(gi) = multicore_bringup::get_graphic_info() { + let paddr = PhysicalAddress::new(gi.physical_address() as usize) + .ok_or("Graphic mode physical address was invalid")?; + let width = gi.width() as usize; + let height = gi.height() as usize; + width_height_paddr = Some((width, height, paddr)); + } + + let (width, height, paddr) = width_height_paddr + .ok_or("Failed to get graphic mode information!")?; + info!("Graphical framebuffer info: {} x {}, at paddr {:#X}", + width, height, paddr, + ); + let framebuffer = unsafe { + compositor::Framebuffer::new_hardware( + paddr, + graphics::FramebufferDimensions { + height, + width, + // TODO: This is correct right? + stride: width, + } + ) + }; + + match compositor::init(graphics::SoftwareDoubleBuffer::new(framebuffer)) { + Ok(compositor::Channels { keyboard, mouse, .. }) => { + device_manager::init(keyboard, mouse)?; + }, + Err(error) => { + error!("Failed to init window manager (expected if using nographic): {error}"); + } } } diff --git a/kernel/compositor/Cargo.toml b/kernel/compositor/Cargo.toml index b3bc97bd1c..1bb20ea8f0 100644 --- a/kernel/compositor/Cargo.toml +++ b/kernel/compositor/Cargo.toml @@ -1,14 +1,22 @@ [package] name = "compositor" version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "a compositor trait which composites a list of buffers to a single buffer" +edition = "2021" -[dependencies.framebuffer] -path = "../framebuffer" +[dependencies] +async_channel = { path = "../async_channel" } +dreadnought = { path = "../dreadnought" } +futures = { version = "0.3.28", default-features = false, features = ["async-await"] } +graphics = { path = "../graphics" } +hashbrown = "0.11.2" +keyboard = { path = "../keyboard" } +log = "0.4.8" +memory = { path = "../memory" } +mouse = { path = "../mouse" } +spin = "0.9.4" +sync_spin = { path = "../../libs/sync_spin" } +waker = { path = "../waker" } +# TODO: Update to 0.7.8 +zerocopy = "0.5.0" -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] +time = { path = "../time" } diff --git a/kernel/compositor/src/lib.rs b/kernel/compositor/src/lib.rs index a663a91622..e96f883b09 100644 --- a/kernel/compositor/src/lib.rs +++ b/kernel/compositor/src/lib.rs @@ -1,199 +1,233 @@ -//! This crate defines a trait of `Compositor` . -//! A compositor composites a list of sources buffers to a single destination buffer. +//! This crate is responsible for composing window buffers, handling window +//! interactions (e.g. dragging, resizing), and sending input to the correct +//! window. +//! +//! It is roughly equivalent in scope to a compositing window manager on Linux. -#![allow(clippy::range_plus_one)] #![no_std] +#![feature(negative_impls)] + +extern crate alloc; + +mod window; + +use alloc::sync::Arc; +use core::{ + ops::Deref, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use async_channel::Channel; +use futures::StreamExt; +use graphics::{GraphicsDriver, Horizontal, Vertical}; +pub use graphics::{AlphaPixel, Coordinates, Framebuffer, Pixel, Rectangle}; +use hashbrown::HashMap; +use keyboard::KeyEvent; +use log::error; +use mouse::MouseEvent; +use spin::Once; +use sync_spin::RwLock; +use window::LockedInner; + +use crate::window::Inner; +pub use crate::window::Window; + +static COMPOSITOR: Once> = Once::new(); + +pub fn init(driver: T) -> Result +where + T: Into>, +{ + let mut windows = WINDOWS.write(); + if windows.is_some() { + return Err("initialised compositor multiple times"); + } + *windows = Some(HashMap::new()); -extern crate framebuffer; -extern crate shapes; + let channels = Channels::new(); + let cloned = channels.clone(); + // TODO: Remove clone. + COMPOSITOR.call_once(|| channels.window.clone()); -use core::iter::IntoIterator; + dreadnought::task::spawn_async(compositor_loop(driver.into(), cloned))?; + Ok(channels) +} -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use core::ops::Range; +pub fn screen_size() -> (usize, usize) { + // TODO + (0x500, 0x400) +} -/// A compositor composites (combines or blends) a series of "source" framebuffers onto a single "destination" framebuffer. -/// The type parameter `B` allows a compositor to support multiple types of regions or "bounding boxes", -/// given by the trait bound `CompositableRegion`. -pub trait Compositor { - /// Composites the framebuffers in the list of source framebuffers `src_fbs` onto the destination framebuffer `dest_fb`. - /// - /// # Arguments - /// * `src_fbs`: an iterator over the source framebuffers to be composited, - /// along with where in the `dest_fb` they should be composited. - /// * `dest_fb`: the destination framebuffer that will hold the source framebuffers to be composited. - /// * `dest_bounding_boxes`: an iterator over bounding boxes that specify which regions - /// in the destination framebuffer should be updated. - /// For each source framebuffer in `src_fbs`, the compositor will iterate over every bounding box - /// and find the corresponding region in that source framebuffer and then blend that region into the destination. - /// - /// For example, if the window manager wants to draw a new partially-transparent window, - /// it will pass the framebuffers for all existing windows plus the new window (in bottom-to-top order) - /// to the compositor, in the argument `src_fbs`. - /// The `dest_fb` would be the final framebuffer mapped to the display device (screen memory), - /// and the `bounding_boxes` would be an iterator over just a single region in the final framebuffer - /// where that new window will be located. - /// When the source framebuffers are composited from bottom to top, the compositor will redraw the region of every source framebuffer - /// that intersects with that bounding box. - /// - /// For another example, suppose the window manager wants to draw a transparent mouse pointer on top of all windows. - /// It will pass the framebuffers of existing windows as well as a top framebuffer that contains the mouse pointer image. - /// In this case, the `bounding_boxes` could be the coordinates of all individual pixels in the mouse pointer image - /// (expressed as coordinates in the final framebuffer). - fn composite<'a, B: CompositableRegion + Clone, P: 'a + Pixel>( - &mut self, - src_fbs: impl IntoIterator>, - dest_fb: &mut Framebuffer

, - dest_bounding_boxes: impl IntoIterator + Clone, - ) -> Result<(), &'static str>; +#[derive(Clone)] +pub struct Request { + window_id: usize, + ty: RequestType, } +#[derive(Clone)] +enum RequestType { + /// Request the compositor to refresh the given dirty rectangle. + /// + /// The lock on the window must not be held when the request is made, and + /// the application must wait for a release event prior to obtaining the + /// lock again. + Refresh { dirty: Rectangle }, +} -/// A source framebuffer to be composited, along with its target position. -pub struct FramebufferUpdates<'a, P: Pixel> { - /// The source framebuffer to be composited. - pub src_framebuffer: &'a Framebuffer

, - /// The coordinate in the destination framebuffer where the source `framebuffer` - /// should be composited. - /// This coordinate is expressed relative to the top-left corner of the destination framebuffer. - pub coordinate_in_dest_framebuffer: Coord, +#[derive(Clone, Debug)] +pub enum Event { + Keyboard(KeyEvent), + Mouse(MouseEvent), + Resize(Rectangle), } -/// A `CompositableRegion` is an abstract region (i.e., a bounding box) -/// that can optimize the compositing (blending) of one framebuffer into another framebuffer -/// according to the specifics of the region's shape. -/// For example, a single 2-D point (`Coord`) offers no real room for optimization -/// because only one pixel will be composited, -/// but a rectangle **does** allow for optimization, as a large chunk of pixels can be composited all at once. -/// -/// In addition, a `CompositableRegion` makes it easier for a compositor to only composite pixels in a subset of a given source framebuffer -/// rather than forcing it to composite the whole framebuffer, which vastly improves performance. -pub trait CompositableRegion { - /// Returns the number of pixels in the region. - fn size(&self) -> usize; - - /// Returns the range of rows covered by this region, - /// given as row indices where row `0` is the top row in the region. - fn row_range(&self) -> Range; - - /// Blends the pixels in the source framebuffer `src_fb` within the range of rows (`src_fb_row_range`) - /// into the pixels in the destination framebuffer `dest_fb`. - /// The `dest_coord` is the coordinate in the destination buffer (relative to its top-left corner) - /// where the `src_fb` will be composited (starting at the `src_fb`'s top-left corner). - /// `src_fb_row_range` is the index range of rows in the source framebuffer to blend. - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - src_fb_row_range: Range - ) -> Result<(), &'static str>; +fn absolute(coordinates: Coordinates, mut rectangle: Rectangle) -> Rectangle { + rectangle.coordinates += coordinates; + rectangle } -impl CompositableRegion for Coord { - #[inline] - fn row_range(&self) -> Range { - self.y..self.y + 1 +fn refresh(driver: &mut GraphicsDriver, window: T, dirty: Rectangle) +where + T: Deref + core::fmt::Debug, +{ + let framebuffer = driver.back(); + + // TODO: Take into account windows above. + // TODO: Take into account dirty rectangle. + let left = dirty.x(Horizontal::Left); + let width = dirty.width(); + + for y in dirty.y(Vertical::Top)..(dirty.y(Vertical::Bottom) + 1) { + let start = y * framebuffer.stride() + left; + let end = start + width; + framebuffer[start..end].clone_from_slice(&window.framebuffer[start..end]); } - #[inline] - fn size(&self) -> usize { - 1 - } + // log::warn!("a: {:?}", time::Instant::now().duration_since(start)); + // TODO: This should be called in an interrupt handler or something like that to + // prevent screen tearing. + driver.swap(&[absolute(window.coordinates, dirty)]); +} + +#[derive(Clone)] +pub struct Channels { + // FIXME: Deadlock prevention. + pub window: Channel, + // FIXME: Deadlock prevention. + pub keyboard: Channel, + // FIXME: Deadlock prevention. + pub mouse: Channel, +} - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - _src_fb_row_range: Range, - ) -> Result<(), &'static str>{ - let relative_coord = *self - dest_coord; - if let Some(pixel) = src_fb.get_pixel(relative_coord) { - dest_fb.draw_pixel(*self, pixel); +impl Channels { + fn new() -> Self { + Self { + window: Channel::new(8), + keyboard: Channel::new(8), + mouse: Channel::new(8), } - Ok(()) } } -impl CompositableRegion for Rectangle { - #[inline] - fn row_range(&self) -> Range { - self.top_left.y..self.bottom_right.y +// spin rwlock? +// IDEA(tsoutsman): Optimisation: struct of arrays: RwLock<(Vec, +// Vec), Vec, Vec)> ordered by z-index. To +// get all rectangles of windows above window n, just do +// WINDOWS.read().2[n..].clone() to minimise holding the lock. Not sure about +// race conditions, and if we need to hold the lock the entire time. +static WINDOWS: RwLock>>> = RwLock::new(None); +static WINDOW_ID: AtomicUsize = AtomicUsize::new(0); + +pub fn window(width: usize, height: usize) -> Window { + let (Window { id, inner }, clone) = + Window::new(WINDOW_ID.fetch_add(1, Ordering::Relaxed), width, height); + WINDOWS.write().as_mut().unwrap().insert(id, inner); + clone +} + +async fn compositor_loop(mut driver: GraphicsDriver, mut channels: Channels) { + loop { + // log::info!("compositor looping"); + // The select macro is not available in no_std. + futures::select_biased!( + request = channels.window.next() => { + let start = time::Instant::now(); + let request = request.unwrap(); + handle_window_request(&mut driver, request).await; + } + request = channels.keyboard.next() => { + let start = time::Instant::now(); + let request = request.unwrap(); + handle_keyboard_request(request); + } + request = channels.mouse.next() => { + let request = request.unwrap(); + handle_mouse_request(request); + } + complete => panic!("compositor loop exited"), + ); + // log::info!("compositor looped"); } +} - #[inline] - fn size(&self) -> usize { - (self.bottom_right.x - self.top_left.x) as usize * (self.bottom_right.y - self.top_left.y) as usize +async fn handle_window_request(driver: &mut GraphicsDriver, request: Request) { + let id = request.window_id; + + let windows = WINDOWS.read(); + let inner = windows.as_ref().unwrap().get(&id).cloned(); + drop(windows); + + // TODO: Take events out of inner (or at least out of Mutex). + let mut waker = None; + + if let Some(inner) = inner { + if let Some(mut locked) = inner.locked.try_write() { + match request.ty { + RequestType::Refresh { dirty } => { + // This will be true once we drop the lock. + locked.is_unlocked = true; + + match &locked.waker { + Some(w) => waker = Some(w.clone()), + None => error!("no registered waker"), + } + + refresh(driver, locked, dirty); + } + } + } else { + error!("window was locked"); + } + } else { + error!("invalid window ID"); } - fn blend_buffers( - &self, - src_fb: &Framebuffer

, - dest_fb: &mut Framebuffer

, - dest_coord: Coord, - src_fb_row_range: Range, - ) -> Result<(), &'static str> { - let (dest_width, dest_height) = dest_fb.get_size(); - let (src_width, src_height) = src_fb.get_size(); - - let start_y = core::cmp::max(src_fb_row_range.start as isize + dest_coord.y, self.top_left.y); - let end_y = core::cmp::min(src_fb_row_range.end as isize + dest_coord.y, self.bottom_right.y); - - // skip if the updated part is not in the dest framebuffer - let dest_start = Coord::new( - core::cmp::max(0, self.top_left.x), - core::cmp::max(0, start_y) - ); + if let Some(waker) = waker { + waker.wake(); + } +} - let dest_end = Coord::new( - core::cmp::min(dest_width as isize, self.bottom_right.x), - core::cmp::min(dest_height as isize, end_y) - ); - if dest_end.x < 0 - || dest_start.x > dest_width as isize - || dest_end.y < 0 - || dest_start.y > dest_height as isize +fn handle_keyboard_request(event: KeyEvent) { + if let Some(active_window) = active_window() { + if active_window + .events + .try_send(Event::Keyboard(event)) + .is_err() { - return Ok(()); - } - - // skip if the updated part is not in the source framebuffer - let coordinate_start = dest_start - dest_coord; - let coordinate_end = dest_end - dest_coord; - if coordinate_end.x < 0 - || coordinate_start.x > src_width as isize - || coordinate_end.y < 0 - || coordinate_start.y > src_height as isize - { - return Ok(()); + log::info!("dropping keyboard event"); } + } +} - let src_x_start = core::cmp::max(0, coordinate_start.x) as usize; - let src_y_start = core::cmp::max(0, coordinate_start.y) as usize; - - // draw only the part within the dest buffer - let width = core::cmp::min(coordinate_end.x as usize, src_width) - src_x_start; - let height = core::cmp::min(coordinate_end.y as usize, src_height) - src_y_start; - - // copy every line of the block to the dest framebuffer. - let src_buffer = &src_fb.buffer(); - for i in 0..height { - let src_start = Coord::new(src_x_start as isize, (src_y_start + i) as isize); - let src_start_index = match src_fb.index_of(src_start) { - Some(index) => index, - None => {continue;} - }; - let src_end_index = src_start_index + width; - let dest_start = src_start + dest_coord; - let dest_start_index = match dest_fb.index_of(dest_start) { - Some(index) => index, - None => {continue;} - }; - dest_fb.composite_buffer(&src_buffer[src_start_index..src_end_index], dest_start_index); +fn handle_mouse_request(event: MouseEvent) { + if let Some(active_window) = active_window() { + if active_window.events.try_send(Event::Mouse(event)).is_err() { + log::info!("dropping mouse event"); } - - Ok(()) } } + +fn active_window() -> Option> { + // TODO: Get active window. + WINDOWS.read().as_ref().unwrap().get(&0).cloned() +} diff --git a/kernel/compositor/src/window.rs b/kernel/compositor/src/window.rs new file mode 100644 index 0000000000..59129c6f06 --- /dev/null +++ b/kernel/compositor/src/window.rs @@ -0,0 +1,248 @@ +use alloc::{boxed::Box, sync::Arc}; +use core::{ + future::Future, + ops::{Deref, DerefMut}, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use async_channel::Channel; +use graphics::{AlphaPixel, Coordinates, Framebuffer, FramebufferDimensions, Rectangle}; +use sync_spin::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::{Event, Request, RequestType, COMPOSITOR}; + +pub struct Window { + pub(crate) id: usize, + /// The data contained in the window. + /// + /// We use a spin mutex, because this mutex should never experience + /// contention. The application and compositor coordinate access using + /// messages. Strictly speaking, the mutex isn't even necessary. + pub(crate) inner: Arc, +} + +pub(crate) struct Inner { + // TODO: This could be an `UnsafeCell`. + pub(crate) locked: RwLock, + // TODO: Not spin. (unbounded?) + pub(crate) events: Channel, +} + +#[derive(Debug)] +pub(crate) struct LockedInner { + pub(crate) coordinates: Coordinates, + // pub border_size: usize, + // pub title_bar_height: usize, + pub(crate) framebuffer: Framebuffer, + pub(crate) waker: Option, + pub(crate) is_unlocked: bool, +} + +// Explain why +unsafe impl Sync for Inner {} + +// Functions that access inner.locked must take a mutable reference. This +// ensures that only one client thread is accessing inner.locked at any one +// time. The only other entity that could access inner.locked is the compositor, +// but compositor access is forcibly synchronised through the refresh and +// blocking_refresh methods. +impl Window { + pub(crate) fn new(id: usize, width: usize, height: usize) -> (Self, Self) { + let inner = Arc::new(Inner { + locked: RwLock::new(LockedInner { + coordinates: Coordinates::ORIGIN, + framebuffer: Framebuffer::new_software(FramebufferDimensions { + width, + height, + stride: width, + }), + waker: None, + is_unlocked: false, + }), + events: Channel::new(16), + }); + ( + Self { + id, + inner: inner.clone(), + }, + Self { id, inner }, + ) + } + + pub fn as_framebuffer(&self) -> impl Deref> + '_ { + FramebufferRef { + inner: self.inner.locked.try_read().unwrap(), + } + } + + pub fn as_mut_framebuffer(&mut self) -> impl DerefMut> + '_ { + FramebufferMutRef { + inner: self.inner.locked.try_write().unwrap(), + } + } + + pub fn area(&self) -> Rectangle { + Rectangle::new(Coordinates::ORIGIN, 0x500, 0x400) + } + + // TODO: Modify async_channel API so that we can create receivers, and then just + // have a method `events` which returns a receiver. + + pub async fn recv(&self) -> Event { + self.inner.events.recv().await + } + + pub fn try_recv(&self) -> Option { + self.inner.events.try_recv() + } + + pub fn blocking_recv(&self) -> Event { + self.inner.events.blocking_recv() + } + + // IDEA(tsoutsman): + // + // As it currently stands, the client must wait for the compositor to release + // the buffer before doing anything else. This isn't too bad, as we don't have + // graphics-intensive applications, but it's something that could be improved + // on. + // + // `refresh` could be done in a two-step process, returning a future that itself + // returns a future. The first future would send the request, and the second + // future would wait for a response. Notably, the second future would still + // be tied to a mutable reference to self. This is both a blessing and a + // curse, because it correctly enforces that the window can't be used until + // both futures are resolved, but I'm pretty sure the borrow checker would + // have a fit if the second future was stored in a variable across a loop + // (i.e. event loop) iteration. However, this could probably be worked around + // with some trickery. + // + // One could imaging a double-buffering client that commits a front buffer (by + // awaiting the first future), and stores the second future. It then starts + // calculating the next frame and drawing it into the back buffer. After it's + // done, it polls the second future (checking if the server has released the + // shared buffer), and if it's ready, switches the back buffer into the shared + // buffer. + // + // A similar thing could be done with `blocking_refresh`. + + // Note that Refresh<'_> is tied to a mutable reference, and so no other mutable + // accesses of self can occur until the future is consumed. + pub fn refresh(&mut self, dirty: Rectangle) -> Refresh<'_> { + Refresh { + locked: &self.inner.locked, + id: self.id, + dirty, + state: State::Init, + } + } + + pub fn blocking_refresh(&mut self, dirty: Rectangle) { + let (waker, blocker) = waker::new_waker(); + self.inner.locked.try_write().unwrap().waker = Some(waker); + COMPOSITOR.get().unwrap().blocking_send(Request { + window_id: self.id, + ty: RequestType::Refresh { dirty }, + }); + blocker.block(); + } +} + +/// Future returned by [`Window::refresh`]. +pub struct Refresh<'a> { + locked: &'a RwLock, + id: usize, + dirty: Rectangle, + state: State, +} + +enum State { + Init, + Sending(Pin>>), + Sent, +} + +impl PartialEq for State { + fn eq(&self, other: &Self) -> bool { + core::mem::discriminant(self) == core::mem::discriminant(other) + } +} + +impl<'a> Future for Refresh<'a> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Manually implementing async functions :) + + if self.state == State::Init { + let mut locked = self.locked.try_write().unwrap(); + locked.waker = Some(cx.waker().clone()); + locked.is_unlocked = false; + drop(locked); + + let mut future = Box::pin(COMPOSITOR.get().unwrap().send(Request { + window_id: self.id, + ty: RequestType::Refresh { dirty: self.dirty }, + })); + let output = Pin::new(&mut future).poll(cx); + + match output { + Poll::Ready(()) => self.state = State::Sent, + Poll::Pending => { + self.state = State::Sending(future); + return Poll::Pending; + } + } + } else if let State::Sending(ref mut future) = self.state { + match Pin::new(future).poll(cx) { + Poll::Ready(()) => self.state = State::Sent, + Poll::Pending => return Poll::Pending, + }; + } + + // State::Sent + + if let Some(locked) = self.locked.try_read() { + if locked.is_unlocked { + return Poll::Ready(()); + } + } + + // This is not a race condition. Even if the compositor unlocks between our + // check and returning Poll::Pending, it would have woken the waker, + // guaranteeing that this future will be polled again. + Poll::Pending + } +} + +struct FramebufferRef<'a> { + inner: RwLockReadGuard<'a, LockedInner>, +} + +impl Deref for FramebufferRef<'_> { + type Target = Framebuffer; + + fn deref(&self) -> &Self::Target { + &self.inner.framebuffer + } +} + +struct FramebufferMutRef<'a> { + inner: RwLockWriteGuard<'a, LockedInner>, +} + +impl Deref for FramebufferMutRef<'_> { + type Target = Framebuffer; + + fn deref(&self) -> &Self::Target { + &self.inner.framebuffer + } +} + +impl DerefMut for FramebufferMutRef<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.framebuffer + } +} diff --git a/kernel/console/Cargo.toml b/kernel/console/Cargo.toml index a5b18ce8d4..2d2b959e22 100644 --- a/kernel/console/Cargo.toml +++ b/kernel/console/Cargo.toml @@ -10,9 +10,9 @@ log = "0.4.8" core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } app_io = { path = "../app_io" } -async_channel = { path = "../async_channel" } io = { path = "../io" } mod_mgmt = { path = "../mod_mgmt" } +mpmc_channel = { path = "../mpmc_channel" } path = { path = "../path" } serial_port = { path = "../serial_port" } spawn = { path = "../spawn" } diff --git a/kernel/console/src/lib.rs b/kernel/console/src/lib.rs index 723e628fca..46589dd289 100644 --- a/kernel/console/src/lib.rs +++ b/kernel/console/src/lib.rs @@ -5,11 +5,11 @@ extern crate alloc; use alloc::{format, sync::Arc}; -use async_channel::Receiver; use core::sync::atomic::{AtomicU16, Ordering}; use core2::io::Write; use sync_irq::IrqSafeMutex; use log::{error, info, warn}; +use mpmc_channel::Receiver; use serial_port::{get_serial_port, DataChunk, SerialPort, SerialPortAddress}; use task::{JoinableTaskRef, KillReason}; @@ -31,7 +31,7 @@ pub fn ignore_serial_port_input(serial_port_address: u16) { /// /// Returns the newly-spawned detection task. pub fn start_connection_detection() -> Result { - let (sender, receiver) = async_channel::new_channel(4); + let (sender, receiver) = mpmc_channel::new_channel(4); serial_port::set_connection_listener(sender); spawn::new_task_builder(console_connection_detector, receiver) @@ -69,7 +69,7 @@ fn console_connection_detector( } }; - let (sender, receiver) = async_channel::new_channel(16); + let (sender, receiver) = mpmc_channel::new_channel(16); if serial_port.lock().set_data_sender(sender).is_err() { warn!( "Serial port {:?} already had a data sender, skipping console connection request", diff --git a/kernel/device_manager/Cargo.toml b/kernel/device_manager/Cargo.toml index 5f51ab2988..6608328524 100644 --- a/kernel/device_manager/Cargo.toml +++ b/kernel/device_manager/Cargo.toml @@ -8,16 +8,15 @@ edition = "2021" [dependencies] spin = "0.9.4" core2 = { version = "0.4.0", default-features = false, features = ["alloc", "nightly"] } -event_types = { path = "../event_types" } serial_port = { path = "../serial_port" } console = { path = "../console" } logger = { path = "../logger" } pci = { path = "../pci" } derive_more = "0.99.0" -mpmc = "0.1.6" log = "0.4.8" [target.'cfg(target_arch = "x86_64")'.dependencies] +async_channel = { path = "../async_channel" } memory = { path = "../memory" } e1000 = { path = "../e1000" } acpi = { path = "../acpi" } diff --git a/kernel/device_manager/src/lib.rs b/kernel/device_manager/src/lib.rs index bf9dc46754..fd75f0223d 100644 --- a/kernel/device_manager/src/lib.rs +++ b/kernel/device_manager/src/lib.rs @@ -8,8 +8,7 @@ use log::{info, debug}; #[cfg(target_arch = "x86_64")] use { log::{error, warn}, - mpmc::Queue, - event_types::Event, + async_channel::Channel, memory::MemoryManagementInfo, alloc::vec::Vec, io::{ByteReaderWriterWrapper, LockableIo, ReaderWriter}, @@ -43,9 +42,9 @@ pub fn early_init( /// * All other devices discovered on the [`pci`] bus. pub fn init( #[cfg(target_arch = "x86_64")] - key_producer: Queue, + key_producer: Channel, #[cfg(target_arch = "x86_64")] - mouse_producer: Queue, + mouse_producer: Channel, ) -> Result<(), &'static str> { let serial_ports = logger::take_early_log_writers(); diff --git a/kernel/displayable/Cargo.toml b/kernel/displayable/Cargo.toml deleted file mode 100644 index 995e93c492..0000000000 --- a/kernel/displayable/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "displayable" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "provides a Displayable trait, which abstracts objects that can display themselves onto a framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.color] -path = "../color" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/displayable/src/lib.rs b/kernel/displayable/src/lib.rs deleted file mode 100644 index c1282dc6ab..0000000000 --- a/kernel/displayable/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This crate defines a trait of `Displayable`. -//! A displayable is a block of content. It can display itself onto a framebuffer. -//! The coordinate of a displayable represents its origin (top-left point). - -#![no_std] - -extern crate framebuffer; -extern crate shapes; -extern crate color; - -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use color::Color; - -/// The `Displayable` trait is an abstraction for any object that can display itself onto a framebuffer. -/// Examples include a text box, button, window border, etc. -pub trait Displayable { - /// Displays this `Displayable`'s content in the given framebuffer. - /// # Arguments - /// * `coordinate`: the coordinate within the given `framebuffer` where this displayable should render itself. - /// The `coordinate` is relative to the top-left point of the `framebuffer`. - /// * `framebuffer`: the framebuffer to display onto. - /// - /// Returns a rectangle that represents the region of the framebuffer that was updated. - fn display>( - &mut self, - coordinate: Coord, - framebuffer: &mut Framebuffer

, - ) -> Result; - - /// Resizes the displayable area, but does not automatically refresh its display. - fn set_size(&mut self, width: usize, height: usize); - - /// Gets the size of the area occupied by the displayable. - fn get_size(&self) -> (usize, usize); -} diff --git a/kernel/displayable/text_display/Cargo.toml b/kernel/displayable/text_display/Cargo.toml deleted file mode 100644 index 1b0f075881..0000000000 --- a/kernel/displayable/text_display/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "text_display" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "a text display is a block of text which can display onto a framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../../framebuffer" - -[dependencies.displayable] -path = "../" - -[dependencies.framebuffer_printer] -path = "../../framebuffer_printer" - -[dependencies.font] -path = "../../font" - -[dependencies.shapes] -path = "../../shapes" - -[dependencies.color] -path = "../../color" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/displayable/text_display/src/lib.rs b/kernel/displayable/text_display/src/lib.rs deleted file mode 100644 index 12d41fe5b4..0000000000 --- a/kernel/displayable/text_display/src/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! This crate defines a text displayable. -//! A text displayable profiles a block of text to be displayed onto a framebuffer. - -#![no_std] - -extern crate alloc; -extern crate displayable; -extern crate font; -extern crate framebuffer; -extern crate framebuffer_printer; -extern crate shapes; -extern crate color; - -use alloc::string::String; -use displayable::{Displayable}; -use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Pixel, Framebuffer}; -use color::Color; -use shapes::{Coord, Rectangle}; - - -/// A text displayable profiles the size and color of a block of text. It can display in a framebuffer. -#[derive(Debug)] -pub struct TextDisplay { - width: usize, - height: usize, - /// The position where the next character will be displayed. - /// This is updated after each `display()` invocation, and is useful for optimization. - next_col: usize, - next_line: usize, - text: String, - fg_color: Color, - bg_color: Color, - /// The cache of the text that was last displayed. - cache: String, -} - -impl Displayable for TextDisplay { - fn display> ( - &mut self, - coordinate: Coord, - framebuffer: &mut Framebuffer

, - ) -> Result { - let (string, col, line) = if !self.cache.is_empty() && self.text.starts_with(self.cache.as_str()) { - ( - &self.text.as_str()[self.cache.len()..self.text.len()], - self.next_col, - self.next_line, - ) - } else { - (self.text.as_str(), 0, 0) - }; - - let (next_col, next_line, mut bounding_box) = framebuffer_printer::print_string( - framebuffer, - coordinate, - self.width, - self.height, - string, - self.fg_color.into(), - self.bg_color.into(), - col, - line, - ); - - if next_line < self.next_line { - bounding_box.bottom_right.y = ((self.next_line + 1 ) * CHARACTER_HEIGHT) as isize - } - - self.next_col = next_col; - self.next_line = next_line; - self.cache = self.text.clone(); - - Ok(bounding_box + coordinate) - } - - fn set_size(&mut self, width: usize, height: usize) { - self.width = width; - self.height = height; - } - - fn get_size(&self) -> (usize, usize) { - (self.width, self.height) - } -} - -impl TextDisplay { - /// Creates a new text displayable. - /// # Arguments - /// * `width`, `height`: the dimensions of the text area, in number of characters. - /// * `fg_color`, `bg_color`: the color of the text and the background behind the text, respectively. - pub fn new( - width: usize, - height: usize, - fg_color: Color, - bg_color: Color, - ) -> Result { - Ok(TextDisplay { - width, - height, - next_col: 0, - next_line: 0, - text: String::new(), - fg_color, - bg_color, - cache: String::new(), - }) - } - - /// Gets the background color of the text area - pub fn get_bg_color(&self) -> Color { - self.bg_color - } - - /// Clear the cache of the text displayable. - pub fn reset_cache(&mut self) { - self.cache = String::new(); - } - - /// Translate the index of a character in the text to the location of the text displayable. Return (column, line). - pub fn get_location(&self, index: usize) -> (usize, usize) { - let text_width = self.width / CHARACTER_WIDTH; - (index % text_width, index / text_width) - } - - /// Translate the location of a character to its index in the text. - pub fn get_index(&self, column: usize, line: usize) -> usize { - let text_width = self.width / CHARACTER_WIDTH; - line * text_width + column - } - - /// Gets the size of a text displayable in number of characters. - pub fn get_dimensions(&self) -> (usize, usize) { - (self.width / CHARACTER_WIDTH, self.height / CHARACTER_HEIGHT) - } - - /// Gets the index of next character to be displayabled. It is the position next to existing printed characters in the text displayable. - pub fn get_next_index(&self) -> usize { - let col_num = self.width / CHARACTER_WIDTH; - self.next_line * col_num + self.next_col - } - - /// Sets the text of the text displayable - pub fn set_text(&mut self, text: &str) { - self.text = String::from(text); - } -} diff --git a/kernel/draw/Cargo.toml b/kernel/draw/Cargo.toml new file mode 100644 index 0000000000..d10ee9c175 --- /dev/null +++ b/kernel/draw/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "draw" +version = "0.1.0" +edition = "2021" + +[dependencies] +font = { path = "../font" } +geometry = { path = "../../libs/geometry" } +graphics = { path = "../graphics" } +log = "0.4.8" \ No newline at end of file diff --git a/kernel/draw/src/character.rs b/kernel/draw/src/character.rs new file mode 100644 index 0000000000..04a6f9d0e4 --- /dev/null +++ b/kernel/draw/src/character.rs @@ -0,0 +1,53 @@ +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; +use geometry::{Coordinates, Rectangle}; +use graphics::{Framebuffer, Pixel}; + +use crate::{Drawable, Settings}; + +pub struct Char { + pub inner: char, + pub coordinates: Coordinates, +} + +impl Char { + pub fn new(inner: char, coordinates: Coordinates) -> Self { + Self { + inner, + coordinates, + } + } +} + +impl Drawable for Char { + fn draw

(&self, framebuffer: &mut Framebuffer

, settings: &Settings

) -> Rectangle + where + P: Pixel, + { + fn is_set(character: char, coordinates: Coordinates) -> bool { + font::FONT_BASIC[character as usize][coordinates.y] & (0x80 >> coordinates.x) != 0 + } + + // TODO: Optimise + + for row in 0..CHARACTER_HEIGHT { + for col in 0..CHARACTER_WIDTH { + // The coordinates relative to the top left of the character. + let relative = Coordinates::new(col, row); + if col == 0 { + if let Some(background) = settings.background { + framebuffer.set(self.coordinates + relative, background) + } + } else { + let offset_coordinates = Coordinates::new(col - 1, row); + if is_set(self.inner, offset_coordinates) { + framebuffer.set(self.coordinates + relative, settings.foreground) + } else if let Some(background) = settings.background { + framebuffer.set(self.coordinates + relative, background) + } + } + } + } + + Rectangle::new(self.coordinates, CHARACTER_WIDTH, CHARACTER_HEIGHT) + } +} diff --git a/kernel/draw/src/lib.rs b/kernel/draw/src/lib.rs new file mode 100644 index 0000000000..fb48e676a5 --- /dev/null +++ b/kernel/draw/src/lib.rs @@ -0,0 +1,179 @@ +#![no_std] + +mod character; +mod text; + +pub use geometry::{Circle, Coordinates, Line, Rectangle}; +use geometry::{Horizontal, Vertical}; +use graphics::{Framebuffer, Pixel}; + +pub use crate::{character::Char, text::Text}; + +pub struct Settings

+where + P: Pixel, +{ + pub foreground: P, + pub background: Option

, +} + +pub trait Drawable { + fn draw

(&self, framebuffer: &mut Framebuffer

, settings: &Settings

) -> Rectangle + where + P: Pixel; +} + +impl Drawable for Circle { + #[inline] + fn draw

(&self, _framebuffer: &mut Framebuffer

, _settings: &Settings

) -> Rectangle + where + P: Pixel, + { + todo!(); + } +} + +impl Drawable for Line { + #[inline] + fn draw

(&self, framebuffer: &mut Framebuffer

, settings: &Settings

) -> Rectangle + where + P: Pixel, + { + // TODO: Antialiasing + + let refresh_area = Rectangle::new( + Coordinates { + x: self.x(Horizontal::Left), + y: self.y(Vertical::Top), + }, + self.x(Horizontal::Right) - self.x(Horizontal::Left) + 1, + self.y(Vertical::Bottom) - self.y(Vertical::Top) + 1, + ); + + if self.start.y == self.end.y { + // Horizontal line optimisation + + let left = self.x(Horizontal::Left); + let right = self.x(Horizontal::Right); + + // TODO: Unwrap? + framebuffer.rows_mut().nth(self.start.y).unwrap()[left..=right] + .fill(settings.foreground); + } else if self.start.x == self.end.x { + // Vertical line optimisation + + let top = self.y(Vertical::Top); + let bottom = self.y(Vertical::Bottom); + let num_rows = bottom - top + 1; + + for row in framebuffer.rows_mut().skip(top).take(num_rows) { + row[self.start.x] = settings.foreground; + } + } else { + // Taken from: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + // License: https://en.wikipedia.org/wiki/Wikipedia:Text_of_the_Creative_Commons_Attribution-ShareAlike_4.0_International_License + + #[inline] + fn draw_line_low

( + framebuffer: &mut Framebuffer

, + c0: Coordinates, + c1: Coordinates, + settings: &Settings

, + ) where + P: Pixel, + { + let dx = c1.x as isize - c0.x as isize; + let mut dy = c1.y as isize - c0.y as isize; + let yi: isize = if dy < 0 { + dy = -dy; + -1 + } else { + 1 + }; + let mut d = 2 * dy - dx; + let mut y = c0.y; + + for x in c0.x..=c1.x { + framebuffer.set(Coordinates { x, y }, settings.foreground); + if d > 0 { + y = ((y as isize) + yi) as usize; + d += 2 * (dy - dx); + } else { + d += 2 * dy; + } + } + } + + #[inline] + fn draw_line_high

( + framebuffer: &mut Framebuffer

, + c0: Coordinates, + c1: Coordinates, + settings: &Settings

, + ) where + P: Pixel, + { + let mut dx = c1.x as isize - c0.x as isize; + let dy = c1.y as isize - c0.y as isize; + let xi: isize = if dx < 0 { + dx = -dx; + -1 + } else { + 1 + }; + let mut d = 2 * dx - dy; + let mut x = c0.x; + + for y in c0.y..=c1.y { + framebuffer.set(Coordinates { x, y }, settings.foreground); + if d > 0 { + x = ((x as isize) + xi) as usize; + d += 2 * (dx - dy); + } else { + d += 2 * dx; + } + } + } + + let diff = self.end.abs_diff(self.start); + #[allow(clippy::collapsible_else_if)] + if diff.y < diff.x { + if self.start.x > self.end.x { + draw_line_low(framebuffer, self.end, self.start, settings); + } else { + draw_line_low(framebuffer, self.start, self.end, settings); + } + } else { + if self.start.y > self.end.y { + draw_line_high(framebuffer, self.end, self.start, settings); + } else { + draw_line_high(framebuffer, self.start, self.end, settings); + } + } + } + + refresh_area + } +} + +impl Drawable for Rectangle { + #[inline] + fn draw

(&self, framebuffer: &mut Framebuffer

, settings: &Settings

) -> Rectangle + where + P: Pixel, + { + let top = self.y(Vertical::Top); + let bottom = self.y(Vertical::Bottom); + + let left = self.x(Horizontal::Left); + let right = self.x(Horizontal::Right); + + let num_rows = bottom - top + 1; + + for row in framebuffer.rows_mut().skip(top).take(num_rows) { + row[left..right].fill(settings.foreground); + } + + *self + } +} diff --git a/kernel/draw/src/text.rs b/kernel/draw/src/text.rs new file mode 100644 index 0000000000..d5bf9cf085 --- /dev/null +++ b/kernel/draw/src/text.rs @@ -0,0 +1,128 @@ +// TODO: Move `font` crate to libs + +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; +use geometry::{Coordinates, Rectangle}; +use graphics::{Framebuffer, Pixel}; + +use crate::{Char, Drawable, Settings}; + +pub struct Text +where + T: AsRef, +{ + pub inner: T, + pub coordinates: Coordinates, +} + +impl Text +where + T: AsRef, +{ + pub const fn grid_width

(framebuffer: &Framebuffer

) -> usize + where + P: Pixel, + { + framebuffer.width() / CHARACTER_WIDTH + } + + pub const fn grid_height

(framebuffer: &Framebuffer

) -> usize + where + P: Pixel, + { + framebuffer.height() / CHARACTER_HEIGHT + } +} + +impl Text +where + T: AsRef, +{ + pub fn new(inner: T, coordinates: Coordinates) -> Self { + Self { inner, coordinates } + } + + pub fn next_grid_position

(&self, framebuffer: &Framebuffer

) -> (usize, usize) + where + P: Pixel, + { + let grid_width = Self::grid_width(framebuffer); + let grid_height = Self::grid_height(framebuffer); + + let mut column = 0; + let mut row = 0; + + for c in self.inner.as_ref().chars() { + if c == '\n' { + column = 0; + row += 1; + } else { + column += 1; + + if column == grid_width { + column = 0; + row += 1; + } + } + + if row == grid_height { + break; + } + } + + (column, row) + } +} + +impl Drawable for Text +where + T: AsRef, +{ + fn draw

(&self, framebuffer: &mut Framebuffer

, settings: &Settings

) -> Rectangle + where + P: Pixel, + { + // IDEA: Some potential extensions: https://en.wikipedia.org/wiki/Font_rasterization + + let grid_width = Self::grid_width(framebuffer); + let grid_height = Self::grid_height(framebuffer); + + let s = self.inner.as_ref(); + // FIXME + assert!(s.is_ascii()); + + let mut row = 0; + let mut column = 0; + + let mut bounding_box = Rectangle::new(self.coordinates, 0, 0); + + for c in s.chars() { + if c == '\n' { + column = 0; + row += 1; + } else { + let coordinates = self.coordinates + + Coordinates::new(column * CHARACTER_WIDTH, row * CHARACTER_HEIGHT); + let char_bounding_area = Char { + coordinates, + inner: c, + } + .draw(framebuffer, settings); + + bounding_box = bounding_box.merge(&char_bounding_area); + + column += 1; + + if column == grid_width { + column = 0; + row += 1; + } + } + + if row == grid_height { + break; + } + } + + bounding_box + } +} diff --git a/kernel/event_types/Cargo.toml b/kernel/event_types/Cargo.toml deleted file mode 100644 index c5733e79e9..0000000000 --- a/kernel/event_types/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -authors = ["Kevin Boos "] -name = "event_types" -description = "Types of input and output events that flow throughout the system" -version = "0.1.0" -edition = "2021" - -[dependencies.keycodes_ascii] -path = "../../libs/keycodes_ascii" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.mouse_data] -path = "../../libs/mouse_data" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/event_types/src/lib.rs b/kernel/event_types/src/lib.rs deleted file mode 100644 index 2eb83695f5..0000000000 --- a/kernel/event_types/src/lib.rs +++ /dev/null @@ -1,99 +0,0 @@ -#![no_std] - -extern crate alloc; - -use alloc::string::String; -use keycodes_ascii::KeyEvent; -use mouse_data::MouseEvent; -use shapes::{Coord, Rectangle}; - -/// An event describing mouse position rather than movement differential from last event. -/// It contains two position, `coodinate` for the relative position in each window, and `gcoordinate` for global absolute position of the screen. -#[derive(Debug, Clone)] -pub struct MousePositionEvent { - /// the relative position in window - pub coordinate: Coord, - /// the global position in window - pub gcoordinate: Coord, - /// whether the mouse is scrolling up - pub scrolling_up: bool, - /// whether the mouse is scrolling down - pub scrolling_down: bool, - /// whether the left button holds - pub left_button_hold: bool, - /// whether the right button holds - pub right_button_hold: bool, - /// whether the fourth button holds - pub fourth_button_hold: bool, - /// whether the fifth button holds - pub fifth_button_hold: bool, -} - -impl Default for MousePositionEvent { - fn default() -> Self { - MousePositionEvent { - coordinate: Coord::new(0, 0), - gcoordinate: Coord::new(0, 0), - scrolling_up: false, - scrolling_down: false, - left_button_hold: false, - right_button_hold: false, - fourth_button_hold: false, - fifth_button_hold: false, - } - } -} - -#[derive(Debug, Clone)] -pub enum Event { - /// An input event from a keyboard - KeyboardEvent(KeyboardInputEvent), - /// An input event from a mouse - MouseMovementEvent(MouseEvent), - /// An event indicating that another entity wants to print the given `String`. - OutputEvent(String), - /// Tells an application that the window manager has resized or moved its window - /// so that it knows to refresh its display and perform any necessary tasks, such as text reflow. - /// - /// The new position and size of the window is given by the `Rectangle` within, - /// and represents the content area within the window that is accessible to the application, - /// which excludes the window title bar, borders, etc. - WindowResizeEvent(Rectangle), - /// The event tells application about mouse's position currently (including relative to a window and relative to a screen) - MousePositionEvent(MousePositionEvent), - ExitEvent, -} - -impl Event { - /// Create a new keyboard event - pub fn new_keyboard_event(kev: KeyEvent) -> Event { - Event::KeyboardEvent(KeyboardInputEvent::new(kev)) - } - - /// Create a new output event - pub fn new_output_event(s: S) -> Event - where - S: Into, - { - Event::OutputEvent(s.into()) - } - - /// Create a new window resize event - pub fn new_window_resize_event(new_position: Rectangle) -> Event { - Event::WindowResizeEvent(new_position) - } -} - -/// A keyboard event, indicating that one or more keys were pressed or released. -#[derive(Debug, Clone)] -pub struct KeyboardInputEvent { - /// The key input event from i/o device - pub key_event: KeyEvent, -} - -impl KeyboardInputEvent { - /// Create a new key board input event. `key` is the key input from the keyboard - pub fn new(key_event: KeyEvent) -> KeyboardInputEvent { - KeyboardInputEvent { key_event } - } -} \ No newline at end of file diff --git a/kernel/framebuffer/Cargo.toml b/kernel/framebuffer/Cargo.toml deleted file mode 100644 index db145e069e..0000000000 --- a/kernel/framebuffer/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "framebuffer" -version = "0.1.0" -authors = ["Kevin Boos ", "Wenqiu Yu "] -description = "a framebuffer is a buffer of pixels which can be composited to another framebuffer or be mapped to some physical memory" -edition = "2021" - -[dependencies] -log = "0.4.8" -zerocopy = "0.5.0" - -color = { path = "../color" } -early_printer = { path = "../early_printer" } -memory = { path = "../memory" } -multicore_bringup = { path = "../multicore_bringup" } -shapes = { path = "../shapes" } - -[target.'cfg(target_arch = "x86_64")'.dependencies] -page_attribute_table = { path = "../page_attribute_table" } diff --git a/kernel/framebuffer/src/lib.rs b/kernel/framebuffer/src/lib.rs deleted file mode 100644 index 4b4d0479e7..0000000000 --- a/kernel/framebuffer/src/lib.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! This crate defines a `Framebuffer` structure, which is effectively a region of memory -//! that is interpreted as a 2-D array of pixels. - -#![no_std] - -pub mod pixel; -use core::{ops::{DerefMut, Deref}, hash::{Hash, Hasher}}; -use log::{info, debug}; -use memory::{PteFlags, PteFlagsArch, PhysicalAddress, Mutable, BorrowedSliceMappedPages}; -use shapes::Coord; -pub use pixel::*; - -/// Initializes the final framebuffer based on graphics mode info obtained during boot. -/// -/// The final framebuffer represents the actual pixel content displayed on screen, -/// as its memory is directly mapped to the display device's underlying physical memory. -pub fn init() -> Result, &'static str> { - // Attempt to get the below 3 items of graphic mode info. - let mut width_height_paddr: Option<(usize, usize, PhysicalAddress)> = None; - // Take possession of the early framebuffer and obtain graphic info from it. - if let Some(early_fb) = early_printer::take() { - width_height_paddr = Some(( - early_fb.width as usize, - early_fb.height as usize, - early_fb.paddr, - )); - // Here: the early framebuffer's underlying mapping is dropped. - } - - // If we booted up other CPUs, we may have switched to a better graphics mode. - if let Some(gi) = multicore_bringup::get_graphic_info() { - let paddr = PhysicalAddress::new(gi.physical_address() as usize) - .ok_or("Graphic mode physical address was invalid")?; - let width = gi.width() as usize; - let height = gi.height() as usize; - width_height_paddr = Some((width, height, paddr)); - } - - let (width, height, paddr) = width_height_paddr - .ok_or("Failed to get graphic mode information!")?; - info!("Graphical framebuffer info: {} x {}, at paddr {:#X}", - width, height, paddr, - ); - Framebuffer::new(width, height, Some(paddr)) -} - -/// A framebuffer is a region of memory interpreted as a 2-D array of pixels. -/// The memory buffer is a rectangular region with a width and height. -pub struct Framebuffer { - width: usize, - height: usize, - buffer: BorrowedSliceMappedPages, -} -impl Hash for Framebuffer

{ - fn hash(&self, state: &mut H) { - self.width.hash(state); - self.height.hash(state); - self.buffer.deref().hash(state); - } -} - -impl Framebuffer

{ - /// Creates a new framebuffer with rectangular dimensions of `width * height`, - /// specified in number of pixels. - /// - /// If `physical_address` is `Some`, the returned framebuffer will be a real physical one, - /// i.e., mapped to the physical memory at that address, which is typically hardware graphics memory. - /// In this case, we attempt to map the memory as "write-combining", which only works - /// on x86 if the Page Attribute Table feature is enabled. - /// Otherwise, we map the real physical framebuffer memory with all caching disabled. - /// - /// If `physical_address` is `None`, the returned framebuffer is a "virtual" one - /// that renders to a randomly-allocated chunk of memory. - pub fn new( - width: usize, - height: usize, - physical_address: Option, - ) -> Result, &'static str> { - let kernel_mmi_ref = memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?; - let size = width * height * core::mem::size_of::

(); - let pages = memory::allocate_pages_by_bytes(size) - .ok_or("could not allocate pages for a new framebuffer")?; - - let mapped_framebuffer = if let Some(address) = physical_address { - // For best performance, we map the real physical framebuffer memory - // as write-combining using the PAT (on x86 only). - // If PAT isn't available, fall back to disabling caching altogether. - let mut flags: PteFlagsArch = PteFlags::new() - .valid(true) - .writable(true) - .into(); - - #[cfg(target_arch = "x86_64")] { - if page_attribute_table::is_supported() { - flags = flags.pat_index( - page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index() - ); - info!("Using PAT write-combining mapping for real physical framebuffer memory"); - } else { - flags = flags.device_memory(true); - info!("Falling back to cache-disable mapping for real physical framebuffer memory"); - } - } - #[cfg(not(target_arch = "x86_64"))] { - flags = flags.device_memory(true); - } - - let frames = memory::allocate_frames_by_bytes_at(address, size) - .map_err(|_e| "Couldn't allocate frames for the final framebuffer")?; - let fb_mp = kernel_mmi_ref.lock().page_table.map_allocated_pages_to( - pages, - frames, - flags, - )?; - debug!("Mapped real physical framebuffer: {fb_mp:?}"); - fb_mp - } else { - kernel_mmi_ref.lock().page_table.map_allocated_pages( - pages, - PteFlags::new().valid(true).writable(true), - )? - }; - - Ok(Framebuffer { - width, - height, - buffer: mapped_framebuffer.into_borrowed_slice_mut(0, width * height) - .map_err(|(|_mp, s)| s)?, - }) - } - - /// Returns a mutable reference to this framebuffer's memory as a slice of pixels. - pub fn buffer_mut(&mut self) -> &mut [P] { - &mut self.buffer - } - - /// Returns a reference to this framebuffer's memory as a slice of pixels. - pub fn buffer(&self) -> &[P] { - &self.buffer - } - - /// Returns the `(width, height)` of this framebuffer. - pub fn get_size(&self) -> (usize, usize) { - (self.width, self.height) - } - - /// Composites `src` to the buffer starting from `index`. - pub fn composite_buffer(&mut self, src: &[P], index: usize) { - let len = src.len(); - let dest_end = index + len; - Pixel::composite_buffer(src, &mut self.buffer_mut()[index..dest_end]); - } - - /// Draw a pixel at the given coordinate. - /// The `pixel` will be blended with the existing pixel value - /// at that `coordinate` in this framebuffer. - pub fn draw_pixel(&mut self, coordinate: Coord, pixel: P) { - if let Some(index) = self.index_of(coordinate) { - self.buffer[index] = pixel.blend(self.buffer[index]); - } - } - - /// Overwites a pixel at the given coordinate in this framebuffer - /// instead of blending it like [`draw_pixel`](#method.draw_pixel). - pub fn overwrite_pixel(&mut self, coordinate: Coord, pixel: P) { - self.draw_pixel(coordinate, pixel) - } - - /// Returns the pixel value at the given `coordinate` in this framebuffer. - pub fn get_pixel(&self, coordinate: Coord) -> Option

{ - self.index_of(coordinate).map(|i| self.buffer[i]) - } - - /// Fills (overwrites) the entire framebuffer with the given `pixel` value. - pub fn fill(&mut self, pixel: P) { - for p in self.buffer.deref_mut() { - *p = pixel; - } - } - - /// Returns the index of the given `coordinate` in this framebuffer, - /// if this framebuffer [`contains`](#method.contains) the `coordinate` within its bounds. - pub fn index_of(&self, coordinate: Coord) -> Option { - if self.contains(coordinate) { - Some((self.width * coordinate.y as usize) + coordinate.x as usize) - } else { - None - } - } - - /// Checks if the given `coordinate` is within the framebuffer's bounds. - /// The `coordinate` is relative to the origin coordinate of `(0, 0)` being the top-left point of the framebuffer. - pub fn contains(&self, coordinate: Coord) -> bool { - coordinate.x >= 0 - && coordinate.x < (self.width as isize) - && coordinate.y >= 0 - && coordinate.y < (self.height as isize) - } - - /// Checks if a framebuffer overlaps with an area. - /// # Arguments - /// * `coordinate`: the top-left corner of the area relative to the origin(top-left point) of the framebuffer. - /// * `width`: the width of the area in number of pixels. - /// * `height`: the height of the area in number of pixels. - pub fn overlaps_with(&mut self, coordinate: Coord, width: usize, height: usize) -> bool { - coordinate.x < self.width as isize - && coordinate.x + width as isize >= 0 - && coordinate.y < self.height as isize - && coordinate.y + height as isize >= 0 - } - -} diff --git a/kernel/framebuffer/src/pixel.rs b/kernel/framebuffer/src/pixel.rs deleted file mode 100644 index 087da82c8c..0000000000 --- a/kernel/framebuffer/src/pixel.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Defines the `Pixel` trait as well as basic pixel formats, like RBG/RBGA. - -use core::hash::Hash; -use color::Color; -use zerocopy::FromBytes; - -/// A pixel provides methods to blend with others. -pub trait Pixel: Copy + Hash + FromBytes { - /// Composites the `src` pixel slice to the `dest` pixel slice. - fn composite_buffer(src: &[Self], dest: &mut[Self]); - - /// blend with another pixel considering their extra channel. - fn blend(self, other: Self) -> Self; - - /// Blend two pixels linearly with weights, as `blend` for `origin` and (1-`blend`) for `other`. - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self; -} - - -#[derive(Hash, Debug, Clone, Copy, FromBytes)] -/// An RGB Pixel is a pixel with no extra channel. -pub struct RGBPixel { - pub blue: u8, - pub green: u8, - pub red: u8, - _channel: u8, -} - -#[derive(Hash, Debug, Clone, Copy, FromBytes)] -/// An Alpha Pixel is a pixel with an alpha channel -pub struct AlphaPixel { - pub blue: u8, - pub green: u8, - pub red: u8, - pub alpha: u8 -} - -impl Pixel for RGBPixel { - #[inline] - fn composite_buffer(src: &[Self], dest: &mut[Self]) { - dest.copy_from_slice(src) - } - - #[inline] - fn blend(self, _other: Self) -> Self { - self - } - - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self { - let blend = if blend < 0f32 { - 0f32 - } else if blend > 1f32 { - 1f32 - } else { - blend - }; - - let new_red = - ((origin.red as f32) * blend + (other.red as f32) * (1f32 - blend)) as u8; - let new_green = - ((origin.green as f32) * blend + (other.green as f32) * (1f32 - blend)) as u8; - let new_blue = - ((origin.blue as f32) * blend + (other.blue as f32) * (1f32 - blend)) as u8; - - RGBPixel{ - _channel: 0, - red: new_red, - green: new_green, - blue: new_blue - } - } -} - -impl From for RGBPixel { - fn from(color: Color) -> Self { - RGBPixel { - _channel: 0, - red: color.red(), - green: color.green(), - blue: color.blue(), - } - } -} - -impl Pixel for AlphaPixel { - fn composite_buffer(src: &[Self], dest: &mut[Self]) { - for i in 0..src.len() { - dest[i] = src[i].blend(dest[i]); - } - } - - fn blend(self, other: Self) -> Self { - let alpha = self.alpha as u16; - let red = self.red; - let green = self.green; - let blue = self.blue; - // let ori_alpha = other.alpha; - let ori_red = other.red; - let ori_green = other.green; - let ori_blue = other.blue; - // let new_alpha = (((alpha as u16) * (255 - alpha) + (ori_alpha as u16) * alpha) / 255) as u8; - let new_red = (((red as u16) * (255 - alpha) + (ori_red as u16) * alpha) / 255) as u8; - let new_green = (((green as u16) * (255 - alpha) + (ori_green as u16) * alpha) / 255) as u8; - let new_blue = (((blue as u16) * (255 - alpha) + (ori_blue as u16) * alpha) / 255) as u8; - AlphaPixel { - alpha: alpha as u8, - red: new_red, - green: new_green, - blue: new_blue - } - } - - fn weight_blend(origin: Self, other: Self, blend: f32) -> Self { - let blend = if blend < 0f32 { - 0f32 - } else if blend > 1f32 { - 1f32 - } else { - blend - }; - - let new_channel = - ((origin.alpha as f32) * blend + (other.alpha as f32) * (1f32 - blend)) as u8; - let new_red = - ((origin.red as f32) * blend + (other.red as f32) * (1f32 - blend)) as u8; - let new_green = - ((origin.green as f32) * blend + (other.green as f32) * (1f32 - blend)) as u8; - let new_blue = - ((origin.blue as f32) * blend + (other.blue as f32) * (1f32 - blend)) as u8; - AlphaPixel { - alpha: new_channel, - red: new_red, - green: new_green, - blue: new_blue - } - } -} - -impl From for AlphaPixel { - fn from(color: Color) -> Self { - AlphaPixel { - alpha: color.transparency(), - red: color.red(), - green: color.green(), - blue: color.blue(), - } - } -} diff --git a/kernel/framebuffer_compositor/Cargo.toml b/kernel/framebuffer_compositor/Cargo.toml deleted file mode 100644 index e2e86228be..0000000000 --- a/kernel/framebuffer_compositor/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "framebuffer_compositor" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "the framebuffer compositor composites multiple source framebuffers into one destination framebuffer" - -[dependencies] -spin = "0.9.4" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.compositor] -path = "../compositor" - -[dependencies.hashbrown] -version = "0.11.2" -features = ["nightly"] - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_compositor/src/lib.rs b/kernel/framebuffer_compositor/src/lib.rs deleted file mode 100644 index 5b7780a59f..0000000000 --- a/kernel/framebuffer_compositor/src/lib.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! This crate defines a framebuffer compositor. -//! -//! A framebuffer compositor composites a list of framebuffers into a single destination framebuffer. -//! The coordinate system within a framebuffer is expressed relative to its origin, i.e., the top-left point. -//! -//! # Cache -//! The compositor caches groups of framebuffer rows for better performance. -//! -//! First, it divides each framebuffer into ranges of rows called "blocks" which are `CACHE_BLOCK_HEIGHT` rows in height, -//! and deals with these row ranges one by one. -//! The pixels in each block's row range are a contiguous array of length `CACHE_BLOCK_HEIGHT * framebuffer_width`, -//! and the cache key is the hash value of that pixel array. -//! -//! In the next step, for every `CACHE_BLOCK_HEIGHT` rows, the compositor checks if the pixel array is are already cached. -//! It ignores row ranges that do not overlap with the given bounding box to be updated. -//! If a pixel array is not cached, the compositor will refresh the pixels within the bounding box and cache those `CACHE_BLOCK_HEIGHT` rows. -//! -//! In order to cache a range of rows from the source framebuffer, the compositor needs to cache its contents, its location in the destination framebuffer, and its size. -//! The cache is basically a rectangular region in the destination framebuffer, and we define the structure `CacheBlock` to represent that cached region. - -#![no_std] - -extern crate alloc; -extern crate compositor; -extern crate framebuffer; -extern crate spin; -extern crate hashbrown; -extern crate shapes; - -use alloc::collections::BTreeMap; -use alloc::vec::{Vec}; -use core::hash::{Hash, BuildHasher}; -use hashbrown::hash_map::{DefaultHashBuilder}; -use compositor::{Compositor, FramebufferUpdates, CompositableRegion}; -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; -use spin::Mutex; -use core::ops::Range; - -/// The height of a cache block. In every iteration the compositor will deal with groups of 16 rows and cache them. -pub const CACHE_BLOCK_HEIGHT: usize = 16; - -/// The instance of the framebuffer compositor. -pub static FRAME_COMPOSITOR: Mutex = Mutex::new( - FrameCompositor{ - caches: BTreeMap::new() - } -); - -/// A `CacheBlock` represents the cached (previously-composited) content of a range of rows in the source framebuffer. -/// It specifies the rectangular region in the destination framebuffer and the hash. -/// Once cached, a `CacheBlock` block is independent of the source framebuffer it came from. -/// `content_hash` is the hash value of the actual pixel contents in the cached block. A cache block is identical to some new framebuffer rows to be updated if they share the same `content_hash`, location and width. -pub struct CacheBlock { - /// The rectanglular region in the destination framebuffer occupied by the cached rows in the source framebuffer. - /// We need this information because if an old cache block overlaps with some new framebuffer rows to be updated, - /// the compositor should remove the old one since part of that region will change. - block: Rectangle, - /// The hash value of the actual pixel contents in the cached block. - content_hash: u64, -} - -impl CacheBlock { - /// Checks if a cache block overlaps with another one - pub fn overlaps_with(&self, cache: &CacheBlock) -> bool { - self.contains_corner(cache) || cache.contains_corner(self) - } - - /// checks if the coordinate is within the block - fn contains(&self, coordinate: Coord) -> bool { - coordinate.x >= self.block.top_left.x - && coordinate.x < self.block.bottom_right.x - && coordinate.y >= self.block.top_left.y - && coordinate.y < self.block.bottom_right.y - } - - /// checks if this block contains any of the four corners of another cache block. - fn contains_corner(&self, cache: &CacheBlock) -> bool { - self.contains(cache.block.top_left) - || self.contains(cache.block.top_left + (cache.block.bottom_right.x - cache.block.top_left.x - 1, 0)) - || self.contains(cache.block.top_left + (0, cache.block.bottom_right.y - cache.block.top_left.y - 1)) - || self.contains(cache.block.bottom_right - (1, 1)) - } -} - -/// The framebuffer compositor structure. -/// It caches framebuffer rows since last update as soft states for better performance. -pub struct FrameCompositor { - // Cache of updated framebuffers before - caches: BTreeMap, -} - -impl FrameCompositor { - /// Checks if some rows of a framebuffer are cached. - /// # Arguments - /// * `row_pixels`: the continuous pixels in the rows. - /// * `dest_coord`: the location of the first pixel in the destination framebuffer. - /// * `width`: the width of the rows - /// - fn is_cached(&self, row_pixels: &[P], dest_coord: &Coord, width: usize) -> bool { - match self.caches.get(dest_coord) { - Some(cache) => { - // The same hash and width means the cache block is identical to the row pixels. - // We do not check the height because if the hashes are the same, the number of pixels, namely `width * height` must be the same. - cache.content_hash == hash(row_pixels) && (cache.block.bottom_right.x - cache.block.top_left.x) as usize == width - } - None => false - } - } - - /// This function will return true if several continuous rows in the framebuffer are cached. - /// If false, i.e. the given `row_range` is not in the cache, this function will remove - /// the old cached blocks that overlap with the rows in the given `src_fb_row_range` and cache those rows as a new cache block. - /// # Arguments - /// * `src_fb`: the updated source framebuffer. - /// * `dest_coord`: the position of the source framebuffer (its top-left corner) relative to the destination framebuffer's top-left corner. - /// * `src_fb_row_range`: the range of rows in the source framebuffer to check and cache. - fn check_and_cache( - &mut self, - src_fb: &Framebuffer

, - dest_coord: Coord, - src_fb_row_range: &Range, - ) -> Result { - let (src_width, src_height) = src_fb.get_size(); - let src_buffer_len = src_width * src_height; - - // The start pixel of the rows - let start_index = src_width * src_fb_row_range.start; - let coordinate_start = dest_coord + (0, src_fb_row_range.start as isize); - - // The end pixel of the rows - let end_index = src_width * src_fb_row_range.end; - - let pixel_slice = &src_fb.buffer()[start_index..core::cmp::min(end_index, src_buffer_len)]; - - // Skip if the rows are already cached - if self.is_cached(pixel_slice, &coordinate_start, src_width) { - return Ok(true); - } - - // remove overlapped caches - let new_cache = CacheBlock { - block: Rectangle { - top_left: coordinate_start, - bottom_right: coordinate_start + (src_width as isize, (pixel_slice.len() / src_width) as isize) - }, - content_hash: hash(pixel_slice), - }; - let keys: Vec<_> = self.caches.keys().cloned().collect(); - for key in keys { - if let Some(cache) = self.caches.get_mut(&key) { - if cache.overlaps_with(&new_cache) { - self.caches.remove(&key); - } - }; - } - - self.caches.insert(coordinate_start, new_cache); - Ok(false) - } - - /// Returns the range of rows in the source framebuffer that were (1) previously cached as cache blocks - /// and (2) overlap with the given `dest_bounding_box`. - /// This methods extends the row range of the given bounding box because the compositor deals with chunks of `CACHE_BLOCK_HEIGHT` rows. - /// # Arguments - /// * `dest_coord`: the position in the destination framebuffer (relative to its top-left corner) - /// to where the source framebuffer will be composited. - /// * `dest_bounding_box`: the region of the destination framebuffer that should be composited. - /// * `src_fb_height`: the height of the source framebuffer. - fn get_cache_row_range( - &self, - dest_coord: Coord, - dest_bounding_box: &B, - src_fb_height: usize, - ) -> Range { - let abs_row_range = dest_bounding_box.row_range(); - let mut relative_row_start = abs_row_range.start - dest_coord.y; - let mut relative_row_end = abs_row_range.end - dest_coord.y; - - relative_row_start = core::cmp::max(relative_row_start, 0); - relative_row_end = core::cmp::min(relative_row_end, src_fb_height as isize); - - if relative_row_start >= relative_row_end { - return 0..0; - } - - let cache_row_start = relative_row_start as usize / CACHE_BLOCK_HEIGHT * CACHE_BLOCK_HEIGHT; - let mut cache_row_end = ((relative_row_end - 1) as usize / CACHE_BLOCK_HEIGHT + 1) * CACHE_BLOCK_HEIGHT; - - cache_row_end = core::cmp::min(cache_row_end, src_fb_height); - - cache_row_start..cache_row_end - } - -} - -impl Compositor for FrameCompositor { - fn composite<'a, B: CompositableRegion + Clone, P: 'a + Pixel>( - &mut self, - src_fbs: impl IntoIterator>, - dest_fb: &mut Framebuffer

, - dest_bounding_boxes: impl IntoIterator + Clone, - ) -> Result<(), &'static str> { - let mut box_iter = dest_bounding_boxes.clone().into_iter(); - if box_iter.next().is_none() { - for framebuffer_updates in src_fbs.into_iter() { - let src_fb = framebuffer_updates.src_framebuffer; - let coordinate = framebuffer_updates.coordinate_in_dest_framebuffer; - // Update the whole screen if the caller does not specify the blocks - let (src_width, src_height) = framebuffer_updates.src_framebuffer.get_size(); - // let block_number = (src_height - 1) / CACHE_BLOCK_HEIGHT + 1; - let area = Rectangle { - top_left: coordinate, - bottom_right: coordinate + (src_width as isize, src_height as isize) - }; - let mut row_start = 0; - loop { - if row_start >= src_height { - break; - } - let cache_range = row_start..(row_start + CACHE_BLOCK_HEIGHT); - if !self.check_and_cache(src_fb, coordinate, &cache_range)? { - area.blend_buffers( - src_fb, - dest_fb, - coordinate, - cache_range, - )?; - } - row_start += CACHE_BLOCK_HEIGHT; - } - } - } else { - for framebuffer_updates in src_fbs.into_iter() { - //let mut updated_blocks = Vec::new(); - for bounding_box in dest_bounding_boxes.clone() { - let src_fb = framebuffer_updates.src_framebuffer; - let coordinate = framebuffer_updates.coordinate_in_dest_framebuffer; - let (_, height) = src_fb.get_size(); - let mut row_range = self.get_cache_row_range(coordinate, &bounding_box, height); - // let cache_block_size = CACHE_BLOCK_HEIGHT * width; - - loop { - if row_range.start >= row_range.end { - break; - } - let cache_range = row_range.start..(row_range.start + CACHE_BLOCK_HEIGHT); - // check cache if the bounding box is not a single pixel - if bounding_box.size() > 1 && self.check_and_cache(src_fb, coordinate, &cache_range)? { - row_range.start += CACHE_BLOCK_HEIGHT; - continue; - }; - bounding_box.blend_buffers( - src_fb, - dest_fb, - coordinate, - cache_range, - )?; - row_range.start += CACHE_BLOCK_HEIGHT; - } - } - } - } - - Ok(()) - } -} - -/// Gets the hash of an item -fn hash(item: T) -> u64 { - DefaultHashBuilder::default().hash_one(&item) -} diff --git a/kernel/framebuffer_drawer/Cargo.toml b/kernel/framebuffer_drawer/Cargo.toml deleted file mode 100644 index 605475e224..0000000000 --- a/kernel/framebuffer_drawer/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "framebuffer_drawer" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "basic draw interfaces" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_drawer/src/lib.rs b/kernel/framebuffer_drawer/src/lib.rs deleted file mode 100644 index 52f1629030..0000000000 --- a/kernel/framebuffer_drawer/src/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! This crate contains a series of basic draw functions to draw onto a framebuffer. -//! Displayables invoke these basic functions to display themselves onto a framebuffer. -//! The coordinate in these interfaces is relative to the origin(top-left point) of the framebuffer. - -#![no_std] - -extern crate framebuffer; -extern crate shapes; - -use framebuffer::{Framebuffer, Pixel}; -use shapes::Coord; - -/// Draws a line in a framebuffer. The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `start`: the start coordinate of the line relative to the origin(top-left point) of the framebuffer. -/// * `end`: the end coordinate of the line relative to the origin(top-left point) of the framebuffer. -/// * `color`: the color of the line. -pub fn draw_line( - framebuffer: &mut Framebuffer

, - start: Coord, - end: Coord, - pixel: P, -) { - let width: isize = end.x - start.x; - let height: isize = end.y - start.y; - - let mut line_in_buffer = false; - - // compare the x distance and y distance. Increase/Decrease the longer one at every step. - if width.abs() > height.abs() { - let mut y; - let mut x = start.x; - - // if the end.x is larger than start.x, increase x in the loop. Otherwise decrease it. - let step = if width > 0 { 1 } else { -1 }; - loop { - if x == end.x { - break; - } - y = (x - start.x) * height / width + start.y; - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - line_in_buffer = true; - framebuffer.draw_pixel(coordinate, pixel); - } else if line_in_buffer { - // the part exceeds the buffer will be ignored - break; - } - x += step; - } - } else { - let mut x; - let mut y = start.y; - let step = if height > 0 { 1 } else { -1 }; - loop { - if y == end.y { - break; - } - x = (y - start.y) * width / height + start.x; - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - line_in_buffer = true; - framebuffer.draw_pixel(coordinate, pixel); - } else if line_in_buffer { - // the part exceeds the buffer will be ignored - break; - } - y += step; - } - } -} - -/// Draws a rectangle in a framebuffer. -/// The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `coordinate`: the left top coordinate of the rectangle relative to the origin(top-left point) of the framebuffer. -/// * `width`: the width of the rectangle in number of pixels. -/// * `height`: the height of the rectangle in number of pixels. -/// * `color`: the color of the rectangle's border. -pub fn draw_rectangle( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - pixel: P, -) { - let (buffer_width, buffer_height) = framebuffer.get_size(); - - // return if the rectangle is not within the framebuffer - if !framebuffer.overlaps_with(coordinate, width, height){ - return - } - - // draw the part within the framebuffer - let start_x = core::cmp::max(coordinate.x, 0); - let start_y = core::cmp::max(coordinate.y, 0); - let end_x = core::cmp::min(coordinate.x + width as isize, buffer_width as isize); - let end_y = core::cmp::min(coordinate.y + height as isize, buffer_height as isize); - - // draw the four lines of the rectangle. - let mut top = Coord::new(start_x, start_y); - let end_y_offset = end_y - start_y - 1; - loop { - if top.x == end_x { - break; - } - if coordinate.y >= 0 { - framebuffer.draw_pixel(top, pixel); - } - if (coordinate.y + height as isize) < buffer_height as isize { - framebuffer.draw_pixel(top + (0, end_y_offset), pixel); - } - top.x += 1; - } - - let mut left = Coord::new(start_x, start_y); - let end_x_offset = end_x - start_x - 1; - loop { - if left.y == end_y { - break; - } - if coordinate.x >= 0 { - framebuffer.draw_pixel(left, pixel); - } - if (coordinate.x + width as isize) < buffer_width as isize { - framebuffer.draw_pixel(left + (end_x_offset, 0), pixel); - } - left.y += 1; - } -} - -/// Fills a rectangle in a framebuffer with color. -/// The part exceeding the boundary of the framebuffer will be ignored. -/// # Arguments -/// * `framebuffer`: the framebuffer to draw in. -/// * `coordinate`: the left top coordinate of the retangle relative to the origin(top-left point) of the framebuffer. -/// * `width`: the width of the rectangle in number of pixels. -/// * `height`: the height of the rectangle in number of pixels. -/// * `pixel`: the value of pixels in the rectangle. -pub fn fill_rectangle( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - pixel: P, -) { - let (buffer_width, buffer_height) = framebuffer.get_size(); - // return if the rectangle is not within the framebuffer - if !framebuffer.overlaps_with(coordinate, width, height){ - return - } - - // draw the part within the framebuffer - let start_x = core::cmp::max(coordinate.x, 0); - let start_y = core::cmp::max(coordinate.y, 0); - let end_x = core::cmp::min(coordinate.x + width as isize, buffer_width as isize); - let end_y = core::cmp::min(coordinate.y + height as isize, buffer_height as isize); - - // draw every pixel line by line - let mut coordinate = Coord::new(start_x, start_y); - loop { - loop { - framebuffer.draw_pixel(coordinate, pixel); - coordinate.x += 1; - if coordinate.x == end_x { - break; - } - } - coordinate.y += 1; - if coordinate.y == end_y { - break; - } - coordinate.x = start_x; - } -} - -/// Draw a circle in the framebuffer. `coordinate` is the position of the center of the circle relative to the top-left corner of the framebuffer and `r` is the radius -pub fn draw_circle(framebuffer: &mut Framebuffer

, center: Coord, r: usize, pixel: P) { - let r2 = (r * r) as isize; - for y in center.y - r as isize..center.y + r as isize { - for x in center.x - r as isize..center.x + r as isize { - let coordinate = Coord::new(x, y); - if framebuffer.contains(coordinate) { - let d = coordinate - center; - if d.x * d.x + d.y * d.y <= r2 { - framebuffer.draw_pixel(coordinate, pixel); - } - } - } - } -} \ No newline at end of file diff --git a/kernel/framebuffer_printer/Cargo.toml b/kernel/framebuffer_printer/Cargo.toml deleted file mode 100644 index 779225c625..0000000000 --- a/kernel/framebuffer_printer/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "framebuffer_printer" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "print a string in a framebuffer" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.font] -path = "../font" - -[dependencies.shapes] -path = "../shapes" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/framebuffer_printer/src/lib.rs b/kernel/framebuffer_printer/src/lib.rs deleted file mode 100644 index a5a932374c..0000000000 --- a/kernel/framebuffer_printer/src/lib.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! This crate contains functions to print strings in a framebuffer. -//! The coordinate in these functions is relative to the origin(top-left point) of the framebuffer. - -#![no_std] - -extern crate alloc; -extern crate font; -extern crate framebuffer; -extern crate shapes; - -use alloc::vec; -use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Framebuffer, Pixel}; -use shapes::{Coord, Rectangle}; - - -type Ascii = u8; - -/// Prints a string in a framebuffer. -/// Returns (column, line, rectangle), i.e. the position of the next symbol and an rectangle which covers the updated area. -/// A block item (index, width) represents the index of line number and the width of charaters in this line as pixels. It can be viewed as a framebuffer block which is described in the `framebuffer_compositor` crate. -/// # Arguments -/// * `framebuffer`: the framebuffer to display in. -/// * `coordinate`: the left top coordinate of the text block relative to the origin(top-left point) of the framebuffer. -/// * `width`, `height`: the size of the text block in number of pixels. -/// * `slice`: the string to display. -/// * `fg_pixel`: the value of pixels in the foreground. -/// * `bg_pixel` the value of pixels in the background. -/// * `column`, `line`: the location of the text in the text block in number of characters. -#[allow(clippy::too_many_arguments)] -pub fn print_string( - framebuffer: &mut Framebuffer

, - coordinate: Coord, - width: usize, - height: usize, - slice: &str, - fg_pixel: P, - bg_pixel: P, - column: usize, - line: usize, -) -> (usize, usize, Rectangle) { - let buffer_width = width / CHARACTER_WIDTH; - let buffer_height = height / CHARACTER_HEIGHT; - let (x, y) = (coordinate.x, coordinate.y); - - let mut curr_line = line; - let mut curr_column = column; - - let top_left = Coord::new(0, (curr_line * CHARACTER_HEIGHT) as isize); - - for byte in slice.bytes() { - if byte == b'\n' { - let mut blank = Rectangle { - top_left: Coord::new( - coordinate.x + (curr_column * CHARACTER_WIDTH) as isize, - coordinate.y + (curr_line * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - coordinate.x + width as isize, - coordinate.y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ) - }; - // fill the remaining blank of current line and go to the next line - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - curr_column = 0; - curr_line += 1; - if curr_line == buffer_height { - break; - } - } else { - if curr_column == buffer_width { - curr_column = 0; - curr_line += 1; - if curr_line == buffer_height { - break; - } - } - // print the next character - print_ascii_character( - framebuffer, - byte, - fg_pixel, - bg_pixel, - coordinate, - curr_column, - curr_line, - ); - curr_column += 1; - } - } - - let mut blank = Rectangle { - top_left: Coord::new( - x + (curr_column * CHARACTER_WIDTH) as isize, - y + (curr_line * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - x + width as isize, - y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ) - }; - // fill the blank of the last line - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - - let bottom_right = Coord::new( - (buffer_width * CHARACTER_WIDTH) as isize, - ((curr_line + 1) * CHARACTER_HEIGHT) as isize - ); - - let update_area = Rectangle { - top_left, - bottom_right, - }; - - // fill the blank of the remaining part - blank = Rectangle { - top_left: Coord::new( - x, - y + ((curr_line + 1) * CHARACTER_HEIGHT) as isize, - ), - bottom_right: Coord::new( - x + width as isize, - y + height as isize, - ) - }; - fill_blank( - framebuffer, - &mut blank, - bg_pixel, - ); - - // return the position of next symbol and updated blocks. - (curr_column, curr_line, update_area) -} - -/// Prints a character to the framebuffer at position (line, column) of all characters in the text area. -/// # Arguments -/// * `framebuffer`: the framebuffer to display in. -/// * `character`: the ASCII code of the character to display. -/// * `fg_pixel`: the value of every pixel in the character. -/// * `bg_color`: the value of every pixel in the background. -/// * `coordinate`: the left top coordinate of the text block relative to the origin(top-left point) of the framebuffer. -/// * `column`, `line`: the location of the character in the text block as symbols. -pub fn print_ascii_character( - framebuffer: &mut Framebuffer

, - character: Ascii, - fg_pixel: P, - bg_pixel: P, - coordinate: Coord, - column: usize, - line: usize, -) { - let start = coordinate + ((column * CHARACTER_WIDTH) as isize, (line * CHARACTER_HEIGHT) as isize); - if !framebuffer.overlaps_with(start, CHARACTER_WIDTH, CHARACTER_HEIGHT) { - return - } - // print from the offset within the framebuffer - let (buffer_width, buffer_height) = framebuffer.get_size(); - let off_set_x: usize = if start.x < 0 { -(start.x) as usize } else { 0 }; - let off_set_y: usize = if start.y < 0 { -(start.y) as usize } else { 0 }; - let mut j = off_set_x; - let mut i = off_set_y; - loop { - let coordinate = start + (j as isize, i as isize); - if framebuffer.contains(coordinate) { - let pixel = if j >= 1 { - // leave 1 pixel gap between two characters - let index = j - 1; - let char_font = font::FONT_BASIC[character as usize][i]; - if get_bit(char_font, index) != 0 { - fg_pixel - } else { - bg_pixel - } - } else { - bg_pixel - }; - framebuffer.draw_pixel(coordinate, pixel); - } - j += 1; - if j == CHARACTER_WIDTH || start.x + j as isize == buffer_width as isize { - i += 1; - if i == CHARACTER_HEIGHT || start.y + i as isize == buffer_height as isize { - return - } - j = off_set_x; - } - } -} - -/// Fill a blank text area (left, top, right, bottom) with color. The tuple specifies the location of the area relative to the origin(top-left point) of the framebuffer. -pub fn fill_blank( - framebuffer: &mut Framebuffer

, - blank: &mut Rectangle, - pixel: P, -) { - - let (width, height) = framebuffer.get_size(); - // fill the part within the framebuffer - blank.top_left.x = core::cmp::max(0, blank.top_left.x); - blank.top_left.y = core::cmp::max(0, blank.top_left.y); - blank.bottom_right.x = core::cmp::min(blank.bottom_right.x, width as isize); - blank.bottom_right.y = core::cmp::min(blank.bottom_right.y, height as isize); - - if blank.top_left.x >= blank.bottom_right.x || - blank.top_left.y >= blank.bottom_right.y { - return - } - - let fill = vec![pixel; (blank.bottom_right.x - blank.top_left.x) as usize]; - let mut coordinate = blank.top_left; - loop { - if coordinate.y == blank.bottom_right.y { - return - } - if let Some(start) = framebuffer.index_of(coordinate) { - framebuffer.composite_buffer(&fill, start); - } - coordinate.y += 1; - } -} - -/// Gets the i_th most significant bit of `char_font`. The returned value is `1` or `0`. -fn get_bit(char_font: u8, i: usize) -> u8 { - char_font & (0x80 >> i) -} - diff --git a/kernel/graphics/Cargo.toml b/kernel/graphics/Cargo.toml new file mode 100644 index 0000000000..0b5e8ce544 --- /dev/null +++ b/kernel/graphics/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "graphics" +version = "0.1.0" +edition = "2021" + +[dependencies] +color = { path = "../color" } +geometry = { path = "../../libs/geometry" } +log = "0.4.8" +memory = { path = "../memory" } +page_attribute_table = { path = "../page_attribute_table" } +# TODO: Upgrade to 0.7.25 +zerocopy = "0.5.0" +time = { path = "../time" } diff --git a/kernel/graphics/src/framebuffer.rs b/kernel/graphics/src/framebuffer.rs new file mode 100644 index 0000000000..d95e620a1b --- /dev/null +++ b/kernel/graphics/src/framebuffer.rs @@ -0,0 +1,223 @@ +use core::{ + borrow::{Borrow, BorrowMut}, + ops::{Deref, DerefMut}, +}; +use core::fmt::{Debug, Formatter}; + +use geometry::{Containable, Coordinates, Rectangle}; +use memory::{BorrowedSliceMappedPages, Mutable, PhysicalAddress, PteFlags, PteFlagsArch}; + +use crate::Pixel; + +pub struct Framebuffer

+where + P: Pixel, +{ + pub(crate) inner: BorrowedSliceMappedPages, + dimensions: FramebufferDimensions, +} + +#[derive(Debug, Copy, Clone)] +pub struct FramebufferDimensions { + pub width: usize, + pub height: usize, + pub stride: usize, +} + +impl

Debug for Framebuffer

+where + P: Pixel +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Framebuffer") + .field("dimensions", &self.dimensions) + .finish() + } +} + +impl

Framebuffer

+where + P: Pixel, +{ + #[inline] + pub fn new( + inner: BorrowedSliceMappedPages, + dimensions: FramebufferDimensions, + ) -> Self { + assert!( + dimensions.width <= dimensions.stride, + "invalid framebuffer dimensions" + ); + assert_eq!( + dimensions.stride * dimensions.height, + inner.len(), + "framebuffer dimensions did not match buffer dimensions" + ); + + Self { inner, dimensions } + } + + #[inline] + pub fn new_software(dimensions: FramebufferDimensions) -> Self { + assert!( + dimensions.width <= dimensions.stride, + "invalid framebuffer dimensions" + ); + + // TODO Error handling. + + let kernel_mmi_ref = memory::get_kernel_mmi_ref().unwrap(); + + let size = dimensions.height * dimensions.stride * core::mem::size_of::

(); + let pages = memory::allocate_pages_by_bytes(size).unwrap(); + let mapped_pages = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages(pages, PteFlags::new().valid(true).writable(true)) + .unwrap(); + + Self { + inner: mapped_pages + .into_borrowed_slice_mut(0, dimensions.height * dimensions.stride) + .unwrap(), + dimensions, + } + } + + #[inline] + pub unsafe fn new_hardware(address: PhysicalAddress, dimensions: FramebufferDimensions) -> Self { + assert!( + dimensions.width <= dimensions.stride, + "invalid framebuffer dimensions" + ); + + // TODO Error handling. + + let kernel_mmi_ref = memory::get_kernel_mmi_ref().unwrap(); + let size = dimensions.height * dimensions.stride * core::mem::size_of::

(); + let pages = memory::allocate_pages_by_bytes(size).unwrap(); + + // For best performance, we map the real physical framebuffer memory + // as write-combining using the PAT (on x86 only). + // If PAT isn't available, fall back to disabling caching altogether. + let mut flags: PteFlagsArch = PteFlags::new().valid(true).writable(true).into(); + + #[cfg(target_arch = "x86_64")] + { + if page_attribute_table::is_supported() { + flags = flags.pat_index( + page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index(), + ); + } else { + flags = flags.device_memory(true); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + flags = flags.device_memory(true); + } + + let frames = memory::allocate_frames_by_bytes_at(address, size).unwrap(); + let mapped_pages = kernel_mmi_ref + .lock() + .page_table + .map_allocated_pages_to(pages, frames, flags) + .unwrap(); + + Self { + inner: mapped_pages + .into_borrowed_slice_mut(0, dimensions.height * dimensions.stride) + .unwrap(), + dimensions, + } + } + + #[inline] + pub const fn dimensions(&self) -> FramebufferDimensions { + self.dimensions + } + + #[inline] + pub const fn width(&self) -> usize { + self.dimensions.width + } + + #[inline] + pub const fn height(&self) -> usize { + self.dimensions.height + } + + #[inline] + pub const fn stride(&self) -> usize { + self.dimensions.stride + } + + #[inline] + pub fn rows(&self) -> impl Iterator { + self.inner.chunks(self.stride()) + } + + #[inline] + pub fn rows_mut(&mut self) -> impl Iterator { + let stride = self.stride(); + self.inner.chunks_mut(stride) + } + + #[inline] + pub fn contains(&self, containable: T) -> bool + where + T: Containable, + { + // TODO: Width or stride? + // TODO: Zero-width or zero-height framebuffer would panic. + let rectangle = Rectangle::new(Coordinates::ORIGIN, self.width(), self.height()); + rectangle.contains(containable) + } + + pub fn set(&mut self, coordinates: Coordinates, pixel: P) { + let stride = self.stride(); + self[coordinates.y * stride + coordinates.x] = pixel; + } +} + +impl

Borrow<[P]> for Framebuffer

+where + P: Pixel, +{ + fn borrow(&self) -> &[P] { + self.deref() + } +} + +impl

BorrowMut<[P]> for Framebuffer

+where + P: Pixel, +{ + fn borrow_mut(&mut self) -> &mut [P] { + self.deref_mut() + } +} + +impl

Deref for Framebuffer

+where + P: Pixel, +{ + type Target = [P]; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl

DerefMut for Framebuffer

+where + P: Pixel, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +mod private { + pub trait Sealed {} +} diff --git a/kernel/graphics/src/lib.rs b/kernel/graphics/src/lib.rs new file mode 100644 index 0000000000..de5101c45c --- /dev/null +++ b/kernel/graphics/src/lib.rs @@ -0,0 +1,196 @@ +#![no_std] +#![feature(const_trait_impl)] + +mod framebuffer; +mod pixel; + +use core::cmp::min; + +pub use geometry::{Coordinates, Horizontal, Rectangle, Vertical}; + +pub use crate::{ + framebuffer::{Framebuffer, FramebufferDimensions}, + pixel::{AlphaPixel, Pixel}, +}; + +pub struct SoftwareDoubleBuffer

+where + P: Pixel, +{ + front: Framebuffer

, + back: Framebuffer

, +} + +impl

SoftwareDoubleBuffer

+where + P: Pixel, +{ + /// Returns a double buffered software driver given a hardware buffer + /// `front`. + pub fn new(front: Framebuffer

) -> Self { + Self { + back: Framebuffer::new_software(front.dimensions()), + front, + } + } + + pub fn back(&mut self) -> &mut Framebuffer

{ + &mut self.back + } + + pub fn swap(&mut self, rectangles: &[Rectangle]) { + let start = time::Instant::now(); + for rectangle in rectangles { + let top = rectangle.y(Vertical::Top); + // Non-inclusive + let bottom = min(top + rectangle.height(), self.height()); + + let left = rectangle.x(Horizontal::Left); + // Non-inclusive + // TODO: Width or stride? + let right = min(left + rectangle.width(), self.width()); + + // log::error!("{rectangle:?}"); + // log::error!("{top:?} {bottom:?} {left:?} {right:?}"); + + if left == 0 + && right == self.stride() - 1 + // TODO: Do we need this condition? + && self.width() == self.stride() + { + let start = top * self.stride(); + let end = bottom * self.stride(); + + self.front[start..end].copy_from_slice(&self.back[start..end]); + } + + let num_rows = bottom - top; + + for (front_row, back_row) in self + .front + .rows_mut() + .skip(top) + .take(num_rows) + .zip(self.back.rows_mut().skip(top).take(num_rows)) + { + front_row[left..right].copy_from_slice(&back_row[left..right]); + } + } + // log::warn!("thingy took: {:?}", time::Instant::now().duration_since(start)); + // log::warn!("rectangle length: {}", rectangles.len()); + } + + // The front and back buffers have the same dimension. + + #[inline] + pub fn width(&self) -> usize { + self.front.width() + } + + #[inline] + pub fn height(&self) -> usize { + self.front.height() + } + + #[inline] + pub fn stride(&self) -> usize { + self.front.stride() + } +} + +pub struct HardwareDoubleBuffer

+where + P: Pixel, +{ + front: Framebuffer

, + back: Framebuffer

, + swap_function: fn(), +} + +impl

HardwareDoubleBuffer

+where + P: Pixel, +{ + pub fn new(front: Framebuffer

, back: Framebuffer

, swap_function: fn()) -> Self { + Self { + front, + back, + swap_function, + } + } + + pub fn back(&mut self) -> &mut Framebuffer

{ + &mut self.back + } + + pub fn swap(&mut self) { + // TODO: This is correct right? + core::mem::swap(&mut self.front, &mut self.back); + (self.swap_function)(); + } +} + +// pub struct HardwareTripleBuffer

+// where +// P: Pixel, +// { +// front: Framebuffer

, +// first_back: Framebuffer

, +// second_back: Framebuffer

, +// } + +pub enum GraphicsDriver

+where + P: Pixel, +{ + SoftwareDouble(SoftwareDoubleBuffer

), + HardwareDouble(HardwareDoubleBuffer

), + // HardwareTriple(HardwareTripleBuffer

), +} + +impl

GraphicsDriver

+where + P: Pixel, +{ + pub fn back(&mut self) -> &mut Framebuffer

{ + match self { + GraphicsDriver::SoftwareDouble(b) => b.back(), + GraphicsDriver::HardwareDouble(b) => b.back(), + } + } + + pub fn swap(&mut self, rectangles: &[Rectangle]) { + match self { + // TODO: Compute rectangle overlap. + GraphicsDriver::SoftwareDouble(b) => b.swap(rectangles), + GraphicsDriver::HardwareDouble(b) => b.swap(), + } + } +} + +impl

From> for GraphicsDriver

+where + P: Pixel, +{ + fn from(value: SoftwareDoubleBuffer

) -> Self { + Self::SoftwareDouble(value) + } +} + +impl

From> for GraphicsDriver

+where + P: Pixel, +{ + fn from(value: HardwareDoubleBuffer

) -> Self { + Self::HardwareDouble(value) + } +} + +// impl

From> for GraphicsDriver

+// where +// P: Pixel, +// { +// fn from(value: HardwareTripleBuffer

) -> Self { +// Self::HardwareTriple(value) +// } +// } diff --git a/kernel/graphics/src/pixel.rs b/kernel/graphics/src/pixel.rs new file mode 100644 index 0000000000..97ad36b729 --- /dev/null +++ b/kernel/graphics/src/pixel.rs @@ -0,0 +1,28 @@ +use color::Color; +use zerocopy::FromBytes; + +pub trait Pixel: FromBytes + Copy {} + +#[derive(Clone, Copy, FromBytes)] +// This is necessary right? +#[repr(C)] +pub struct AlphaPixel { + blue: u8, + green: u8, + red: u8, + alpha: u8, +} + +impl Pixel for AlphaPixel {} + +// TODO: Constify +impl From for AlphaPixel { + fn from(color: Color) -> Self { + AlphaPixel { + alpha: color.transparency(), + red: color.red(), + green: color.green(), + blue: color.blue(), + } + } +} diff --git a/kernel/graphics/src/rectangle.rs b/kernel/graphics/src/rectangle.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kernel/kernel_config/src/time.rs b/kernel/kernel_config/src/time.rs index 87d62e8803..7cbb586153 100644 --- a/kernel/kernel_config/src/time.rs +++ b/kernel/kernel_config/src/time.rs @@ -9,7 +9,7 @@ pub const CONFIG_PIT_FREQUENCY_HZ: u32 = 1000; pub const CONFIG_RTC_FREQUENCY_HZ: usize = 128; /// The timeslice period, specified in microseconds. -pub const CONFIG_TIMESLICE_PERIOD_MICROSECONDS: u32 = 8000; // 8ms +pub const CONFIG_TIMESLICE_PERIOD_MICROSECONDS: u32 = 800; // 8ms /// the heartbeat period in milliseconds pub const CONFIG_HEARTBEAT_PERIOD_MS: usize = 10000; \ No newline at end of file diff --git a/kernel/keyboard/Cargo.toml b/kernel/keyboard/Cargo.toml index 9b1d022139..754167a99d 100644 --- a/kernel/keyboard/Cargo.toml +++ b/kernel/keyboard/Cargo.toml @@ -8,16 +8,15 @@ edition = "2021" [dependencies] spin = "0.9.4" x86_64 = "0.14.8" -mpmc = "0.1.6" log = "0.4.8" once_cell = { version = "1", default-features = false } +[dependencies.async_channel] +path = "../async_channel" + [dependencies.keycodes_ascii] path = "../../libs/keycodes_ascii" -[dependencies.event_types] -path = "../event_types" - [dependencies.ps2] path = "../ps2" diff --git a/kernel/keyboard/src/lib.rs b/kernel/keyboard/src/lib.rs index 1a6ca62634..0672ee29f3 100644 --- a/kernel/keyboard/src/lib.rs +++ b/kernel/keyboard/src/lib.rs @@ -4,12 +4,12 @@ #![feature(abi_x86_interrupt)] use core::sync::atomic::{AtomicBool, Ordering}; -use keycodes_ascii::{Keycode, KeyboardModifiers, KEY_RELEASED_OFFSET, KeyAction, KeyEvent}; +pub use keycodes_ascii::KeyEvent; +use keycodes_ascii::{Keycode, KeyboardModifiers, KEY_RELEASED_OFFSET, KeyAction}; use log::{error, warn, debug}; use once_cell::unsync::Lazy; use spin::Once; -use mpmc::Queue; -use event_types::Event; +use async_channel::Channel; use ps2::{PS2Keyboard, KeyboardType, LEDState, ScancodeSet}; use x86_64::structures::idt::InterruptStackFrame; @@ -24,7 +24,7 @@ static KEYBOARD: Once = Once::new(); struct KeyboardInterruptParams { keyboard: PS2Keyboard<'static>, - queue: Queue, + queue: Channel, } /// Initialize the PS/2 keyboard driver and register its interrupt handler. @@ -33,7 +33,7 @@ struct KeyboardInterruptParams { /// * `keyboard`: a wrapper around keyboard functionality, used by the keyboard interrupt handler. /// * `keyboard_queue_producer`: the queue onto which the keyboard interrupt handler /// will push new keyboard events when a key action occurs. -pub fn init(keyboard: PS2Keyboard<'static>, keyboard_queue_producer: Queue) -> Result<(), &'static str> { +pub fn init(keyboard: PS2Keyboard<'static>, keyboard_queue_producer: Channel) -> Result<(), &'static str> { // Detect which kind of keyboard is connected. // TODO: actually do something with the keyboard type. match keyboard.keyboard_detect() { @@ -114,7 +114,7 @@ extern "x86-interrupt" fn ps2_keyboard_handler(_stack_frame: InterruptStackFrame /// /// Returns Ok(()) if everything was handled properly. /// Otherwise, returns an error string. -fn handle_keyboard_input(keyboard: &PS2Keyboard, queue: &Queue, scan_code: u8, extended: bool) -> Result<(), &'static str> { +fn handle_keyboard_input(keyboard: &PS2Keyboard, queue: &Channel, scan_code: u8, extended: bool) -> Result<(), &'static str> { // SAFE: no real race conditions with keyboard presses let modifiers = unsafe { &mut KBD_MODIFIERS }; // debug!("KBD_MODIFIERS before {}: {:?}", scan_code, modifiers); @@ -194,8 +194,8 @@ fn handle_keyboard_input(keyboard: &PS2Keyboard, queue: &Queue, scan_code }; if let Ok(keycode) = Keycode::try_from(adjusted_scan_code) { - let event = Event::new_keyboard_event(KeyEvent::new(keycode, action, **modifiers)); - queue.push(event).map_err(|_| "keyboard input queue is full") + let event = KeyEvent::new(keycode, action, **modifiers); + queue.try_send(event).map_err(|_| "keyboard input queue is full") } else { error!("handle_keyboard_input(): Unknown scancode: {scan_code:?}, adjusted scancode: {adjusted_scan_code:?}"); Err("unknown keyboard scancode") diff --git a/kernel/libterm/Cargo.toml b/kernel/libterm/Cargo.toml index 46adb12171..bee3d15291 100644 --- a/kernel/libterm/Cargo.toml +++ b/kernel/libterm/Cargo.toml @@ -2,8 +2,12 @@ name = "libterm" version = "0.1.0" authors = ["Andrew Pham ", "Zhiyao Ma "] +edition = "2021" [dependencies] +compositor = { path = "../compositor" } +draw = { path = "../draw" } +geometry = { path = "../../libs/geometry" } [dependencies.log] version = "0.4.8" @@ -21,33 +25,6 @@ path = "../root" [dependencies.color] path = "../color" -[dependencies.event_types] -path = "../event_types" - -[dependencies.text_display] -path = "../displayable/text_display" - -[dependencies.displayable] -path = "../displayable" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.framebuffer_printer] -path = "../framebuffer_printer" - -[dependencies.window_manager] -path = "../window_manager" - -[dependencies.window] -path = "../window" - [dependencies.time] path = "../time" diff --git a/kernel/libterm/src/cursor.rs b/kernel/libterm/src/cursor.rs index 06c1d2d0a8..efc1dcee9d 100644 --- a/kernel/libterm/src/cursor.rs +++ b/kernel/libterm/src/cursor.rs @@ -1,5 +1,6 @@ use super::*; use time::{Duration, Instant}; +use draw::{Coordinates, Settings, Text, Rectangle, Drawable, Char}; /// The cursor structure used in the terminal. /// A cursor is a special symbol shown in the text box of a terminal. It indicates the position of character where the next input would be put or the delete operation works on. @@ -61,7 +62,7 @@ impl Cursor { /// Display a cursor in a framebuffer /// # Arguments - /// * `coordinate`: the start point of a textarea in the framebuffer. + /// * `coordinates`: the start point of a textarea in the framebuffer. /// * `column`: the column of the cursor in the textarea. /// * `line`: the line of the cursor in the textarea. /// * `framebuffer`: the framebuffer to display the cursor in. @@ -69,47 +70,32 @@ impl Cursor { /// Returns a bounding box which wraps the cursor. pub fn display( &mut self, - coordinate: Coord, + coordinates: Coordinates, column: usize, line: usize, framebuffer: &mut Framebuffer

, ) -> Result where Color: Into

{ if self.blink() { if self.show() { - framebuffer_drawer::fill_rectangle( - framebuffer, - coordinate - + ( - (column * CHARACTER_WIDTH) as isize, - (line * CHARACTER_HEIGHT) as isize, - ) - + (0, 1), - CHARACTER_WIDTH, - CHARACTER_HEIGHT - 2, - self.color.into(), - ); + let settings = Settings { + foreground: self.color.into(), + background: None, + }; + let coordinates = coordinates + Coordinates::new(column * CHARACTER_WIDTH, line * CHARACTER_HEIGHT); + Rectangle::new(coordinates, CHARACTER_WIDTH, CHARACTER_HEIGHT - 2).draw(framebuffer, &settings); } else { - framebuffer_printer::print_ascii_character( - framebuffer, - self.underlying_char, - FONT_FOREGROUND_COLOR.into(), - FONT_BACKGROUND_COLOR.into(), - coordinate, - column, - line, - ) + let settings = Settings { + foreground: FONT_FOREGROUND_COLOR.into(), + background: Some(FONT_BACKGROUND_COLOR.into()), + }; + let coordinates = coordinates + Coordinates::new(column * CHARACTER_WIDTH, line * CHARACTER_HEIGHT); + Char::new(self.underlying_char as char, coordinates).draw(framebuffer, &settings); } } - let top_left = coordinate - + ( - (column * CHARACTER_WIDTH) as isize, - (line * CHARACTER_HEIGHT) as isize, - ); - let bounding_box = Rectangle { - top_left, - bottom_right: top_left + (CHARACTER_WIDTH as isize, CHARACTER_HEIGHT as isize), - }; + let top_left = + coordinates + Coordinates::new(column * CHARACTER_WIDTH, line * CHARACTER_HEIGHT); + let bounding_box = Rectangle::new(top_left, CHARACTER_WIDTH, CHARACTER_HEIGHT); Ok(bounding_box) } diff --git a/kernel/libterm/src/lib.rs b/kernel/libterm/src/lib.rs index bea9ab297c..37cd6b8bd5 100644 --- a/kernel/libterm/src/lib.rs +++ b/kernel/libterm/src/lib.rs @@ -1,8 +1,10 @@ //! A basic terminal emulator library. //! -//! The terminal has several main responsibilities: -//! * Managing the scrollback buffer, a string of characters that should be printed to the screen. -//! * Determining which parts of that buffer should be displayed and using the window manager to do so. +//! The terminal has several main responsibilities: +//! * Managing the scrollback buffer, a string of characters that should be +//! printed to the screen. +//! * Determining which parts of that buffer should be displayed and using the +//! window manager to do so. //! * Handling the command line user input. //! * Displaying the cursor at the right position //! * Handling events delivered from the window manager. @@ -10,37 +12,24 @@ #![no_std] extern crate alloc; -#[macro_use] extern crate log; -extern crate dfqueue; -extern crate environment; -extern crate event_types; -extern crate displayable; -extern crate font; -extern crate framebuffer; -extern crate framebuffer_drawer; -extern crate framebuffer_printer; -extern crate time; -extern crate window_manager; -extern crate window; -extern crate text_display; -extern crate shapes; -extern crate color; - -use core::ops::DerefMut; -use alloc::string::{String, ToString}; -use alloc::vec::Vec; -use cursor::*; -use text_display::TextDisplay; -use displayable::Displayable; -use event_types::Event; -use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; -use framebuffer::{Framebuffer, Pixel}; + +pub mod cursor; + +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; + use color::Color; -use shapes::{Coord, Rectangle}; -use window::Window; +pub use compositor::Event; +use compositor::{Framebuffer, Pixel, Window}; +use draw::{Coordinates, Drawable, Rectangle, Settings, Text}; +use font::{CHARACTER_HEIGHT, CHARACTER_WIDTH}; +use geometry::{Horizontal, Vertical}; use time::Duration; -pub mod cursor; +use crate::cursor::*; pub const FONT_FOREGROUND_COLOR: Color = color::LIGHT_GREEN; pub const FONT_BACKGROUND_COLOR: Color = color::BLACK; @@ -49,55 +38,71 @@ const DEFAULT_CURSOR_FREQ: Duration = Duration::from_millis(530); /// Error type for tracking different scroll errors that a terminal /// application could encounter. pub enum ScrollError { - /// Occurs when a index-calculation returns an index that is outside of the + /// Occurs when a index-calculation returns an index that is outside of the /// bounds of the scroll buffer - OffEndBound + OffEndBound, } /// An instance of a graphical terminal emulator. pub struct Terminal { /// The terminal's own window. pub window: Window, - /// The terminal's scrollback buffer which stores a string to be displayed by the text display + /// The terminal's scrollback buffer which stores a string to be displayed + /// by the text display scrollback_buffer: String, - /// Indicates whether the text display is displaying the last part of the scrollback buffer slice + /// Indicates whether the text display is displaying the last part of the + /// scrollback buffer slice is_scroll_end: bool, - /// The starting index of the scrollback buffer string slice that is currently being displayed on the text display + /// The starting index of the scrollback buffer string slice that is + /// currently being displayed on the text display scroll_start_idx: usize, - /// The text displayable which the terminal prints to. - text_display: TextDisplay, /// The cursor of the terminal. pub cursor: Cursor, + // TODO: Document + pub text: String, } /// Private methods of `Terminal`. impl Terminal { - /// Gets the width and height of the text displayable in number of characters. + /// Gets the width and height of the text displayable in number of + /// characters. pub fn get_text_dimensions(&self) -> (usize, usize) { - self.text_display.get_dimensions() + ( + // The type parameter doesn't matter. + Text::<&str>::grid_width(&self.window.as_framebuffer()), + Text::<&str>::grid_height(&self.window.as_framebuffer()), + ) } - /// This function takes in the end index of some index in the scrollback buffer and calculates the starting index of the - /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of - /// text display. - /// If the text display's first line will display a continuation of a syntactical line in the scrollback buffer, this function - /// calculates the starting index so that when displayed on the text display, it preserves that line so that it looks the same - /// as if the whole physical line is displayed on the buffer - /// - /// Return: starting index of the string and the cursor position(with respect to position on the screen, not in the scrollback buffer) in that order + /// This function takes in the end index of some index in the scrollback + /// buffer and calculates the starting index of the scrollback buffer so + /// that a slice containing the starting and ending index would perfectly + /// fit inside the dimensions of text display. + /// If the text display's first line will display a continuation of a + /// syntactical line in the scrollback buffer, this function calculates + /// the starting index so that when displayed on the text display, it + /// preserves that line so that it looks the same as if the whole + /// physical line is displayed on the buffer + /// + /// Return: starting index of the string and the cursor position(with + /// respect to position on the screen, not in the scrollback buffer) in that + /// order fn calc_start_idx(&self, end_idx: usize) -> (usize, usize) { let (buffer_width, buffer_height) = self.get_text_dimensions(); let mut start_idx = end_idx; - // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) + // Grabs a max-size slice of the scrollback buffer (usually does not totally fit + // because of newlines) let result = if end_idx > buffer_width * buffer_height { - self.scrollback_buffer.get(end_idx - buffer_width * buffer_height..end_idx) + self.scrollback_buffer + .get(end_idx - buffer_width * buffer_height..end_idx) } else { self.scrollback_buffer.get(0..end_idx) }; if let Some(slice) = result { let mut total_lines = 0; - // Obtains a vector of indices of newlines in the slice in the REVERSE order that they occur + // Obtains a vector of indices of newlines in the slice in the REVERSE order + // that they occur let new_line_indices: Vec<(usize, &str)> = slice.rmatch_indices('\n').collect(); // if there are no new lines in the slice if new_line_indices.is_empty() { @@ -105,129 +110,168 @@ impl Terminal { return (0, end_idx); } else { start_idx -= buffer_height * buffer_width; // text with no newlines will fill the entire buffer - return (start_idx, buffer_height * buffer_width -1); + return (start_idx, buffer_height * buffer_width - 1); } } let mut last_line_chars = 0; // Case where the last newline does not occur at the end of the slice if new_line_indices[0].0 != slice.len() - 1 { - start_idx -= slice.len() -1 - new_line_indices[0].0; - total_lines += (slice.len()-1 - new_line_indices[0].0)/buffer_width + 1; - last_line_chars = (slice.len() -1 - new_line_indices[0].0) % buffer_width; // fix: account for more than one line - } - else { + start_idx -= slice.len() - 1 - new_line_indices[0].0; + total_lines += (slice.len() - 1 - new_line_indices[0].0) / buffer_width + 1; + last_line_chars = (slice.len() - 1 - new_line_indices[0].0) % buffer_width; + // fix: account for more than one line + } else { start_idx -= 1; total_lines += 1; } - // covers everything *up to* the characters between the beginning of the slice and the first new line character - for i in 0..new_line_indices.len()-1 { + // covers everything *up to* the characters between the beginning of the slice + // and the first new line character + for i in 0..new_line_indices.len() - 1 { if total_lines >= buffer_height { break; } - let num_chars = new_line_indices[i].0 - new_line_indices[i+1].0; - let num_lines = if (num_chars-1)%buffer_width != 0 || (num_chars -1) == 0 { - (num_chars-1) / buffer_width + 1 - } else { - (num_chars-1)/buffer_width}; // using (num_chars -1) because that's the number of characters that actually show up on the screen - if num_chars > start_idx { // prevents subtraction overflow + let num_chars = new_line_indices[i].0 - new_line_indices[i + 1].0; + let num_lines = if (num_chars - 1) % buffer_width != 0 || (num_chars - 1) == 0 { + (num_chars - 1) / buffer_width + 1 + } else { + (num_chars - 1) / buffer_width + }; // using (num_chars -1) because that's the number of characters that actually + // show up on the screen + if num_chars > start_idx { + // prevents subtraction overflow return (0, total_lines * buffer_width + last_line_chars); - } + } start_idx -= num_chars; total_lines += num_lines; } - // tracks the characters between the beginning of the slice and the first new line character - let first_chars = new_line_indices[new_line_indices.len() -1].0; - let first_chars_lines = first_chars/buffer_width + 1; + // tracks the characters between the beginning of the slice and the first new + // line character + let first_chars = new_line_indices[new_line_indices.len() - 1].0; + let first_chars_lines = first_chars / buffer_width + 1; - // covers the case where the text inside the new_lines_indices array overflow the text buffer + // covers the case where the text inside the new_lines_indices array overflow + // the text buffer if total_lines > buffer_height { start_idx += (total_lines - buffer_height) * buffer_width; // adds back the overcounted lines to the starting index total_lines = buffer_height; - // covers the case where the text between the last newline and the end of the slice overflow the text buffer + // covers the case where the text between the last newline and the + // end of the slice overflow the text buffer } else if first_chars_lines + total_lines > buffer_height { let diff = buffer_height - total_lines; total_lines += diff; start_idx -= diff * buffer_width; - // covers the case where the text between the last newline and the end of the slice exactly fits the text buffer + // covers the case where the text between the last newline and the + // end of the slice exactly fits the text buffer } else if first_chars_lines + total_lines == buffer_height { total_lines += first_chars_lines; start_idx -= first_chars; - // covers the case where the slice fits within the text buffer (i.e. there is not enough output to fill the screen) + // covers the case where the slice fits within the text buffer (i.e. + // there is not enough output to fill the screen) } else { - return (0, total_lines * buffer_width + last_line_chars); // In the case that an end index argument corresponded to a string slice that underfits the text display + return (0, total_lines * buffer_width + last_line_chars); // In + // the + // case + // that + // an end + // index + // argument + // corresponded + // to a + // string + // slice + // that + // underfits + // the + // text + // display } - // If the previous loop overcounted, this cuts off the excess string from string. Happens when there are many charcters between newlines at the beginning of the slice - (start_idx, (total_lines - 1) * buffer_width + last_line_chars) - + // If the previous loop overcounted, this cuts off the excess string from + // string. Happens when there are many charcters between newlines at the + // beginning of the slice + ( + start_idx, + (total_lines - 1) * buffer_width + last_line_chars, + ) } else { - (0,0) /* WARNING: should change to Option<> rather than returning (0, 0) */ - } + (0, 0) /* WARNING: should change to Option<> rather than + * returning (0, 0) */ + } } - /// This function takes in the start index of some index in the scrollback buffer and calculates the end index of the - /// scrollback buffer so that a slice containing the starting and ending index would perfectly fit inside the dimensions of - /// text display. + /// This function takes in the start index of some index in the scrollback + /// buffer and calculates the end index of the scrollback buffer so that + /// a slice containing the starting and ending index would perfectly fit + /// inside the dimensions of text display. fn calc_end_idx(&self, start_idx: usize) -> Result { let (buffer_width, buffer_height) = self.get_text_dimensions(); let scrollback_buffer_len = self.scrollback_buffer.len(); let mut end_idx = start_idx; - // Grabs a max-size slice of the scrollback buffer (usually does not totally fit because of newlines) + // Grabs a max-size slice of the scrollback buffer (usually does not totally fit + // because of newlines) let result = if start_idx + buffer_width * buffer_height > scrollback_buffer_len { - self.scrollback_buffer.get(start_idx..scrollback_buffer_len-1) + self.scrollback_buffer + .get(start_idx..scrollback_buffer_len - 1) } else { - self.scrollback_buffer.get(start_idx..start_idx + buffer_width * buffer_height) + self.scrollback_buffer + .get(start_idx..start_idx + buffer_width * buffer_height) }; // calculate the starting index for the slice if let Some(slice) = result { let mut total_lines = 0; - // Obtains a vector of the indices of the slice where newlines occur in ascending order + // Obtains a vector of the indices of the slice where newlines occur in + // ascending order let new_line_indices: Vec<(usize, &str)> = slice.match_indices('\n').collect(); // if there are no new lines in the slice if new_line_indices.is_empty() { - // indicates that the text is just one continuous string with no newlines and will therefore fill the buffer completely + // indicates that the text is just one continuous string with no newlines and + // will therefore fill the buffer completely end_idx += buffer_height * buffer_width; if end_idx < self.scrollback_buffer.len() { - return Ok(end_idx); + return Ok(end_idx); } else { return Err(ScrollError::OffEndBound); } } let mut counter = 0; - // Covers the case where the start idx argument corresponds to a string that does not start on a newline + // Covers the case where the start idx argument corresponds to a string that + // does not start on a newline if new_line_indices[0].0 != 0 { end_idx += new_line_indices[0].0; - total_lines += new_line_indices[0].0/buffer_width + 1; + total_lines += new_line_indices[0].0 / buffer_width + 1; } // the characters between the last newline and the end of the slice - let last_line_chars = slice.len() -1 - new_line_indices[new_line_indices.len() -1].0; - let num_last_lines = last_line_chars%buffer_width + 1; // +1 to account for the physical line that the last characters will take up + let last_line_chars = slice.len() - 1 - new_line_indices[new_line_indices.len() - 1].0; + let num_last_lines = last_line_chars % buffer_width + 1; // +1 to account for the physical line that the last characters will take up - for i in 0..new_line_indices.len()-1 { + for i in 0..new_line_indices.len() - 1 { if total_lines >= buffer_height { break; } - let num_chars = new_line_indices[i+1].0 - new_line_indices[i].0; - let num_lines = num_chars/buffer_width + 1; + let num_chars = new_line_indices[i + 1].0 - new_line_indices[i].0; + let num_lines = num_chars / buffer_width + 1; end_idx += num_chars; total_lines += num_lines; counter += 1; } - // covers the case where the text inside the new_line_indices array overflows the text buffer capacity + // covers the case where the text inside the new_line_indices array overflows + // the text buffer capacity if total_lines > buffer_height { - let num_chars = new_line_indices[counter].0 - new_line_indices[counter -1].0; + let num_chars = new_line_indices[counter].0 - new_line_indices[counter - 1].0; end_idx -= num_chars; end_idx += buffer_width; - // covers the case where the characters between the last newline and the end of the slice overflow the text buffer capacity + // covers the case where the characters between the last newline and + // the end of the slice overflow the text buffer capacity } else if total_lines + num_last_lines >= total_lines { let diff = buffer_height - total_lines; end_idx += diff * buffer_width; - // covers the case where the entire slice exactly fits or is smaller than the text buffer capacity + // covers the case where the entire slice exactly fits or is smaller + // than the text buffer capacity } else { end_idx += last_line_chars; } @@ -238,7 +282,8 @@ impl Terminal { Err(ScrollError::OffEndBound) } } else { - Ok(self.scrollback_buffer.len() - 1) /* WARNING: maybe should return Error? */ + Ok(self.scrollback_buffer.len() - 1) /* WARNING: maybe should + * return Error? */ } } @@ -248,7 +293,7 @@ impl Terminal { let mut start_idx = self.scroll_start_idx; //indicates that the user has scrolled to the top of the page if start_idx < 1 { - return; + return; } else { start_idx -= 1; } @@ -256,20 +301,27 @@ impl Terminal { let result; let slice_len; if buffer_width < start_idx { - result = self.scrollback_buffer.as_str().get(start_idx - buffer_width .. start_idx); + result = self + .scrollback_buffer + .as_str() + .get(start_idx - buffer_width..start_idx); slice_len = buffer_width; } else { - result = self.scrollback_buffer.as_str().get(0 .. start_idx); + result = self.scrollback_buffer.as_str().get(0..start_idx); slice_len = start_idx; } // Searches this slice for a newline if let Some(slice) = result { - let index = slice.rfind('\n'); + let index = slice.rfind('\n'); new_start_idx = match index { - Some(index) => { start_idx - slice_len + index }, // Moves the starting index back to the position of the nearest newline back - None => { start_idx - slice_len}, // If no newline is found, moves the start index back by the buffer width value - }; // we're moving the cursor one position to the right relative to the end of the input string + Some(index) => start_idx - slice_len + index, /* Moves the starting index back + * to the position of the nearest + * newline back */ + None => start_idx - slice_len, /* If no newline is found, moves the start index + * back by the buffer width value */ + }; // we're moving the cursor one position to the right relative to + // the end of the input string } else { return; } @@ -284,16 +336,17 @@ impl Terminal { // Prevents the user from scrolling down if already at the bottom of the page if self.is_scroll_end { return; - } + } let prev_start_idx = self.scroll_start_idx; let result = self.calc_end_idx(prev_start_idx); let mut end_idx = match result { Ok(end_idx) => end_idx, - Err(ScrollError::OffEndBound) => self.scrollback_buffer.len() -1, + Err(ScrollError::OffEndBound) => self.scrollback_buffer.len() - 1, }; - // If the newly calculated end index is the bottom of the scrollback buffer, recalculates the start index and returns - if end_idx == self.scrollback_buffer.len() -1 { + // If the newly calculated end index is the bottom of the scrollback buffer, + // recalculates the start index and returns + if end_idx == self.scrollback_buffer.len() - 1 { self.is_scroll_end = true; let new_start = self.calc_start_idx(end_idx).0; self.scroll_start_idx = new_start; @@ -304,21 +357,30 @@ impl Terminal { { let result; let slice_len; // specifies the length of the grabbed slice - // Grabs a slice (the size of the buffer width at most) of the scrollback buffer that is directly below the current slice being displayed on the text display + // Grabs a slice (the size of the buffer width at most) of the scrollback buffer + // that is directly below the current slice being displayed on the text display if self.scrollback_buffer.len() > end_idx + buffer_width { slice_len = buffer_width; - result = self.scrollback_buffer.as_str().get(end_idx .. end_idx + buffer_width); + result = self + .scrollback_buffer + .as_str() + .get(end_idx..end_idx + buffer_width); } else { - slice_len = self.scrollback_buffer.len() - end_idx -1; - result = self.scrollback_buffer.as_str().get(end_idx .. self.scrollback_buffer.len()); + slice_len = self.scrollback_buffer.len() - end_idx - 1; + result = self + .scrollback_buffer + .as_str() + .get(end_idx..self.scrollback_buffer.len()); } // Searches the grabbed slice for a newline if let Some(slice) = result { - let index = slice.find('\n'); + let index = slice.find('\n'); new_end_idx = match index { - Some(index) => { end_idx + index + 1}, // Moves end index forward to the next newline - None => { end_idx + slice_len}, // If no newline is found, moves the end index forward by the buffer width value - }; + Some(index) => end_idx + index + 1, /* Moves end index forward to the next + * newline */ + None => end_idx + slice_len, /* If no newline is found, moves the end index + * forward by the buffer width value */ + }; } else { return; } @@ -328,26 +390,28 @@ impl Terminal { self.scroll_start_idx = start_idx; } - /// Shifts the text display up by making the previous first line the last line displayed on the text display + /// Shifts the text display up by making the previous first line the last + /// line displayed on the text display fn page_up(&mut self) { let new_end_idx = self.scroll_start_idx; let new_start_idx = self.calc_start_idx(new_end_idx); self.scroll_start_idx = new_start_idx.0; } - /// Shifts the text display down by making the previous last line the first line displayed on the text display + /// Shifts the text display down by making the previous last line the first + /// line displayed on the text display fn page_down(&mut self) { let start_idx = self.scroll_start_idx; let result = self.calc_end_idx(start_idx); let new_start_idx = match result { - Ok(idx) => idx+ 1, + Ok(idx) => idx + 1, Err(ScrollError::OffEndBound) => { let scrollback_buffer_len = self.scrollback_buffer.len(); let new_start_idx = self.calc_start_idx(scrollback_buffer_len).0; self.scroll_start_idx = new_start_idx; self.is_scroll_end = true; return; - }, + } }; let result = self.calc_end_idx(new_start_idx); let new_end_idx = match result { @@ -358,47 +422,46 @@ impl Terminal { self.scroll_start_idx = new_start_idx; self.is_scroll_end = true; return; - }, + } }; - if new_end_idx == self.scrollback_buffer.len() -1 { - // if the user page downs near the bottom of the page so only gets a partial shift + if new_end_idx == self.scrollback_buffer.len() - 1 { + // if the user page downs near the bottom of the page so only gets a partial + // shift self.is_scroll_end = true; return; } self.scroll_start_idx = new_start_idx; } - /// Updates the text display by taking a string index and displaying as much as it starting from the passed string index (i.e. starts from the top of the display and goes down) + /// Updates the text display by taking a string index and displaying as much + /// as it starting from the passed string index (i.e. starts from the top of + /// the display and goes down) fn update_display_forwards(&mut self, start_idx: usize) -> Result<(), &'static str> { self.scroll_start_idx = start_idx; let result = self.calc_end_idx(start_idx); let end_idx = match result { Ok(end_idx) => end_idx, Err(ScrollError::OffEndBound) => { - let new_end_idx = self.scrollback_buffer.len() -1; + let new_end_idx = self.scrollback_buffer.len() - 1; let new_start_idx = self.calc_start_idx(new_end_idx).0; self.scroll_start_idx = new_start_idx; new_end_idx - }, + } }; - let result = self.scrollback_buffer.get(start_idx..=end_idx); // =end_idx includes the end index in the slice + let result = self.scrollback_buffer.get(start_idx..=end_idx); // =end_idx includes the end index in the slice if let Some(slice) = result { - self.text_display.set_text(slice); - self.display_text()?; + // TODO: Avoid allocating? + self.text = slice.to_owned(); + display_text(&mut self.window, slice); } else { return Err("could not get slice of scrollback buffer string"); } Ok(()) } - /// Display the text displayable in the window and render it to the screen - fn display_text(&mut self) -> Result<(), &'static str>{ - let coord = self.window.area().top_left; - let area_to_render = self.text_display.display(coord, self.window.framebuffer_mut().deref_mut())?; - self.window.render(Some(area_to_render)) - } - - /// Updates the text display by taking a string index and displaying as much as it can going backwards from the passed string index (i.e. starts from the bottom of the display and goes up) + /// Updates the text display by taking a string index and displaying as much + /// as it can going backwards from the passed string index (i.e. starts from + /// the bottom of the display and goes up) fn update_display_backwards(&mut self, end_idx: usize) -> Result<(), &'static str> { let (start_idx, _cursor_pos) = self.calc_start_idx(end_idx); self.scroll_start_idx = start_idx; @@ -406,8 +469,9 @@ impl Terminal { let result = self.scrollback_buffer.get(start_idx..end_idx); if let Some(slice) = result { - self.text_display.set_text(slice); - self.display_text()?; + // TODO: Avoid allocating? + self.text = slice.to_owned(); + display_text(&mut self.window, slice); } else { return Err("could not get slice of scrollback buffer string"); } @@ -419,38 +483,29 @@ impl Terminal { impl Terminal { /// Creates a new terminal and adds it to the window manager `wm_mutex` pub fn new() -> Result { - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; - let (window_width, window_height) = { - let wm = wm_ref.lock(); - wm.get_screen_size() - }; + let (window_width, window_height) = compositor::screen_size(); - let window = window::Window::new( - Coord::new(0, 0), - window_width, - window_height, - FONT_BACKGROUND_COLOR, - )?; - - let area = window.area(); - let text_display = TextDisplay::new(area.width(), area.height(), FONT_FOREGROUND_COLOR, FONT_BACKGROUND_COLOR)?; + let window = compositor::window(window_width, window_height); let mut terminal = Terminal { window, scrollback_buffer: String::new(), scroll_start_idx: 0, is_scroll_end: true, - text_display, cursor: Cursor::default(), + text: String::new(), }; - terminal.display_text()?; + // TODO: Fill background. - terminal.print_to_terminal("Theseus Terminal Emulator\nPress Ctrl+C to quit a task\n".to_string()); + terminal.print_to_terminal( + "Theseus Terminal Emulator\nPress Ctrl+C to quit a task\n".to_string(), + ); Ok(terminal) } - /// Adds a string to be printed to the terminal to the terminal scrollback buffer. - /// Note that one needs to call `refresh_display` to get things actually printed. + /// Adds a string to be printed to the terminal to the terminal scrollback + /// buffer. Note that one needs to call `refresh_display` to get things + /// actually printed. pub fn print_to_terminal(&mut self, s: String) { self.scrollback_buffer.push_str(&s); } @@ -458,7 +513,8 @@ impl Terminal { /// Actually refresh the screen. Currently it's expensive. pub fn refresh_display(&mut self) -> Result<(), &'static str> { let start_idx = self.scroll_start_idx; - // handling display refreshing errors here so that we don't clog the main loop of the terminal + // handling display refreshing errors here so that we don't clog the main loop + // of the terminal if self.is_scroll_end { let buffer_len = self.scrollback_buffer.len(); self.update_display_backwards(buffer_len)?; @@ -474,40 +530,57 @@ impl Terminal { /// # Arguments /// /// * `c`: the new character to insert. - /// * `offset_from_end`: the position to insert the character. It represents the distance relative to the end of the whole output in the terminal in number of characters. + /// * `offset_from_end`: the position to insert the character. It represents + /// the distance relative to the end of the whole output in the terminal + /// in number of characters. /// /// # Examples /// - /// * `terminal.insert_char(char, 0)` will append `char` to the end of existing text. - /// * `terminal.insert_char(char, 5)` will insert `char` right before the last 5 characters. + /// * `terminal.insert_char(char, 0)` will append `char` to the end of + /// existing text. + /// * `terminal.insert_char(char, 5)` will insert `char` right before the + /// last 5 characters. /// - /// After invoke this function, one must call `refresh_display` to get the updates actually showed on the screen. + /// After invoke this function, one must call `refresh_display` to get the + /// updates actually showed on the screen. pub fn insert_char(&mut self, c: char, offset_from_end: usize) -> Result<(), &'static str> { let buflen = self.scrollback_buffer.len(); - if buflen < offset_from_end { return Err("offset_from_end is larger than length of scrollback buffer"); } + if buflen < offset_from_end { + return Err("offset_from_end is larger than length of scrollback buffer"); + } let insert_idx = buflen - offset_from_end; - self.scrollback_buffer.insert_str(insert_idx, &c.to_string()); + self.scrollback_buffer + .insert_str(insert_idx, &c.to_string()); Ok(()) } /// Remove a character from the terminal. /// /// # Arguments - /// * `offset_from_end`: the position of the character to remove. It represents the distance relative to the end of the whole output in the terminal in number of characters. `offset_from_end == 0` is *invalid* here. + /// * `offset_from_end`: the position of the character to remove. It + /// represents the distance relative to the end of the whole output in the + /// terminal in number of characters. `offset_from_end == 0` is *invalid* + /// here. /// /// # Examples - /// * `terminal.remove_char(1)` will remove the last character in the screen. + /// * `terminal.remove_char(1)` will remove the last character in the + /// screen. /// - /// After invoke this function, one must call `refresh_display` to get the updates actually showed on the screen. + /// After invoke this function, one must call `refresh_display` to get the + /// updates actually showed on the screen. pub fn remove_char(&mut self, offset_from_end: usize) -> Result<(), &'static str> { let buflen = self.scrollback_buffer.len(); - if buflen < offset_from_end { return Err("offset_from_end is larger than length of scrollback buffer"); } - if offset_from_end == 0 { return Err("cannot remove character at offset_from_end == 0"); } + if buflen < offset_from_end { + return Err("offset_from_end is larger than length of scrollback buffer"); + } + if offset_from_end == 0 { + return Err("cannot remove character at offset_from_end == 0"); + } let remove_idx = buflen - offset_from_end; self.scrollback_buffer.remove(remove_idx); Ok(()) } - + /// Scroll the screen to the very beginning. pub fn move_screen_to_begin(&mut self) -> Result<(), &'static str> { // Home command only registers if the text display has the ability to scroll @@ -517,7 +590,7 @@ impl Terminal { self.cursor.disable(); self.display_cursor()?; } - + Ok(()) } @@ -586,71 +659,77 @@ impl Terminal { } /// Gets an event from the window's event queue. - /// + /// /// Returns `None` if no events have been sent to this window. + // TODO: This function shouldn't exist. It should either block, or return a + // future. pub fn get_event(&mut self) -> Option { - match self.window.handle_event() { - Ok(event) => event, - Err(_e) => { - error!("Terminal::get_event(): error in the window's event handler: {:?}.", _e); - Some(Event::ExitEvent) - } - } + self.window.try_recv() } /// Display the cursor of the terminal. pub fn display_cursor(&mut self) -> Result<(), &'static str> { // get info about the text displayable - let (col_num, line_num, text_next_pos) = { - let text_next_pos = self.text_display.get_next_index(); - let (col_num, line_num) = self.get_text_dimensions(); - (col_num, line_num, text_next_pos) - }; + let (next_col, next_row) = Text::new(&self.text, Coordinates::ORIGIN) + .next_grid_position(&*self.window.as_framebuffer()); + let (num_col, num_row) = self.get_text_dimensions(); // return if the cursor is not in the screen - if text_next_pos >= col_num * line_num { - return Ok(()) + if next_col >= num_col || next_row >= num_row { + return Ok(()); } - // calculate the cursor position - let cursor_pos = text_next_pos - self.cursor.offset_from_end; - let cursor_line = cursor_pos / col_num; - let cursor_col = cursor_pos % col_num; - // Get the bounding box that contains the displayed cursor. let bounding_box = { - let coord = self.window.area().top_left; + let coord = self.window.area().vertex(Vertical::Top, Horizontal::Left); self.cursor.display( coord, - cursor_col, - cursor_line, - self.window.framebuffer_mut().deref_mut(), + next_col, + next_row, + &mut *self.window.as_mut_framebuffer(), )? - }; + }; - self.window.render(Some(bounding_box)) + self.window.blocking_refresh(bounding_box); + Ok(()) } - /// Gets the position of the cursor relative to the end of text in number of characters. + /// Gets the position of the cursor relative to the end of text in number of + /// characters. pub fn get_cursor_offset_from_end(&self) -> usize { self.cursor.offset_from_end } /// Updates the position of a cursor. /// # Arguments - /// * `offset_from_end`: the position of the cursor relative to the end of text in number of characters. - /// * `underlying_char`: the ASCII code of the underlying character when the cursor is unseen. + /// * `offset_from_end`: the position of the cursor relative to the end of + /// text in number of characters. + /// * `underlying_char`: the ASCII code of the underlying character when the + /// cursor is unseen. pub fn update_cursor_pos(&mut self, offset_from_end: usize, underlying_char: u8) { self.cursor.offset_from_end = offset_from_end; self.cursor.underlying_char = underlying_char; } - /// Resizes this terminal and its underlying text display and then refreshes the window. - /// This does not automatically redisplay the terminal cursor. + /// Resizes this terminal and its underlying text display and then refreshes + /// the window. This does not automatically redisplay the terminal + /// cursor. pub fn resize(&mut self, new_position: Rectangle) -> Result<(), &'static str> { - self.text_display.set_size(new_position.width(), new_position.height()); - self.text_display.reset_cache(); self.refresh_display()?; Ok(()) } } + +fn display_text(window: &mut Window, text: &str) { + let settings = Settings { + foreground: FONT_FOREGROUND_COLOR.into(), + background: Some(FONT_BACKGROUND_COLOR.into()), + }; + let refresh_area = Text::new(text, Coordinates::ORIGIN) + .draw(&mut *window.as_mut_framebuffer(), &settings); + // TODO + log::info!("{refresh_area:?}"); + let start = time::Instant::now(); + window.blocking_refresh(refresh_area); + log::info!("refresh took: {:?}", time::Instant::now().duration_since(start)); +} diff --git a/kernel/mouse/Cargo.toml b/kernel/mouse/Cargo.toml index 4ddfa55f2d..5135e39d19 100644 --- a/kernel/mouse/Cargo.toml +++ b/kernel/mouse/Cargo.toml @@ -7,10 +7,12 @@ edition = "2021" [dependencies] spin = "0.9.4" -mpmc = "0.1.6" log = "0.4.8" x86_64 = "0.14.8" +[dependencies.async_channel] +path = "../async_channel" + [dependencies.mouse_data] path = "../../libs/mouse_data" @@ -20,8 +22,5 @@ path = "../interrupts" [dependencies.ps2] path = "../ps2" -[dependencies.event_types] -path = "../event_types" - [lib] crate-type = ["rlib"] diff --git a/kernel/mouse/src/lib.rs b/kernel/mouse/src/lib.rs index 2368abfa06..973e1e203d 100644 --- a/kernel/mouse/src/lib.rs +++ b/kernel/mouse/src/lib.rs @@ -5,10 +5,10 @@ use log::{error, warn}; use spin::Once; -use mpmc::Queue; -use event_types::Event; +use async_channel::Channel; use x86_64::structures::idt::InterruptStackFrame; -use mouse_data::{MouseButtons, MouseEvent, MouseMovementRelative}; +pub use mouse_data::MouseEvent; +use mouse_data::{MouseButtons, MouseMovementRelative}; use ps2::{PS2Mouse, MousePacket}; /// The first PS/2 port for the mouse is connected directly to IRQ 0xC. @@ -19,7 +19,7 @@ static MOUSE: Once = Once::new(); struct MouseInterruptParams { mouse: PS2Mouse<'static>, - queue: Queue, + queue: Channel, } /// Initialize the PS/2 mouse driver and register its interrupt handler. @@ -28,7 +28,7 @@ struct MouseInterruptParams { /// * `mouse`: a wrapper around mouse functionality and id, used by the mouse interrupt handler. /// * `mouse_queue_producer`: the queue onto which the mouse interrupt handler /// will push new mouse events when a mouse action occurs. -pub fn init(mut mouse: PS2Mouse<'static>, mouse_queue_producer: Queue) -> Result<(), &'static str> { +pub fn init(mut mouse: PS2Mouse<'static>, mouse_queue_producer: Channel) -> Result<(), &'static str> { // Set MouseId to the highest possible one if let Err(e) = mouse.set_mouse_id() { error!("Failed to set the mouse id: {e}"); @@ -79,14 +79,12 @@ extern "x86-interrupt" fn ps2_mouse_handler(_stack_frame: InterruptStackFrame) { /// enqueue a Mouse Event according to the data -fn handle_mouse_input(mouse_packet: MousePacket, queue: &Queue) -> Result<(), &'static str> { +fn handle_mouse_input(mouse_packet: MousePacket, queue: &Channel) -> Result<(), &'static str> { let buttons = Buttons::from(&mouse_packet).0; let movement = Movement::from(&mouse_packet).0; - let mouse_event = MouseEvent::new(buttons, movement); - let event = Event::MouseMovementEvent(mouse_event); - - queue.push(event).map_err(|_| "failed to enqueue the mouse event") + let event = MouseEvent::new(buttons, movement); + queue.try_send(event).map_err(|_| "failed to enqueue the mouse event") } // both MouseMovementRelative and MousePacketBits4 are in different crates, so we need a newtype wrapper: diff --git a/kernel/mpmc_channel/Cargo.toml b/kernel/mpmc_channel/Cargo.toml new file mode 100644 index 0000000000..cd1680a106 --- /dev/null +++ b/kernel/mpmc_channel/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Kevin Boos "] +name = "mpmc_channel" +description = "Channel for asynchronous Inter-Task Communication via a bounded buffer" +version = "0.1.0" + +[dependencies] +crossbeam-utils = { version = "0.8.12", default-features = false } +mpmc = "0.1.6" + +[dependencies.log] +version = "0.4.8" + +[dependencies.debugit] +path = "../../libs/debugit" + +[dependencies.wait_queue] +path = "../wait_queue" + +[dependencies.sync] +path = "../../libs/sync" + +[dependencies.sync_spin] +path = "../../libs/sync_spin" + +[dependencies.core2] +version = "0.4.0" +default-features = false +features = ["alloc", "nightly"] + +[lib] +crate-type = ["rlib"] diff --git a/kernel/mpmc_channel/src/lib.rs b/kernel/mpmc_channel/src/lib.rs new file mode 100644 index 0000000000..ae82aca872 --- /dev/null +++ b/kernel/mpmc_channel/src/lib.rs @@ -0,0 +1,523 @@ +//! An asynchronous channel for Inter-Task Communication (ITC) with an internal queue for buffering messages. +//! +//! This crate offers an asynchronous channel that allows multiple tasks +//! to exchange messages through the use of a bounded-capacity intermediate buffer. +//! Unlike the `rendezvous` channel, the sender and receiver do not need to rendezvous to send or receive data. +//! +//! Only `Send` types can be sent or received through the channel. +//! +//! This is not a zero-copy channel; to avoid copying large messages, +//! use a reference type like `Box` or another layer of indirection. + +#![no_std] + +extern crate alloc; +#[cfg(trace_channel)] #[macro_use] extern crate log; +#[cfg(trace_channel)] #[macro_use] extern crate debugit; +extern crate wait_queue; +extern crate mpmc; +extern crate crossbeam_utils; +extern crate core2; +extern crate sync; +extern crate sync_spin; + +use alloc::sync::Arc; +use mpmc::Queue as MpmcQueue; +use wait_queue::WaitQueue; +use crossbeam_utils::atomic::AtomicCell; +use core::sync::atomic::{AtomicUsize, Ordering}; +use sync::DeadlockPrevention; +use sync_spin::Spin; + +/// Create a new channel that allows senders and receivers to +/// asynchronously exchange messages via an internal intermediary buffer. +/// +/// This channel's buffer has a bounded capacity of minimum size 2 messages, +/// and it must be a power of 2 due to the restrictions of the current MPMC queue type that is used. +/// The given `minimum_capacity` will be rounded up to the next largest power of 2, with a minimum value of 2. +/// +/// When the number of pending (buffered) messages is larger than the capacity, +/// the channel is considered full. +/// Depending on whether a non-blocking or blocking send function is invoked, +/// future attempts to send another message will either block or return a `Full` error +/// until the channel's buffer is drained by a receiver and space in the buffer becomes available. +/// +/// For the vast majority of use cases, this function is recommended way to create +/// a new channel, because there is no need to specify a deadlock prevention method. +/// To create a channel with different deadlock prevention, see [`new_channel_with()`]. +pub fn new_channel(minimum_capacity: usize) -> (Sender, Receiver) { + new_channel_with(minimum_capacity) +} + +/// Creates a new asynchronous channel with the specified deadlock prevention method. +/// +/// See [`new_channel()`] for more details. +/// +/// The asynchronous channel uses a wait queue internally and hence exposes a +/// deadlock prevention type parameter `P` that is [`Spin`] by default. +/// See [`WaitQueue`]'s documentation for more info on setting this type parameter. +pub fn new_channel_with( + minimum_capacity: usize, +) -> (Sender, Receiver) { + let channel = Arc::new(Channel { + queue: MpmcQueue::with_capacity(minimum_capacity), + waiting_senders: WaitQueue::new(), + waiting_receivers: WaitQueue::new(), + channel_status: AtomicCell::new(ChannelStatus::Connected), + sender_count: AtomicUsize::new(1), + receiver_count: AtomicUsize::new(1), + }); + ( + Sender { channel: channel.clone() }, + Receiver { channel }, + ) +} + +/// Indicates whether channel is Connected or Disconnected +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ChannelStatus { + /// Channel is working. Initially channel is created with Connected status. + Connected, + /// Set to Disconnected when Sender end is dropped. + SenderDisconnected, + /// Set to Disconnected when Receiver end is dropped. + ReceiverDisconnected, +} + +/// Error type for tracking different type of errors sender and receiver +/// can encounter. +#[derive(Debug, PartialEq)] +pub enum Error { + /// Occurs when a "try" operation would need to block to complete. + /// + /// I.e. `try_send` is performed on a full channel or `try_receive` is + /// performed on an empty channel. + WouldBlock, + /// Occurs when one end of channel is dropped + ChannelDisconnected, +} + +impl From for core2::io::Error { + fn from(e: Error) -> Self { + match e { + Error::WouldBlock => core2::io::ErrorKind::WouldBlock, + Error::ChannelDisconnected => core2::io::ErrorKind::BrokenPipe, + } + .into() + } +} + +/// The inner channel for asynchronous communication between `Sender`s and `Receiver`s. +/// +/// This struct is effectively a wrapper around a MPMC queue +/// with waitqueues for senders (producers) and receivers (consumers). +/// +/// This channel object is not Send/Sync or cloneable itself; +/// it can be shared across tasks using an `Arc`. +struct Channel { + queue: MpmcQueue, + waiting_senders: WaitQueue

, + waiting_receivers: WaitQueue

, + channel_status: AtomicCell, + sender_count: AtomicUsize, + receiver_count: AtomicUsize, +} + +// Ensure that `AtomicCell` is actually a lock-free atomic. +const _: () = assert!(AtomicCell::::is_lock_free()); + +impl Channel { + /// Returns true if the channel is disconnected. + #[inline(always)] + fn is_disconnected(&self) -> bool { + self.get_channel_status() != ChannelStatus::Connected + } + + /// Returns the channel's current status. + #[inline(always)] + fn get_channel_status(&self) -> ChannelStatus { + self.channel_status.load() + } + + /// Returns another `Sender` endpoint connected to the given channel. + /// + /// This increments the channel's sender count. + /// If there were previously no senders, the channel status is updated to `Connected`. + fn add_sender(channel: &Arc) -> Sender { + if channel.sender_count.fetch_add(1, Ordering::SeqCst) == 0 { + channel.channel_status.store(ChannelStatus::Connected); + } + Sender { channel: channel.clone() } + } + + /// Returns another `Receiver` endpoint connected to the given channel. + /// + /// This increments the channel's receiver count. + /// If there were previously no receivers, the channel status is updated to `Connected`. + fn add_receiver(channel: &Arc) -> Receiver { + if channel.receiver_count.fetch_add(1, Ordering::SeqCst) == 0 { + channel.channel_status.store(ChannelStatus::Connected); + } + Receiver { channel: channel.clone() } + } +} + +/// The sender (transmit) side of a channel. +pub struct Sender { + channel: Arc>, +} + +impl Clone for Sender { + /// Clones this `Sender`, returning another `Sender` connected to the same channel. + /// + /// This increments the channel's sender count. + /// If there were previously no senders, the channel status is updated to `Connected`. + fn clone(&self) -> Self { + Channel::add_sender( &self.channel ) + } +} + +impl Sender { + /// Send a message, blocking until space in the channel's buffer is available. + /// + /// Returns `Ok(())` if the message was sent successfully, + /// otherwise returns an [`Error`]. + pub fn send(&self, msg: T) -> Result<(), Error> { + #[cfg(trace_channel)] + trace!("async_channel: sending msg: {:?}", debugit!(msg)); + // Fast path: attempt to send the message, assuming the buffer isn't full + let msg = match self.try_send(msg) { + // if successful return ok + Ok(()) => return Ok(()), + // if unsunccessful check whether it fails due to any other reason than channel being full + Err((returned_msg, channel_error)) => { + if channel_error != Error::WouldBlock { + return Err(channel_error); + } + returned_msg + }, + }; + + // Slow path: the buffer was full, so now we need to block until space becomes available. + // The code can move to this point only if fast path failed due to channel being full + // trace!("waiting for space to send..."); + + // Here we use an option to store the un-sent message outside of the `closure` + // so that we can repeatedly try to re-send it upon the next invocation of the `closure` + // (which happens when this sender task is notified in the future). + let mut msg = Some(msg); + + // This closure is invoked from within a locked context, so we cannot just call `try_send()` here + // because it will notify the receivers which can cause deadlock. + // Therefore, we need to perform the nofity action outside of this closure after it returns. + let mut closure = || { + let owned_msg = msg.take(); + let result = owned_msg.and_then(|m| match self.channel.queue.push(m) { + Ok(()) => { + // trace!("Sending in closure"); + // We wrap the result in Some() since `wait_until` progresses only when `Some` is returned. + Some(Ok(())) + }, + Err(returned_msg) => { + // Here: we (the sender) woke up and failed to send, + // so we save the returned message outside of the closure to retry later. + // trace!("try_send() failed, saving message {:?} for next retry.", debugit!(returned_msg)); + msg = Some(returned_msg); + None + } + }); + + if self.channel.is_disconnected() { + // trace!("Receiver Endpoint is dropped"); + // Here the receiver end has dropped. + // So we don't wait anymore in the waitqueue + Some(Err(Error::ChannelDisconnected)) + } else { + result + } + + }; + + // When `wait_until_mut` returns it can be either a successful send marked as Ok(Ok()), + // Error in the condition (channel disconnection) marked as Ok(Err()), + // or the wait_until runs into error (Err()) + let res = self.channel.waiting_senders.wait_until(&mut closure); + + // trace!("... sending space became available."); + + // If we successfully sent a message, we need to notify any waiting receivers. + // As stated above, to avoid deadlock, this must be done here rather than in the above closure. + if res.is_ok() { + // trace!("successful send() is notifying receivers."); + self.channel.waiting_receivers.notify_one(); + } + res + } + + /// Sends a slice of objects through the channel, returning how many objects were sent. + /// + /// This method only blocks on the first object being sent. + pub fn send_buf(&self, buf: &[T]) -> Result + where + T: Copy + { + for (idx, item) in buf.iter().enumerate() { + if idx == 0 { + self.send(*item)?; + } else { + match self.try_send(*item) { + Ok(_) => {}, + Err((_, Error::WouldBlock)) => return Ok(idx), + Err((_, e)) => return Err(e), + } + } + } + Ok(buf.len()) + } + + /// Attempts to send an entire slice of objects through the channel. + pub fn send_all(&self, buf: &[T]) -> Result<(), Error> + where + T: Copy + { + for item in buf.iter() { + self.send(*item)?; + } + Ok(()) + } + + /// Tries to send the message, only succeeding if buffer space is available. + /// + /// If no buffer space is available, it returns the `msg` with `Error` back to the caller without blocking. + pub fn try_send(&self, msg: T) -> Result<(), (T, Error)> { + // first we'll check whether the channel is active + match self.channel.get_channel_status() { + ChannelStatus::SenderDisconnected => { + self.channel.channel_status.store(ChannelStatus::Connected); + }, + ChannelStatus::ReceiverDisconnected => { + return Err((msg, Error::ChannelDisconnected)); + }, + _ => {}, + } + + match self.channel.queue.push(msg) { + // successfully sent + Ok(()) => { + // trace!("successful try_send() is notifying receivers."); + self.channel.waiting_receivers.notify_one(); + Ok(()) + } + // queue was full, return message back to caller + Err(returned_msg) => Err((returned_msg, Error::WouldBlock)), + } + } + + /// Sends a slice of objects through the channel, returning how many objects were sent. + /// + /// This method does not block. + pub fn try_send_buf(&self, buf: &[T]) -> Result + where + T: Copy + { + for (idx, item) in buf.iter().enumerate() { + if idx == 0 { + self.try_send(*item).map_err(|(_, e)| e)?; + } else { + match self.try_send(*item) { + Ok(_) => {}, + Err((_, Error::WouldBlock)) => return Ok(idx), + Err((_, e)) => return Err(e), + } + } + } + Ok(buf.len()) + } + + /// Returns true if the channel is disconnected. + pub fn is_disconnected(&self) -> bool { + self.channel.is_disconnected() + } + + /// Obtain a `Receiver` endpoint connected to the same channel as this `Sender`. + pub fn receiver(&self) -> Receiver { + Channel::add_receiver( &self.channel ) + } +} + +/// The receiver side of a channel. +pub struct Receiver { + channel: Arc>, +} + +impl Clone for Receiver { + /// Clones this `Receiver`, returning another `Receiver` connected to the same channel. + /// + /// This increments the channel's receiver count. + /// If there were previously no receivers, the channel status is updated to `Connected`. + fn clone(&self) -> Self { + Channel::add_receiver( &self.channel ) + } +} + +impl Receiver { + /// Receive a message, blocking until a message is available in the buffer. + /// + /// Returns the message if it was received properly, otherwise returns an [`Error`]. + pub fn receive(&self) -> Result { + // trace!("async_channel: receive() entry"); + // Fast path: attempt to receive a message, assuming the buffer isn't empty + // The code progresses beyond this match only if try_receive fails due to + // empty channel + match self.try_receive() { + Err(Error::WouldBlock) => {}, + x => { + #[cfg(trace_channel)] + trace!("async_channel: received msg: {:?}", debugit!(x)); + return x; + } + }; + + // Slow path: the buffer was empty, so we need to block until a message is sent. + // trace!("waiting to receive a message..."); + + // This closure is invoked from within a locked context, so we cannot just call `try_receive()` here + // because it will notify the receivers which can cause deadlock. + // Therefore, we need to perform the nofity action outside of this closure after it returns + // Closure would output the message if received or an error if channel is disconnected. + // It would output `None` if neither happens, resulting in waiting in the queue. + let closure = || { + match self.channel.queue.pop() { + Some(msg) => Some(Ok(msg)), + _ => { + if self.channel.is_disconnected() { + Some(Err(Error::ChannelDisconnected)) + } else { + None + } + }, + } + }; + + // When wait returns it can be either a successful receiver marked as Ok(Ok(msg)), + // Error in wait condition marked as Ok(Err(error)), + // or the wait_until runs into error (Err()) + let res = self.channel.waiting_receivers.wait_until(&closure); + // trace!("... received msg."); + + // If we successfully received a message, we need to notify any waiting senders. + // As stated above, to avoid deadlock, this must be done here rather than in the above closure. + if let Ok(ref _msg) = res { + // trace!("async_channel: successful receive() is notifying senders."); + self.channel.waiting_senders.notify_one(); + } + + #[cfg(trace_channel)] + trace!("async_channel: received msg: {:?}", debugit!(res)); + + res + } + + /// Receives objects placing them in a buffer and returning the number of objects received. + /// + /// This method only blocks on the first object being received. + pub fn receive_buf(&self, buf: &mut [T]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut byte = self.receive()?; + let mut read = 0; + + loop { + buf[read] = byte; + read += 1; + + if read == buf.len() { + return Ok(read); + } + + byte = match self.try_receive() { + Ok(b) => b, + Err(Error::WouldBlock) => return Ok(read), + Err(e) => return Err(e), + }; + } + } + + /// Tries to receive a message, only succeeding if a message is already available in the buffer. + /// + /// If receive succeeds returns `Some(Ok(T))`. + /// If an endpoint is disconnected returns `Some(Err(ChannelStatus::Disconnected))`. + /// If no such message exists, it returns `None` without blocking + pub fn try_receive(&self) -> Result { + if let Some(msg) = self.channel.queue.pop() { + // trace!("successful try_receive() is notifying senders."); + self.channel.waiting_senders.notify_one(); + Ok(msg) + } else { + // We check whether the channel is disconnected + match self.channel.get_channel_status() { + ChannelStatus::ReceiverDisconnected => { + self.channel.channel_status.store(ChannelStatus::Connected); + Err(Error::WouldBlock) + }, + ChannelStatus::SenderDisconnected => { + Err(Error::ChannelDisconnected) + }, + _ => { + Err(Error::WouldBlock) + }, + } + } + } + + /// Receives objects placing them in a buffer and returning the number of objects received. + /// + /// This method does not block. + pub fn try_receive_buf(&self, buf: &mut [T]) -> Result { + for (idx, item) in buf.iter_mut().enumerate() { + *item = match self.try_receive() { + Ok(byte) => byte, + Err(Error::WouldBlock) => return Ok(idx + 1), + Err(e) => return Err(e), + }; + } + Ok(buf.len()) + } + + /// Returns true if the channel is disconnected. + pub fn is_disconnected(&self) -> bool { + self.channel.is_disconnected() + } + + /// Obtain a `Sender` endpoint connected to the same channel as this `Receiver`. + pub fn sender(&self) -> Sender { + Channel::add_sender( &self.channel ) + } +} + + +/// When the only remaining `Receiver` is dropped, we mark the channel as disconnected +/// and notify all of the `Senders` +impl Drop for Receiver { + fn drop(&mut self) { + // trace!("Dropping a receiver"); + if self.channel.receiver_count.fetch_sub(1, Ordering::SeqCst) == 1 { + self.channel.channel_status.store(ChannelStatus::ReceiverDisconnected); + self.channel.waiting_senders.notify_all(); + } + } +} + +/// When the only remaining `Sender` is dropped, we mark the channel as disconnected +/// and notify all of the `Receivers` +impl Drop for Sender { + fn drop(&mut self) { + // trace!("Dropping a sender"); + if self.channel.sender_count.fetch_sub(1, Ordering::SeqCst) == 1 { + self.channel.channel_status.store(ChannelStatus::SenderDisconnected); + self.channel.waiting_receivers.notify_all(); + } + } +} diff --git a/kernel/preemption/src/lib.rs b/kernel/preemption/src/lib.rs index e1b490bb1a..df58e64ce6 100644 --- a/kernel/preemption/src/lib.rs +++ b/kernel/preemption/src/lib.rs @@ -9,7 +9,6 @@ use cpu::CpuId; /// A reference to the preemption counter for the current CPU (in CPU-local storage). -// NOTE: This offset must be kept in sync with `cpu_local::PerCpuField`. #[cls_macros::cpu_local(cls_dep = false)] static PREEMPTION_COUNT: u8 = 0; @@ -115,8 +114,9 @@ impl PreemptionGuard { impl Drop for PreemptionGuard { fn drop(&mut self) { let cpu_id = cpu::current_cpu(); - assert!( - self.cpu_id == cpu_id, + assert_eq!( + self.cpu_id, + cpu_id, "PreemptionGuard::drop(): BUG: CPU IDs did not match! \ Task unexpectedly migrated from CPU {} to CPU {}.", self.cpu_id, diff --git a/kernel/serial_port/Cargo.toml b/kernel/serial_port/Cargo.toml index 326278dff9..1fda3e0e7e 100644 --- a/kernel/serial_port/Cargo.toml +++ b/kernel/serial_port/Cargo.toml @@ -16,7 +16,7 @@ interrupts = { path = "../interrupts" } deferred_interrupt_tasks = { path = "../deferred_interrupt_tasks" } # Dependencies below here are temporary, for console creation testing. -async_channel = { path = "../async_channel" } +mpmc_channel = { path = "../mpmc_channel" } [lib] crate-type = ["rlib"] diff --git a/kernel/serial_port/src/lib.rs b/kernel/serial_port/src/lib.rs index 00416bd6b9..1c1cdd5a89 100644 --- a/kernel/serial_port/src/lib.rs +++ b/kernel/serial_port/src/lib.rs @@ -39,8 +39,7 @@ use interrupts::{PL011_RX_SPI, init_pl011_rx_interrupt}; // Dependencies below here are temporary and will be removed // after we have support for separate interrupt handling tasks. -extern crate async_channel; -use async_channel::Sender; +use mpmc_channel::Sender; /// A temporary hack to allow the serial port interrupt handler /// to inform a listener on the other end of this channel diff --git a/kernel/shapes/Cargo.toml b/kernel/shapes/Cargo.toml deleted file mode 100644 index b6cd153b1e..0000000000 --- a/kernel/shapes/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "shapes" -version = "0.1.0" -authors = ["Wenqiu Yu "] -description = "define the basic shapes used in display subsystem" - -[dependencies] - -[lib] -crate-type = ["rlib"] diff --git a/kernel/shapes/src/lib.rs b/kernel/shapes/src/lib.rs deleted file mode 100644 index f95a5e2c9e..0000000000 --- a/kernel/shapes/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! This crate defines the basic shapes used for display. - -#![no_std] - -use core::ops::{Add, Sub}; -use core::cmp::{Ord, Ordering}; - -/// A 2-D integer coordinate. -#[derive(Clone, Copy, PartialEq, Debug, Hash)] -pub struct Coord { - /// The x coordinate - pub x: isize, - /// The y coordinate - pub y: isize, -} - -impl Coord { - /// Creates a new coordinate. - pub fn new(x: isize, y: isize) -> Coord { - Coord { x, y } - } -} - -impl Add<(isize, isize)> for Coord { - type Output = Coord; - - fn add(self, rhs: (isize, isize)) -> Coord { - Coord { x: self.x + rhs.0, y: self.y + rhs.1 } - } -} - -impl Sub<(isize, isize)> for Coord { - type Output = Coord; - - fn sub(self, rhs: (isize, isize)) -> Coord { - Coord { x: self.x - rhs.0, y: self.y - rhs.1 } - } -} - -impl Add for Coord { - type Output = Coord; - - fn add(self, rhs: Coord) -> Coord { - Coord { - x: self.x + rhs.x, - y: self.y + rhs.y, - } - } -} - -impl Sub for Coord { - type Output = Coord; - - fn sub(self, rhs: Coord) -> Coord { - Coord { - x: self.x - rhs.x, - y: self.y - rhs.y, - } - } -} - -impl Ord for Coord { - fn cmp(&self, other: &Self) -> Ordering { - match (self.y, other.y) { - (s, o) if s > o => Ordering::Greater, - (s, o) if s < o => Ordering::Less, - _ => self.x.cmp(&other.x), - } - } -} - -impl PartialOrd for Coord { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Eq for Coord { } - - -/// A rectangle given by its top-left coordinate and bottom-right coordinate. -#[derive(Clone, Copy, PartialEq, Debug, Hash)] -pub struct Rectangle { - /// The top-left point - pub top_left: Coord, - /// The bottom-right point - pub bottom_right: Coord, -} - -impl Rectangle { - /// Returns the width of this Rectangle. - pub fn width(&self) -> usize { - (self.bottom_right.x - self.top_left.x) as usize - } - - /// Returns the height of this Rectangle. - pub fn height(&self) -> usize { - (self.bottom_right.y - self.top_left.y) as usize - } -} - -impl Add for Rectangle { - type Output = Rectangle; - - fn add(self, rhs: Coord) -> Rectangle { - Rectangle { - top_left: self.top_left + rhs, - bottom_right: self.bottom_right + rhs, - } - } -} - -impl Sub for Rectangle { - type Output = Rectangle; - - fn sub(self, rhs: Coord) -> Rectangle { - Rectangle { - top_left: self.top_left - rhs, - bottom_right: self.bottom_right - rhs, - } - } -} diff --git a/kernel/spawn/src/lib.rs b/kernel/spawn/src/lib.rs index c89966572b..9701a53bb6 100755 --- a/kernel/spawn/src/lib.rs +++ b/kernel/spawn/src/lib.rs @@ -185,6 +185,13 @@ pub fn new_task_builder( TaskBuilder::new(func, argument) } +pub fn spawn(f: F) -> Result +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + new_task_builder(|_| f(), ()).spawn() +} /// Every executable application must have an entry function named "main". const ENTRY_POINT_SECTION_NAME: &str = "main"; diff --git a/kernel/sync_block/src/condvar.rs b/kernel/sync_block/src/condvar.rs index 439d5a5041..409c1e34ac 100644 --- a/kernel/sync_block/src/condvar.rs +++ b/kernel/sync_block/src/condvar.rs @@ -1,15 +1,17 @@ -use crate::MutexGuard; -use preemption::hold_preemption_no_timer_disable; use mpmc_queue::Queue; +use preemption::hold_preemption_no_timer_disable; use sync::DeadlockPrevention; use sync_spin::Spin; use task::{get_my_current_task, TaskRef}; +use crate::MutexGuard; + /// A condition variable. /// /// Condition variables represent the ability to block a thread such that it /// consumes no CPU time while waiting for an event to occur. // TODO: Is there even a point to exposing this generic? +#[derive(Debug)] pub struct Condvar

where P: DeadlockPrevention, @@ -17,6 +19,15 @@ where inner: Queue, } +impl

Default for Condvar

+where + P: DeadlockPrevention, +{ + fn default() -> Self { + Self::new() + } +} + impl

Condvar

where P: DeadlockPrevention, diff --git a/kernel/text_terminal/Cargo.toml b/kernel/text_terminal/Cargo.toml index 95ab31da2f..95da61816d 100644 --- a/kernel/text_terminal/Cargo.toml +++ b/kernel/text_terminal/Cargo.toml @@ -14,8 +14,5 @@ derive_more = "0.99.0" [dependencies.log] version = "0.4.8" -[dependencies.event_types] -path = "../event_types" - [lib] crate-type = ["rlib"] diff --git a/kernel/text_terminal/src/lib.rs b/kernel/text_terminal/src/lib.rs index 5a0875353b..2f6eed4e7a 100644 --- a/kernel/text_terminal/src/lib.rs +++ b/kernel/text_terminal/src/lib.rs @@ -32,7 +32,6 @@ #[macro_use] extern crate alloc; #[macro_use] extern crate log; #[macro_use] extern crate bitflags; -extern crate event_types; extern crate unicode_width; extern crate core2; extern crate vte; diff --git a/kernel/tty/Cargo.toml b/kernel/tty/Cargo.toml index f1a5fa6ad6..5d3224aa5f 100644 --- a/kernel/tty/Cargo.toml +++ b/kernel/tty/Cargo.toml @@ -6,7 +6,7 @@ description = "TTY abstractions" edition = "2021" [dependencies] -async_channel = { path = "../async_channel" } +mpmc_channel = { path = "../mpmc_channel" } sync_block = { path = "../sync_block" } [dependencies.core2] diff --git a/kernel/tty/src/channel.rs b/kernel/tty/src/channel.rs index 28ed06a185..27b147ecba 100644 --- a/kernel/tty/src/channel.rs +++ b/kernel/tty/src/channel.rs @@ -1,5 +1,5 @@ -use async_channel::{Receiver, Sender}; use core2::io::Result; +use mpmc_channel::{Receiver, Sender}; #[derive(Clone)] pub(crate) struct Channel { @@ -9,7 +9,7 @@ pub(crate) struct Channel { impl Channel { pub(crate) fn new() -> Self { - let (sender, receiver) = async_channel::new_channel(256); + let (sender, receiver) = mpmc_channel::new_channel(256); Self { sender, receiver } } diff --git a/kernel/tty/src/discipline.rs b/kernel/tty/src/discipline.rs index 36e52f992b..e1f7cf8bb2 100644 --- a/kernel/tty/src/discipline.rs +++ b/kernel/tty/src/discipline.rs @@ -2,8 +2,8 @@ use core::sync::atomic::{AtomicBool, Ordering}; use crate::Channel; use alloc::vec::Vec; -use async_channel::{new_channel, Receiver, Sender}; use core2::io::Result; +use mpmc_channel::{new_channel, Receiver, Sender}; use sync_block::Mutex; // FIXME: Ctrl+C, Ctrl+Z, etc. diff --git a/kernel/unified_channel/Cargo.toml b/kernel/unified_channel/Cargo.toml index 417ef04d1a..b1dd40c228 100644 --- a/kernel/unified_channel/Cargo.toml +++ b/kernel/unified_channel/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "unified_channel" version = "0.1.0" -description = "A cfg-based wrapper that unifies rendezvous channels and async channels, for evaluation purposes" +description = "A cfg-based wrapper that unifies rendezvous channels and mpmc channels, for evaluation purposes" authors = ["Kevin Boos "] [dependencies] cfg-if = "0.1.6" -[dependencies.async_channel] -path = "../async_channel" +[dependencies.mpmc_channel] +path = "../mpmc_channel" [dependencies.rendezvous] path = "../rendezvous" diff --git a/kernel/unified_channel/src/lib.rs b/kernel/unified_channel/src/lib.rs index b5d9d3ed82..68171fbfcb 100644 --- a/kernel/unified_channel/src/lib.rs +++ b/kernel/unified_channel/src/lib.rs @@ -1,22 +1,22 @@ //! A cfg-based wrapper that unifies rendezvous channels and async channels, for evaluation purposes. //! -//! The cfg option is `use_async_channel`, the default is to use the rendezvous channel. +//! The cfg option is `use_mpmc_channel`, the default is to use the rendezvous channel. #![no_std] extern crate alloc; -extern crate async_channel; +extern crate mpmc_channel; extern crate rendezvous; use alloc::string::String; pub fn new_string_channel(_minimum_capacity: usize) -> (StringSender, StringReceiver) { - #[cfg(use_async_channel)] { - let (sender, receiver) = async_channel::new_channel::(_minimum_capacity); + #[cfg(use_mpmc_channel)] { + let (sender, receiver) = mpmc_channel::new_channel::(_minimum_capacity); return (StringSender { sender }, StringReceiver { receiver }); } - #[cfg(not(use_async_channel))] { + #[cfg(not(use_mpmc_channel))] { let (sender, receiver) = rendezvous::new_channel::(); return (StringSender { sender }, StringReceiver { receiver }); } @@ -24,18 +24,18 @@ pub fn new_string_channel(_minimum_capacity: usize) -> (StringSender, StringRece #[derive(Clone)] pub struct StringSender { - #[cfg(use_async_channel)] - sender: async_channel::Sender, - #[cfg(not(use_async_channel))] + #[cfg(use_mpmc_channel)] + sender: mpmc_channel::Sender, + #[cfg(not(use_mpmc_channel))] sender: rendezvous::Sender, } impl StringSender { - #[cfg(use_async_channel)] + #[cfg(use_mpmc_channel)] pub fn send(&self, msg: String) -> Result<(), &'static str> { self.sender.send(msg).map_err(|_e| "async channel send error") } - #[cfg(not(use_async_channel))] + #[cfg(not(use_mpmc_channel))] pub fn send(&self, msg: String) -> Result<(), &'static str> { self.sender.send(msg) } @@ -43,18 +43,18 @@ impl StringSender { #[derive(Clone)] pub struct StringReceiver { - #[cfg(use_async_channel)] - receiver: async_channel::Receiver, - #[cfg(not(use_async_channel))] + #[cfg(use_mpmc_channel)] + receiver: mpmc_channel::Receiver, + #[cfg(not(use_mpmc_channel))] receiver: rendezvous::Receiver, } impl StringReceiver { - #[cfg(use_async_channel)] + #[cfg(use_mpmc_channel)] pub fn receive(&self) -> Result { self.receiver.receive().map_err(|_e| "async channel receive error") } - #[cfg(not(use_async_channel))] + #[cfg(not(use_mpmc_channel))] pub fn receive(&self) -> Result { self.receiver.receive() } diff --git a/kernel/waker_generic/src/lib.rs b/kernel/waker_generic/src/lib.rs index 85ed8c66ab..a6f2c9725d 100644 --- a/kernel/waker_generic/src/lib.rs +++ b/kernel/waker_generic/src/lib.rs @@ -43,6 +43,7 @@ pub struct Blocker { /// If true, this Blocker can cease blocking and return to its caller. woken: Arc>, } + impl Blocker { /// Performs the given `block_action` within this `Blocker`, /// looping until the associated waker is woken. diff --git a/kernel/window/Cargo.toml b/kernel/window/Cargo.toml deleted file mode 100644 index 467b2f5a82..0000000000 --- a/kernel/window/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "window" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "an easy-to-use window object owned by application" - -[dependencies] -spin = "0.9.4" -mpmc = "0.1.6" - -[dependencies.log] -version = "0.4.8" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.color] -path = "../color" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.window_inner] -path = "../window_inner" - -[dependencies.window_manager] -path = "../window_manager" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.event_types] -path = "../event_types" - -[dependencies.spawn] -path = "../spawn" - -[dependencies.mouse] -path = "../mouse" - -[dependencies.path] -path = "../path" - -[dependencies.dereffer] -path = "../../libs/dereffer" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/window/src/lib.rs b/kernel/window/src/lib.rs deleted file mode 100644 index 1e9f9e4cc7..0000000000 --- a/kernel/window/src/lib.rs +++ /dev/null @@ -1,511 +0,0 @@ -//! A `Window` object should be owned by an application. It can display a `Displayable` object in its framebuffer. See `applications/new_window` as a demo to use this library. -//! -//! This library will create a window with default title bar and border. It handles the commonly used interactions like moving -//! the window or close the window. Also, it is responsible to show title bar differently when window is active. -//! -//! A window can render itself to the screen via a window manager. The window manager will compute the bounding box of the updated part and composites it with other existing windows according to their order. -//! -//! The library -//! frees applications from handling the complicated interaction with window manager, however, advanced users could learn from -//! this library about how to use window manager APIs directly. -//! - -#![no_std] -#![feature(type_alias_impl_trait)] - -extern crate alloc; -extern crate mpmc; -extern crate event_types; -extern crate spin; -#[macro_use] -extern crate log; -extern crate framebuffer; -extern crate framebuffer_drawer; -extern crate mouse; -extern crate window_inner; -extern crate window_manager; -extern crate shapes; -extern crate color; -extern crate dereffer; - -use alloc::sync::Arc; -use dereffer::{DerefsTo, DerefsToMut}; -use mpmc::Queue; -use event_types::{Event, MousePositionEvent}; -use framebuffer::{Framebuffer, AlphaPixel}; -use color::Color; -use shapes::{Coord, Rectangle}; -use spin::{Mutex, MutexGuard}; -use window_inner::{WindowInner, WindowMovingStatus, DEFAULT_BORDER_SIZE, DEFAULT_TITLE_BAR_HEIGHT}; -use window_manager::{WINDOW_MANAGER}; - - -// border radius, in number of pixels -const WINDOW_RADIUS: usize = 5; -// border and title bar color when window is inactive -const WINDOW_BORDER_COLOR_INACTIVE: Color = Color::new(0x00333333); -// border and title bar color when window is active, the top part color -const WINDOW_BORDER_COLOR_ACTIVE_TOP: Color = Color::new(0x00BBBBBB); -// border and title bar color when window is active, the bottom part color -const WINDOW_BORDER_COLOR_ACTIVE_BOTTOM: Color = Color::new(0x00666666); -// window button color: red -const WINDOW_BUTTON_COLOR_CLOSE: Color = Color::new(0x00E74C3C); -// window button color: green -const WINDOW_BUTTON_COLOR_MINIMIZE_MAMIMIZE: Color = Color::new(0x00239B56); -// window button color: purple -const WINDOW_BUTTON_COLOR_HIDE: Color = Color::new(0x007D3C98); -// window button margin from left, in number of pixels -const WINDOW_BUTTON_BIAS_X: usize = 12; -// the interval between buttons, in number of pixels -const WINDOW_BUTTON_BETWEEN: usize = 15; -// the button size, in number of pixels -const WINDOW_BUTTON_SIZE: usize = 6; - -// The buttons shown in title bar -enum TopButton { - // Button to close the window - Close, - // Button to minimize/maximize the window (depends on the current state) - MinimizeMaximize, - // Button to hide the window - Hide, -} - -impl From for TopButton { - fn from(item: usize) -> Self { - match item { - 0 => TopButton::Close, - 1 => TopButton::MinimizeMaximize, - 2 => TopButton::Hide, - _ => TopButton::Close, - } - } -} - - -/// This struct is the application-facing representation of a window. -/// -pub struct Window { - /// The system-facing inner representation of this window. - /// The window manager interacts with this object directly; - /// thus, applications should not be able to access this directly. - /// - /// This is wrapped in an `Arc` such that the window manager can hold `Weak` references to it. - inner: Arc>, - /// The event queue - event_consumer: Queue, - /// last mouse position event, used to judge click and press-moving event - /// TODO FIXME (kevinaboos): why is mouse-specific stuff here? - last_mouse_position_event: MousePositionEvent, - /// record last result of whether this window is active, to reduce redraw overhead - last_is_active: bool, -} - -impl Window { - /// Creates a new window to be displayed on screen. - /// - /// The given `framebuffer` will be filled with the `initial_background` color. - /// - /// The newly-created `Window` will be set as the "active" window that has current focus. - /// - /// # Arguments: - /// * `coordinate`: the position of the window relative to the top-left corner of the screen. - /// * `width`, `height`: the dimensions of the window in pixels. - /// * `initial_background`: the default color of the window. - pub fn new( - coordinate: Coord, - width: usize, - height: usize, - initial_background: Color, - ) -> Result { - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; - - // Create a new virtual framebuffer to hold this window's contents only, - // and fill it with the initial background color. - let mut framebuffer = Framebuffer::new(width, height, None)?; - framebuffer.fill(initial_background.into()); - let (width, height) = framebuffer.get_size(); - - // TODO: FIXME: (kevinaboos) this condition seems wrong... at least the first conditional does. - if width <= 2 * DEFAULT_TITLE_BAR_HEIGHT || height <= DEFAULT_TITLE_BAR_HEIGHT + DEFAULT_BORDER_SIZE { - return Err("window dimensions must be large enough for the title bar and borders to be drawn"); - } - - // Create an event queue to allow the window manager to pass events to this `Window` via its `WindowInner` instance, - // and to allow applications to receive events from this `Window` object itself. - let event_consumer = Queue::with_capacity(100); - let event_producer = event_consumer.clone(); - - let window_inner = WindowInner::new(coordinate, framebuffer, event_producer); - let mut window = Window { - inner: Arc::new(Mutex::new(window_inner)), - event_consumer, - last_mouse_position_event: MousePositionEvent::default(), - last_is_active: true, // new window is now set as the active window by default - }; - - // Draw the actual window frame, the title bar and borders. - window.draw_border(true); - { - let mut inner = window.inner.lock(); - window.show_button(TopButton::Close, 1, &mut inner); - window.show_button(TopButton::MinimizeMaximize, 1, &mut inner); - window.show_button(TopButton::Hide, 1, &mut inner); - } - - let mut wm = wm_ref.lock(); - wm.set_active(&window.inner, false)?; - - // Currently, refresh the whole screen instead of just the new window's bounds - // wm.refresh_bottom_windows(Some(window_bounding_box), true)?; - wm.refresh_bottom_windows(Option::::None, true)?; - - Ok(window) - } - - - /// Tries to receive an `Event` that has been sent to this `Window`. - /// If no events exist on the queue, it returns `Ok(None)`. - /// - /// "Internal" events will be automatically handled rather than returned. - /// If an error occurs while obtaining the event (or when handling internal events), - /// - /// Otherwise, the event at the front of this window's event queue will be popped off and returned. - pub fn handle_event(&mut self) -> Result, &'static str> { - let mut call_later_do_refresh_floating_border = false; - let mut call_later_do_move_active_window = false; - let mut need_to_set_active = false; - let mut need_refresh_three_button = false; - - let wm_ref = window_manager::WINDOW_MANAGER.get().ok_or("The window manager is not initialized")?; - - let is_active = { - let wm = wm_ref.lock(); - wm.is_active(&self.inner) - }; - if is_active != self.last_is_active { - self.draw_border(is_active); - self.last_is_active = is_active; - let mut inner = self.inner.lock(); - self.show_button(TopButton::Close, 1, &mut inner); - self.show_button(TopButton::MinimizeMaximize, 1, &mut inner); - self.show_button(TopButton::Hide, 1, &mut inner); - } - - // If we cannot handle this event as an "internal" event (e.g., clicking on the window title bar or border), - // we simply return that event from this function such that the application can handle it. - let mut unhandled_event: Option = None; - - - while let Some(event) = self.event_consumer.pop() { - // TODO FIXME: for a performant design, the goal is to AVOID holding the lock on `inner` as much as possible. - // That means that most of the drawing logic should be moved into the `window_inner` crate itself. - let mut inner = self.inner.lock(); - let (width, height) = inner.get_size(); - - match event { - Event::MousePositionEvent(ref mouse_event) => { - match inner.moving { - WindowMovingStatus::Moving(_) => { - // only wait for left button up to exit this mode - if !mouse_event.left_button_hold { - self.last_mouse_position_event = mouse_event.clone(); - call_later_do_move_active_window = true; - } - call_later_do_refresh_floating_border = true; - }, - WindowMovingStatus::Stationary => { - if (mouse_event.coordinate.y as usize) < inner.title_bar_height - && (mouse_event.coordinate.x as usize) < width - { - // the region of title bar - let r2 = WINDOW_RADIUS * WINDOW_RADIUS; - let mut is_three_button = false; - for i in 0..3 { - let dcoordinate = Coord::new( - mouse_event.coordinate.x - - WINDOW_BUTTON_BIAS_X as isize - - (i as isize) * WINDOW_BUTTON_BETWEEN as isize, - mouse_event.coordinate.y - inner.title_bar_height as isize / 2, - ); - if dcoordinate.x * dcoordinate.x + dcoordinate.y * dcoordinate.y - <= r2 as isize - { - is_three_button = true; - if mouse_event.left_button_hold { - self.show_button(TopButton::from(i), 2, &mut inner); - need_refresh_three_button = true; - } else { - self.show_button(TopButton::from(i), 0, &mut inner); - need_refresh_three_button = true; - if self.last_mouse_position_event.left_button_hold { - // Kevin: disabling the close button until it actually works - /* - // click event - if i == 0 { - debug!("close window"); - return Err("user close window"); - // window will not close until app drop self - } - */ - } - } - } else { - self.show_button(TopButton::from(i), 1, &mut inner); - need_refresh_three_button = true; - } - } - // check if user clicked and held the title bar, which means user wanted to move the window - if !is_three_button - && !self.last_mouse_position_event.left_button_hold - && mouse_event.left_button_hold - { - inner.moving = WindowMovingStatus::Moving(mouse_event.gcoordinate); - call_later_do_refresh_floating_border = true; - } - } else { - // The mouse event occurred within the actual window content, not in the title bar. - // Thus, we let the caller handle it. - unhandled_event = Some(Event::MousePositionEvent(mouse_event.clone())); - } - if (mouse_event.coordinate.y as usize) < height - && (mouse_event.coordinate.x as usize) < width - && !self.last_mouse_position_event.left_button_hold - && mouse_event.left_button_hold - { - need_to_set_active = true; - } - self.last_mouse_position_event = mouse_event.clone(); - } - } - } - unhandled => { - unhandled_event = Some(unhandled); - } - } - - // Immediately return any unhandled events to the caller - // before we loop back to handle additional events. - if unhandled_event.is_some() { - break; - } - } - - let mut wm = wm_ref.lock(); - if need_to_set_active { - wm.set_active(&self.inner, true)?; - } - - if need_refresh_three_button { - let area = self.get_button_area(); - wm.refresh_active_window(Some(area))?; - wm.refresh_mouse()?; - } - - if call_later_do_refresh_floating_border { - wm.move_floating_border()?; - } - - if call_later_do_move_active_window { - wm.move_active_window()?; - self.inner.lock().moving = WindowMovingStatus::Stationary; - } - - Ok(unhandled_event) - } - - /// Renders the area of this `Window` specified by the given `bounding_box`, - /// which is relative to the top-left coordinate of this `Window`. - /// - /// Refreshes the whole window if `bounding_box` is `None`. - /// - /// This method should be invoked after updating the window's contents in order to see its new content. - pub fn render(&mut self, bounding_box: Option) -> Result<(), &'static str> { - let wm_ref = WINDOW_MANAGER.get().ok_or("The static window manager was not yet initialized")?; - - // Convert the given relative `bounding_box` to an absolute one (relative to the screen, not the window). - let coordinate = { - let window = self.inner.lock(); - window.get_position() - }; - let absolute_bounding_box = bounding_box.map(|bb| bb + coordinate); - - wm_ref.lock().refresh_windows(absolute_bounding_box) - } - - /// Returns a `Rectangle` describing the position and dimensions of this Window's content region, - /// i.e., the area within the window excluding the title bar and border - /// that is available for rendering application content. - /// - /// The returned `Rectangle` is expressed relative to this Window's position. - pub fn area(&self) -> Rectangle { - self.inner.lock().content_area() - } - - /// Returns an immutable reference to this window's virtual `Framebuffer`. - pub fn framebuffer(&self) -> FramebufferRef { - FramebufferRef::new( - self.inner.lock(), - |guard| guard.framebuffer(), - ) - } - - /// Returns a mutable reference to this window's virtual `Framebuffer`. - pub fn framebuffer_mut(&mut self) -> FramebufferRefMut { - FramebufferRefMut::new( - self.inner.lock(), - |guard| guard.framebuffer(), - |guard| guard.framebuffer_mut(), - ) - } - - /// Returns `true` if this window is the currently active window. - /// - /// Obtains the lock on the window manager instance. - pub fn is_active(&self) -> bool { - WINDOW_MANAGER.get() - .map(|wm| wm.lock().is_active(&self.inner)) - .unwrap_or(false) - } - - /// Draw the border of this window, with argument of whether this window is active now - fn draw_border(&mut self, active: bool) { - let mut inner = self.inner.lock(); - let border_size = inner.border_size; - let title_bar_height = inner.title_bar_height; - - // first draw left, bottom, right border - let border_color = if active { - WINDOW_BORDER_COLOR_ACTIVE_BOTTOM - } else { - WINDOW_BORDER_COLOR_INACTIVE - }; - let (width, height) = inner.get_size(); - - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, title_bar_height as isize), - border_size, - height - title_bar_height, - border_color.into(), - ); - - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, (height - border_size) as isize), - width, - border_size, - border_color.into(), - ); - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new( - (width - border_size) as isize, - title_bar_height as isize, - ), - border_size, - height - title_bar_height, - border_color.into(), - ); - - // then draw the title bar - if active { - for i in 0..title_bar_height { - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, i as isize), - width, - 1, - framebuffer::Pixel::weight_blend( - WINDOW_BORDER_COLOR_ACTIVE_BOTTOM.into(), - WINDOW_BORDER_COLOR_ACTIVE_TOP.into(), - (i as f32) / (title_bar_height as f32) - ) - ); - } - } else { - framebuffer_drawer::draw_rectangle( - inner.framebuffer_mut(), - Coord::new(0, 0), - width, - title_bar_height, - border_color.into(), - ); - } - - // draw radius finally - let r2 = WINDOW_RADIUS * WINDOW_RADIUS; - let trans_pixel = color::TRANSPARENT.into(); - - for i in 0..WINDOW_RADIUS { - for j in 0..WINDOW_RADIUS { - let dx1 = WINDOW_RADIUS - i; - let dy1 = WINDOW_RADIUS - j; - if dx1 * dx1 + dy1 * dy1 > r2 { - // draw this to transparent - inner.framebuffer_mut().overwrite_pixel(Coord::new(i as isize, j as isize), trans_pixel); - inner.framebuffer_mut().overwrite_pixel(Coord::new((width - i - 1) as isize, j as isize), trans_pixel); - } - } - } - } - - /// show three button with status. state = 0,1,2 for three different color - fn show_button(&self, button: TopButton, state: usize, inner: &mut WindowInner) { - let y = inner.title_bar_height / 2; - let x = WINDOW_BUTTON_BIAS_X - + WINDOW_BUTTON_BETWEEN - * match button { - TopButton::Close => 0, - TopButton::MinimizeMaximize => 1, - TopButton::Hide => 2, - }; - let color = match button { - TopButton::Close => WINDOW_BUTTON_COLOR_CLOSE, - TopButton::MinimizeMaximize => WINDOW_BUTTON_COLOR_MINIMIZE_MAMIMIZE, - TopButton::Hide => WINDOW_BUTTON_COLOR_HIDE, - }; - framebuffer_drawer::draw_circle( - inner.framebuffer_mut(), - Coord::new(x as isize, y as isize), - WINDOW_BUTTON_SIZE, - framebuffer::Pixel::weight_blend( - color::BLACK.into(), - color.into(), - 0.2f32 * (state as f32), - ), - ); - } - - /// Gets the rectangle occupied by the three buttons - fn get_button_area(&self) -> Rectangle { - let inner = self.inner.lock(); - let width = inner.get_size().0; - Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(width as isize, inner.title_bar_height as isize) - } - } -} - -impl Drop for Window{ - fn drop(&mut self){ - if let Some(wm) = WINDOW_MANAGER.get() { - if let Err(err) = wm.lock().delete_window(&self.inner) { - error!("Failed to delete_window upon drop: {:?}", err); - } - } else { - error!("BUG: Could not delete_window upon drop because the window manager was not initialized"); - } - } -} - -/// A wrapper around a locked inner window that immutably derefs to a `Framebuffer`. -/// -/// The lock is auto-released when this object is dropped. -pub type FramebufferRef<'g> = DerefsTo, Framebuffer>; - -/// A wrapper around a locked inner window that mutably derefs to a `Framebuffer`. -/// -/// The lock is auto-released when this object is dropped. -pub type FramebufferRefMut<'g> = DerefsToMut, Framebuffer>; diff --git a/kernel/window_inner/Cargo.toml b/kernel/window_inner/Cargo.toml deleted file mode 100644 index f0451bbdb5..0000000000 --- a/kernel/window_inner/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "window_inner" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "allocate new windows and manage a list of existing windows" - -[dependencies] -mpmc = "0.1.6" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.event_types] -path = "../event_types" - -[lib] -crate-type = ["rlib"] diff --git a/kernel/window_inner/src/lib.rs b/kernel/window_inner/src/lib.rs deleted file mode 100644 index ea0029c3ee..0000000000 --- a/kernel/window_inner/src/lib.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! The `WindowInner` struct is the internal representation of a `Window` used by the window manager. -//! -//! In comparison, the `Window` struct is application-facing, meaning it is used by (owned by) -//! and exposed directly to applications or tasks that wish to display content. -//! -//! The `WindowInner` in the window_manager-facing version of the `Window`, -//! and each `Window` contains a reference to its `WindowInner`. -//! -//! The window manager typically holds `Weak` references to a `WindowInner` struct, -//! which allows it to control the window itself and handle non-application-related components of the window, -//! such as the title bar, border, etc. -//! -//! It also allows the window manager to control the window, e.g., move, hide, show, or resize it -//! in a way that applications may not be able to do. - -#![no_std] - -extern crate mpmc; -extern crate event_types; -extern crate framebuffer; -extern crate shapes; - -use mpmc::Queue; -use event_types::{Event}; -use framebuffer::{Framebuffer, AlphaPixel}; -use shapes::{Coord, Rectangle}; - - -// The title bar height, in number of pixels -pub const DEFAULT_TITLE_BAR_HEIGHT: usize = 16; -// left, right, bottom border size, in number of pixels -pub const DEFAULT_BORDER_SIZE: usize = 2; - - -/// Whether a window is moving (being dragged by the mouse). -pub enum WindowMovingStatus { - /// The window is not in motion. - Stationary, - /// The window is currently in motion. - /// The enclosed `Coord` represents the initial position of the window before it started moving. - Moving(Coord), -} - -/// The `WindowInner` struct is the internal system-facing representation of a window. -/// Its members and functions describe the size, state, and events related to window handling, -/// including elements like: -/// * The underlying virtual framebuffer to which the window is rendered, -/// * THe location and dimensions of the window in the final screen, -/// * The window's title bar, buttons, and borders, -/// * Queues for events that have been received by this window, and more. -/// -/// The window manager directly interacts with instances of `WindowInner` rather than `Window`, -/// and the application tasks should not have direct access to this struct for correctness reasons. -/// See the crate-level documentation for more details about how to use this -/// and how it differs from `Window`. -pub struct WindowInner { - /// The position of the top-left corner of the window, - /// expressed relative to the top-left corner of the screen. - coordinate: Coord, - /// The width of the border in pixels. - /// By default, there is a border on the left, right, and bottom edges of the window. - pub border_size: usize, - /// The height of title bar in pixels. - /// By default, there is one title bar at the top edge of the window. - pub title_bar_height: usize, - /// The producer side of this window's event queue. - /// Entities that want to send events to this window (or the application that owns this window) - /// should push events onto this queue. - /// - /// The corresponding consumer for this event queue is found in the `Window` struct - /// that created and owns this `WindowInner` instance. - event_producer: Queue, // event output used by window manager - /// The virtual framebuffer that is used exclusively for rendering only this window. - framebuffer: Framebuffer, - /// Whether a window is moving or stationary. - /// - /// TODO: FIXME (kevinaboos): this should be private, and window moving logic should be moved into this crate. - pub moving: WindowMovingStatus, -} - -impl WindowInner { - /// Creates a new `WindowInner` object backed by the given `framebuffer` - /// and that will be rendered at the given `coordinate` relative to the screen. - pub fn new( - coordinate: Coord, - framebuffer: Framebuffer, - event_producer: Queue, - ) -> WindowInner { - WindowInner { - coordinate, - border_size: DEFAULT_BORDER_SIZE, - title_bar_height: DEFAULT_TITLE_BAR_HEIGHT, - event_producer, - framebuffer, - moving: WindowMovingStatus::Stationary, - } - } - - /// Returns `true` if the given `coordinate` (relative to the top-left corner of this window) - /// is within the bounds of this window. - pub fn contains(&self, coordinate: Coord) -> bool { - self.framebuffer.contains(coordinate) - } - - /// Gets the size of a window in pixels - pub fn get_size(&self) -> (usize, usize) { - self.framebuffer.get_size() - } - - /// Gets the top-left position of the window relative to the top-left of the screen - pub fn get_position(&self) -> Coord { - self.coordinate - } - - /// Sets the top-left position of the window relative to the top-left of the screen - pub fn set_position(&mut self, coordinate: Coord) { - self.coordinate = coordinate; - } - - /// Returns an immutable reference to this window's virtual Framebuffer. - pub fn framebuffer(&self) -> &Framebuffer { - &self.framebuffer - } - - /// Returns a mutable reference to this window's virtual Framebuffer. - pub fn framebuffer_mut(&mut self) -> &mut Framebuffer { - &mut self.framebuffer - } - - /// Returns the pixel value at the given `coordinate`, - /// if the `coordinate` is within the window's bounds. - pub fn get_pixel(&self, coordinate: Coord) -> Option { - self.framebuffer.get_pixel(coordinate) - } - - /// Returns the size of the Window border in pixels. - /// There is a border drawn on the left, right, and bottom edges. - pub fn get_border_size(&self) -> usize { - self.border_size - } - - /// Returns the size of the Window title bar in pixels. - /// There is a title bar drawn on the top edge of the Window. - pub fn get_title_bar_height(&self) -> usize { - self.title_bar_height - } - - /// Returns the position and dimensions of the Window's content region, - /// i.e., the area within the window excluding the title bar and border. - /// - /// The returned `Rectangle` is expressed relative to this Window's position. - pub fn content_area(&self) -> Rectangle { - let (window_width, window_height) = self.get_size(); - // There is one title bar on top, and a border on the left, right, and bottom - let top_left = Coord::new(self.border_size as isize, self.title_bar_height as isize); - let bottom_right = Coord::new((window_width - self.border_size) as isize, (window_height - self.border_size) as isize); - Rectangle { top_left, bottom_right } - } - - /// Resizes and moves this window to fit the given `Rectangle` that describes its new position. - pub fn resize(&mut self, new_position: Rectangle) -> Result<(), &'static str> { - // First, perform the actual resize of the inner window - self.coordinate = new_position.top_left; - self.framebuffer = Framebuffer::new(new_position.width(), new_position.height(), None)?; - - // Second, send a resize event to that application window (the `Window` object) - // so it knows to refresh its display. - // Rather than send the total size of the whole window, - // we instead send the size and position of the inner content area of the window. - // This prevents the application from thinking it can render over the area - // that contains this window's title bar or border. - self.send_event(Event::new_window_resize_event(self.content_area())) - .map_err(|_e| "Failed to enqueue the resize event; window event queue was full.")?; - - Ok(()) - } - - /// Sends the given `event` to this window. - /// - /// If the event queue was full, `Err(event)` is returned. - pub fn send_event(&self, event: Event) -> Result<(), Event> { - self.event_producer.push(event) - } -} diff --git a/kernel/window_manager/Cargo.toml b/kernel/window_manager/Cargo.toml deleted file mode 100644 index aaefc57836..0000000000 --- a/kernel/window_manager/Cargo.toml +++ /dev/null @@ -1,64 +0,0 @@ -[package] -name = "window_manager" -version = "0.1.0" -authors = ["Yue Wu ", "Wenqiu Yu "] -description = "allocate new windows and manage a list of existing windows" - -[dependencies] -spin = "0.9.4" -mpmc = "0.1.6" - -[dependencies.log] -version = "0.4.8" - -[dependencies.framebuffer_drawer] -path = "../framebuffer_drawer" - -[dependencies.window_inner] -path = "../window_inner" - -[dependencies.compositor] -path = "../compositor" - -[dependencies.framebuffer_compositor] -path = "../framebuffer_compositor" - -[dependencies.shapes] -path = "../shapes" - -[dependencies.framebuffer] -path = "../framebuffer" - -[dependencies.color] -path = "../color" - -[dependencies.event_types] -path = "../event_types" - -[dependencies.font] -path = "../font" - -[dependencies.lazy_static] -features = ["spin_no_std"] -version = "1.4.0" - -[dependencies.mod_mgmt] -path = "../mod_mgmt" - -[dependencies.spawn] -path = "../spawn" - -[dependencies.mouse_data] -path = "../../libs/mouse_data" - -[dependencies.path] -path = "../../kernel/path" - -[lib] -crate-type = ["rlib"] - -[dependencies.keycodes_ascii] -path = "../../libs/keycodes_ascii" - -[dependencies.scheduler] -path = "../../kernel/scheduler" diff --git a/kernel/window_manager/src/lib.rs b/kernel/window_manager/src/lib.rs deleted file mode 100644 index 470f3ec29d..0000000000 --- a/kernel/window_manager/src/lib.rs +++ /dev/null @@ -1,800 +0,0 @@ -//! This crate acts as a manager of a list of windows. It defines a `WindowManager` structure and an instance of it. -//! -//! A window manager holds a set of `WindowInner` objects, including an active window, a list of shown windows and a list of hidden windows. The hidden windows are totally overlapped by others. -//! -//! A window manager owns a bottom framebuffer and a top framebuffer. The bottom is the background of the desktop and the top framebuffer contains a floating window border and a mouse arrow. -//! A window manager also contains a final framebuffer which is mapped to the screen. In refreshing an area, the manager will render all the framebuffers to the final one in order: bottom -> hide list -> showlist -> active -> top. -//! -//! The window manager provides methods to update within some bounding boxes rather than the whole screen for better performance. - -#![no_std] - -extern crate spin; -#[macro_use] extern crate log; -extern crate alloc; -extern crate mpmc; -extern crate event_types; -extern crate compositor; -extern crate framebuffer; -extern crate framebuffer_compositor; -extern crate framebuffer_drawer; -extern crate keycodes_ascii; -extern crate mod_mgmt; -extern crate mouse_data; -extern crate path; -extern crate scheduler; -extern crate spawn; -extern crate window_inner; -extern crate shapes; -extern crate color; - -use alloc::collections::VecDeque; -use alloc::string::ToString; -use alloc::sync::{Arc, Weak}; -use alloc::vec::Vec; -use compositor::{Compositor, FramebufferUpdates, CompositableRegion}; - -use mpmc::Queue; -use event_types::{Event, MousePositionEvent}; -use framebuffer::{Framebuffer, AlphaPixel}; -use color::Color; -use shapes::{Coord, Rectangle}; -use framebuffer_compositor::{FRAME_COMPOSITOR}; -use keycodes_ascii::{KeyAction, KeyEvent, Keycode}; -use mouse_data::MouseEvent; -use spin::{Mutex, Once}; -use window_inner::{WindowInner, WindowMovingStatus}; - -/// The instance of the default window manager -pub static WINDOW_MANAGER: Once> = Once::new(); - -/// The width and height size of mouse in number of pixels. -const MOUSE_POINTER_SIZE_Y: usize = 18; -const MOUSE_POINTER_SIZE_X: usize = 11; -/// The mouse pointer image defined as a 2-D pixel array. -static MOUSE_POINTER_IMAGE: [[Color; MOUSE_POINTER_SIZE_Y]; MOUSE_POINTER_SIZE_X] = { - const T: Color = color::TRANSPARENT; - const C: Color = color::BLACK; // Cursor - const B: Color = color::WHITE; // Border - [ - [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, T, T], - [T, B, C, C, C, C, C, C, C, C, C, C, C, C, B, T, T, T], - [T, T, B, C, C, C, C, C, C, C, C, C, C, B, T, T, T, T], - [T, T, T, B, C, C, C, C, C, C, C, C, B, T, T, T, T, T], - [T, T, T, T, B, C, C, C, C, C, C, C, C, B, B, T, T, T], - [T, T, T, T, T, B, C, C, C, C, C, C, C, C, C, B, B, T], - [T, T, T, T, T, T, B, C, C, C, C, B, B, C, C, C, C, B], - [T, T, T, T, T, T, T, B, C, C, B, T, T, B, B, C, B, T], - [T, T, T, T, T, T, T, T, B, C, B, T, T, T, T, B, B, T], - [T, T, T, T, T, T, T, T, T, B, B, T, T, T, T, T, T, T], - [T, T, T, T, T, T, T, T, T, T, B, T, T, T, T, T, T, T], - ] -}; - -// the border indicating new window position and size -const WINDOW_BORDER_SIZE: usize = 3; -// border's inner color -const WINDOW_BORDER_COLOR_INNER: Color = Color::new(0x00CA6F1E); - -/// Window manager structure which maintains a list of windows and a mouse. -pub struct WindowManager { - /// those window currently not shown on screen - hide_list: VecDeque>>, - /// those window shown on screen that may overlapping each other - show_list: VecDeque>>, - /// the only active window, receiving all keyboard events (except for those remained for WM) - active: Weak>, // this one is not in show_list - /// current mouse position - mouse: Coord, - /// If a window is being repositioned (e.g., by dragging it), this is the position of that window's border - repositioned_border: Option, - /// The bottom framebuffer typically contains the background/wallpaper image, - /// which is displayed by default when no other windows exist on top of it. - bottom_fb: Framebuffer, - /// The top framebuffer is used for overlaying visual elements atop the rest of the windows, - /// e.g., the mouse pointer, the border of a window being dragged/moved, etc. - top_fb: Framebuffer, - /// The final framebuffer which is mapped to the screen (the actual display device). - pub final_fb: Framebuffer, -} - -impl WindowManager { - /// Sets one window as active, push last active (if exists) to top of show_list. if `refresh` is `true`, will then refresh the window's area. - /// Returns whether this window is the first active window in the manager. - /// - /// TODO FIXME: (kevinaboos) remove this dumb notion of "first active". This is a bad hack. - pub fn set_active( - &mut self, - inner_ref: &Arc>, - refresh: bool, - ) -> Result { - // if it is currently actived, just return - let first_active = match self.active.upgrade() { - Some(current_active) => { - if Arc::ptr_eq(¤t_active, inner_ref) { - return Ok(true); // do nothing - } else { - // save this to show_list - self.show_list.push_front(self.active.clone()); - } - false - } - None => true, - }; - - if let Some(i) = self.is_window_in_show_list(inner_ref) { - self.show_list.remove(i); - } - if let Some(i) = self.is_window_in_hide_list(inner_ref) { - self.hide_list.remove(i); - } - self.active = Arc::downgrade(inner_ref); - let area = { - let window = inner_ref.lock(); - let top_left = window.get_position(); - let (width, height) = window.get_size(); - Rectangle { - top_left, - bottom_right: top_left + (width as isize, height as isize) - } - }; - if refresh { - self.refresh_bottom_windows(Some(area), true)?; - } - Ok(first_active) - } - - /// Returns the index of a window if it is in the show list - fn is_window_in_show_list(&mut self, window: &Arc>) -> Option { - for (i, item) in self.show_list.iter().enumerate() { - if let Some(item_ptr) = item.upgrade() { - if Arc::ptr_eq(&item_ptr, window) { - return Some(i); - } - } - } - None - } - - /// Returns the index of a window if it is in the hide list - fn is_window_in_hide_list(&mut self, window: &Arc>) -> Option { - for (i, item) in self.hide_list.iter().enumerate() { - if let Some(item_ptr) = item.upgrade() { - if Arc::ptr_eq(&item_ptr, window) { - return Some(i); - } - } - } - None - } - - /// delete a window and refresh its region - pub fn delete_window(&mut self, inner_ref: &Arc>) -> Result<(), &'static str> { - let (top_left, bottom_right) = { - let inner = inner_ref.lock(); - let top_left = inner.get_position(); - let (width, height) = inner.get_size(); - let bottom_right = top_left + (width as isize, height as isize); - (top_left, bottom_right) - }; - let area = Some( - Rectangle { - top_left, - bottom_right - } - ); - - if let Some(current_active) = self.active.upgrade() { - if Arc::ptr_eq(¤t_active, inner_ref) { - self.refresh_bottom_windows(area, false)?; - if let Some(window) = self.show_list.remove(0) { - self.active = window; - } else if let Some(window) = self.hide_list.remove(0) { - self.active = window; - } else { - self.active = Weak::new(); // delete reference - } - return Ok(()); - } - } - - if let Some(index) = self.is_window_in_show_list(inner_ref) { - self.show_list.remove(index); - self.refresh_windows(area)?; - return Ok(()) - } - - if let Some(index) = self.is_window_in_hide_list(inner_ref) { - self.hide_list.remove(index); - return Ok(()) - } - Err("cannot find this window") - } - - /// Refresh the region in `bounding_box`. Only render the bottom final framebuffer and windows. Ignore the active window if `active` is false. - pub fn refresh_bottom_windows( - &mut self, - bounding_box: impl IntoIterator + Clone, - active: bool, - ) -> Result<(), &'static str> { - // bottom framebuffer - let bottom_fb_area = FramebufferUpdates { - src_framebuffer: &self.bottom_fb, - coordinate_in_dest_framebuffer: Coord::new(0, 0), - }; - - // list of windows to be updated - let mut window_ref_list = Vec::new(); - for window in &self.hide_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - for window in &self.show_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - if active { - if let Some(window_ref) = self.active.upgrade() { - window_ref_list.push(window_ref) - } - } - - // lock windows - let locked_window_list = &window_ref_list.iter().map(|x| x.lock()).collect::>(); - - // create updated framebuffer info objects - let window_bufferlist = locked_window_list.iter().map(|window| { - FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - } - }); - - let buffer_iter = Some(bottom_fb_area).into_iter().chain(window_bufferlist); - FRAME_COMPOSITOR.lock().composite(buffer_iter, &mut self.final_fb, bounding_box)?; - - Ok(()) - } - - /// Refresh the region of `bounding_box` in the top framebuffer - pub fn refresh_top( - &mut self, - bounding_box: impl IntoIterator + Clone - ) -> Result<(), &'static str> { - let top_buffer = FramebufferUpdates { - src_framebuffer: &self.top_fb, - coordinate_in_dest_framebuffer: Coord::new(0, 0), - }; - - FRAME_COMPOSITOR.lock().composite(Some(top_buffer), &mut self.final_fb, bounding_box) - } - - /// Refresh the part in `bounding_box` of every window. `bounding_box` is a region relative to the top-left of the screen. Refresh the whole screen if the bounding box is None. - pub fn refresh_windows( - &mut self, - bounding_box: impl IntoIterator + Clone, - ) -> Result<(), &'static str> { - // reference of windows - let mut window_ref_list = Vec::new(); - for window in &self.hide_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - for window in &self.show_list { - if let Some(window_ref) = window.upgrade() { - window_ref_list.push(window_ref); - } - } - - if let Some(window_ref) = self.active.upgrade() { - window_ref_list.push(window_ref) - } - - // lock windows - let locked_window_list = &window_ref_list.iter().map(|x| x.lock()).collect::>(); - // create updated framebuffer info objects - let bufferlist = locked_window_list.iter().map(|window| { - FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - } - }); - - FRAME_COMPOSITOR.lock().composite(bufferlist, &mut self.final_fb, bounding_box) - } - - - /// Refresh the part in `bounding_box` of the active window. `bounding_box` is a region relative to the top-left of the screen. Refresh the whole screen if the bounding box is None. - pub fn refresh_active_window(&mut self, bounding_box: Option) -> Result<(), &'static str> { - if let Some(window_ref) = self.active.upgrade() { - let window = window_ref.lock(); - let buffer_update = FramebufferUpdates { - src_framebuffer: window.framebuffer(), - coordinate_in_dest_framebuffer: window.get_position(), - }; - FRAME_COMPOSITOR.lock().composite(Some(buffer_update), &mut self.final_fb, bounding_box) - } else { - Ok(()) - } - } - - /// Passes the given keyboard event to the currently active window. - fn pass_keyboard_event_to_window(&self, key_event: KeyEvent) -> Result<(), &'static str> { - let active_window = self.active.upgrade().ok_or("no window was set as active to receive a keyboard event")?; - active_window.lock().send_event(Event::new_keyboard_event(key_event)) - .map_err(|_e| "Failed to enqueue the keyboard event; window event queue was full.")?; - Ok(()) - } - - /// Passes the given mouse event to the window that the mouse is currently over. - /// - /// If the mouse is not over any window, an error is returned; - /// however, this error is quite common and expected when the mouse is not positioned within a window, - /// and is not a true failure. - fn pass_mouse_event_to_window(&self, mouse_event: MouseEvent) -> Result<(), &'static str> { - let coordinate = { &self.mouse }; - let mut event: MousePositionEvent = MousePositionEvent { - coordinate: Coord::new(0, 0), - gcoordinate: *coordinate, - scrolling_up: mouse_event.movement.scroll_movement > 0, //TODO: might be more beneficial to save scroll_movement here - scrolling_down: mouse_event.movement.scroll_movement < 0, //FIXME: also might be the wrong way around - left_button_hold: mouse_event.buttons.left(), - right_button_hold: mouse_event.buttons.right(), - fourth_button_hold: mouse_event.buttons.fourth(), - fifth_button_hold: mouse_event.buttons.fifth(), - }; - - // TODO: FIXME: improve this logic to just send the mouse event to the top-most window in the entire WM list, - // not just necessarily the active one. (For example, scroll wheel events can be sent to non-active windows). - - - // first check the active one - if let Some(current_active) = self.active.upgrade() { - let current_active_win = current_active.lock(); - let current_coordinate = current_active_win.get_position(); - if current_active_win.contains(*coordinate - current_coordinate) || matches!(current_active_win.moving, WindowMovingStatus::Moving(_)) - { - event.coordinate = *coordinate - current_coordinate; - // debug!("pass to active: {}, {}", event.x, event.y); - current_active_win.send_event(Event::MousePositionEvent(event)) - .map_err(|_e| "Failed to enqueue the mouse event; window event queue was full.")?; - return Ok(()); - } - } - - // TODO FIXME: (kevinaboos): the logic below here is actually incorrect -- it could send mouse events to an invisible window below others. - - // then check show_list - for i in 0..self.show_list.len() { - if let Some(now_inner_mutex) = self.show_list[i].upgrade() { - let now_inner = now_inner_mutex.lock(); - let current_coordinate = now_inner.get_position(); - if now_inner.contains(*coordinate - current_coordinate) { - event.coordinate = *coordinate - current_coordinate; - now_inner.send_event(Event::MousePositionEvent(event)) - .map_err(|_e| "Failed to enqueue the mouse event; window event queue was full.")?; - return Ok(()); - } - } - } - - Err("the mouse position does not fall within the bounds of any window") - } - - /// Refresh the floating border, which is used to show the outline of a window while it is being moved. - /// `show` indicates whether to show the border or not. - /// `new_border` defines the rectangular outline of the border. - fn refresh_floating_border( - &mut self, - show: bool, - new_border: Rectangle, - ) -> Result<(), &'static str> { - // first clear old border if exists - if let Some(border) = self.repositioned_border { - let pixels = self.draw_floating_border(&border, color::TRANSPARENT); - self.refresh_bottom_windows(pixels.into_iter(), true)?; - } - - // then draw current border - if show { - let pixels = self.draw_floating_border(&new_border, WINDOW_BORDER_COLOR_INNER); - self.refresh_top(pixels.into_iter())?; - self.repositioned_border = Some(new_border); - } else { - self.repositioned_border = None; - } - - Ok(()) - } - - /// draw the floating border with `pixel`. Return the list of coordinates of pixels that were updated. - /// `border` indicates the position of the border as a rectangle. - /// `color` is the color of the floating border. - fn draw_floating_border(&mut self, border: &Rectangle, color: Color) -> Vec { - let mut coordinates = Vec::new(); - let pixel = color.into(); - for i in 0..(WINDOW_BORDER_SIZE) as isize { - let width = (border.bottom_right.x - border.top_left.x) - 2 * i; - let height = (border.bottom_right.y - border.top_left.y) - 2 * i; - let coordinate = border.top_left + (i, i); - if width <= 0 || height <= 0 { - break; - } - framebuffer_drawer::draw_rectangle( - &mut self.top_fb, - coordinate, - width as usize, - height as usize, - pixel - ); - - for m in 0..width { - coordinates.push(coordinate + (m, 0)); - coordinates.push(coordinate + (m, height)); - } - - for m in 1..height - 1 { - coordinates.push(coordinate + (0, m)); - coordinates.push(coordinate + (width, m)); - } - } - - coordinates - } - - /// take active window's base position and current mouse, move the window with delta - pub fn move_active_window(&mut self) -> Result<(), &'static str> { - if let Some(current_active) = self.active.upgrade() { - let border = Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(0, 0) - }; - self.refresh_floating_border(false, border)?; - - let (old_top_left, old_bottom_right, new_top_left, new_bottom_right) = { - let mut current_active_win = current_active.lock(); - let (current_x, current_y) = { - let m = &self.mouse; - (m.x, m.y) - }; - match current_active_win.moving { - WindowMovingStatus::Moving(base) => { - let old_top_left = current_active_win.get_position(); - let new_top_left = old_top_left + ((current_x - base.x), (current_y - base.y)); - let (width, height) = current_active_win.get_size(); - let old_bottom_right = old_top_left + (width as isize, height as isize); - let new_bottom_right = new_top_left + (width as isize, height as isize); - current_active_win.set_position(new_top_left); - (old_top_left, old_bottom_right, new_top_left, new_bottom_right) - }, - WindowMovingStatus::Stationary => { - return Err("The window is not moving"); - } - } - }; - self.refresh_bottom_windows(Some(Rectangle{top_left: old_top_left, bottom_right: old_bottom_right}), false)?; - - self.refresh_active_window(Some(Rectangle{top_left: new_top_left, bottom_right: new_bottom_right}))?; - self.refresh_mouse()?; - } else { - return Err("cannot find active window to move"); - } - Ok(()) - } - - /// Refresh the mouse display - pub fn refresh_mouse(&mut self) -> Result<(), &'static str> { - let bounding_box = Some(Rectangle { - top_left: self.mouse, - bottom_right: self.mouse + (MOUSE_POINTER_SIZE_X as isize, MOUSE_POINTER_SIZE_Y as isize) - }); - - self.refresh_top(bounding_box) - } - - /// Move mouse. `relative` indicates the new position relative to current position. - fn move_mouse(&mut self, relative: Coord) -> Result<(), &'static str> { - let old = self.mouse; - let mut new = old + relative; - - let (screen_width, screen_height) = self.get_screen_size(); - if new.x < 0 { - new.x = 0; - } - if new.y < 0 { - new.y = 0; - } - - // keep mouse pointer border in the screen when it is at the right or bottom side. - const MOUSE_POINTER_BORDER: isize = 3; - new.x = core::cmp::min(new.x, screen_width as isize - MOUSE_POINTER_BORDER); - new.y = core::cmp::min(new.y, screen_height as isize - MOUSE_POINTER_BORDER); - - self.move_mouse_to(new) - } - - // Move mouse to absolute position `new` - fn move_mouse_to(&mut self, new: Coord) -> Result<(), &'static str> { - // clear old mouse - for y in self.mouse.y..self.mouse.y + MOUSE_POINTER_SIZE_Y as isize { - for x in - self.mouse.x..self.mouse.x + MOUSE_POINTER_SIZE_X as isize { - let coordinate = Coord::new(x, y); - self.top_fb.overwrite_pixel(coordinate, color::TRANSPARENT.into()); - } - } - let bounding_box = Some(Rectangle { - top_left: self.mouse, - bottom_right: self.mouse + (MOUSE_POINTER_SIZE_X as isize, MOUSE_POINTER_SIZE_Y as isize) - }); - self.refresh_bottom_windows(bounding_box.into_iter(), true)?; - - // draw new mouse - self.mouse = new; - for y in new.y..new.y + MOUSE_POINTER_SIZE_Y as isize { - for x in new.x..new.x + MOUSE_POINTER_SIZE_X as isize { - let coordinate = Coord::new(x, y); - let pixel = MOUSE_POINTER_IMAGE[(x - new.x) as usize][(y - new.y) as usize].into(); - self.top_fb.overwrite_pixel(coordinate, pixel); - } - } - self.refresh_mouse()?; - - Ok(()) - } - - /// Move the floating border when a window is moving. - pub fn move_floating_border(&mut self) -> Result<(), &'static str> { - let (new_x, new_y) = { - let m = &self.mouse; - (m.x, m.y) - }; - - if let Some(current_active) = self.active.upgrade() { - let (is_draw, border_start, border_end) = { - let current_active_win = current_active.lock(); - match current_active_win.moving { - WindowMovingStatus::Moving(base) => { - // move this window - // for better performance, while moving window, only border is shown for indication - let coordinate = current_active_win.get_position(); - // let (current_x, current_y) = (coordinate.x, coordinate.y); - let (width, height) = current_active_win.get_size(); - let border_start = coordinate + (new_x - base.x, new_y - base.y); - let border_end = border_start + (width as isize, height as isize); - (true, border_start, border_end) - } - WindowMovingStatus::Stationary => (false, Coord::new(0, 0), Coord::new(0, 0)), - } - }; - let border = Rectangle { - top_left: border_start, - bottom_right: border_end, - }; - self.refresh_floating_border(is_draw, border)?; - } else { - let border = Rectangle { - top_left: Coord::new(0, 0), - bottom_right: Coord::new(0, 0), - }; - self.refresh_floating_border(false, border)?; - } - - Ok(()) - } - - /// Returns true if the given `window` is the currently active window. - pub fn is_active(&self, window: &Arc>) -> bool { - self.active.upgrade() - .map(|active| Arc::ptr_eq(&active, window)) - .unwrap_or(false) - } - - /// Returns the `(width, height)` in pixels of the screen itself (the final framebuffer). - pub fn get_screen_size(&self) -> (usize, usize) { - self.final_fb.get_size() - } -} - -/// Initialize the window manager. It returns (keyboard_producer, mouse_producer) for the I/O devices. -pub fn init() -> Result<(Queue, Queue), &'static str> { - let final_fb: Framebuffer = framebuffer::init()?; - let (width, height) = final_fb.get_size(); - - let mut bottom_fb = Framebuffer::new(width, height, None)?; - let mut top_fb = Framebuffer::new(width, height, None)?; - let (screen_width, screen_height) = bottom_fb.get_size(); - bottom_fb.fill(color::LIGHT_GRAY.into()); - top_fb.fill(color::TRANSPARENT.into()); - - // Initial position for the mouse - let mouse = Coord { - x: screen_width as isize / 2, - y: screen_height as isize / 2, - }; - - // Initialize static window manager - let window_manager = WindowManager { - hide_list: VecDeque::new(), - show_list: VecDeque::new(), - active: Weak::new(), - mouse, - repositioned_border: None, - bottom_fb, - top_fb, - final_fb, - }; - WINDOW_MANAGER.call_once(|| Mutex::new(window_manager)); - - // keyinput queue initialization - let key_consumer: Queue = Queue::with_capacity(100); - let key_producer = key_consumer.clone(); - - // mouse input queue initialization - let mouse_consumer: Queue = Queue::with_capacity(100); - let mouse_producer = mouse_consumer.clone(); - - spawn::new_task_builder(window_manager_loop, (key_consumer, mouse_consumer)) - .name("window_manager_loop".to_string()) - .spawn()?; - - Ok((key_producer, mouse_producer)) -} - -/// handles all keyboard and mouse movement in this window manager -fn window_manager_loop( - (key_consumer, mouse_consumer): (Queue, Queue), -) -> Result<(), &'static str> { - loop { - let event_opt = key_consumer.pop() - .or_else(||mouse_consumer.pop()) - .or_else(||{ - scheduler::schedule(); - None - }); - - if let Some(event) = event_opt { - // Currently, the window manager only cares about keyboard or mouse events - match event { - Event::KeyboardEvent(ref input_event) => { - let key_input = input_event.key_event; - keyboard_handle_application(key_input)?; - } - Event::MouseMovementEvent(ref mouse_event) => { - //as isize to fit larger values - let mut x = mouse_event.movement.x_movement as isize; - let mut y = mouse_event.movement.y_movement as isize; - - // need to combine mouse events if there pending a lot - while let Some(next_event) = mouse_consumer.pop() { - match next_event { - Event::MouseMovementEvent(ref next_mouse_event) => { - if next_mouse_event.movement.scroll_movement - == mouse_event.movement.scroll_movement - && next_mouse_event.buttons.left() - == mouse_event.buttons.left() - && next_mouse_event.buttons.right() - == mouse_event.buttons.right() - && next_mouse_event.buttons.fourth() - == mouse_event.buttons.fourth() - && next_mouse_event.buttons.fifth() - == mouse_event.buttons.fifth() { - x = x.saturating_add(next_mouse_event.movement.x_movement as isize); - y = y.saturating_add(next_mouse_event.movement.y_movement as isize); - } - } - _ => { - break; - } - } - // next_event.mark_completed(); - } - if x != 0 || y != 0 { - let mut wm = WINDOW_MANAGER - .get() - .ok_or("The static window manager was not yet initialized")? - .lock(); - wm.move_mouse( - Coord::new(x, -y) - )?; - } - cursor_handle_application(mouse_event.clone())?; // tell the event to application, or moving window - } - _other => { - trace!("WINDOW_MANAGER: ignoring unexpected event: {:?}", _other); - } - } - } - } -} - -/// handle keyboard event, push it to the active window if one exists -fn keyboard_handle_application(key_input: KeyEvent) -> Result<(), &'static str> { - let win_mgr = WINDOW_MANAGER.get().ok_or("The window manager was not yet initialized")?; - - // First, we handle keyboard shortcuts understood by the window manager. - - // "Super + Arrow" will resize and move windows to the specified half of the screen (left, right, top, or bottom) - if key_input.modifiers.is_super_key() && key_input.action == KeyAction::Pressed { - let screen_dimensions = win_mgr.lock().get_screen_size(); - let (width, height) = (screen_dimensions.0 as isize, screen_dimensions.1 as isize); - let new_position: Option = match key_input.keycode { - Keycode::Left => Some(Rectangle { - top_left: Coord { x: 0, y: 0 }, - bottom_right: Coord { x: width / 2, y: height }, - }), - Keycode::Right => Some(Rectangle { - top_left: Coord { x: width / 2, y: 0 }, - bottom_right: Coord { x: width, y: height }, - }), - Keycode::Up => Some(Rectangle { - top_left: Coord { x: 0, y: 0 }, - bottom_right: Coord { x: width, y: height / 2 }, - }), - Keycode::Down => Some(Rectangle { - top_left: Coord { x: 0, y: height / 2 }, - bottom_right: Coord { x: width, y: height }, - }), - _ => None, - }; - - if let Some(position) = new_position { - let mut wm = win_mgr.lock(); - if let Some(active_window) = wm.active.upgrade() { - debug!("window_manager: resizing active window to {:?}", new_position); - active_window.lock().resize(position)?; - - // force refresh the entire screen for now - // TODO: perform a proper screen refresh here: only refresh the area that contained the active_window's old bounds. - wm.refresh_bottom_windows(Option::::None, true)?; - } - } - - return Ok(()); - } - - // Spawn a new terminal via Ctrl+Alt+T - if key_input.modifiers.is_control() - && key_input.modifiers.is_alt() - && key_input.keycode == Keycode::T - && key_input.action == KeyAction::Pressed - { - // Because this task (the window manager loop) runs in a kernel-only namespace, - // we have to create a new application namespace in order to be able to actually spawn a shell. - - let new_app_namespace = mod_mgmt::create_application_namespace(None)?; - let shell_objfile = new_app_namespace.dir().get_file_starting_with("shell-") - .ok_or("Couldn't find shell application file to run upon Ctrl+Alt+T")?; - let path = shell_objfile.lock().get_absolute_path(); - spawn::new_application_task_builder(path.as_ref(), Some(new_app_namespace))? - .name("shell".to_string()) - .spawn()?; - - debug!("window_manager: spawned new shell app in new app namespace."); - return Ok(()); - } - - // Any keyboard event unhandled above should be passed to the active window. - if let Err(_e) = win_mgr.lock().pass_keyboard_event_to_window(key_input) { - warn!("window_manager: failed to pass keyboard event to active window. Error: {:?}", _e); - // If no window is currently active, then something might be potentially wrong, - // but we can likely recover in the future when another window becomes active. - // Thus, we don't need to return a hard error here. - } - Ok(()) -} - -/// handle mouse event, push it to related window or anyone asked for it -fn cursor_handle_application(mouse_event: MouseEvent) -> Result<(), &'static str> { - let wm = WINDOW_MANAGER.get().ok_or("The static window manager was not yet initialized")?.lock(); - if wm.pass_mouse_event_to_window(mouse_event).is_err() { - // the mouse event should be passed to the window that satisfies: - // 1. the mouse position is currently in the window area - // 2. the window is the top one (active window or show_list windows) under the mouse pointer - // if no window is found in this position, that is system background area. Add logic to handle those events later - } - Ok(()) -} diff --git a/libs/geometry/Cargo.toml b/libs/geometry/Cargo.toml new file mode 100644 index 0000000000..5defe2662c --- /dev/null +++ b/libs/geometry/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "geometry" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/libs/geometry/src/circle.rs b/libs/geometry/src/circle.rs new file mode 100644 index 0000000000..bc7301e010 --- /dev/null +++ b/libs/geometry/src/circle.rs @@ -0,0 +1,22 @@ +use crate::{Containable, Coordinates}; + +#[derive(Clone, Copy, PartialEq, Debug, Hash)] +pub struct Circle { + pub center: Coordinates, + pub radius: usize, +} + +impl Circle { + pub fn contains(&self, containable: T) -> bool + where + T: Containable, + { + for coordinates in containable.vertices() { + let diff = self.center.abs_diff(coordinates); + if diff.x.pow(2) + diff.y.pow(2) > self.radius.pow(2) { + return false; + } + } + true + } +} diff --git a/libs/geometry/src/coordinates.rs b/libs/geometry/src/coordinates.rs new file mode 100644 index 0000000000..e8df4d3d2b --- /dev/null +++ b/libs/geometry/src/coordinates.rs @@ -0,0 +1,73 @@ +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +use crate::{Containable, ConvexPolygon}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Coordinates { + pub x: usize, + pub y: usize, +} + +impl Coordinates { + pub const ORIGIN: Self = Self { x: 0, y: 0 }; + + pub const MAX: Self = Self { + x: usize::MAX, + y: usize::MAX, + }; + + pub fn new(x: usize, y: usize) -> Self { + Self { x, y } + } + + pub fn abs_diff(self, other: Coordinates) -> Self { + Self { + x: self.x.abs_diff(other.x), + y: self.y.abs_diff(other.y), + } + } +} + +impl Add for Coordinates { + type Output = Coordinates; + + fn add(self, rhs: Coordinates) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl AddAssign for Coordinates { + fn add_assign(&mut self, rhs: Coordinates) { + *self = *self + rhs; + } +} + +impl Sub for Coordinates { + type Output = Coordinates; + + fn sub(self, rhs: Coordinates) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl SubAssign for Coordinates { + fn sub_assign(&mut self, rhs: Coordinates) { + *self = *self - rhs; + } +} + +unsafe impl ConvexPolygon for Coordinates {} + +impl Containable for Coordinates { + type I = core::iter::Once; + + fn vertices(&self) -> Self::I { + core::iter::once(*self) + } +} diff --git a/libs/geometry/src/lib.rs b/libs/geometry/src/lib.rs new file mode 100644 index 0000000000..bd2446e94a --- /dev/null +++ b/libs/geometry/src/lib.rs @@ -0,0 +1,35 @@ +#![no_std] + +mod circle; +mod coordinates; +mod line; +mod rectangle; + +pub use circle::Circle; +pub use coordinates::Coordinates; +pub use line::Line; +pub use rectangle::Rectangle; + +pub enum Vertical { + Top, + Bottom, +} + +pub enum Horizontal { + Left, + Right, +} + +/// A shape that can be contained by another shape. +/// +/// Since the shape must be a convex polygon, it is enough to check that all the +/// vertices of the shape are contained. +pub trait Containable: ConvexPolygon { + type I: Iterator; + + /// The vertices of the shape. + fn vertices(&self) -> Self::I; +} + +/// A convex polygon. +pub unsafe trait ConvexPolygon {} diff --git a/libs/geometry/src/line.rs b/libs/geometry/src/line.rs new file mode 100644 index 0000000000..ec80688f87 --- /dev/null +++ b/libs/geometry/src/line.rs @@ -0,0 +1,27 @@ +use core::cmp::{max, min}; + +use crate::{Coordinates, Horizontal, Vertical}; + +#[derive(Clone, Copy, PartialEq, Debug, Hash)] +pub struct Line { + pub start: Coordinates, + pub end: Coordinates, +} + +impl Line { + pub fn x(&self, horizontal: Horizontal) -> usize { + let f = match horizontal { + Horizontal::Left => min, + Horizontal::Right => max, + }; + f(self.start.x, self.end.x) + } + + pub fn y(&self, vertical: Vertical) -> usize { + let f = match vertical { + Vertical::Top => min, + Vertical::Bottom => max, + }; + f(self.start.y, self.end.y) + } +} diff --git a/libs/geometry/src/rectangle.rs b/libs/geometry/src/rectangle.rs new file mode 100644 index 0000000000..8b8429b099 --- /dev/null +++ b/libs/geometry/src/rectangle.rs @@ -0,0 +1,128 @@ +use core::cmp; + +use crate::{Containable, ConvexPolygon, Coordinates, Horizontal, Vertical}; + +/// A rectangle. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Rectangle { + /// The top left vertex of the rectangle. + pub coordinates: Coordinates, + width: usize, + height: usize, +} + +impl Rectangle { + pub const MAX: Self = Self::new(Coordinates::ORIGIN, usize::MAX, usize::MAX); + + /// Returns a rectangle with the top left vertex at `coordinates`. + /// + /// # Panics + /// + /// Panics if `width` is 0, or `height` is 0. + pub const fn new(coordinates: Coordinates, width: usize, height: usize) -> Self { + // assert!(width > 0, "rectangle must have width greater than 0"); + // assert!(height > 0, "rectangle must have height greater than 0"); + Self { + coordinates, + width, + height, + } + } + + /// Returns the wdith of the rectangle. + pub const fn width(&self) -> usize { + self.width + } + + /// Returns the height of the rectangle. + pub const fn height(&self) -> usize { + self.height + } + + /// Returns the furthest `x` coordinate in `direction`. + /// + /// If the rectangle has a width of zero, then the right coordinate will be + /// one less than the left coordinate. Note that this means a rectangle of + /// width zero, with x coordinate zero will have a right coordinate of + /// `usize::MAX`. + // TODO: I really don't like this underflow behaviour but I don't think there's + // a better solution. + pub const fn x(&self, direction: Horizontal) -> usize { + match direction { + Horizontal::Left => self.coordinates.x, + Horizontal::Right => self.coordinates.x + self.width - 1, + } + } + + /// Returns the furthest `y` coordinate in `direction`. + /// + /// If the rectangle has a height of zero, then the bottom coordinate will + /// be one less than the top coordinate. Note that this means a rectangle of + /// height zero, with y coordinate zero will have a bottom coordinate of + /// `usize::MAX`. + // TODO: I really don't like this underflow behaviour but I don't think there's + // a better solution. + pub const fn y(&self, direction: Vertical) -> usize { + match direction { + Vertical::Top => self.coordinates.y, + Vertical::Bottom => self.coordinates.y + self.height - 1, + } + } + + /// Returns the vertex specified by `vertical` and `horizontal`. + pub const fn vertex(&self, vertical: Vertical, horizontal: Horizontal) -> Coordinates { + Coordinates { + x: self.x(horizontal), + y: self.y(vertical), + } + } + + pub fn merge(&self, other: &Rectangle) -> Rectangle { + let left = cmp::min(self.coordinates.x, other.coordinates.x); + let top = cmp::min(self.coordinates.y, other.coordinates.y); + Rectangle { + coordinates: Coordinates { x: left, y: top }, + width: cmp::max( + self.x(Horizontal::Right) - left, + other.x(Horizontal::Right) - left, + ) + 1, + height: cmp::max( + self.y(Vertical::Bottom) - top, + other.y(Vertical::Bottom) - top, + ) + 1, + } + } + + /// Returns whether the rectangle fully contains `T`. + pub fn contains(&self, containable: T) -> bool + where + T: Containable, + { + for coordinates in containable.vertices() { + if coordinates.x < self.x(Horizontal::Left) + || coordinates.x > self.x(Horizontal::Right) + || coordinates.y < self.y(Vertical::Top) + || coordinates.y > self.y(Vertical::Bottom) + { + return false; + } + } + true + } +} + +unsafe impl ConvexPolygon for Rectangle {} + +impl Containable for Rectangle { + type I = core::array::IntoIter; + + fn vertices(&self) -> Self::I { + [ + self.vertex(Vertical::Top, Horizontal::Left), + self.vertex(Vertical::Top, Horizontal::Right), + self.vertex(Vertical::Bottom, Horizontal::Right), + self.vertex(Vertical::Bottom, Horizontal::Left), + ] + .into_iter() + } +} diff --git a/libs/mpmc_queue/src/lib.rs b/libs/mpmc_queue/src/lib.rs index 56d56377bb..b48a85405c 100644 --- a/libs/mpmc_queue/src/lib.rs +++ b/libs/mpmc_queue/src/lib.rs @@ -16,6 +16,7 @@ use core::{ use sync::{Mutex, MutexFlavor, MutexGuard}; /// A growable, first-in first-out, multi-producer, multi-consumer, queue. +#[derive(Debug, Default)] pub struct Queue where F: MutexFlavor, @@ -25,6 +26,7 @@ where len: AtomicUsize, } +#[derive(Debug, Default)] struct Pointers { head: Option>>, tail: Option>>, diff --git a/libs/sync/src/rw_lock.rs b/libs/sync/src/rw_lock.rs index 1998671413..ef053867a2 100644 --- a/libs/sync/src/rw_lock.rs +++ b/libs/sync/src/rw_lock.rs @@ -187,6 +187,7 @@ where /// RAII structure used to release the shared read access of a lock when /// dropped. +#[derive(Debug)] pub struct RwLockReadGuard<'a, T, F> where T: ?Sized, @@ -223,6 +224,7 @@ where /// RAII structure used to release the exclusive write access of a lock when /// dropped. +#[derive(Debug)] pub struct RwLockWriteGuard<'a, T, F> where T: ?Sized, diff --git a/rustfmt.toml b/rustfmt.toml index 9a37ca7d83..838196863c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,12 +1,6 @@ -ignore = [ - "/applications", - "/compiler_plugins", - "/kernel", - "/libs", - "/libtheseus", - "/old_crates", - "/ports", - "/theseus_features", - "/tlibc", - "/tools" -] +edition = "2021" +format_code_in_doc_comments = true +format_strings = true +wrap_comments = true +imports_granularity = "Crate" +group_imports = "StdExternalCrate"