initial commit for session and lock features
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
@@ -14,14 +14,14 @@ impl Plugin for CameraPlugin {
|
||||
|
||||
/// Set up the 3D camera
|
||||
///
|
||||
/// Camera is positioned at (4, 3, 6) looking at the cube's initial position (0, 0.5, 0).
|
||||
/// This provides a good viewing angle to see the cube, ground plane, and any movements.
|
||||
/// Camera is positioned at (4, 3, 6) looking at the cube's initial position (0,
|
||||
/// 0.5, 0). This provides a good viewing angle to see the cube, ground plane,
|
||||
/// and any movements.
|
||||
fn setup_camera(mut commands: Commands) {
|
||||
info!("Setting up camera");
|
||||
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(4.0, 3.0, 6.0)
|
||||
.looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y),
|
||||
Transform::from_xyz(4.0, 3.0, 6.0).looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use lib::{
|
||||
networking::{NetworkedEntity, NetworkedTransform, Synced},
|
||||
networking::{
|
||||
NetworkedEntity,
|
||||
NetworkedTransform,
|
||||
Synced,
|
||||
},
|
||||
persistence::Persisted,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Marker component for the replicated cube
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
//! Debug UI overlay using egui
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::{egui, EguiContexts, EguiPrimaryContextPass};
|
||||
use lib::networking::{GossipBridge, NodeVectorClock};
|
||||
use bevy_egui::{
|
||||
egui,
|
||||
EguiContexts,
|
||||
EguiPrimaryContextPass,
|
||||
};
|
||||
use lib::networking::{
|
||||
GossipBridge,
|
||||
NodeVectorClock,
|
||||
};
|
||||
|
||||
pub struct DebugUiPlugin;
|
||||
|
||||
@@ -17,7 +24,10 @@ fn render_debug_ui(
|
||||
mut contexts: EguiContexts,
|
||||
node_clock: Option<Res<NodeVectorClock>>,
|
||||
gossip_bridge: Option<Res<GossipBridge>>,
|
||||
cube_query: Query<(&Transform, &lib::networking::NetworkedEntity), With<crate::cube::CubeMarker>>,
|
||||
cube_query: Query<
|
||||
(&Transform, &lib::networking::NetworkedEntity),
|
||||
With<crate::cube::CubeMarker>,
|
||||
>,
|
||||
) {
|
||||
let Ok(ctx) = contexts.ctx_mut() else {
|
||||
return;
|
||||
@@ -35,7 +45,8 @@ fn render_debug_ui(
|
||||
if let Some(clock) = &node_clock {
|
||||
ui.label(format!("Node ID: {}", &clock.node_id.to_string()[..8]));
|
||||
// Show the current node's clock value (timestamp)
|
||||
let current_timestamp = clock.clock.clocks.get(&clock.node_id).copied().unwrap_or(0);
|
||||
let current_timestamp =
|
||||
clock.clock.clocks.get(&clock.node_id).copied().unwrap_or(0);
|
||||
ui.label(format!("Clock: {}", current_timestamp));
|
||||
ui.label(format!("Known nodes: {}", clock.clock.clocks.len()));
|
||||
} else {
|
||||
@@ -46,7 +57,10 @@ fn render_debug_ui(
|
||||
|
||||
// Gossip bridge status
|
||||
if let Some(bridge) = &gossip_bridge {
|
||||
ui.label(format!("Bridge Node: {}", &bridge.node_id().to_string()[..8]));
|
||||
ui.label(format!(
|
||||
"Bridge Node: {}",
|
||||
&bridge.node_id().to_string()[..8]
|
||||
));
|
||||
ui.label("Status: Connected");
|
||||
} else {
|
||||
ui.label("Gossip: Not ready");
|
||||
@@ -58,25 +72,38 @@ fn render_debug_ui(
|
||||
|
||||
// Cube information
|
||||
match cube_query.iter().next() {
|
||||
Some((transform, networked)) => {
|
||||
| Some((transform, networked)) => {
|
||||
let pos = transform.translation;
|
||||
ui.label(format!("Position: ({:.2}, {:.2}, {:.2})", pos.x, pos.y, pos.z));
|
||||
ui.label(format!(
|
||||
"Position: ({:.2}, {:.2}, {:.2})",
|
||||
pos.x, pos.y, pos.z
|
||||
));
|
||||
|
||||
let (axis, angle) = transform.rotation.to_axis_angle();
|
||||
let angle_deg: f32 = angle.to_degrees();
|
||||
ui.label(format!("Rotation: {:.2}° around ({:.2}, {:.2}, {:.2})",
|
||||
angle_deg, axis.x, axis.y, axis.z));
|
||||
ui.label(format!(
|
||||
"Rotation: {:.2}° around ({:.2}, {:.2}, {:.2})",
|
||||
angle_deg, axis.x, axis.y, axis.z
|
||||
));
|
||||
|
||||
ui.label(format!("Scale: ({:.2}, {:.2}, {:.2})",
|
||||
transform.scale.x, transform.scale.y, transform.scale.z));
|
||||
ui.label(format!(
|
||||
"Scale: ({:.2}, {:.2}, {:.2})",
|
||||
transform.scale.x, transform.scale.y, transform.scale.z
|
||||
));
|
||||
|
||||
ui.add_space(5.0);
|
||||
ui.label(format!("Network ID: {}", &networked.network_id.to_string()[..8]));
|
||||
ui.label(format!("Owner: {}", &networked.owner_node_id.to_string()[..8]));
|
||||
}
|
||||
None => {
|
||||
ui.label(format!(
|
||||
"Network ID: {}",
|
||||
&networked.network_id.to_string()[..8]
|
||||
));
|
||||
ui.label(format!(
|
||||
"Owner: {}",
|
||||
&networked.owner_node_id.to_string()[..8]
|
||||
));
|
||||
},
|
||||
| None => {
|
||||
ui.label("Cube: Not spawned yet");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Gossip networking setup with dedicated tokio runtime
|
||||
//!
|
||||
//! This module manages iroh-gossip networking with a tokio runtime running as a sidecar to Bevy.
|
||||
//! The tokio runtime runs in a dedicated background thread, separate from Bevy's ECS loop.
|
||||
//! This module manages iroh-gossip networking with a tokio runtime running as a
|
||||
//! sidecar to Bevy. The tokio runtime runs in a dedicated background thread,
|
||||
//! separate from Bevy's ECS loop.
|
||||
//!
|
||||
//! # Architecture
|
||||
//!
|
||||
@@ -48,9 +49,16 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use bevy::prelude::*;
|
||||
use lib::networking::GossipBridge;
|
||||
use lib::networking::{GossipBridge, SessionId};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Session ID to use for network initialization
|
||||
///
|
||||
/// This resource must be inserted before setup_gossip_networking runs.
|
||||
/// It provides the session ID used to derive the session-specific ALPN.
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct InitialSessionId(pub SessionId);
|
||||
|
||||
/// Channel for receiving the GossipBridge from the background thread
|
||||
///
|
||||
/// This resource exists temporarily during startup. Once the GossipBridge
|
||||
@@ -69,8 +77,21 @@ pub struct GossipBridgeChannel(crossbeam_channel::Receiver<GossipBridge>);
|
||||
///
|
||||
/// - **macOS**: Full support with mDNS discovery
|
||||
/// - **iOS**: Not yet implemented
|
||||
pub fn setup_gossip_networking(mut commands: Commands) {
|
||||
info!("Setting up gossip networking...");
|
||||
///
|
||||
/// # Requirements
|
||||
///
|
||||
/// The InitialSessionId resource must be inserted before this system runs.
|
||||
/// If not present, an error is logged and networking is disabled.
|
||||
pub fn setup_gossip_networking(
|
||||
mut commands: Commands,
|
||||
session_id: Option<Res<InitialSessionId>>,
|
||||
) {
|
||||
let Some(session_id) = session_id else {
|
||||
error!("InitialSessionId resource not found - cannot initialize networking");
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Setting up gossip networking for session {}...", session_id.0);
|
||||
|
||||
// Spawn dedicated thread with Tokio runtime for gossip initialization
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
@@ -78,19 +99,20 @@ pub fn setup_gossip_networking(mut commands: Commands) {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
commands.insert_resource(GossipBridgeChannel(receiver));
|
||||
|
||||
let session_id = session_id.0.clone();
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async move {
|
||||
match init_gossip().await {
|
||||
Ok(bridge) => {
|
||||
match init_gossip(session_id).await {
|
||||
| Ok(bridge) => {
|
||||
info!("Gossip bridge initialized successfully");
|
||||
if let Err(e) = sender.send(bridge) {
|
||||
error!("Failed to send bridge to main thread: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
},
|
||||
| Err(e) => {
|
||||
error!("Failed to initialize gossip: {}", e);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -114,8 +136,7 @@ pub fn setup_gossip_networking(mut commands: Commands) {
|
||||
/// - **iOS**: No-op (networking not implemented)
|
||||
pub fn poll_gossip_bridge(
|
||||
mut commands: Commands,
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
channel: Option<Res<GossipBridgeChannel>>,
|
||||
#[cfg(not(target_os = "ios"))] channel: Option<Res<GossipBridgeChannel>>,
|
||||
) {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(channel) = channel {
|
||||
@@ -127,16 +148,21 @@ pub fn poll_gossip_bridge(
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize iroh-gossip networking stack
|
||||
/// Initialize iroh-gossip networking stack with session-specific ALPN
|
||||
///
|
||||
/// This async function runs in the background tokio runtime and:
|
||||
/// 1. Creates an iroh endpoint with mDNS discovery
|
||||
/// 2. Spawns the gossip protocol
|
||||
/// 3. Sets up the router to accept gossip connections
|
||||
/// 4. Subscribes to a shared topic (ID: [42; 32])
|
||||
/// 5. Waits for join with a 2-second timeout
|
||||
/// 6. Creates and configures the GossipBridge
|
||||
/// 7. Spawns forwarding tasks to bridge messages
|
||||
/// 3. Derives session-specific ALPN from session ID (using BLAKE3)
|
||||
/// 4. Sets up the router to accept connections on the session ALPN
|
||||
/// 5. Subscribes to a topic derived from the session ALPN
|
||||
/// 6. Waits for join with a 2-second timeout
|
||||
/// 7. Creates and configures the GossipBridge
|
||||
/// 8. Spawns forwarding tasks to bridge messages
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `session_id`: The session ID used to derive the ALPN for network isolation
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
@@ -147,12 +173,16 @@ pub fn poll_gossip_bridge(
|
||||
///
|
||||
/// This function is only compiled on non-iOS platforms.
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
async fn init_gossip() -> Result<GossipBridge> {
|
||||
use iroh::discovery::mdns::MdnsDiscovery;
|
||||
use iroh::protocol::Router;
|
||||
use iroh::Endpoint;
|
||||
use iroh_gossip::net::Gossip;
|
||||
use iroh_gossip::proto::TopicId;
|
||||
async fn init_gossip(session_id: SessionId) -> Result<GossipBridge> {
|
||||
use iroh::{
|
||||
discovery::mdns::MdnsDiscovery,
|
||||
protocol::Router,
|
||||
Endpoint,
|
||||
};
|
||||
use iroh_gossip::{
|
||||
net::Gossip,
|
||||
proto::TopicId,
|
||||
};
|
||||
|
||||
info!("Creating endpoint with mDNS discovery...");
|
||||
let endpoint = Endpoint::builder()
|
||||
@@ -172,14 +202,21 @@ async fn init_gossip() -> Result<GossipBridge> {
|
||||
info!("Spawning gossip protocol...");
|
||||
let gossip = Gossip::builder().spawn(endpoint.clone());
|
||||
|
||||
// Derive session-specific ALPN for network isolation
|
||||
let session_alpn = session_id.to_alpn();
|
||||
info!(
|
||||
"Using session-specific ALPN (session: {})",
|
||||
session_id
|
||||
);
|
||||
|
||||
info!("Setting up router...");
|
||||
let router = Router::builder(endpoint.clone())
|
||||
.accept(iroh_gossip::ALPN, gossip.clone())
|
||||
.accept(session_alpn.as_slice(), gossip.clone())
|
||||
.spawn();
|
||||
|
||||
// Subscribe to shared topic
|
||||
let topic_id = TopicId::from_bytes([42; 32]);
|
||||
info!("Subscribing to topic...");
|
||||
// Subscribe to topic derived from session ALPN (use same bytes for consistency)
|
||||
let topic_id = TopicId::from_bytes(session_alpn);
|
||||
info!("Subscribing to session topic...");
|
||||
let subscribe_handle = gossip.subscribe(topic_id, vec![]).await?;
|
||||
|
||||
let (sender, mut receiver) = subscribe_handle.split();
|
||||
@@ -187,9 +224,9 @@ async fn init_gossip() -> Result<GossipBridge> {
|
||||
// Wait for join (with timeout since we might be the first node)
|
||||
info!("Waiting for gossip join...");
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(2), receiver.joined()).await {
|
||||
Ok(Ok(())) => info!("Joined gossip swarm"),
|
||||
Ok(Err(e)) => warn!("Join error: {} (proceeding anyway)", e),
|
||||
Err(_) => info!("Join timeout (first node in swarm)"),
|
||||
| Ok(Ok(())) => info!("Joined gossip swarm"),
|
||||
| Ok(Err(e)) => warn!("Join error: {} (proceeding anyway)", e),
|
||||
| Err(_) => info!("Join timeout (first node in swarm)"),
|
||||
}
|
||||
|
||||
// Create bridge
|
||||
@@ -204,16 +241,19 @@ async fn init_gossip() -> Result<GossipBridge> {
|
||||
|
||||
/// Spawn tokio tasks to forward messages between iroh-gossip and GossipBridge
|
||||
///
|
||||
/// This function spawns two concurrent tokio tasks that run for the lifetime of the application:
|
||||
/// This function spawns two concurrent tokio tasks that run for the lifetime of
|
||||
/// the application:
|
||||
///
|
||||
/// 1. **Outgoing Task**: Polls GossipBridge for outgoing messages and broadcasts them via gossip
|
||||
/// 2. **Incoming Task**: Receives messages from gossip and pushes them into GossipBridge
|
||||
/// 1. **Outgoing Task**: Polls GossipBridge for outgoing messages and
|
||||
/// broadcasts them via gossip
|
||||
/// 2. **Incoming Task**: Receives messages from gossip and pushes them into
|
||||
/// GossipBridge
|
||||
///
|
||||
/// # Lifetime Management
|
||||
///
|
||||
/// The iroh resources (endpoint, router, gossip) are moved into the first task to keep them
|
||||
/// alive for the application lifetime. Without this, they would be dropped immediately and
|
||||
/// the gossip connection would close.
|
||||
/// The iroh resources (endpoint, router, gossip) are moved into the first task
|
||||
/// to keep them alive for the application lifetime. Without this, they would be
|
||||
/// dropped immediately and the gossip connection would close.
|
||||
///
|
||||
/// # Platform Support
|
||||
///
|
||||
@@ -227,10 +267,11 @@ fn spawn_bridge_tasks(
|
||||
_router: iroh::protocol::Router,
|
||||
_gossip: iroh_gossip::net::Gossip,
|
||||
) {
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_lite::StreamExt;
|
||||
use lib::networking::VersionedMessage;
|
||||
use std::time::Duration;
|
||||
|
||||
let node_id = bridge.node_id();
|
||||
|
||||
@@ -239,8 +280,8 @@ fn spawn_bridge_tasks(
|
||||
let bridge_out = bridge.clone();
|
||||
tokio::spawn(async move {
|
||||
let _endpoint = _endpoint; // Keep alive for app lifetime
|
||||
let _router = _router; // Keep alive for app lifetime
|
||||
let _gossip = _gossip; // Keep alive for app lifetime
|
||||
let _router = _router; // Keep alive for app lifetime
|
||||
let _gossip = _gossip; // Keep alive for app lifetime
|
||||
|
||||
loop {
|
||||
if let Some(msg) = bridge_out.try_recv_outgoing() {
|
||||
@@ -259,7 +300,7 @@ fn spawn_bridge_tasks(
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match tokio::time::timeout(Duration::from_millis(100), receiver.next()).await {
|
||||
Ok(Some(Ok(event))) => {
|
||||
| Ok(Some(Ok(event))) => {
|
||||
if let iroh_gossip::api::Event::Received(msg) = event {
|
||||
if let Ok(versioned_msg) =
|
||||
bincode::deserialize::<VersionedMessage>(&msg.content)
|
||||
@@ -269,10 +310,10 @@ fn spawn_bridge_tasks(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(e))) => error!("[Node {}] Receiver error: {}", node_id, e),
|
||||
Ok(None) => break,
|
||||
Err(_) => {} // Timeout
|
||||
},
|
||||
| Ok(Some(Err(e))) => error!("[Node {}] Receiver error: {}", node_id, e),
|
||||
| Ok(None) => break,
|
||||
| Err(_) => {}, // Timeout
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,31 +5,52 @@
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
time::{
|
||||
Duration,
|
||||
Instant,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use app::CubeMarker;
|
||||
use bevy::{
|
||||
app::{App, ScheduleRunnerPlugin},
|
||||
app::{
|
||||
App,
|
||||
ScheduleRunnerPlugin,
|
||||
},
|
||||
ecs::world::World,
|
||||
prelude::*,
|
||||
MinimalPlugins,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_lite::StreamExt;
|
||||
use iroh::{protocol::Router, Endpoint};
|
||||
use iroh::{
|
||||
protocol::Router,
|
||||
Endpoint,
|
||||
};
|
||||
use iroh_gossip::{
|
||||
api::{GossipReceiver, GossipSender},
|
||||
api::{
|
||||
GossipReceiver,
|
||||
GossipSender,
|
||||
},
|
||||
net::Gossip,
|
||||
proto::TopicId,
|
||||
};
|
||||
use lib::{
|
||||
networking::{
|
||||
GossipBridge, NetworkedEntity, NetworkedTransform, NetworkingConfig, NetworkingPlugin,
|
||||
Synced, VersionedMessage,
|
||||
GossipBridge,
|
||||
NetworkedEntity,
|
||||
NetworkedTransform,
|
||||
NetworkingConfig,
|
||||
NetworkingPlugin,
|
||||
Synced,
|
||||
VersionedMessage,
|
||||
},
|
||||
persistence::{
|
||||
Persisted,
|
||||
PersistenceConfig,
|
||||
PersistencePlugin,
|
||||
},
|
||||
persistence::{Persisted, PersistenceConfig, PersistencePlugin},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
@@ -66,11 +87,9 @@ mod test_utils {
|
||||
pub fn create_test_app(node_id: Uuid, db_path: PathBuf, bridge: GossipBridge) -> App {
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins(
|
||||
MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
|
||||
1.0 / 60.0,
|
||||
))),
|
||||
)
|
||||
app.add_plugins(MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(
|
||||
Duration::from_secs_f64(1.0 / 60.0),
|
||||
)))
|
||||
.insert_resource(bridge)
|
||||
.add_plugins(NetworkingPlugin::new(NetworkingConfig {
|
||||
node_id,
|
||||
@@ -117,8 +136,7 @@ mod test_utils {
|
||||
check_fn: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Fn(&mut World, &mut World) -> bool,
|
||||
{
|
||||
F: Fn(&mut World, &mut World) -> bool, {
|
||||
let start = Instant::now();
|
||||
let mut tick_count = 0;
|
||||
|
||||
@@ -200,10 +218,10 @@ mod test_utils {
|
||||
println!(" Connecting to bootstrap peers...");
|
||||
for addr in &bootstrap_addrs {
|
||||
match endpoint.connect(addr.clone(), iroh_gossip::ALPN).await {
|
||||
Ok(_conn) => println!(" ✓ Connected to bootstrap peer: {}", addr.id),
|
||||
Err(e) => {
|
||||
| Ok(_conn) => println!(" ✓ Connected to bootstrap peer: {}", addr.id),
|
||||
| Err(e) => {
|
||||
println!(" ✗ Failed to connect to bootstrap peer {}: {}", addr.id, e)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,11 +238,11 @@ mod test_utils {
|
||||
if has_bootstrap_peers {
|
||||
println!(" Waiting for join to complete (with timeout)...");
|
||||
match tokio::time::timeout(Duration::from_secs(3), receiver.joined()).await {
|
||||
Ok(Ok(())) => println!(" Join completed!"),
|
||||
Ok(Err(e)) => println!(" Join error: {}", e),
|
||||
Err(_) => {
|
||||
| Ok(Ok(())) => println!(" Join completed!"),
|
||||
| Ok(Err(e)) => println!(" Join error: {}", e),
|
||||
| Err(_) => {
|
||||
println!(" Join timeout - proceeding anyway (mDNS may still connect later)")
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
println!(" No bootstrap peers - skipping join wait (first node in swarm)");
|
||||
@@ -270,7 +288,8 @@ mod test_utils {
|
||||
Ok((ep1, ep2, router1, router2, bridge1, bridge2))
|
||||
}
|
||||
|
||||
/// Spawn background tasks to forward messages between iroh-gossip and GossipBridge
|
||||
/// Spawn background tasks to forward messages between iroh-gossip and
|
||||
/// GossipBridge
|
||||
fn spawn_gossip_bridge_tasks(
|
||||
sender: GossipSender,
|
||||
mut receiver: GossipReceiver,
|
||||
@@ -290,7 +309,7 @@ mod test_utils {
|
||||
node_id, msg_count
|
||||
);
|
||||
match bincode::serialize(&versioned_msg) {
|
||||
Ok(bytes) => {
|
||||
| Ok(bytes) => {
|
||||
if let Err(e) = sender.broadcast(Bytes::from(bytes)).await {
|
||||
eprintln!("[Node {}] Failed to broadcast message: {}", node_id, e);
|
||||
} else {
|
||||
@@ -299,8 +318,8 @@ mod test_utils {
|
||||
node_id, msg_count
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
},
|
||||
| Err(e) => eprintln!(
|
||||
"[Node {}] Failed to serialize message for broadcast: {}",
|
||||
node_id, e
|
||||
),
|
||||
@@ -318,7 +337,7 @@ mod test_utils {
|
||||
println!("[Node {}] Gossip receiver task started", node_id);
|
||||
loop {
|
||||
match tokio::time::timeout(Duration::from_millis(100), receiver.next()).await {
|
||||
Ok(Some(Ok(event))) => {
|
||||
| Ok(Some(Ok(event))) => {
|
||||
println!(
|
||||
"[Node {}] Received gossip event: {:?}",
|
||||
node_id,
|
||||
@@ -331,7 +350,7 @@ mod test_utils {
|
||||
node_id, msg_count
|
||||
);
|
||||
match bincode::deserialize::<VersionedMessage>(&msg.content) {
|
||||
Ok(versioned_msg) => {
|
||||
| Ok(versioned_msg) => {
|
||||
if let Err(e) = bridge_in.push_incoming(versioned_msg) {
|
||||
eprintln!(
|
||||
"[Node {}] Failed to push to bridge incoming: {}",
|
||||
@@ -343,24 +362,24 @@ mod test_utils {
|
||||
node_id, msg_count
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
},
|
||||
| Err(e) => eprintln!(
|
||||
"[Node {}] Failed to deserialize gossip message: {}",
|
||||
node_id, e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(Err(e))) => {
|
||||
},
|
||||
| Ok(Some(Err(e))) => {
|
||||
eprintln!("[Node {}] Gossip receiver error: {}", node_id, e)
|
||||
}
|
||||
Ok(None) => {
|
||||
},
|
||||
| Ok(None) => {
|
||||
println!("[Node {}] Gossip stream ended", node_id);
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
},
|
||||
| Err(_) => {
|
||||
// Timeout, no message available
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user