implements PLN-0118
This commit is contained in:
parent
cfb5b1ac89
commit
6b5b27d068
@ -12,6 +12,6 @@ pub use services::process;
|
||||
pub use services::task;
|
||||
pub use services::vm_runtime::{
|
||||
RenderWorkerConfig, RenderWorkerController, RenderWorkerHandoff, RenderWorkerHandoffWait,
|
||||
VirtualMachineRuntime,
|
||||
RenderWorkerOwnership, VirtualMachineRuntime,
|
||||
};
|
||||
pub use services::windows;
|
||||
|
||||
@ -27,6 +27,7 @@ impl VirtualMachineRuntime {
|
||||
render_capabilities: RenderRuntimeCapabilities::default(),
|
||||
render_manager: RenderManager::new(AppMode::Game),
|
||||
render_worker_handoff: Arc::new(RenderWorkerHandoff::default()),
|
||||
render_worker_ownership: RenderWorkerOwnership::default(),
|
||||
render_worker_controller: None,
|
||||
gfx2d_commands: Vec::new(),
|
||||
gfxui_commands: Vec::new(),
|
||||
@ -129,6 +130,8 @@ impl VirtualMachineRuntime {
|
||||
self.render_manager = RenderManager::new(AppMode::Game);
|
||||
let _ = self.stop_render_worker();
|
||||
self.render_worker_handoff = Arc::new(RenderWorkerHandoff::default());
|
||||
self.render_worker_ownership =
|
||||
RenderWorkerOwnership::new(self.render_manager.active_ownership());
|
||||
self.gfx2d_commands.clear();
|
||||
self.gfxui_commands.clear();
|
||||
self.logs_written_this_frame.clear();
|
||||
@ -159,9 +162,11 @@ impl VirtualMachineRuntime {
|
||||
{
|
||||
let _ = self.stop_render_worker();
|
||||
self.render_worker_handoff = Arc::new(RenderWorkerHandoff::default());
|
||||
self.render_worker_ownership.set(self.render_manager.active_ownership());
|
||||
self.render_worker_controller = Some(RenderWorkerController::start(
|
||||
config,
|
||||
Arc::clone(&self.render_worker_handoff),
|
||||
self.render_worker_ownership.clone(),
|
||||
backend,
|
||||
sink,
|
||||
));
|
||||
@ -184,6 +189,10 @@ impl VirtualMachineRuntime {
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn sync_render_worker_ownership(&self) {
|
||||
self.render_worker_ownership.set(self.render_manager.active_ownership());
|
||||
}
|
||||
|
||||
pub fn initialize_vm(
|
||||
&mut self,
|
||||
log_service: &mut LogService,
|
||||
|
||||
@ -21,7 +21,7 @@ use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier};
|
||||
use prometeu_hal::{Gfx2dCommand, GfxUiCommand};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
pub use render_manager::{RenderManager, RenderRuntimeCapabilities};
|
||||
pub use render_worker::{RenderWorkerConfig, RenderWorkerController};
|
||||
pub use render_worker::{RenderWorkerConfig, RenderWorkerController, RenderWorkerOwnership};
|
||||
pub use render_worker_handoff::{RenderWorkerHandoff, RenderWorkerHandoffWait};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@ -42,6 +42,7 @@ pub struct VirtualMachineRuntime {
|
||||
pub render_capabilities: RenderRuntimeCapabilities,
|
||||
pub render_manager: RenderManager,
|
||||
pub render_worker_handoff: Arc<RenderWorkerHandoff>,
|
||||
pub render_worker_ownership: RenderWorkerOwnership,
|
||||
pub render_worker_controller: Option<RenderWorkerController>,
|
||||
pub gfx2d_commands: Vec<Gfx2dCommand>,
|
||||
pub gfxui_commands: Vec<GfxUiCommand>,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use crate::services::vm_runtime::{RenderWorkerHandoff, RenderWorkerHandoffWait};
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::{
|
||||
FrameId, RenderWorkerBackend, RenderWorkerError, RenderWorkerFrameSink, RenderWorkerTelemetry,
|
||||
FrameId, OwnedRgba8888Frame, RenderOwnership, RenderWorkerBackend, RenderWorkerError,
|
||||
RenderWorkerFrameSink, RenderWorkerTelemetry,
|
||||
};
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
|
||||
@ -23,12 +25,39 @@ impl Default for RenderWorkerConfig {
|
||||
struct RenderWorkerState {
|
||||
telemetry: RenderWorkerTelemetry,
|
||||
last_error: Option<RenderWorkerError>,
|
||||
latest_published_frame: Option<OwnedRgba8888Frame>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderWorkerOwnership {
|
||||
active: Arc<Mutex<RenderOwnership>>,
|
||||
}
|
||||
|
||||
impl Default for RenderWorkerOwnership {
|
||||
fn default() -> Self {
|
||||
Self::new(RenderOwnership::new(0, AppMode::Game, 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderWorkerOwnership {
|
||||
pub fn new(active: RenderOwnership) -> Self {
|
||||
Self { active: Arc::new(Mutex::new(active)) }
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> RenderOwnership {
|
||||
*self.active.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn set(&self, active: RenderOwnership) {
|
||||
*self.active.lock().unwrap() = active;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderWorkerController {
|
||||
config: RenderWorkerConfig,
|
||||
handoff: Arc<RenderWorkerHandoff>,
|
||||
ownership: RenderWorkerOwnership,
|
||||
state: Arc<Mutex<RenderWorkerState>>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
done_rx: Receiver<Result<(), RenderWorkerError>>,
|
||||
@ -38,6 +67,7 @@ impl RenderWorkerController {
|
||||
pub fn start<B, S>(
|
||||
config: RenderWorkerConfig,
|
||||
handoff: Arc<RenderWorkerHandoff>,
|
||||
ownership: RenderWorkerOwnership,
|
||||
backend: B,
|
||||
sink: S,
|
||||
) -> Self
|
||||
@ -47,13 +77,14 @@ impl RenderWorkerController {
|
||||
{
|
||||
let state = Arc::new(Mutex::new(RenderWorkerState::default()));
|
||||
let worker_handoff = Arc::clone(&handoff);
|
||||
let worker_ownership = ownership.clone();
|
||||
let worker_state = Arc::clone(&state);
|
||||
let panic_state = Arc::clone(&state);
|
||||
let (done_tx, done_rx) = mpsc::channel();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||
run_worker_loop(worker_handoff, worker_state, backend, sink);
|
||||
run_worker_loop(worker_handoff, worker_ownership, worker_state, backend, sink);
|
||||
}))
|
||||
.map_err(|_| RenderWorkerError::WorkerPanic);
|
||||
if let Err(error) = result {
|
||||
@ -62,7 +93,7 @@ impl RenderWorkerController {
|
||||
let _ = done_tx.send(result);
|
||||
});
|
||||
|
||||
Self { config, handoff, state, handle: Some(handle), done_rx }
|
||||
Self { config, handoff, ownership, state, handle: Some(handle), done_rx }
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> Result<(), RenderWorkerError> {
|
||||
@ -95,8 +126,12 @@ impl RenderWorkerController {
|
||||
telemetry.render_failures = state.telemetry.render_failures;
|
||||
telemetry.publish_failures = state.telemetry.publish_failures;
|
||||
telemetry.present_failures = state.telemetry.present_failures;
|
||||
telemetry.stale_submission_discards = state.telemetry.stale_submission_discards;
|
||||
telemetry.repeated_frames = state.telemetry.repeated_frames;
|
||||
telemetry.shutdown_timeouts = state.telemetry.shutdown_timeouts;
|
||||
telemetry.worker_panics = state.telemetry.worker_panics;
|
||||
telemetry.last_presented_frame_id = state.telemetry.last_presented_frame_id;
|
||||
telemetry.last_dropped_frame_id = state.telemetry.last_dropped_frame_id;
|
||||
telemetry.last_published_frame_id = state.telemetry.last_published_frame_id;
|
||||
telemetry.last_error_frame_id = state.telemetry.last_error_frame_id;
|
||||
telemetry
|
||||
@ -105,6 +140,21 @@ impl RenderWorkerController {
|
||||
pub fn last_error(&self) -> Option<RenderWorkerError> {
|
||||
self.state.lock().unwrap().last_error
|
||||
}
|
||||
|
||||
pub fn active_ownership(&self) -> RenderOwnership {
|
||||
self.ownership.snapshot()
|
||||
}
|
||||
|
||||
pub fn latest_published_frame(&self) -> Option<OwnedRgba8888Frame> {
|
||||
self.state.lock().unwrap().latest_published_frame.clone()
|
||||
}
|
||||
|
||||
pub fn repeat_latest_frame(&self) -> Option<OwnedRgba8888Frame> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let frame = state.latest_published_frame.clone()?;
|
||||
state.telemetry.record_repeated_frame(frame.frame_id.get());
|
||||
Some(frame)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RenderWorkerController {
|
||||
@ -117,6 +167,7 @@ impl Drop for RenderWorkerController {
|
||||
|
||||
fn run_worker_loop<B, S>(
|
||||
handoff: Arc<RenderWorkerHandoff>,
|
||||
ownership: RenderWorkerOwnership,
|
||||
state: Arc<Mutex<RenderWorkerState>>,
|
||||
backend: B,
|
||||
sink: S,
|
||||
@ -141,17 +192,28 @@ fn run_worker_loop<B, S>(
|
||||
}
|
||||
};
|
||||
|
||||
let publish_result = catch_unwind(AssertUnwindSafe(|| sink.publish(frame)))
|
||||
if frame.ownership != ownership.snapshot() {
|
||||
record_stale_discard(&state, frame_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let publish_result = catch_unwind(AssertUnwindSafe(|| sink.publish(frame.clone())))
|
||||
.map_err(|_| RenderWorkerError::WorkerPanic);
|
||||
match publish_result {
|
||||
Ok(Ok(())) => record_published(&state, frame_id),
|
||||
Ok(Ok(())) => record_published(&state, frame),
|
||||
Ok(Err(error)) | Err(error) => record_error(&state, error, frame_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_published(state: &Arc<Mutex<RenderWorkerState>>, frame_id: FrameId) {
|
||||
state.lock().unwrap().telemetry.record_published(frame_id.get());
|
||||
fn record_published(state: &Arc<Mutex<RenderWorkerState>>, frame: OwnedRgba8888Frame) {
|
||||
let mut state = state.lock().unwrap();
|
||||
state.telemetry.record_published(frame.frame_id.get());
|
||||
state.latest_published_frame = Some(frame);
|
||||
}
|
||||
|
||||
fn record_stale_discard(state: &Arc<Mutex<RenderWorkerState>>, frame_id: FrameId) {
|
||||
state.lock().unwrap().telemetry.record_stale_submission_discarded(frame_id.get());
|
||||
}
|
||||
|
||||
fn record_error(
|
||||
@ -171,7 +233,10 @@ mod tests {
|
||||
use crate::services::vm_runtime::render_worker_test_harness::{
|
||||
FakeRenderBackend, RenderGate, game_submission,
|
||||
};
|
||||
use prometeu_hal::{FrameId, Game2DFramePacket, OwnedRgba8888Frame, RenderSubmission};
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::{
|
||||
FrameId, Game2DFramePacket, OwnedRgba8888Frame, RenderOwnership, RenderSubmission,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SharedFakeBackend(Arc<FakeRenderBackend>);
|
||||
@ -220,6 +285,7 @@ mod tests {
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig::default(),
|
||||
Arc::clone(&handoff),
|
||||
RenderWorkerOwnership::default(),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
SharedFakeBackend(backend),
|
||||
);
|
||||
@ -235,6 +301,7 @@ mod tests {
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig::default(),
|
||||
Arc::clone(&handoff),
|
||||
RenderWorkerOwnership::new(RenderOwnership::new(1, AppMode::Game, 1)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
);
|
||||
@ -256,6 +323,7 @@ mod tests {
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig { shutdown_timeout: Duration::from_millis(1) },
|
||||
Arc::clone(&handoff),
|
||||
RenderWorkerOwnership::default(),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
);
|
||||
@ -278,6 +346,7 @@ mod tests {
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig::default(),
|
||||
Arc::clone(&handoff),
|
||||
RenderWorkerOwnership::default(),
|
||||
PanickingBackend,
|
||||
NoopSink,
|
||||
);
|
||||
@ -290,6 +359,53 @@ mod tests {
|
||||
assert_eq!(controller.telemetry().last_error_frame_id, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_worker_controller_discards_stale_frame_before_publish() {
|
||||
let handoff = Arc::new(RenderWorkerHandoff::default());
|
||||
let gate = RenderGate::default();
|
||||
let backend = Arc::new(FakeRenderBackend::with_gate(gate.clone()));
|
||||
let ownership = RenderWorkerOwnership::new(RenderOwnership::new(1, AppMode::Game, 1));
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig::default(),
|
||||
Arc::clone(&handoff),
|
||||
ownership.clone(),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
);
|
||||
|
||||
handoff.publish(game_submission(21, 1, 1));
|
||||
gate.wait_for_entries(1);
|
||||
ownership.set(RenderOwnership::new(2, AppMode::Game, 1));
|
||||
gate.release_one();
|
||||
|
||||
assert_eq!(controller.stop(), Ok(()));
|
||||
assert!(backend.published_frames().is_empty());
|
||||
assert_eq!(controller.telemetry().stale_submission_discards, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_worker_controller_repeats_latest_published_frame_without_rendering() {
|
||||
let handoff = Arc::new(RenderWorkerHandoff::default());
|
||||
let backend = Arc::new(FakeRenderBackend::default());
|
||||
let mut controller = RenderWorkerController::start(
|
||||
RenderWorkerConfig::default(),
|
||||
Arc::clone(&handoff),
|
||||
RenderWorkerOwnership::new(RenderOwnership::new(1, AppMode::Game, 1)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
SharedFakeBackend(Arc::clone(&backend)),
|
||||
);
|
||||
|
||||
handoff.publish(game_submission(22, 1, 1));
|
||||
assert_eq!(controller.stop(), Ok(()));
|
||||
|
||||
let latest = controller.latest_published_frame().expect("latest frame");
|
||||
let repeated = controller.repeat_latest_frame().expect("repeated frame");
|
||||
|
||||
assert_eq!(latest, repeated);
|
||||
assert_eq!(backend.published_frames().len(), 1);
|
||||
assert_eq!(controller.telemetry().repeated_frames, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_publishes_game_submissions_to_worker_without_waiting_for_render() {
|
||||
let gate = RenderGate::default();
|
||||
|
||||
@ -72,7 +72,14 @@ impl FakeRenderBackend {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(owned_frame(submission.frame_id, 0xff00_0000 | submission.frame_id.get() as u32))
|
||||
OwnedRgba8888Frame::packed(
|
||||
submission.frame_id,
|
||||
submission.ownership,
|
||||
1,
|
||||
1,
|
||||
vec![0xff00_0000 | submission.frame_id.get() as u32],
|
||||
)
|
||||
.map_err(|_| RenderWorkerError::InternalFailure)
|
||||
}
|
||||
|
||||
pub(crate) fn publish(&self, frame: OwnedRgba8888Frame) {
|
||||
|
||||
@ -246,6 +246,7 @@ impl VirtualMachineRuntime {
|
||||
self.current_cartridge_app_mode,
|
||||
self.current_app_id,
|
||||
);
|
||||
self.sync_render_worker_ownership();
|
||||
if self.current_cartridge_app_mode == AppMode::Game {
|
||||
let mut packet = platform.game2d_frame_composer().close_game2d_packet();
|
||||
packet.gfx2d = std::mem::take(&mut self.gfx2d_commands);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
id: PLN-0118
|
||||
ticket: real-render-worker-establishment
|
||||
title: Add Stale Epoch and Repeat Frame Behavior
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-06-15
|
||||
completed:
|
||||
ref_decisions: [DEC-0033]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user