oats/
bowl.rs

1use crate::oat::Oat;
2use std::{hint::spin_loop, sync::{Arc, Mutex}, time::SystemTime};
3
4/// Defines the behavior of generating new Oats
5#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
6pub enum GenerationBehavior {
7    /// This option takes the current timestamp and increments seq id until it overflows and adds one to the timestamp.
8    Lazy,
9    /// This is the default mode, it maintains the current timestamp like lazy until there are no free seq id's left, then it syncs the timestamp to the current time.
10    Normal,
11    /// As long as there is no significant time change, the seq id will be incremented. If there is a time change, the seq id is reset and the new time is used.
12    Realtime,
13}
14
15/// The WrappedBowl is a thread-safe wrapper around the Bowl.
16#[derive(Debug, Clone)]
17pub struct WrappedBowl(Arc<Mutex<Bowl>>);
18
19impl WrappedBowl {
20    /// Creates a new WrappedBowl instance with the given node id, generation behavior mode and optional epoch.
21    ///
22    /// # Arguments
23    ///
24    /// * `node` - The node id for the WrappedBowl instance.
25    /// * `mode` - The generation behavior mode for the WrappedBowl instance.
26    /// * `epoch` - An optional epoch for the WrappedBowl instance.
27    ///
28    /// # Returns
29    ///
30    /// A new WrappedBowl instance.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use oats::bowl::{GenerationBehavior, WrappedBowl};
36    /// use std::time::SystemTime;
37    ///
38    /// let wrapped_bowl = WrappedBowl::of(1, GenerationBehavior::Normal, Some(SystemTime::now()));
39    /// ```
40
41    pub fn of(node: u8, mode: GenerationBehavior, epoch: Option<SystemTime>) -> Self {
42        WrappedBowl(Arc::new(Mutex::new(Bowl::of(node, mode, epoch))))
43    }
44
45    /// Generates a new Oat value based on given parameters.
46    ///
47    /// # Returns
48    ///
49    /// A new Oat value.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use oats::bowl::{GenerationBehavior, WrappedBowl};
55    /// use std::time::SystemTime;
56    ///
57    /// let wrapped_bowl = WrappedBowl::of(1, GenerationBehavior::Normal, Some(SystemTime::now()));
58    /// let oat = wrapped_bowl.generate();
59    ///
60    /// assert_eq!(oat.node(), 1);
61    /// ```
62    pub fn generate(&self) -> Oat {
63        let node;
64        let seq;
65        let time;
66
67        {
68            let mut lock = self.0.lock().expect("Failed to get lock.");
69            node = lock.node;
70            seq = lock.new_seq();
71            time = lock.last_timestamp;
72
73            drop(lock)
74        }
75
76        Oat::of(node, seq, time)
77    }
78}
79
80/// The Bowl is used for generating Oat values in a unified way.
81#[derive(Debug, Clone, Copy)]
82pub(crate) struct Bowl {
83    mode: GenerationBehavior,
84    node: u8,
85    epoch: Option<SystemTime>,
86    current_seq: u16,    // max 12 bits (= 1,5 bytes)
87    last_timestamp: u64, // max 44 bits (= 5,5 bytes)
88}
89
90impl Bowl {
91    pub(crate) fn of(node: u8, mode: GenerationBehavior, epoch: Option<SystemTime>) -> Self {
92        Bowl {
93            mode,
94            node,
95            epoch,
96            current_seq: 0,
97            last_timestamp: get_time_millis(epoch),
98        }
99    }
100
101    pub(crate) fn new_seq(&mut self) -> u16 {
102        self.current_seq = (self.current_seq + 1) % 4096;
103        let mut now_millis = get_time_millis(self.epoch);
104
105        match self.mode {
106            GenerationBehavior::Lazy => {
107                if self.current_seq == 0 {
108                    self.last_timestamp += 1;
109                }
110            }
111            GenerationBehavior::Normal => {
112                // Maintenance `last_time_millis` for every 4096 ids generated.
113                if self.current_seq == 0 {
114                    if now_millis == self.last_timestamp {
115                        now_millis = biding_time_conditions(self.last_timestamp, self.epoch);
116                    }
117
118                    self.last_timestamp = now_millis;
119                }
120            }
121            GenerationBehavior::Realtime => {
122                // supplement code for 'clock is moving backwards situation'.
123
124                // If the milliseconds of the current clock are equal to
125                // the number of milliseconds of the most recently generated id,
126                // then check if enough 4096 are generated,
127                // if enough then busy wait until the next millisecond.
128                if now_millis == self.last_timestamp {
129                    if self.current_seq == 0 {
130                        now_millis = biding_time_conditions(self.last_timestamp, self.epoch);
131                        self.last_timestamp = now_millis;
132                    }
133                } else {
134                    self.last_timestamp = now_millis;
135                    self.current_seq = 0;
136                }
137            }
138        }
139
140        self.current_seq
141    }
142}
143
144fn get_time_millis(epoch: Option<SystemTime>) -> u64 {
145    SystemTime::now()
146        .duration_since(epoch.unwrap_or(SystemTime::UNIX_EPOCH))
147        .expect("Clock went backwards.")
148        .as_millis() as u64
149}
150
151// Constantly refreshing the latest milliseconds by busy waiting.
152fn biding_time_conditions(last_time_millis: u64, epoch: Option<SystemTime>) -> u64 {
153    let mut latest_time_millis: u64;
154    loop {
155        latest_time_millis = get_time_millis(epoch);
156        if latest_time_millis > last_time_millis {
157            return latest_time_millis;
158        }
159        spin_loop();
160    }
161}