Skip to main content

spice/core/
cell.rs

1/*!
2SPICE cells, the toolkit's own dynamic arrays.
3
4## Description
5
6A cell is a fixed capacity array with a control area that CSPICE maintains itself. Routines such as
7[`dskobj`][crate::raw::dskobj] or [`spkcov`][crate::raw::spkcov] report their results by *appending*
8to one, which is why they take a cell rather than returning a vector.
9
10[`Cell`] owns its backing storage, so it is created, grown into by CSPICE and freed like any other
11Rust value:
12
13```no_run
14# #[cfg(not(feature = "lock"))]
15# {
16let mut ids = spice::Cell::<i32>::new(64);
17spice::raw::spkobj("/path/to/kernel.bsp", &mut ids);
18
19for id in ids.iter() {
20    println!("{}", spice::bodc2n(id).0);
21}
22# }
23```
24
25See the [C documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/cells.html).
26*/
27
28use crate::c::{
29    _SpiceDataType_SPICE_BOOL, _SpiceDataType_SPICE_CHR, _SpiceDataType_SPICE_DP,
30    _SpiceDataType_SPICE_INT, _SpiceDataType_SPICE_TIME, SpiceCell, SpiceCellDataType, SpiceChar,
31    SpiceDouble, SpiceInt,
32};
33use crate::core::ffi::{from_cbuf, to_cstring, CELL_CTRLSZ};
34use std::fmt;
35use std::marker::PhantomData;
36use std::ops::Deref;
37
38/// Default length of the slots of a character cell.
39pub const CELL_MAX_LEN: usize = crate::MAX_LEN_OUT;
40
41/// Default capacity used by the wrappers that allocate a cell on the caller's behalf.
42pub const CELL_MAXID: usize = 10_000;
43
44/**
45An element a [`Cell`] can hold.
46
47Implemented for the element types the CSPICE C API can actually read and write: [`i32`], [`f64`]
48and [`String`]. Boolean cells exist in the toolkit's type enumeration but no C routine operates on
49one, so [`Cell::new_bool`] hands back an integer cell tagged as boolean.
50*/
51pub trait CellItem: Sized {
52    /// The CSPICE data type of a cell holding `Self`.
53    const DTYPE: SpiceCellDataType;
54
55    /// How `Self` is stored in the backing buffer.
56    type Raw: Copy + Default;
57
58    /// Read one element out of its slot; `slot` is `length` items long for a character cell.
59    fn read(slot: &[Self::Raw]) -> Self;
60
61    /// Append one element, through the CSPICE routine that knows how to update the control area.
62    fn append(cell: &mut Cell<Self>, item: Self);
63}
64
65/**
66A CSPICE cell holding elements of type `T`.
67
68The backing storage is owned, and released when the cell is dropped.
69*/
70pub struct Cell<T: CellItem> {
71    /// The descriptor handed to CSPICE; its `base` and `data` point into `buf`.
72    raw: SpiceCell,
73    /// The control area followed by the `size` element slots.
74    buf: Vec<T::Raw>,
75    /// Number of `T::Raw` per element: the slot length for characters, one otherwise.
76    elem: usize,
77    _marker: PhantomData<fn() -> T>,
78}
79
80impl<T: CellItem> Cell<T> {
81    /**
82    Allocate a cell able to hold `size` elements.
83
84    Character cells get slots of [`CELL_MAX_LEN`] bytes; use [`Cell::with_length`] to choose.
85    */
86    pub fn new(size: usize) -> Self {
87        Self::with_length(size, CELL_MAX_LEN)
88    }
89
90    /**
91    Allocate a cell able to hold `size` elements whose slots are `length` items long.
92
93    `length` is only meaningful for character cells, where it is the maximum length of an element.
94    */
95    pub fn with_length(size: usize, length: usize) -> Self {
96        Self::build(T::DTYPE, size, length)
97    }
98
99    /// Allocate a cell of an explicit CSPICE data type, for the types with no Rust counterpart.
100    fn build(dtype: SpiceCellDataType, size: usize, length: usize) -> Self {
101        let character = dtype == _SpiceDataType_SPICE_CHR;
102        let elem = if character { length.max(1) } else { 1 };
103
104        let mut buf = vec![T::Raw::default(); (CELL_CTRLSZ + size) * elem];
105        let base = buf.as_mut_ptr();
106
107        Self {
108            raw: SpiceCell {
109                dtype,
110                length: if character { elem as SpiceInt } else { 0 },
111                size: size as SpiceInt,
112                card: 0,
113                isSet: 1,
114                adjust: 0,
115                init: 0,
116                base: base.cast(),
117                // SAFETY: `buf` holds at least `CELL_CTRLSZ * elem` items, so this is in bounds.
118                data: unsafe { base.add(CELL_CTRLSZ * elem) }.cast(),
119            },
120            buf,
121            elem,
122            _marker: PhantomData,
123        }
124    }
125
126    /// Number of elements currently held, the *cardinality* in SPICE terms.
127    pub fn len(&self) -> usize {
128        self.raw.card.max(0) as usize
129    }
130
131    /// Whether the cell holds no element.
132    pub fn is_empty(&self) -> bool {
133        self.len() == 0
134    }
135
136    /// Number of elements the cell can hold.
137    pub fn capacity(&self) -> usize {
138        self.raw.size.max(0) as usize
139    }
140
141    /// The element at `index`, or `None` past the cardinality.
142    pub fn get(&self, index: usize) -> Option<T> {
143        if index >= self.len() {
144            return None;
145        }
146        let start = (CELL_CTRLSZ + index) * self.elem;
147        Some(T::read(&self.buf[start..start + self.elem]))
148    }
149
150    /// Iterate over the elements currently held.
151    pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
152        (0..self.len()).filter_map(move |index| self.get(index))
153    }
154
155    /// Collect the elements currently held.
156    pub fn to_vec(&self) -> Vec<T> {
157        self.iter().collect()
158    }
159
160    /// Append an element, provided the cell is not full.
161    pub fn push(&mut self, item: T) {
162        T::append(self, item);
163    }
164
165    /// Drop every element, keeping the allocation.
166    pub fn clear(&mut self) {
167        let cell = self.as_mut_ptr();
168        unsafe { crate::c::scard_c(0, cell) };
169    }
170
171    /// The cell descriptor, for calls to the [unsafe C API][crate::c].
172    pub fn as_ptr(&self) -> *const SpiceCell {
173        &self.raw
174    }
175
176    /**
177    The cell descriptor, for calls to the [unsafe C API][crate::c].
178
179    The `base` and `data` pointers are refreshed from the backing buffer on every call, so the
180    descriptor handed out is always the one CSPICE should write through.
181    */
182    pub fn as_mut_ptr(&mut self) -> *mut SpiceCell {
183        let base = self.buf.as_mut_ptr();
184        self.raw.base = base.cast();
185        // SAFETY: `buf` holds at least `CELL_CTRLSZ * elem` items, so this is in bounds.
186        self.raw.data = unsafe { base.add(CELL_CTRLSZ * self.elem) }.cast();
187        &mut self.raw
188    }
189}
190
191impl Cell<i32> {
192    /// Allocate an integer cell able to hold `size` elements.
193    pub fn new_int(size: i32) -> Self {
194        Self::new(size.max(0) as usize)
195    }
196
197    /// Allocate a boolean cell able to hold `size` elements.
198    pub fn new_bool(size: i32) -> Self {
199        Self::build(_SpiceDataType_SPICE_BOOL, size.max(0) as usize, 0)
200    }
201
202    /// The element at `index`, or `0` past the cardinality.
203    pub fn get_data_int(&self, index: usize) -> i32 {
204        self.get(index).unwrap_or_default()
205    }
206
207    /// The element at `index` as a boolean flag, or `0` past the cardinality.
208    pub fn get_data_bool(&self, index: usize) -> i32 {
209        self.get_data_int(index)
210    }
211}
212
213impl Cell<f64> {
214    /// Allocate a double precision cell able to hold `size` elements.
215    pub fn new_double(size: i32) -> Self {
216        Self::new(size.max(0) as usize)
217    }
218
219    /// Allocate a time cell able to hold `size` elements.
220    pub fn new_time(size: i32) -> Self {
221        Self::build(_SpiceDataType_SPICE_TIME, size.max(0) as usize, 0)
222    }
223
224    /// The element at `index`, or `0.0` past the cardinality.
225    pub fn get_data_double(&self, index: usize) -> f64 {
226        self.get(index).unwrap_or_default()
227    }
228}
229
230impl Cell<String> {
231    /// Allocate a character cell able to hold `size` elements of at most `length` bytes.
232    pub fn new_character(size: i32, length: i32) -> Self {
233        Self::with_length(size.max(0) as usize, length.max(1) as usize)
234    }
235
236    /// The element at `index`, or the empty string past the cardinality.
237    pub fn get_data_character(&self, index: usize) -> String {
238        self.get(index).unwrap_or_default()
239    }
240}
241
242impl CellItem for i32 {
243    const DTYPE: SpiceCellDataType = _SpiceDataType_SPICE_INT;
244    type Raw = SpiceInt;
245
246    fn read(slot: &[Self::Raw]) -> Self {
247        slot[0]
248    }
249
250    fn append(cell: &mut Cell<Self>, item: Self) {
251        let raw = cell.as_mut_ptr();
252        unsafe { crate::c::appndi_c(item, raw) };
253    }
254}
255
256impl CellItem for f64 {
257    const DTYPE: SpiceCellDataType = _SpiceDataType_SPICE_DP;
258    type Raw = SpiceDouble;
259
260    fn read(slot: &[Self::Raw]) -> Self {
261        slot[0]
262    }
263
264    fn append(cell: &mut Cell<Self>, item: Self) {
265        let raw = cell.as_mut_ptr();
266        unsafe { crate::c::appndd_c(item, raw) };
267    }
268}
269
270impl CellItem for String {
271    const DTYPE: SpiceCellDataType = _SpiceDataType_SPICE_CHR;
272    type Raw = SpiceChar;
273
274    fn read(slot: &[Self::Raw]) -> Self {
275        from_cbuf(slot)
276    }
277
278    fn append(cell: &mut Cell<Self>, item: String) {
279        let item = to_cstring(item);
280        let raw = cell.as_mut_ptr();
281        unsafe { crate::c::appndc_c(item.as_ptr() as *mut SpiceChar, raw) };
282    }
283}
284
285/// Read only access to the raw descriptor, for the `card` and `size` fields CSPICE maintains.
286impl<T: CellItem> Deref for Cell<T> {
287    type Target = SpiceCell;
288
289    fn deref(&self) -> &Self::Target {
290        &self.raw
291    }
292}
293
294impl<T: CellItem> Clone for Cell<T> {
295    fn clone(&self) -> Self {
296        let mut clone = Self {
297            raw: self.raw,
298            buf: self.buf.clone(),
299            elem: self.elem,
300            _marker: PhantomData,
301        };
302        // Point the copied descriptor at the copied buffer rather than at the original one.
303        clone.as_mut_ptr();
304        clone
305    }
306}
307
308impl<T: CellItem + fmt::Debug> fmt::Debug for Cell<T> {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        f.debug_struct("Cell")
311            .field("card", &self.len())
312            .field("size", &self.capacity())
313            .field("items", &self.to_vec())
314            .finish()
315    }
316}
317
318// SAFETY: the descriptor only points into the owned buffer, which moves with the cell.
319unsafe impl<T: CellItem + Send> Send for Cell<T> {}