makepad/xr/tests/sync_md_loopback.rs
Admin 55dde7b02a Land the 3D renderer and leftover sim/math from rik2.
libs/render (Renderer, SceneDraws, preview play) plus the Arcade
sim/math it still draws through. Gameplay crates stay out.
2026-08-18 14:23:59 +02:00

945 lines
33 KiB
Rust

use makepad_xr::makepad_widgets::*;
use makepad_xr::{net::*, scene::*};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::mpsc,
time::{Duration, Instant},
};
const TEST_IO_TIMEOUT: Duration = Duration::from_secs(3);
fn wait_for_event<F>(node: &XrNetNode, mut predicate: F) -> Option<XrNetIncoming>
where
F: FnMut(&XrNetIncoming) -> bool,
{
let start = Instant::now();
while start.elapsed() < TEST_IO_TIMEOUT {
match node
.incoming_receiver
.recv_timeout(Duration::from_millis(50))
{
Ok(event) if predicate(&event) => return Some(event),
Ok(_) => continue,
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(mpsc::RecvTimeoutError::Disconnected) => return None,
}
}
None
}
fn localhost_config(
node_id: u64,
discovery_port: u16,
data_port: u16,
sync_port: u16,
discovery_targets: Vec<u16>,
) -> XrNetConfig {
XrNetConfig {
node_id: XrNetPeerId(node_id),
discovery_bind: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), discovery_port),
data_bind: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), data_port),
sync_bind: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), sync_port),
discovery_targets: discovery_targets
.into_iter()
.map(|port| SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
.collect(),
timing: XrNetTimingConfig {
discovery_interval: Duration::from_millis(20),
peer_timeout: Duration::from_millis(150),
poll_interval: Duration::from_millis(5),
},
}
}
fn wait_for_join_pair(left: &XrNetNode, right: &XrNetNode) {
let _ = wait_for_event(left, |event| matches!(event, XrNetIncoming::Join { .. }))
.expect("left test client should discover right");
let _ = wait_for_event(right, |event| matches!(event, XrNetIncoming::Join { .. }))
.expect("right test client should discover left");
}
fn wait_for_sync_pair(left: &mut XrNetNode, right: &mut XrNetNode) {
wait_for_join_pair(left, right);
let left_barrier =
left.send_activity(XrActivityId(makepad_xr::live_id!(sync_barrier_left)), 0.1);
let received_left_barrier = wait_for_event(right, |event| {
matches!(
event,
XrNetIncoming::Activity { control, .. }
if control.state() == left_barrier
)
})
.expect("right test client should receive the left->right sync barrier");
match received_left_barrier {
XrNetIncoming::Activity { control, .. } => assert_eq!(control.state(), left_barrier),
_ => unreachable!(),
}
let right_barrier =
right.send_activity(XrActivityId(makepad_xr::live_id!(sync_barrier_right)), 0.2);
let received_right_barrier = wait_for_event(left, |event| {
matches!(
event,
XrNetIncoming::Activity { control, .. }
if control.state() == right_barrier
)
})
.expect("left test client should receive the right->left sync barrier");
match received_right_barrier {
XrNetIncoming::Activity { control, .. } => assert_eq!(control.state(), right_barrier),
_ => unreachable!(),
}
}
fn shooter_activity_id() -> XrActivityId {
XrActivityId(makepad_xr::live_id!(ico_shoot_scene))
}
fn shooter_binding(widget_uid: WidgetUid) -> XrSpawnableObjectBinding {
XrSpawnableObjectBinding {
object_id: XrSpawnableObjectId(0x44),
allocation_group_id: XrSpawnableObjectId(0x91),
widget_uid,
bootstrap_shared: false,
}
}
#[test]
fn shooter_particle_spawn_state_and_despawn_roundtrip_over_loopback() {
let activity_id = shooter_activity_id();
let widget_uid = WidgetUid(101);
let mut left = XrNetNode::with_config(localhost_config(501, 45046, 45047, 45048, vec![45056]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(502, 45056, 45057, 45058, vec![45046]))
.expect("right test client should bind");
let left_peer_id = left.node_id();
let right_peer_id = right.node_id();
let mut right_registry = XrSharedObjectRegistry::default();
right_registry.set_local_peer_id(right_peer_id);
right_registry.replace_spawnables(activity_id, [shooter_binding(widget_uid)]);
wait_for_sync_pair(&mut left, &mut right);
let activity = left.send_activity(activity_id, 1.0);
let activity_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::Activity { control, .. }
if control.state() == activity
)
})
.expect("right test client should receive the synced shooter activity");
let received_activity = match activity_event {
XrNetIncoming::Activity { control, .. } => control.state(),
_ => unreachable!(),
};
assert_eq!(received_activity, activity);
let object_id = xr_make_shared_object_id(left_peer_id, XrSharedObjectCounter(7))
.expect("counter should fit");
let spawn = XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch: 0,
authority: left_peer_id,
fidelity: XrSharedObjectFidelity::ImpactCritical,
shape: XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id: XrSpawnableObjectId(0x44),
},
pose: Pose::new(Quat::default(), vec3f(0.12, 1.18, -0.46)),
linvel: vec3f(0.8, 0.1, -6.4),
angvel: vec3f(0.0, 1.6, 0.0),
};
left.send_shared_object_control(spawn.clone());
let spawn_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrSpawnObject { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("right test client should receive the shooter particle spawn");
let received_spawn = match spawn_event {
XrNetIncoming::SharedObjectControl { control, .. } => control,
_ => unreachable!(),
};
let remote_widget = match received_spawn {
XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch,
authority,
fidelity,
shape:
XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id,
},
pose,
linvel,
angvel,
} => right_registry
.register_remote_shared_object(
activity_id,
0.0,
object_id,
epoch,
authority,
fidelity,
spawnable_id,
pose,
linvel,
angvel,
)
.expect("remote shooter particle should bind into the projectile pool"),
_ => unreachable!(),
};
assert_eq!(remote_widget, widget_uid);
let state = XrNetSharedObjectState {
seq: 0,
sent_at: 1.2,
physics_tick: 17,
object_id,
epoch: 0,
authority: left_peer_id,
fidelity: XrSharedObjectFidelity::ImpactCritical,
mode: XrSharedObjectMode::Dynamic,
pose: Pose::new(Quat::default(), vec3f(0.18, 1.16, -0.73)),
linvel: vec3f(0.4, -0.2, -6.0),
angvel: vec3f(0.0, 1.1, 0.0),
};
left.send_shared_object_state(state);
let state_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectState { state: received, .. }
if received.object_id == object_id && received.physics_tick == 17
)
})
.expect("right test client should receive the shooter particle udp state");
let (received_peer_id, received_state) = match state_event {
XrNetIncoming::SharedObjectState { peer, state } => (peer.id, state),
_ => unreachable!(),
};
assert_eq!(received_peer_id, left_peer_id);
assert_eq!(
right_registry.record_remote_shared_object_state(received_peer_id, received_state),
Some(widget_uid)
);
let remote_snapshot = right_registry
.remote_shared_object_snapshot(object_id)
.expect("remote shooter particle snapshot should exist after the udp state");
assert_eq!(remote_snapshot.widget_uid, widget_uid);
assert_eq!(remote_snapshot.latest_state, Some(received_state));
let despawn = XrNetSharedObjectControl::XrDespawnObject {
object_id,
epoch: 0,
};
left.send_shared_object_control(despawn.clone());
let despawn_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrDespawnObject { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("right test client should receive the shooter particle despawn");
let received_despawn = match despawn_event {
XrNetIncoming::SharedObjectControl { control, .. } => control,
_ => unreachable!(),
};
assert_eq!(received_despawn, despawn);
assert_eq!(
right_registry.release_remote_shared_object(object_id),
Some(widget_uid)
);
assert_eq!(right_registry.active_count(), 0);
}
#[test]
fn shared_object_state_roundtrip_preserves_contact_dominated_and_sleeping_modes() {
let activity_id = shooter_activity_id();
let widget_uid = WidgetUid(111);
let mut left = XrNetNode::with_config(localhost_config(506, 45066, 45067, 45068, vec![45076]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(507, 45076, 45077, 45078, vec![45066]))
.expect("right test client should bind");
let left_peer_id = left.node_id();
let right_peer_id = right.node_id();
let mut right_registry = XrSharedObjectRegistry::default();
right_registry.set_local_peer_id(right_peer_id);
right_registry.replace_spawnables(activity_id, [shooter_binding(widget_uid)]);
wait_for_sync_pair(&mut left, &mut right);
let object_id = xr_make_shared_object_id(left_peer_id, XrSharedObjectCounter(8))
.expect("counter should fit");
let spawn = XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch: 0,
authority: left_peer_id,
fidelity: XrSharedObjectFidelity::ImpactCritical,
shape: XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id: XrSpawnableObjectId(0x44),
},
pose: Pose::new(Quat::default(), vec3f(0.05, 1.11, -0.41)),
linvel: vec3f(0.6, 0.0, -4.8),
angvel: vec3f(0.0, 1.2, 0.0),
};
left.send_shared_object_control(spawn.clone());
let spawn_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrSpawnObject { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("right test client should receive the shared-object spawn before state updates");
match spawn_event {
XrNetIncoming::SharedObjectControl {
control:
XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch,
authority,
fidelity,
shape:
XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id,
},
pose,
linvel,
angvel,
},
..
} => {
assert_eq!(
right_registry.register_remote_shared_object(
activity_id,
0.0,
object_id,
epoch,
authority,
fidelity,
spawnable_id,
pose,
linvel,
angvel,
),
Some(widget_uid)
);
}
_ => unreachable!(),
}
let held_state = XrNetSharedObjectState {
seq: 0,
sent_at: 2.0,
physics_tick: 21,
object_id,
epoch: 0,
authority: left_peer_id,
fidelity: XrSharedObjectFidelity::ImpactCritical,
mode: XrSharedObjectMode::ContactDominated {
authority: left_peer_id,
hand: XrSharedHand::RightHand,
},
pose: Pose::new(Quat::default(), vec3f(0.09, 1.08, -0.55)),
linvel: vec3f(0.0, 0.0, -0.2),
angvel: vec3f(0.0, 0.0, 0.0),
};
left.send_shared_object_state(held_state);
let held_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectState { state, .. }
if state.object_id == object_id
&& state.physics_tick == 21
&& matches!(
state.mode,
XrSharedObjectMode::ContactDominated {
authority,
hand: XrSharedHand::RightHand,
} if authority == left_peer_id
)
)
})
.expect("right test client should receive the held-body shared-object state");
let (held_peer_id, held_state) = match held_event {
XrNetIncoming::SharedObjectState { peer, state } => (peer.id, state),
_ => unreachable!(),
};
assert_eq!(held_peer_id, left_peer_id);
assert_eq!(
right_registry.record_remote_shared_object_state(held_peer_id, held_state),
Some(widget_uid)
);
assert_eq!(
right_registry
.remote_shared_object_snapshot(object_id)
.expect("remote shared object should exist after held state")
.latest_state
.expect("held state should be stored")
.mode,
XrSharedObjectMode::ContactDominated {
authority: left_peer_id,
hand: XrSharedHand::RightHand,
}
);
let sleeping_state = XrNetSharedObjectState {
seq: 0,
sent_at: 2.1,
physics_tick: 22,
object_id,
epoch: 0,
authority: left_peer_id,
fidelity: XrSharedObjectFidelity::ImpactCritical,
mode: XrSharedObjectMode::Sleeping,
pose: Pose::new(Quat::default(), vec3f(0.10, 0.82, -0.61)),
linvel: vec3f(0.0, 0.0, 0.0),
angvel: vec3f(0.0, 0.0, 0.0),
};
left.send_shared_object_state(sleeping_state);
let sleeping_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectState { state, .. }
if state.object_id == object_id
&& state.physics_tick == 22
&& state.mode == XrSharedObjectMode::Sleeping
)
})
.expect("right test client should receive the sleeping shared-object state");
let (sleeping_peer_id, sleeping_state) = match sleeping_event {
XrNetIncoming::SharedObjectState { peer, state } => (peer.id, state),
_ => unreachable!(),
};
assert_eq!(sleeping_peer_id, left_peer_id);
assert_eq!(
right_registry.record_remote_shared_object_state(sleeping_peer_id, sleeping_state),
Some(widget_uid)
);
let snapshot = right_registry
.remote_shared_object_snapshot(object_id)
.expect("remote shared object should still exist after sleeping state");
assert_eq!(
snapshot
.latest_state
.expect("sleeping state should be stored")
.mode,
XrSharedObjectMode::Sleeping
);
}
#[test]
fn two_clients_handoff_shared_object_back_and_forth_without_reallocating_identity() {
let activity_id = shooter_activity_id();
let widget_uid = WidgetUid(101);
let mut left = XrNetNode::with_config(localhost_config(511, 45146, 45147, 45148, vec![45156]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(512, 45156, 45157, 45158, vec![45146]))
.expect("right test client should bind");
let left_peer_id = left.node_id();
let right_peer_id = right.node_id();
let mut left_registry = XrSharedObjectRegistry::default();
left_registry.set_local_peer_id(left_peer_id);
left_registry.replace_spawnables(activity_id, [shooter_binding(widget_uid)]);
let mut right_registry = XrSharedObjectRegistry::default();
right_registry.set_local_peer_id(right_peer_id);
right_registry.replace_spawnables(activity_id, [shooter_binding(widget_uid)]);
wait_for_sync_pair(&mut left, &mut right);
let allocation = left_registry
.allocate_local_shared_object(activity_id, widget_uid)
.expect("left shooter particle should allocate a local shared object id");
let object_id = allocation.shared_object_id;
let initial_pose = Pose::new(Quat::default(), vec3f(0.10, 1.04, -0.52));
let initial_linvel = vec3f(0.9, 0.1, -5.8);
let initial_angvel = vec3f(0.0, 0.8, 0.0);
let spawn = XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch: allocation.epoch,
authority: left_peer_id,
fidelity: allocation.fidelity,
shape: XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id: allocation.spawnable_object_id,
},
pose: initial_pose,
linvel: initial_linvel,
angvel: initial_angvel,
};
left.send_shared_object_control(spawn);
let spawn_event = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrSpawnObject { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("right test client should receive the initial shared object spawn");
match spawn_event {
XrNetIncoming::SharedObjectControl {
control:
XrNetSharedObjectControl::XrSpawnObject {
object_id,
epoch,
authority,
fidelity,
shape:
XrSharedObjectShape::ActivitySpawnable {
activity_id,
spawnable_id,
},
pose,
linvel,
angvel,
},
..
} => {
let widget = right_registry
.register_remote_shared_object(
activity_id,
0.0,
object_id,
epoch,
authority,
fidelity,
spawnable_id,
pose,
linvel,
angvel,
)
.expect("right registry should bind the remote shared object");
assert_eq!(widget, widget_uid);
}
_ => unreachable!(),
}
let request_id = 17u32;
let accepted_pose = Pose::new(Quat::default(), vec3f(0.24, 1.01, -0.42));
let accepted_linvel = vec3f(0.2, 0.0, -1.0);
let accepted_angvel = vec3f(0.0, 0.0, 0.0);
let takeover_request = XrNetSharedObjectControl::XrTakeoverRequest {
object_id,
epoch: allocation.epoch,
request_id,
based_on_seq: 0,
based_on_tick: 0,
candidate_owner: right_peer_id,
hand: XrSharedHand::RightHand,
hand_pose: accepted_pose,
hand_linvel: accepted_linvel,
};
right.send_shared_object_control(takeover_request.clone());
let received_request = wait_for_event(&left, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverRequest { request_id: event_id, .. },
..
} if *event_id == request_id
)
})
.expect("left test client should receive the takeover request");
match received_request {
XrNetIncoming::SharedObjectControl { control, .. } => {
assert_eq!(control, takeover_request);
}
_ => unreachable!(),
}
assert!(left_registry.schedule_authority_transfer(
object_id,
allocation.epoch.wrapping_add(1),
left_peer_id,
right_peer_id,
0.0,
0,
request_id,
Some(XrSharedHand::RightHand),
accepted_pose,
accepted_linvel,
accepted_angvel,
));
let takeover_accept = XrNetSharedObjectControl::XrTakeoverAccept {
object_id,
epoch: allocation.epoch.wrapping_add(1),
request_id,
new_authority: right_peer_id,
effective_at: 0.0,
effective_tick: 0,
pose: accepted_pose,
linvel: accepted_linvel,
angvel: accepted_angvel,
};
left.send_shared_object_control(takeover_accept.clone());
let received_accept = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverAccept { request_id: event_id, .. },
..
} if *event_id == request_id
)
})
.expect("right test client should receive the takeover accept");
match received_accept {
XrNetIncoming::SharedObjectControl { control, .. } => {
assert_eq!(control, takeover_accept);
}
_ => unreachable!(),
}
assert!(right_registry.schedule_authority_transfer(
object_id,
allocation.epoch.wrapping_add(1),
left_peer_id,
right_peer_id,
0.0,
0,
request_id,
None,
accepted_pose,
accepted_linvel,
accepted_angvel,
));
let left_transfer = left_registry.apply_scheduled_authority_transfers(0.1, 1);
let right_transfer = right_registry.apply_scheduled_authority_transfers(0.1, 1);
assert_eq!(left_transfer.len(), 1);
assert_eq!(right_transfer.len(), 1);
assert!(left_transfer[0].shadow);
assert!(!right_transfer[0].shadow);
assert_eq!(left_transfer[0].object_id, object_id);
assert_eq!(right_transfer[0].object_id, object_id);
assert_eq!(
left_registry
.remote_shared_object_snapshot(object_id)
.expect("left registry should demote the object into a remote shadow")
.authority,
right_peer_id
);
assert_eq!(
right_registry
.local_shared_object_snapshot(object_id)
.expect("right registry should promote the same object id into local authority")
.authority,
right_peer_id
);
let request_back_id = 18u32;
let handoff_pose = Pose::new(Quat::default(), vec3f(-0.18, 1.03, -0.40));
let handoff_linvel = vec3f(-0.1, 0.0, -0.4);
let handoff_angvel = vec3f(0.0, 0.0, 0.0);
let takeover_back = XrNetSharedObjectControl::XrTakeoverRequest {
object_id,
epoch: allocation.epoch.wrapping_add(1),
request_id: request_back_id,
based_on_seq: 0,
based_on_tick: 0,
candidate_owner: left_peer_id,
hand: XrSharedHand::LeftHand,
hand_pose: handoff_pose,
hand_linvel: handoff_linvel,
};
left.send_shared_object_control(takeover_back.clone());
let received_back_request = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverRequest { request_id: event_id, .. },
..
} if *event_id == request_back_id
)
})
.expect("right test client should receive the return takeover request");
match received_back_request {
XrNetIncoming::SharedObjectControl { control, .. } => {
assert_eq!(control, takeover_back);
}
_ => unreachable!(),
}
assert!(right_registry.schedule_authority_transfer(
object_id,
allocation.epoch.wrapping_add(2),
right_peer_id,
left_peer_id,
0.0,
0,
request_back_id,
Some(XrSharedHand::LeftHand),
handoff_pose,
handoff_linvel,
handoff_angvel,
));
let takeover_back_accept = XrNetSharedObjectControl::XrTakeoverAccept {
object_id,
epoch: allocation.epoch.wrapping_add(2),
request_id: request_back_id,
new_authority: left_peer_id,
effective_at: 0.0,
effective_tick: 0,
pose: handoff_pose,
linvel: handoff_linvel,
angvel: handoff_angvel,
};
right.send_shared_object_control(takeover_back_accept.clone());
let received_back_accept = wait_for_event(&left, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverAccept { request_id: event_id, .. },
..
} if *event_id == request_back_id
)
})
.expect("left test client should receive the return takeover accept");
match received_back_accept {
XrNetIncoming::SharedObjectControl { control, .. } => {
assert_eq!(control, takeover_back_accept);
}
_ => unreachable!(),
}
assert!(left_registry.schedule_authority_transfer(
object_id,
allocation.epoch.wrapping_add(2),
right_peer_id,
left_peer_id,
0.0,
0,
request_back_id,
None,
handoff_pose,
handoff_linvel,
handoff_angvel,
));
let left_return = left_registry.apply_scheduled_authority_transfers(0.2, 2);
let right_return = right_registry.apply_scheduled_authority_transfers(0.2, 2);
assert_eq!(left_return.len(), 1);
assert_eq!(right_return.len(), 1);
assert!(!left_return[0].shadow);
assert!(right_return[0].shadow);
assert_eq!(left_return[0].object_id, object_id);
assert_eq!(right_return[0].object_id, object_id);
assert_eq!(
left_registry
.local_shared_object_snapshot(object_id)
.expect("left registry should regain local authority on the same object id")
.authority,
left_peer_id
);
assert_eq!(
right_registry
.remote_shared_object_snapshot(object_id)
.expect("right registry should demote the object back into a remote shadow")
.authority,
left_peer_id
);
}
#[test]
fn takeover_reject_roundtrip_over_loopback_preserves_request_identity() {
let mut left = XrNetNode::with_config(localhost_config(521, 45246, 45247, 45248, vec![45256]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(522, 45256, 45257, 45258, vec![45246]))
.expect("right test client should bind");
let object_id = xr_make_shared_object_id(left.node_id(), XrSharedObjectCounter(9)).unwrap();
wait_for_sync_pair(&mut left, &mut right);
let request = XrNetSharedObjectControl::XrTakeoverRequest {
object_id,
epoch: 3,
request_id: 31,
based_on_seq: 14,
based_on_tick: 22,
candidate_owner: right.node_id(),
hand: XrSharedHand::RightHand,
hand_pose: Pose::new(Quat::default(), vec3f(0.16, 1.02, -0.34)),
hand_linvel: vec3f(0.2, 0.0, -0.1),
};
right.send_shared_object_control(request.clone());
let received_request = wait_for_event(&left, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverRequest { request_id, .. },
..
} if *request_id == 31
)
})
.expect("left test client should receive the takeover request");
match received_request {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, request),
_ => unreachable!(),
}
let reject = XrNetSharedObjectControl::XrTakeoverReject {
object_id,
epoch: 3,
request_id: 31,
authoritative_seq: 15,
authoritative_tick: 23,
};
left.send_shared_object_control(reject.clone());
let received_reject = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrTakeoverReject { request_id, .. },
..
} if *request_id == 31
)
})
.expect("right test client should receive the takeover reject");
match received_reject {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, reject),
_ => unreachable!(),
}
}
#[test]
fn contact_impulse_and_reset_controls_roundtrip_over_loopback() {
let mut left = XrNetNode::with_config(localhost_config(531, 45346, 45347, 45348, vec![45356]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(532, 45356, 45357, 45358, vec![45346]))
.expect("right test client should bind");
let object_id = xr_make_shared_object_id(left.node_id(), XrSharedObjectCounter(11)).unwrap();
wait_for_sync_pair(&mut left, &mut right);
let impulse = XrNetSharedObjectControl::XrContactImpulse {
object_id,
epoch: 2,
based_on_seq: 8,
based_on_tick: 13,
hand: XrSharedHand::LeftHand,
hand_pose: Pose::new(Quat::default(), vec3f(-0.14, 1.04, -0.30)),
point: vec3f(-0.12, 0.98, -0.46),
impulse: vec3f(0.3, 0.1, -0.8),
};
right.send_shared_object_control(impulse.clone());
let received_impulse = wait_for_event(&left, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrContactImpulse { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("left test client should receive the contact impulse control");
match received_impulse {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, impulse),
_ => unreachable!(),
}
let reset = XrNetSharedObjectControl::XrResetObject {
object_id,
epoch: 3,
pose: Pose::new(Quat::default(), vec3f(0.02, 1.10, -0.40)),
linvel: vec3f(0.0, 0.0, 0.0),
angvel: vec3f(0.0, 0.0, 0.0),
};
left.send_shared_object_control(reset.clone());
let received_reset = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrResetObject { object_id: event_id, .. },
..
} if *event_id == object_id
)
})
.expect("right test client should receive the reset control");
match received_reset {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, reset),
_ => unreachable!(),
}
}
#[test]
fn clock_ping_and_pong_controls_roundtrip_over_loopback() {
let mut left = XrNetNode::with_config(localhost_config(541, 45446, 45447, 45448, vec![45456]))
.expect("left test client should bind");
let mut right = XrNetNode::with_config(localhost_config(542, 45456, 45457, 45458, vec![45446]))
.expect("right test client should bind");
wait_for_sync_pair(&mut left, &mut right);
let ping = XrNetSharedObjectControl::XrClockPing {
seq: 41,
sent_at: 12.5,
};
left.send_shared_object_control(ping.clone());
let received_ping = wait_for_event(&right, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrClockPing { seq, .. },
..
} if *seq == 41
)
})
.expect("right test client should receive the clock ping");
match received_ping {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, ping),
_ => unreachable!(),
}
let pong = XrNetSharedObjectControl::XrClockPong {
seq: 41,
echoed_at: 12.5,
replied_at: 12.56,
};
right.send_shared_object_control(pong.clone());
let received_pong = wait_for_event(&left, |event| {
matches!(
event,
XrNetIncoming::SharedObjectControl {
control: XrNetSharedObjectControl::XrClockPong { seq, .. },
..
} if *seq == 41
)
})
.expect("left test client should receive the clock pong");
match received_pong {
XrNetIncoming::SharedObjectControl { control, .. } => assert_eq!(control, pong),
_ => unreachable!(),
}
}