use anyhow::Error;
use chrono::{DateTime, Utc};
use std::{
future::Future,
pin::Pin,
sync::{atomic::Ordering, Arc},
time::Duration,
};
use tokio::{
sync::{watch::Receiver, Notify},
task::JoinHandle,
time::sleep,
};
use super::{
clock::{CheatClockRemote, ClockBase, TrueClock},
market::Market,
strategy::{AtomicState, ErrorInStat, State, StratStat, StrategyFactory},
};
const KILL_TIMEOUT: Duration = Duration::from_secs(5);
pub struct App {
handle: Option<JoinHandle<()>>,
remote: Option<Box<dyn CheatClockRemote>>,
clock: Box<dyn ClockBase>,
strategy_state: Arc<AtomicState>,
strategy_notify: Arc<Notify>,
strategy_channel: Receiver<Result<StratStat, ErrorInStat>>,
}
impl App {
pub async fn new<M: Market>(
mut strategy_factory: Box<dyn StrategyFactory<M>>,
remote: Option<Box<dyn CheatClockRemote>>,
) -> Self {
let mut strategy = strategy_factory.instanciate();
let strategy_state = strategy.state().clone();
let strategy_notify = strategy.notify().clone();
let strategy_channel = strategy.stat_channel();
let handle = Some(tokio::spawn(async move {
tracing::debug!("Strategy created");
strategy.run().await;
tracing::debug!("Strategy is over");
}));
if remote.is_some() {
let clock = remote.as_ref().unwrap().clone_box();
Self {
handle,
remote,
clock,
strategy_state,
strategy_notify,
strategy_channel,
}
} else {
Self {
handle,
remote,
clock: Box::new(TrueClock::new()),
strategy_state,
strategy_notify,
strategy_channel,
}
}
}
pub fn run(&self) {
if self.handle.is_none() {
tracing::warn!("App is not running");
return;
}
self.send(State::Running);
}
pub fn send(&self, state: State) {
self.strategy_state.store(state, Ordering::Relaxed);
self.strategy_notify.notify_waiters();
tracing::debug!("New state sent : {}", state);
}
pub async fn terminate(&mut self) -> Result<StratStat, Error> {
if self.handle.is_none() {
return Err(Error::msg("App is not running"));
}
self.send(State::Terminated);
self.wait_for_the_bot().await;
if let Err(err) = self.handle.take().unwrap().await {
tracing::error!("Failed to stop the app properly: {}", err);
}
self.strategy_channel.changed().await?;
let stat = self.strategy_channel.borrow_and_update().clone()?;
Ok(stat)
}
pub async fn kill(&mut self) {
if self.handle.is_none() {
tracing::warn!("App is not running");
return;
}
self.send(State::Killed);
sleep(KILL_TIMEOUT).await;
self.handle.take().unwrap().abort();
}
pub async fn get_stats(&mut self) -> Result<StratStat, Error> {
self.send(State::Stats);
self.strategy_channel.changed().await?;
let stat = self.strategy_channel.borrow_and_update().clone()?;
Ok(stat)
}
pub fn advance_toward(&self, date: DateTime<Utc>) -> bool {
match self.remote.as_ref() {
Some(remote) => {
remote.advance_toward(date);
true
}
None => false,
}
}
pub fn wait_for_the_bot(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
if let Some(remote) = self.remote.as_ref() {
remote.wait_for_the_clock().await;
}
})
}
pub fn get_time(&self) -> DateTime<Utc> {
self.clock.now()
}
}