diff --git a/crates/app/src/engine_bridge.rs b/crates/app/src/engine_bridge.rs index b0f3d04..84f0fd6 100644 --- a/crates/app/src/engine_bridge.rs +++ b/crates/app/src/engine_bridge.rs @@ -7,7 +7,7 @@ use bevy::prelude::*; use libmarathon::{ engine::{EngineBridge, EngineCommand, EngineEvent}, - networking::{CurrentSession, NetworkedEntity, NodeVectorClock, Session, SessionState, VectorClock}, + networking::{CurrentSession, NetworkedEntity, NodeVectorClock, Session, SessionState}, }; pub struct EngineBridgePlugin; @@ -42,9 +42,8 @@ fn detect_changes_and_tick( /// 1. Polls all available events from the EngineBridge /// 2. Dispatches them to update Bevy resources and state fn poll_engine_events( - mut commands: Commands, bridge: Res, - mut current_session: Option>, + mut current_session: ResMut, mut node_clock: ResMut, ) { let events = (*bridge).poll_events(); @@ -53,70 +52,77 @@ fn poll_engine_events( for event in events { match event { EngineEvent::NetworkingStarted { session_id, node_id } => { - info!("🌐 Networking started: session={}, node={}", + info!("Networking started: session={}, node={}", session_id.to_code(), node_id); - // Create session if it doesn't exist - if current_session.is_none() { - let mut session = Session::new(session_id.clone()); - session.state = SessionState::Active; - commands.insert_resource(CurrentSession::new(session, VectorClock::new())); - info!("Created new session resource: {}", session_id.to_code()); - } else if let Some(ref mut session) = current_session { - // Update existing session state to Active - session.session.state = SessionState::Active; - } + // Update session to use the new session ID and set state to Active + current_session.session = Session::new(session_id.clone()); + current_session.session.state = SessionState::Active; + info!("Updated CurrentSession to Active: {}", session_id.to_code()); // Update node ID in clock node_clock.node_id = node_id; } EngineEvent::NetworkingFailed { error } => { - error!("❌ Networking failed: {}", error); + error!("Networking failed: {}", error); - // Keep session state as Created (if session exists) - if let Some(ref mut session) = current_session { - session.session.state = SessionState::Created; - } + // Keep session state as Created + current_session.session.state = SessionState::Created; } EngineEvent::NetworkingStopped => { - info!("🔌 Networking stopped"); + info!("Networking stopped"); - // Update session state to Disconnected (if session exists) - if let Some(ref mut session) = current_session { - session.session.state = SessionState::Disconnected; - } + // Update session state to Disconnected + current_session.session.state = SessionState::Disconnected; } EngineEvent::PeerJoined { node_id } => { - info!("👋 Peer joined: {}", node_id); + info!("Peer joined: {}", node_id); // TODO(Phase 3.3): Trigger sync } EngineEvent::PeerLeft { node_id } => { - info!("👋 Peer left: {}", node_id); + info!("Peer left: {}", node_id); } EngineEvent::LockAcquired { entity_id, holder } => { - debug!("🔒 Lock acquired: entity={}, holder={}", entity_id, holder); + debug!("Lock acquired: entity={}, holder={}", entity_id, holder); // TODO(Phase 3.4): Update lock visuals } EngineEvent::LockReleased { entity_id } => { - debug!("🔓 Lock released: entity={}", entity_id); + debug!("Lock released: entity={}", entity_id); // TODO(Phase 3.4): Update lock visuals } + EngineEvent::ClockTicked { sequence, clock: _ } => { + debug!("Clock ticked: sequence={}", sequence); + // Clock tick confirmed - no action needed + } + EngineEvent::SessionJoined { session_id } => { + info!("Session joined: {}", session_id.to_code()); + // Update session state + current_session.session.state = SessionState::Joining; + } + EngineEvent::SessionLeft => { + info!("Session left"); + // Update session state + current_session.session.state = SessionState::Left; + } + EngineEvent::EntitySpawned { entity_id, position, rotation, version: _ } => { + debug!("Entity spawned: id={}, pos={:?}, rot={:?}", entity_id, position, rotation); + // TODO: Spawn entity in Bevy + } + EngineEvent::EntityUpdated { entity_id, position, rotation, version: _ } => { + debug!("Entity updated: id={}, pos={:?}, rot={:?}", entity_id, position, rotation); + // TODO: Update entity in Bevy + } + EngineEvent::EntityDeleted { entity_id, version: _ } => { + debug!("Entity deleted: id={}", entity_id); + // TODO: Delete entity in Bevy + } EngineEvent::LockDenied { entity_id, current_holder } => { - debug!("⛔ Lock denied: entity={}, holder={}", entity_id, current_holder); - // TODO(Phase 3.4): Show visual feedback + debug!("Lock denied: entity={}, current_holder={}", entity_id, current_holder); + // TODO: Show lock denied feedback } EngineEvent::LockExpired { entity_id } => { - debug!("⏰ Lock expired: entity={}", entity_id); - // TODO(Phase 3.4): Update lock visuals - } - EngineEvent::ClockTicked { sequence, clock } => { - debug!("🕐 Clock ticked to {}", sequence); - - // Update the NodeVectorClock resource with the new clock state - node_clock.clock = clock; - } - _ => { - debug!("Unhandled engine event: {:?}", event); + debug!("Lock expired: entity={}", entity_id); + // TODO: Update lock visuals } } } diff --git a/crates/app/src/session.rs b/crates/app/src/session.rs index dbc28f1..7f9282f 100644 --- a/crates/app/src/session.rs +++ b/crates/app/src/session.rs @@ -6,17 +6,17 @@ use bevy::prelude::*; use libmarathon::{ networking::{ - EntityLockRegistry, NetworkEntityMap, NodeVectorClock, VectorClock, + CurrentSession, EntityLockRegistry, NetworkEntityMap, NodeVectorClock, Session, VectorClock, }, }; use uuid::Uuid; /// Initialize offline resources on app startup /// -/// This sets up the vector clock and networking-related resources, but does NOT -/// create a session. Sessions only exist when networking is active. +/// This sets up the vector clock and networking-related resources. +/// Creates an offline CurrentSession that will be updated when networking starts. pub fn initialize_offline_resources(world: &mut World) { - info!("Initializing offline resources (no session yet)..."); + info!("Initializing offline resources..."); // Create node ID (persists for this app instance) let node_id = Uuid::new_v4(); @@ -32,5 +32,11 @@ pub fn initialize_offline_resources(world: &mut World) { world.insert_resource(NetworkEntityMap::default()); world.insert_resource(EntityLockRegistry::default()); - info!("Offline resources initialized (vector clock ready)"); + // Create offline session (will be updated when networking starts) + // This ensures CurrentSession resource always exists for UI binding + let offline_session_id = libmarathon::networking::SessionId::new(); + let offline_session = Session::new(offline_session_id); + world.insert_resource(CurrentSession::new(offline_session, VectorClock::new())); + + info!("Offline resources initialized (vector clock ready, session created in offline state)"); } diff --git a/crates/app/src/session_ui.rs b/crates/app/src/session_ui.rs index 38edb77..d4f2ecb 100644 --- a/crates/app/src/session_ui.rs +++ b/crates/app/src/session_ui.rs @@ -7,7 +7,7 @@ use bevy::prelude::*; use libmarathon::{ debug_ui::{egui, EguiContexts, EguiPrimaryContextPass}, engine::{EngineBridge, EngineCommand}, - networking::{CurrentSession, NodeVectorClock, SessionId}, + networking::{CurrentSession, NodeVectorClock, SessionId, SessionState}, }; pub struct SessionUiPlugin; @@ -28,10 +28,15 @@ struct SessionUiState { fn session_ui_panel( mut contexts: EguiContexts, mut ui_state: ResMut, - current_session: Option>, + current_session: Res, node_clock: Option>, bridge: Res, ) { + // Log session state for debugging + debug!("Session UI: state={:?}, id={}", + current_session.session.state, + current_session.session.id.to_code()); + let Ok(ctx) = contexts.ctx_mut() else { return; }; @@ -40,21 +45,22 @@ fn session_ui_panel( .default_pos([320.0, 10.0]) .default_width(280.0) .show(ctx, |ui| { - if let Some(session) = current_session.as_ref() { - // ONLINE MODE: Session exists, networking is active + // Check if networking is active based on session state + if current_session.session.state == SessionState::Active { + // ONLINE MODE: Networking is active ui.heading("Session (Online)"); ui.separator(); ui.horizontal(|ui| { ui.label("Code:"); - ui.code(session.session.id.to_code()); + ui.code(current_session.session.id.to_code()); if ui.small_button("📋").clicked() { // TODO: Copy to clipboard (requires clipboard API) - info!("Session code: {}", session.session.id.to_code()); + info!("Session code: {}", current_session.session.id.to_code()); } }); - ui.label(format!("State: {:?}", session.session.state)); + ui.label(format!("State: {:?}", current_session.session.state)); if let Some(clock) = node_clock.as_ref() { ui.label(format!("Connected nodes: {}", clock.clock.clocks.len())); @@ -68,7 +74,7 @@ fn session_ui_panel( bridge.send_command(EngineCommand::StopNetworking); } } else { - // OFFLINE MODE: No session, networking not started + // OFFLINE MODE: Networking not started or disconnected ui.heading("Offline Mode"); ui.separator();