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}