Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4e4417f
fix: Add net tracing for rollback frame and add current frame to inpu…
MaxCWhitehead Sep 7, 2024
99bf5e9
feat: DesyncHash trait and std impls
MaxCWhitehead Sep 12, 2024
0df9991
feat: DesyncHash derive macro
MaxCWhitehead Sep 12, 2024
988bef5
feat: ScheamDesyncHash typedata to opt optionally implement DesyncHash
MaxCWhitehead Sep 12, 2024
16a006f
feat: #[net] sugar macro for #[derive_type_data(SchemaDesyncHash)].
MaxCWhitehead Sep 12, 2024
f3f6eca
feat: Basic tests for desync hash
MaxCWhitehead Sep 12, 2024
8de5ab3
feat: Impl DesyncHash for World, Resources, ComponentStores, etc
MaxCWhitehead Sep 12, 2024
990925b
feat: Expose ggrs desync setting, allow overriding of hash function.
MaxCWhitehead Sep 12, 2024
3c7fc2e
feat: Enable desync detect on transform
MaxCWhitehead Sep 12, 2024
a311425
chore: Fixup docs
MaxCWhitehead Sep 12, 2024
ab632ca
feat: wip desync tree
MaxCWhitehead Sep 14, 2024
c61248f
feat: wip dump hash tree on detected desync
MaxCWhitehead Sep 15, 2024
f4c1f43
chore: Fix bumping bones to 0.4
MaxCWhitehead Sep 15, 2024
8e0c0e1
chore: fix feature comment
MaxCWhitehead Sep 15, 2024
91be293
fix: impl DesyncHash for arrays
MaxCWhitehead Sep 15, 2024
f6db4b3
chore: update lockfile
MaxCWhitehead Oct 19, 2024
19de3ad
feat: Allow tree to include unhashable nodes, make hash optional on node
MaxCWhitehead Sep 15, 2024
5177eb7
feat: Add #[desync_exclude] attribute to exclude fields from desync h…
MaxCWhitehead Sep 20, 2024
83ec3fb
fix: fix missing brace
MaxCWhitehead Sep 20, 2024
2a35abc
refactor: Support entity Names, make
MaxCWhitehead Nov 3, 2024
f88be6c
chore: Fixup errors after rebase
MaxCWhitehead Nov 3, 2024
87bf925
test: Add test for desync tree entity names
MaxCWhitehead Nov 3, 2024
7eaf0dd
fix: Remove `derive(Serialize)` from `Transform`
MaxCWhitehead Nov 3, 2024
0eb1411
chore(ci): Clippy + doc warnings
MaxCWhitehead Nov 3, 2024
ee4ba10
chore: Fix derive_desync_hash tuple indexing warning
MaxCWhitehead Nov 3, 2024
dda4964
chore: desync tree iteration: Pass None if children is empty instead …
MaxCWhitehead Nov 3, 2024
603e911
chore(tests): Fix comments in tests
MaxCWhitehead Nov 5, 2024
b864466
chore: move DesyncTree types to own file.
MaxCWhitehead Nov 5, 2024
8ab4485
chore: Serialize tree as pretty json
MaxCWhitehead Nov 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fxhash = "0.2"
hashbrown = "0.14"
maybe-owned = "0.3"
parking_lot = "0.12"
serde_json = "1"
smallvec = "1.11"
ustr = "0.10"
iroh-net = "0.27"
Expand Down
2 changes: 1 addition & 1 deletion framework_crates/bones_asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ paste = "1.0"
path-absolutize = { version = "3.1", features = ["use_unix_paths_on_wasm"] }
semver = { version = "1.0", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json = { workspace = true }
serde_yaml = "0.9"
sha2 = "0.10"
tracing = "0.1"
Expand Down
13 changes: 13 additions & 0 deletions framework_crates/bones_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![cfg_attr(doc, allow(unknown_lints))]
#![deny(rustdoc::all)]

use bones_utils::DesyncHash;
use serde::{de::DeserializeSeed, Deserializer};

/// Helper to export the same types in the crate root and in the prelude.
Expand Down Expand Up @@ -282,6 +283,18 @@ impl<T> From<Option<T>> for Maybe<T> {
}
}

impl<T: DesyncHash> DesyncHash for Maybe<T> {
fn hash(&self, hasher: &mut dyn std::hash::Hasher) {
match self {
Maybe::Unset => 0.hash(hasher),
Maybe::Set(value) => {
1.hash(hasher);
value.hash(hasher)
}
}
}
}

fn maybe_loader(
ctx: &mut MetaAssetLoadCtx,
ptr: SchemaRefMut<'_>,
Expand Down
3 changes: 2 additions & 1 deletion framework_crates/bones_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ keywords.workspace = true
default = ["derive", "keysize16"]
miri = ["derive", "keysize10"]
derive = ["dep:bones_ecs_macros"]
glam = ["dep:glam", "dep:paste", "bones_schema/glam"]
glam = ["dep:glam", "dep:paste", "bones_schema/glam", "bones_utils/glam"]
serde = ["dep:serde"]

keysize10 = []
Expand All @@ -36,6 +36,7 @@ serde = { version = "1", features = ["derive"], optional = true }

anyhow = "1.0"
branches = { workspace = true }
fxhash = { workspace = true }
atomicell = "0.2"
bitset-core = "0.1"
once_map = "0.4.12"
Expand Down
109 changes: 109 additions & 0 deletions framework_crates/bones_ecs/src/components.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! ECS component storage.

use fxhash::FxHasher;
use once_map::OnceMap;
use std::sync::Arc;

Expand Down Expand Up @@ -47,6 +48,114 @@ impl Clone for ComponentStores {
}
}

