bevy_brp_mcp 0.22.2

MCP server for Bevy Remote Protocol (BRP) integration
//! Watch manager for coordinating file-based watch logging

use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;

use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tracing::info;

use crate::brp_tools::Port;
use crate::error::Error;
use crate::error::Result;

/// Global watch manager instance
pub(super) static WATCH_MANAGER: LazyLock<Arc<Mutex<WatchManager>>> =
    std::sync::LazyLock::new(|| Arc::new(Mutex::new(WatchManager::new())));

/// Information about an active watch
#[derive(Debug, Clone)]
pub(super) struct WatchInfo {
    pub(super) id:        u32,
    pub(super) entity_id: u64,
    pub(super) kind:      String,
    pub(super) log_path:  PathBuf,
    pub(super) port:      Port,
}

/// Manager for watch subscriptions
pub(super) struct WatchManager {
    /// Monotonic counter for watch IDs
    next_watch_id:             AtomicU32,
    /// Active watches mapped by watch ID
    pub(super) active_watches: HashMap<u32, (WatchInfo, JoinHandle<()>)>,
}

impl WatchManager {
    /// Create a new watch manager
    fn new() -> Self {
        Self {
            next_watch_id:  AtomicU32::new(1),
            active_watches: HashMap::new(),
        }
    }

    /// Get the next watch ID (monotonically increasing)
    pub(super) fn next_id(&self) -> u32 { self.next_watch_id.fetch_add(1, Ordering::SeqCst) }

    /// Stop a watch by ID
    pub(super) fn stop_watch(&mut self, watch_id: u32) -> Result<()> {
        if let Some((info, handle)) = self.active_watches.remove(&watch_id) {
            info!("Stopping watch {watch_id} for entity {}", info.entity_id);
            handle.abort();
            Ok(())
        } else {
            Err(error_stack::Report::new(Error::WatchOperation(format!(
                "Failed to stop watch {watch_id}: watch not found"
            ))))
        }
    }

    /// List all active watches
    pub(super) fn list_active_watches(&self) -> Vec<WatchInfo> {
        self.active_watches
            .values()
            .map(|(info, _)| info.clone())
            .collect()
    }
}