rustial-engine 0.0.1

Framework-agnostic 2.5D map engine for rustial
Documentation
//! Tile lifecycle diagnostics for the tile pipeline.
//!
//! The types in this module make a tile observable across the core engine
//! stages: selection, queueing, dispatch, completion, promotion, visible use,
//! cancellation, and eviction.
//!
//! The tracker is intentionally lightweight:
//!
//! - active per-tile state is bounded by the number of tracked tiles
//! - recent events are stored in a ring buffer
//! - recent terminal records are also stored in a ring buffer
//! - timing metrics are recorded in frame counts for deterministic tests

use rustial_math::TileId;
use std::collections::{HashMap, VecDeque};

const DEFAULT_RECENT_EVENT_CAPACITY: usize = 2048;
const DEFAULT_RECENT_TERMINAL_RECORD_CAPACITY: usize = 512;

/// One lifecycle transition observed for a tile.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TileLifecycleEvent {
    /// Frame index at which the transition was observed.
    pub frame: u64,
    /// Tile affected by the transition.
    pub tile: TileId,
    /// Kind of transition recorded.
    pub kind: TileLifecycleEventKind,
}

/// Lifecycle transition kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TileLifecycleEventKind {
    /// Tile entered the desired set for the current frame.
    Selected,
    /// Tile was inserted into the pending queue.
    Queued,
    /// Tile request was dispatched to the source transport.
    Dispatched,
    /// Tile fetch completed and returned to the engine.
    Completed,
    /// Tile payload passed validation / decode stage.
    Decoded,
    /// Tile was promoted into the cache as renderable payload.
    PromotedToCache,
    /// Tile was used as the exact visible tile.
    UsedAsExact,
    /// Tile was used as fallback imagery for another target.
    UsedAsFallback,
    /// Tile request was cancelled because it became stale.
    CancelledAsStale,
    /// Tile was evicted while still pending.
    EvictedWhilePending,
    /// Tile was evicted after it had renderable payload.
    EvictedAfterRenderableUse,
    /// Tile failed to load or validate.
    Failed,
}

/// Per-tile lifecycle record with first-observed timings.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileLifecycleRecord {
    /// Tile tracked by this record.
    pub tile: TileId,
    /// First frame where the tile was selected.
    pub first_selected_frame: Option<u64>,
    /// First frame where the tile entered the pending queue.
    pub first_queued_frame: Option<u64>,
    /// First frame where the tile was dispatched.
    pub first_dispatched_frame: Option<u64>,
    /// First frame where the tile completed.
    pub first_completed_frame: Option<u64>,
    /// First frame where the tile payload passed validation / decode.
    pub first_decoded_frame: Option<u64>,
    /// First frame where the tile was promoted to the cache.
    pub first_promoted_frame: Option<u64>,
    /// First frame where the tile became renderable in the visible set.
    pub first_renderable_frame: Option<u64>,
    /// First frame where the tile was used as the exact visible tile.
    pub first_exact_frame: Option<u64>,
    /// First frame where the tile was used as fallback imagery.
    pub first_fallback_frame: Option<u64>,
    /// Frame delta between first queue and first dispatch.
    pub queued_frames_to_dispatch: Option<u64>,
    /// Frame delta between first dispatch and first completion.
    pub in_flight_frames_to_complete: Option<u64>,
    /// Frame delta between first completion and first visible renderable use.
    pub completion_to_visible_use_frames: Option<u64>,
    /// Most recent frame where any lifecycle event was recorded.
    pub last_event_frame: u64,
    /// Terminal transition, if the record has completed.
    pub terminal_event: Option<TileLifecycleEventKind>,
}

impl TileLifecycleRecord {
    fn new(tile: TileId) -> Self {
        Self {
            tile,
            first_selected_frame: None,
            first_queued_frame: None,
            first_dispatched_frame: None,
            first_completed_frame: None,
            first_decoded_frame: None,
            first_promoted_frame: None,
            first_renderable_frame: None,
            first_exact_frame: None,
            first_fallback_frame: None,
            queued_frames_to_dispatch: None,
            in_flight_frames_to_complete: None,
            completion_to_visible_use_frames: None,
            last_event_frame: 0,
            terminal_event: None,
        }
    }
}

/// Snapshot of current lifecycle diagnostics.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TileLifecycleDiagnostics {
    /// Active tile records still being tracked.
    pub active_records: Vec<TileLifecycleRecord>,
    /// Recently completed terminal records.
    pub recent_terminal_records: Vec<TileLifecycleRecord>,
    /// Recent lifecycle events in chronological order.
    pub recent_events: Vec<TileLifecycleEvent>,
}

#[derive(Debug)]
pub(crate) struct TileLifecycleTracker {
    current_frame: u64,
    active_records: HashMap<TileId, TileLifecycleRecord>,
    recent_terminal_records: VecDeque<TileLifecycleRecord>,
    recent_events: VecDeque<TileLifecycleEvent>,
    recent_event_capacity: usize,
    recent_terminal_capacity: usize,
}

impl Default for TileLifecycleTracker {
    fn default() -> Self {
        Self {
            current_frame: 0,
            active_records: HashMap::new(),
            recent_terminal_records: VecDeque::with_capacity(
                DEFAULT_RECENT_TERMINAL_RECORD_CAPACITY,
            ),
            recent_events: VecDeque::with_capacity(DEFAULT_RECENT_EVENT_CAPACITY),
            recent_event_capacity: DEFAULT_RECENT_EVENT_CAPACITY,
            recent_terminal_capacity: DEFAULT_RECENT_TERMINAL_RECORD_CAPACITY,
        }
    }
}

