implements PLN-0093
This commit is contained in:
parent
3521eec319
commit
c88c927fee
@ -29,6 +29,23 @@ pub struct TelemetryFrame {
|
||||
|
||||
// Log Pressure from the last completed logical frame
|
||||
pub logs_count: u32,
|
||||
|
||||
// Render handoff/presentation telemetry
|
||||
pub produced_submissions: u64,
|
||||
pub replaced_before_consume: u64,
|
||||
pub consumed_submissions: u64,
|
||||
pub presented_frames: u64,
|
||||
pub repeated_presents: u64,
|
||||
pub render_errors: u64,
|
||||
pub present_errors: u64,
|
||||
pub stale_epoch_discards: u64,
|
||||
pub shutdown_discards: u64,
|
||||
pub last_produced_frame_id: u64,
|
||||
pub last_consumed_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,
|
||||
}
|
||||
|
||||
/// Thread-safe, atomic telemetry storage for real-time monitoring by the host.
|
||||
@ -62,6 +79,23 @@ pub struct AtomicTelemetry {
|
||||
pub current_logs_count: Arc<AtomicU32>,
|
||||
// Persisted log count from the last completed logical frame
|
||||
pub logs_count: AtomicU32,
|
||||
|
||||
// Render handoff/presentation telemetry
|
||||
pub produced_submissions: AtomicU64,
|
||||
pub replaced_before_consume: AtomicU64,
|
||||
pub consumed_submissions: AtomicU64,
|
||||
pub presented_frames: AtomicU64,
|
||||
pub repeated_presents: AtomicU64,
|
||||
pub render_errors: AtomicU64,
|
||||
pub present_errors: AtomicU64,
|
||||
pub stale_epoch_discards: AtomicU64,
|
||||
pub shutdown_discards: AtomicU64,
|
||||
pub last_produced_frame_id: AtomicU64,
|
||||
pub last_consumed_frame_id: AtomicU64,
|
||||
pub last_presented_frame_id: AtomicU64,
|
||||
pub last_dropped_frame_id: AtomicU64,
|
||||
pub last_error_frame_id: AtomicU64,
|
||||
pub active_render_epoch: AtomicU64,
|
||||
}
|
||||
|
||||
impl AtomicTelemetry {
|
||||
@ -91,6 +125,21 @@ impl AtomicTelemetry {
|
||||
vm_string_materializations: self.vm_string_materializations.load(Ordering::Relaxed),
|
||||
logs_count: self.logs_count.load(Ordering::Relaxed),
|
||||
vm_steps: self.vm_steps.load(Ordering::Relaxed),
|
||||
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),
|
||||
presented_frames: self.presented_frames.load(Ordering::Relaxed),
|
||||
repeated_presents: self.repeated_presents.load(Ordering::Relaxed),
|
||||
render_errors: self.render_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),
|
||||
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_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),
|
||||
active_render_epoch: self.active_render_epoch.load(Ordering::Relaxed),
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +162,21 @@ impl AtomicTelemetry {
|
||||
self.vm_steps.store(0, Ordering::Relaxed);
|
||||
self.logs_count.store(0, Ordering::Relaxed);
|
||||
self.current_logs_count.store(0, Ordering::Relaxed);
|
||||
self.produced_submissions.store(0, Ordering::Relaxed);
|
||||
self.replaced_before_consume.store(0, Ordering::Relaxed);
|
||||
self.consumed_submissions.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.present_errors.store(0, Ordering::Relaxed);
|
||||
self.stale_epoch_discards.store(0, Ordering::Relaxed);
|
||||
self.shutdown_discards.store(0, Ordering::Relaxed);
|
||||
self.last_produced_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_consumed_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);
|
||||
self.active_render_epoch.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::telemetry::AtomicTelemetry;
|
||||
use prometeu_hal::{
|
||||
FrameId, Game2DFramePacket, RenderOwnership, RenderSubmission, RenderSubmissionPacket,
|
||||
ShellUiFramePacket,
|
||||
};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RenderTransitionState {
|
||||
@ -59,6 +61,78 @@ pub trait RenderSurface {
|
||||
fn consume_submission(&mut self, submission: &RenderSubmission);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct RenderTelemetrySnapshot {
|
||||
pub produced_submissions: u64,
|
||||
pub replaced_before_consume: u64,
|
||||
pub consumed_submissions: u64,
|
||||
pub presented_frames: u64,
|
||||
pub repeated_presents: u64,
|
||||
pub render_errors: u64,
|
||||
pub present_errors: u64,
|
||||
pub stale_epoch_discards: u64,
|
||||
pub shutdown_discards: u64,
|
||||
pub last_produced_frame_id: u64,
|
||||
pub last_consumed_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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct RenderTelemetry {
|
||||
snapshot: RenderTelemetrySnapshot,
|
||||
}
|
||||
|
||||
impl RenderTelemetry {
|
||||
fn record_produced(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.produced_submissions = self.snapshot.produced_submissions.wrapping_add(1);
|
||||
self.snapshot.last_produced_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_replaced_before_consume(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.replaced_before_consume =
|
||||
self.snapshot.replaced_before_consume.wrapping_add(1);
|
||||
self.snapshot.last_dropped_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_consumed(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.consumed_submissions = self.snapshot.consumed_submissions.wrapping_add(1);
|
||||
self.snapshot.last_consumed_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_presented(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.presented_frames = self.snapshot.presented_frames.wrapping_add(1);
|
||||
self.snapshot.last_presented_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_repeated_present(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.repeated_presents = self.snapshot.repeated_presents.wrapping_add(1);
|
||||
self.snapshot.last_presented_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_render_error(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.render_errors = self.snapshot.render_errors.wrapping_add(1);
|
||||
self.snapshot.last_error_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_present_error(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.present_errors = self.snapshot.present_errors.wrapping_add(1);
|
||||
self.snapshot.last_error_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_stale_epoch_discard(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.stale_epoch_discards = self.snapshot.stale_epoch_discards.wrapping_add(1);
|
||||
self.snapshot.last_dropped_frame_id = frame_id.get();
|
||||
}
|
||||
|
||||
fn record_shutdown_discard(&mut self, frame_id: FrameId) {
|
||||
self.snapshot.shutdown_discards = self.snapshot.shutdown_discards.wrapping_add(1);
|
||||
self.snapshot.last_dropped_frame_id = frame_id.get();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RenderHandoff {
|
||||
pending: Option<RenderSubmission>,
|
||||
@ -125,6 +199,7 @@ pub struct RenderManager {
|
||||
active_policy: RenderPolicy,
|
||||
next_frame_id: FrameId,
|
||||
handoff: RenderHandoff,
|
||||
telemetry: RenderTelemetry,
|
||||
transition_state: RenderTransitionState,
|
||||
}
|
||||
|
||||
@ -137,6 +212,7 @@ impl RenderManager {
|
||||
active_policy: RenderPolicy::for_app_mode(active_app_mode),
|
||||
next_frame_id: FrameId::ZERO,
|
||||
handoff: RenderHandoff::default(),
|
||||
telemetry: RenderTelemetry::default(),
|
||||
transition_state: RenderTransitionState::Idle,
|
||||
}
|
||||
}
|
||||
@ -182,7 +258,52 @@ impl RenderManager {
|
||||
}
|
||||
|
||||
pub fn discard_pending_submission(&mut self) -> Option<RenderSubmission> {
|
||||
self.handoff.discard_pending()
|
||||
let discarded = self.handoff.discard_pending();
|
||||
if let Some(submission) = discarded.as_ref() {
|
||||
self.telemetry.record_shutdown_discard(submission.frame_id);
|
||||
}
|
||||
discarded
|
||||
}
|
||||
|
||||
pub fn render_telemetry(&self) -> RenderTelemetrySnapshot {
|
||||
let mut snapshot = self.telemetry.snapshot;
|
||||
snapshot.active_render_epoch = self.active_ownership.epoch;
|
||||
snapshot
|
||||
}
|
||||
|
||||
pub fn sync_telemetry(&self, telemetry: &AtomicTelemetry) {
|
||||
let snapshot = self.render_telemetry();
|
||||
telemetry.produced_submissions.store(snapshot.produced_submissions, Ordering::Relaxed);
|
||||
telemetry
|
||||
.replaced_before_consume
|
||||
.store(snapshot.replaced_before_consume, Ordering::Relaxed);
|
||||
telemetry.consumed_submissions.store(snapshot.consumed_submissions, Ordering::Relaxed);
|
||||
telemetry.presented_frames.store(snapshot.presented_frames, Ordering::Relaxed);
|
||||
telemetry.repeated_presents.store(snapshot.repeated_presents, Ordering::Relaxed);
|
||||
telemetry.render_errors.store(snapshot.render_errors, Ordering::Relaxed);
|
||||
telemetry.present_errors.store(snapshot.present_errors, Ordering::Relaxed);
|
||||
telemetry.stale_epoch_discards.store(snapshot.stale_epoch_discards, Ordering::Relaxed);
|
||||
telemetry.shutdown_discards.store(snapshot.shutdown_discards, Ordering::Relaxed);
|
||||
telemetry.last_produced_frame_id.store(snapshot.last_produced_frame_id, Ordering::Relaxed);
|
||||
telemetry.last_consumed_frame_id.store(snapshot.last_consumed_frame_id, Ordering::Relaxed);
|
||||
telemetry
|
||||
.last_presented_frame_id
|
||||
.store(snapshot.last_presented_frame_id, Ordering::Relaxed);
|
||||
telemetry.last_dropped_frame_id.store(snapshot.last_dropped_frame_id, Ordering::Relaxed);
|
||||
telemetry.last_error_frame_id.store(snapshot.last_error_frame_id, Ordering::Relaxed);
|
||||
telemetry.active_render_epoch.store(snapshot.active_render_epoch, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn record_repeated_present(&mut self, frame_id: FrameId) {
|
||||
self.telemetry.record_repeated_present(frame_id);
|
||||
}
|
||||
|
||||
pub fn record_render_error(&mut self, frame_id: FrameId) {
|
||||
self.telemetry.record_render_error(frame_id);
|
||||
}
|
||||
|
||||
pub fn record_present_error(&mut self, frame_id: FrameId) {
|
||||
self.telemetry.record_present_error(frame_id);
|
||||
}
|
||||
|
||||
pub fn set_active_app_mode(&mut self, app_mode: AppMode) {
|
||||
@ -226,6 +347,10 @@ impl RenderManager {
|
||||
};
|
||||
|
||||
self.next_frame_id = self.next_frame_id.next();
|
||||
if let Some(replaced) = self.handoff.pending_submission() {
|
||||
self.telemetry.record_replaced_before_consume(replaced.frame_id);
|
||||
}
|
||||
self.telemetry.record_produced(frame_id);
|
||||
self.handoff.publish(submission);
|
||||
Ok(self.handoff.pending_submission().expect("submission was just stored"))
|
||||
}
|
||||
@ -244,10 +369,13 @@ impl RenderManager {
|
||||
return false;
|
||||
};
|
||||
if submission.ownership != self.active_ownership {
|
||||
self.telemetry.record_stale_epoch_discard(submission.frame_id);
|
||||
self.handoff.record_stale_epoch_discard(submission);
|
||||
return false;
|
||||
}
|
||||
self.telemetry.record_consumed(submission.frame_id);
|
||||
surface.consume_submission(&submission);
|
||||
self.telemetry.record_presented(submission.frame_id);
|
||||
self.handoff.record_consumed(submission);
|
||||
true
|
||||
}
|
||||
@ -262,6 +390,8 @@ impl Default for RenderManager {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
#[derive(Default)]
|
||||
struct RecordingSurface {
|
||||
@ -460,4 +590,64 @@ mod tests {
|
||||
RenderOwnership::new(0, AppMode::Game, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_telemetry_counts_produced_replaced_consumed_and_presented() {
|
||||
let mut manager = RenderManager::new(AppMode::Game);
|
||||
let mut surface = RecordingSurface::default();
|
||||
|
||||
manager.close_compat_frame();
|
||||
manager.close_compat_frame();
|
||||
assert!(manager.publish_latest(&mut surface));
|
||||
|
||||
let telemetry = manager.render_telemetry();
|
||||
assert_eq!(telemetry.produced_submissions, 2);
|
||||
assert_eq!(telemetry.replaced_before_consume, 1);
|
||||
assert_eq!(telemetry.consumed_submissions, 1);
|
||||
assert_eq!(telemetry.presented_frames, 1);
|
||||
assert_eq!(telemetry.last_produced_frame_id, 1);
|
||||
assert_eq!(telemetry.last_dropped_frame_id, 0);
|
||||
assert_eq!(telemetry.last_consumed_frame_id, 1);
|
||||
assert_eq!(telemetry.last_presented_frame_id, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_telemetry_counts_stale_repeat_and_error_hooks() {
|
||||
let mut manager = RenderManager::new(AppMode::Game);
|
||||
let mut surface = RecordingSurface::default();
|
||||
|
||||
manager.close_compat_frame();
|
||||
manager.transition_render_owner(AppMode::Shell, 1);
|
||||
assert!(!manager.publish_latest(&mut surface));
|
||||
manager.record_repeated_present(FrameId::new(9));
|
||||
manager.record_render_error(FrameId::new(10));
|
||||
manager.record_present_error(FrameId::new(11));
|
||||
|
||||
let telemetry = manager.render_telemetry();
|
||||
assert_eq!(telemetry.stale_epoch_discards, 1);
|
||||
assert_eq!(telemetry.repeated_presents, 1);
|
||||
assert_eq!(telemetry.render_errors, 1);
|
||||
assert_eq!(telemetry.present_errors, 1);
|
||||
assert_eq!(telemetry.last_presented_frame_id, 9);
|
||||
assert_eq!(telemetry.last_error_frame_id, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_telemetry_syncs_to_atomic_telemetry() {
|
||||
let mut manager = RenderManager::new(AppMode::Game);
|
||||
let mut surface = RecordingSurface::default();
|
||||
let atomic = AtomicTelemetry::new(Arc::new(AtomicU32::new(0)));
|
||||
|
||||
manager.transition_render_owner(AppMode::Game, 5);
|
||||
manager.close_compat_frame();
|
||||
assert!(manager.publish_latest(&mut surface));
|
||||
manager.sync_telemetry(&atomic);
|
||||
|
||||
let snapshot = atomic.snapshot();
|
||||
assert_eq!(snapshot.produced_submissions, 1);
|
||||
assert_eq!(snapshot.consumed_submissions, 1);
|
||||
assert_eq!(snapshot.presented_frames, 1);
|
||||
assert_eq!(snapshot.last_presented_frame_id, 0);
|
||||
assert_eq!(snapshot.active_render_epoch, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,6 +304,7 @@ impl VirtualMachineRuntime {
|
||||
self.atomic_telemetry
|
||||
.host_cpu_time_us
|
||||
.store(start.elapsed().as_micros() as u64, Ordering::Relaxed);
|
||||
self.render_manager.sync_telemetry(&self.atomic_telemetry);
|
||||
|
||||
let current_frame_logs =
|
||||
self.atomic_telemetry.current_logs_count.load(Ordering::Relaxed);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{"type":"meta","next_id":{"DSC":42,"AGD":42,"DEC":32,"PLN":98,"LSN":48,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}
|
||||
{"type":"discussion","id":"DSC-0040","status":"in_progress","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-05","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[{"id":"AGD-0040","file":"AGD-0040-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-04","updated_at":"2026-06-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-05","updated_at":"2026-06-05","ref_agenda":"AGD-0040"}],"plans":[{"id":"PLN-0087","file":"PLN-0087-fix-game2d-packet-overlay-composition.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0088","file":"PLN-0088-define-read-only-render-resource-boundary.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0089","file":"PLN-0089-introduce-render-handoff-abstraction.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0090","file":"PLN-0090-add-render-frame-pacing-contract.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0091","file":"PLN-0091-implement-appmode-render-policy-matrix.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0092","file":"PLN-0092-add-render-epoch-ownership-transitions.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0093","file":"PLN-0093-add-render-telemetry-counters-and-events.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0094","file":"PLN-0094-specify-async-render-boundary.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0095","file":"PLN-0095-prototype-local-render-worker-capability.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0096","file":"PLN-0096-define-render-shutdown-and-failure-handling.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0097","file":"PLN-0097-add-async-render-integration-test-matrix.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0040","status":"in_progress","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-05","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[{"id":"AGD-0040","file":"AGD-0040-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-04","updated_at":"2026-06-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-vm-and-render-parallel-execution-boundary.md","status":"accepted","created_at":"2026-06-05","updated_at":"2026-06-05","ref_agenda":"AGD-0040"}],"plans":[{"id":"PLN-0087","file":"PLN-0087-fix-game2d-packet-overlay-composition.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0088","file":"PLN-0088-define-read-only-render-resource-boundary.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0089","file":"PLN-0089-introduce-render-handoff-abstraction.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0090","file":"PLN-0090-add-render-frame-pacing-contract.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0091","file":"PLN-0091-implement-appmode-render-policy-matrix.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0092","file":"PLN-0092-add-render-epoch-ownership-transitions.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0093","file":"PLN-0093-add-render-telemetry-counters-and-events.md","status":"done","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0094","file":"PLN-0094-specify-async-render-boundary.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0095","file":"PLN-0095-prototype-local-render-worker-capability.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0096","file":"PLN-0096-define-render-shutdown-and-failure-handling.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0097","file":"PLN-0097-add-async-render-integration-test-matrix.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0038","status":"done","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0047","file":"discussion/lessons/DSC-0038-render-frame-packet-boundary/LSN-0047-typed-render-submissions-preserve-domain-boundaries.md","status":"done","created_at":"2026-06-04","updated_at":"2026-06-04"}]}
|
||||
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0093
|
||||
ticket: vm-render-parallel-execution-boundary
|
||||
title: Add Render Telemetry Counters and Events
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-06-05
|
||||
ref_decisions: [DEC-0031]
|
||||
tags: [runtime, renderer, telemetry, diagnostics]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user