aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs2
-rw-r--r--src/statistics/mod.rs144
-rw-r--r--src/statistics/trackers.rs221
3 files changed, 285 insertions, 82 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 9d3c150..c4c70a1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@
//!
//! (Look at the note on "Buffering" in the [parser
//! module](raw/parser/index.html#buffering))
-#![feature(try_trait)]
+#![feature(try_trait, stmt_expr_attributes)]
#[macro_use]
extern crate quick_error;
#[macro_use]
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)
+ }
+}