diff options
-rw-r--r-- | src/lib.rs | 157 | ||||
-rw-r--r-- | src/processing.rs | 156 |
2 files changed, 162 insertions, 151 deletions
@@ -32,8 +32,9 @@ //! //! `evtclib` provides two convenience functions to obtain a [`Log`][Log]: //! -//! If you have a stream (that is, something that is [`Read`][Read] + [`Seek`][Seek]), you can use -//! [`process_stream`][process_stream] to obtain a [`Log`][Log] by reading from the stream. +//! If you have a stream (that is, something that is [`Read`][std::io::Read] + +//! [`Seek`][std::io::Seek]), you can use [`process_stream`][process_stream] to obtain a +//! [`Log`][Log] by reading from the stream. //! //! If your evtc is saved in a file, you can use [`process_file`][process_file] to obtain a [`Log`] //! from it. This will also ensure that the buffering is set up correctly, to avoid unnecessary @@ -89,10 +90,7 @@ use std::collections::HashMap; use std::convert::TryFrom; -use std::fs::File; -use std::io::{BufReader, Read, Seek}; use std::marker::PhantomData; -use std::path::Path; use getset::{CopyGetters, Getters}; use num_traits::FromPrimitive; @@ -103,6 +101,9 @@ pub mod raw; pub mod event; pub use event::{Event, EventKind}; +mod processing; +pub use processing::{Compression, process, process_file, process_stream}; + pub mod gamedata; use gamedata::CmTrigger; pub use gamedata::{Boss, EliteSpec, Profession}; @@ -909,152 +910,6 @@ impl 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<Log, EvtcError> { - // 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<dyn std::error::Error>> { -/// 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<R: Read + Seek>( - input: R, - compression: Compression, -) -> Result<Log, EvtcError> { - 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<dyn std::error::Error>> { -/// let log = evtclib::process_file("logfile.zevtc", Compression::Zip)?; -/// # Ok(()) } -/// ``` -pub fn process_file<P: AsRef<Path>>(path: P, compression: Compression) -> Result<Log, EvtcError> { - let file = File::open(path).map_err(Into::<raw::ParseError>::into)?; - let buffered = BufReader::new(file); - process_stream(buffered, compression) -} - -fn setup_agents(data: &raw::Evtc) -> Result<Vec<Agent>, 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(()) -} - fn time_between_buffs(events: &[Event], wanted_buff_id: u32) -> u64 { let mut time_maps: HashMap<u64, Vec<u64>> = HashMap::new(); for event in events { diff --git a/src/processing.rs b/src/processing.rs new file mode 100644 index 0000000..f35ab35 --- /dev/null +++ b/src/processing.rs @@ -0,0 +1,156 @@ +//! Private module to contain the processing functions. + +use std::{ + convert::TryFrom, + fs::File, + io::{Read, Seek, BufReader}, + path::Path, +}; + +use super::{Agent, Event, EvtcError, Log, raw}; + +/// 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<Log, EvtcError> { + // 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<dyn std::error::Error>> { +/// 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<R: Read + Seek>( + input: R, + compression: Compression, +) -> Result<Log, EvtcError> { + 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<dyn std::error::Error>> { +/// let log = evtclib::process_file("logfile.zevtc", Compression::Zip)?; +/// # Ok(()) } +/// ``` +pub fn process_file<P: AsRef<Path>>(path: P, compression: Compression) -> Result<Log, EvtcError> { + let file = File::open(path).map_err(Into::<raw::ParseError>::into)?; + let buffered = BufReader::new(file); + process_stream(buffered, compression) +} + +fn setup_agents(data: &raw::Evtc) -> Result<Vec<Agent>, 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(()) +} |