From 902e9dd1e384c704a0ab58bbcc38f6b0b5611044 Mon Sep 17 00:00:00 2001 From: Nick Babcock Date: Sun, 15 Dec 2024 08:10:01 -0600 Subject: [PATCH] Refactor out object index map to own struct IMO, better to have this concept formalized to make the code a bit easier to read when I come back to it in a couple years :) --- src/network/attributes.rs | 42 +++++++--------- src/network/mod.rs | 98 +++++++++++-------------------------- src/network/models.rs | 2 +- src/network/object_index.rs | 66 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 93 deletions(-) create mode 100644 src/network/object_index.rs diff --git a/src/network/attributes.rs b/src/network/attributes.rs index 5a5df6c..1bbf0d7 100644 --- a/src/network/attributes.rs +++ b/src/network/attributes.rs @@ -1,10 +1,11 @@ use crate::bits::RlBits; use crate::errors::AttributeError; -use crate::network::{ActorId, ObjectId, Quaternion, Rotation, Vector3f, VersionTriplet}; +use crate::network::{ + ActorId, ObjectId, ObjectIndex, Quaternion, Rotation, Vector3f, VersionTriplet, +}; use crate::parsing_utils::{decode_utf16, decode_windows1252}; use bitter::{BitReader, LittleEndianReader}; use encoding_rs::WINDOWS_1252; -use fnv::FnvHashMap; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum AttributeTag { @@ -425,27 +426,22 @@ pub(crate) struct ProductValueDecoder { } impl ProductValueDecoder { - pub fn create(version: VersionTriplet, name_obj_ind: &FnvHashMap<&str, ObjectId>) -> Self { - let color_ind = name_obj_ind - .get("TAGame.ProductAttribute_UserColor_TA") - .copied() - .unwrap_or(ObjectId(0)); - let painted_ind = name_obj_ind - .get("TAGame.ProductAttribute_Painted_TA") - .copied() - .unwrap_or(ObjectId(0)); - let title_ind = name_obj_ind - .get("TAGame.ProductAttribute_TitleID_TA") - .copied() - .unwrap_or(ObjectId(0)); - let special_edition_ind = name_obj_ind - .get("TAGame.ProductAttribute_SpecialEdition_TA") - .copied() - .unwrap_or(ObjectId(0)); - let team_edition_ind = name_obj_ind - .get("TAGame.ProductAttribute_TeamEdition_TA") - .copied() - .unwrap_or(ObjectId(0)); + pub fn create(version: VersionTriplet, object_index: &ObjectIndex) -> Self { + let color_ind = object_index + .primary_by_name("TAGame.ProductAttribute_UserColor_TA") + .unwrap_or_default(); + let painted_ind = object_index + .primary_by_name("TAGame.ProductAttribute_Painted_TA") + .unwrap_or_default(); + let title_ind = object_index + .primary_by_name("TAGame.ProductAttribute_TitleID_TA") + .unwrap_or_default(); + let special_edition_ind = object_index + .primary_by_name("TAGame.ProductAttribute_SpecialEdition_TA") + .unwrap_or_default(); + let team_edition_ind = object_index + .primary_by_name("TAGame.ProductAttribute_TeamEdition_TA") + .unwrap_or_default(); ProductValueDecoder { version, diff --git a/src/network/mod.rs b/src/network/mod.rs index 93129a9..4b5702e 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,9 +1,11 @@ pub(crate) use self::attributes::*; pub use self::models::*; +pub(crate) use self::object_index::*; pub mod attributes; mod frame_decoder; mod models; +mod object_index; use crate::data::{ATTRIBUTES, PARENT_CLASSES, SPAWN_STATS}; use crate::errors::NetworkError; @@ -13,7 +15,6 @@ use crate::network::frame_decoder::FrameDecoder; use crate::parser::ReplayBody; use fnv::FnvHashMap; use std::cmp; -use std::collections::hash_map::Entry; #[derive(Debug)] pub(crate) struct CacheInfo<'a> { @@ -47,39 +48,17 @@ pub(crate) fn parse(header: &Header, body: &ReplayBody) -> Result = body.objects.iter().map(|x| normalize_object(x)).collect(); - // The exact same name can appear multiple times in body.objects. Very annoying. - // So we designate a primary index for each name, and reverse lookups. - let mut name_index: FnvHashMap<&str, ObjectId> = FnvHashMap::default(); - let mut secondary_indices: FnvHashMap> = FnvHashMap::default(); - let mut primary_ind: FnvHashMap = FnvHashMap::default(); - - for (i, name) in body.objects.iter().enumerate() { - let val = ObjectId(i as i32); - match name_index.entry(name) { - Entry::Occupied(occupied_entry) => { - primary_ind.insert(val, *occupied_entry.get()); - secondary_indices - .entry(*occupied_entry.get()) - .or_default() - .push(val); - } - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(val); - } - }; - } + let object_index = object_index::ObjectIndex::new(&body.objects); // Create a parallel vector where we lookup how to decode an object's initial trajectory // when they spawn as a new actor let mut spawns: Vec> = vec![None; body.objects.len()]; for (object_name, spawn) in SPAWN_STATS.iter() { - let all_indices = name_index - .get(object_name) - .map(|ind| std::iter::once(ind).chain(secondary_indices.get(ind).into_iter().flatten())) - .into_iter() - .flatten(); + let Some(id) = object_index.primary_by_name(object_name) else { + continue; + }; - for i in all_indices { + for i in object_index.all_indices(id) { spawns[i.0 as usize] = Some(*spawn); } } @@ -92,13 +71,7 @@ pub(crate) fn parse(header: &Header, body: &ReplayBody) -> Result> = FnvHashMap::default(); @@ -128,7 +101,7 @@ pub(crate) fn parse(header: &Header, body: &ReplayBody) -> Result, NetworkError>>()?; let key = ObjectId(cache.object_ind); - let primary_object = primary_ind.get(&key).copied().unwrap_or(key); + let primary_object = object_index.primary_by_index(key); // The same primary object can occur multiple times, though it tends to // be just duplicates. @@ -144,11 +117,11 @@ pub(crate) fn parse(header: &Header, body: &ReplayBody) -> Result Result Result Result, NetworkError>>()?; - let product_decoder = ProductValueDecoder::create(version, &name_index); + let product_decoder = ProductValueDecoder::create(version, &object_index); // 1023 stolen from rattletrap let max_channels = header.max_channels().unwrap_or(1023) as u32; @@ -246,8 +217,7 @@ fn net_traversal( parent_stack: &mut Vec<(ObjectId, Vec<(StreamId, ObjectAttribute)>)>, net_properties: &FnvHashMap>, obj_ind: &ObjectId, - name_index: &FnvHashMap<&str, ObjectId>, - secondary_indices: &FnvHashMap>, + object_index: &object_index::ObjectIndex, object_ind_attrs: &mut FnvHashMap>, ) { acc_attrs.clear(); @@ -259,26 +229,21 @@ fn net_traversal( while let Some(parent) = PARENT_CLASSES.get(object_name) { object_name = parent; - let Some(ind) = name_index.get(parent) else { + let Some(ind) = object_index.primary_by_name(parent) else { continue; }; - let attrs = net_properties.get(ind).cloned().unwrap_or_default(); - parent_stack.push((*ind, attrs)); - - let Some(parent_attributes) = object_ind_attrs.get(ind) else { - let attrs = net_properties.get(ind).cloned().unwrap_or_default(); - parent_stack.push((*ind, attrs)); + let Some(parent_attributes) = object_ind_attrs.get(&ind) else { + let attrs = net_properties.get(&ind).cloned().unwrap_or_default(); + parent_stack.push((ind, attrs)); continue; }; acc_attrs.extend(parent_attributes.iter().map(|(x, y)| (*x, *y))); while let Some((ind, attrs)) = parent_stack.pop() { acc_attrs.extend(attrs); - let equiv_parents = - std::iter::once(&ind).chain(secondary_indices.get(&ind).into_iter().flatten()); - for parent in equiv_parents { - object_ind_attrs.insert(*parent, acc_attrs.iter().cloned().collect()); + for parent in object_index.all_indices(ind) { + object_ind_attrs.insert(parent, acc_attrs.iter().cloned().collect()); } } @@ -288,10 +253,8 @@ fn net_traversal( while let Some((ind, attrs)) = parent_stack.pop() { acc_attrs.extend(attrs); if !acc_attrs.is_empty() { - let equiv_parents = - std::iter::once(&ind).chain(secondary_indices.get(&ind).into_iter().flatten()); - for parent in equiv_parents { - object_ind_attrs.insert(*parent, acc_attrs.iter().cloned().collect()); + for parent in object_index.all_indices(ind) { + object_ind_attrs.insert(parent, acc_attrs.iter().cloned().collect()); } } } @@ -299,27 +262,24 @@ fn net_traversal( fn spawn_traversal( mut object_name: &str, - name_index: &FnvHashMap<&str, ObjectId>, - secondary_indices: &FnvHashMap>, + object_index: &object_index::ObjectIndex, spawns: &mut [Option], parent_stack: &mut Vec, ) { while let Some(parent) = PARENT_CLASSES.get(object_name) { object_name = parent; - let Some(ind) = name_index.get(parent) else { + let Some(ind) = object_index.primary_by_name(parent) else { continue; }; let Some(parent_spawn) = spawns[ind.0 as usize] else { - parent_stack.push(*ind); + parent_stack.push(ind); continue; }; while let Some(p_ind) = parent_stack.pop() { - let equiv_parents = - std::iter::once(&p_ind).chain(secondary_indices.get(&p_ind).into_iter().flatten()); - for i in equiv_parents { + for i in object_index.all_indices(p_ind) { spawns[i.0 as usize] = Some(parent_spawn) } } diff --git a/src/network/models.rs b/src/network/models.rs index ad46bb1..5c985b3 100644 --- a/src/network/models.rs +++ b/src/network/models.rs @@ -226,7 +226,7 @@ pub struct Frame { /// A replay encodes a list of objects that appear in the network data. The index of an object in /// this list is used as a key in many places: reconstructing the attribute hierarchy and new /// actors in the network data. -#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Serialize)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Serialize, Default)] pub struct ObjectId(pub i32); impl From for i32 { diff --git a/src/network/object_index.rs b/src/network/object_index.rs new file mode 100644 index 0000000..535a5bf --- /dev/null +++ b/src/network/object_index.rs @@ -0,0 +1,66 @@ +use crate::ObjectId; +use fnv::FnvHashMap; +use std::collections::hash_map::Entry; + +/// A lookup of an object's ID (its index in body.objects) from its name. +/// +/// The exact same name can appear multiple times in body.objects, so we +/// designate these additional occurrences as "secondary IDs", and an +/// `ObjectIndex` is a bidirectional map of primary to secondary IDs. +pub(crate) struct ObjectIndex<'a> { + name_index: FnvHashMap<&'a str, ObjectId>, + secondary_indices: FnvHashMap>, + primary_ind: FnvHashMap, +} + +impl<'a> ObjectIndex<'a> { + pub(crate) fn new(objects: &'a [String]) -> Self { + let mut name_index: FnvHashMap<&str, ObjectId> = FnvHashMap::default(); + let mut secondary_indices: FnvHashMap> = FnvHashMap::default(); + let mut primary_ind: FnvHashMap = FnvHashMap::default(); + + for (i, name) in objects.iter().enumerate() { + let val = ObjectId(i as i32); + match name_index.entry(name) { + Entry::Occupied(occupied_entry) => { + primary_ind.insert(val, *occupied_entry.get()); + secondary_indices + .entry(*occupied_entry.get()) + .or_default() + .push(val); + } + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(val); + } + }; + } + + Self { + name_index, + secondary_indices, + primary_ind, + } + } + + /// Return primary `ObjectId` given the object name + pub(crate) fn primary_by_name(&self, name: &str) -> Option { + self.name_index.get(name).copied() + } + + /// Return the primary `ObjectId` given either a primary or secondary `ObjectId` + pub(crate) fn primary_by_index(&self, id: ObjectId) -> ObjectId { + self.primary_ind.get(&id).copied().unwrap_or(id) + } + + /// Returns a list of equivalent `ObjectId` as the primary id passed in. + /// Includes self. + pub(crate) fn all_indices(&self, id: ObjectId) -> impl Iterator + '_ { + std::iter::once(id).chain( + self.secondary_indices + .get(&id) + .into_iter() + .flatten() + .copied(), + ) + } +}