spsc_ringbuf_core/
shared_singleton.rs

1
2#![allow(dead_code)]
3use core::{cell::Cell, cell::UnsafeCell};
4use core::mem::MaybeUninit;
5use core::marker::Sync;
6
7#[derive(Debug)]
8pub enum ErrCode {
9    NotOwned
10}
11
12#[derive(Copy, Clone, PartialEq)]
13enum Owner {
14    Vacant, // can be claimed for write
15    Producer,  // claimed state
16    Consumer,  // write done, passed to consumer
17}
18
19/// Single producer Single consumer Shared Singleton
20/// Note that different from RefCell, the shared singleton cannot be read until
21/// written by the producer
22/// 
23/// The inner UnsafeCell can be replaced by RefCell<T> is a much more sophisticated 
24/// implementation with checks for multiple borrows. 
25/// Here this version removes the safeguards assuming users handle the rest. The only protection
26/// is the tristate owner flag which does not allow allocating for write more than once before
27/// commit
28pub struct SharedSingleton <T> {
29    // TODO: enforce the owner field to entire word
30    owner: Cell<Owner>,
31    ucell: UnsafeCell<MaybeUninit<T>>,
32}
33
34// Delcare this is thread safe due to the owner protection
35// sequence (Producer-> consumer , consumer -> owner)
36unsafe impl <T> Sync for SharedSingleton<T> {}
37
38impl <T> SharedSingleton<T> {
39    
40    const INIT_U: UnsafeCell<MaybeUninit<T>> = UnsafeCell::new(MaybeUninit::uninit());
41    pub const INIT_0: SharedSingleton<T> = Self::new();
42
43    #[inline]
44    pub const fn new() -> Self {
45        SharedSingleton { owner: Cell::new(Owner::Vacant), ucell: Self::INIT_U  }
46    }
47
48    #[inline]
49    pub fn is_vacant(&self) -> bool {
50        self.owner.get() == Owner::Vacant
51    }
52
53    /// Returns mutable reference of T if singleton is vacant
54    #[inline]
55    pub fn try_write(&self) -> Option<&mut T> {
56        if self.owner.get() == Owner::Vacant {
57            let x: *mut MaybeUninit<T> = self.ucell.get();
58            let t: &mut T = unsafe {  &mut *(x as *mut T)};
59            self.owner.set(Owner::Producer);
60            Some(t)
61        }
62        else {
63            None
64        }
65    }
66
67    /// Pass ownership to Consumer from Producer
68    #[inline]
69    pub fn write_done(&self) -> Result<(),ErrCode> {
70        if self.owner.get() == Owner::Producer {
71            self.owner.set(Owner::Consumer);
72            Ok(())
73        }
74        else {
75            Err(ErrCode::NotOwned)
76        }
77    }
78
79    /// Returns &T is location is owned by Consumer
80    /// otherwise None
81    /// NOTE: does not check for multiple calls
82    #[inline]
83    pub fn try_read(&self) -> Option<&T> {
84        if self.owner.get() == Owner::Consumer {
85            let x: *mut MaybeUninit<T> = self.ucell.get();
86            let t: & T = unsafe {  & *(x as * const T)};
87            Some(t)
88        }
89        else {
90            None
91        }
92    }
93
94    /// Release location back to Producer
95    #[inline]
96    pub fn read_done(&self) -> Result<(),ErrCode> {
97        if self.owner.get() == Owner::Consumer {
98            self.owner.set(Owner::Vacant);
99            Ok(())
100        }
101        else {
102            Err(ErrCode::NotOwned)
103        }
104    }
105}
106
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    
112    pub struct SomeStruct {
113        id: u32,
114    }
115
116    #[test]
117    fn ownership_changes() {
118
119        let shared = SharedSingleton::<SomeStruct>::new();
120
121        if let Some(payload) = shared.try_write() {
122
123            // Can only allocate once before commit
124            assert!(shared.try_write().is_none());
125
126            payload.id = 42;
127            assert!(shared.write_done().is_ok());
128        }
129
130        // once passed to consumer, can't get mut_ref
131        assert!(shared.try_write().is_none());
132
133        assert!(shared.try_read().is_some());
134
135        assert!(shared.try_read().unwrap().id == 42);
136
137        assert!(shared.read_done().is_ok());
138
139    }
140
141}