implements PLN-0117

This commit is contained in:
bQUARKz 2026-06-15 16:20:46 +01:00
parent 0c6a669e6e
commit cfb5b1ac89
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 126 additions and 15 deletions

View File

@ -112,7 +112,7 @@ fn matrix_packet_policy_and_pacing_are_mode_specific() {
assert_eq!(game_policy.execution, RenderExecutionPolicy::WorkerCapableWithLocalFallback);
assert_eq!(
game_policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
RenderConsumerPath::LocalWorkerPrototype
RenderConsumerPath::RealWorker
);
let shell_policy = RenderPolicy::for_app_mode(AppMode::Shell);
@ -163,7 +163,7 @@ fn matrix_sync_and_worker_paths_share_latest_wins_handoff_contract() {
let outcome = match path {
RenderConsumerPath::LocalSynchronous => manager.consume_latest(&mut surface),
RenderConsumerPath::LocalWorkerPrototype => {
RenderConsumerPath::RealWorker => {
let mut worker = LocalRenderWorker;
worker.consume_latest(&mut manager, &mut surface)
}
@ -172,7 +172,7 @@ fn matrix_sync_and_worker_paths_share_latest_wins_handoff_contract() {
(outcome, surface.seen, manager.render_telemetry().replaced_before_consume)
}
for path in [RenderConsumerPath::LocalSynchronous, RenderConsumerPath::LocalWorkerPrototype] {
for path in [RenderConsumerPath::LocalSynchronous, RenderConsumerPath::RealWorker] {
let (outcome, seen, replaced) = consume_with(path);
assert_eq!(outcome, RenderConsumeOutcome::Presented { frame_id: FrameId::new(1) });
assert_eq!(seen, vec![FrameId::new(1)]);

View File

@ -3,6 +3,7 @@ use crate::CrashReport;
use crate::fs::{FsBackend, FsState, VirtualFS};
use prometeu_hal::cartridge::Cartridge;
use prometeu_hal::log::{LogLevel, LogSource};
use prometeu_hal::{RenderWorkerBackend, RenderWorkerError, RenderWorkerFrameSink};
use std::collections::HashMap;
impl VirtualMachineRuntime {
@ -25,6 +26,8 @@ impl VirtualMachineRuntime {
frame_scheduler: FrameScheduler::default(),
render_capabilities: RenderRuntimeCapabilities::default(),
render_manager: RenderManager::new(AppMode::Game),
render_worker_handoff: Arc::new(RenderWorkerHandoff::default()),
render_worker_controller: None,
gfx2d_commands: Vec::new(),
gfxui_commands: Vec::new(),
logs_written_this_frame: HashMap::new(),
@ -124,6 +127,8 @@ impl VirtualMachineRuntime {
self.frame_scheduler = FrameScheduler::default();
self.render_capabilities = RenderRuntimeCapabilities::default();
self.render_manager = RenderManager::new(AppMode::Game);
let _ = self.stop_render_worker();
self.render_worker_handoff = Arc::new(RenderWorkerHandoff::default());
self.gfx2d_commands.clear();
self.gfxui_commands.clear();
self.logs_written_this_frame.clear();
@ -147,6 +152,38 @@ impl VirtualMachineRuntime {
self.render_capabilities = capabilities;
}
pub fn start_render_worker<B, S>(&mut self, config: RenderWorkerConfig, backend: B, sink: S)
where
B: RenderWorkerBackend,
S: RenderWorkerFrameSink,
{
let _ = self.stop_render_worker();
self.render_worker_handoff = Arc::new(RenderWorkerHandoff::default());
self.render_worker_controller = Some(RenderWorkerController::start(
config,
Arc::clone(&self.render_worker_handoff),
backend,
sink,
));
self.render_capabilities.game_render_worker = true;
}
pub fn stop_render_worker(&mut self) -> Result<(), RenderWorkerError> {
if let Some(mut controller) = self.render_worker_controller.take() {
controller.stop()
} else {
Ok(())
}
}
pub(crate) fn publish_pending_render_to_worker(&mut self) -> bool {
let Some(submission) = self.render_manager.take_pending_for_worker() else {
return false;
};
self.render_worker_handoff.publish(submission);
true
}
pub fn initialize_vm(
&mut self,
log_service: &mut LogService,

View File

@ -41,6 +41,8 @@ pub struct VirtualMachineRuntime {
pub frame_scheduler: FrameScheduler,
pub render_capabilities: RenderRuntimeCapabilities,
pub render_manager: RenderManager,
pub render_worker_handoff: Arc<RenderWorkerHandoff>,
pub render_worker_controller: Option<RenderWorkerController>,
pub gfx2d_commands: Vec<Gfx2dCommand>,
pub gfxui_commands: Vec<GfxUiCommand>,
pub logs_written_this_frame: HashMap<u32, u32>,

View File

@ -59,7 +59,7 @@ pub struct RenderRuntimeCapabilities {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderConsumerPath {
LocalSynchronous,
LocalWorkerPrototype,
RealWorker,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -95,7 +95,7 @@ impl RenderPolicy {
) -> RenderConsumerPath {
match (self.execution, capabilities.game_render_worker) {
(RenderExecutionPolicy::WorkerCapableWithLocalFallback, true) => {
RenderConsumerPath::LocalWorkerPrototype
RenderConsumerPath::RealWorker
}
_ => RenderConsumerPath::LocalSynchronous,
}
@ -107,8 +107,10 @@ pub trait RenderSurface {
}
#[derive(Debug, Default)]
#[cfg(test)]
pub struct LocalRenderWorker;
#[cfg(test)]
impl LocalRenderWorker {
pub fn consume_latest<S: RenderSurface>(
&mut self,
@ -329,6 +331,12 @@ impl RenderManager {
discarded
}
pub fn take_pending_for_worker(&mut self) -> Option<RenderSubmission> {
let submission = self.handoff.take_latest()?;
self.telemetry.record_consumed(submission.frame_id);
Some(submission)
}
pub fn request_shutdown(&mut self) -> Option<RenderSubmission> {
self.consumer_state = RenderConsumerState::Stopped;
self.discard_pending_submission()
@ -624,7 +632,7 @@ mod tests {
);
assert_eq!(
policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
RenderConsumerPath::LocalWorkerPrototype
RenderConsumerPath::RealWorker
);
}

View File

@ -107,6 +107,14 @@ impl RenderWorkerController {
}
}
impl Drop for RenderWorkerController {
fn drop(&mut self) {
if self.handle.is_some() {
let _ = self.stop();
}
}
}
fn run_worker_loop<B, S>(
handoff: Arc<RenderWorkerHandoff>,
state: Arc<Mutex<RenderWorkerState>>,
@ -159,10 +167,11 @@ fn record_error(
#[cfg(test)]
mod tests {
use super::*;
use crate::services::vm_runtime::VirtualMachineRuntime;
use crate::services::vm_runtime::render_worker_test_harness::{
FakeRenderBackend, RenderGate, game_submission,
};
use prometeu_hal::{FrameId, OwnedRgba8888Frame, RenderSubmission};
use prometeu_hal::{FrameId, Game2DFramePacket, OwnedRgba8888Frame, RenderSubmission};
#[derive(Clone, Debug)]
struct SharedFakeBackend(Arc<FakeRenderBackend>);
@ -280,4 +289,52 @@ mod tests {
assert_eq!(controller.telemetry().worker_panics, 1);
assert_eq!(controller.telemetry().last_error_frame_id, 13);
}
#[test]
fn runtime_publishes_game_submissions_to_worker_without_waiting_for_render() {
let gate = RenderGate::default();
let backend = Arc::new(FakeRenderBackend::with_gate(gate.clone()));
let mut runtime = VirtualMachineRuntime::new(None);
runtime.start_render_worker(
RenderWorkerConfig::default(),
SharedFakeBackend(Arc::clone(&backend)),
SharedFakeBackend(Arc::clone(&backend)),
);
runtime
.render_manager
.close_frame_with_packet(prometeu_hal::RenderSubmissionPacket::Game2D(
Game2DFramePacket::default(),
))
.expect("game packet should close");
assert!(runtime.publish_pending_render_to_worker());
gate.wait_for_entries(1);
runtime
.render_manager
.close_frame_with_packet(prometeu_hal::RenderSubmissionPacket::Game2D(
Game2DFramePacket::default(),
))
.expect("second game packet should close");
assert!(runtime.publish_pending_render_to_worker());
runtime
.render_manager
.close_frame_with_packet(prometeu_hal::RenderSubmissionPacket::Game2D(
Game2DFramePacket::default(),
))
.expect("third game packet should close");
assert!(runtime.publish_pending_render_to_worker());
assert_eq!(runtime.render_worker_handoff.telemetry().replaced_before_consume, 1);
gate.release_one();
gate.wait_for_entries(2);
gate.release_one();
assert_eq!(runtime.stop_render_worker(), Ok(()));
let frames = backend.published_frames();
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].frame_id, FrameId::ZERO);
assert_eq!(frames[1].frame_id, FrameId::new(2));
}
}

View File

@ -1,7 +1,5 @@
use super::dispatch::VmRuntimeHost;
use super::render_manager::{
LocalRenderWorker, RenderConsumeOutcome, RenderConsumerPath, RenderSurface,
};
use super::render_manager::{RenderConsumeOutcome, RenderConsumerPath, RenderSurface};
use super::*;
use crate::CrashReport;
use crate::fs::{FsState, VirtualFS};
@ -273,9 +271,18 @@ impl VirtualMachineRuntime {
RenderConsumerPath::LocalSynchronous => {
self.render_manager.consume_latest(&mut surface)
}
RenderConsumerPath::LocalWorkerPrototype => {
let mut worker = LocalRenderWorker;
worker.consume_latest(&mut self.render_manager, &mut surface)
RenderConsumerPath::RealWorker
if self.render_worker_controller.is_some()
&& self.current_cartridge_app_mode == AppMode::Game =>
{
if self.publish_pending_render_to_worker() {
RenderConsumeOutcome::NoSubmission
} else {
RenderConsumeOutcome::NoSubmission
}
}
RenderConsumerPath::RealWorker => {
self.render_manager.consume_latest(&mut surface)
}
}
}));

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
id: PLN-0117
ticket: real-render-worker-establishment
title: Integrate Runtime Submission With Worker
status: open
status: done
created: 2026-06-15
completed:
ref_decisions: [DEC-0033]