triple_buf_64 0.1.1

A cheap lock-free triple buffer data structure that stores 64 bits of data
Documentation
use core::marker::PhantomData;

#[cfg(target_has_atomic = "64")]
use alloc::sync::Arc;
#[cfg(target_has_atomic = "64")]
use core::cell::Cell;
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::{AtomicU64, Ordering};

#[cfg(not(target_has_atomic = "64"))]
use triple_buffer::{Input, Output, TripleBuffer};

use crate::Data64Bit;

/// Returns `true` if [`triple_buffer_64`] will use atomics, or `false` if it will
/// fall back to using a triple buffer.
pub const fn is_atomic() -> bool {
    #[cfg(target_has_atomic = "64")]
    return true;
    #[cfg(not(target_has_atomic = "64"))]
    return false;
}

/// Create a new realtime-safe lock-free triple buffer type that stores 64 bits
/// of data.
///
/// On platforms which natively support 64 bit atomics, this is implemented with
/// a single cheap [`AtomicU64`](core::sync::atomic::AtomicU64) load/store operation
/// (this can optionally use [portable_atomic](https://crates.io/crates/portable-atomic)
/// for maximum compatibility).
///
/// On platforms which do NOT natively support 64 bit atomics, this is implemented
/// using a more expensive triple buffer implementation (using
/// [triple_buffer](https://crates.io/crates/triple_buffer)).
///
/// ## Data Types
///
/// This triple buffer accepts any data type that implements the [`Data64Bit`]
/// trait. Implementations for the following primitives are provided:
/// * [`u64`]
/// * [`i64`]
/// * [`f64`]
/// * [[`u32`]; 2]
/// * [[`i32`]; 2]
/// * [[`f32`]; 2]
/// * [[`u16`]; 4]
/// * [[`i16`]; 4]
/// * [[`u8`]; 8]
/// * [[`i8`]; 8]
/// * [[`bool`]; 8]
///
/// ## Example
/// ```
/// # use triple_buf_64::triple_buffer_64;
/// // Construct an output/input pair with the given 64 bit type.
/// let (mut input, mut output) = triple_buffer_64::<i64>(-3);
///
/// // The output will read the initial value.
/// assert_eq!(output.read(), -3);
/// assert_eq!(output.read(), -3);
///
/// // Write new data into the buffer.
/// input.write(-9845);
///
/// assert_eq!(output.read(), -9845);
/// assert_eq!(output.read(), -9845);
///
/// input.write(69);
/// input.write(68);
/// input.write(67);
///
/// // The output always reads the latest value that was pushed to the buffer.
/// assert_eq!(output.read(), 67);
/// assert_eq!(output.read(), 67);
/// ```
pub fn triple_buffer_64<T: Data64Bit>(val: T) -> (Input64<T>, Output64<T>) {
    #[cfg(target_has_atomic = "64")]
    {
        let val = Arc::new(AtomicU64::new(u64::from_ne_bytes(T::to_ne_bytes(val))));

        (
            Input64 {
                val: Arc::clone(&val),
                _non_sync: PhantomData,
                _data_type: PhantomData,
            },
            Output64 {
                val,
                _non_sync: PhantomData,
                _data_type: PhantomData,
            },
        )
    }

    #[cfg(not(target_has_atomic = "64"))]
    {
        let data = T::to_ne_bytes(val);

        let (input, output) = TripleBuffer::<[u8; 8]>::new(&data).split();

        (
            Input64 {
                input,
                _data_type: PhantomData,
            },
            Output64 {
                output,
                _data_type: PhantomData,
            },
        )
    }
}

/// The producer end of a simple realtime-safe lock-free triple buffer type that
/// stores 64 bits of data.
///
/// On platforms which natively support 64 bit atomics, this is implemented with
/// a single cheap [`AtomicU64`](core::sync::atomic::AtomicU64) load/store operation.
///
/// On platforms which do NOT natively support 64 bit atomics, this is implemented
/// using a more expensive triple buffer implementation (using
/// <https://crates.io/crates/triple_buffer>).
///
/// This can be useful when you want to pass a small amount of data between
/// realtime threads (i.e. audio threads) very cheaply on most platforms, but
/// also have a realtime-safe fallback for platforms which do not have 64 bit
/// atomics (i.e. PowerPC).
pub struct Input64<T: Data64Bit> {
    #[cfg(target_has_atomic = "64")]
    val: Arc<AtomicU64>,
    // Make this struct !Sync
    #[cfg(target_has_atomic = "64")]
    _non_sync: PhantomData<Cell<()>>,

    #[cfg(not(target_has_atomic = "64"))]
    input: Input<[u8; 8]>,

    _data_type: PhantomData<T>,
}

impl<T: Data64Bit> Input64<T> {
    /// Write a new value into the buffer.
    pub fn write(&mut self, val: T) {
        #[cfg(target_has_atomic = "64")]
        self.val
            .store(u64::from_ne_bytes(T::to_ne_bytes(val)), Ordering::Relaxed);

        #[cfg(not(target_has_atomic = "64"))]
        self.input.write(T::to_ne_bytes(val));
    }
}

/// The consumer end of a simple realtime-safe lock-free triple buffer type that
/// stores 64 bits of data.
///
/// On platforms which natively support 64 bit atomics, this is implemented with
/// a single cheap [`AtomicU64`](core::sync::atomic::AtomicU64) load/store operation.
///
/// On platforms which do NOT natively support 64 bit atomics, this is implemented
/// using a more expensive triple buffer implementation (using
/// <https://crates.io/crates/triple_buffer>).
///
/// This can be useful when you want to pass a small amount of data between
/// realtime threads (i.e. audio threads) very cheaply on most platforms, but
/// also have a realtime-safe fallback for platforms which do not have 64 bit
/// atomics (i.e. PowerPC).
pub struct Output64<T: Data64Bit> {
    #[cfg(target_has_atomic = "64")]
    val: Arc<AtomicU64>,
    // Make this struct !Sync
    #[cfg(target_has_atomic = "64")]
    _non_sync: PhantomData<Cell<()>>,

    #[cfg(not(target_has_atomic = "64"))]
    output: Output<[u8; 8]>,

    _data_type: PhantomData<T>,
}

impl<T: Data64Bit> Output64<T> {
    /// Read the latest value that was written to the buffer.
    pub fn read(&mut self) -> T {
        #[cfg(target_has_atomic = "64")]
        return T::from_ne_bytes(u64::to_ne_bytes(self.val.load(Ordering::Relaxed)));

        #[cfg(not(target_has_atomic = "64"))]
        return T::from_ne_bytes(*self.output.read());
    }
}