From 62249e950c0c5dee0b09b962842543fd60c9ccb4 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 1 Oct 2020 16:44:59 +0200 Subject: move Agent definition to a separate file Just like with Event, we now have Agent defined in its own submodule. The amount of code that it entailed was a lot, so it made sense to split it off, especially with the deserialization being another big chunk of Agent related code in lib.rs The main issue was that the processing submodule accessed private fields of the Agent struct, which is now no longer possible (since processing is no longer a submodule of the module in which Agent is defined). Therefore, some simple crate-public setters for those fields have been added. Those setters are not public because we do not want outside crates to mess with the innards of Agent (yet). Although with (de)serialization being a thing, we need to ensure that we can handle nonsensical values anyway, since we can no longer guarantee that we have full control over all of the values, even without setters. --- src/agent.rs | 732 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 739 +----------------------------------------------------- src/processing.rs | 20 +- 3 files changed, 750 insertions(+), 741 deletions(-) create mode 100644 src/agent.rs diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 0000000..11f81d1 --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,732 @@ +use std::convert::TryFrom; +use std::marker::PhantomData; + +use getset::{CopyGetters, Getters, Setters}; +use num_traits::FromPrimitive; + +use super::{ + gamedata::{EliteSpec, Profession}, + raw, EvtcError, +}; + +/// Player-specific agent data. +/// +/// Player agents are characters controlled by a player and as such, they contain data about the +/// account and character used (name, profession), as well as the squad composition. +/// +/// Note that a `Player` is only the player character itself. Any additional entities that are +/// spawned by the player (clones, illusions, banners, ...) are either a [`Character`][Character] +/// or a [`Gadget`][Gadget]. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] +pub struct Player { + /// The player's profession. + #[get_copy = "pub"] + profession: Profession, + + /// The player's elite specialization, if any is equipped. + #[get_copy = "pub"] + elite: Option, + + character_name: String, + + account_name: String, + + /// The subgroup the player was in. + #[get_copy = "pub"] + subgroup: u8, +} + +impl Player { + /// The player's character name. + pub fn character_name(&self) -> &str { + &self.character_name + } + + /// The player's account name. + /// + /// This includes the leading colon and the 4-digit denominator. + pub fn account_name(&self) -> &str { + &self.account_name + } +} + +/// Gadget-specific agent data. +/// +/// Gadgets are entities that are spawned by certain skills. They are mostly inanimate objects that +/// only exist to achieve a certain skill effect. +/// +/// Examples of this include the [banners](https://wiki.guildwars2.com/wiki/Banner) spawned by +/// Warriors, but also skill effects like the roots created by +/// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] +pub struct Gadget { + /// The id of the gadget. + /// + /// Note that gadgets do not have true ids and the id is generated "through a combination of + /// gadget parameters". + #[get_copy = "pub"] + id: u16, + name: String, +} + +impl Gadget { + /// The name of the gadget. + pub fn name(&self) -> &str { + &self.name + } +} + +/// Character-specific agent data. +/// +/// Characters are NPCs such as the bosses themselves, additional mobs that they spawn, but also +/// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] +pub struct Character { + /// The id of the character. + #[get_copy = "pub"] + id: u16, + name: String, +} + +impl Character { + /// The name of the character. + pub fn name(&self) -> &str { + &self.name + } +} + +/// The type of an agent. +/// +/// arcdps differentiates between three types of agents: [`Player`][Player], +/// [`Character`][Character] and [`Gadget`][Gadget]. This enum unifies handling between them by +/// allowing you to pattern match or use one of the accessor methods. +/// +/// The main way to obtain a `AgentKind` is by using the [`.kind()`][Agent::kind] method on an +/// [`Agent`][Agent]. In cases where you already have a [`raw::Agent`][raw::Agent] available, you +/// can also use the [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits to convert a +/// `raw::Agent` or `&raw::Agent` to a `AgentKind`: +/// +/// ```no_run +/// # use evtclib::{AgentKind, raw}; +/// use std::convert::TryInto; +/// // Get a raw::Agent from somewhere +/// let raw_agent: raw::Agent = panic!(); +/// // Convert it +/// let agent: AgentKind = raw_agent.try_into().unwrap(); +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum AgentKind { + /// The agent is a player. + /// + /// The player-specific data is in the included [`Player`][Player] struct. + Player(Player), + /// The agent is a gadget. + /// + /// The gadget-specific data is in the included [`Gadget`][Gadget] struct. + Gadget(Gadget), + /// The agent is a character. + /// + /// The character-specific data is in the included [`Character`][Character] struct. + Character(Character), +} + +impl AgentKind { + fn from_raw_character(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_character()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Character { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_gadget(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_gadget()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Gadget { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_player(raw_agent: &raw::Agent) -> Result { + assert!(raw_agent.is_player()); + let character_name = raw::cstr_up_to_nul(&raw_agent.name) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..]) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0'; + let elite = if raw_agent.is_elite == 0 { + None + } else { + Some( + EliteSpec::from_u32(raw_agent.is_elite) + .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, + ) + }; + Ok(Player { + profession: Profession::from_u32(raw_agent.prof) + .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, + elite, + character_name: character_name.to_owned(), + account_name: account_name.to_owned(), + subgroup, + }) + } + + /// Accesses the inner [`Player`][Player] struct, if available. + pub fn as_player(&self) -> Option<&Player> { + if let AgentKind::Player(ref player) = *self { + Some(player) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a player. + pub fn is_player(&self) -> bool { + self.as_player().is_some() + } + + /// Accesses the inner [`Gadget`][Gadget] struct, if available. + pub fn as_gadget(&self) -> Option<&Gadget> { + if let AgentKind::Gadget(ref gadget) = *self { + Some(gadget) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a gadget. + pub fn is_gadget(&self) -> bool { + self.as_gadget().is_some() + } + + /// Accesses the inner [`Character`][Character] struct, if available. + pub fn as_character(&self) -> Option<&Character> { + if let AgentKind::Character(ref character) = *self { + Some(character) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a character. + pub fn is_character(&self) -> bool { + self.as_character().is_some() + } +} + +impl TryFrom for AgentKind { + type Error = EvtcError; + /// Convenience method to avoid manual borrowing. + /// + /// Note that this conversion will consume the agent, so if you plan on re-using it, use the + /// `TryFrom<&raw::Agent>` implementation that works with a reference. + fn try_from(raw_agent: raw::Agent) -> Result { + Self::try_from(&raw_agent) + } +} + +impl TryFrom<&raw::Agent> for AgentKind { + type Error = EvtcError; + + /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent]. + /// + /// This automatically discerns between player, gadget and characters. + /// + /// Note that in most cases, you probably want to use `Agent::try_from` or even + /// [`process`][process] instead of this function. + fn try_from(raw_agent: &raw::Agent) -> Result { + if raw_agent.is_character() { + Ok(AgentKind::Character(AgentKind::from_raw_character( + raw_agent, + )?)) + } else if raw_agent.is_gadget() { + Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?)) + } else if raw_agent.is_player() { + Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?)) + } else { + Err(EvtcError::InvalidData) + } + } +} + +/// An agent. +/// +/// Agents in arcdps are very versatile, as a lot of things end up being an "agent". This includes: +/// * Players +/// * Bosses +/// * Any additional mobs that spawn +/// * Mesmer illusions +/// * Ranger spirits, pets +/// * Guardian spirit weapons +/// * ... +/// +/// Generally, you can divide them into three kinds ([`AgentKind`][AgentKind]): +/// * [`Player`][Player]: All players themselves. +/// * [`Character`][Character]: Non-player mobs, including most bosses, "adds" and player-generated +/// characters. +/// * [`Gadget`][Gadget]: Some additional gadgets, such as ley rifts, continuum split, ... +/// +/// All of these agents share some common fields, which are the ones accessible in `Agent`. +/// The kind can be retrieved using [`.kind()`][Agent::kind], which can be matched on. +/// +/// # Obtaining an agent +/// +/// The normal way to obtain the agents is to use the [`.agents()`](Log::agents) method on a +/// [`Log`][Log], or one of the other accessor methods (like [`.players()`][Log::players] or +/// [`.agent_by_addr()`][Log::agent_by_addr]). +/// +/// In the cases where you already have a [`raw::Agent`][raw::Agent] available, you can also +/// convert it to an [`Agent`][Agent] by using the standard +/// [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits: +/// +/// ```no_run +/// # use evtclib::{Agent, raw}; +/// use std::convert::TryInto; +/// let raw_agent: raw::Agent = panic!(); +/// let agent: Agent = raw_agent.try_into().unwrap(); +/// ``` +/// +/// Note that you can convert references as well, so if you plan on re-using the raw agent +/// afterwards, you should opt for `Agent::try_from(&raw_agent)` instead. +/// +/// # The `Kind` parameter +/// +/// The type parameter is not actually used and only exists at the type level. It can be used to +/// tag `Agent`s containing a known kind. For example, `Agent` implements +/// [`.player()`][Agent::player], which returns a `&Player` directly (instead of a +/// `Option<&Player>`). This works because such tagged `Agent`s can only be constructed (safely) +/// using [`.as_player()`][Agent::as_player], [`.as_gadget()`][Agent::as_gadget] or +/// [`.as_character()`][Agent::as_character]. This is useful since functions like +/// [`Log::players`][Log::players], which already filter only players, don't require the consumer +/// to do another check/pattern match for the right agent kind. +/// +/// The unit type `()` is used to tag `Agent`s which contain an undetermined type, and it is the +/// default if you write `Agent` without any parameters. +/// +/// The downside is that methods which work on `Agent`s theoretically should be generic over +/// `Kind`. An escape hatch is the method [`.erase()`][Agent::erase], which erases the kind +/// information and produces the default `Agent<()>`. Functions/methods that only take `Agent<()>` +/// can therefore be used by any other agent as well. +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Getters, CopyGetters, Setters)] +// For the reasoning of #[repr(C)] see Agent::transmute. +#[repr(C)] +pub struct Agent { + /// The address of this agent. + /// + /// This is not actually the address of the in-memory Rust object, but rather a serialization + /// detail of arcdps. You should consider this as an opaque number and only compare it to other + /// agent addresses. + #[getset(get_copy = "pub", set = "pub(crate)")] + addr: u64, + + /// The kind of this agent. + #[getset(get = "pub", set = "pub(crate)")] + kind: AgentKind, + + /// The toughness of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// toughness relative to the other people in the squad. + /// + /// 0 means lowest toughness, 10 means highest toughness. + #[get_copy = "pub"] + toughness: i16, + + /// The concentration of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// concentration relative to the other people in the squad. + /// + /// 0 means lowest concentration, 10 means highest concentration. + #[getset(get_copy = "pub", set = "pub(crate)")] + concentration: i16, + + /// The healing power of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// healing power relative to the other people in the squad. + /// + /// 0 means lowest healing power, 10 means highest healing power. + #[getset(get_copy = "pub", set = "pub(crate)")] + healing: i16, + + /// The condition damage of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// condition damage relative to the other people in the squad. + /// + /// 0 means lowest condition damage, 10 means highest condition damage. + #[getset(get_copy = "pub", set = "pub(crate)")] + condition: i16, + + /// The instance ID of this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + instance_id: u16, + + /// The timestamp of the first event entry with this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + first_aware: u64, + + /// The timestamp of the last event entry with this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + last_aware: u64, + + /// The master agent's address. + #[getset(get_copy = "pub", set = "pub(crate)")] + master_agent: Option, + + #[cfg_attr(feature = "serde", serde(skip_serializing))] + phantom_data: PhantomData, +} + +// We could derive this, however that would derive Deserialize generically for Agent, where T is +// deserializable. In particular, this would mean that you could deserialize Agent, +// Agent and Agent directly, which would not be too bad - the problem is that serde +// has no way of knowing if the serialized agent actually is a character/player/gadget agent, as +// that information only exists on the type level. This meant that you could "coerce" agents into +// the wrong type, safely, using a serialization followed by a deserialization. +// Now this doesn't actually lead to memory unsafety or other bad behaviour, but it could mean that +// the program would panic if you tried to access a non-existing field, e.g. by calling +// Agent::id() on a non-character agent. +// In order to prevent this, we manually implement Deserialize only for Agent<()>, so that the +// usual conversion functions with the proper checks have to be used. +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Agent { + fn deserialize>(deserializer: D) -> Result { + use serde::de::{self, MapAccess, Visitor}; + use std::fmt; + + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Addr, + Kind, + Toughness, + Concentration, + Healing, + Condition, + InstanceId, + FirstAware, + LastAware, + MasterAgent, + }; + + struct AgentVisitor; + + impl<'de> Visitor<'de> for AgentVisitor { + type Value = Agent; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Agent") + } + + fn visit_map>(self, mut map: V) -> Result { + let mut addr = None; + let mut kind = None; + let mut toughness = None; + let mut concentration = None; + let mut healing = None; + let mut condition = None; + let mut instance_id = None; + let mut first_aware = None; + let mut last_aware = None; + let mut master_agent = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Addr => { + if addr.is_some() { + return Err(de::Error::duplicate_field("addr")); + } + addr = Some(map.next_value()?); + } + Field::Kind => { + if kind.is_some() { + return Err(de::Error::duplicate_field("kind")); + } + kind = Some(map.next_value()?); + } + Field::Toughness => { + if toughness.is_some() { + return Err(de::Error::duplicate_field("toughness")); + } + toughness = Some(map.next_value()?); + } + Field::Concentration => { + if concentration.is_some() { + return Err(de::Error::duplicate_field("concentration")); + } + concentration = Some(map.next_value()?); + } + Field::Healing => { + if healing.is_some() { + return Err(de::Error::duplicate_field("healing")); + } + healing = Some(map.next_value()?); + } + Field::Condition => { + if condition.is_some() { + return Err(de::Error::duplicate_field("condition")); + } + condition = Some(map.next_value()?); + } + Field::InstanceId => { + if instance_id.is_some() { + return Err(de::Error::duplicate_field("instance_id")); + } + instance_id = Some(map.next_value()?); + } + Field::FirstAware => { + if first_aware.is_some() { + return Err(de::Error::duplicate_field("first_aware")); + } + first_aware = Some(map.next_value()?); + } + Field::LastAware => { + if last_aware.is_some() { + return Err(de::Error::duplicate_field("last_aware")); + } + last_aware = Some(map.next_value()?); + } + Field::MasterAgent => { + if master_agent.is_some() { + return Err(de::Error::duplicate_field("master_agent")); + } + master_agent = Some(map.next_value()?); + } + } + } + + Ok(Agent { + addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?, + kind: kind.ok_or_else(|| de::Error::missing_field("kind"))?, + toughness: toughness.ok_or_else(|| de::Error::missing_field("toughness"))?, + concentration: concentration + .ok_or_else(|| de::Error::missing_field("concentration"))?, + healing: healing.ok_or_else(|| de::Error::missing_field("healing"))?, + condition: condition.ok_or_else(|| de::Error::missing_field("condition"))?, + instance_id: instance_id + .ok_or_else(|| de::Error::missing_field("instance_id"))?, + first_aware: first_aware + .ok_or_else(|| de::Error::missing_field("first_aware"))?, + last_aware: last_aware.ok_or_else(|| de::Error::missing_field("last_aware"))?, + master_agent: master_agent + .ok_or_else(|| de::Error::missing_field("master_agent"))?, + phantom_data: PhantomData, + }) + } + }; + + const FIELDS: &'static [&'static str] = &[ + "addr", + "kind", + "toughness", + "concentration", + "healing", + "condition", + "instance_id", + "first_aware", + "last_aware", + "master_agent", + ]; + deserializer.deserialize_struct("Agent", FIELDS, AgentVisitor) + } +} + +impl TryFrom<&raw::Agent> for Agent { + type Error = EvtcError; + + /// Parse a raw agent. + fn try_from(raw_agent: &raw::Agent) -> Result { + let kind = AgentKind::try_from(raw_agent)?; + Ok(Agent { + addr: raw_agent.addr, + kind, + toughness: raw_agent.toughness, + concentration: raw_agent.concentration, + healing: raw_agent.healing, + condition: raw_agent.condition, + instance_id: 0, + first_aware: 0, + last_aware: u64::max_value(), + master_agent: None, + phantom_data: PhantomData, + }) + } +} + +impl TryFrom for Agent { + type Error = EvtcError; + + /// Convenience method to avoid manual borrowing. + /// + /// Note that this conversion will consume the agent, so if you plan on re-using it, use the + /// `TryFrom<&raw::Agent>` implementation that works with a reference. + fn try_from(raw_agent: raw::Agent) -> Result { + Agent::try_from(&raw_agent) + } +} + +impl Agent { + /// Unconditionally change the tagged type. + #[inline] + fn transmute(&self) -> &Agent { + // Beware, unsafe code ahead! + // + // What are we doing here? + // In Agent, T is a marker type that only exists at the type level. There is no actual + // value of type T being held, instead, we use PhantomData under the hood. This is so we + // can implement special methods on Agent, Agent and Agent, + // which allows us in some cases to avoid the "second check" (e.g. Log::players() can + // return Agent, as the function already makes sure all returned agents are + // players). This makes the interface more ergonomical, as we can prove to the type checker + // at compile time that a given Agent has a certain AgentKind. + // + // Why is this safe? + // PhantomData (which is what Agent boils down to) is a zero-sized type, which means + // it does not actually change the layout of the struct. There is some discussion in [1], + // which suggests that this is true for #[repr(C)] structs (which Agent is). We can + // therefore safely transmute from Agent to Agent, for any U and T. + // + // Can this lead to unsafety? + // No, the actual data access is still done through safe rust and a if-let. In the worst + // case it can lead to an unexpected panic, but the "guarantee" made by T is rather weak in + // that regard. + // + // What are the alternatives? + // None, as far as I'm aware. Going from Agent to Agent is possible in safe Rust by + // destructuring the struct, or alternatively by [2] (if it would be implemented). However, + // when dealing with references, there seems to be no way to safely go from Agent to + // Agent, even if they share the same layout. + // + // [1]: https://www.reddit.com/r/rust/comments/avrbvc/is_it_safe_to_transmute_foox_to_fooy_if_the/ + // [2]: https://github.com/rust-lang/rfcs/pull/2528 + unsafe { &*(self as *const Agent as *const Agent) } + } + + /// Erase any extra information about the contained agent kind. + #[inline] + pub fn erase(&self) -> &Agent { + self.transmute() + } + + /// Try to convert this `Agent` to an `Agent` representing a `Player`. + #[inline] + pub fn as_player(&self) -> Option<&Agent> { + if self.kind.is_player() { + Some(self.transmute()) + } else { + None + } + } + + /// Try to convert this `Agent` to an `Agent` representing a `Gadget`. + #[inline] + pub fn as_gadget(&self) -> Option<&Agent> { + if self.kind.is_gadget() { + Some(self.transmute()) + } else { + None + } + } + + /// Try to convert this `Agent` to an `Agent` representing a `Character`. + #[inline] + pub fn as_character(&self) -> Option<&Agent> { + if self.kind.is_character() { + Some(self.transmute()) + } else { + None + } + } +} + +impl Agent { + /// Directly access the underlying player data. + #[inline] + pub fn player(&self) -> &Player { + self.kind.as_player().expect("Agent had no player!") + } + + /// Shorthand to get the player's account name. + #[inline] + pub fn account_name(&self) -> &str { + self.player().account_name() + } + + /// Shorthand to get the player's character name. + #[inline] + pub fn character_name(&self) -> &str { + self.player().character_name() + } + + /// Shorthand to get the player's elite specialization. + #[inline] + pub fn elite(&self) -> Option { + self.player().elite() + } + + /// Shorthand to get the player's profession. + #[inline] + pub fn profession(&self) -> Profession { + self.player().profession() + } + + /// Shorthand to get the player's subgroup. + #[inline] + pub fn subgroup(&self) -> u8 { + self.player().subgroup() + } +} + +impl Agent { + /// Directly access the underlying gadget data. + #[inline] + pub fn gadget(&self) -> &Gadget { + self.kind.as_gadget().expect("Agent had no gadget!") + } + + /// Shorthand to get the gadget's id. + #[inline] + pub fn id(&self) -> u16 { + self.gadget().id() + } + + /// Shorthand to get the gadget's name. + #[inline] + pub fn name(&self) -> &str { + self.gadget().name() + } +} + +impl Agent { + /// Directly access the underlying character data. + #[inline] + pub fn character(&self) -> &Character { + self.kind + .as_character() + .expect("Agent had no character!") + } + + /// Shorthand to get the character's id. + #[inline] + pub fn id(&self) -> u16 { + self.character().id() + } + + /// Shorthand to get the character's name. + #[inline] + pub fn name(&self) -> &str { + self.character().name() + } +} diff --git a/src/lib.rs b/src/lib.rs index 5ba6340..bb71c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,15 +88,14 @@ //! While there are legitimate use cases for writing/modification support, they are currently not //! implemented (but might be in a future version). -use std::convert::TryFrom; -use std::marker::PhantomData; - -use getset::{CopyGetters, Getters}; use num_traits::FromPrimitive; use thiserror::Error; pub mod raw; +mod agent; +pub use agent::{Agent, AgentKind, Character, Gadget, Player}; + pub mod event; pub use event::{Event, EventKind}; @@ -137,728 +136,6 @@ pub enum EvtcError { Utf8Error(#[from] std::str::Utf8Error), } -/// Player-specific agent data. -/// -/// Player agents are characters controlled by a player and as such, they contain data about the -/// account and character used (name, profession), as well as the squad composition. -/// -/// Note that a `Player` is only the player character itself. Any additional entities that are -/// spawned by the player (clones, illusions, banners, ...) are either a [`Character`][Character] -/// or a [`Gadget`][Gadget]. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] -pub struct Player { - /// The player's profession. - #[get_copy = "pub"] - profession: Profession, - - /// The player's elite specialization, if any is equipped. - #[get_copy = "pub"] - elite: Option, - - character_name: String, - - account_name: String, - - /// The subgroup the player was in. - #[get_copy = "pub"] - subgroup: u8, -} - -impl Player { - /// The player's character name. - pub fn character_name(&self) -> &str { - &self.character_name - } - - /// The player's account name. - /// - /// This includes the leading colon and the 4-digit denominator. - pub fn account_name(&self) -> &str { - &self.account_name - } -} - -/// Gadget-specific agent data. -/// -/// Gadgets are entities that are spawned by certain skills. They are mostly inanimate objects that -/// only exist to achieve a certain skill effect. -/// -/// Examples of this include the [banners](https://wiki.guildwars2.com/wiki/Banner) spawned by -/// Warriors, but also skill effects like the roots created by -/// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] -pub struct Gadget { - /// The id of the gadget. - /// - /// Note that gadgets do not have true ids and the id is generated "through a combination of - /// gadget parameters". - #[get_copy = "pub"] - id: u16, - name: String, -} - -impl Gadget { - /// The name of the gadget. - pub fn name(&self) -> &str { - &self.name - } -} - -/// Character-specific agent data. -/// -/// Characters are NPCs such as the bosses themselves, additional mobs that they spawn, but also -/// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] -pub struct Character { - /// The id of the character. - #[get_copy = "pub"] - id: u16, - name: String, -} - -impl Character { - /// The name of the character. - pub fn name(&self) -> &str { - &self.name - } -} - -/// The type of an agent. -/// -/// arcdps differentiates between three types of agents: [`Player`][Player], -/// [`Character`][Character] and [`Gadget`][Gadget]. This enum unifies handling between them by -/// allowing you to pattern match or use one of the accessor methods. -/// -/// The main way to obtain a `AgentKind` is by using the [`.kind()`][Agent::kind] method on an -/// [`Agent`][Agent]. In cases where you already have a [`raw::Agent`][raw::Agent] available, you -/// can also use the [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits to convert a -/// `raw::Agent` or `&raw::Agent` to a `AgentKind`: -/// -/// ```no_run -/// # use evtclib::{AgentKind, raw}; -/// use std::convert::TryInto; -/// // Get a raw::Agent from somewhere -/// let raw_agent: raw::Agent = panic!(); -/// // Convert it -/// let agent: AgentKind = raw_agent.try_into().unwrap(); -/// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum AgentKind { - /// The agent is a player. - /// - /// The player-specific data is in the included [`Player`][Player] struct. - Player(Player), - /// The agent is a gadget. - /// - /// The gadget-specific data is in the included [`Gadget`][Gadget] struct. - Gadget(Gadget), - /// The agent is a character. - /// - /// The character-specific data is in the included [`Character`][Character] struct. - Character(Character), -} - -impl AgentKind { - fn from_raw_character(raw_agent: &raw::Agent) -> Result { - assert!(raw_agent.is_character()); - let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; - Ok(Character { - id: raw_agent.prof as u16, - name: name.to_str()?.to_owned(), - }) - } - - fn from_raw_gadget(raw_agent: &raw::Agent) -> Result { - assert!(raw_agent.is_gadget()); - let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; - Ok(Gadget { - id: raw_agent.prof as u16, - name: name.to_str()?.to_owned(), - }) - } - - fn from_raw_player(raw_agent: &raw::Agent) -> Result { - assert!(raw_agent.is_player()); - let character_name = raw::cstr_up_to_nul(&raw_agent.name) - .ok_or(EvtcError::InvalidData)? - .to_str()?; - let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..]) - .ok_or(EvtcError::InvalidData)? - .to_str()?; - let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0'; - let elite = if raw_agent.is_elite == 0 { - None - } else { - Some( - EliteSpec::from_u32(raw_agent.is_elite) - .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, - ) - }; - Ok(Player { - profession: Profession::from_u32(raw_agent.prof) - .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, - elite, - character_name: character_name.to_owned(), - account_name: account_name.to_owned(), - subgroup, - }) - } - - /// Accesses the inner [`Player`][Player] struct, if available. - pub fn as_player(&self) -> Option<&Player> { - if let AgentKind::Player(ref player) = *self { - Some(player) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a player. - pub fn is_player(&self) -> bool { - self.as_player().is_some() - } - - /// Accesses the inner [`Gadget`][Gadget] struct, if available. - pub fn as_gadget(&self) -> Option<&Gadget> { - if let AgentKind::Gadget(ref gadget) = *self { - Some(gadget) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a gadget. - pub fn is_gadget(&self) -> bool { - self.as_gadget().is_some() - } - - /// Accesses the inner [`Character`][Character] struct, if available. - pub fn as_character(&self) -> Option<&Character> { - if let AgentKind::Character(ref character) = *self { - Some(character) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a character. - pub fn is_character(&self) -> bool { - self.as_character().is_some() - } -} - -impl TryFrom for AgentKind { - type Error = EvtcError; - /// Convenience method to avoid manual borrowing. - /// - /// Note that this conversion will consume the agent, so if you plan on re-using it, use the - /// `TryFrom<&raw::Agent>` implementation that works with a reference. - fn try_from(raw_agent: raw::Agent) -> Result { - Self::try_from(&raw_agent) - } -} - -impl TryFrom<&raw::Agent> for AgentKind { - type Error = EvtcError; - - /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent]. - /// - /// This automatically discerns between player, gadget and characters. - /// - /// Note that in most cases, you probably want to use `Agent::try_from` or even - /// [`process`][process] instead of this function. - fn try_from(raw_agent: &raw::Agent) -> Result { - if raw_agent.is_character() { - Ok(AgentKind::Character(AgentKind::from_raw_character( - raw_agent, - )?)) - } else if raw_agent.is_gadget() { - Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?)) - } else if raw_agent.is_player() { - Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?)) - } else { - Err(EvtcError::InvalidData) - } - } -} - -/// An agent. -/// -/// Agents in arcdps are very versatile, as a lot of things end up being an "agent". This includes: -/// * Players -/// * Bosses -/// * Any additional mobs that spawn -/// * Mesmer illusions -/// * Ranger spirits, pets -/// * Guardian spirit weapons -/// * ... -/// -/// Generally, you can divide them into three kinds ([`AgentKind`][AgentKind]): -/// * [`Player`][Player]: All players themselves. -/// * [`Character`][Character]: Non-player mobs, including most bosses, "adds" and player-generated -/// characters. -/// * [`Gadget`][Gadget]: Some additional gadgets, such as ley rifts, continuum split, ... -/// -/// All of these agents share some common fields, which are the ones accessible in `Agent`. -/// The kind can be retrieved using [`.kind()`][Agent::kind], which can be matched on. -/// -/// # Obtaining an agent -/// -/// The normal way to obtain the agents is to use the [`.agents()`](Log::agents) method on a -/// [`Log`][Log], or one of the other accessor methods (like [`.players()`][Log::players] or -/// [`.agent_by_addr()`][Log::agent_by_addr]). -/// -/// In the cases where you already have a [`raw::Agent`][raw::Agent] available, you can also -/// convert it to an [`Agent`][Agent] by using the standard -/// [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits: -/// -/// ```no_run -/// # use evtclib::{Agent, raw}; -/// use std::convert::TryInto; -/// let raw_agent: raw::Agent = panic!(); -/// let agent: Agent = raw_agent.try_into().unwrap(); -/// ``` -/// -/// Note that you can convert references as well, so if you plan on re-using the raw agent -/// afterwards, you should opt for `Agent::try_from(&raw_agent)` instead. -/// -/// # The `Kind` parameter -/// -/// The type parameter is not actually used and only exists at the type level. It can be used to -/// tag `Agent`s containing a known kind. For example, `Agent` implements -/// [`.player()`][Agent::player], which returns a `&Player` directly (instead of a -/// `Option<&Player>`). This works because such tagged `Agent`s can only be constructed (safely) -/// using [`.as_player()`][Agent::as_player], [`.as_gadget()`][Agent::as_gadget] or -/// [`.as_character()`][Agent::as_character]. This is useful since functions like -/// [`Log::players`][Log::players], which already filter only players, don't require the consumer -/// to do another check/pattern match for the right agent kind. -/// -/// The unit type `()` is used to tag `Agent`s which contain an undetermined type, and it is the -/// default if you write `Agent` without any parameters. -/// -/// The downside is that methods which work on `Agent`s theoretically should be generic over -/// `Kind`. An escape hatch is the method [`.erase()`][Agent::erase], which erases the kind -/// information and produces the default `Agent<()>`. Functions/methods that only take `Agent<()>` -/// can therefore be used by any other agent as well. -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, Getters, CopyGetters)] -// For the reasoning of #[repr(C)] see Agent::transmute. -#[repr(C)] -pub struct Agent { - /// The address of this agent. - /// - /// This is not actually the address of the in-memory Rust object, but rather a serialization - /// detail of arcdps. You should consider this as an opaque number and only compare it to other - /// agent addresses. - #[get_copy = "pub"] - addr: u64, - - /// The kind of this agent. - #[get = "pub"] - kind: AgentKind, - - /// The toughness of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// toughness relative to the other people in the squad. - /// - /// 0 means lowest toughness, 10 means highest toughness. - #[get_copy = "pub"] - toughness: i16, - - /// The concentration of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// concentration relative to the other people in the squad. - /// - /// 0 means lowest concentration, 10 means highest concentration. - #[get_copy = "pub"] - concentration: i16, - - /// The healing power of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// healing power relative to the other people in the squad. - /// - /// 0 means lowest healing power, 10 means highest healing power. - #[get_copy = "pub"] - healing: i16, - - /// The condition damage of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// condition damage relative to the other people in the squad. - /// - /// 0 means lowest condition damage, 10 means highest condition damage. - #[get_copy = "pub"] - condition: i16, - - /// The instance ID of this agent. - #[get_copy = "pub"] - instance_id: u16, - - /// The timestamp of the first event entry with this agent. - #[get_copy = "pub"] - first_aware: u64, - - /// The timestamp of the last event entry with this agent. - #[get_copy = "pub"] - last_aware: u64, - - /// The master agent's address. - #[get_copy = "pub"] - master_agent: Option, - - #[cfg_attr(feature = "serde", serde(skip_serializing))] - phantom_data: PhantomData, -} - -// We could derive this, however that would derive Deserialize generically for Agent, where T is -// deserializable. In particular, this would mean that you could deserialize Agent, -// Agent and Agent directly, which would not be too bad - the problem is that serde -// has no way of knowing if the serialized agent actually is a character/player/gadget agent, as -// that information only exists on the type level. This meant that you could "coerce" agents into -// the wrong type, safely, using a serialization followed by a deserialization. -// Now this doesn't actually lead to memory unsafety or other bad behaviour, but it could mean that -// the program would panic if you tried to access a non-existing field, e.g. by calling -// Agent::id() on a non-character agent. -// In order to prevent this, we manually implement Deserialize only for Agent<()>, so that the -// usual conversion functions with the proper checks have to be used. -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Agent { - fn deserialize>(deserializer: D) -> Result { - use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; - use std::fmt; - - #[derive(serde::Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Addr, - Kind, - Toughness, - Concentration, - Healing, - Condition, - InstanceId, - FirstAware, - LastAware, - MasterAgent, - }; - - struct AgentVisitor; - - impl<'de> Visitor<'de> for AgentVisitor { - type Value = Agent; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct Agent") - } - - fn visit_map>(self, mut map: V) -> Result { - let mut addr = None; - let mut kind = None; - let mut toughness = None; - let mut concentration = None; - let mut healing = None; - let mut condition = None; - let mut instance_id = None; - let mut first_aware = None; - let mut last_aware = None; - let mut master_agent = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Addr => { - if addr.is_some() { - return Err(de::Error::duplicate_field("addr")); - } - addr = Some(map.next_value()?); - } - Field::Kind => { - if kind.is_some() { - return Err(de::Error::duplicate_field("kind")); - } - kind = Some(map.next_value()?); - } - Field::Toughness => { - if toughness.is_some() { - return Err(de::Error::duplicate_field("toughness")); - } - toughness = Some(map.next_value()?); - } - Field::Concentration => { - if concentration.is_some() { - return Err(de::Error::duplicate_field("concentration")); - } - concentration = Some(map.next_value()?); - } - Field::Healing => { - if healing.is_some() { - return Err(de::Error::duplicate_field("healing")); - } - healing = Some(map.next_value()?); - } - Field::Condition => { - if condition.is_some() { - return Err(de::Error::duplicate_field("condition")); - } - condition = Some(map.next_value()?); - } - Field::InstanceId=> { - if instance_id.is_some() { - return Err(de::Error::duplicate_field("instance_id")); - } - instance_id = Some(map.next_value()?); - } - Field::FirstAware=> { - if first_aware.is_some() { - return Err(de::Error::duplicate_field("first_aware")); - } - first_aware = Some(map.next_value()?); - } - Field::LastAware => { - if last_aware.is_some() { - return Err(de::Error::duplicate_field("last_aware")); - } - last_aware = Some(map.next_value()?); - } - Field::MasterAgent => { - if master_agent.is_some() { - return Err(de::Error::duplicate_field("master_agent")); - } - master_agent = Some(map.next_value()?); - } - } - } - - Ok(Agent { - addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?, - kind: kind.ok_or_else(|| de::Error::missing_field("kind"))?, - toughness: toughness.ok_or_else(|| de::Error::missing_field("toughness"))?, - concentration: concentration - .ok_or_else(|| de::Error::missing_field("concentration"))?, - healing: healing.ok_or_else(|| de::Error::missing_field("healing"))?, - condition: condition.ok_or_else(|| de::Error::missing_field("condition"))?, - instance_id: instance_id - .ok_or_else(|| de::Error::missing_field("instance_id"))?, - first_aware: first_aware - .ok_or_else(|| de::Error::missing_field("first_aware"))?, - last_aware: last_aware.ok_or_else(|| de::Error::missing_field("last_aware"))?, - master_agent: master_agent - .ok_or_else(|| de::Error::missing_field("master_agent"))?, - phantom_data: PhantomData, - }) - } - }; - - const FIELDS: &'static [&'static str] = &[ - "addr", - "kind", - "toughness", - "concentration", - "healing", - "condition", - "instance_id", - "first_aware", - "last_aware", - "master_agent", - ]; - deserializer.deserialize_struct("Agent", FIELDS, AgentVisitor) - } -} - -impl TryFrom<&raw::Agent> for Agent { - type Error = EvtcError; - - /// Parse a raw agent. - fn try_from(raw_agent: &raw::Agent) -> Result { - let kind = AgentKind::try_from(raw_agent)?; - Ok(Agent { - addr: raw_agent.addr, - kind, - toughness: raw_agent.toughness, - concentration: raw_agent.concentration, - healing: raw_agent.healing, - condition: raw_agent.condition, - instance_id: 0, - first_aware: 0, - last_aware: u64::max_value(), - master_agent: None, - phantom_data: PhantomData, - }) - } -} - -impl TryFrom for Agent { - type Error = EvtcError; - - /// Convenience method to avoid manual borrowing. - /// - /// Note that this conversion will consume the agent, so if you plan on re-using it, use the - /// `TryFrom<&raw::Agent>` implementation that works with a reference. - fn try_from(raw_agent: raw::Agent) -> Result { - Agent::try_from(&raw_agent) - } -} - -impl Agent { - /// Unconditionally change the tagged type. - #[inline] - fn transmute(&self) -> &Agent { - // Beware, unsafe code ahead! - // - // What are we doing here? - // In Agent, T is a marker type that only exists at the type level. There is no actual - // value of type T being held, instead, we use PhantomData under the hood. This is so we - // can implement special methods on Agent, Agent and Agent, - // which allows us in some cases to avoid the "second check" (e.g. Log::players() can - // return Agent, as the function already makes sure all returned agents are - // players). This makes the interface more ergonomical, as we can prove to the type checker - // at compile time that a given Agent has a certain AgentKind. - // - // Why is this safe? - // PhantomData (which is what Agent boils down to) is a zero-sized type, which means - // it does not actually change the layout of the struct. There is some discussion in [1], - // which suggests that this is true for #[repr(C)] structs (which Agent is). We can - // therefore safely transmute from Agent to Agent, for any U and T. - // - // Can this lead to unsafety? - // No, the actual data access is still done through safe rust and a if-let. In the worst - // case it can lead to an unexpected panic, but the "guarantee" made by T is rather weak in - // that regard. - // - // What are the alternatives? - // None, as far as I'm aware. Going from Agent to Agent is possible in safe Rust by - // destructuring the struct, or alternatively by [2] (if it would be implemented). However, - // when dealing with references, there seems to be no way to safely go from Agent to - // Agent, even if they share the same layout. - // - // [1]: https://www.reddit.com/r/rust/comments/avrbvc/is_it_safe_to_transmute_foox_to_fooy_if_the/ - // [2]: https://github.com/rust-lang/rfcs/pull/2528 - unsafe { &*(self as *const Agent as *const Agent) } - } - - /// Erase any extra information about the contained agent kind. - #[inline] - pub fn erase(&self) -> &Agent { - self.transmute() - } - - /// Try to convert this `Agent` to an `Agent` representing a `Player`. - #[inline] - pub fn as_player(&self) -> Option<&Agent> { - if self.kind.is_player() { - Some(self.transmute()) - } else { - None - } - } - - /// Try to convert this `Agent` to an `Agent` representing a `Gadget`. - #[inline] - pub fn as_gadget(&self) -> Option<&Agent> { - if self.kind.is_gadget() { - Some(self.transmute()) - } else { - None - } - } - - /// Try to convert this `Agent` to an `Agent` representing a `Character`. - #[inline] - pub fn as_character(&self) -> Option<&Agent> { - if self.kind.is_character() { - Some(self.transmute()) - } else { - None - } - } -} - -impl Agent { - /// Directly access the underlying player data. - #[inline] - pub fn player(&self) -> &Player { - self.kind.as_player().expect("Agent had no player!") - } - - /// Shorthand to get the player's account name. - #[inline] - pub fn account_name(&self) -> &str { - self.player().account_name() - } - - /// Shorthand to get the player's character name. - #[inline] - pub fn character_name(&self) -> &str { - self.player().character_name() - } - - /// Shorthand to get the player's elite specialization. - #[inline] - pub fn elite(&self) -> Option { - self.player().elite() - } - - /// Shorthand to get the player's profession. - #[inline] - pub fn profession(&self) -> Profession { - self.player().profession() - } - - /// Shorthand to get the player's subgroup. - #[inline] - pub fn subgroup(&self) -> u8 { - self.player().subgroup() - } -} - -impl Agent { - /// Directly access the underlying gadget data. - #[inline] - pub fn gadget(&self) -> &Gadget { - self.kind.as_gadget().expect("Agent had no gadget!") - } - - /// Shorthand to get the gadget's id. - #[inline] - pub fn id(&self) -> u16 { - self.gadget().id() - } - - /// Shorthand to get the gadget's name. - #[inline] - pub fn name(&self) -> &str { - self.gadget().name() - } -} - -impl Agent { - /// Directly access the underlying character data. - #[inline] - pub fn character(&self) -> &Character { - self.kind - .as_character() - .expect("Agent had no character!") - } - - /// Shorthand to get the character's id. - #[inline] - pub fn id(&self) -> u16 { - self.character().id() - } - - /// Shorthand to get the character's name. - #[inline] - pub fn name(&self) -> &str { - self.character().name() - } -} - /// A fully processed log file. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] @@ -877,12 +154,12 @@ impl Log { /// Return an agent based on its address. pub fn agent_by_addr(&self, addr: u64) -> Option<&Agent> { - self.agents.iter().find(|a| a.addr == addr) + self.agents.iter().find(|a| a.addr() == addr) } /// Return an agent based on the instance ID. pub fn agent_by_instance_id(&self, instance_id: u16) -> Option<&Agent> { - self.agents.iter().find(|a| a.instance_id == instance_id) + self.agents.iter().find(|a| a.instance_id() == instance_id) } /// Return the master agent of the given agent. @@ -890,7 +167,7 @@ impl Log { /// * `addr` - The address of the agent which to get the master for. pub fn master_agent(&self, addr: u64) -> Option<&Agent> { self.agent_by_addr(addr) - .and_then(|a| a.master_agent) + .and_then(|a| a.master_agent()) .and_then(|a| self.agent_by_addr(a)) } @@ -915,7 +192,7 @@ impl Log { /// and Xera. pub fn boss(&self) -> &Agent { self.characters() - .find(|c| c.character().id == self.boss_id) + .find(|c| c.character().id() == self.boss_id) .map(Agent::erase) .expect("Boss has no agent!") } @@ -930,7 +207,7 @@ impl Log { .map(Encounter::bosses) .unwrap_or(&[] as &[_]); self.characters() - .filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id)) + .filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id())) .map(Agent::erase) .collect() } diff --git a/src/processing.rs b/src/processing.rs index 722440b..11bdda7 100644 --- a/src/processing.rs +++ b/src/processing.rs @@ -110,7 +110,7 @@ fn setup_agents(data: &raw::Evtc) -> Result, EvtcError> { fn get_agent_by_addr(agents: &mut [Agent], addr: u64) -> Option<&mut Agent> { for agent in agents { - if agent.addr == addr { + if agent.addr() == addr { return Some(agent); } } @@ -121,11 +121,11 @@ fn set_agent_awares(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcEr 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.set_instance_id(event.src_instid); + if current_agent.first_aware() == 0 { + current_agent.set_first_aware(event.time); } - current_agent.last_aware = event.time; + current_agent.set_last_aware(event.time); } } } @@ -137,17 +137,17 @@ fn set_agent_masters(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcE 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 + if agent.instance_id() == event.src_master_instid + && agent.first_aware() < event.time + && event.time < agent.last_aware() { - master_addr = Some(agent.addr); + 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); + current_slave.set_master_agent(Some(master_addr)); } } } -- cgit v1.2.3