diff options
author | Daniel Schadt <kingdread@gmx.de> | 2018-04-25 19:41:43 +0200 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2018-04-25 19:41:43 +0200 |
commit | 85e203629580192413dbaa8156c2b4ca351c4bfc (patch) | |
tree | 8b41109b0f5e97c04df1716f1f09230a8c1bf828 /src/statistics/trackers.rs | |
parent | 6d0e4a82f298dbd282214d458d2e00e8a52c0768 (diff) | |
download | evtclib-85e203629580192413dbaa8156c2b4ca351c4bfc.tar.gz evtclib-85e203629580192413dbaa8156c2b4ca351c4bfc.tar.bz2 evtclib-85e203629580192413dbaa8156c2b4ca351c4bfc.zip |
introduce trackers
Trackers help us to keep the code somewhat cleaner, especially in the
statistics::calculate function.
Diffstat (limited to 'src/statistics/trackers.rs')
-rw-r--r-- | src/statistics/trackers.rs | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/statistics/trackers.rs b/src/statistics/trackers.rs new file mode 100644 index 0000000..4f2c871 --- /dev/null +++ b/src/statistics/trackers.rs @@ -0,0 +1,221 @@ +//! 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<Self::Stat, Self::Error>; +} + +/// 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<Error>>; +} + +impl<S, E: Error + 'static, T: Tracker<Stat = S, Error = E>> RunnableTracker for T { + fn run_feed(&mut self, event: &Event) -> Result<(), Box<Error>> { + self.feed(event).map_err(|e| Box::new(e) as Box<Error>) + } +} + +/// A tracker that tracks per-target damage of all agents. +pub struct DamageTracker<'l> { + log: &'l Log, + // Source -> Target -> Damage + damages: HashMap<u64, HashMap<u64, DamageStats>>, +} + +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<u64, HashMap<u64, DamageStats>>; + 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<Self::Stat, Self::Error> { + 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<Self::Stat, Self::Error> { + Ok(self.event_time) + } +} + +/// A tracker that tracks the combat entry and exit times for each agent. +#[derive(Default)] +pub struct CombatTimeTracker { + times: HashMap<u64, (u64, u64)>, +} + +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<u64, (u64, u64)>; + 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<Self::Stat, Self::Error> { + Ok(self.times) + } +} |