Skip to main content

fastmetrics/raw/
atomic.rs

1use std::sync::atomic::*;
2
3use crate::raw::number::Number;
4
5/// Atomic operations for the integer or float value.
6///
7/// # Note
8///
9/// `inc_by`/`dec_by` use the underlying atomic add/sub operations.
10///
11/// For integer atomics, this means they may wrap on overflow/underflow.
12/// If you need a stronger guarantee (e.g. saturating arithmetic),
13/// use [`Atomic::update`] to implement it via a CAS loop.
14///
15/// For floating-point atomics, `inc_by`/`dec_by` are implemented via [`Atomic::update`].
16pub trait Atomic<N: Number>: Default + Send + Sync {
17    /// Increase the value by `v`.
18    fn inc_by(&self, v: N);
19
20    /// Decrease the value by `v`.
21    fn dec_by(&self, v: N);
22
23    /// Atomically updates the current value.
24    fn update<F>(&self, f: F)
25    where
26        F: FnMut(N) -> N;
27
28    /// Set the value.
29    fn set(&self, v: N);
30
31    /// Get the value.
32    fn get(&self) -> N;
33}
34
35macro_rules! impl_atomic_for_integer {
36    ($($ty:ident => $atomic:ident);* $(;)?) => ($(
37        impl Atomic<$ty> for $atomic {
38            #[inline(always)]
39            fn inc_by(&self, v: $ty) {
40                self.fetch_add(v, Ordering::Relaxed);
41            }
42
43            #[inline(always)]
44            fn dec_by(&self, v: $ty) {
45                self.fetch_sub(v, Ordering::Relaxed);
46            }
47
48            #[inline]
49            fn update<F>(&self, mut f: F)
50            where
51                F: FnMut($ty) -> $ty,
52            {
53                let _ = self.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| Some(f(old)));
54            }
55
56            #[inline(always)]
57            fn set(&self, v: $ty) {
58                self.store(v, Ordering::Relaxed);
59            }
60
61            #[inline(always)]
62            fn get(&self) -> $ty {
63                self.load(Ordering::Relaxed)
64            }
65        }
66    )*);
67}
68
69impl_atomic_for_integer! {
70    i32 => AtomicI32;
71    i64 => AtomicI64;
72    isize => AtomicIsize;
73
74    u32 => AtomicU32;
75    u64 => AtomicU64;
76    usize => AtomicUsize;
77}
78
79macro_rules! impl_atomic_for_float  {
80    ($($ty:ident => $atomic:ident);* $(;)?) => ($(
81        impl Atomic<$ty> for $atomic {
82            #[inline(always)]
83            fn inc_by(&self, v: $ty) {
84                let _ = self.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old_bits| {
85                    let old_value = $ty::from_bits(old_bits);
86                    Some($ty::to_bits(old_value + v))
87                });
88            }
89
90            #[inline(always)]
91            fn dec_by(&self, v: $ty) {
92                let _ = self.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old_bits| {
93                    let old_value = $ty::from_bits(old_bits);
94                    Some($ty::to_bits(old_value - v))
95                });
96            }
97
98            #[inline]
99            fn update<F>(&self, mut f: F)
100            where
101                F: FnMut($ty) -> $ty,
102            {
103                let _ = self.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old_bits| {
104                    let old_value = $ty::from_bits(old_bits);
105                    let new_value = f(old_value);
106                    Some($ty::to_bits(new_value))
107                });
108            }
109
110            #[inline]
111            fn set(&self, v: $ty) {
112                self.store($ty::to_bits(v), Ordering::Relaxed);
113            }
114
115            #[inline]
116            fn get(&self) -> $ty {
117                let value = self.load(Ordering::Relaxed);
118                $ty::from_bits(value)
119            }
120        }
121    )*);
122}
123
124impl_atomic_for_float! {
125    f32 => AtomicU32;
126    f64 => AtomicU64;
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_atomic_f32() {
135        let value = AtomicU32::new(0);
136
137        value.set(100f32);
138        let new: f32 = value.get();
139        assert_eq!(new, 100f32);
140
141        value.inc_by(10f32);
142        let new: f32 = value.get();
143        assert_eq!(new, 110f32);
144
145        value.dec_by(10f32);
146        let new: f32 = value.get();
147        assert_eq!(new, 100f32);
148    }
149
150    #[test]
151    fn test_atomic_f64() {
152        let value = AtomicU64::new(0);
153
154        value.set(100f64);
155        let new: f64 = value.get();
156        assert_eq!(new, 100f64);
157
158        value.inc_by(10f64);
159        let new: f64 = value.get();
160        assert_eq!(new, 110f64);
161
162        value.dec_by(10f64);
163        let new: f64 = value.get();
164        assert_eq!(new, 100f64);
165    }
166
167    #[test]
168    fn test_atomic_i32_update_saturating_overflow_underflow() {
169        let value = AtomicI32::new(0);
170
171        value.set(i32::MAX);
172        Atomic::update(&value, |old| old.saturating_add(1));
173        assert_eq!(value.get(), i32::MAX);
174
175        value.set(i32::MIN);
176        Atomic::update(&value, |old| old.saturating_sub(1));
177        assert_eq!(value.get(), i32::MIN);
178    }
179
180    #[test]
181    fn test_atomic_u64_update_saturating_overflow_underflow() {
182        let value = AtomicU64::new(0);
183
184        <AtomicU64 as Atomic<u64>>::set(&value, u64::MAX);
185        <AtomicU64 as Atomic<u64>>::update(&value, |old| old.saturating_add(1));
186        assert_eq!(<AtomicU64 as Atomic<u64>>::get(&value), u64::MAX);
187
188        <AtomicU64 as Atomic<u64>>::set(&value, 0);
189        <AtomicU64 as Atomic<u64>>::update(&value, |old| old.saturating_sub(1));
190        assert_eq!(<AtomicU64 as Atomic<u64>>::get(&value), 0);
191    }
192}