aboutsummaryrefslogtreecommitdiff
path: root/src/processing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/processing.rs')
-rw-r--r--src/processing.rs156
1 files changed, 156 insertions, 0 deletions
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(())
+}