impl DesyncHash for ComponentStores {
fn hash(&self, hasher: &mut dyn std::hash::Hasher) {
// Compute child hashes and sort
let mut hashes = self
.components
.read_only_view()
.iter()
.filter_map(|(_, component_store)| {
// Verify Schema for component store implement desync hash. If no hash_fn, we don't
// want to add hash.
let component_store = component_store.as_ref().borrow();
if component_store
.schema()
.type_data
.get::<SchemaDesyncHash>()
.is_some()
{
// We need to compute hashes first
return Some(component_store.compute_hash::<FxHasher>());
}

None
})
.collect::<Vec<u64>>();
hashes.sort();

// Udpate parent hasher from sorted hashes
for hash in hashes.iter() {
hash.hash(hasher);
}
}
}

impl BuildDesyncNode for ComponentStores {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
include_unhashable: bool,
) -> DefaultDesyncTreeNode {
let mut any_hashable = false;

// We get the Name component store so we can lookup entity names and set those on component leaves.
let names = self.get::<Name>().borrow();

let mut child_nodes = self
.components
.read_only_view()
.iter()
.filter_map(|(_, component_store)| {
let component_store = component_store.as_ref().borrow();
let is_hashable = component_store
.schema()
.type_data
.get::<SchemaDesyncHash>()
.is_some();

if is_hashable {
any_hashable = true;
}

if include_unhashable || is_hashable {
let mut child_node = component_store.desync_tree_node::<H>(include_unhashable);

// Our child here is a component store, and its children are component leaves.
// Iterate through children, retrieve metadata storing entity_idx if set, and use this
// to update the node's name from Name component.
//
// This is fairly hacky, but should be good enough for now.
for component_node in child_node.children_mut().iter_mut() {
if let DesyncNodeMetadata::Component { entity_idx } =
component_node.metadata()
{
// Constructing Entity with fake generation is bit of a hack - but component store does not
// use generation, only the index.
if let Some(name) = names.get(Entity::new(*entity_idx, 0)) {
component_node.set_name(name.0.clone());
}
}
}

return Some(child_node);
}
None
})
.collect::<Vec<DefaultDesyncTreeNode>>();
child_nodes.sort();

let hash = if any_hashable {
let mut hasher = H::default();
for node in child_nodes.iter() {
// Update parent node hash from data
if let Some(hash) = node.get_hash() {
DesyncHash::hash(&hash, &mut hasher);
}
}
Some(hasher.finish())
} else {
None
};

DefaultDesyncTreeNode::new(
hash,
Some("Components".into()),
child_nodes,
DesyncNodeMetadata::None,
)
}
}

impl ComponentStores {
/// Get the components of a certain type
pub fn get_cell<T: HasSchema>(&self) -> AtomicComponentStore<T> {
Expand Down
63 changes: 63 additions & 0 deletions framework_crates/bones_ecs/src/components/untyped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,69 @@ impl Drop for UntypedComponentStore {
}
}

impl DesyncHash for UntypedComponentStore {
fn hash(&self, hasher: &mut dyn std::hash::Hasher) {
self.schema().full_name.hash(hasher);
for component in self.iter() {
DesyncHash::hash(&component, hasher);
}
}
}

impl BuildDesyncNode for UntypedComponentStore {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
_include_unhashable: bool,
) -> DefaultDesyncTreeNode {
let mut hasher = H::default();

// Iterate over components by index so we can save entity ID.
let iter = 0..self.bitset().bit_len();
let child_nodes: Vec<DefaultDesyncTreeNode> = iter
.filter_map(|entity_idx| -> Option<DefaultDesyncTreeNode> {
if let Some(component) = self.get_idx(entity_idx) {
let hash = if component
.schema()
.type_data
.get::<SchemaDesyncHash>()
.is_some()
{
// Update parent node hash from data
DesyncHash::hash(&component, &mut hasher);
Some(component.compute_hash::<H>())
} else {
None
};

return Some(DefaultDesyncTreeNode::new(
hash,
None,
vec![],
DesyncNodeMetadata::Component {
entity_idx: entity_idx as u32,
},
));
}

None
})
.collect();

let hash = if !child_nodes.is_empty() {
Some(hasher.finish())
} else {
None
};

DefaultDesyncTreeNode::new(
hash,
Some(self.schema().full_name.to_string()),
child_nodes,
DesyncNodeMetadata::None,
)
}
}

impl UntypedComponentStore {
/// Create a arbitrary [`UntypedComponentStore`].
///
Expand Down
16 changes: 16 additions & 0 deletions framework_crates/bones_ecs/src/entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ impl Default for Entities {
}
}

/// Utility component storing a name for entity
#[derive(HasSchema, Clone, Debug)]
pub struct Name(pub String);

impl Default for Name {
fn default() -> Self {
Self("Unnamed".to_string())
}
}

impl From<&str> for Name {
fn from(value: &str) -> Self {
Self(value.into())
}
}

/// A type representing a component-joining entity query.
pub trait QueryItem {
/// The type of iterator this query item creates
Expand Down
Loading