concinnity-dev 0.19.119

The Concinnity dev tooling library: world authoring, the in-engine editor, the debug server, docs and packaging
//! The shared world-snapshot data model the debug server exposes. `wire::server`
//! rebuilds it once per frame from the live `World`; the verbs in `super::verbs`
//! read it to answer client queries. Kept as plain data (no sockets, no engine
//! driving) so the verbs stay unit-testable against a hand-built snapshot.

use concinnity_core::profile;
use concinnity_engine::gfx::streaming::system::StreamingStats;
use concinnity_engine::shutdown::ShutdownToken;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use super::hot_reload::ReloadSignals;
use super::queue::RuntimeQueue;

// The world snapshot rebuilt by `tick`. The asset/system lists are not cheap
// to rebuild, so they refresh on an interval while `frame` advances every tick.
#[derive(Default)]
pub(crate) struct DebugState {
    pub(super) frame: u64,
    pub(super) system_count: usize,
    pub(super) component_count: usize,
    pub(super) systems: Vec<String>,
    pub(super) assets: Vec<AssetEntry>,
    // `AssetId` -> declared name, indexed by id. Captured once after build.
    //
    // Shared rather than owned: the interner is thread-local to the engine
    // thread, so a command handler on the socket thread can only resolve a name
    // through this snapshot, and it has to take it out from under the lock
    // before blocking on the engine. An `Arc` makes that handoff a refcount
    // bump instead of copying every name in the world per command.
    pub(super) names: Arc<Vec<String>>,
    // Asset-streaming `(resident, pending, unloaded)` counts, refreshed every
    // tick (cheap) so the `streaming` command reflects live progress.
    pub(super) streaming: StreamingStats,
    // Frame scratch reserve, peak and lifetime overflow count, refreshed every
    // tick so `memory` can answer whether the reserve is sized right.
    pub(super) scratch: concinnity_core::ecs::ScratchStats,
    // Per-system CPU step times (micros) from the last completed frame,
    // refreshed every tick for the `profile` command.
    pub(super) profile_systems: Vec<(String, u32)>,
    // Per-system heap-allocation counts from the last completed frame, same
    // order as `profile_systems`. Empty in builds the frame loop does not
    // sample (release, or no tracking allocator).
    pub(super) profile_allocs: Vec<(String, u32)>,
    // Whole-frame heap-allocation count from the most recent frame, `None`
    // under the same conditions `profile_allocs` is empty.
    pub(super) profile_frame_allocs: Option<u32>,
    // Render-backend stats from the most recent frame, for `profile`.
    pub(super) profile_render: profile::RenderStats,
    // Runtime shutdown token, set once via `FrameHook::attach_shutdown`. The
    // `shutdown` command cancels it to exit the engine cleanly.
    pub(super) shutdown_token: Option<ShutdownToken>,
    // Shared shader-reload flag captured from the active graphics backend.
    // `Some` once `tick` has seen a `GraphicsSystem` whose backend opted into
    // hot-reload (Metal under `cn debug`); `None` otherwise. The
    // `reload-shaders` command flips this to `true` and the backend polls it
    // at the top of `draw_frame`.
    pub(super) shader_reload: Option<Arc<AtomicBool>>,
    // The hot-reload driver's signals, captured once `tick` has armed it.
    // The `reload-assets` command raises them; the driver takes them each frame.
    pub(super) reload: Option<Arc<ReloadSignals>>,
    // Active-camera pose, refreshed every tick for the `camera-get` query.
    // `None` until the first tick that finds a `Camera3D` (a world with no
    // camera never sets it).
    pub(super) camera: Option<CameraSnapshot>,
    // Process thread + memory budgets, refreshed every tick for the `budget`
    // query. `None` until `Runtime::start` has published them.
    pub(super) budget: Option<BudgetSnapshot>,
    // Live RAM back-off pressure on streaming, refreshed every tick for the
    // `streaming` query. `None` until StreamingSystem publishes its first sample
    // (or when the valve is inert: no `MemoryBudget` / RSS available).
    pub(super) streaming_pressure: Option<PressureSnapshot>,
    // Jobs the verb handlers queue and the per-frame debug drive runs.
    pub(super) queue: RuntimeQueue,
}

// A read-only snapshot of the process thread + memory budgets (see
// `concinnity_engine::app::budget`), served by the `budget` query. `rss_mib` is
// the live resident set size sampled each tick; the rest are fixed at start.
#[derive(Clone, Default, serde::Serialize)]
pub(crate) struct BudgetSnapshot {
    pub(super) threads: BudgetThreads,
    pub(super) memory: BudgetMemory,
}

#[derive(Clone, Default, serde::Serialize)]
pub(crate) struct BudgetThreads {
    pub(super) total_cores: usize,
    pub(super) job_threads: usize,
}

#[derive(Clone, Default, serde::Serialize)]
pub(crate) struct BudgetMemory {
    pub(super) total_ram_mib: Option<u64>,
    pub(super) budget_mib: u64,
    pub(super) overridden: bool,
    pub(super) rss_mib: Option<u64>,
}

// A read-only snapshot of the streaming RAM back-off valve (see
// `concinnity_engine::gfx::streaming::system::StreamingPressure`), served
// alongside the `streaming` query so the valve is headless-verifiable.
#[derive(Clone, Copy, Default, serde::Serialize)]
pub(crate) struct PressureSnapshot {
    pub(super) rss_bytes: u64,
    pub(super) budget_bytes: u64,
    pub(super) under_pressure: bool,
}

// A read-only snapshot of the active `Camera3D`, served by `camera-get`.
#[derive(Clone, serde::Serialize)]
pub(crate) struct CameraSnapshot {
    pub(super) position: [f32; 3],
    pub(super) yaw: f32,
    pub(super) pitch: f32,
    pub(super) fov_y_degrees: f32,
    pub(super) near: f32,
    pub(super) view_distance: Option<f32>,
}

// A structural census entry: one component type and how many of it the world
// holds. Per-component names are not retained at runtime (`BlobAssetDef::to_def`
// drops them), so this carries only the type discriminant; the `names` table
// handles the id -> name remap separately.
#[derive(Clone, serde::Serialize)]
pub(crate) struct AssetEntry {
    pub(super) discriminant: u8,
    pub(super) count: u32,
}