Skip to content

Commit

Permalink
Use a fixed state hasher in bevy_reflect for deterministic Reflect::r…
Browse files Browse the repository at this point in the history
…eflect_hash() across processes (#7583)

# Objective

- bevy_ggrs uses `reflect_hash` in order to produce checksums for its world snapshots. These checksums are sent between clients in order to detect desyncronization.
- However, since we currently use `async::AHasher` with the `std` feature, this means that hashes will always be different for different peers, even if the state is identical.
- This means bevy_ggrs needs a way to get a deterministic (fixed) hash.

## Solution

- ~~Add a feature to use `bevy_utils::FixedState` for the hasher used by bevy_reflect.~~
- Always use `bevy_utils::FixedState` for initializing the bevy_reflect hasher. 

---

## Changelog

- bevy_reflect now uses a fixed state for its hasher, which means the output of `Reflect::reflect_hash` is now deterministic across processes.
  • Loading branch information
johanhelsing committed Feb 17, 2023
1 parent 0425673 commit 18cfb22
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! the derive helper attribute for `Reflect`, which looks like:
//! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`.

use crate::fq_std::{FQAny, FQDefault, FQOption};
use crate::fq_std::{FQAny, FQOption};
use crate::utility;
use proc_macro2::{Ident, Span};
use quote::quote_spanned;
Expand Down Expand Up @@ -225,7 +225,7 @@ impl ReflectTraits {
&TraitImpl::Implemented(span) => Some(quote_spanned! {span=>
fn reflect_hash(&self) -> #FQOption<u64> {
use ::core::hash::{Hash, Hasher};
let mut hasher: #bevy_reflect_path::ReflectHasher = #FQDefault::default();
let mut hasher = #bevy_reflect_path::utility::reflect_hasher();
Hash::hash(&#FQAny::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
#FQOption::Some(Hasher::finish(&hasher))
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_reflect/src/array.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut, ReflectOwned, ReflectRef,
TypeInfo, Typed,
utility::{reflect_hasher, NonGenericTypeInfoCell},
DynamicInfo, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed,
};
use std::{
any::{Any, TypeId},
Expand Down Expand Up @@ -340,7 +340,7 @@ impl<'a> ExactSizeIterator for ArrayIter<'a> {}
/// Returns the `u64` hash of the given [array](Array).
#[inline]
pub fn array_hash<A: Array>(array: &A) -> Option<u64> {
let mut hasher = crate::ReflectHasher::default();
let mut hasher = reflect_hasher();
std::any::Any::type_id(array).hash(&mut hasher);
array.len().hash(&mut hasher);
for value in array.iter() {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_reflect/src/enums/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{Enum, Reflect, ReflectRef, VariantType};
use crate::{utility::reflect_hasher, Enum, Reflect, ReflectRef, VariantType};
use std::fmt::Debug;
use std::hash::{Hash, Hasher};

/// Returns the `u64` hash of the given [enum](Enum).
#[inline]
pub fn enum_hash<TEnum: Enum>(value: &TEnum) -> Option<u64> {
let mut hasher = crate::ReflectHasher::default();
let mut hasher = reflect_hasher();
std::any::Any::type_id(value).hash(&mut hasher);
value.variant_name().hash(&mut hasher);
value.variant_type().hash(&mut hasher);
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_reflect/src/impls/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
VariantInfo, VariantType,
};

use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell};
use crate::utility::{reflect_hasher, GenericTypeInfoCell, NonGenericTypeInfoCell};
use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value};
use bevy_utils::{Duration, Instant};
use bevy_utils::{HashMap, HashSet};
Expand Down Expand Up @@ -993,7 +993,7 @@ impl Reflect for Cow<'static, str> {
}

fn reflect_hash(&self) -> Option<u64> {
let mut hasher = crate::ReflectHasher::default();
let mut hasher = reflect_hasher();
Hash::hash(&std::any::Any::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
Some(hasher.finish())
Expand Down Expand Up @@ -1101,7 +1101,7 @@ impl Reflect for &'static Path {
}

fn reflect_hash(&self) -> Option<u64> {
let mut hasher = crate::ReflectHasher::default();
let mut hasher = reflect_hasher();
Hash::hash(&std::any::Any::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
Some(hasher.finish())
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_reflect/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::any::{Any, TypeId};
use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};

use crate::utility::NonGenericTypeInfoCell;
use crate::utility::{reflect_hasher, NonGenericTypeInfoCell};
use crate::{
DynamicInfo, FromReflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed,
};
Expand Down Expand Up @@ -378,7 +378,7 @@ impl<'a> ExactSizeIterator for ListIter<'a> {}
/// Returns the `u64` hash of the given [list](List).
#[inline]
pub fn list_hash<L: List>(list: &L) -> Option<u64> {
let mut hasher = crate::ReflectHasher::default();
let mut hasher = reflect_hasher();
std::any::Any::type_id(list).hash(&mut hasher);
list.len().hash(&mut hasher);
for value in list.iter() {
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_reflect/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::{
};

use crate::utility::NonGenericTypeInfoCell;
pub use bevy_utils::AHasher as ReflectHasher;

/// An immutable enumeration of "kinds" of reflected type.
///
Expand Down
19 changes: 17 additions & 2 deletions crates/bevy_reflect/src/utility.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Helpers for working with Bevy reflection.

use crate::TypeInfo;
use bevy_utils::HashMap;
use bevy_utils::{FixedState, HashMap};
use once_cell::race::OnceBox;
use parking_lot::RwLock;
use std::any::{Any, TypeId};
use std::{
any::{Any, TypeId},
hash::BuildHasher,
};

/// A container for [`TypeInfo`] over non-generic types, allowing instances to be stored statically.
///
Expand Down Expand Up @@ -147,3 +150,15 @@ impl GenericTypeInfoCell {
})
}
}

/// Deterministic fixed state hasher to be used by implementors of [`Reflect::reflect_hash`].
///
/// Hashes should be deterministic across processes so hashes can be used as
/// checksums for saved scenes, rollback snapshots etc. This function returns
/// such a hasher.
///
/// [`Reflect::reflect_hash`]: crate::Reflect
#[inline]
pub fn reflect_hasher() -> bevy_utils::AHasher {
FixedState.build_hasher()
}

0 comments on commit 18cfb22

Please sign in to comment.