Add State Saving to PingPong example.
This commit is contained in:
parent
1dbf29be79
commit
3645d5d9d6
|
@ -0,0 +1 @@
|
|||
saved_states/
|
|
@ -7,4 +7,9 @@ edition = "2021"
|
|||
overwatch-rs = { path = "../../overwatch-rs" }
|
||||
overwatch-derive = { path = "../../overwatch-derive" }
|
||||
async-trait = "0.1.83"
|
||||
tokio = { version = "1", features = ["macros"] }
|
||||
tokio = { version = "1.42.0", features = ["macros"] }
|
||||
thiserror = "2.0.8"
|
||||
serde_json = "1.0.134"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
const_format = "0.2.34"
|
||||
project-root = "0.2.2"
|
|
@ -1,17 +1,29 @@
|
|||
# PingPong
|
||||
|
||||
This example project demonstrates how to set up a basic Overwatch application in Rust.
|
||||
|
||||
### Behaviour
|
||||
|
||||
This project demonstrates a simple communication pattern between two services.
|
||||
|
||||
1. Every second, the `Ping` service sends a message to the `Pong` service.
|
||||
2. The `Pong` service receives the message and prints it to the console. Afterwards, it sends a message back to the `Ping` service.
|
||||
2. The `Pong` service receives the message and prints it to the console. Afterwards, it sends a message back to the
|
||||
`Ping` service.
|
||||
3. The `Ping` service receives the message and prints it to the console.
|
||||
- After each received `Pong` message, a counter is incremented and saved into a file from which it can be restored
|
||||
when the application is restarted.
|
||||
|
||||
### Features
|
||||
|
||||
- **Services**: Shows how to define and register services within an Overwatch application.
|
||||
- **Message Passing**: Demonstrates communication between services using the relay.
|
||||
- **Messages**: Demonstrates communication between services using the relay.
|
||||
- **Settings**: Shows how to define and access settings within an Overwatch application.
|
||||
- **States**: Demonstrates how to use the state to store data within an Overwatch application.
|
||||
- **StateOperators**: Shows how to use the state operator to interact with the state.
|
||||
- In this example, in combination with **State**, it shows how to save and load data from the state.
|
||||
|
||||
### About
|
||||
This project serves as a barebones template to get started with the Overwatch library.
|
||||
|
||||
This project serves as a barebones template to get started with the Overwatch library.
|
||||
It provides the foundational structure for creating more complex applications.
|
||||
|
||||
|
|
|
@ -5,10 +5,14 @@ use overwatch_rs::services::handle::ServiceHandle;
|
|||
// Internal
|
||||
use crate::service_ping::PingService;
|
||||
use crate::service_pong::PongService;
|
||||
use crate::settings::PingSettings;
|
||||
|
||||
mod messages;
|
||||
mod operators;
|
||||
mod service_ping;
|
||||
mod service_pong;
|
||||
mod settings;
|
||||
mod states;
|
||||
|
||||
#[derive(Services)]
|
||||
struct PingPong {
|
||||
|
@ -16,8 +20,19 @@ struct PingPong {
|
|||
pong: ServiceHandle<PongService>,
|
||||
}
|
||||
|
||||
const PING_STATE_SAVE_PATH: &str = const_format::formatcp!(
|
||||
"{}/saved_states/ping_state.json",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
|
||||
fn main() {
|
||||
let ping_pong_settings = PingPongServiceSettings { ping: (), pong: () };
|
||||
let ping_settings = PingSettings {
|
||||
state_save_path: String::from(PING_STATE_SAVE_PATH),
|
||||
};
|
||||
let ping_pong_settings = PingPongServiceSettings {
|
||||
ping: ping_settings,
|
||||
pong: (),
|
||||
};
|
||||
let ping_pong =
|
||||
OverwatchRunner::<PingPong>::run(ping_pong_settings, None).expect("OverwatchRunner failed");
|
||||
ping_pong.wait_finished();
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// STD
|
||||
use std::fmt::Debug;
|
||||
// Crates
|
||||
use overwatch_rs::services::state::{ServiceState, StateOperator};
|
||||
// Internal
|
||||
use crate::states::PingState;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StateSaveOperator {
|
||||
save_path: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl StateOperator for StateSaveOperator {
|
||||
type StateInput = PingState;
|
||||
|
||||
fn from_settings(settings: <Self::StateInput as ServiceState>::Settings) -> Self {
|
||||
Self {
|
||||
save_path: settings.state_save_path,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self, state: Self::StateInput) {
|
||||
let json_state = serde_json::to_string(&state).expect("Failed to serialize state");
|
||||
std::fs::write(&self.save_path, json_state).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,26 @@
|
|||
// Crates
|
||||
use overwatch_rs::services::handle::ServiceStateHandle;
|
||||
use overwatch_rs::services::state::{NoOperator, NoState};
|
||||
use overwatch_rs::services::{ServiceCore, ServiceData, ServiceId};
|
||||
use overwatch_rs::DynError;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
// Internal
|
||||
use crate::messages::{PingMessage, PongMessage};
|
||||
use crate::operators::StateSaveOperator;
|
||||
use crate::service_pong::PongService;
|
||||
use crate::settings::PingSettings;
|
||||
use crate::states::PingState;
|
||||
|
||||
pub struct PingService {
|
||||
service_state_handle: ServiceStateHandle<Self>,
|
||||
initial_state: <Self as ServiceData>::State,
|
||||
}
|
||||
|
||||
impl ServiceData for PingService {
|
||||
const SERVICE_ID: ServiceId = "ping";
|
||||
type Settings = ();
|
||||
type State = NoState<Self::Settings>;
|
||||
type StateOperator = NoOperator<Self::State>;
|
||||
type Settings = PingSettings;
|
||||
type State = PingState;
|
||||
type StateOperator = StateSaveOperator;
|
||||
type Message = PingMessage;
|
||||
}
|
||||
|
||||
|
@ -25,16 +28,18 @@ impl ServiceData for PingService {
|
|||
impl ServiceCore for PingService {
|
||||
fn init(
|
||||
service_state_handle: ServiceStateHandle<Self>,
|
||||
_initial_state: Self::State,
|
||||
initial_state: Self::State,
|
||||
) -> Result<Self, DynError> {
|
||||
Ok(Self {
|
||||
service_state_handle,
|
||||
initial_state,
|
||||
})
|
||||
}
|
||||
|
||||
async fn run(self) -> Result<(), DynError> {
|
||||
let Self {
|
||||
service_state_handle,
|
||||
initial_state,
|
||||
} = self;
|
||||
|
||||
let mut inbound_relay = service_state_handle.inbound_relay;
|
||||
|
@ -44,28 +49,31 @@ impl ServiceCore for PingService {
|
|||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut pong_count = 0;
|
||||
let Self::State { mut pong_count } = initial_state;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sleep(Duration::from_secs(1)) => {
|
||||
println!("Sending Ping");
|
||||
pong_outbound_relay.send(PongMessage::Ping).await.unwrap();
|
||||
}
|
||||
Some(message) = inbound_relay.recv() => {
|
||||
match message {
|
||||
PingMessage::Pong => {
|
||||
println!("Received Pong");
|
||||
pong_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
true = async {
|
||||
pong_count >= 5
|
||||
} => {
|
||||
println!("Received {} Pongs. Exiting...", pong_count);
|
||||
break;
|
||||
}
|
||||
_ = sleep(Duration::from_secs(1)) => {
|
||||
println!("Sending Ping");
|
||||
pong_outbound_relay.send(PongMessage::Ping).await.unwrap();
|
||||
}
|
||||
Some(message) = inbound_relay.recv() => {
|
||||
match message {
|
||||
PingMessage::Pong => {
|
||||
pong_count += 1;
|
||||
service_state_handle.state_updater.update(
|
||||
Self::State { pong_count }
|
||||
);
|
||||
println!("Received Pong. Total: {}", pong_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
true = async {
|
||||
pong_count >= 30
|
||||
} => {
|
||||
println!("Received {} Pongs. Exiting...", pong_count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PingSettings {
|
||||
pub(crate) state_save_path: String,
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// STD
|
||||
use std::io;
|
||||
// Crates
|
||||
use overwatch_rs::services::state::ServiceState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
// Internal
|
||||
use crate::settings::PingSettings;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PingStateError {}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PingState {
|
||||
pub pong_count: u32,
|
||||
}
|
||||
|
||||
impl PingState {
|
||||
fn load_saved_state(save_path: &str) -> io::Result<Self> {
|
||||
let json_state = std::fs::read(save_path)?;
|
||||
let state = serde_json::from_slice(json_state.as_slice())
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
Ok(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceState for PingState {
|
||||
type Settings = PingSettings;
|
||||
type Error = PingStateError;
|
||||
|
||||
fn from_settings(settings: &Self::Settings) -> Result<Self, Self::Error> {
|
||||
let state = Self::load_saved_state(settings.state_save_path.as_str())
|
||||
.unwrap_or_else(|_error| Self::default());
|
||||
Ok(state)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue