bevy_fairy 0.3.2

An agentic debugger for Bevy games: a Bevy plugin that exposes the game's state over BRP, and an MCP server that lets a coding agent read and drive it
Documentation
use std::sync::Arc;
use tokio::sync::RwLock;
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
use serde_json::json;

use bevy_fairy::{
    server::config::Config,
    server::brp_client::BrpClient,
    server::lazy_init::{LazyComponents, preload_critical_components},
    server::semantic_analyzer::{SemanticAnalyzer, SemanticThresholds},
    server::memory_optimization_tracker::{MemoryOptimizationTracker, OptimizationTarget},
    server::memory_pools::{GameDebugPools, GAME_POOLS},
    server::mcp_server::McpServer,
};

/// Benchmark memory usage of lazy initialization patterns
async fn bench_lazy_initialization() {
    let mut config = Config::default();
    config.bevy_brp_host = "localhost".to_string();
    config.bevy_brp_port = 15702;
    config.mcp_port = 3001;
    
    let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
    let components = LazyComponents::new(brp_client);
    
    // Benchmark lazy vs eager initialization
    let start = std::time::Instant::now();
    
    // Initialize all components lazily
    let _entity_inspector = components.get_entity_inspector().await;
    let _system_profiler = components.get_system_profiler().await;
    let _debug_router = components.get_debug_command_router().await;
    let _pattern_learning = components.get_pattern_learning_system().await;
    let _suggestion_engine = components.get_suggestion_engine().await;
    let _workflow_automation = components.get_workflow_automation().await;
    let _hot_reload = components.get_hot_reload_system().await;
    
    let initialization_time = start.elapsed();
    println!("Lazy initialization time: {:?}", initialization_time);
    
    // Test preload critical components
    let start = std::time::Instant::now();
    preload_critical_components(&components).await.unwrap();
    let preload_time = start.elapsed();
    println!("Preload time: {:?}", preload_time);
}

/// Benchmark semantic analyzer string optimization
fn bench_semantic_analyzer_clones(c: &mut Criterion) {
    let custom_thresholds = SemanticThresholds {
        stuck_velocity_threshold: 0.05,
        fast_velocity_threshold: 100.0,
        ..Default::default()
    };
    
    let analyzer = SemanticAnalyzer::with_thresholds(custom_thresholds).unwrap();
    
    let mut group = c.benchmark_group("semantic_analyzer");
    
    for query in ["find stuck entities", "show fast moving objects", "detect physics violations"].iter() {
        group.bench_with_input(BenchmarkId::new("analyze_query", query), query, |b, query| {
            b.iter(|| {
                let _result = analyzer.analyze(query).unwrap();
            })
        });
    }
    
    group.finish();
}

/// Benchmark object pooling effectiveness
async fn bench_object_pooling() {
    let pools = &*GAME_POOLS;
    
    // Warm up pools
    pools.warm_up_pools().await;
    
    let start = std::time::Instant::now();
    
    // Simulate high-frequency allocations without pooling
    let mut non_pooled_vecs = Vec::new();
    for _ in 0..1000 {
        let mut vec = Vec::<String>::with_capacity(16);
        vec.push("Transform".to_string());
        vec.push("Velocity".to_string());
        vec.push("Collider".to_string());
        non_pooled_vecs.push(vec);
    }
    
    let non_pooled_time = start.elapsed();
    drop(non_pooled_vecs);
    
    // Simulate high-frequency allocations with pooling
    let start = std::time::Instant::now();
    
    for _ in 0..1000 {
        let mut vec = pools.get_string_vec().await;
        vec.push("Transform".to_string());
        vec.push("Velocity".to_string());
        vec.push("Collider".to_string());
        pools.return_string_vec(vec).await;
    }
    
    let pooled_time = start.elapsed();
    
    println!("Non-pooled allocation time: {:?}", non_pooled_time);
    println!("Pooled allocation time: {:?}", pooled_time);
    println!("Pool efficiency: {:.2}x faster", 
             non_pooled_time.as_nanos() as f64 / pooled_time.as_nanos() as f64);
}

