mod variant;
pub use variant::*;
use blake3::{Hash, Hasher};
use rbx_dom_weak::{
types::{Ref, Variant},
Instance, Ustr, WeakDom,
};
use std::collections::HashMap;
use crate::{variant_eq::variant_eq, Project};
use super::{descendants, filter_properties_preallocated};
pub fn hash_tree(project: &Project, dom: &WeakDom, root_ref: Ref) -> HashMap<Ref, Hash> {
let mut order = descendants(dom, root_ref);
let mut map: HashMap<Ref, Hash> = HashMap::with_capacity(order.len());
let mut prop_list = Vec::with_capacity(2);
let mut child_hashes = Vec::new();
while let Some(referent) = order.pop() {
let inst = dom.get_by_ref(referent).unwrap();
let mut hasher = hash_inst_filtered(project, inst, &mut prop_list);
add_children(inst, &map, &mut child_hashes, &mut hasher);
map.insert(referent, hasher.finalize());
}
map
}
#[inline]
pub fn hash_instance(project: &Project, dom: &WeakDom, referent: Ref) -> Option<Hash> {
let mut prop_list = Vec::with_capacity(2);
let inst = dom.get_by_ref(referent)?;
Some(hash_inst_filtered(project, inst, &mut prop_list).finalize())
}
fn add_children(
inst: &Instance,
map: &HashMap<Ref, Hash>,
child_hashes: &mut Vec<[u8; 32]>,
hasher: &mut Hasher,
) {
for child_ref in inst.children() {
if let Some(hash) = map.get(child_ref) {
child_hashes.push(*hash.as_bytes())
} else {
panic!("Invariant violated: child not hashed before parent")
}
}
child_hashes.sort_unstable();
for hash in child_hashes.drain(..) {
hasher.update(&hash);
}
}
fn hash_inst_filtered<'inst>(
project: &Project,
inst: &'inst Instance,
prop_list: &mut Vec<(Ustr, &'inst Variant)>,
) -> Hasher {
filter_properties_preallocated(project, inst, prop_list);
hash_inst_prefilled(inst, prop_list)
}
fn hash_inst_prefilled<'inst>(
inst: &'inst Instance,
prop_list: &mut Vec<(Ustr, &'inst Variant)>,
) -> Hasher {
let mut hasher = Hasher::new();
hasher.update(inst.name.as_bytes());
hasher.update(inst.class.as_bytes());
prop_list.sort_unstable_by_key(|(name, _)| *name);
let descriptor = rbx_reflection_database::get()
.unwrap()
.classes
.get(inst.class.as_str());
if let Some(descriptor) = descriptor {
for (name, value) in prop_list.drain(..) {
hasher.update(name.as_bytes());
if let Some(default) = descriptor.default_properties.get(name.as_str()) {
if !variant_eq(default, value) {
hash_variant(&mut hasher, value)
}
} else {
hash_variant(&mut hasher, value)
}
}
} else {
for (name, value) in prop_list.drain(..) {
hasher.update(name.as_bytes());
hash_variant(&mut hasher, value)
}
}
hasher
}