embedded_3dgfx/
scene_stream.rs1use crate::error::{RecoveryAction, RenderError, RuntimeFaultKind, StallKind};
2use crate::scene_format::{ChunkCursor, ChunkKind, SceneChunkRef};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum UploadStatus {
6 Uploaded,
7 Busy,
8}
9
10pub trait ResourceUploader {
11 fn upload_mesh(&mut self, payload: &[u8]) -> Result<UploadStatus, RenderError>;
12 fn upload_texture(&mut self, payload: &[u8]) -> Result<UploadStatus, RenderError>;
13 fn upload_meta(&mut self, payload: &[u8]) -> Result<UploadStatus, RenderError>;
14}
15
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17pub struct StreamProgress {
18 pub chunks_total: usize,
19 pub chunks_uploaded: usize,
20 pub chunks_deferred: usize,
21}
22
23pub struct CooperativeChunkLoader<'a, U: ResourceUploader> {
24 cursor: ChunkCursor<'a>,
25 uploader: U,
26 max_chunks_per_poll: usize,
27 deferred: Option<SceneChunkRef<'a>>,
28 progress: StreamProgress,
29}
30
31impl<'a, U: ResourceUploader> CooperativeChunkLoader<'a, U> {
32 pub fn new(
33 bytes: &'a [u8],
34 uploader: U,
35 max_chunks_per_poll: usize,
36 ) -> Result<Self, RenderError> {
37 if max_chunks_per_poll == 0 {
38 return Err(RenderError::InvalidInput(
39 "max_chunks_per_poll must be >= 1",
40 ));
41 }
42 let cursor = ChunkCursor::new(bytes)?;
43 Ok(Self {
44 cursor,
45 uploader,
46 max_chunks_per_poll,
47 deferred: None,
48 progress: StreamProgress::default(),
49 })
50 }
51
52 fn upload_chunk(&mut self, chunk: SceneChunkRef<'a>) -> Result<UploadStatus, RenderError> {
53 match chunk.kind {
54 ChunkKind::Mesh => self.uploader.upload_mesh(chunk.payload),
55 ChunkKind::Texture => self.uploader.upload_texture(chunk.payload),
56 ChunkKind::Meta => self.uploader.upload_meta(chunk.payload),
57 }
58 }
59
60 pub fn poll(&mut self) -> Result<bool, RenderError> {
61 let mut processed = 0usize;
62 while processed < self.max_chunks_per_poll {
63 let chunk = if let Some(ch) = self.deferred.take() {
64 ch
65 } else {
66 match self.cursor.next_chunk()? {
67 Some(ch) => ch,
68 None => return Ok(true),
69 }
70 };
71 self.progress.chunks_total += 1;
72 match self.upload_chunk(chunk)? {
73 UploadStatus::Uploaded => {
74 self.progress.chunks_uploaded += 1;
75 }
76 UploadStatus::Busy => {
77 self.progress.chunks_deferred += 1;
78 self.deferred = Some(chunk);
79 return Err(RenderError::Recoverable {
80 fault: RuntimeFaultKind::Stall(StallKind::PresentStage),
81 action: RecoveryAction::Retry,
82 });
83 }
84 }
85 processed += 1;
86 }
87 Ok(false)
88 }
89
90 pub fn progress(&self) -> StreamProgress {
91 self.progress
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::scene_format::{ChunkKind, EncodedChunk, encode_scene};
99
100 struct TestUploader {
101 busy_once: bool,
102 meshes: usize,
103 }
104
105 impl ResourceUploader for TestUploader {
106 fn upload_mesh(&mut self, _payload: &[u8]) -> Result<UploadStatus, RenderError> {
107 if self.busy_once {
108 self.busy_once = false;
109 return Ok(UploadStatus::Busy);
110 }
111 self.meshes += 1;
112 Ok(UploadStatus::Uploaded)
113 }
114 fn upload_texture(&mut self, _payload: &[u8]) -> Result<UploadStatus, RenderError> {
115 Ok(UploadStatus::Uploaded)
116 }
117 fn upload_meta(&mut self, _payload: &[u8]) -> Result<UploadStatus, RenderError> {
118 Ok(UploadStatus::Uploaded)
119 }
120 }
121
122 #[test]
123 fn cooperative_loader_retries_busy_upload() {
124 let chunks = [
125 EncodedChunk {
126 kind: ChunkKind::Mesh,
127 payload: &[1, 2],
128 },
129 EncodedChunk {
130 kind: ChunkKind::Texture,
131 payload: &[3, 4],
132 },
133 ];
134 let bytes = encode_scene::<128>(&chunks).unwrap();
135 let uploader = TestUploader {
136 busy_once: true,
137 meshes: 0,
138 };
139 let mut loader = CooperativeChunkLoader::new(&bytes, uploader, 4).unwrap();
140 let first = loader.poll();
141 assert!(matches!(
142 first,
143 Err(RenderError::Recoverable {
144 action: RecoveryAction::Retry,
145 ..
146 })
147 ));
148 let done = loader.poll().unwrap();
149 assert!(done);
150 assert!(loader.progress().chunks_uploaded >= 2);
151 }
152}