diff options
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) -    } -} | 
