Skip to content

Commit

Permalink
refactor: Support entity Names, make
Browse files Browse the repository at this point in the history
tree iteratable for testing, cleanup generics / simplify.
  • Loading branch information
MaxCWhitehead committed Nov 3, 2024
1 parent 83ec3fb commit 2a35abc
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 66 deletions.
34 changes: 30 additions & 4 deletions framework_crates/bones_ecs/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use fxhash::FxHasher;
use once_map::OnceMap;
use std::{any::Any, sync::Arc};
use std::sync::Arc;

use crate::prelude::*;

Expand Down Expand Up @@ -81,12 +81,16 @@ impl DesyncHash for ComponentStores {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for ComponentStores {
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()
Expand All @@ -104,7 +108,24 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for ComponentStores {
}

if include_unhashable || is_hashable {
let child_node = component_store.desync_tree_node::<H>(include_unhashable);
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);
}
Expand All @@ -126,7 +147,12 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for ComponentStores {
None
};

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

Expand Down
56 changes: 37 additions & 19 deletions framework_crates/bones_ecs/src/components/untyped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,42 @@ impl DesyncHash for UntypedComponentStore {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedComponentStore {
impl BuildDesyncNode for UntypedComponentStore {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
_include_unhashable: bool,
) -> DefaultDesyncTreeNode {
let mut hasher = H::default();
let child_nodes: Vec<DefaultDesyncTreeNode> = self
.iter()
.map(|component| -> DefaultDesyncTreeNode {
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
};

DefaultDesyncTreeNode::new(hash, None, vec![])

// 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();

Expand All @@ -114,7 +127,12 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedComponentStore {
None
};

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

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
17 changes: 11 additions & 6 deletions framework_crates/bones_ecs/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl DesyncHash for UntypedResource {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResource {
impl BuildDesyncNode for UntypedResource {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
_include_unhashable: bool,
Expand All @@ -49,10 +49,10 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResource {
} else {
None
};
return DefaultDesyncTreeNode::new(hash, name, vec![]);
return DefaultDesyncTreeNode::new(hash, name, vec![], DesyncNodeMetadata::None);
}

DefaultDesyncTreeNode::new(None, name, vec![])
DefaultDesyncTreeNode::new(None, name, vec![], DesyncNodeMetadata::None)
}
}

Expand Down Expand Up @@ -197,7 +197,7 @@ impl DesyncHash for UntypedResources {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResources {
impl BuildDesyncNode for UntypedResources {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
include_unhashable: bool,
Expand Down Expand Up @@ -230,7 +230,12 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResources {
}
}

DefaultDesyncTreeNode::new(Some(hasher.finish()), Some("Resources".into()), child_nodes)
DefaultDesyncTreeNode::new(
Some(hasher.finish()),
Some("Resources".into()),
child_nodes,
DesyncNodeMetadata::None,
)
}
}

Expand Down Expand Up @@ -314,7 +319,7 @@ impl DesyncHash for Resources {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for Resources {
impl BuildDesyncNode for Resources {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
include_unhashable: bool,
Expand Down
25 changes: 23 additions & 2 deletions framework_crates/bones_ecs/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl DesyncHash for World {
}
}

impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for World {
impl BuildDesyncNode for World {
fn desync_tree_node<H: std::hash::Hasher + Default>(
&self,
include_unhashable: bool,
Expand All @@ -64,7 +64,12 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for World {
}
child_nodes.push(resources_node);

DefaultDesyncTreeNode::new(Some(hasher.finish()), Some("World".into()), child_nodes)
DefaultDesyncTreeNode::new(
Some(hasher.finish()),
Some("World".into()),
child_nodes,
DesyncNodeMetadata::None,
)
}
}

Expand Down Expand Up @@ -246,6 +251,22 @@ impl World {
// Always maintain to clean up any killed entities
self.maintain();
}
/// Build [`DefaultDesyncTree`] from [`World`].
///
/// `include_unhashable` sets whether components or resources be included as non-contributing nodes
/// in tree, to see what could be opted-in to desync hashing.
///
/// # Panics
///
/// This will immutably borrow all components and resources, if any are mutably borrowed, this will panic.
pub fn desync_hash_tree<H: std::hash::Hasher + Default>(
&self,
include_unhashable: bool,
) -> DefaultDesyncTree {
let root = self.desync_tree_node::<H>(include_unhashable);

DefaultDesyncTree::from_root(root)
}
}

/// Creates an instance of the type this trait is implemented for
Expand Down
1 change: 1 addition & 0 deletions framework_crates/bones_framework/src/networking/desync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bones_lib::{ecs::World, prelude::default};
/// but is private so cannot be used directly.
const MAX_DESYNC_HISTORY_BUFFER: usize = 32;

/// Settings for desync detection
#[derive(Clone)]
pub struct DetectDesyncs {
/// Interval in frames of how often to hash state and check for desync with other clients.
Expand Down
11 changes: 6 additions & 5 deletions framework_crates/bones_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ keywords.workspace = true

[features]
default = ["ulid"]
glam = ["dep:glam"]
serde = ["dep:serde", "hashbrown/serde"]
ulid = ["dep:ulid", "instant", "turborand"]
glam = ["dep:glam"]
serde = ["dep:serde", "hashbrown/serde"]
ulid = ["dep:ulid", "instant", "turborand"]

[dependencies]
bones_utils_macros = { version = "0.4", path = "./macros" }
fxhash = { workspace = true }
hashbrown = { workspace = true }
tree_iterators_rs = { version = "1.2.1" }

# Optional
instant = { version = "0.1", features = ["wasm-bindgen"], optional = true }
serde = { version = "1.0", optional = true }
turborand = { version = "0.10", optional = true }
ulid = { version = "1.0", optional = true }
glam = { version = "0.24", optional = true }
paste = { version = "1.0" }
glam = { version = "0.24", optional = true }
paste = { version = "1.0" }

# Make sure that the getrandom package, used in `ulid` works on web
# when compiling for WASM.
Expand Down
Loading

0 comments on commit 2a35abc

Please sign in to comment.