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 | |
| 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')
| -rw-r--r-- | src/statistics/mod.rs | 144 | ||||
| -rw-r--r-- | src/statistics/trackers.rs | 221 | 
2 files changed, 284 insertions, 81 deletions
| diff --git a/src/statistics/mod.rs b/src/statistics/mod.rs index a9dd178..1ebb517 100644 --- a/src/statistics/mod.rs +++ b/src/statistics/mod.rs @@ -1,17 +1,36 @@  //! This module aids in the creation of actual boss statistics.  use super::*;  use std::collections::HashMap; +use std::error::Error;  pub mod boon; +pub mod trackers; + +use self::trackers::{RunnableTracker, Tracker};  pub type StatResult<T> = Result<T, StatError>;  quick_error! { -    #[derive(Clone, Debug)] +    #[derive(Debug)]      pub enum StatError { +        TrackerError(err: Box<Error>) { +            description("tracker error") +            display("tracker returned an error: {}", err) +            cause(&**err) +        }      }  } +macro_rules! try_tracker { +    ($expr:expr) => { +        #[allow(unreachable_code)] +        match $expr { +            Ok(e) => e, +            Err(e) => return Err(StatError::TrackerError(e)), +        } +    }; +} +  /// A struct containing the calculated statistics for the log.  #[derive(Clone, Debug)]  pub struct Statistics { @@ -56,94 +75,57 @@ pub struct DamageStats {      pub add_damage: u64,  } -// 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 -    }}; +/// Takes a bunch of trackers and runs them on the given log. +/// +/// This method returns "nothing", as the statistics are saved in the trackers. +/// It's the job of the caller to extract them appropriately. +pub fn run_trackers(log: &Log, trackers: &mut [&mut RunnableTracker]) -> StatResult<()> { +    for event in log.events() { +        for mut tracker in trackers.iter_mut() { +            try_tracker!((*tracker).run_feed(event)); +        } +    } +    Ok(())  }  /// Calculate the statistics for the given log.  pub fn calculate(log: &Log) -> StatResult<Statistics> { -    use super::EventKind::*; -      let mut agent_stats = HashMap::<u64, AgentStats>::new(); -    let mut log_start_time = 0; -    fn get_stats(map: &mut HashMap<u64, AgentStats>, source: u64, target: u64) -> &mut DamageStats { -        map.entry(source) -            .or_insert_with(Default::default) -            .per_target_damage -            .entry(target) -            .or_insert_with(Default::default) +    let mut damage_tracker = trackers::DamageTracker::new(log); +    let mut log_start_tracker = trackers::LogStartTracker::new(); +    let mut combat_time_tracker = trackers::CombatTimeTracker::new(); + +    run_trackers( +        log, +        &mut [ +            &mut damage_tracker, +            &mut log_start_tracker, +            &mut combat_time_tracker, +        ], +    )?; + +    let log_start_time = try_tracker!(log_start_tracker.finalize()); + +    let combat_times = try_tracker!(combat_time_tracker.finalize()); +    for (agent_addr, &(enter_time, exit_time)) in &combat_times { +        let agent = agent_stats +            .entry(*agent_addr) +            .or_insert_with(Default::default); +        if enter_time != 0 { +            agent.enter_combat = enter_time - log_start_time; +        } +        if exit_time != 0 { +            agent.exit_combat = exit_time - log_start_time; +        }      } -    for event in log.events() { -        match event.kind { -            LogStart { .. } => { -                log_start_time = event.time; -            } - -            EnterCombat { agent_addr, .. } => { -                agent_stats -                    .entry(agent_addr) -                    .or_insert_with(Default::default) -                    .enter_combat = event.time - log_start_time; -            } - -            ExitCombat { agent_addr } => { -                agent_stats -                    .entry(agent_addr) -                    .or_insert_with(Default::default) -                    .exit_combat = event.time - log_start_time; -            } - -            Physical { -                source_agent_addr, -                destination_agent_addr, -                damage, -                .. -            } => { -                with! { stats = get_stats(&mut agent_stats, source_agent_addr, destination_agent_addr) => { -                    stats.total_damage += damage as u64; -                    stats.power_damage += damage as u64; -                }} - -                if let Some(master) = log.master_agent(source_agent_addr) { -                    let master_stats = -                        get_stats(&mut agent_stats, master.addr, destination_agent_addr); -                    master_stats.total_damage += damage as u64; -                    master_stats.add_damage += damage as u64; -                } -            } - -            ConditionTick { -                source_agent_addr, -                destination_agent_addr, -                damage, -                .. -            } => { -                with! { stats = get_stats(&mut agent_stats, source_agent_addr, destination_agent_addr) => { -                    stats.total_damage += damage as u64; -                    stats.condition_damage += damage as u64; -                }} - -                if let Some(master) = log.master_agent(source_agent_addr) { -                    let master_stats = -                        get_stats(&mut agent_stats, master.addr, destination_agent_addr); -                    master_stats.total_damage += damage as u64; -                    master_stats.add_damage += damage as u64; -                } -            } - -            _ => (), -        } +    let damages = try_tracker!(damage_tracker.finalize()); +    for agent in damages.keys() { +        agent_stats +            .entry(*agent) +            .or_insert_with(Default::default) +            .per_target_damage = damages[agent].clone();      }      let boss = log.boss(); 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) +    } +} | 