impl TileLifecycleTracker {
    /// Start a new engine frame for lifecycle timing.
    pub fn begin_frame(&mut self, frame: u64) {
        self.current_frame = frame;
    }

    /// Record that a tile entered the desired set.
    pub fn record_selected(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_selected_frame.is_none() {
                record.first_selected_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::Selected);
        }
    }

    /// Record that a tile entered the pending queue.
    pub fn record_queued(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_queued_frame.is_none() {
                record.first_queued_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::Queued);
        }
    }

    /// Record that a tile request was dispatched.
    pub fn record_dispatched(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_dispatched_frame.is_none() {
                record.first_dispatched_frame = Some(frame);
                if let Some(queued_frame) = record.first_queued_frame {
                    record.queued_frames_to_dispatch = Some(frame.saturating_sub(queued_frame));
                }
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::Dispatched);
        }
    }

    /// Record that a tile fetch completed.
    pub fn record_completed(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_completed_frame.is_none() {
                record.first_completed_frame = Some(frame);
                if let Some(dispatched_frame) = record.first_dispatched_frame {
                    record.in_flight_frames_to_complete =
                        Some(frame.saturating_sub(dispatched_frame));
                }
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::Completed);
        }
    }

    /// Record that a tile payload passed validation / decode.
    pub fn record_decoded(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_decoded_frame.is_none() {
                record.first_decoded_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::Decoded);
        }
    }

    /// Record that a tile was promoted into the cache.
    pub fn record_promoted_to_cache(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_promoted_frame.is_none() {
                record.first_promoted_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::PromotedToCache);
        }
    }

    /// Record that a tile was used exactly in the visible set.
    pub fn record_used_as_exact(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_renderable_frame.is_none() {
                record.first_renderable_frame = Some(frame);
                if let Some(completed_frame) = record.first_completed_frame {
                    record.completion_to_visible_use_frames =
                        Some(frame.saturating_sub(completed_frame));
                }
            }
            if record.first_exact_frame.is_none() {
                record.first_exact_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::UsedAsExact);
        }
    }

    /// Record that a tile was used as fallback imagery.
    pub fn record_used_as_fallback(&mut self, tile: TileId) {
        let frame = self.current_frame;
        let mut emit = false;
        {
            let record = self.ensure_record(tile);
            if record.first_renderable_frame.is_none() {
                record.first_renderable_frame = Some(frame);
                if let Some(completed_frame) = record.first_completed_frame {
                    record.completion_to_visible_use_frames =
                        Some(frame.saturating_sub(completed_frame));
                }
            }
            if record.first_fallback_frame.is_none() {
                record.first_fallback_frame = Some(frame);
                emit = true;
            }
            record.last_event_frame = frame;
        }
        if emit {
            self.push_event(tile, TileLifecycleEventKind::UsedAsFallback);
        }
    }

    /// Record a non-terminal failure for a tile.
    pub fn record_failed(&mut self, tile: TileId) {
        let frame = self.current_frame;
        {
            let record = self.ensure_record(tile);
            record.last_event_frame = frame;
        }
        self.push_event(tile, TileLifecycleEventKind::Failed);
    }

    /// Record that a tile was cancelled as stale and finalize the record.
    pub fn record_cancelled_as_stale(&mut self, tile: TileId) {
        self.finalize(tile, TileLifecycleEventKind::CancelledAsStale);
    }

    /// Record that a tile was evicted while pending and finalize the record.
    pub fn record_evicted_while_pending(&mut self, tile: TileId) {
        self.finalize(tile, TileLifecycleEventKind::EvictedWhilePending);
    }

    /// Record that a tile was evicted after becoming renderable and finalize the record.
    pub fn record_evicted_after_renderable_use(&mut self, tile: TileId) {
        self.finalize(tile, TileLifecycleEventKind::EvictedAfterRenderableUse);
    }

    /// Return a snapshot of the current lifecycle diagnostics.
    pub fn diagnostics(&self) -> TileLifecycleDiagnostics {
        let mut active_records: Vec<_> = self.active_records.values().cloned().collect();
        active_records.sort_by_key(|record| (record.tile.zoom, record.tile.y, record.tile.x));

        TileLifecycleDiagnostics {
            active_records,
            recent_terminal_records: self.recent_terminal_records.iter().cloned().collect(),
            recent_events: self.recent_events.iter().copied().collect(),
        }
    }

    fn ensure_record(&mut self, tile: TileId) -> &mut TileLifecycleRecord {
        self.active_records
            .entry(tile)
            .or_insert_with(|| TileLifecycleRecord::new(tile))
    }

    fn finalize(&mut self, tile: TileId, kind: TileLifecycleEventKind) {
        let frame = self.current_frame;
        let mut record = self
            .active_records
            .remove(&tile)
            .unwrap_or_else(|| TileLifecycleRecord::new(tile));
        record.last_event_frame = frame;
        record.terminal_event = Some(kind);
        self.push_event(tile, kind);
        self.recent_terminal_records.push_back(record);
        while self.recent_terminal_records.len() > self.recent_terminal_capacity {
            let _ = self.recent_terminal_records.pop_front();
        }
    }

    fn push_event(&mut self, tile: TileId, kind: TileLifecycleEventKind) {
        self.recent_events.push_back(TileLifecycleEvent {
            frame: self.current_frame,
            tile,
            kind,
        });
        while self.recent_events.len() > self.recent_event_capacity {
            let _ = self.recent_events.pop_front();
        }
    }
}