bevy_fleet 0.1.0

bevy swarm diagnostic, event, metric, and telemetry client
Documentation
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

/// A tracked event in the application.
#[derive(Clone, Debug, Serialize, Deserialize, Event, Message)]
pub struct FleetEvent {
    pub event_type: String,
    pub data: HashMap<String, String>,
    pub timestamp: u64,
}

impl FleetEvent {
    pub fn new(event_type: impl Into<String>) -> Self {
        Self {
            event_type: event_type.into(),
            data: HashMap::new(),
            timestamp: current_unix_time_secs(),
        }
    }

    pub fn with_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.data.insert(key.into(), value.into());
        self
    }
}

/// Buffer that retains Fleet events across frames until the publisher collects them.
#[derive(Default, Resource)]
pub struct FleetEventBuffer {
    events: Vec<FleetEvent>,
}

impl FleetEventBuffer {
    pub fn extend<I>(&mut self, iter: I)
    where
        I: IntoIterator<Item = FleetEvent>,
    {
        self.events.extend(iter);
    }

    pub fn take(&mut self) -> Vec<FleetEvent> {
        std::mem::take(&mut self.events)
    }
}

/// Serializes custom Bevy events into `FleetEvent`s so they can be consumed by Fleet.
pub fn forward_serialized_events<T>(
    mut reader: MessageReader<T>,
    mut fleet_writer: MessageWriter<FleetEvent>,
) where
    T: Message + Serialize + Clone + Send + Sync + 'static,
{
    for event in reader.read() {
        let event_type = std::any::type_name::<T>().to_string();

        let (serialized_value, serialization_error) = match serde_json::to_value(event) {
            Ok(value) => (value, None),
            Err(err) => (Value::Null, Some(err.to_string())),
        };

        let mut fleet_event = FleetEvent::new(event_type.clone());

        match &serialized_value {
            Value::Object(map) => {
                for (key, value) in map {
                    fleet_event.data.insert(key.clone(), value_to_string(value));
                }
            }
            Value::Null => {}
            other => {
                fleet_event
                    .data
                    .insert("value".to_string(), value_to_string(other));
            }
        }

        if let Some(err) = &serialization_error {
            fleet_event
                .data
                .insert("serialization_error".to_string(), err.clone());
        }

        fleet_writer.write(fleet_event);
    }
}

/// Collects Fleet events written this frame and stores them in the persistent buffer.
pub fn collect_fleet_events(
    mut reader: MessageReader<FleetEvent>,
    mut buffer: ResMut<FleetEventBuffer>,
) {
    let new_events: Vec<FleetEvent> = reader.read().cloned().collect();
    if !new_events.is_empty() {
        buffer.extend(new_events);
    }
}

fn current_unix_time_secs() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

fn value_to_string(value: &Value) -> String {
    match value {
        Value::String(s) => s.clone(),
        Value::Null => "null".to_string(),
        other => other.to_string(),
    }
}