use rustial_math::TileId;
use std::collections::{HashMap, VecDeque};
const DEFAULT_RECENT_EVENT_CAPACITY: usize = 2048;
const DEFAULT_RECENT_TERMINAL_RECORD_CAPACITY: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TileLifecycleEvent {
pub frame: u64,
pub tile: TileId,
pub kind: TileLifecycleEventKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TileLifecycleEventKind {
Selected,
Queued,
Dispatched,
Completed,
Decoded,
PromotedToCache,
UsedAsExact,
UsedAsFallback,
CancelledAsStale,
EvictedWhilePending,
EvictedAfterRenderableUse,
Failed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileLifecycleRecord {
pub tile: TileId,
pub first_selected_frame: Option<u64>,
pub first_queued_frame: Option<u64>,
pub first_dispatched_frame: Option<u64>,
pub first_completed_frame: Option<u64>,
pub first_decoded_frame: Option<u64>,
pub first_promoted_frame: Option<u64>,
pub first_renderable_frame: Option<u64>,
pub first_exact_frame: Option<u64>,
pub first_fallback_frame: Option<u64>,
pub queued_frames_to_dispatch: Option<u64>,
pub in_flight_frames_to_complete: Option<u64>,
pub completion_to_visible_use_frames: Option<u64>,
pub last_event_frame: u64,
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,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TileLifecycleDiagnostics {
pub active_records: Vec<TileLifecycleRecord>,
pub recent_terminal_records: Vec<TileLifecycleRecord>,
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 {
pub fn begin_frame(&mut self, frame: u64) {
self.current_frame = frame;
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
pub fn record_cancelled_as_stale(&mut self, tile: TileId) {
self.finalize(tile, TileLifecycleEventKind::CancelledAsStale);
}
pub fn record_evicted_while_pending(&mut self, tile: TileId) {
self.finalize(tile, TileLifecycleEventKind::EvictedWhilePending);
}
pub fn record_evicted_after_renderable_use(&mut self, tile: TileId) {
self.finalize(tile, TileLifecycleEventKind::EvictedAfterRenderableUse);
}
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();
}
}
}