//! A hosted pet: the shared engine (tokenling-core, the same rules the //! browser runs locally) plus this host's ownership and error fields. use anyhow::{Result, ensure}; use serde::{Deserialize, Serialize}; use std::ops::{Deref, DerefMut}; use tokenling_core::types::PetState; pub use tokenling_core::{ body::{DAY, HOUR, MIN, simulate_body, weather_at}, engine::{Command, Effect, Engine, Game, Task}, }; pub fn now() -> f64 { chrono::Utc::now().timestamp_millis() as f64 } /// The host draws from the thread's random generator. pub fn dice() -> impl FnMut() -> f64 { rand::random:: } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct HostedPet { /// Stored flattened, so rows keep the layout they had before the engine moved to core. #[serde(flatten)] pub engine: Engine, pub epoch: u64, pub authority: String, pub local_device: Option, pub last_seen: f64, pub last_error: Option, } impl Deref for HostedPet { type Target = Engine; fn deref(&self) -> &Engine { &self.engine } } impl DerefMut for HostedPet { fn deref_mut(&mut self) -> &mut Engine { &mut self.engine } } impl HostedPet { pub fn new(state: PetState, t: f64) -> Self { Self { engine: Engine::new(state, t), epoch: 0, authority: "local".into(), local_device: None, last_seen: t, last_error: None, } } /// Runs the body while this host owns the pet. pub fn advance(&mut self, t: f64, seed: &str) { if self.authority == "remote" { self.engine.advance(t, seed, false, &mut dice()); } } pub fn command(&mut self, command: Command, t: f64, seed: &str) -> Result<()> { ensure!(self.authority == "remote", "this pet is running locally"); self.last_seen = t; self.engine.command(command, t, seed, &mut dice())?; Ok(()) } pub fn apply_line(&mut self, body: &str, t: f64, seed: &str) { self.engine.apply_line(body, t, seed, &mut dice()); } pub fn answer_offer(&mut self, yes: bool, t: f64, seed: &str) { self.engine.answer_offer(yes, t, seed, &mut dice()); } pub fn schedule_idle(&mut self, t: f64) { self.engine.schedule_idle(t, &mut dice()); } /// The next task, after a failed turn's back-off unless the owner asked for one. pub fn choose_task(&self, t: f64) -> Option { if self.pending.is_none() && self.last_error.is_some() && t < self.next_idle { return None; } self.engine.choose_task(t, true) } }