diff options
author | Daniel Schadt <kingdread@gmx.de> | 2020-04-27 14:39:36 +0200 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2020-04-27 14:39:36 +0200 |
commit | 08465ea1b8c1b9f90057bcc10fb8887ee57cac8c (patch) | |
tree | 7786e81336db4f814a323ed1cdacdced801f9abe /src/statistics/trackers.rs | |
parent | 2a9aef0a371ffb860bfc48b691b9aaf0393e3df7 (diff) | |
download | evtclib-08465ea1b8c1b9f90057bcc10fb8887ee57cac8c.tar.gz evtclib-08465ea1b8c1b9f90057bcc10fb8887ee57cac8c.tar.bz2 evtclib-08465ea1b8c1b9f90057bcc10fb8887ee57cac8c.zip |
remove statistics submodule
The way the trackers worked was rather... "adventurous", and while there
were some good ideas and it mostly worked, the implementation and
interface could do better.
Additionally, it was incomplete, for example there were a lot of
mechanics just missing.
While I'm not against having this functionality provided by evtclib, I
think it would be more worthwile with a better designed implementation &
API, so this "proof of concept" implementation is gone until there is a
better way of doing things.
gamedata is being kept, as the boss identifiers are useful and
applications shouldn't have to deal with keeping this low-level list
themselves.
Diffstat (limited to 'src/statistics/trackers.rs')
-rw-r--r-- | src/statistics/trackers.rs | 443 |
1 files changed, 0 insertions, 443 deletions
diff --git a/src/statistics/trackers.rs b/src/statistics/trackers.rs deleted file mode 100644 index 16cb755..0000000 --- a/src/statistics/trackers.rs +++ /dev/null @@ -1,443 +0,0 @@ -//! 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. -//! -//! If you want to track stats separated by player or phases, consider writing -//! your tracker in a way that it only tracks statistics for a single player, -//! and then use a [`Multiplexer`](struct.Multiplexer.html) to automatically -//! track it for every player/agent. -//! -//! 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::boon::{BoonLog, BoonQueue}; -use super::damage::{DamageLog, DamageType}; -use super::gamedata::{self, Mechanic, Trigger}; -use super::mechanics::MechanicLog; - -use super::super::raw::CbtResult; - -use fnv::FnvHashMap; - -/// 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<dyn Error>>; -} - -impl<S, E: Error + 'static, T: Tracker<Stat = S, Error = E>> RunnableTracker for T { - fn run_feed(&mut self, event: &Event) -> Result<(), Box<dyn Error>> { - self.feed(event).map_err(|e| Box::new(e) as Box<dyn Error>) - } -} - -/// A tracker that tracks per-target damage of all agents. -pub struct DamageTracker<'l> { - log: &'l Log, - damage_log: DamageLog, -} - -impl<'t> DamageTracker<'t> { - /// Create a new damage tracker for the given log. - pub fn new(log: &Log) -> DamageTracker { - DamageTracker { - log, - damage_log: DamageLog::new(), - } - } -} - -impl<'t> Tracker for DamageTracker<'t> { - type Stat = DamageLog; - type Error = !; - - fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { - match event.kind { - EventKind::Physical { - source_agent_addr, - destination_agent_addr, - damage, - skill_id, - .. - } => { - let source = if let Some(master) = self.log.master_agent(source_agent_addr) { - master.addr - } else { - source_agent_addr - }; - self.damage_log.log( - event.time, - source, - destination_agent_addr, - DamageType::Physical, - skill_id, - damage as u64, - ); - } - - EventKind::ConditionTick { - source_agent_addr, - destination_agent_addr, - damage, - condition_id, - .. - } => { - let source = if let Some(master) = self.log.master_agent(source_agent_addr) { - master.addr - } else { - source_agent_addr - }; - self.damage_log.log( - event.time, - source, - destination_agent_addr, - DamageType::Condition, - condition_id, - damage as u64, - ); - } - - _ => (), - } - Ok(()) - } - - fn finalize(self) -> Result<Self::Stat, Self::Error> { - Ok(self.damage_log) - } -} - -/// 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 { - Default::default() - } -} - -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) - } -} - -/// A tracker that tracks the total "boon area" per agent. -/// -/// The boon area is defined as the amount of stacks multiplied by the time. So -/// 1 stack of Might for 1000 milliseconds equals 1000 "stackmilliseconds" of -/// Might. You can use this boon area to calculate the average amount of stacks -/// by taking the boon area and dividing it by the combat time. -/// -/// Note that this also tracks conditions, because internally, they're handled -/// the same way. -/// -/// This tracker only tracks the boons that are known to evtclib, that is the -/// boons defined in `evtclib::statistics::gamedata::BOONS`. -pub struct BoonTracker { - boon_logs: FnvHashMap<u64, BoonLog>, - boon_queues: FnvHashMap<u64, FnvHashMap<u32, BoonQueue>>, - last_time: u64, -} - -impl BoonTracker { - /// Creates a new boon tracker for the given agent. - pub fn new() -> BoonTracker { - BoonTracker { - boon_logs: Default::default(), - boon_queues: Default::default(), - last_time: 0, - } - } - - /// Updates the internal boon queues by the given amount of milliseconds. - /// - /// * `delta_t` - Amount of milliseconds to update. - fn update_queues(&mut self, delta_t: u64) { - if delta_t == 0 { - return; - } - - self.boon_queues - .values_mut() - .flat_map(|m| m.values_mut()) - .for_each(|queue| queue.simulate(delta_t)); - } - - fn cleanup_queues(&mut self) { - // Throw away empty boon queues or to improve performance - self.boon_queues - .values_mut() - .for_each(|m| m.retain(|_, q| !q.is_empty())); - self.boon_queues.retain(|_, q| !q.is_empty()); - } - - fn update_logs(&mut self, time: u64) { - for (agent, boons) in &self.boon_queues { - let agent_log = self - .boon_logs - .entry(*agent) - .or_insert_with(Default::default); - for (boon_id, queue) in boons { - agent_log.log(time, *boon_id, queue.current_stacks()); - } - } - } - - /// Get the boon queue for the given buff_id. - /// - /// If the queue does not yet exist, create it. - /// - /// * `agent_addr` - The address of the agent. - /// * `buff_id` - The buff (or condition) id. - fn get_queue(&mut self, agent_addr: u64, buff_id: u32) -> Option<&mut BoonQueue> { - use std::collections::hash_map::Entry; - let entry = self - .boon_queues - .entry(agent_addr) - .or_insert_with(Default::default) - .entry(buff_id); - match entry { - // Queue already exists - Entry::Occupied(e) => Some(e.into_mut()), - // Queue needs to be created, but only if we know about that boon. - Entry::Vacant(e) => { - let boon_queue = gamedata::get_boon(buff_id).map(gamedata::Boon::create_queue); - if let Some(queue) = boon_queue { - Some(e.insert(queue)) - } else { - None - } - } - } - } -} - -impl Tracker for BoonTracker { - type Stat = HashMap<u64, BoonLog>; - type Error = !; - - fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { - let delta_t = event.time - self.last_time; - self.update_queues(delta_t); - - match event.kind { - EventKind::BuffApplication { - destination_agent_addr, - buff_id, - duration, - .. - } => { - if let Some(queue) = self.get_queue(destination_agent_addr, buff_id) { - queue.add_stack(duration as u64); - } - } - - // XXX: Properly handle SINGLE and MANUAL removal types - EventKind::BuffRemove { - destination_agent_addr, - buff_id, - .. - } => { - if let Some(queue) = self.get_queue(destination_agent_addr, buff_id) { - queue.clear(); - } - } - - _ => (), - } - - self.update_logs(event.time); - self.last_time = event.time; - self.cleanup_queues(); - - Ok(()) - } - - fn finalize(self) -> Result<Self::Stat, Self::Error> { - // Convert from FnvHashMap to HashMap in order to not leak - // implementation details. - Ok(self.boon_logs.into_iter().collect()) - } -} - -/// A tracker that tracks boss mechanics for each player. -pub struct MechanicTracker { - log: MechanicLog, - available_mechanics: Vec<&'static Mechanic>, - boss_addresses: Vec<u64>, -} - -impl MechanicTracker { - /// Create a new mechanic tracker that watches over the given mechanics. - pub fn new(boss_addresses: Vec<u64>, mechanics: Vec<&'static Mechanic>) -> MechanicTracker { - MechanicTracker { - log: MechanicLog::default(), - available_mechanics: mechanics, - boss_addresses, - } - } -} - -impl MechanicTracker { - fn is_boss(&self, addr: u64) -> bool { - self.boss_addresses.contains(&addr) - } -} - -impl Tracker for MechanicTracker { - type Stat = MechanicLog; - type Error = !; - - fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { - for mechanic in &self.available_mechanics { - match (&event.kind, &mechanic.1) { - ( - EventKind::Physical { - source_agent_addr, - destination_agent_addr, - skill_id, - result, - .. - }, - Trigger::SkillOnPlayer(trigger_id), - ) - if skill_id == trigger_id - && self.is_boss(*source_agent_addr) - && *result != CbtResult::Evade - && *result != CbtResult::Absorb - && *result != CbtResult::Block => - { - self.log - .increase(event.time, mechanic, *destination_agent_addr); - } - - ( - EventKind::BuffApplication { - destination_agent_addr, - buff_id, - .. - }, - Trigger::BoonPlayer(trigger_id), - ) - if buff_id == trigger_id => - { - // Some buff applications are registered multiple times. So - // instead of counting those quick successions separately - // (and thus having a wrong count), we check if this - // mechanic has already been logged "shortly before" (10 millisecons). - if self - .log - .count_between(event.time - 10, event.time + 1, |m, w| { - &m == mechanic && w == *destination_agent_addr - }) - == 0 - { - self.log - .increase(event.time, mechanic, *destination_agent_addr); - } - } - _ => (), - } - } - Ok(()) - } - - fn finalize(self) -> Result<Self::Stat, Self::Error> { - Ok(self.log) - } -} |