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}