Skip to content

Commit 9a73585

Browse files
committed
feat: Allow tree to include unhashable nodes, make hash optional on node
1 parent 8f1a31d commit 9a73585

File tree

7 files changed

+159
-84
lines changed

7 files changed

+159
-84
lines changed

framework_crates/bones_ecs/src/components.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,29 @@ impl DesyncHash for ComponentStores {
8282
}
8383

8484
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for ComponentStores {
85-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
86-
let mut hasher = H::default();
87-
85+
fn desync_tree_node<H: std::hash::Hasher + Default>(
86+
&self,
87+
include_unhashable: bool,
88+
) -> DefaultDesyncTreeNode {
89+
let mut any_hashable = false;
8890
let mut child_nodes = self
8991
.components
9092
.read_only_view()
9193
.iter()
9294
.filter_map(|(_, component_store)| {
9395
let component_store = component_store.as_ref().borrow();
94-
if component_store
96+
let is_hashable = component_store
9597
.schema()
9698
.type_data
9799
.get::<SchemaDesyncHash>()
98-
.is_some()
99-
{
100-
let child_node = component_store.desync_tree_node::<H>();
100+
.is_some();
101+
102+
if is_hashable {
103+
any_hashable = true;
104+
}
105+
106+
if include_unhashable || is_hashable {
107+
let child_node = component_store.desync_tree_node::<H>(include_unhashable);
101108

102109
return Some(child_node);
103110
}
@@ -106,12 +113,20 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for ComponentStores {
106113
.collect::<Vec<DefaultDesyncTreeNode>>();
107114
child_nodes.sort();
108115

109-
for node in child_nodes.iter() {
110-
// Update parent node hash from data
111-
DesyncHash::hash(&node.get_hash(), &mut hasher);
112-
}
116+
let hash = if any_hashable {
117+
let mut hasher = H::default();
118+
for node in child_nodes.iter() {
119+
// Update parent node hash from data
120+
if let Some(hash) = node.get_hash() {
121+
DesyncHash::hash(&hash, &mut hasher);
122+
}
123+
}
124+
Some(hasher.finish())
125+
} else {
126+
None
127+
};
113128

114-
DefaultDesyncTreeNode::new(hasher.finish(), Some("Components".into()), child_nodes)
129+
DefaultDesyncTreeNode::new(hash, Some("Components".into()), child_nodes)
115130
}
116131
}
117132

framework_crates/bones_ecs/src/components/untyped.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,25 +83,38 @@ impl DesyncHash for UntypedComponentStore {
8383
}
8484

8585
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedComponentStore {
86-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
86+
fn desync_tree_node<H: std::hash::Hasher + Default>(
87+
&self,
88+
_include_unhashable: bool,
89+
) -> DefaultDesyncTreeNode {
8790
let mut hasher = H::default();
88-
let child_nodes = self
91+
let child_nodes: Vec<DefaultDesyncTreeNode> = self
8992
.iter()
9093
.map(|component| -> DefaultDesyncTreeNode {
91-
let hash = component.compute_hash::<H>();
92-
93-
// Update parent node hash from data
94-
DesyncHash::hash(&component, &mut hasher);
94+
let hash = if component
95+
.schema()
96+
.type_data
97+
.get::<SchemaDesyncHash>()
98+
.is_some()
99+
{
100+
// Update parent node hash from data
101+
DesyncHash::hash(&component, &mut hasher);
102+
Some(component.compute_hash::<H>())
103+
} else {
104+
None
105+
};
95106

96107
DefaultDesyncTreeNode::new(hash, None, vec![])
97108
})
98109
.collect();
99110

100-
DefaultDesyncTreeNode::new(
101-
hasher.finish(),
102-
Some(self.schema().full_name.to_string()),
103-
child_nodes,
104-
)
111+
let hash = if !child_nodes.is_empty() {
112+
Some(hasher.finish())
113+
} else {
114+
None
115+
};
116+
117+
DefaultDesyncTreeNode::new(hash, Some(self.schema().full_name.to_string()), child_nodes)
105118
}
106119
}
107120

framework_crates/bones_ecs/src/resources.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,24 @@ impl DesyncHash for UntypedResource {
3535
}
3636

3737
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResource {
38-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
38+
fn desync_tree_node<H: std::hash::Hasher + Default>(
39+
&self,
40+
_include_unhashable: bool,
41+
) -> DefaultDesyncTreeNode {
3942
let name = Some(self.schema().full_name.to_string());
4043

44+
let hashable = self.schema().type_data.get::<SchemaDesyncHash>().is_some();
45+
4146
if let Some(schema_box) = self.cell.borrow().as_ref() {
42-
let hash = schema_box.as_ref().compute_hash::<H>();
47+
let hash = if hashable {
48+
Some(schema_box.as_ref().compute_hash::<H>())
49+
} else {
50+
None
51+
};
4352
return DefaultDesyncTreeNode::new(hash, name, vec![]);
4453
}
4554

46-
// TODO should we only optionally provide node?
47-
DefaultDesyncTreeNode::new(0, name, vec![])
55+
DefaultDesyncTreeNode::new(None, name, vec![])
4856
}
4957
}
5058

@@ -190,7 +198,10 @@ impl DesyncHash for UntypedResources {
190198
}
191199

192200
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResources {
193-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
201+
fn desync_tree_node<H: std::hash::Hasher + Default>(
202+
&self,
203+
include_unhashable: bool,
204+
) -> DefaultDesyncTreeNode {
194205
let mut hasher = H::default();
195206
let mut child_nodes: Vec<DefaultDesyncTreeNode> = self
196207
.resources
@@ -202,8 +213,8 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResources {
202213
if !is_shared {
203214
// Only build child node if hashable
204215
let schema = resource_cell.schema();
205-
if schema.type_data.get::<SchemaDesyncHash>().is_some() {
206-
return Some(resource_cell.desync_tree_node::<H>());
216+
if include_unhashable || schema.type_data.get::<SchemaDesyncHash>().is_some() {
217+
return Some(resource_cell.desync_tree_node::<H>(include_unhashable));
207218
}
208219
}
209220
None
@@ -214,10 +225,12 @@ impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for UntypedResources {
214225

215226
for node in child_nodes.iter() {
216227
// Update parent hash
217-
DesyncHash::hash(&node.get_hash(), &mut hasher);
228+
if let Some(hash) = node.get_hash() {
229+
DesyncHash::hash(&hash, &mut hasher);
230+
}
218231
}
219232

220-
DefaultDesyncTreeNode::new(hasher.finish(), Some("Resources".into()), child_nodes)
233+
DefaultDesyncTreeNode::new(Some(hasher.finish()), Some("Resources".into()), child_nodes)
221234
}
222235
}
223236

@@ -302,8 +315,11 @@ impl DesyncHash for Resources {
302315
}
303316

304317
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for Resources {
305-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
306-
self.untyped.desync_tree_node::<H>()
318+
fn desync_tree_node<H: std::hash::Hasher + Default>(
319+
&self,
320+
include_unhashable: bool,
321+
) -> DefaultDesyncTreeNode {
322+
self.untyped.desync_tree_node::<H>(include_unhashable)
307323
}
308324
}
309325

framework_crates/bones_ecs/src/world.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,27 @@ impl DesyncHash for World {
4444
}
4545

4646
impl BuildDesyncNode<DefaultDesyncTreeNode, u64> for World {
47-
fn desync_tree_node<H: std::hash::Hasher + Default>(&self) -> DefaultDesyncTreeNode {
47+
fn desync_tree_node<H: std::hash::Hasher + Default>(
48+
&self,
49+
include_unhashable: bool,
50+
) -> DefaultDesyncTreeNode {
4851
let mut hasher = H::default();
4952

5053
let mut child_nodes: Vec<DefaultDesyncTreeNode> = vec![];
5154

52-
let components_node = self.components.desync_tree_node::<H>();
53-
components_node.get_hash().hash(&mut hasher);
55+
let components_node = self.components.desync_tree_node::<H>(include_unhashable);
56+
if let Some(hash) = components_node.get_hash() {
57+
hash.hash(&mut hasher);
58+
}
5459
child_nodes.push(components_node);
5560

56-
let resources_node = self.resources.desync_tree_node::<H>();
57-
resources_node.get_hash().hash(&mut hasher);
61+
let resources_node = self.resources.desync_tree_node::<H>(include_unhashable);
62+
if let Some(hash) = resources_node.get_hash() {
63+
hash.hash(&mut hasher);
64+
}
5865
child_nodes.push(resources_node);
5966

60-
DefaultDesyncTreeNode::new(hasher.finish(), Some("World".into()), child_nodes)
67+
DefaultDesyncTreeNode::new(Some(hasher.finish()), Some("World".into()), child_nodes)
6168
}
6269
}
6370

framework_crates/bones_framework/src/networking.rs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use self::{
66
};
77
use crate::prelude::*;
88
use bones_matchmaker_proto::{MATCH_ALPN, PLAY_ALPN};
9-
use desync::DesyncDebugHistoryBuffer;
9+
use desync::{DesyncDebugHistoryBuffer, DetectDesyncs};
1010
use fxhash::FxHasher;
1111
use ggrs::{DesyncDetection, P2PSession};
1212
use instant::Duration;
@@ -59,7 +59,9 @@ impl From<ggrs::InputStatus> for NetworkInputStatus {
5959

6060
/// Module prelude.
6161
pub mod prelude {
62-
pub use super::{input, lan, online, proto, DisconnectedPlayers, SyncingInfo, RUNTIME};
62+
pub use super::{
63+
desync::DetectDesyncs, input, lan, online, proto, DisconnectedPlayers, SyncingInfo, RUNTIME,
64+
};
6365

6466
#[cfg(feature = "net-debug")]
6567
pub use super::debug::prelude::*;
@@ -523,20 +525,12 @@ pub struct GgrsSessionRunner<'a, InputTypes: NetworkInputConfig<'a>> {
523525
/// Local input delay ggrs session was initialized with
524526
local_input_delay: usize,
525527

526-
/// Interval in frames of how often to hash state and check for desync with other clients.
527-
/// i.e if set to 10, will check every 10th frame. Desync detection disabled if None.
528-
pub detect_desyncs: Option<u32>,
528+
/// When provided, desync detection is enabled. Contains settings for desync detection.
529+
detect_desyncs: Option<DetectDesyncs>,
529530

530531
/// History buffer for desync debug data to fetch it upon detected desyncs.
531532
/// [`DefaultDesyncTree`] will be generated and saved here if feature `desync-debug` is enabled.
532533
pub desync_debug_history: Option<DesyncDebugHistoryBuffer<DefaultDesyncTree>>,
533-
534-
/// Override of hash function used to hash world for desync detection.
535-
/// By default, [`World`]'s [`DesyncHash`] impl is used.
536-
///
537-
/// This may be useful if you want to hash only a subset of components or resources
538-
/// during testing.
539-
pub world_hash_func: Option<fn(&World) -> u64>,
540534
}
541535

542536
/// The info required to create a [`GgrsSessionRunner`].
@@ -560,13 +554,8 @@ pub struct GgrsSessionRunnerInfo {
560554
/// `None` will use Bone's default.
561555
pub local_input_delay: Option<usize>,
562556

563-
/// Interval in frames of how often to hash state and check for desync with other clients.
564-
/// i.e if set to 10, will check every 10th frame. Desync detection disabled if None.
565-
pub detect_desyncs: Option<u32>,
566-
567-
/// Override of hash function used to hash world for desync detection.
568-
/// By default, [`World`]'s [`DesyncHash`] impl is used.
569-
pub world_hash_func: Option<fn(&World) -> u64>,
557+
/// When provided, desync detection is enabled. Contains settings for desync detection.
558+
pub detect_desyncs: Option<DetectDesyncs>,
570559
}
571560

572561
impl GgrsSessionRunnerInfo {
@@ -575,8 +564,7 @@ impl GgrsSessionRunnerInfo {
575564
socket: Socket,
576565
max_prediction_window: Option<usize>,
577566
local_input_delay: Option<usize>,
578-
world_hash_func: Option<fn(&World) -> u64>,
579-
detect_desyncs: Option<u32>,
567+
detect_desyncs: Option<DetectDesyncs>,
580568
) -> Self {
581569
let player_idx = socket.player_idx();
582570
let player_count = socket.player_count();
@@ -587,7 +575,6 @@ impl GgrsSessionRunnerInfo {
587575
max_prediction_window,
588576
local_input_delay,
589577
detect_desyncs,
590-
world_hash_func,
591578
}
592579
}
593580
}
@@ -625,8 +612,10 @@ where
625612
.try_send(NetworkDebugMessage::SetMaxPrediction(max_prediction))
626613
.unwrap();
627614

628-
let desync_detection = match info.detect_desyncs {
629-
Some(interval) => DesyncDetection::On { interval },
615+
let desync_detection = match info.detect_desyncs.as_ref() {
616+
Some(config) => DesyncDetection::On {
617+
interval: config.detection_interval,
618+
},
630619
None => DesyncDetection::Off,
631620
};
632621

@@ -655,9 +644,13 @@ where
655644
let session = builder.start_p2p_session(info.socket.clone()).unwrap();
656645

657646
#[cfg(feature = "desync-debug")]
658-
let desync_debug_history = info
659-
.detect_desyncs
660-
.map(DesyncDebugHistoryBuffer::<DefaultDesyncTree>::new);
647+
let desync_debug_history = if let Some(detect_desync) = info.detect_desyncs.as_ref() {
648+
Some(DesyncDebugHistoryBuffer::<DefaultDesyncTree>::new(
649+
detect_desync.detection_interval,
650+
))
651+
} else {
652+
None
653+
};
661654

662655
#[cfg(not(feature = "desync-debug"))]
663656
let desync_debug_history = None;
@@ -678,7 +671,6 @@ where
678671
local_input_disabled: false,
679672
detect_desyncs: info.detect_desyncs,
680673
desync_debug_history,
681-
world_hash_func: info.world_hash_func,
682674
}
683675
}
684676
}
@@ -866,7 +858,9 @@ where
866858
// GGRS should only use hashes from fixed interval.
867859

868860
// If desync detection enabled, hash world.
869-
let checksum = if self.detect_desyncs.is_some() {
861+
let checksum = if let Some(detect_desyncs) =
862+
self.detect_desyncs.as_ref()
863+
{
870864
#[cfg(feature = "desync-debug")]
871865
{
872866
if let Some(desync_debug_history) =
@@ -875,14 +869,17 @@ where
875869
if desync_debug_history
876870
.is_desync_detect_frame(frame as u32)
877871
{
878-
desync_debug_history.record(
879-
frame as u32,
880-
world.desync_tree_node::<FxHasher>().into(),
872+
let tree = DefaultDesyncTree::from(
873+
world.desync_tree_node::<FxHasher>(
874+
detect_desyncs.include_unhashable_nodes,
875+
),
881876
);
877+
desync_debug_history.record(frame as u32, tree);
882878
}
883879
}
884880
}
885-
if let Some(hash_func) = self.world_hash_func {
881+
882+
if let Some(hash_func) = detect_desyncs.world_hash_func {
886883
Some(hash_func(world) as u128)
887884
} else {
888885
let mut hasher = FxHasher::default();
@@ -1043,8 +1040,7 @@ where
10431040
player_count: self.session.num_players().try_into().unwrap(),
10441041
max_prediction_window: Some(self.session.max_prediction()),
10451042
local_input_delay: Some(self.local_input_delay),
1046-
detect_desyncs: self.detect_desyncs,
1047-
world_hash_func: self.world_hash_func,
1043+
detect_desyncs: self.detect_desyncs.clone(),
10481044
};
10491045
*self = GgrsSessionRunner::new(self.original_fps as f32, runner_info);
10501046
}

0 commit comments

Comments
 (0)