Skip to main content

molgfx_render/engine/
chunk_api.rs

1//! Public orchestration for caller-provided out-of-core chunks.
2
3use super::{
4    AttributeChunkWindow, BondChunkPlacement, ChunkPlacementId, ChunkPlacementStatus,
5    ChunkResidencyError, ChunkResidencyMetrics, Engine, InstanceChunkPlacement,
6    InstanceChunkWindow, PointChunkPlacement, RelationChunkPlacement, ResidentGenericChunk,
7    ResidentStructureChunk, ResidentTrajectoryChunk, StructureChunkPlacement,
8    TrajectoryChunkWindow,
9};
10use molgfx_core::{
11    ChunkData, DeviceLossReport, HostWorkingSet, HostWorkingSetError, PagedSpatialAnchor,
12    ResidencyBudget, ResidencyOutput, ResidencyRequest, ResidencyTicket,
13};
14use molgfx_gpu::Device;
15
16impl<D: Device> Engine<D> {
17    /// Replaces the bounded declarative placement set without copying coordinates.
18    ///
19    /// # Errors
20    ///
21    /// Rejects invalid transforms, duplicate identities or capacity overflow.
22    pub fn set_structure_chunk_placements(
23        &mut self,
24        placements: &[StructureChunkPlacement],
25    ) -> Result<(), ChunkResidencyError> {
26        self.chunk_residency.replace_placements(placements)
27    }
28
29    /// Reports whether one declared placement currently enters the indirect batch.
30    #[must_use]
31    pub fn structure_chunk_placement_status(&self, id: ChunkPlacementId) -> ChunkPlacementStatus {
32        self.chunk_residency.placement_status(id)
33    }
34
35    /// Replaces the bounded declarative generic point placement set.
36    ///
37    /// # Errors
38    ///
39    /// Rejects invalid transforms, duplicate identities or capacity overflow.
40    pub fn set_point_chunk_placements(
41        &mut self,
42        placements: &[PointChunkPlacement],
43    ) -> Result<(), ChunkResidencyError> {
44        self.chunk_residency.replace_point_placements(placements)
45    }
46
47    /// Reports whether one generic point placement enters the indirect batch.
48    #[must_use]
49    pub fn point_chunk_placement_status(&self, id: ChunkPlacementId) -> ChunkPlacementStatus {
50        self.chunk_residency.point_placement_status(id)
51    }
52
53    /// Replaces the bounded declarative rigid-instance placement set.
54    ///
55    /// # Errors
56    ///
57    /// Rejects duplicate identities or capacity overflow.
58    pub fn set_instance_chunk_placements(
59        &mut self,
60        placements: &[InstanceChunkPlacement],
61    ) -> Result<(), ChunkResidencyError> {
62        self.chunk_residency.replace_instance_placements(placements)
63    }
64
65    /// Reports whether one rigid-instance placement enters its analytic batch.
66    #[must_use]
67    pub fn instance_chunk_placement_status(&self, id: ChunkPlacementId) -> ChunkPlacementStatus {
68        self.chunk_residency.instance_placement_status(id)
69    }
70
71    /// Replaces the bounded two-frame windows sampled by paged instances.
72    ///
73    /// The CPU only updates this compact table; culling and rendering sample
74    /// exact resident transform pages directly on the GPU.
75    ///
76    /// # Errors
77    ///
78    /// Rejects duplicate occurrence windows or capacity overflow atomically.
79    pub fn set_instance_chunk_windows(
80        &mut self,
81        windows: &[InstanceChunkWindow],
82    ) -> Result<(), ChunkResidencyError> {
83        self.chunk_residency.replace_instance_windows(windows)
84    }
85
86    /// Replaces two-frame windows for scene-independent visual columns.
87    ///
88    /// # Errors
89    ///
90    /// Rejects duplicate stable columns or capacity overflow atomically.
91    pub fn set_attribute_chunk_windows(
92        &mut self,
93        windows: &[AttributeChunkWindow],
94    ) -> Result<(), ChunkResidencyError> {
95        self.chunk_residency.replace_attribute_windows(windows)
96    }
97
98    /// Replaces the bounded set of globally anchored relation occurrences.
99    ///
100    /// # Errors
101    ///
102    /// Rejects duplicate identities or capacity overflow atomically.
103    pub fn set_relation_chunk_placements(
104        &mut self,
105        placements: &[RelationChunkPlacement],
106    ) -> Result<(), ChunkResidencyError> {
107        self.chunk_residency.replace_relation_placements(placements)
108    }
109
110    /// Reports whether a relation payload and all exact anchor occurrences are resident.
111    #[must_use]
112    pub fn relation_chunk_placement_status(&self, id: ChunkPlacementId) -> ChunkPlacementStatus {
113        self.chunk_residency.relation_placement_status(id)
114    }
115
116    /// Reports whether one globally identified spatial anchor currently maps
117    /// to an exact resident occurrence and chunk-local GPU row.
118    #[must_use]
119    pub fn paged_spatial_anchor_is_resident(&self, anchor: PagedSpatialAnchor) -> bool {
120        self.chunk_residency
121            .resolve_spatial_anchor(anchor)
122            .is_some()
123    }
124
125    /// Replaces the bounded declarative analytic bond placement set.
126    ///
127    /// # Errors
128    ///
129    /// Rejects invalid radii/transforms, duplicate identities or capacity overflow.
130    pub fn set_bond_chunk_placements(
131        &mut self,
132        placements: &[BondChunkPlacement],
133    ) -> Result<(), ChunkResidencyError> {
134        self.chunk_residency.replace_bond_placements(placements)
135    }
136
137    /// Replaces the bounded two-frame trajectory windows.
138    ///
139    /// Windows become active only when their exact structure and both exact
140    /// provider-frame generations are resident. Updating interpolation changes
141    /// only the compact window table on the CPU.
142    ///
143    /// # Errors
144    ///
145    /// Rejects duplicate structure windows or capacity overflow.
146    pub fn set_trajectory_chunk_windows(
147        &mut self,
148        windows: &[TrajectoryChunkWindow],
149    ) -> Result<(), ChunkResidencyError> {
150        self.chunk_residency.replace_trajectory_windows(windows)
151    }
152
153    /// Reports whether a declared bond generation and all atom endpoints are resident.
154    #[must_use]
155    pub fn bond_chunk_placement_status(&self, id: ChunkPlacementId) -> ChunkPlacementStatus {
156        self.chunk_residency.bond_placement_status(id)
157    }
158
159    /// CPU payloads retained by the engine's bounded working set.
160    #[must_use]
161    pub const fn host_working_set(&self) -> &HostWorkingSet {
162        &self.host_working_set
163    }
164
165    /// Emits one caller-owned provider request.
166    ///
167    /// # Errors
168    ///
169    /// Returns typed generation or in-flight budget errors.
170    pub fn request_chunk_into(
171        &mut self,
172        request: ResidencyRequest,
173        output: &mut ResidencyOutput,
174    ) -> Result<ResidencyTicket, ChunkResidencyError> {
175        let ticket = self.host_working_set.request_into(request, output)?;
176        self.chunk_residency.apply(output)?;
177        Ok(ticket)
178    }
179
180    /// Accepts an exact provider payload without reimplementing or copying it.
181    ///
182    /// # Errors
183    ///
184    /// Rejects stale tickets, identity mismatches and CPU pressure.
185    pub fn deliver_chunk_into(
186        &mut self,
187        ticket: ResidencyTicket,
188        payload: ChunkData,
189        output: &mut ResidencyOutput,
190    ) -> Result<(), ChunkResidencyError> {
191        self.host_working_set
192            .deliver_into(ticket, payload, output)?;
193        self.chunk_residency.apply(output)
194    }
195
196    /// Explicitly stages and submits one provider-backed structure, bond or
197    /// topology-aligned trajectory-frame chunk.
198    ///
199    /// `Resident` is not published until [`Self::poll_chunk_uploads_into`]
200    /// observes the backend completion signal.
201    ///
202    /// # Errors
203    ///
204    /// Returns typed host lifecycle, arena, staging, capacity or GPU errors.
205    pub fn upload_chunk_into(
206        &mut self,
207        ticket: ResidencyTicket,
208        output: &mut ResidencyOutput,
209    ) -> Result<(), ChunkResidencyError> {
210        self.host_working_set.begin_upload_into(ticket, output)?;
211        let staged = match self.host_working_set.payload(ticket.key) {
212            Some(payload) => self
213                .chunk_residency
214                .stage(&self.device, &self.queue, ticket, payload),
215            None => Err(ChunkResidencyError::PayloadMissing),
216        };
217        if let Err(error) = staged {
218            self.host_working_set.defer_upload_into(ticket, output)?;
219            self.chunk_residency.apply(output)?;
220            return Err(error);
221        }
222        // Staging only filled the host ring. One device epoch carries every
223        // chunk staged since the last flush, so this is where the bytes
224        // actually reach the arena — once per upload, not once per segment.
225        self.chunk_residency.flush(&self.device, &self.queue)?;
226        Ok(())
227    }
228
229    /// Polls backend completion and promotes only signalled uploads.
230    ///
231    /// # Errors
232    ///
233    /// Device loss and current-generation lifecycle failures are returned.
234    pub fn poll_chunk_uploads_into(
235        &mut self,
236        output: &mut ResidencyOutput,
237    ) -> Result<(), ChunkResidencyError> {
238        // A frame that delivered chunks without uploading them still needs its
239        // epoch flushed before completion can be observed.
240        self.chunk_residency.flush(&self.device, &self.queue)?;
241        self.chunk_residency.poll(&self.device, &self.queue)?;
242        let completed = self.chunk_residency.completed().len();
243        for index in 0..completed {
244            let ticket = self.chunk_residency.completed()[index];
245            match self.host_working_set.complete_upload_into(ticket, output) {
246                Ok(()) => self.chunk_residency.apply(output)?,
247                Err(HostWorkingSetError::StaleCompletion(_)) => {
248                    self.chunk_residency.discard(ticket)?;
249                }
250                Err(error) => {
251                    self.chunk_residency.discard(ticket)?;
252                    self.chunk_residency.apply(output)?;
253                    return Err(error.into());
254                }
255            }
256        }
257        Ok(())
258    }
259
260    /// Cancels one generation and retires submitted bytes only after its fence.
261    ///
262    /// # Errors
263    ///
264    /// Rejects stale cancellation tickets.
265    pub fn cancel_chunk_into(
266        &mut self,
267        ticket: ResidencyTicket,
268        output: &mut ResidencyOutput,
269    ) -> Result<(), ChunkResidencyError> {
270        self.host_working_set.cancel_into(ticket, output)?;
271        self.chunk_residency.apply(output)
272    }
273
274    /// Applies hard budgets and releases evicted arena pages.
275    ///
276    /// # Errors
277    ///
278    /// Returns a stale physical allocation error if ownership diverged.
279    pub fn set_chunk_budget_into(
280        &mut self,
281        budget: ResidencyBudget,
282        output: &mut ResidencyOutput,
283    ) -> Result<(), ChunkResidencyError> {
284        self.host_working_set.set_budget_into(budget, output);
285        self.chunk_residency.apply(output)
286    }
287
288    /// Invalidates GPU allocations and preserves provider storage for re-upload.
289    ///
290    /// # Errors
291    ///
292    /// Returns generation exhaustion or replacement allocation failure.
293    pub fn chunk_device_lost_into(
294        &mut self,
295        output: &mut ResidencyOutput,
296    ) -> Result<DeviceLossReport, ChunkResidencyError> {
297        let report = self.host_working_set.device_lost_into(output)?;
298        self.chunk_residency.reset(&self.device)?;
299        Ok(report)
300    }
301
302    /// Resolves one global ticket to its chunk-local GPU range.
303    #[must_use]
304    pub fn resident_structure_chunk(
305        &self,
306        ticket: ResidencyTicket,
307    ) -> Option<ResidentStructureChunk> {
308        self.chunk_residency.resident(ticket)
309    }
310
311    /// Resolves one exact provider-frame ticket to its bounded GPU range.
312    #[must_use]
313    pub fn resident_trajectory_chunk(
314        &self,
315        ticket: ResidencyTicket,
316    ) -> Option<ResidentTrajectoryChunk> {
317        self.chunk_residency.resident_frame(ticket)
318    }
319
320    /// Resolves one exact generic payload generation to its shared arena range.
321    #[must_use]
322    pub fn resident_generic_chunk(&self, ticket: ResidencyTicket) -> Option<ResidentGenericChunk> {
323        self.chunk_residency.resident_generic(ticket)
324    }
325
326    /// Real bounded-storage counters for chunk residency.
327    #[must_use]
328    pub fn chunk_residency_metrics(&self) -> ChunkResidencyMetrics {
329        self.chunk_residency.metrics()
330    }
331}