triple_buf_64 0.1.0

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

use crate::Data64Bit;

portable_atomic::cfg_has_atomic_64! {
    use alloc::sync::Arc;
    use core::sync::atomic::Ordering;
    use core::cell::Cell;
    use portable_atomic::AtomicU64;

    /// 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>) {
        let val = Arc::new(AtomicU64::new(u64::from_ne_bytes(T::to_ne_bytes(val))));

        (
            Input64 {
                val: Arc::clone(&val),
                _data_type: PhantomData,
                _non_sync: PhantomData,
            },
            Output64 {
                val,
                _data_type: PhantomData,
                _non_sync: 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`] 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> {
        val: Arc<AtomicU64>,
        _data_type: PhantomData<T>,
        // Make this struct !Sync
        _non_sync: PhantomData<Cell<()>>,
    }

    impl<T: Data64Bit> Input64<T> {
        /// Write a new value into the buffer.
        pub fn write(&mut self, val: T) {
            self.val.store(u64::from_ne_bytes(T::to_ne_bytes(val)), Ordering::Relaxed);
        }
    }

    /// 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`] 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> {
        val: Arc<AtomicU64>,
        _data_type: PhantomData<T>,
        // Make this struct !Sync
        _non_sync: PhantomData<Cell<()>>,
    }

    impl<T: Data64Bit> Output64<T> {
        /// Read the latest value that was written to the buffer.
        pub fn read(&mut self) -> T {
            T::from_ne_bytes(u64::to_ne_bytes(self.val.load(Ordering::Relaxed)))
        }
    }
}

portable_atomic::cfg_no_atomic_64! {
    use triple_buffer::{Input, Output, TripleBuffer};

    /// 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)).
    ///
    /// 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).
    ///
    /// ## 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>) {
        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`](portable_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> {
        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) {
            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`](portable_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> {
        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 {
            T::from_ne_bytes(*self.output.read())
        }
    }
}