1use crate::backend::RenderBackend;
9use alloc::boxed::Box;
10use alloc::vec::Vec;
11use concinnity_core::gfx::chunk_coord::ChunkCoord;
12
13type BackendOp = Box<dyn FnOnce(&mut dyn RenderBackend, &mut ReplayOutcome) + Send>;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum OpFailure {
19 MeshUpload {
22 stream_id: usize,
24 },
25 ChunkAdd {
27 coord: ChunkCoord,
29 },
30}
31
32#[derive(Debug, Default)]
34pub struct ReplayOutcome {
35 pub failures: Vec<OpFailure>,
37 pub memory_pressure: bool,
39}
40
41#[derive(Default)]
44pub struct RenderOps {
45 ops: Vec<BackendOp>,
46}
47
48impl core::fmt::Debug for RenderOps {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 f.debug_struct("RenderOps")
51 .field("len", &self.ops.len())
52 .finish()
53 }
54}
55
56impl RenderOps {
57 pub fn record(&mut self, op: impl FnOnce(&mut dyn RenderBackend) + Send + 'static) {
59 self.ops.push(Box::new(move |backend, _| op(backend)));
60 }
61
62 pub fn record_with(
65 &mut self,
66 op: impl FnOnce(&mut dyn RenderBackend, &mut ReplayOutcome) + Send + 'static,
67 ) {
68 self.ops.push(Box::new(op));
69 }
70
71 pub fn is_empty(&self) -> bool {
73 self.ops.is_empty()
74 }
75
76 pub fn len(&self) -> usize {
78 self.ops.len()
79 }
80
81 pub fn drain_into(&mut self, dst: &mut RenderOps) {
84 dst.ops.append(&mut self.ops);
85 }
86
87 pub fn replay(&mut self, backend: &mut dyn RenderBackend) -> ReplayOutcome {
89 let mut outcome = ReplayOutcome::default();
90 for op in self.ops.drain(..) {
91 op(backend, &mut outcome);
92 }
93 outcome
94 }
95
96 pub fn clear(&mut self) {
98 self.ops.clear();
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 use alloc::vec;
107 #[test]
110 fn replay_runs_ops_in_record_order_and_drains() {
111 let mut ops = RenderOps::default();
112 for i in 0..3 {
113 ops.record_with(move |_, out| {
114 out.failures.push(OpFailure::MeshUpload { stream_id: i });
115 });
116 }
117 assert_eq!(ops.len(), 3);
118 let mut backend = crate::backend::test_stub::StubBackend;
119 let outcome = ops.replay(&mut backend);
120 let order: Vec<usize> = outcome
121 .failures
122 .iter()
123 .map(|f| match f {
124 OpFailure::MeshUpload { stream_id } => *stream_id,
125 _ => usize::MAX,
126 })
127 .collect();
128 assert_eq!(order, vec![0, 1, 2]);
129 assert!(ops.is_empty(), "replay drains the queue");
130 }
131
132 #[test]
133 fn drain_into_appends_preserving_order() {
134 let mut a = RenderOps::default();
135 let mut b = RenderOps::default();
136 a.record_with(|_, out| out.failures.push(OpFailure::MeshUpload { stream_id: 1 }));
137 b.record_with(|_, out| out.failures.push(OpFailure::MeshUpload { stream_id: 2 }));
138 a.drain_into(&mut b);
139 assert!(a.is_empty());
140 let mut backend = crate::backend::test_stub::StubBackend;
141 let outcome = b.replay(&mut backend);
142 assert_eq!(
143 outcome.failures,
144 vec![
145 OpFailure::MeshUpload { stream_id: 2 },
146 OpFailure::MeshUpload { stream_id: 1 },
147 ]
148 );
149 }
150}