//! Private module to contain the processing functions. use std::{ convert::TryFrom, fs::File, io::{BufReader, Read, Seek}, path::Path, }; use super::{raw, Agent, Event, EvtcError, Log}; /// Main function to turn a low-level [`Evtc`][raw::Evtc] to a high-level [`Log`][Log]. /// /// This function takes an [`Evtc`][raw::Evtc] and does the required type conversions and /// pre-processing to get a high-level [`Log`][Log]. This pre-processing includes /// /// * Setting the correct aware times for the agents /// * Setting the master agents for each agent /// * Converting all events /// /// Note that the structures are quite different, so this function does not consume the given /// [`Evtc`][raw::Evtc]. pub fn process(data: &raw::Evtc) -> Result { // Prepare "augmented" agents let mut agents = setup_agents(data)?; // Do the first aware/last aware field set_agent_awares(data, &mut agents)?; // Set the master addr field set_agent_masters(data, &mut agents)?; let events = data .events .iter() .filter_map(|e| Event::try_from(e).ok()) .collect(); Ok(Log { agents, events, boss_id: data.header.combat_id, }) } /// Indicates the given compression method for the file. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Compression { /// No compression was used. None, /// The file is wrapped in a zip archive. Zip, } /// Convenience function to process a given stream directly. /// /// This is a shorthand for using [`raw::parse_file`][raw::parse_file] followed by /// [`process`][process]. /// /// The [`Seek`][Seek] bound is needed for zip compressed archives. If you have a reader that does /// not support seeking, you can use [`raw::parse_file`][raw::parse_file] directly instead. /// /// ```no_run /// # fn main() -> Result<(), Box> { /// use std::io::Cursor; /// use evtclib::Compression; /// let data = Cursor::new(vec![]); /// let log = evtclib::process_stream(data, Compression::None)?; /// # Ok(()) } /// ``` pub fn process_stream( input: R, compression: Compression, ) -> Result { let evtc = match compression { Compression::None => raw::parse_file(input)?, Compression::Zip => raw::parse_zip(input)?, }; process(&evtc) } /// Convenience function to process a given file directly. /// /// This is a shorthand for opening the file and then using [`process_stream`][process_stream] with /// it. This function automatically wraps the raw file in a buffered reader, to ensure the bext /// reading performance. /// /// If you need more fine-grained control, consider using [`process_stream`][process_stream] or /// [`raw::parse_file`][raw::parse_file] followed by [`process`][process] instead. /// /// ```no_run /// # use evtclib::Compression; /// # fn main() -> Result<(), Box> { /// let log = evtclib::process_file("logfile.zevtc", Compression::Zip)?; /// # Ok(()) } /// ``` pub fn process_file>(path: P, compression: Compression) -> Result { let file = File::open(path).map_err(Into::::into)?; let buffered = BufReader::new(file); process_stream(buffered, compression) } fn setup_agents(data: &raw::Evtc) -> Result, EvtcError> { let mut agents = Vec::with_capacity(data.agents.len()); for raw_agent in &data.agents { agents.push(Agent::try_from(raw_agent)?); } Ok(agents) } fn get_agent_by_addr(agents: &mut [Agent], addr: u64) -> Option<&mut Agent> { for agent in agents { if agent.addr == addr { return Some(agent); } } None } fn set_agent_awares(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcError> { for event in &data.events { if event.is_statechange == raw::CbtStateChange::None { if let Some(current_agent) = get_agent_by_addr(agents, event.src_agent) { current_agent.instance_id = event.src_instid; if current_agent.first_aware == 0 { current_agent.first_aware = event.time; } current_agent.last_aware = event.time; } } } Ok(()) } fn set_agent_masters(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcError> { for event in &data.events { if event.src_master_instid != 0 { let mut master_addr = None; for agent in &*agents { if agent.instance_id == event.src_master_instid && agent.first_aware < event.time && event.time < agent.last_aware { master_addr = Some(agent.addr); break; } } if let Some(master_addr) = master_addr { if let Some(current_slave) = get_agent_by_addr(agents, event.src_agent) { current_slave.master_agent = Some(master_addr); } } } } Ok(()) }