use core::{
any::{self},
hash::{Hash, Hasher},
};
use bevy::{
ecs::{entity::EntityHashMap, lifecycle::HookContext, world::DeferredWorld},
platform::{collections::HashMap, hash::NoOpHash},
prelude::*,
};
use deterministic_hash::DeterministicHasher;
use log::{debug, error};
use xxhash_rust::xxh3::Xxh3Default;
#[derive(Component, Reflect, Debug, Clone, Copy)]
#[component(on_add = register_hash, on_remove = unregister_hash)]
pub struct Signature {
salt: Option<u64>,
#[reflect(ignore)]
fns: &'static [HashFn],
client: Option<Entity>,
hash: u64,
}
impl Signature {
#[must_use]
pub fn of<C: Component + Hash>() -> Self {
Self {
salt: None,
fns: &[hash::<C>],
client: None,
hash: 0,
}
}
#[must_use]
pub fn of_n<S: SignatureComponents>() -> Self {
Self {
salt: None,
fns: S::HASH_FNS,
client: None,
hash: 0,
}
}
#[must_use]
pub fn with_salt<T: Hash>(mut self, value: T) -> Self {
let mut hasher = DeterministicHasher::<Xxh3Default>::default();
value.hash(&mut hasher);
self.salt = Some(hasher.finish());
self
}
#[must_use]
pub fn for_client(mut self, client: Entity) -> Self {
self.client = Some(client);
self
}
pub(crate) fn client(&self) -> Option<Entity> {
self.client
}
pub(crate) fn hash(&self) -> u64 {
self.hash
}
fn eval<'a>(&self, entity: impl Into<EntityRef<'a>>) -> u64 {
let mut hasher = DeterministicHasher::<Xxh3Default>::default();
if let Some(salt) = self.salt {
salt.hash(&mut hasher);
}
let entity = entity.into();
for hash_fn in self.fns {
(hash_fn)(&entity, &mut hasher);
}
hasher.finish()
}
}
impl<T: Hash> From<T> for Signature {
fn from(value: T) -> Self {
let mut hasher = DeterministicHasher::<Xxh3Default>::default();
value.hash(&mut hasher);
Self {
salt: Some(hasher.finish()),
fns: &[],
client: None,
hash: 0,
}
}
}
fn register_hash(mut world: DeferredWorld, ctx: HookContext) {
let mut entity = world.entity_mut(ctx.entity);
let signature = entity.get::<Signature>().unwrap();
let hash = signature.eval(&entity);
let mut signature = entity.get_mut::<Signature>().unwrap();
signature.hash = hash;
if let Some(mut map) = world.get_resource_mut::<SignatureMap>() {
map.insert(ctx.entity, hash);
} else {
debug!("ignoring hash 0x{hash:016x} for `{}`", ctx.entity);
}
}
fn unregister_hash(mut world: DeferredWorld, ctx: HookContext) {
if let Some(mut map) = world.get_resource_mut::<SignatureMap>() {
map.remove(ctx.entity);
}
}
#[derive(Resource, Default)]
pub(crate) struct SignatureMap {
to_hashes: EntityHashMap<u64>,
to_entities: HashMap<u64, Entity, NoOpHash>, }
impl SignatureMap {
pub(crate) fn get(&self, hash: u64) -> Option<Entity> {
self.to_entities.get(&hash).copied()
}
fn insert(&mut self, entity: Entity, hash: u64) {
match self.to_entities.try_insert(hash, entity) {
Ok(_) => {
debug!("inserting hash 0x{hash:016x} for `{entity}`");
self.to_hashes.insert(entity, hash);
}
Err(e) => error!(
"hash 0x{hash:016x} for `{entity}` already corresponds to `{}` and will be ignored",
e.value
),
}
}
pub(crate) fn remove(&mut self, entity: Entity) {
if let Some(hash) = self.to_hashes.remove(&entity) {
debug!("removing hash 0x{hash:016x} for `{entity}`");
self.to_entities.remove(&hash);
}
}
}
pub trait SignatureComponents {
const HASH_FNS: &'static [HashFn];
}
type HashFn = fn(&EntityRef, &mut DeterministicHasher<Xxh3Default>);
fn hash<C: Component + Hash>(entity: &EntityRef, hasher: &mut DeterministicHasher<Xxh3Default>) {
let type_name = any::type_name::<C>();
type_name.hash(hasher);
if let Some(component) = entity.get::<C>() {
component.hash(hasher);
} else {
error!(
"unable to get `{type_name}` from `{}` to calculate hash",
entity.id()
);
}
}
macro_rules! impl_signature_components {
($($C:ident),*) => {
impl<$($C: Component + Hash),*> SignatureComponents for ($($C,)*) {
const HASH_FNS: &'static [HashFn] = &[
$(hash::<$C>,)*
];
}
};
}
variadics_please::all_tuples!(impl_signature_components, 0, 6, C);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_panic_without_map() {
let mut world = World::new();
world.spawn(Signature::from(0)).despawn();
}
#[test]
fn single_component() {
let mut world = World::new();
let entity1 = world.spawn(C(true)).id();
let entity2 = world.spawn(C(true)).id();
let entity3 = world.spawn(C(false)).id();
let entity4 = world.spawn(A).id();
let signature = Signature::of::<C>();
let hash1 = signature.eval(world.entity(entity1));
let hash2 = signature.eval(world.entity(entity2));
let hash3 = signature.eval(world.entity(entity3));
let hash4 = signature.eval(world.entity(entity4));
assert_eq!(hash1, 8868928108740117422);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
assert_ne!(hash3, hash4);
}
#[test]
fn multiple_components() {
let mut world = World::new();
let entity1 = world.spawn((A, C(true))).id();
let entity2 = world.spawn((A, C(true))).id();
let entity3 = world.spawn((A, C(false))).id();
let entity4 = world.spawn(A).id();
let signature = Signature::of_n::<(A, C)>();
let hash1 = signature.eval(world.entity(entity1));
let hash2 = signature.eval(world.entity(entity2));
let hash3 = signature.eval(world.entity(entity3));
let hash4 = signature.eval(world.entity(entity4));
assert_eq!(hash1, 15617178438747089367);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
assert_ne!(hash3, hash4);
assert_ne!(hash1, hash4);
}
#[test]
fn different_salt() {
let mut world = World::new();
let entity1 = world.spawn((A, B)).id();
let entity2 = world.spawn((A, B)).id();
let signature = Signature::of_n::<(A, B)>();
let signature_42 = Signature::of_n::<(A, B)>().with_salt(42usize);
let hash1 = signature.eval(world.entity(entity1));
let hash2 = signature.eval(world.entity(entity2));
let hash1_42 = signature_42.eval(world.entity(entity1));
let hash2_42 = signature_42.eval(world.entity(entity2));
assert_eq!(hash1, 11681676351098321858);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash1_42);
assert_eq!(hash1_42, 3735346266121839838);
assert_eq!(hash1_42, hash2_42);
}
#[test]
fn different_component_names() {
let mut world = World::new();
let entity = world.spawn((A, B)).id();
let signature_a = Signature::of::<A>();
let signature_b = Signature::of::<B>();
let hash_a = signature_a.eval(world.entity(entity));
let hash_b = signature_b.eval(world.entity(entity));
assert_ne!(hash_a, hash_b);
}
#[derive(Component, Hash)]
struct A;
#[derive(Component, Hash)]
struct B;
#[derive(Component, Hash)]
struct C(bool);
}