bevy_fleet 0.1.0

bevy swarm diagnostic, event, metric, and telemetry client
Documentation
use bevy::diagnostic::{
    Diagnostic, DiagnosticPath, Diagnostics, EntityCountDiagnosticsPlugin,
    FrameTimeDiagnosticsPlugin, RegisterDiagnostic, SystemInformationDiagnosticsPlugin,
};
use bevy::prelude::*;
use bevy::time::TimerMode;
use bevy_fleet::{FleetConfig, FleetEvent, FleetPlugin, forward_serialized_events};
use serde::Serialize;

// Custom diagnostic paths for game metrics
const PLAYER_SCORE: DiagnosticPath = DiagnosticPath::const_new("game/player_score");
const PLAYER_LEVEL: DiagnosticPath = DiagnosticPath::const_new("game/player_level");

fn main() {
    let fleet_config = FleetConfig {
        app_id: "advanced-example".to_string(),
        aggregation_url: "http://127.0.0.1:4318".to_string(),
        enabled: true,
        publish_interval_secs: 10,
        ..Default::default()
    };

    let mut app = App::new();
    app.insert_resource(fleet_config.clone());
    app.add_plugins(DefaultPlugins)
        .add_plugins(EntityCountDiagnosticsPlugin::default())
        .add_plugins(FrameTimeDiagnosticsPlugin::default())
        .add_plugins(SystemInformationDiagnosticsPlugin::default())
        .add_plugins(FleetPlugin {
            config: fleet_config,
        })
        .register_diagnostic(Diagnostic::new(PLAYER_SCORE))
        .register_diagnostic(Diagnostic::new(PLAYER_LEVEL))
        .add_message::<ItemPickupEvent>()
        .add_systems(Startup, setup)
        .add_systems(
            Update,
            (
                track_player_events,
                emit_item_pickups,
                forward_serialized_events::<ItemPickupEvent>,
                update_game_diagnostics,
                panic_after_delay,
            ),
        );

    app.run();
}

#[derive(Component)]
struct Player {
    score: u32,
    level: u32,
}

#[derive(Clone, Event, Serialize, Message)]
struct ItemPickupEvent {
    item: String,
    quantity: u32,
    player_level: u32,
}

#[derive(Resource)]
struct EventTimers {
    status: Timer,
    pickup: Timer,
}

#[derive(Resource)]
struct PanicTimer(Timer);

fn setup(mut commands: Commands) {
    commands.spawn(Player { score: 0, level: 1 });
    commands.insert_resource(EventTimers {
        status: Timer::from_seconds(5.0, TimerMode::Repeating),
        pickup: Timer::from_seconds(7.0, TimerMode::Repeating),
    });
    commands.insert_resource(PanicTimer(Timer::from_seconds(30.0, TimerMode::Once)));
}

fn track_player_events(
    mut events: MessageWriter<FleetEvent>,
    query: Query<&Player>,
    time: Res<Time>,
    mut timers: ResMut<EventTimers>,
) {
    if timers.status.tick(time.delta()).just_finished() {
        for player in query.iter() {
            let event = FleetEvent::new("player_status")
                .with_data("score", player.score.to_string())
                .with_data("level", player.level.to_string());

            events.write(event);
        }
    }
}

fn emit_item_pickups(
    mut events: MessageWriter<ItemPickupEvent>,
    query: Query<&Player>,
    time: Res<Time>,
    mut timers: ResMut<EventTimers>,
) {
    if timers.pickup.tick(time.delta()).just_finished() {
        for player in query.iter() {
            events.write(ItemPickupEvent {
                item: "health_potion".to_string(),
                quantity: 1,
                player_level: player.level,
            });
        }
    }
}

fn update_game_diagnostics(mut diagnostics: Diagnostics, query: Query<&Player>) {
    for player in query.iter() {
        diagnostics.add_measurement(&PLAYER_SCORE, || player.score as f64);
        diagnostics.add_measurement(&PLAYER_LEVEL, || player.level as f64);
    }
}

fn panic_after_delay(mut timer: ResMut<PanicTimer>, time: Res<Time>) {
    if timer.0.tick(time.delta()).just_finished() {
        panic!("Intentional panic after 30s to verify Fleet export");
    }
}