binpool/
lib.rs

1//! A uniform binary file format designed for particle physics analysis.
2//!
3//! When running particle physics simulations,
4//! it is sometimes handy to record the data and perform analysis
5//! separately from the simulation process.
6//!
7//! This binary format is designed to read and write particle
8//! physics data to files or streams, such that the tools for analysis
9//! can be reused with minimum setup.
10//!
11//! Particle physics data is similar to video or animation streams
12//! with the difference that time is arbitrarily defined.
13//! This format allows precise control over data changes,
14//! by changing the offset instance id and range.
15//!
16//! 10 built-in Rust number types are supported,
17//! using array notation, with vector and matrix dimensions up to 80x80.
18//! You can also define custom binary formats.
19//!
20//! You can repeat the same data multiple times,
21//! or write only ranges that changes.
22//! Semantics is controlled by the application,
23//! but the format is generic enough to reuse algorithms across projects.
24//!
25//!
26//! ### Format
27//!
28//! ```ignore
29//! type format == 0 => end of stream
30//! type format: u16, property id: u16
31//! |- bytes == 0 => no more data, next property
32//! |- bytes: u64, offset instance id: u64, data: [u8; bytes]
33//! ```
34//!
35//! Integers are stored in little-endian format.
36//!
37//! The number of items in the data are inferred from the number of bytes
38//! and knowledge about the type format.
39//!
40//! ### Motivation for the design
41//!
42//! A file format is uniform when it organized the same way everywhere.
43//!
44//! One benefit with a uniform format is that you can easily split
45//! data up into multiple files, or stream it across a network.
46//! It is also easy to generate while running a physics simulation.
47//!
48//! This file format assumes that the application has some
49//! form of data structure, where each particle instance
50//! is assigned a unique id, and their object relations
51//! can be derived from a property id.
52//! Since the property ids are known, one can use external tools
53//! to analyze the physical properties relative to each other,
54//! without any knowledge about the data structure.
55//!
56//! To describe time, just create a new property id, e.g. f32 scalar,
57//! and write out this value before other data in the same time step.
58//!
59//! ### Usage
60//!
61//! The traits `Scalar`, `Vector` and `Matrix` are implemented for
62//! array types of primitive integer and float formats.
63//!
64//! When you write data to a file the order is preserved.
65//!
66//! ```ignore
67//! use binpool::Scalar;
68//!
69//! let prop_id = 0; // A unique property id.
70//! let data: Vec<f32> = vec![1.0, 2.0, 3.0];
71//! Scalar::write_array(prop_id, &data, &mut file).unwrap();
72//! ```
73//!
74//! When you read from a file, e.g. to replay a recorded simulation,
75//! you often read a single frame at a time then wait for the time to read next frame.
76//! To do this, use a loop with flags for each property and break when
77//! all read flags are set.
78//!
79//! ```ignore
80//! use binpool::State;
81//!
82//! let prop_id = 0; // A unique property id.
83//! let mut read_prop_id = false;
84//! let mut data: Vec<[f32; 2]> = vec![];
85//! while let Ok((Some(state), ty, prop)) = State::read(&mut file) {
86//!     match prop {
87//!         prop_id if !read_prop_id => {
88//!             Vector::read_array(state, ty, &mut data, &mut file).unwrap();
89//!             read_prop_id = true;
90//!         }
91//!         _ => break,
92//!     }
93//! }
94//! ```
95//!
96//! Data is often stored in a struct and overwritten for each frame.
97//! The example above uses a local variable just for showing how to read data.
98
99#![deny(missing_docs)]
100
101use std::marker::PhantomData;
102use std::io;
103
104pub use read_write::{Array, Matrix, Vector, Scalar};
105
106const TYPES: u16 = 10;
107const SIZE: u16 = 80;
108
109mod read_write;
110
111/// Type format for a property.
112#[derive(Copy, Clone, PartialEq, Eq, Debug)]
113pub enum Type {
114    /// Unsigned 8 bit integer.
115    U8,
116    /// Unsigned 16 bit integer.
117    U16,
118    /// Unsigned 32 bit integer.
119    U32,
120    /// Unsigned 64 bit integer.
121    U64,
122    /// Signed 8 bit integer.
123    I8,
124    /// Signed 16 bit integer.
125    I16,
126    /// Signed 32 bit integer.
127    I32,
128    /// Signed 64 bit integer.
129    I64,
130    /// 32 bit float.
131    F32,
132    /// 64 bit float.
133    F64,
134}
135
136impl Type {
137    /// A unique number representing each type.
138    pub fn type_id(&self) -> u16 {
139        match *self {
140            Type::U8 => 0,
141            Type::U16 => 1,
142            Type::U32 => 2,
143            Type::U64 => 3,
144            Type::I8 => 4,
145            Type::I16 => 5,
146            Type::I32 => 6,
147            Type::I64 => 7,
148            Type::F32 => 8,
149            Type::F64 => 9,
150        }
151    }
152
153    /// Returns the size of type in bytes.
154    pub fn type_size(&self) -> u64 {
155        match *self {
156            Type::U8 => 1,
157            Type::U16 => 2,
158            Type::U32 => 4,
159            Type::U64 => 8,
160            Type::I8 => 1,
161            Type::I16 => 2,
162            Type::I32 => 4,
163            Type::I64 => 8,
164            Type::F32 => 4,
165            Type::F64 => 8,
166        }
167    }
168
169    /// Returns the type format and size in bytes for a matrix.
170    ///
171    /// Notice that this method uses rows and columns, not width and height.
172    ///
173    /// Returns `None` if the matrix exceed dimensions 80x80.
174    /// Returns `None` if the width or height is zero.
175    pub fn matrix(&self, rows: u8, cols: u8) -> Option<(u16, u64)> {
176        if cols == 0 || rows == 0 || cols as u16 > SIZE || rows as u16 > SIZE {
177            None
178        } else {
179            Some((
180                1 + self.type_id() * SIZE * SIZE +
181                ((rows-1) as u16) * SIZE + ((cols-1) as u16),
182                self.type_size() * cols as u64 * rows as u64
183            ))
184        }
185    }
186
187    /// Returns the type format and size in bytes for a scalar.
188    pub fn scalar(&self) -> (u16, u64) {
189        (1 + self.type_id() * SIZE * SIZE, self.type_size())
190    }
191
192    /// Returns the type format and size in bytes for a vector.
193    ///
194    /// Returns `None` if the vector exceed dimension 80.
195    /// Returns `None` if the vector has dimension zero.
196    pub fn vector(&self, dim: u8) -> Option<(u16, u64)> {
197        if dim == 0 || dim as u16 > SIZE {
198            None
199        } else {
200            Some((
201                1 + self.type_id() * SIZE * SIZE + (dim - 1) as u16,
202                self.type_size() * dim as u64
203            ))
204        }
205    }
206
207    /// Returns the offset for specifying a custom format.
208    pub fn offset_custom_format() -> u16 {
209        1 + TYPES * SIZE * SIZE
210    }
211
212    /// Returns the number of available custom formats.
213    pub fn custom_formats() -> u16 {
214        (((1 as u32) << 16) - Type::offset_custom_format() as u32) as u16
215    }
216
217    /// Returns the type and matrix dimensions from type format.
218    pub fn info(format: u16) -> Option<(Type, u8, u8)> {
219        if format == 0 || format >= Type::offset_custom_format() {
220            None
221        } else {
222            // Remove offset at 1.
223            let format = format - 1;
224            let ty = format / (SIZE * SIZE);
225            let rows = (format % (SIZE * SIZE)) / SIZE + 1;
226            let cols = format % SIZE + 1;
227            Some((match ty {
228                0 => Type::U8,
229                1 => Type::U16,
230                2 => Type::U32,
231                3 => Type::U64,
232                4 => Type::I8,
233                5 => Type::I16,
234                6 => Type::I32,
235                7 => Type::I64,
236                8 => Type::F32,
237                9 => Type::F64,
238                _ => return None,
239            }, rows as u8, cols as u8))
240        }
241    }
242}
243
244/// Type format state.
245pub struct TypeFormat;
246/// Property Id state.
247pub struct PropertyId;
248/// Bytes state.
249pub struct Bytes;
250/// Offset instance id state.
251pub struct OffsetInstanceId;
252/// Data state.
253pub struct Data;
254
255/// Stores the state for writing and reading.
256pub struct State<T = TypeFormat>(PhantomData<T>);
257
258impl State {
259    /// Creates a new state.
260    pub fn new() -> State {
261        State(PhantomData)
262    }
263
264    /// Reads type format and property.
265    ///
266    /// Returns `None` in first argument if there is no more data.
267    pub fn read<R: io::Read>(r: &mut R) -> io::Result<(Option<State<Bytes>>, u16, u16)> {
268        let mut ty: u16 = 0;
269        let mut property_id: u16 = 0;
270        let state = State::new().read_type_format(&mut ty, r)?;
271        if ty == 0 {Ok((None, 0, 0))}
272        else {
273            Ok((
274                Some(state.read_property_id(&mut property_id, r)?),
275                ty, property_id
276            ))
277        }
278    }
279
280    /// Writes type format.
281    pub fn write_type_format<W: io::Write>(
282        self,
283        type_format: u16,
284        w: &mut W
285    ) -> io::Result<State<PropertyId>> {
286        use read_write::Scalar;
287
288        type_format.write(w)?;
289        Ok(State(PhantomData))
290    }
291
292    /// Reads type format.
293    pub fn read_type_format<R: io::Read>(
294        self,
295        type_format: &mut u16,
296        r: &mut R
297    ) -> io::Result<State<PropertyId>> {
298        use read_write::Scalar;
299
300        type_format.read(r)?;
301        Ok(State(PhantomData))
302    }
303
304    /// Ends writing state.
305    pub fn end_type_formats<W: io::Write>(self, w: &mut W) -> io::Result<()> {
306        use read_write::Scalar;
307
308        (0 as u16).write(w)?;
309        Ok(())
310    }
311}
312
313impl State<PropertyId> {
314    /// Writes property id.
315    pub fn write_property_id<W: io::Write>(
316        self,
317        property_id: u16,
318        w: &mut W
319    ) -> io::Result<State<Bytes>> {
320        use read_write::Scalar;
321
322        property_id.write(w)?;
323        Ok(State(PhantomData))
324    }
325
326    /// Reads property id.
327    pub fn read_property_id<R: io::Read>(
328        self,
329        property_id: &mut u16,
330        r: &mut R
331    ) -> io::Result<State<Bytes>> {
332        use read_write::Scalar;
333
334        property_id.read(r)?;
335        Ok(State(PhantomData))
336    }
337}
338
339impl State<Bytes> {
340    /// Writes number of bytes in data.
341    pub fn write_bytes<W: io::Write>(
342        self,
343        bytes: u64,
344        w: &mut W
345    ) -> io::Result<State<OffsetInstanceId>> {
346        use read_write::Scalar;
347
348        bytes.write(w)?;
349        Ok(State(PhantomData))
350    }
351
352    /// Reads bytes.
353    pub fn read_bytes<R: io::Read>(
354        self,
355        bytes: &mut u64,
356        r: &mut R
357    ) -> io::Result<State<OffsetInstanceId>> {
358        use read_write::Scalar;
359
360        bytes.read(r)?;
361        Ok(State(PhantomData))
362    }
363
364    /// Ends byte block.
365    pub fn end_bytes<W: io::Write>(
366        self,
367        w: &mut W
368    ) -> io::Result<State<TypeFormat>> {
369        use read_write::Scalar;
370
371        (0 as u64).write(w)?;
372        Ok(State(PhantomData))
373    }
374
375    /// Checks if this is the end of bytes.
376    pub fn has_end_bytes<R: io::Read>(
377        self,
378        r: &mut R
379    ) -> io::Result<State<TypeFormat>> {
380        let mut val: u64 = 0;
381        val.read(r)?;
382        if val == 0 {
383            Ok(State(PhantomData))
384        } else {
385            Err(io::ErrorKind::InvalidData.into())
386        }
387    }
388}
389
390impl State<OffsetInstanceId> {
391    /// Writes offset instance id.
392    pub fn write_offset_instance_id<W: io::Write>(
393        self,
394        offset_instance_id: u64,
395        w: &mut W
396    ) -> io::Result<State<Data>> {
397        use read_write::Scalar;
398
399        offset_instance_id.write(w)?;
400        Ok(State(PhantomData))
401    }
402
403    /// Reads offset instance id.
404    pub fn read_offset_instance_id<R: io::Read>(
405        self,
406        offset_instance_id: &mut u64,
407        r: &mut R
408    ) -> io::Result<State<Data>> {
409        use read_write::Scalar;
410
411        offset_instance_id.read(r)?;
412        Ok(State(PhantomData))
413    }
414}
415
416impl State<Data> {
417    /// Writes data.
418    pub fn write_data<W: io::Write>(
419        self,
420        data: &[u8],
421        w: &mut W
422    ) -> io::Result<State<Data>> {
423        w.write(data)?;
424        Ok(State(PhantomData))
425    }
426
427    /// End of data.
428    pub fn end_data(self) -> State<Bytes> {
429        State(PhantomData)
430    }
431}