//! evtclib tracker definitions. //! //! The idea behind a "tracker" is to have one object taking care of one //! specific thing. This makes it easier to organize the whole "statistic //! gathering loop", and it keeps each tracker somewhat small. //! //! It's also easy to define your own trackers if there are any statistics that //! you want to track. Just implement [`Tracker`](trait.Tracker.html). It //! doesn't matter what you track, it doesn't matter how many trackers you //! define. //! //! You can use [`run_trackers`](../fn.run_trackers.html) to run multiple //! trackers on the same log. use std::collections::HashMap; use std::error::Error; use super::super::{Event, EventKind, Log}; use super::DamageStats; // A support macro to introduce a new block. // // Doesn't really require a macro, but it's nicer to look at // with! { foo = bar } // rather than // { let foo = bar; ... } macro_rules! with { ($name:ident = $expr:expr => $bl:block) => {{ let $name = $expr; $bl }}; } /// A tracker. /// /// A tracker should be responsible for tracking a single statistic. Each /// tracker is fed each event. If an error is returned while feeding, the whole /// calculation will be aborted. pub trait Tracker { /// The resulting statistic that this tracker will return. type Stat; /// The error that this tracker might return. type Error: Error; /// Feed a single event into this tracker. /// /// The tracker will update its internal state. fn feed(&mut self, event: &Event) -> Result<(), Self::Error>; /// Finalize this tracker and get the statistics out. fn finalize(self) -> Result; } /// A helper trait that erases the types from a tracker. /// /// This makes it able to use references like `&mut RunnableTracker` without /// having to specify the generic types, allowing you to e.g. store a bunch of /// them in a vector, regardless of their output type. Unless you want to do /// that, you probably don't want to use this trait directly. /// /// Note that you do not need to implement this yourself. It is automatically /// implemented for all types that implement `Tracker`. /// /// RunnableTrackers provide no way to extract their resources, and they wrap /// all errors in `Box<_>`, so you should always keep a "real" reference around /// if you plan on getting any data. pub trait RunnableTracker { /// See `Tracker.feed()`. Renamed to avoid conflicts. fn run_feed(&mut self, event: &Event) -> Result<(), Box>; } impl> RunnableTracker for T { fn run_feed(&mut self, event: &Event) -> Result<(), Box> { self.feed(event).map_err(|e| Box::new(e) as Box) } } /// A tracker that tracks per-target damage of all agents. pub struct DamageTracker<'l> { log: &'l Log, // Source -> Target -> Damage damages: HashMap>, } impl<'t> DamageTracker<'t> { /// Create a new damage tracker for the given log. pub fn new(log: &Log) -> DamageTracker { DamageTracker { log, damages: HashMap::new(), } } fn get_stats(&mut self, source: u64, target: u64) -> &mut DamageStats { self.damages .entry(source) .or_insert_with(Default::default) .entry(target) .or_insert_with(Default::default) } } impl<'t> Tracker for DamageTracker<'t> { type Stat = HashMap>; type Error = !; fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { match event.kind { EventKind::Physical { source_agent_addr, destination_agent_addr, damage, .. } => { with! { stats = self.get_stats(source_agent_addr, destination_agent_addr) => { stats.total_damage += damage as u64; stats.power_damage += damage as u64; }} if let Some(master) = self.log.master_agent(source_agent_addr) { let master_stats = self.get_stats(master.addr, destination_agent_addr); master_stats.total_damage += damage as u64; master_stats.add_damage += damage as u64; } } EventKind::ConditionTick { source_agent_addr, destination_agent_addr, damage, .. } => { with! { stats = self.get_stats(source_agent_addr, destination_agent_addr) => { stats.total_damage += damage as u64; stats.condition_damage += damage as u64; }} if let Some(master) = self.log.master_agent(source_agent_addr) { let master_stats = self.get_stats(master.addr, destination_agent_addr); master_stats.total_damage += damage as u64; master_stats.add_damage += damage as u64; } } _ => (), } Ok(()) } fn finalize(self) -> Result { Ok(self.damages) } } /// Tracks when the log has been started. #[derive(Default)] pub struct LogStartTracker { event_time: u64, } impl LogStartTracker { /// Create a new log start tracker. pub fn new() -> LogStartTracker { LogStartTracker { event_time: 0 } } } impl Tracker for LogStartTracker { type Stat = u64; type Error = !; fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { if let EventKind::LogStart { .. } = event.kind { self.event_time = event.time; } Ok(()) } fn finalize(self) -> Result { Ok(self.event_time) } } /// A tracker that tracks the combat entry and exit times for each agent. #[derive(Default)] pub struct CombatTimeTracker { times: HashMap, } impl CombatTimeTracker { /// Create a new combat time tracker. pub fn new() -> CombatTimeTracker { CombatTimeTracker { times: HashMap::new(), } } } impl Tracker for CombatTimeTracker { // Maps from agent addr to (entry time, exit time) type Stat = HashMap; type Error = !; fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { match event.kind { EventKind::EnterCombat { agent_addr, .. } => { self.times.entry(agent_addr).or_insert((0, 0)).0 = event.time; } EventKind::ExitCombat { agent_addr } => { self.times.entry(agent_addr).or_insert((0, 0)).1 = event.time; } _ => (), } Ok(()) } fn finalize(self) -> Result { Ok(self.times) } }