All done!
This commit is contained in:
parent
43a093afd1
commit
0a3edeb4a8
@ -4,7 +4,7 @@ version = "0.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"
|
||||||
|
|
||||||
|
69
LICENSE.md
69
LICENSE.md
@ -1,69 +0,0 @@
|
|||||||
Copyright (c) <2025> CyberMages LLC
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or
|
|
||||||
without modification, are permitted provided that the
|
|
||||||
following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above
|
|
||||||
copyright notice, this list of conditions and the
|
|
||||||
following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the
|
|
||||||
following disclaimer in the documentation and/or other
|
|
||||||
materials provided with the distribution.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this license, each
|
|
||||||
copyright holder and contributor hereby grants to those
|
|
||||||
receiving rights under this license a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable (except for failure to satisfy the conditions
|
|
||||||
of this license) patent license to make, have made, use,
|
|
||||||
offer to sell, sell, import, and otherwise transfer this
|
|
||||||
software, where such license applies only to those patent
|
|
||||||
claims, already acquired or hereafter acquired, licensable
|
|
||||||
by such copyright holder or contributor that are
|
|
||||||
necessarily infringed by:
|
|
||||||
|
|
||||||
(a) their Contribution(s) (the licensed copyrights of
|
|
||||||
copyright holders and non-copyrightable additions
|
|
||||||
of contributors, in source or binary form) alone;
|
|
||||||
|
|
||||||
-- or --
|
|
||||||
|
|
||||||
(b) combination of their Contribution(s) with the work
|
|
||||||
of authorship to which such Contribution(s) was
|
|
||||||
added by such copyright holder or contributor, if,
|
|
||||||
at the time the Contribution is added, such
|
|
||||||
addition causes such combination to be necessarily
|
|
||||||
infringed. The patent license shall not apply to
|
|
||||||
any other combinations which include the
|
|
||||||
Contribution.
|
|
||||||
|
|
||||||
Except as expressly stated above, no rights or licenses
|
|
||||||
from any copyright holder or contributor is granted under
|
|
||||||
this license, whether expressly, by implication, estoppel
|
|
||||||
or otherwise.
|
|
||||||
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
||||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
||||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
||||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
||||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
YOU ACKNOWLEDGE THAT THIS SOFTWARE IS NOT DESIGNED,
|
|
||||||
LICENSED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION,
|
|
||||||
OPERATION OR MAINTENANCE OF ANY MILITARY FACILITY OR
|
|
||||||
RELIGIOUS INSTITUTION.
|
|
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.
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
# Problem Statement
|
|
||||||
You are a member of the flight software team at Umbra and are responsible
|
|
||||||
for writing code that manages the satellite’s propulsion system.
|
|
||||||
|
|
||||||
Firing the propulsion system involves waiting for a certain period of time before ignition.
|
|
||||||
|
|
||||||
The following is an example usage of this system:
|
|
||||||
|
|
||||||
* At absolute time t = 0, send a command to the computer to fire the propulsion in 15 seconds
|
|
||||||
* At absolute time t = 2, send a command to the computer to fire the propulsion in 30 seconds
|
|
||||||
* At absolute time t = 32, the computer begins firing the propulsion
|
|
||||||
|
|
||||||
The flight computer should accept a command with a relative time of when to fire
|
|
||||||
the propulsion - once that time has elapsed, the program should print out “firing now!”.
|
|
||||||
If another command is received before the propulsion is fired then the most recently
|
|
||||||
received relative time should overwrite any existing commands.
|
|
||||||
|
|
||||||
More formally, if a command _A_ is waiting to fire and another command _B_ is received
|
|
||||||
before A has fired, then B should replace _A_ as the pending command and _A_ should never fire.
|
|
||||||
|
|
||||||
If a time of -1 is given, any outstanding commands to fire the propulsion are cancelled.
|
|
||||||
|
|
||||||
Note that the flight computer should be able to fire the thruster multiple times in a
|
|
||||||
single execution of the program.
|
|
||||||
|
|
||||||
You may use exactly one of the following interfaces for getting data into and out of your program:
|
|
||||||
* Standard input/standard output
|
|
||||||
* TCP
|
|
||||||
|
|
||||||
You can use whichever is more convenient - we note that some languages make asynchronous
|
|
||||||
IO with standard input/standard output cumbersome and have thus included TCP.
|
|
||||||
|
|
||||||
If you do choose to use TCP, please have your server listen on port `8124`.
|
|
||||||
|
|
||||||
A sample TCP client, `propulsion_tcp_client.py`, is provided that plumbs standard input
|
|
||||||
to TCP writes and likewise plumbs TCP reads to standard output.
|
|
||||||
|
|
||||||
Commands should be delineated by newlines.
|
|
||||||
|
|
||||||
A sample _complete_ execution of your program using standard input and output is shown below
|
|
||||||
|
|
||||||
```
|
|
||||||
./your_program
|
|
||||||
15
|
|
||||||
30
|
|
||||||
firing now!
|
|
||||||
```
|
|
||||||
|
|
||||||
# Submitting your code
|
|
||||||
You may complete this assignment in any programming language that you wish.
|
|
||||||
We're most familiar with Rust and Python at Umbra, but we always enjoy learning
|
|
||||||
new languages and seeing solutions with different tools!
|
|
||||||
|
|
||||||
Your final submission for this exercise should be a zip file including:
|
|
||||||
* The source code of your program.
|
|
||||||
* A brief README with a description of your approach and instructions on how to
|
|
||||||
build and run the program.
|
|
||||||
* Any libraries or frameworks needed to run your program, or instructions for installing them.
|
|
||||||
|
|
||||||
Please ensure that your code is clear and legible before submitting.
|
|
||||||
|
|
||||||
While we don't expect you to write production-quality code for this exercise,
|
|
||||||
readability goes a long way in helping us review your submission.
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
use tokio::time::Duration;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// The commands available to send to the Satellite.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum Command
|
|
||||||
{
|
|
||||||
/// 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
|
|
||||||
}
|
|
236
src/main.rs
236
src/main.rs
@ -1,104 +1,106 @@
|
|||||||
//! Rust Challenge
|
//! Rust Challenge
|
||||||
|
|
||||||
mod commands;
|
|
||||||
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::sync::{mpsc, watch};
|
use tokio::sync::{mpsc, watch};
|
||||||
use tokio::time::{Duration, Interval};
|
use tokio::time::{Duration, Interval};
|
||||||
|
|
||||||
use crate::commands::Command;
|
/// Just an easier to read type.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type IoResult<T> = Result<T, std::io::Error>;
|
type IoResult<T> = Result<T, std::io::Error>;
|
||||||
|
|
||||||
|
/// The commands available to send to the Satellite.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Command {
|
||||||
|
/// 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.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum ProgramState {
|
||||||
|
/// The program is currently running.
|
||||||
|
Running,
|
||||||
|
|
||||||
|
/// The program has switched to shutting down.
|
||||||
|
Terminated,
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads input from the user until the termination signal is
|
/// Reads input from the user until the termination signal is
|
||||||
/// received. It will send command messages every time it reads valid input.
|
/// received. It will send command messages every time it reads valid input.
|
||||||
fn read_user_input(command_sender: mpsc::Sender<Command>,
|
fn read_user_input(
|
||||||
term_sender: watch::Sender<bool>)
|
command_sender: mpsc::Sender<Command>,
|
||||||
-> IoResult<()>
|
term_sender: watch::Sender<ProgramState>,
|
||||||
{
|
) -> IoResult<()> {
|
||||||
let mut running: bool = true;
|
let mut running: bool = true;
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
// Loop and read the user input.
|
// Loop and read the user input.
|
||||||
while running
|
while running {
|
||||||
{
|
// Clearing the last read line and read a new one.
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
std::io::stdin().read_line(&mut buffer)?;
|
std::io::stdin().read_line(&mut buffer)?;
|
||||||
std::io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let input = buffer.trim();
|
let input = buffer.trim();
|
||||||
|
|
||||||
match input
|
match input {
|
||||||
{
|
|
||||||
// A termination method wasn't specified in the document, so
|
// A termination method wasn't specified in the document, so
|
||||||
// this will terminate when either stop, exit, or quit is entered.
|
// this will terminate when either stop, exit, or quit is entered.
|
||||||
"exit" | "stop" | "quit" =>
|
"exit" | "stop" | "quit" => {
|
||||||
{
|
println!("Exiting program.");
|
||||||
println!("Exiting application.");
|
|
||||||
running = false;
|
running = false;
|
||||||
term_sender.send_replace(running);
|
term_sender.send_replace(ProgramState::Terminated);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ =>
|
_ => {
|
||||||
{
|
|
||||||
// Check to see if we were given a number.
|
// Check to see if we were given a number.
|
||||||
match input.parse::<i32>()
|
match input.parse::<i32>() {
|
||||||
{
|
Ok(seconds) => {
|
||||||
Ok(seconds) =>
|
|
||||||
{
|
|
||||||
// Here we are just handling the different possible numbers
|
// Here we are just handling the different possible numbers
|
||||||
// passed in. -1 cancels commands, less than -1 is ignored
|
// passed in. -1 cancels commands, less than -1 is ignored
|
||||||
// with a message, and greated than -1 is turned into a
|
// with a message, and greated than -1 is turned into a
|
||||||
// propulsion command.
|
// propulsion command.
|
||||||
if seconds == -1
|
if seconds == -1 {
|
||||||
{
|
match command_sender.blocking_send(Command::Cancel) {
|
||||||
match command_sender.blocking_send(Command::Cancel)
|
Ok(_) => {}
|
||||||
{
|
Err(e) => eprintln!("Failed to send command: {}", e),
|
||||||
Ok(_) =>
|
|
||||||
{
|
|
||||||
println!("Cancelling any outstanding commands.")
|
|
||||||
}
|
|
||||||
Err(e) => println!("Failed to send command: {}", e)
|
|
||||||
}
|
}
|
||||||
}
|
} else if seconds < -1 {
|
||||||
else if seconds < -1
|
println!(
|
||||||
{
|
"All propulsion delay times are given in \
|
||||||
println!("All propulsion delay times are given in \
|
seconds within the range of [{}, {}]",
|
||||||
seconds within the range of [0, {}]",
|
0,
|
||||||
u32::MAX);
|
i32::MAX
|
||||||
println!("A value of -1 will cancel any current \
|
);
|
||||||
propulsion commands.");
|
println!(
|
||||||
}
|
"A value of -1 will cancel any current \
|
||||||
else
|
propulsion commands."
|
||||||
{
|
);
|
||||||
let larger_seconds: u64 = seconds as u64;
|
} else {
|
||||||
|
// Time to create the Propulsion command.
|
||||||
|
let delay_secs: u64 = seconds as u64;
|
||||||
let delay_duration: Duration =
|
let delay_duration: Duration =
|
||||||
Duration::from_secs(larger_seconds);
|
Duration::from_secs(delay_secs);
|
||||||
match command_sender.try_send(Command::Propulsion { delay: delay_duration })
|
match command_sender.try_send(Command::Propulsion {
|
||||||
{
|
delay: delay_duration,
|
||||||
Ok(_) =>
|
}) {
|
||||||
{
|
Ok(_) => {}
|
||||||
println!("Firing the engines in {} seconds.",
|
Err(e) => eprintln!("Failed to send command: {}", e),
|
||||||
seconds)
|
|
||||||
}
|
|
||||||
Err(e) => println!("Failed to send command: {}", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(e) =>
|
Err(e) => {
|
||||||
{
|
|
||||||
println!("Unable to parse the given input into seconds.");
|
println!("Unable to parse the given input into seconds.");
|
||||||
println!("Please specify only seconds until the time to \
|
println!("{:?}\n", e);
|
||||||
|
println!("Please specify only the desired whole seconds until the time to \
|
||||||
fire the engines.");
|
fire the engines.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,69 +111,80 @@ fn read_user_input(command_sender: mpsc::Sender<Command>,
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines if there is an interval that needs to be run or if
|
||||||
|
/// there is nothing to really do.
|
||||||
///
|
///
|
||||||
fn maybe_tick<'a>(interval: Option<&'a mut Interval>)
|
/// This was the trickiest part due to the syntax.
|
||||||
-> Pin<Box<dyn Future<Output = ()> + Send + 'a>>
|
fn maybe_tick<'a>(
|
||||||
{
|
interval: Option<&'a mut Interval>,
|
||||||
match interval
|
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||||
{
|
match interval {
|
||||||
Some(interval) => Box::pin(async move { interval.tick().await; () }),
|
Some(interval) => Box::pin(async move {
|
||||||
None => Box::pin(std::future::pending())
|
interval.tick().await;
|
||||||
|
()
|
||||||
|
}),
|
||||||
|
|
||||||
|
None => Box::pin(std::future::pending()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_commands(mut command_receiver: mpsc::Receiver<Command>,
|
/// The command task.
|
||||||
mut term_receiver: watch::Receiver<bool>)
|
///
|
||||||
-> IoResult<()>
|
/// Processes the commands sent and fire the engine if a given
|
||||||
{
|
/// propulsion command completes.
|
||||||
let mut propulsion_interval: Option<Interval> = None;
|
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 stdout = tokio::io::stdout();
|
||||||
|
let mut propulsion_interval: Option<Interval> = None;
|
||||||
let mut running: bool = true;
|
let mut running: bool = true;
|
||||||
while running
|
|
||||||
{
|
while running {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
// Process commands.
|
||||||
Some(command) = command_receiver.recv() =>
|
Some(command) = command_receiver.recv() =>
|
||||||
{
|
{
|
||||||
match command
|
match command
|
||||||
{
|
{
|
||||||
Command::Cancel =>
|
Command::Cancel =>
|
||||||
{
|
{
|
||||||
stdout.write_all(b"Received: Cancel\n").await?;
|
// Dump any interval that is running.
|
||||||
stdout.flush().await?;
|
|
||||||
propulsion_interval = None;
|
propulsion_interval = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Propulsion { delay } =>
|
Command::Propulsion { delay } =>
|
||||||
{
|
{
|
||||||
let mut buffer: Vec<u8> = Vec::new();
|
// Overwrite any interval with this great new interval.
|
||||||
writeln!(&mut buffer, "Received: {:?} delay", delay);
|
|
||||||
stdout.write_all(&buffer).await?;
|
|
||||||
stdout.flush().await?;
|
|
||||||
|
|
||||||
propulsion_interval = Some(tokio::time::interval(delay));
|
propulsion_interval = Some(tokio::time::interval(delay));
|
||||||
|
|
||||||
// Skip the first immediate tick
|
// Skip the first immediate tick.
|
||||||
if let Some(interval) = propulsion_interval.as_mut()
|
if let Some(interval) = propulsion_interval.as_mut()
|
||||||
{
|
{
|
||||||
interval.tick().await; // skip first tick
|
interval.tick().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the interval timer if there is one.
|
||||||
_ = maybe_tick(propulsion_interval.as_mut()) =>
|
_ = maybe_tick(propulsion_interval.as_mut()) =>
|
||||||
{
|
{
|
||||||
stdout.write_all(b"firing now!\n").await?;
|
stdout.write_all(b"firing now!\n").await?;
|
||||||
stdout.flush().await?;
|
stdout.flush().await?;
|
||||||
|
|
||||||
|
// Clear the command out since it is done.
|
||||||
|
propulsion_interval = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for termination.
|
||||||
_ = term_receiver.changed() =>
|
_ = term_receiver.changed() =>
|
||||||
{
|
{
|
||||||
stdout.write_all(b"Communication task received shutdown message.").await?;
|
running = match *term_receiver.borrow_and_update()
|
||||||
stdout.flush().await?;
|
{
|
||||||
running = *term_receiver.borrow_and_update();
|
ProgramState::Running => { true }
|
||||||
|
ProgramState::Terminated => { false }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,20 +194,18 @@ async fn process_commands(mut command_receiver: mpsc::Receiver<Command>,
|
|||||||
|
|
||||||
/// Program entry point.
|
/// Program entry point.
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> IoResult<()>
|
async fn main() -> IoResult<()> {
|
||||||
{
|
|
||||||
// The channel that will be used to send satellite commands between tasks.
|
// The channel that will be used to send satellite commands between tasks.
|
||||||
let (command_sender, mut command_receiver) = mpsc::channel::<Command>(100);
|
let (command_sender, command_receiver) = mpsc::channel::<Command>(100);
|
||||||
|
|
||||||
// The channel that will be used to signal termination of the program.
|
// The channel that will be used to signal termination of the program.
|
||||||
// True means the program is running. False means it has been terminated.
|
|
||||||
//
|
//
|
||||||
// This could be done as a satellite command since the program is driven by
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// Ctrl+C, if the program was changed to use TCP or something else.
|
||||||
let (term_sender, mut term_receiver) = watch::channel(true);
|
let (term_sender, term_receiver) = watch::channel(ProgramState::Running);
|
||||||
|
|
||||||
// Spawn a task to handle reading input from the user. We use a thread here
|
// 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
|
// because tokio recommends it for the blocking read calls for interactive
|
||||||
@ -204,13 +215,42 @@ async fn main() -> IoResult<()>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Spawn a task to handle simulating the satellite.
|
// Spawn a task to handle simulating the satellite.
|
||||||
let sim_task = tokio::spawn(async move {
|
let command_task = tokio::spawn(async move {
|
||||||
process_commands(command_receiver, term_receiver).await
|
process_commands(command_receiver, term_receiver).await
|
||||||
});
|
});
|
||||||
|
|
||||||
let (sim_result, input_result) = tokio::join!(sim_task, input_task);
|
// Run both tasks at the same time.
|
||||||
sim_result?;
|
let (command_result, input_result) = tokio::join!(command_task, input_task);
|
||||||
input_result??;
|
|
||||||
|
// 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.");
|
println!("Shutdown complete.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
pub struct Satellite
|
|
||||||
{
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user