mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-21 22:49:44 +02:00
Add the description of the chat-app exercise (#641)
* Adds a description of the async chat exercise * Simplifies the use of Error in chat-async * Links the solution to the async chat exercise * Removes the elevator exercise
This commit is contained in:
parent
8406697449
commit
83663daaa2
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -187,7 +187,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-websockets",
|
||||
]
|
||||
@ -1996,6 +1995,15 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[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 = "siphasher"
|
||||
version = "0.3.10"
|
||||
@ -2185,7 +2193,9 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
@ -2251,9 +2261,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-websockets"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d009a9a2a71bd44791363f42ba2dd7d47bf49aa61f7c43eabcd29a285e75a865"
|
||||
checksum = "8d1df20f31f428a351ec353699a82aad49360090e8f6320ac0dacf5def4f1573"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
|
@ -52,6 +52,7 @@ editable = true
|
||||
"exercises/day-4/link-checker.html" = "../concurrency/link-checker.html"
|
||||
"exercises/day-4/morning.html" = "../concurrency/morning.html"
|
||||
"exercises/day-4/solutions-morning.html" = "../concurrency/solutions-morning.html"
|
||||
"exercises/concurrency/elevator.html" = "chat-app.html"
|
||||
"generics/closures.html" = "../traits/closures.html"
|
||||
"generics/impl-trait.html" = "../traits/impl-trait.html"
|
||||
"generics/trait-bounds.html" = "../traits/trait-bounds.html"
|
||||
|
@ -292,7 +292,7 @@
|
||||
- [Async Traits](async/pitfalls/async-traits.md)
|
||||
- [Exercises](exercises/concurrency/afternoon.md)
|
||||
- [Dining Philosophers](exercises/concurrency/dining-philosophers-async.md)
|
||||
- [Elevator Operations](exercises/concurrency/elevator.md)
|
||||
- [Broadcast Chat Application](exercises/concurrency/chat-app.md)
|
||||
|
||||
|
||||
# Final Words
|
||||
|
@ -5,8 +5,8 @@ To practice your Async Rust skills, we have again two exercises for you:
|
||||
* Dining philosophers: we already saw this problem in the morning. This time
|
||||
you are going to implement it with Async Rust.
|
||||
|
||||
* The Elevator Problem: this is a larger project that allows you experiment
|
||||
with more advanced Async Rust features and some of its pitfalls!
|
||||
* A Broadcast Chat Application: this is a larger project that allows you
|
||||
experiment with more advanced Async Rust features.
|
||||
|
||||
<details>
|
||||
|
||||
|
109
src/exercises/concurrency/chat-app.md
Normal file
109
src/exercises/concurrency/chat-app.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Broadcast Chat Application
|
||||
|
||||
In this exercise, we want to use our new knowledge to implement a broadcast
|
||||
chat application. We have a chat server that the clients connect to and publish
|
||||
their messages. The client reads user messages from the standard input, and
|
||||
sends them to the server. The chat server broadcasts each message that it
|
||||
receives to all the clients.
|
||||
|
||||
For this, we use [a broadcast channel][1] on the server, and
|
||||
[`tokio_websockets`][2] for the communication between the client and the
|
||||
server.
|
||||
|
||||
Create a new Cargo project and add the following dependencies:
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
<!-- File Cargo.toml -->
|
||||
|
||||
```toml
|
||||
{{#include chat-async/Cargo.toml}}
|
||||
```
|
||||
|
||||
## The required APIs
|
||||
You are going to need the following functions from `tokio` and
|
||||
[`tokio_websockets`][2]. Spend a few minutes to familiarize yourself with the
|
||||
API.
|
||||
|
||||
- [WebsocketStream::next()][3]: for asynchronously reading messages from a
|
||||
Websocket Stream.
|
||||
- [SinkExt::send()][4] implemented by `WebsocketStream`: for asynchronously
|
||||
sending messages on a Websocket Stream.
|
||||
- [BufReader::read_line()][5]: for asynchronously reading user messages
|
||||
from the standard input.
|
||||
- [Sender::subscribe()][6]: for subscribing to a broadcast channel.
|
||||
|
||||
|
||||
## Two binaries
|
||||
|
||||
Normally in a Cargo project, you can have only one binary, and one
|
||||
`src/main.rs` file. In this project, we need two binaries. One for the client,
|
||||
and one for the server. You could potentially make them two separate Cargo
|
||||
projects, but we are going to put them in a single Cargo project with two
|
||||
binaries. For this to work, the client and the server code should go under
|
||||
`src/bin` (see the [documentation][7]).
|
||||
|
||||
Copy the following server and client code into `src/bin/server.rs` and
|
||||
`src/bin/client.rs`, respectively. Your task is to complete these files as
|
||||
described below.
|
||||
|
||||
`src/bin/server.rs`:
|
||||
|
||||
<!-- File src/bin/server.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include chat-async/src/bin/server.rs:setup}}
|
||||
|
||||
{{#include chat-async/src/bin/server.rs:handle_connection}}
|
||||
|
||||
// TODO: For a hint, see the description of the task below.
|
||||
|
||||
{{#include chat-async/src/bin/server.rs:main}}
|
||||
```
|
||||
|
||||
`src/bin/client.rs`:
|
||||
|
||||
<!-- File src/bin/client.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include chat-async/src/bin/client.rs:setup}}
|
||||
|
||||
// TODO: For a hint, see the description of the task below.
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Running the binaries
|
||||
Run the server with:
|
||||
|
||||
```shell
|
||||
$ cargo run --bin server
|
||||
```
|
||||
|
||||
and the client with:
|
||||
|
||||
```shell
|
||||
$ cargo run --bin client
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
* Implement the `handle_connection` function in `src/bin/server.rs`.
|
||||
* Hint: Use `tokio::select!` for concurrently performing two tasks in a
|
||||
continuous loop. One task receives messages from the client and broadcasts
|
||||
them. The other sends messages received by the server to the client.
|
||||
* Complete the main function in `src/bin/client.rs`.
|
||||
* Hint: As before, use `tokio::select!` in a continuous loop for concurrently
|
||||
performing two tasks: (1) reading user messages from standard input and
|
||||
sending them to the server, and (2) receiving messages from the server, and
|
||||
displaying them for the user.
|
||||
* Optional: Once you are done, change the code to broadcast messages to all
|
||||
clients, but the sender of the message.
|
||||
|
||||
[1]: https://docs.rs/tokio/latest/tokio/sync/broadcast/fn.channel.html
|
||||
[2]: https://docs.rs/tokio-websockets/0.3.2/tokio_websockets/
|
||||
[3]: https://docs.rs/tokio-websockets/0.3.2/tokio_websockets/proto/struct.WebsocketStream.html#method.next
|
||||
[4]: https://docs.rs/futures-util/0.3.28/futures_util/sink/trait.SinkExt.html#method.send
|
||||
[5]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line
|
||||
[6]: https://docs.rs/tokio/latest/tokio/sync/broadcast/struct.Sender.html#method.subscribe
|
||||
[7]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
|
@ -6,6 +6,5 @@ edition = "2021"
|
||||
[dependencies]
|
||||
futures-util = "0.3.28"
|
||||
http = "0.2.9"
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.28.1", features = ["net", "macros", "time", "rt", "rt-multi-thread", "io-std", "io-util"] }
|
||||
tokio-websockets = "0.3.0"
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
tokio-websockets = "0.3.2"
|
||||
|
@ -1,3 +1,18 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ANCHOR: setup
|
||||
use futures_util::SinkExt;
|
||||
use http::Uri;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
@ -12,6 +27,8 @@ async fn main() -> Result<(), tokio_websockets::Error> {
|
||||
let stdin = tokio::io::stdin();
|
||||
let mut stdin = BufReader::new(stdin);
|
||||
|
||||
// ANCHOR_END: setup
|
||||
// Continuous loop for concurrently sending and receiving messages.
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
tokio::select! {
|
||||
|
@ -1,46 +1,42 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ANCHOR: setup
|
||||
use futures_util::sink::SinkExt;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use thiserror::Error;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::broadcast::error::{RecvError, SendError};
|
||||
use tokio::sync::broadcast::{channel, Sender};
|
||||
use tokio_websockets::{Message, ServerBuilder, WebsocketStream};
|
||||
// ANCHOR_END: setup
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum ServerError {
|
||||
#[error("websocket error: {0}")]
|
||||
Websocket(String),
|
||||
#[error("io error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
#[error("broadcast channel SendError: {0}")]
|
||||
SendError(#[from] SendError<String>),
|
||||
#[error("broadcast channel RecvError: {0}")]
|
||||
RecvError(#[from] RecvError),
|
||||
}
|
||||
|
||||
// tokio_websockets Error types do not implement std::error::Error, so we make do by just capturing
|
||||
// the debug format for the error.
|
||||
impl From<tokio_websockets::Error> for ServerError {
|
||||
fn from(err: tokio_websockets::Error) -> Self {
|
||||
ServerError::Websocket(format!("{:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio_websockets::proto::ProtocolError> for ServerError {
|
||||
fn from(err: tokio_websockets::proto::ProtocolError) -> Self {
|
||||
ServerError::Websocket(format!("{:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: handle_connection
|
||||
async fn handle_connection(
|
||||
addr: SocketAddr,
|
||||
mut ws_stream: WebsocketStream<TcpStream>,
|
||||
bcast_tx: Sender<String>,
|
||||
) -> Result<(), ServerError> {
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
// ANCHOR_END: handle_connection
|
||||
|
||||
ws_stream
|
||||
.send(Message::text("Welcome to chat! Type a message".into()))
|
||||
.await?;
|
||||
let mut bcast_rx = bcast_tx.subscribe();
|
||||
|
||||
// A continuous loop for concurrently performing two tasks: (1) receiving
|
||||
// messages from `ws_stream` and broadcasting them, and (2) receiving
|
||||
// messages on `bcast_rx` and sending them to the client.
|
||||
loop {
|
||||
tokio::select! {
|
||||
incoming = ws_stream.next() => {
|
||||
@ -59,10 +55,11 @@ async fn handle_connection(
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR: main
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ServerError> {
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let (bcast_tx, _) = channel(16);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:2000").await?;
|
||||
@ -80,3 +77,4 @@ async fn main() -> Result<(), ServerError> {
|
||||
});
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: main
|
||||
|
@ -1,91 +0,0 @@
|
||||
# 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`:
|
||||
|
||||
<!-- File src/main.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include elevator/src/main.rs}}
|
||||
```
|
||||
|
||||
`src/building.rs`:
|
||||
|
||||
<!-- File src/building.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include elevator/src/building.rs}}
|
||||
```
|
||||
|
||||
`src/driver.rs`:
|
||||
|
||||
<!-- File src/driver.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include elevator/src/driver.rs}}
|
||||
```
|
||||
|
||||
`src/controller.rs`:
|
||||
|
||||
<!-- File src/controller.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include elevator/src/controller.rs}}
|
||||
```
|
||||
|
||||
`Cargo.toml` (you shouldn't need to change this):
|
||||
|
||||
<!-- File Cargo.toml -->
|
||||
|
||||
```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.
|
318
src/exercises/concurrency/elevator/Cargo.lock
generated
318
src/exercises/concurrency/elevator/Cargo.lock
generated
@ -1,318 +0,0 @@
|
||||
# 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"
|
@ -1,9 +0,0 @@
|
||||
[workspace]
|
||||
|
||||
[package]
|
||||
name = "elevator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.26.0", features = ["full"] }
|
@ -1,234 +0,0 @@
|
||||
//! 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<Passenger>,
|
||||
}
|
||||
|
||||
/// 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<FloorId>,
|
||||
/// Passengers currently on the elevator.
|
||||
passengers: Vec<Passenger>,
|
||||
/// 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<Floor>,
|
||||
elevators: Vec<Elevator>,
|
||||
}
|
||||
|
||||
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<BuildingEvent>,
|
||||
mpsc::Sender<BuildingCommand>,
|
||||
mpsc::Sender<DriverCommand>,
|
||||
) {
|
||||
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<BuildingEvent>,
|
||||
mut building_cmd_rx: mpsc::Receiver<BuildingCommand>,
|
||||
mut driver_cmd_rx: mpsc::Receiver<DriverCommand>,
|
||||
) {
|
||||
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<BuildingEvent>) {
|
||||
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<BuildingEvent>,
|
||||
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<BuildingEvent>,
|
||||
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<Passenger>, Vec<Passenger>) = 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
//! 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<BuildingEvent>,
|
||||
building_cmd_tx: mpsc::Sender<BuildingCommand>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//! 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<BuildingEvent>,
|
||||
driver_cmd_tx: mpsc::Sender<DriverCommand>,
|
||||
) {
|
||||
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();
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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<BuildingEvent>) {
|
||||
while let Ok(evt) = events_rx.recv().await {
|
||||
println!("BuildingEvent::{:?}", evt);
|
||||
}
|
||||
}
|
@ -8,3 +8,19 @@
|
||||
{{#include dining-philosophers-async.rs}}
|
||||
```
|
||||
|
||||
## Broadcast Chat Application
|
||||
|
||||
([back to exercise](chat-app.md))
|
||||
|
||||
`src/bin/server.rs`:
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include chat-async/src/bin/server.rs}}
|
||||
```
|
||||
|
||||
`src/bin/client.rs`:
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include chat-async/src/bin/client.rs}}
|
||||
```
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user