A no_std crate for a fixed-size statically allocated ring-buffer with optional serde (De)-Serialization.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use circ_buffer::RingBuffer;

fn main() {
    // Create a new buffer
    let mut circ_buffer = RingBuffer::<u8, 4>::new();

    // Add Elements
    circ_buffer.push(3);
    assert_eq!(circ_buffer.len(), 1);
    circ_buffer.push(1);
    assert_eq!(circ_buffer.len(), 2);
    circ_buffer.push(12);
    assert_eq!(circ_buffer.len(), 3);
    circ_buffer.push(212);
    assert_eq!(circ_buffer.len(), 4);

    // Now the buffer is full and we overwrite the initial element
    circ_buffer.push(122);
    assert_eq!(circ_buffer.len(), 4);
    assert_eq!(circ_buffer[0], 2);
}

Motivation

During my development of celluar_raza, I implemented various numerical solvers. One of them was the Adams-Bashforth solver for updating position and velocities of the individual agents. This solver utilizes a number of previously saved integration steps N. Since cellular_raza aims to provide flexibility in model design, the type which represents the position or velocity of the agent must be generic. Furthermore, since this solver only requires the last N steps, we can overwrite any step before that. This leads to the Ringbuffer datastructure. My main problem with existing solutions was that none provided statically-sized buffers which are very important for performance and many did not have support for serde which I am using extensively.

Design

This leads us to a datastructure which holds N elements of generic type T. For efficient access, cache locality and efficient iteration, I chose a generic array [T; N] as the underlying type. Together with two internal counters of type usize, this is a very small memory footprint. To iterate over this datastructure, I had to implement a custom type which is returned by the IntoIterator trait. Finally, implementing serde was a simply matter of deriving it.