Skip to main content

embedded_3dgfx/
scene_stream.rs

1use 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}