implements PLN-0117
This commit is contained in:
parent
0c6a669e6e
commit
cfb5b1ac89
@ -112,7 +112,7 @@ fn matrix_packet_policy_and_pacing_are_mode_specific() {
|
|||||||
assert_eq!(game_policy.execution, RenderExecutionPolicy::WorkerCapableWithLocalFallback);
|
assert_eq!(game_policy.execution, RenderExecutionPolicy::WorkerCapableWithLocalFallback);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
game_policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
|
game_policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
|
||||||
RenderConsumerPath::LocalWorkerPrototype
|
RenderConsumerPath::RealWorker
|
||||||
);
|
);
|
||||||
|
|
||||||
let shell_policy = RenderPolicy::for_app_mode(AppMode::Shell);
|
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 {
|
let outcome = match path {
|
||||||
RenderConsumerPath::LocalSynchronous => manager.consume_latest(&mut surface),
|
RenderConsumerPath::LocalSynchronous => manager.consume_latest(&mut surface),
|
||||||
RenderConsumerPath::LocalWorkerPrototype => {
|
RenderConsumerPath::RealWorker => {
|
||||||
let mut worker = LocalRenderWorker;
|
let mut worker = LocalRenderWorker;
|
||||||
worker.consume_latest(&mut manager, &mut surface)
|
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)
|
(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);
|
let (outcome, seen, replaced) = consume_with(path);
|
||||||
assert_eq!(outcome, RenderConsumeOutcome::Presented { frame_id: FrameId::new(1) });
|
assert_eq!(outcome, RenderConsumeOutcome::Presented { frame_id: FrameId::new(1) });
|
||||||
assert_eq!(seen, vec![FrameId::new(1)]);
|
assert_eq!(seen, vec![FrameId::new(1)]);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::CrashReport;
|
|||||||
use crate::fs::{FsBackend, FsState, VirtualFS};
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
use prometeu_hal::cartridge::Cartridge;
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
|
use prometeu_hal::{RenderWorkerBackend, RenderWorkerError, RenderWorkerFrameSink};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
impl VirtualMachineRuntime {
|
||||||
@ -25,6 +26,8 @@ impl VirtualMachineRuntime {
|
|||||||
frame_scheduler: FrameScheduler::default(),
|
frame_scheduler: FrameScheduler::default(),
|
||||||
render_capabilities: RenderRuntimeCapabilities::default(),
|
render_capabilities: RenderRuntimeCapabilities::default(),
|
||||||
render_manager: RenderManager::new(AppMode::Game),
|
render_manager: RenderManager::new(AppMode::Game),
|
||||||
|
render_worker_handoff: Arc::new(RenderWorkerHandoff::default()),
|
||||||
|
render_worker_controller: None,
|
||||||
gfx2d_commands: Vec::new(),
|
gfx2d_commands: Vec::new(),
|
||||||
gfxui_commands: Vec::new(),
|
gfxui_commands: Vec::new(),
|
||||||
logs_written_this_frame: HashMap::new(),
|
logs_written_this_frame: HashMap::new(),
|
||||||
@ -124,6 +127,8 @@ impl VirtualMachineRuntime {
|
|||||||
self.frame_scheduler = FrameScheduler::default();
|
self.frame_scheduler = FrameScheduler::default();
|
||||||
self.render_capabilities = RenderRuntimeCapabilities::default();
|
self.render_capabilities = RenderRuntimeCapabilities::default();
|
||||||
self.render_manager = RenderManager::new(AppMode::Game);
|
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.gfx2d_commands.clear();
|
||||||
self.gfxui_commands.clear();
|
self.gfxui_commands.clear();
|
||||||
self.logs_written_this_frame.clear();
|
self.logs_written_this_frame.clear();
|
||||||
@ -147,6 +152,38 @@ impl VirtualMachineRuntime {
|
|||||||
self.render_capabilities = capabilities;
|
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(
|
pub fn initialize_vm(
|
||||||
&mut self,
|
&mut self,
|
||||||
log_service: &mut LogService,
|
log_service: &mut LogService,
|
||||||
|
|||||||
@ -41,6 +41,8 @@ pub struct VirtualMachineRuntime {
|
|||||||
pub frame_scheduler: FrameScheduler,
|
pub frame_scheduler: FrameScheduler,
|
||||||
pub render_capabilities: RenderRuntimeCapabilities,
|
pub render_capabilities: RenderRuntimeCapabilities,
|
||||||
pub render_manager: RenderManager,
|
pub render_manager: RenderManager,
|
||||||
|
pub render_worker_handoff: Arc<RenderWorkerHandoff>,
|
||||||
|
pub render_worker_controller: Option<RenderWorkerController>,
|
||||||
pub gfx2d_commands: Vec<Gfx2dCommand>,
|
pub gfx2d_commands: Vec<Gfx2dCommand>,
|
||||||
pub gfxui_commands: Vec<GfxUiCommand>,
|
pub gfxui_commands: Vec<GfxUiCommand>,
|
||||||
pub logs_written_this_frame: HashMap<u32, u32>,
|
pub logs_written_this_frame: HashMap<u32, u32>,
|
||||||
|
|||||||
@ -59,7 +59,7 @@ pub struct RenderRuntimeCapabilities {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RenderConsumerPath {
|
pub enum RenderConsumerPath {
|
||||||
LocalSynchronous,
|
LocalSynchronous,
|
||||||
LocalWorkerPrototype,
|
RealWorker,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -95,7 +95,7 @@ impl RenderPolicy {
|
|||||||
) -> RenderConsumerPath {
|
) -> RenderConsumerPath {
|
||||||
match (self.execution, capabilities.game_render_worker) {
|
match (self.execution, capabilities.game_render_worker) {
|
||||||
(RenderExecutionPolicy::WorkerCapableWithLocalFallback, true) => {
|
(RenderExecutionPolicy::WorkerCapableWithLocalFallback, true) => {
|
||||||
RenderConsumerPath::LocalWorkerPrototype
|
RenderConsumerPath::RealWorker
|
||||||
}
|
}
|
||||||
_ => RenderConsumerPath::LocalSynchronous,
|
_ => RenderConsumerPath::LocalSynchronous,
|
||||||
}
|
}
|
||||||
@ -107,8 +107,10 @@ pub trait RenderSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
#[cfg(test)]
|
||||||
pub struct LocalRenderWorker;
|
pub struct LocalRenderWorker;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
impl LocalRenderWorker {
|
impl LocalRenderWorker {
|
||||||
pub fn consume_latest<S: RenderSurface>(
|
pub fn consume_latest<S: RenderSurface>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -329,6 +331,12 @@ impl RenderManager {
|
|||||||
discarded
|
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> {
|
pub fn request_shutdown(&mut self) -> Option<RenderSubmission> {
|
||||||
self.consumer_state = RenderConsumerState::Stopped;
|
self.consumer_state = RenderConsumerState::Stopped;
|
||||||
self.discard_pending_submission()
|
self.discard_pending_submission()
|
||||||
@ -624,7 +632,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
|
policy.resolve_consumer_path(RenderRuntimeCapabilities { game_render_worker: true }),
|
||||||
RenderConsumerPath::LocalWorkerPrototype
|
RenderConsumerPath::RealWorker
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>(
|
fn run_worker_loop<B, S>(
|
||||||
handoff: Arc<RenderWorkerHandoff>,
|
handoff: Arc<RenderWorkerHandoff>,
|
||||||
state: Arc<Mutex<RenderWorkerState>>,
|
state: Arc<Mutex<RenderWorkerState>>,
|
||||||
@ -159,10 +167,11 @@ fn record_error(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::services::vm_runtime::VirtualMachineRuntime;
|
||||||
use crate::services::vm_runtime::render_worker_test_harness::{
|
use crate::services::vm_runtime::render_worker_test_harness::{
|
||||||
FakeRenderBackend, RenderGate, game_submission,
|
FakeRenderBackend, RenderGate, game_submission,
|
||||||
};
|
};
|
||||||
use prometeu_hal::{FrameId, OwnedRgba8888Frame, RenderSubmission};
|
use prometeu_hal::{FrameId, Game2DFramePacket, OwnedRgba8888Frame, RenderSubmission};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct SharedFakeBackend(Arc<FakeRenderBackend>);
|
struct SharedFakeBackend(Arc<FakeRenderBackend>);
|
||||||
@ -280,4 +289,52 @@ mod tests {
|
|||||||
assert_eq!(controller.telemetry().worker_panics, 1);
|
assert_eq!(controller.telemetry().worker_panics, 1);
|
||||||
assert_eq!(controller.telemetry().last_error_frame_id, 13);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
use super::dispatch::VmRuntimeHost;
|
use super::dispatch::VmRuntimeHost;
|
||||||
use super::render_manager::{
|
use super::render_manager::{RenderConsumeOutcome, RenderConsumerPath, RenderSurface};
|
||||||
LocalRenderWorker, RenderConsumeOutcome, RenderConsumerPath, RenderSurface,
|
|
||||||
};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use crate::fs::{FsState, VirtualFS};
|
use crate::fs::{FsState, VirtualFS};
|
||||||
@ -273,9 +271,18 @@ impl VirtualMachineRuntime {
|
|||||||
RenderConsumerPath::LocalSynchronous => {
|
RenderConsumerPath::LocalSynchronous => {
|
||||||
self.render_manager.consume_latest(&mut surface)
|
self.render_manager.consume_latest(&mut surface)
|
||||||
}
|
}
|
||||||
RenderConsumerPath::LocalWorkerPrototype => {
|
RenderConsumerPath::RealWorker
|
||||||
let mut worker = LocalRenderWorker;
|
if self.render_worker_controller.is_some()
|
||||||
worker.consume_latest(&mut self.render_manager, &mut surface)
|
&& 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
@ -2,7 +2,7 @@
|
|||||||
id: PLN-0117
|
id: PLN-0117
|
||||||
ticket: real-render-worker-establishment
|
ticket: real-render-worker-establishment
|
||||||
title: Integrate Runtime Submission With Worker
|
title: Integrate Runtime Submission With Worker
|
||||||
status: open
|
status: done
|
||||||
created: 2026-06-15
|
created: 2026-06-15
|
||||||
completed:
|
completed:
|
||||||
ref_decisions: [DEC-0033]
|
ref_decisions: [DEC-0033]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user