dev/render-worker-establishment #32

Merged
bquarkz merged 47 commits from dev/real-render-worker-establishment into master 2026-06-20 16:11:08 +00:00
5 changed files with 288 additions and 2 deletions
Showing only changes of commit 57bf2b84d3 - Show all commits

View File

@ -23,6 +23,7 @@ pub mod platform;
pub mod primitives;
pub mod render_resources;
pub mod render_submission;
pub mod render_worker;
pub mod sample;
pub mod scene_bank;
pub mod scene_layer;
@ -59,4 +60,5 @@ pub use render_submission::{
Gfx2dCommand, GfxUiCommand, HudCommand, HudPacket, RenderOwnership, RenderSubmission,
RenderSubmissionPacket, ShellUiFramePacket,
};
pub use render_worker::{RenderWorkerError, RenderWorkerTelemetry};
pub use touch_bridge::TouchBridge;

View File

@ -0,0 +1,190 @@
use std::fmt;
/// Typed failure surface for the real render worker and its backend.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderWorkerError {
BackendUnavailable,
RenderFailed,
PublishFailed,
PresentFailed,
ShutdownTimeout,
StaleSubmissionDiscarded,
WorkerPanic,
InternalFailure,
}
impl fmt::Display for RenderWorkerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BackendUnavailable => f.write_str("render backend unavailable"),
Self::RenderFailed => f.write_str("render failed"),
Self::PublishFailed => f.write_str("render publish failed"),
Self::PresentFailed => f.write_str("render present failed"),
Self::ShutdownTimeout => f.write_str("render worker shutdown timed out"),
Self::StaleSubmissionDiscarded => f.write_str("stale render submission discarded"),
Self::WorkerPanic => f.write_str("render worker panicked"),
Self::InternalFailure => f.write_str("internal render worker failure"),
}
}
}
impl std::error::Error for RenderWorkerError {}
/// Snapshot of render worker handoff, render, publish, and shutdown counters.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct RenderWorkerTelemetry {
pub produced_submissions: u64,
pub replaced_before_consume: u64,
pub consumed_submissions: u64,
pub published_frames: u64,
pub presented_frames: u64,
pub repeated_frames: u64,
pub stale_submission_discards: u64,
pub render_failures: u64,
pub publish_failures: u64,
pub present_failures: u64,
pub shutdown_timeouts: u64,
pub worker_panics: u64,
pub last_produced_frame_id: u64,
pub last_consumed_frame_id: u64,
pub last_published_frame_id: u64,
pub last_presented_frame_id: u64,
pub last_dropped_frame_id: u64,
pub last_error_frame_id: u64,
pub active_render_epoch: u64,
}
impl RenderWorkerTelemetry {
pub fn record_produced(&mut self, frame_id: u64) {
self.produced_submissions += 1;
self.last_produced_frame_id = frame_id;
}
pub fn record_replaced_before_consume(&mut self, frame_id: u64) {
self.replaced_before_consume += 1;
self.last_dropped_frame_id = frame_id;
}
pub fn record_consumed(&mut self, frame_id: u64) {
self.consumed_submissions += 1;
self.last_consumed_frame_id = frame_id;
}
pub fn record_published(&mut self, frame_id: u64) {
self.published_frames += 1;
self.last_published_frame_id = frame_id;
}
pub fn record_presented(&mut self, frame_id: u64) {
self.presented_frames += 1;
self.last_presented_frame_id = frame_id;
}
pub fn record_repeated_frame(&mut self, frame_id: u64) {
self.repeated_frames += 1;
self.last_presented_frame_id = frame_id;
}
pub fn record_stale_submission_discarded(&mut self, frame_id: u64) {
self.stale_submission_discards += 1;
self.last_dropped_frame_id = frame_id;
}
pub fn record_error(&mut self, error: RenderWorkerError, frame_id: u64) {
match error {
RenderWorkerError::BackendUnavailable | RenderWorkerError::RenderFailed => {
self.render_failures += 1;
}
RenderWorkerError::PublishFailed => {
self.publish_failures += 1;
}
RenderWorkerError::PresentFailed => {
self.present_failures += 1;
}
RenderWorkerError::ShutdownTimeout => {
self.shutdown_timeouts += 1;
}
RenderWorkerError::WorkerPanic => {
self.worker_panics += 1;
}
RenderWorkerError::StaleSubmissionDiscarded => {
self.stale_submission_discards += 1;
self.last_dropped_frame_id = frame_id;
}
RenderWorkerError::InternalFailure => {
self.render_failures += 1;
}
}
self.last_error_frame_id = frame_id;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_worker_error_is_copy_equatable_and_debuggable() {
let error = RenderWorkerError::ShutdownTimeout;
let copied = error;
assert_eq!(copied, RenderWorkerError::ShutdownTimeout);
assert_eq!(format!("{:?}", error), "ShutdownTimeout");
assert_eq!(error.to_string(), "render worker shutdown timed out");
}
#[test]
fn render_worker_telemetry_defaults_to_zero() {
assert_eq!(
RenderWorkerTelemetry::default(),
RenderWorkerTelemetry {
produced_submissions: 0,
replaced_before_consume: 0,
consumed_submissions: 0,
published_frames: 0,
presented_frames: 0,
repeated_frames: 0,
stale_submission_discards: 0,
render_failures: 0,
publish_failures: 0,
present_failures: 0,
shutdown_timeouts: 0,
worker_panics: 0,
last_produced_frame_id: 0,
last_consumed_frame_id: 0,
last_published_frame_id: 0,
last_presented_frame_id: 0,
last_dropped_frame_id: 0,
last_error_frame_id: 0,
active_render_epoch: 0,
}
);
}
#[test]
fn render_worker_telemetry_records_counters_and_frame_ids() {
let mut telemetry = RenderWorkerTelemetry::default();
telemetry.record_produced(10);
telemetry.record_replaced_before_consume(10);
telemetry.record_consumed(11);
telemetry.record_published(11);
telemetry.record_presented(11);
telemetry.record_repeated_frame(11);
telemetry.record_error(RenderWorkerError::PublishFailed, 12);
telemetry.record_error(RenderWorkerError::WorkerPanic, 13);
telemetry.record_stale_submission_discarded(14);
assert_eq!(telemetry.produced_submissions, 1);
assert_eq!(telemetry.replaced_before_consume, 1);
assert_eq!(telemetry.consumed_submissions, 1);
assert_eq!(telemetry.published_frames, 1);
assert_eq!(telemetry.presented_frames, 1);
assert_eq!(telemetry.repeated_frames, 1);
assert_eq!(telemetry.publish_failures, 1);
assert_eq!(telemetry.worker_panics, 1);
assert_eq!(telemetry.stale_submission_discards, 1);
assert_eq!(telemetry.last_error_frame_id, 13);
assert_eq!(telemetry.last_dropped_frame_id, 14);
}
}

View File

@ -1,4 +1,5 @@
use crate::log::{LogLevel, LogService, LogSource};
use crate::render_worker::RenderWorkerTelemetry;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
@ -34,20 +35,51 @@ pub struct TelemetryFrame {
pub produced_submissions: u64,
pub replaced_before_consume: u64,
pub consumed_submissions: u64,
pub published_frames: u64,
pub presented_frames: u64,
pub repeated_presents: u64,
pub render_errors: u64,
pub publish_errors: u64,
pub present_errors: u64,
pub stale_epoch_discards: u64,
pub shutdown_discards: u64,
pub shutdown_timeouts: u64,
pub worker_panics: u64,
pub last_produced_frame_id: u64,
pub last_consumed_frame_id: u64,
pub last_published_frame_id: u64,
pub last_presented_frame_id: u64,
pub last_dropped_frame_id: u64,
pub last_error_frame_id: u64,
pub active_render_epoch: u64,
}
impl TelemetryFrame {
pub fn render_worker_telemetry(&self) -> RenderWorkerTelemetry {
RenderWorkerTelemetry {
produced_submissions: self.produced_submissions,
replaced_before_consume: self.replaced_before_consume,
consumed_submissions: self.consumed_submissions,
published_frames: self.published_frames,
presented_frames: self.presented_frames,
repeated_frames: self.repeated_presents,
stale_submission_discards: self.stale_epoch_discards,
render_failures: self.render_errors,
publish_failures: self.publish_errors,
present_failures: self.present_errors,
shutdown_timeouts: self.shutdown_timeouts,
worker_panics: self.worker_panics,
last_produced_frame_id: self.last_produced_frame_id,
last_consumed_frame_id: self.last_consumed_frame_id,
last_published_frame_id: self.last_published_frame_id,
last_presented_frame_id: self.last_presented_frame_id,
last_dropped_frame_id: self.last_dropped_frame_id,
last_error_frame_id: self.last_error_frame_id,
active_render_epoch: self.active_render_epoch,
}
}
}
/// Thread-safe, atomic telemetry storage for real-time monitoring by the host.
/// This follows the push-based model from DEC-0005 to avoid expensive scans or locks.
#[derive(Debug, Default)]
@ -84,14 +116,19 @@ pub struct AtomicTelemetry {
pub produced_submissions: AtomicU64,
pub replaced_before_consume: AtomicU64,
pub consumed_submissions: AtomicU64,
pub published_frames: AtomicU64,
pub presented_frames: AtomicU64,
pub repeated_presents: AtomicU64,
pub render_errors: AtomicU64,
pub publish_errors: AtomicU64,
pub present_errors: AtomicU64,
pub stale_epoch_discards: AtomicU64,
pub shutdown_discards: AtomicU64,
pub shutdown_timeouts: AtomicU64,
pub worker_panics: AtomicU64,
pub last_produced_frame_id: AtomicU64,
pub last_consumed_frame_id: AtomicU64,
pub last_published_frame_id: AtomicU64,
pub last_presented_frame_id: AtomicU64,
pub last_dropped_frame_id: AtomicU64,
pub last_error_frame_id: AtomicU64,
@ -128,14 +165,19 @@ impl AtomicTelemetry {
produced_submissions: self.produced_submissions.load(Ordering::Relaxed),
replaced_before_consume: self.replaced_before_consume.load(Ordering::Relaxed),
consumed_submissions: self.consumed_submissions.load(Ordering::Relaxed),
published_frames: self.published_frames.load(Ordering::Relaxed),
presented_frames: self.presented_frames.load(Ordering::Relaxed),
repeated_presents: self.repeated_presents.load(Ordering::Relaxed),
render_errors: self.render_errors.load(Ordering::Relaxed),
publish_errors: self.publish_errors.load(Ordering::Relaxed),
present_errors: self.present_errors.load(Ordering::Relaxed),
stale_epoch_discards: self.stale_epoch_discards.load(Ordering::Relaxed),
shutdown_discards: self.shutdown_discards.load(Ordering::Relaxed),
shutdown_timeouts: self.shutdown_timeouts.load(Ordering::Relaxed),
worker_panics: self.worker_panics.load(Ordering::Relaxed),
last_produced_frame_id: self.last_produced_frame_id.load(Ordering::Relaxed),
last_consumed_frame_id: self.last_consumed_frame_id.load(Ordering::Relaxed),
last_published_frame_id: self.last_published_frame_id.load(Ordering::Relaxed),
last_presented_frame_id: self.last_presented_frame_id.load(Ordering::Relaxed),
last_dropped_frame_id: self.last_dropped_frame_id.load(Ordering::Relaxed),
last_error_frame_id: self.last_error_frame_id.load(Ordering::Relaxed),
@ -165,14 +207,19 @@ impl AtomicTelemetry {
self.produced_submissions.store(0, Ordering::Relaxed);
self.replaced_before_consume.store(0, Ordering::Relaxed);
self.consumed_submissions.store(0, Ordering::Relaxed);
self.published_frames.store(0, Ordering::Relaxed);
self.presented_frames.store(0, Ordering::Relaxed);
self.repeated_presents.store(0, Ordering::Relaxed);
self.render_errors.store(0, Ordering::Relaxed);
self.publish_errors.store(0, Ordering::Relaxed);
self.present_errors.store(0, Ordering::Relaxed);
self.stale_epoch_discards.store(0, Ordering::Relaxed);
self.shutdown_discards.store(0, Ordering::Relaxed);
self.shutdown_timeouts.store(0, Ordering::Relaxed);
self.worker_panics.store(0, Ordering::Relaxed);
self.last_produced_frame_id.store(0, Ordering::Relaxed);
self.last_consumed_frame_id.store(0, Ordering::Relaxed);
self.last_published_frame_id.store(0, Ordering::Relaxed);
self.last_presented_frame_id.store(0, Ordering::Relaxed);
self.last_dropped_frame_id.store(0, Ordering::Relaxed);
self.last_error_frame_id.store(0, Ordering::Relaxed);
@ -398,4 +445,51 @@ mod tests {
assert_eq!(snapshot.vm_heap_allocations, 2);
assert_eq!(snapshot.vm_string_materializations, 5);
}
#[test]
fn snapshot_maps_render_worker_telemetry() {
let current = Arc::new(AtomicU32::new(0));
let tel = AtomicTelemetry::new(current);
tel.produced_submissions.store(2, Ordering::Relaxed);
tel.replaced_before_consume.store(1, Ordering::Relaxed);
tel.consumed_submissions.store(1, Ordering::Relaxed);
tel.published_frames.store(1, Ordering::Relaxed);
tel.presented_frames.store(1, Ordering::Relaxed);
tel.repeated_presents.store(3, Ordering::Relaxed);
tel.render_errors.store(4, Ordering::Relaxed);
tel.publish_errors.store(5, Ordering::Relaxed);
tel.present_errors.store(6, Ordering::Relaxed);
tel.stale_epoch_discards.store(7, Ordering::Relaxed);
tel.shutdown_timeouts.store(8, Ordering::Relaxed);
tel.worker_panics.store(9, Ordering::Relaxed);
tel.last_produced_frame_id.store(10, Ordering::Relaxed);
tel.last_consumed_frame_id.store(11, Ordering::Relaxed);
tel.last_published_frame_id.store(12, Ordering::Relaxed);
tel.last_presented_frame_id.store(13, Ordering::Relaxed);
tel.last_dropped_frame_id.store(14, Ordering::Relaxed);
tel.last_error_frame_id.store(15, Ordering::Relaxed);
tel.active_render_epoch.store(16, Ordering::Relaxed);
let worker = tel.snapshot().render_worker_telemetry();
assert_eq!(worker.produced_submissions, 2);
assert_eq!(worker.replaced_before_consume, 1);
assert_eq!(worker.consumed_submissions, 1);
assert_eq!(worker.published_frames, 1);
assert_eq!(worker.presented_frames, 1);
assert_eq!(worker.repeated_frames, 3);
assert_eq!(worker.render_failures, 4);
assert_eq!(worker.publish_failures, 5);
assert_eq!(worker.present_failures, 6);
assert_eq!(worker.stale_submission_discards, 7);
assert_eq!(worker.shutdown_timeouts, 8);
assert_eq!(worker.worker_panics, 9);
assert_eq!(worker.last_produced_frame_id, 10);
assert_eq!(worker.last_consumed_frame_id, 11);
assert_eq!(worker.last_published_frame_id, 12);
assert_eq!(worker.last_presented_frame_id, 13);
assert_eq!(worker.last_dropped_frame_id, 14);
assert_eq!(worker.last_error_frame_id, 15);
assert_eq!(worker.active_render_epoch, 16);
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
id: PLN-0112
ticket: real-render-worker-establishment
title: Define Render Worker Errors and Telemetry
status: open
status: done
created: 2026-06-15
completed:
ref_decisions: [DEC-0033]