/// Test memory optimization tracking accuracy
async fn test_memory_tracking() {
    let tracker = MemoryOptimizationTracker::new();
    
    // Set up optimization targets
    let lazy_init_target = OptimizationTarget::new("lazy_init".to_string(), 56, 40.0);
    let mcp_server_target = OptimizationTarget::new("mcp_server".to_string(), 29, 40.0);
    let semantic_analyzer_target = OptimizationTarget::new("semantic_analyzer".to_string(), 21, 40.0);
    
    tracker.add_optimization_target(lazy_init_target);
    tracker.add_optimization_target(mcp_server_target);
    tracker.add_optimization_target(semantic_analyzer_target);
    
    // Simulate our actual optimization results
    tracker.update_target("lazy_init", 0);         // 100% reduction
    tracker.update_target("mcp_server", 8);        // 72% reduction
    tracker.update_target("semantic_analyzer", 8); // 62% reduction
    
    let summary = tracker.get_optimization_summary();
    summary.log_summary();
    
    // Verify targets are met
    for target in &summary.targets {
        assert!(target.is_target_met(), "Target {} not met: {:.1}% reduction", 
                target.name, target.reduction_achieved());
    }
    
    let total_reduction = summary.targets.iter().map(|t| t.reduction_achieved()).sum::<f64>() / summary.targets.len() as f64;
    assert!(total_reduction >= 40.0, "Overall reduction target not met: {:.1}%", total_reduction);
    
    println!("✅ Memory optimization targets achieved! Average reduction: {:.1}%", total_reduction);
}

/// Benchmark Arc vs Box vs Rc for different usage patterns
fn bench_smart_pointer_overhead(c: &mut Criterion) {
    let mut group = c.benchmark_group("smart_pointers");
    
    // Test Arc cloning overhead
    group.bench_function("arc_clone", |b| {
        let data = Arc::new(vec![1, 2, 3, 4, 5]);
        b.iter(|| {
            let _clone = Arc::clone(&data);
        })
    });
    
    // Test Box allocation overhead
    group.bench_function("box_alloc", |b| {
        b.iter(|| {
            let _box = Box::new(vec![1, 2, 3, 4, 5]);
        })
    });
    
    group.finish();
}

/// Memory regression test to ensure optimizations don't regress
#[tokio::test]
async fn test_memory_regression() {
    // Test lazy initialization memory efficiency
    bench_lazy_initialization().await;
    
    // Test object pooling efficiency
    bench_object_pooling().await;
    
    // Test memory tracking accuracy
    test_memory_tracking().await;
    
    // Get pool statistics
    let pools = &*GAME_POOLS;
    let stats = pools.get_pool_stats().await;
    stats.log_stats();
    
    // Ensure pools are functioning
    // An unsigned count is always >= 0; reading it is the check that the stats are available.
    let _ = stats.total_pooled_objects();
}

/// Integration test for complete memory optimization pipeline
#[tokio::test]
async fn test_memory_optimization_integration() {
    let mut config = Config::default();
    config.bevy_brp_host = "localhost".to_string();
    config.bevy_brp_port = 15702;
    config.mcp_port = 3001;
    
    let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
    
    // Test optimized MCP server initialization
    let server = McpServer::new(config, brp_client);
    
    // Clone server (tests optimized Clone implementation)
    let _server_clone = server.clone();
    
    // Test semantic analyzer with optimization
    let analyzer = SemanticAnalyzer::new().unwrap();
    let result = analyzer.analyze("find stuck entities").unwrap();
    
    assert!(!result.explanations.is_empty());
    println!("Semantic analysis completed with {} explanations", result.explanations.len());
    
    // Test pool usage
    let pools = &*GAME_POOLS;
    let filters = pools.get_component_filters().await;
    pools.return_component_filters(filters).await;
    
    println!("✅ Memory optimization integration test passed");
}

/// Performance benchmark comparing before/after optimization
fn bench_memory_performance(c: &mut Criterion) {
    let mut group = c.benchmark_group("memory_performance");
    
    // Benchmark semantic analyzer performance after optimization
    let thresholds = SemanticThresholds::default();
    let analyzer = SemanticAnalyzer::with_thresholds(thresholds).unwrap();
    
    group.bench_function("semantic_analysis_optimized", |b| {
        b.iter(|| {
            let _result = analyzer.analyze("find stuck entities").unwrap();
        })
    });
    
    group.finish();
}

criterion_group!(
    memory_benches,
    bench_semantic_analyzer_clones,
    bench_smart_pointer_overhead,
    bench_memory_performance
);

criterion_main!(memory_benches);