From 6c97e1a7be0c537df16144db370a26ffa42aeb4f Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 13 Apr 2023 10:26:57 -0400 Subject: [PATCH] Add a new async exercise: elevator simulation. (#557) --- src/SUMMARY.md | 4 + src/exercises/day-4/async-elevator.md | 1 + src/exercises/day-4/elevator.md | 91 +++++ src/exercises/day-4/elevator/Cargo.lock | 318 ++++++++++++++++++ src/exercises/day-4/elevator/Cargo.toml | 9 + src/exercises/day-4/elevator/src/building.rs | 234 +++++++++++++ .../day-4/elevator/src/controller.rs | 28 ++ src/exercises/day-4/elevator/src/driver.rs | 35 ++ src/exercises/day-4/elevator/src/main.rs | 23 ++ 9 files changed, 743 insertions(+) create mode 100644 src/exercises/day-4/async-elevator.md create mode 100644 src/exercises/day-4/elevator.md create mode 100644 src/exercises/day-4/elevator/Cargo.lock create mode 100644 src/exercises/day-4/elevator/Cargo.toml create mode 100644 src/exercises/day-4/elevator/src/building.rs create mode 100644 src/exercises/day-4/elevator/src/controller.rs create mode 100644 src/exercises/day-4/elevator/src/driver.rs create mode 100644 src/exercises/day-4/elevator/src/main.rs diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e3c8724e..72346bf8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -226,6 +226,10 @@ - [With Java](android/interoperability/java.md) - [Exercises](exercises/day-4/android.md) +# Async (temp until issue181 is closed) + +- [Exercises](exercises/day-4/elevator.md) + # Final Words - [Thanks!](thanks.md) diff --git a/src/exercises/day-4/async-elevator.md b/src/exercises/day-4/async-elevator.md new file mode 100644 index 00000000..eba3257d --- /dev/null +++ b/src/exercises/day-4/async-elevator.md @@ -0,0 +1 @@ +# Exercises diff --git a/src/exercises/day-4/elevator.md b/src/exercises/day-4/elevator.md new file mode 100644 index 00000000..88b486a5 --- /dev/null +++ b/src/exercises/day-4/elevator.md @@ -0,0 +1,91 @@ +# Elevator Operation + +Elevators seem simple. You press a button, doors open, you wait, and you're at +the floor you requested. But implementing an elevator controller is surprisingly +difficult! This exercise involves building a simple elevator control that +operates in a simple simulator. + +The overall design of this elevator uses the actor pattern: you will implement a +controller task that communicates with other components of the elevator system +by sending and receiving messages. + +## Getting Started + +Download the [exercise template](../../comprehensive-rust-exercises.zip) and look in the `elevator` +directory for the following files. + +`src/main.rs`: + + + +```rust,compile_fail +{{#include elevator/src/main.rs}} +``` + +`src/building.rs`: + + + +```rust,compile_fail +{{#include elevator/src/building.rs}} +``` + +`src/driver.rs`: + + + +```rust,compile_fail +{{#include elevator/src/driver.rs}} +``` + +`src/controller.rs`: + + + +```rust,compile_fail +{{#include elevator/src/controller.rs}} +``` + +`Cargo.toml` (you shouldn't need to change this): + + + +```toml +{{#include elevator/Cargo.toml}} +``` + +Use `cargo run` to run the elevator simulation. + +## Exercises + +Begin by implementing a controller that can transport the passengers provided by +the simple driver. There is only one elevator, and passengers always go from +floor 0 to floor 2, one-by-one. + +Once you have this done, make the problem more complex. Suggested tasks: + + * Make the driver more complex, with passengers arriving at random floors with + random destinations at random times. + + * Create a building with more than one elevator, and adjust the controller to + handle this efficiently. + + * Add additional events and metadata to analyze your controller's efficiency. + What is the distribution of wait time for passengers? Is the result fair? + + * Modify the building to support a maximum passenger capacity for each + elevator, and modify the controller to take this information into account. + + * Update the driver to simulate business traffic, with lots of passengers going + up from the ground floor at the same time, and those passengers returning to + the ground floor some time later. Can your controller adjust to these + circumstances? + + * Modify the building to support "destination dispatch", where passengers + signal their destination floor in the elevator lobby, before boarding the + elevator. + + * If you are taking the course with other students, trade controllers or + drivers with another student to see how robust your design is. + + * Build a textual or graphical display of the elevators as they run. diff --git a/src/exercises/day-4/elevator/Cargo.lock b/src/exercises/day-4/elevator/Cargo.lock new file mode 100644 index 00000000..a03c0f39 --- /dev/null +++ b/src/exercises/day-4/elevator/Cargo.lock @@ -0,0 +1,318 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "elevator" +version = "0.1.0" +dependencies = [ + "tokio", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/src/exercises/day-4/elevator/Cargo.toml b/src/exercises/day-4/elevator/Cargo.toml new file mode 100644 index 00000000..f2c83877 --- /dev/null +++ b/src/exercises/day-4/elevator/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "elevator" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1.26.0", features = ["full"] } diff --git a/src/exercises/day-4/elevator/src/building.rs b/src/exercises/day-4/elevator/src/building.rs new file mode 100644 index 00000000..3aca0321 --- /dev/null +++ b/src/exercises/day-4/elevator/src/building.rs @@ -0,0 +1,234 @@ +//! The building simulates floors and elevators. + +use tokio::sync::{broadcast, mpsc}; +use tokio::task; +use tokio::time; + +#[derive(Debug, Clone)] +pub enum Direction { + Up, + Down, +} + +/// A passenger is a person with a destination floor in mind. +#[derive(Debug)] +struct Passenger { + destination: FloorId, +} + +/// FloorId identifies a floor. These are zero-based integers. +pub type FloorId = usize; + +/// Floor represents the current status of a floor in the building. +#[derive(Default, Debug)] +struct Floor { + passengers: Vec, +} + +/// ElevatorId identifies an elevator in the building. These are zero-based integers. +pub type ElevatorId = usize; + +/// Elevator represents the current status of an elevator in the building. +#[derive(Default, Debug)] +struct Elevator { + /// Floor the elevator is currently on. In the simulation the elevator + /// transports instantaneously from one floor to the next in a single + /// simulation tick. + position: FloorId, + /// Destination floor for the elevator, if any. This can change at any time. + destination: Option, + /// Passengers currently on the elevator. + passengers: Vec, + /// True if the elevator is stopped with the doors open. The elevator + /// will not move with the doors open, but they will close at the next + /// tick of the simulation. + doors_open: bool, +} + +/// A BuildingEvent is an event that occurs in the building. +#[derive(Debug, Clone)] +pub enum BuildingEvent { + /// A passenger has pressed a floor button in the elevator. + FloorButtonPressed(ElevatorId, FloorId), + /// A passenger on the given floor has pressed the call button. + CallButtonPressed(FloorId, Direction), + /// The elevator has arrived at the given floor. If this is the + /// elevator's destination, then it will stop open its doors. + AtFloor(ElevatorId, FloorId), + /// A passenger has been delivered to their desired floor. + PassengerDelivered(FloorId), +} + +/// A BuildingCommand tells the building what to do. +#[derive(Debug)] +pub enum BuildingCommand { + /// Set the elevator's destination. The elevator will close its doors + /// if necessary and then begin moving toward this floor. + GoToFloor(ElevatorId, FloorId), +} + +/// A DriverCommand is a message from the driver to change the state of +/// the building. +#[derive(Debug)] +pub enum DriverCommand { + /// A passenger has arrived and is waiting for an elevator. The passenger will automatically + /// press the relevant call button, board the elevator when it arrives, press their floor + /// button, and depart when the doors open on their destination floor. + PassengerArrived { at: FloorId, destination: FloorId }, + + /// Halt all activity in the building and end the building task. + Halt, +} + +/// Building manages the current status of the building. +#[derive(Debug)] +pub struct Building { + floors: Vec, + elevators: Vec, +} + +impl Building { + pub fn new(num_floors: usize, num_elevators: usize) -> Self { + let mut floors = vec![]; + for _ in 0..num_floors { + floors.push(Floor::default()); + } + let mut elevators = vec![]; + for _ in 0..num_elevators { + elevators.push(Elevator::default()); + } + Self { floors, elevators } + } + + /// Start the building. The resulting channels are used to communicate + /// with the building + pub fn start( + self, + ) -> ( + task::JoinHandle<()>, + broadcast::Receiver, + mpsc::Sender, + mpsc::Sender, + ) { + let (events_tx, events_rx) = broadcast::channel(10); + let (building_cmd_tx, building_cmd_rx) = mpsc::channel(10); + let (driver_cmd_tx, driver_cmd_rx) = mpsc::channel(10); + let task = tokio::spawn(self.run(events_tx, building_cmd_rx, driver_cmd_rx)); + (task, events_rx, building_cmd_tx, driver_cmd_tx) + } + + async fn run( + mut self, + events_tx: broadcast::Sender, + mut building_cmd_rx: mpsc::Receiver, + mut driver_cmd_rx: mpsc::Receiver, + ) { + let mut ticker = time::interval(time::Duration::from_millis(100)); + loop { + tokio::select! { + Some(BuildingCommand::GoToFloor(el, fl)) = building_cmd_rx.recv() => { + self.elevators[el].destination = Some(fl); + } + Some(cmd) = driver_cmd_rx.recv() => { + match cmd { + DriverCommand::PassengerArrived{at, destination} => { + self.new_passenger(&events_tx, at, destination).await; + } + DriverCommand::Halt => return, + } + } + _ = ticker.tick() => self.move_elevators(&events_tx).await + } + } + } + + /// Move the elevators toward their destinations. + async fn move_elevators(&mut self, events_tx: &broadcast::Sender) { + for el in 0..self.elevators.len() { + let elevator = &mut self.elevators[el]; + + // If the elevator's doors are open, close them and wait for the next tick. + if elevator.doors_open { + elevator.doors_open = false; + continue; + } + + // If the elevator has somewhere to go, move toward it. + if let Some(dest) = elevator.destination { + if dest > elevator.position { + elevator.position += 1; + } + if dest < elevator.position { + elevator.position -= 1; + } + events_tx + .send(BuildingEvent::AtFloor(el, elevator.position)) + .unwrap(); + + // If the elevator has reached its destination, open + // the doors and let passengers get on and off. + if elevator.position == dest { + elevator.destination = None; + elevator.doors_open = true; + self.exchange_passengers(&events_tx, el).await; + } + } + } + } + + /// Handle a new passenger arriving at the given floor. + async fn new_passenger( + &mut self, + events_tx: &broadcast::Sender, + at: FloorId, + destination: FloorId, + ) { + println!("Passenger arrived at {} going to {}", at, destination); + if at == destination { + events_tx + .send(BuildingEvent::PassengerDelivered(destination)) + .unwrap(); + return; + } + + self.floors[at].passengers.push(Passenger { destination }); + let dir = if at < destination { + Direction::Up + } else { + Direction::Down + }; + events_tx + .send(BuildingEvent::CallButtonPressed(at, dir)) + .unwrap(); + } + + /// The doors for the given elevator are open, so take on and discharge passengers. + async fn exchange_passengers( + &mut self, + events_tx: &broadcast::Sender, + el: ElevatorId, + ) { + let elevator = &mut self.elevators[el]; + let fl = elevator.position; + + // Handle passengers leaving the elevator at their floor. + let (this_floor, other_floors): (Vec, Vec) = elevator + .passengers + .drain(..) + .partition(|px| px.destination == fl); + for px in this_floor { + events_tx + .send(BuildingEvent::PassengerDelivered(px.destination)) + .unwrap(); + } + elevator.passengers = other_floors; + + // Handle passengers entering the elevator. + for px in self.floors[fl].passengers.drain(..) { + events_tx + .send(BuildingEvent::FloorButtonPressed(el, px.destination)) + .unwrap(); + elevator.passengers.push(px); + } + } +} diff --git a/src/exercises/day-4/elevator/src/controller.rs b/src/exercises/day-4/elevator/src/controller.rs new file mode 100644 index 00000000..855fa223 --- /dev/null +++ b/src/exercises/day-4/elevator/src/controller.rs @@ -0,0 +1,28 @@ +//! The controller directs the elevators to operate so that passengers +//! get to their destinations. + +use crate::building::{BuildingCommand, BuildingEvent}; +use tokio::sync::{broadcast, mpsc}; + +pub async fn controller( + mut events_rx: broadcast::Receiver, + building_cmd_tx: mpsc::Sender, +) { + while let Ok(evt) = events_rx.recv().await { + match evt { + BuildingEvent::CallButtonPressed(at, _) => { + building_cmd_tx + .send(BuildingCommand::GoToFloor(0, at)) + .await + .unwrap(); + } + BuildingEvent::FloorButtonPressed(_, destination) => { + building_cmd_tx + .send(BuildingCommand::GoToFloor(0, destination)) + .await + .unwrap(); + } + _ => {} + } + } +} diff --git a/src/exercises/day-4/elevator/src/driver.rs b/src/exercises/day-4/elevator/src/driver.rs new file mode 100644 index 00000000..e9e07ad2 --- /dev/null +++ b/src/exercises/day-4/elevator/src/driver.rs @@ -0,0 +1,35 @@ +//! The driver controls when and where passengers arrive. + +use crate::building::{Building, BuildingEvent, DriverCommand}; +use tokio::sync::{broadcast, mpsc}; + +/// Create a new building to be driven by this driver. +pub fn make_building() -> Building { + Building::new(3, 1) +} + +/// Simulate people arriving at the ground floor and going to the first floor, one by one. +pub async fn driver( + mut events_rx: broadcast::Receiver, + driver_cmd_tx: mpsc::Sender, +) { + for _ in 0..3 { + // A passenger has arrived.. + driver_cmd_tx + .send(DriverCommand::PassengerArrived { + at: 0, + destination: 2, + }) + .await + .unwrap(); + + // Wait until they are delivered.. + while let Ok(evt) = events_rx.recv().await { + if let BuildingEvent::PassengerDelivered(_) = evt { + break; + } + } + } + + driver_cmd_tx.send(DriverCommand::Halt).await.unwrap(); +} diff --git a/src/exercises/day-4/elevator/src/main.rs b/src/exercises/day-4/elevator/src/main.rs new file mode 100644 index 00000000..d7162580 --- /dev/null +++ b/src/exercises/day-4/elevator/src/main.rs @@ -0,0 +1,23 @@ +use building::BuildingEvent; +use tokio::sync::broadcast; + +mod building; +mod controller; +mod driver; + +#[tokio::main] +async fn main() { + let building = driver::make_building(); + let (building_task, events_rx, building_cmd_tx, driver_cmd_tx) = building.start(); + + tokio::spawn(print_events(events_rx.resubscribe())); + tokio::spawn(driver::driver(events_rx.resubscribe(), driver_cmd_tx)); + tokio::spawn(controller::controller(events_rx, building_cmd_tx)); + building_task.await.unwrap(); +} + +async fn print_events(mut events_rx: broadcast::Receiver) { + while let Ok(evt) = events_rx.recv().await { + println!("BuildingEvent::{:?}", evt); + } +}