1use super::{ChunkId, DatasetId, LocalRow, LogicalRow, ProviderError};
4use crate::annotation::AtomAnnotation;
5use crate::chunk::AtomChunk;
6use crate::column::Presence;
7use crate::coords::CoordinateBlock;
8use crate::structure::Structure;
9use std::ops::Range;
10use std::sync::Arc;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ChunkDescriptor {
15 dataset: DatasetId,
16 chunk: ChunkId,
17 logical_start: LogicalRow,
18 rows: u32,
19}
20
21impl ChunkDescriptor {
22 pub fn new(
28 dataset: DatasetId,
29 chunk: ChunkId,
30 logical_start: LogicalRow,
31 rows: u32,
32 ) -> Result<Self, ProviderError> {
33 if rows > 0
34 && logical_start
35 .get()
36 .checked_add(u64::from(rows - 1))
37 .is_none()
38 {
39 return Err(ProviderError::IdentityOverflow {
40 dataset,
41 field: "logical row range",
42 });
43 }
44 Ok(Self {
45 dataset,
46 chunk,
47 logical_start,
48 rows,
49 })
50 }
51
52 #[must_use]
54 pub const fn dataset(self) -> DatasetId {
55 self.dataset
56 }
57
58 #[must_use]
60 pub const fn chunk(self) -> ChunkId {
61 self.chunk
62 }
63
64 #[must_use]
66 pub const fn logical_start(self) -> LogicalRow {
67 self.logical_start
68 }
69
70 #[must_use]
72 pub const fn rows(self) -> u32 {
73 self.rows
74 }
75
76 pub fn resolve(self, local: LocalRow) -> Result<LogicalRow, ProviderError> {
82 if local.get() >= self.rows {
83 return Err(ProviderError::LocalRowOutOfRange {
84 row: local.get(),
85 rows: self.rows,
86 });
87 }
88 let value = self
89 .logical_start
90 .get()
91 .checked_add(u64::from(local.get()))
92 .ok_or(ProviderError::IdentityOverflow {
93 dataset: self.dataset,
94 field: "logical row",
95 })?;
96 Ok(LogicalRow::new(value))
97 }
98}
99
100#[derive(Clone, Debug)]
102pub struct StructureChunk {
103 descriptor: ChunkDescriptor,
104 structure: Structure,
105 storage_chunk: usize,
106 coordinates: CoordinateBlock,
107 coordinate_range: Range<u32>,
108}
109
110impl StructureChunk {
111 pub fn shared(
117 descriptor: ChunkDescriptor,
118 structure: Structure,
119 storage_chunk: usize,
120 ) -> Result<Self, ProviderError> {
121 let chunk = structure.data().chunks.get(storage_chunk).ok_or(
122 ProviderError::StorageChunkUnavailable {
123 index: storage_chunk,
124 },
125 )?;
126 if chunk.len() != descriptor.rows() {
127 return Err(ProviderError::PayloadLengthMismatch {
128 declared: descriptor.rows(),
129 actual: chunk.len(),
130 });
131 }
132 let model = crate::ModelIndex::new(chunk.model());
133 let coordinates = structure.data().coords.block(model).cloned().ok_or(
134 ProviderError::ModelUnavailable {
135 model: chunk.model(),
136 },
137 )?;
138 let coordinate_range = chunk.atoms();
139 if coordinates.range(coordinate_range.clone()).is_none() {
140 return Err(ProviderError::StorageChunkUnavailable {
141 index: storage_chunk,
142 });
143 }
144 Ok(Self {
145 descriptor,
146 structure,
147 storage_chunk,
148 coordinates,
149 coordinate_range,
150 })
151 }
152
153 #[must_use]
155 pub const fn descriptor(&self) -> ChunkDescriptor {
156 self.descriptor
157 }
158
159 #[must_use]
161 pub fn atoms(&self) -> &AtomChunk {
162 &self.structure.data().chunks[self.storage_chunk]
163 }
164
165 #[must_use]
167 pub fn positions(&self) -> &[[f32; 3]] {
168 match self.coordinates.range(self.coordinate_range.clone()) {
169 Some(positions) => positions,
170 None => &[],
171 }
172 }
173
174 #[must_use]
176 pub const fn structure(&self) -> &Structure {
177 &self.structure
178 }
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum PropertyKind {
184 Boolean,
186 Integer,
188 Real,
190 Symbol,
192}
193
194#[derive(Clone, Copy, Debug, PartialEq)]
196pub enum PropertyValue {
197 Boolean(bool, Presence),
199 Integer(i64, Presence),
201 Real(f64, Presence),
203 Symbol(crate::SymbolId, Presence),
205}
206
207#[derive(Clone, Debug)]
209pub struct PropertyChunk {
210 descriptor: ChunkDescriptor,
211 structure: Structure,
212 name: Arc<str>,
213 atom_start: u32,
214 storage_chunk: usize,
215}
216
217impl PropertyChunk {
218 pub fn shared(
224 descriptor: ChunkDescriptor,
225 structure: Structure,
226 name: Arc<str>,
227 storage_chunk: usize,
228 ) -> Result<Self, ProviderError> {
229 let (atom_start, chunk_rows, property_rows, atom_count) = {
230 let chunk = structure.data().chunks.get(storage_chunk).ok_or(
231 ProviderError::StorageChunkUnavailable {
232 index: storage_chunk,
233 },
234 )?;
235 let property = structure.annotations().get(&name).ok_or_else(|| {
236 ProviderError::PropertyUnavailable {
237 name: Box::from(name.as_ref()),
238 }
239 })?;
240 (
241 chunk.atoms().start,
242 chunk.len(),
243 property.len(),
244 structure.atom_count(),
245 )
246 };
247 if chunk_rows != descriptor.rows() {
248 return Err(ProviderError::PayloadLengthMismatch {
249 declared: descriptor.rows(),
250 actual: chunk_rows,
251 });
252 }
253 if property_rows != atom_count {
254 return Err(ProviderError::PropertyLengthMismatch {
255 name: Box::from(name.as_ref()),
256 expected: atom_count,
257 actual: property_rows,
258 });
259 }
260 Ok(Self {
261 descriptor,
262 structure,
263 name,
264 atom_start,
265 storage_chunk,
266 })
267 }
268
269 #[must_use]
271 pub const fn descriptor(&self) -> ChunkDescriptor {
272 self.descriptor
273 }
274
275 #[must_use]
277 pub fn name(&self) -> &str {
278 &self.name
279 }
280
281 #[must_use]
283 pub fn kind(&self) -> Option<PropertyKind> {
284 Some(match self.property()? {
285 AtomAnnotation::Boolean(_) => PropertyKind::Boolean,
286 AtomAnnotation::Integer(_) => PropertyKind::Integer,
287 AtomAnnotation::Real(_) => PropertyKind::Real,
288 AtomAnnotation::Symbol(_) => PropertyKind::Symbol,
289 })
290 }
291
292 pub fn value(&self, local: LocalRow) -> Result<PropertyValue, ProviderError> {
298 let _ = self.descriptor.resolve(local)?;
299 let atom =
300 self.atom_start
301 .checked_add(local.get())
302 .ok_or(ProviderError::IdentityOverflow {
303 dataset: self.descriptor.dataset(),
304 field: "structure-local atom row",
305 })?;
306 let property = self
307 .property()
308 .ok_or_else(|| ProviderError::PropertyUnavailable {
309 name: Box::from(self.name.as_ref()),
310 })?;
311 match property {
312 AtomAnnotation::Boolean(column) => column
313 .get(atom)
314 .map(|(value, presence)| PropertyValue::Boolean(value, presence)),
315 AtomAnnotation::Integer(column) => column
316 .get(atom)
317 .map(|(value, presence)| PropertyValue::Integer(value, presence)),
318 AtomAnnotation::Real(column) => column
319 .get(atom)
320 .map(|(value, presence)| PropertyValue::Real(value, presence)),
321 AtomAnnotation::Symbol(column) => column
322 .get(atom)
323 .map(|(value, presence)| PropertyValue::Symbol(value, presence)),
324 }
325 .ok_or(ProviderError::LocalRowOutOfRange {
326 row: local.get(),
327 rows: self.descriptor.rows(),
328 })
329 }
330
331 #[must_use]
333 pub fn integers(&self) -> Option<&[i64]> {
334 let Some(AtomAnnotation::Integer(column)) = self.property() else {
335 return None;
336 };
337 slice_values(column.values(), self.atom_start, self.descriptor.rows())
338 }
339
340 #[must_use]
342 pub fn reals(&self) -> Option<&[f64]> {
343 let Some(AtomAnnotation::Real(column)) = self.property() else {
344 return None;
345 };
346 slice_values(column.values(), self.atom_start, self.descriptor.rows())
347 }
348
349 #[must_use]
351 pub fn bounds(&self) -> crate::Aabb {
352 match self.structure.data().chunks.get(self.storage_chunk) {
353 Some(chunk) => chunk.stats().bounds,
354 None => crate::Aabb::EMPTY,
355 }
356 }
357
358 fn property(&self) -> Option<&AtomAnnotation> {
359 self.structure.annotations().get(&self.name)
360 }
361}
362
363#[derive(Clone, Debug)]
365pub struct FrameChunk {
366 descriptor: ChunkDescriptor,
367 coordinates: CoordinateBlock,
368 range: Range<u32>,
369}
370
371impl FrameChunk {
372 pub fn shared(
378 descriptor: ChunkDescriptor,
379 coordinates: CoordinateBlock,
380 range: Range<u32>,
381 ) -> Result<Self, ProviderError> {
382 let actual =
383 range
384 .end
385 .checked_sub(range.start)
386 .ok_or(ProviderError::PayloadLengthMismatch {
387 declared: descriptor.rows(),
388 actual: 0,
389 })?;
390 if actual != descriptor.rows() || coordinates.range(range.clone()).is_none() {
391 return Err(ProviderError::PayloadLengthMismatch {
392 declared: descriptor.rows(),
393 actual,
394 });
395 }
396 Ok(Self {
397 descriptor,
398 coordinates,
399 range,
400 })
401 }
402
403 #[must_use]
405 pub const fn descriptor(&self) -> ChunkDescriptor {
406 self.descriptor
407 }
408
409 #[must_use]
411 pub fn positions(&self) -> &[[f32; 3]] {
412 match self.coordinates.range(self.range.clone()) {
413 Some(positions) => positions,
414 None => &[],
415 }
416 }
417
418 #[must_use]
420 pub const fn coordinates(&self) -> &CoordinateBlock {
421 &self.coordinates
422 }
423
424 #[must_use]
426 pub fn bounds(&self) -> crate::Aabb {
427 self.coordinates.bounds(self.range.clone())
428 }
429}
430
431fn slice_values<T>(values: &[T], start: u32, rows: u32) -> Option<&[T]> {
432 let start = usize::try_from(start).ok()?;
433 let rows = usize::try_from(rows).ok()?;
434 let end = start.checked_add(rows)?;
435 values.get(start..end)
436}
437
438#[cfg(test)]
439#[path = "payload_tests.rs"]
440mod tests;