Challenge finished.
This commit is contained in:
parent
b4735b5807
commit
a9891242d1
261
Cargo.lock
generated
Normal file
261
Cargo.lock
generated
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.172"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "satellite"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
@ -1,13 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "satellite"
|
name = "satellite"
|
||||||
version = "0.0.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Rust challenge"
|
description = "Rust Challenge"
|
||||||
repository = "/myrddin/satellite"
|
repository = "/myrddin/satellite"
|
||||||
authors = ["CyberMages LLC <Software@CyberMagesLLC.com>", "Jason Travis Smith <Myrddin@CyberMages.tech>"]
|
authors = ["Jason Travis Smith <Myrddin@CyberMages.tech>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license-file = "LICENSE.md"
|
license-file = "LICENSE.md"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread", "io-std", "io-util", "time", "signal", "sync"] }
|
||||||
|
89
README.md
89
README.md
@ -1,3 +1,88 @@
|
|||||||
# satellite
|
# Satellite Propulsion System
|
||||||
|
|
||||||
Rust challenge
|
## Approach
|
||||||
|
This program will be using standard I/O for getting data.
|
||||||
|
|
||||||
|
Tackling this problem requires the use of asynchronous code. This is because
|
||||||
|
the user will constantly be giving input on the command line yet time needs
|
||||||
|
to keep moving forward to fire any propulsion commands.
|
||||||
|
|
||||||
|
To handle this the program uses two separate tasks. One for handling
|
||||||
|
the input and one for running the commands given. The input, coming from
|
||||||
|
standard I/O, will run on a separate thread because it will use blocking
|
||||||
|
functions. The command processing can be handled in an asynchronous
|
||||||
|
task since it can use completely asynchronous functions.
|
||||||
|
|
||||||
|
The two tasks are linked with message passing channels. One channel is for
|
||||||
|
passing commands from the input task to the command processing task. The
|
||||||
|
other channel is a watch channel on a boolean value that the input task
|
||||||
|
will flip from true to false. This will designate that the program should
|
||||||
|
no longer be running and needs to terminate.
|
||||||
|
|
||||||
|
The input reading task is the main driver of the program. It will loop until
|
||||||
|
the program is told to stop running. This is done by the user typing "exit",
|
||||||
|
"stop", or "quit". It will then trigger termination through the watch
|
||||||
|
channel. If the user types a whole number between 0 and i32::MAX, it
|
||||||
|
will create and send a propulsion command with a delay time of the given
|
||||||
|
number, as seconds, and send it over the command channel. If the user types
|
||||||
|
a '-1', it will create a cancel command and send it over the command
|
||||||
|
channel. Cancel is handled as a separate command because it is a different
|
||||||
|
action and it is clearer to handle it separately.
|
||||||
|
|
||||||
|
The command processing task will loop until the termination channel tells
|
||||||
|
it to end. It will handle two async jobs. The first is processing
|
||||||
|
commands from the command channel, either creating a propulsion interval
|
||||||
|
countdown or clearing any that are already running. The second job is
|
||||||
|
to run the current propulsion firing interval until it completes one firing.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
### Build
|
||||||
|
To build the program you must have rustc and cargo installed.
|
||||||
|
The instructions to do so can be found
|
||||||
|
[here](https://www.rust-lang.org/tools/install).
|
||||||
|
|
||||||
|
Then to build the program navigate to the directory and run the
|
||||||
|
following command.
|
||||||
|
```
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to include debugging symbols then you can run the
|
||||||
|
following command.
|
||||||
|
```
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will pull all the required dependencies from crates.io.
|
||||||
|
Below is the list of dependencies; this can be verified in Cargo.toml.
|
||||||
|
* tokio
|
||||||
|
|
||||||
|
|
||||||
|
### Run
|
||||||
|
To run the program type the following command.
|
||||||
|
```
|
||||||
|
cargo run --release
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to run a version with all the debugging symbols then run the
|
||||||
|
following command.
|
||||||
|
```
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the currently built version from the 'target' subdirectory.
|
||||||
|
Debug builds can be found in target/debug.
|
||||||
|
Release builds can be found in target/release.
|
||||||
|
The executable's name is satellite.
|
||||||
|
|
||||||
|
### Operation
|
||||||
|
Once the program is running it will expect input commands from the user.
|
||||||
|
The commands can either be:
|
||||||
|
* A whole number - To specify the amount of delay, in seconds, before the
|
||||||
|
next time the satellite will fire its engine.
|
||||||
|
* The number '-1' - To specify that all current propulsion commands should
|
||||||
|
be canceled.
|
||||||
|
* The any one of the case sensitive words
|
||||||
|
"quit", "stop", or "exit" - To specify that the program should terminate.
|
||||||
|
285
src/main.rs
285
src/main.rs
@ -1,20 +1,283 @@
|
|||||||
//! Rust challenge
|
//! Rust Challenge
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::sync::{mpsc, watch};
|
||||||
|
use tokio::time::{Duration, Interval};
|
||||||
|
|
||||||
mod project;
|
/// Just an easier to read type.
|
||||||
|
type IoResult<T> = Result<T, std::io::Error>;
|
||||||
|
|
||||||
|
/// The commands available to send to the Satellite.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
/// Print the version of the project.
|
pub enum Command
|
||||||
fn print_version()
|
|
||||||
{
|
{
|
||||||
println!("{} v{}", project::get_name(), project::get_version());
|
/// Fire the engine in T minus x seconds!
|
||||||
|
Propulsion
|
||||||
|
{
|
||||||
|
/// The amount of seconds to wait before we fire the engines.
|
||||||
|
delay: Duration
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Cancel any waiting commands.
|
||||||
|
Cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state of the program.
|
||||||
/// The usual starting point of your project.
|
#[derive(Clone, Copy)]
|
||||||
fn main()
|
pub enum ProgramState
|
||||||
{
|
{
|
||||||
print_version();
|
/// The program is currently running.
|
||||||
|
Running,
|
||||||
|
|
||||||
|
/// The program has switched to shutting down.
|
||||||
|
Terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads input from the user until the termination signal is
|
||||||
|
/// received. It will send command messages every time it reads valid input.
|
||||||
|
fn read_user_input(command_sender: mpsc::Sender<Command>,
|
||||||
|
term_sender: watch::Sender<ProgramState>)
|
||||||
|
-> IoResult<()>
|
||||||
|
{
|
||||||
|
let mut running: bool = true;
|
||||||
|
let mut buffer = String::new();
|
||||||
|
|
||||||
|
// Loop and read the user input.
|
||||||
|
while running
|
||||||
|
{
|
||||||
|
// Clearing the last read line and read a new one.
|
||||||
|
buffer.clear();
|
||||||
|
std::io::stdin().read_line(&mut buffer)?;
|
||||||
|
|
||||||
|
let input = buffer.trim();
|
||||||
|
|
||||||
|
match input
|
||||||
|
{
|
||||||
|
// A termination method wasn't specified in the document, so
|
||||||
|
// this will terminate when either stop, exit, or quit is entered.
|
||||||
|
"exit" | "stop" | "quit" =>
|
||||||
|
{
|
||||||
|
println!("Exiting program.");
|
||||||
|
running = false;
|
||||||
|
term_sender.send_replace(ProgramState::Terminated);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ =>
|
||||||
|
{
|
||||||
|
// Check to see if we were given a number.
|
||||||
|
match input.parse::<i32>()
|
||||||
|
{
|
||||||
|
Ok(seconds) =>
|
||||||
|
{
|
||||||
|
// Here we are just handling the different possible numbers
|
||||||
|
// passed in. -1 cancels commands, less than -1 is ignored
|
||||||
|
// with a message, and greated than -1 is turned into a
|
||||||
|
// propulsion command.
|
||||||
|
if seconds == -1
|
||||||
|
{
|
||||||
|
match command_sender.blocking_send(Command::Cancel)
|
||||||
|
{
|
||||||
|
Ok(_) =>
|
||||||
|
{}
|
||||||
|
Err(e) => eprintln!("Failed to send command: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if seconds < -1
|
||||||
|
{
|
||||||
|
println!("All propulsion delay times are given in \
|
||||||
|
seconds within the range of [{}, {}]",
|
||||||
|
0,
|
||||||
|
i32::MAX);
|
||||||
|
println!("A value of -1 will cancel any current \
|
||||||
|
propulsion commands.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Time to create the Propulsion command.
|
||||||
|
let delay_secs: u64 = seconds as u64;
|
||||||
|
let delay_duration: Duration =
|
||||||
|
Duration::from_secs(delay_secs);
|
||||||
|
match command_sender.try_send(Command::Propulsion {
|
||||||
|
delay: delay_duration,
|
||||||
|
}) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => eprintln!("Failed to send command: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) =>
|
||||||
|
{
|
||||||
|
println!("Unable to parse the given input into seconds.");
|
||||||
|
println!("{:?}\n", e);
|
||||||
|
println!("Please specify only the desired whole seconds \
|
||||||
|
until the time to fire the engines.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if there is an interval that needs to be run or if
|
||||||
|
/// there is nothing to really do.
|
||||||
|
///
|
||||||
|
/// This was the trickiest part due to the syntax.
|
||||||
|
fn maybe_tick<'a>(interval: Option<&'a mut Interval>)
|
||||||
|
-> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
|
||||||
|
{
|
||||||
|
match interval
|
||||||
|
{
|
||||||
|
Some(interval) =>
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
|
interval.tick().await;
|
||||||
|
()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
None => Box::pin(std::future::pending())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The command task.
|
||||||
|
///
|
||||||
|
/// Processes the commands sent and fire the engine if a given
|
||||||
|
/// propulsion command completes.
|
||||||
|
async fn process_commands(mut command_receiver: mpsc::Receiver<Command>,
|
||||||
|
mut term_receiver: watch::Receiver<ProgramState>)
|
||||||
|
-> IoResult<()>
|
||||||
|
{
|
||||||
|
let mut stdout = tokio::io::stdout();
|
||||||
|
let mut propulsion_interval: Option<Interval> = None;
|
||||||
|
let mut running: bool = true;
|
||||||
|
|
||||||
|
while running
|
||||||
|
{
|
||||||
|
tokio::select! {
|
||||||
|
// Process commands.
|
||||||
|
Some(command) = command_receiver.recv() =>
|
||||||
|
{
|
||||||
|
match command
|
||||||
|
{
|
||||||
|
Command::Cancel =>
|
||||||
|
{
|
||||||
|
// Dump any interval that is running.
|
||||||
|
propulsion_interval = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Propulsion { delay } =>
|
||||||
|
{
|
||||||
|
// Overwrite any interval with this great new interval.
|
||||||
|
propulsion_interval = Some(tokio::time::interval(delay));
|
||||||
|
|
||||||
|
// Skip the first immediate tick.
|
||||||
|
if let Some(interval) = propulsion_interval.as_mut()
|
||||||
|
{
|
||||||
|
interval.tick().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the interval timer if there is one.
|
||||||
|
_ = maybe_tick(propulsion_interval.as_mut()) =>
|
||||||
|
{
|
||||||
|
stdout.write_all(b"firing now!\n").await?;
|
||||||
|
stdout.flush().await?;
|
||||||
|
|
||||||
|
// Clear the command out since it is done.
|
||||||
|
propulsion_interval = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for termination.
|
||||||
|
_ = term_receiver.changed() =>
|
||||||
|
{
|
||||||
|
running = match *term_receiver.borrow_and_update()
|
||||||
|
{
|
||||||
|
ProgramState::Running => { true }
|
||||||
|
ProgramState::Terminated => { false }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Program entry point.
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> IoResult<()>
|
||||||
|
{
|
||||||
|
// The channel that will be used to send satellite commands between tasks.
|
||||||
|
let (command_sender, command_receiver) = mpsc::channel::<Command>(100);
|
||||||
|
|
||||||
|
// The channel that will be used to signal termination of the program.
|
||||||
|
//
|
||||||
|
// This could be done as a satellite command since the program is driven by
|
||||||
|
// user interaction and it all happens from the input_task, but this is a
|
||||||
|
// program signal so I prefer to keep it seperate from the command messages.
|
||||||
|
// It also allows for moving termination control to another task, say from
|
||||||
|
// Ctrl+C, if the program was changed to use TCP or something else.
|
||||||
|
let (term_sender, term_receiver) = watch::channel(ProgramState::Running);
|
||||||
|
|
||||||
|
// Spawn a task to handle reading input from the user. We use a thread here
|
||||||
|
// because tokio recommends it for the blocking read calls for interactive
|
||||||
|
// user input.
|
||||||
|
let input_task = tokio::task::spawn_blocking(move || {
|
||||||
|
read_user_input(command_sender, term_sender)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spawn a task to handle simulating the satellite.
|
||||||
|
let command_task = tokio::spawn(async move {
|
||||||
|
process_commands(command_receiver, term_receiver).await
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run both tasks at the same time.
|
||||||
|
let (command_result, input_result) = tokio::join!(command_task, input_task);
|
||||||
|
|
||||||
|
// Handle errors from the tasks.
|
||||||
|
match command_result
|
||||||
|
{
|
||||||
|
Ok(Ok(())) =>
|
||||||
|
{}
|
||||||
|
|
||||||
|
Ok(Err(e)) =>
|
||||||
|
{
|
||||||
|
eprintln!("Command task failed: {}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) =>
|
||||||
|
{
|
||||||
|
eprintln!("Command task panicked or was aborted: {}", e);
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match input_result
|
||||||
|
{
|
||||||
|
Ok(Ok(())) =>
|
||||||
|
{}
|
||||||
|
|
||||||
|
Ok(Err(e)) =>
|
||||||
|
{
|
||||||
|
eprintln!("Input task failed: {}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) =>
|
||||||
|
{
|
||||||
|
eprintln!("Input task panicked or was aborted: {}", e);
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Shutdown complete.");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user