merge_machines 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +57 -0
  3. data/Cargo.lock +294 -0
  4. data/Cargo.toml +24 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +252 -0
  7. data/ext/merge_machines_native/core/Cargo.toml +24 -0
  8. data/ext/merge_machines_native/core/src/gcounter.rs +242 -0
  9. data/ext/merge_machines_native/core/src/gset.rs +181 -0
  10. data/ext/merge_machines_native/core/src/hybrid.rs +222 -0
  11. data/ext/merge_machines_native/core/src/lib.rs +221 -0
  12. data/ext/merge_machines_native/core/src/logical.rs +139 -0
  13. data/ext/merge_machines_native/core/src/lww.rs +213 -0
  14. data/ext/merge_machines_native/core/src/orset.rs +334 -0
  15. data/ext/merge_machines_native/core/src/pncounter.rs +165 -0
  16. data/ext/merge_machines_native/extconf.rb +3 -0
  17. data/ext/merge_machines_native/ffi/Cargo.toml +20 -0
  18. data/ext/merge_machines_native/ffi/extconf.rb +21 -0
  19. data/ext/merge_machines_native/ffi/src/data.rs +44 -0
  20. data/ext/merge_machines_native/ffi/src/gcounter.rs +138 -0
  21. data/ext/merge_machines_native/ffi/src/gset.rs +187 -0
  22. data/ext/merge_machines_native/ffi/src/hash.rs +33 -0
  23. data/ext/merge_machines_native/ffi/src/hybrid.rs +110 -0
  24. data/ext/merge_machines_native/ffi/src/lib.rs +221 -0
  25. data/ext/merge_machines_native/ffi/src/logical.rs +89 -0
  26. data/ext/merge_machines_native/ffi/src/lww.rs +360 -0
  27. data/ext/merge_machines_native/ffi/src/names.rs +82 -0
  28. data/ext/merge_machines_native/ffi/src/orset.rs +240 -0
  29. data/ext/merge_machines_native/ffi/src/pncounter.rs +143 -0
  30. data/ext/merge_machines_native/ffi/src/shape.rs +116 -0
  31. data/ext/merge_machines_native/ffi/src/wall.rs +37 -0
  32. data/lib/merge_machines/version.rb +5 -0
  33. data/lib/merge_machines.rb +8 -0
  34. data/sig/merge_machines.rbs +238 -0
  35. metadata +98 -0
@@ -0,0 +1,242 @@
1
+ //! A counter that only goes up, held as one count per replica.
2
+
3
+ #[cfg(feature = "alloc")]
4
+ use crate::boxed_frame;
5
+ use crate::{
6
+ COUNT_LEN, CapacityError, CodecError, FIELD_LEN, Merge, ReplicaId, SHAPE_LEN, TAG,
7
+ TAG_GCOUNTER, TAG_LEN, free_slot, read_small, read_u64, small_field, tagged_frame, write_into,
8
+ };
9
+
10
+ /// Bytes one slot record occupies: the replica id then its count.
11
+ const RECORD_LEN: usize = 2 * FIELD_LEN;
12
+
13
+ /// Where the replica id sits in a slot record.
14
+ const REPLICA: core::ops::Range<usize> = 0..FIELD_LEN;
15
+
16
+ /// Where the count sits in a slot record.
17
+ const COUNT: core::ops::Range<usize> = FIELD_LEN..RECORD_LEN;
18
+
19
+ /// Where the number of records carrying a slot sits in a body.
20
+ const HELD: core::ops::Range<usize> = 0..COUNT_LEN;
21
+
22
+ /// Where the slot records start in a body.
23
+ const RECORDS: usize = HELD.end;
24
+
25
+ /// Where the shape sits in a frame.
26
+ const SHAPE: core::ops::Range<usize> = TAG_LEN..TAG_LEN + SHAPE_LEN;
27
+
28
+ /// Where the body starts in a frame.
29
+ const BODY: usize = SHAPE.end;
30
+
31
+ /// One replica's contribution to a [`GCounter`].
32
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
33
+ pub struct Entry {
34
+ /// The replica that owns this count.
35
+ pub replica: ReplicaId,
36
+ /// How much that replica has added.
37
+ pub count: u64,
38
+ }
39
+
40
+ /// A counter that only grows, counted separately by each replica.
41
+ ///
42
+ /// ```
43
+ /// use merge_machines::{GCounter, Merge};
44
+ ///
45
+ /// let mut left = GCounter::<4>::new();
46
+ /// let mut right = GCounter::<4>::new();
47
+ ///
48
+ /// assert_eq!(left.increment(1, 3), Ok(()));
49
+ /// assert_eq!(right.increment(2, 7), Ok(()));
50
+ ///
51
+ /// assert_eq!(left.merge(&right), Ok(()));
52
+ /// assert_eq!(left.value(), 10);
53
+ ///
54
+ /// // Merging the same state again adds nothing.
55
+ /// assert_eq!(left.merge(&right), Ok(()));
56
+ /// assert_eq!(left.value(), 10);
57
+ /// ```
58
+ #[derive(Debug, Clone, Copy, Eq)]
59
+ pub struct GCounter<const R: usize> {
60
+ slots: [Option<Entry>; R],
61
+ }
62
+
63
+ impl<const R: usize> GCounter<R> {
64
+ #[must_use]
65
+ pub const fn new() -> Self {
66
+ Self { slots: [None; R] }
67
+ }
68
+
69
+ /// Adds `by` to `replica`'s own count, saturating at [`u64::MAX`].
70
+ ///
71
+ /// # Errors
72
+ ///
73
+ /// Returns [`CapacityError`] when `replica` has no slot yet and all `R` are
74
+ /// taken.
75
+ pub fn increment(&mut self, replica: ReplicaId, by: u64) -> Result<(), CapacityError> {
76
+ let entry = self.entry_mut(replica).ok_or(CapacityError)?;
77
+ entry.count = entry.count.saturating_add(by);
78
+ Ok(())
79
+ }
80
+
81
+ /// What `replica` has added, zero if it has no slot here.
82
+ #[must_use]
83
+ pub fn count(&self, replica: ReplicaId) -> u64 {
84
+ self.slots
85
+ .iter()
86
+ .flatten()
87
+ .find(|entry| entry.replica == replica)
88
+ .map_or(0, |entry| entry.count)
89
+ }
90
+
91
+ /// Every replica's count, totalled wide enough never to clamp.
92
+ #[must_use]
93
+ pub fn value(&self) -> u128 {
94
+ self.entries().map(|entry| u128::from(entry.count)).sum()
95
+ }
96
+
97
+ /// The per-replica counts, in no particular order.
98
+ pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
99
+ self.slots.iter().flatten().copied()
100
+ }
101
+
102
+ /// Raises `replica`'s count to `count` if that is higher.
103
+ fn raise(&mut self, replica: ReplicaId, count: u64) -> Result<(), CapacityError> {
104
+ let entry = self.entry_mut(replica).ok_or(CapacityError)?;
105
+ if count > entry.count {
106
+ entry.count = count;
107
+ }
108
+ Ok(())
109
+ }
110
+
111
+ /// Length of the [`encode`](Self::encode) frame.
112
+ pub const ENCODED_LEN: usize = BODY + Self::BODY_LEN;
113
+
114
+ /// Length of the slot table a frame carries after its tag and shape.
115
+ pub(crate) const BODY_LEN: usize = RECORDS + R * RECORD_LEN;
116
+
117
+ /// Writes the counter into `out`, returning the number of bytes written.
118
+ ///
119
+ /// # Errors
120
+ ///
121
+ /// [`CodecError::Truncated`] when `out` is shorter than
122
+ /// [`ENCODED_LEN`](Self::ENCODED_LEN).
123
+ pub fn encode(&self, out: &mut [u8]) -> Result<usize, CodecError> {
124
+ write_into(out, Self::ENCODED_LEN, |frame| self.write_frame(frame))
125
+ }
126
+
127
+ /// The counter's frame, exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
128
+ #[cfg(feature = "alloc")]
129
+ #[must_use]
130
+ pub fn encoded(&self) -> alloc::boxed::Box<[u8]> {
131
+ boxed_frame(Self::ENCODED_LEN, |frame| self.write_frame(frame))
132
+ }
133
+
134
+ /// Fills a frame of exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
135
+ fn write_frame(&self, frame: &mut [u8]) {
136
+ frame[TAG] = TAG_GCOUNTER;
137
+ frame[SHAPE].copy_from_slice(&small_field(R));
138
+ self.write_body(&mut frame[BODY..]);
139
+ }
140
+
141
+ /// Fills a slot table of exactly [`BODY_LEN`](Self::BODY_LEN) bytes.
142
+ pub(crate) fn write_body(&self, body: &mut [u8]) {
143
+ body.fill(0);
144
+ body[HELD].copy_from_slice(&small_field(self.entries().count()));
145
+
146
+ let (records, _) = body[RECORDS..].as_chunks_mut::<RECORD_LEN>();
147
+ for (record, entry) in records.iter_mut().zip(self.entries()) {
148
+ record[REPLICA].copy_from_slice(&entry.replica.to_le_bytes());
149
+ record[COUNT].copy_from_slice(&entry.count.to_le_bytes());
150
+ }
151
+ }
152
+
153
+ /// Reads a counter written by [`encode`](Self::encode).
154
+ ///
155
+ /// # Errors
156
+ ///
157
+ /// [`CodecError::Truncated`] when `bytes` is too short, [`CodecError::Tag`]
158
+ /// when it holds another structure, [`CodecError::Parameters`] when it was
159
+ /// written with another replica count.
160
+ pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
161
+ let mut counter = Self::new();
162
+ counter.decode_into(bytes)?;
163
+ Ok(counter)
164
+ }
165
+
166
+ /// Reads a frame over a counter the caller already holds.
167
+ ///
168
+ /// The counter is untouched when the frame is rejected.
169
+ ///
170
+ /// # Errors
171
+ ///
172
+ /// The same as [`decode`](Self::decode).
173
+ pub fn decode_into(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
174
+ let frame = tagged_frame(bytes, Self::ENCODED_LEN, TAG_GCOUNTER)?;
175
+ if read_small(&frame[SHAPE]) != R {
176
+ return Err(CodecError::Parameters);
177
+ }
178
+
179
+ self.read_body(&frame[BODY..]);
180
+ Ok(())
181
+ }
182
+
183
+ /// Reads a slot table of exactly [`BODY_LEN`](Self::BODY_LEN) bytes.
184
+ pub(crate) fn read_body(&mut self, body: &[u8]) {
185
+ let held = read_small(&body[HELD]);
186
+ let (records, _) = body[RECORDS..].as_chunks::<RECORD_LEN>();
187
+
188
+ self.slots = [None; R];
189
+ for (slot, record) in self.slots.iter_mut().zip(records.iter().take(held)) {
190
+ *slot = Some(Entry {
191
+ replica: read_u64(&record[REPLICA]),
192
+ count: read_u64(&record[COUNT]),
193
+ });
194
+ }
195
+ }
196
+
197
+ fn entry_mut(&mut self, replica: ReplicaId) -> Option<&mut Entry> {
198
+ let held = self
199
+ .slots
200
+ .iter()
201
+ .position(|slot| matches!(slot, Some(entry) if entry.replica == replica));
202
+ if let Some(index) = held {
203
+ return self.slots[index].as_mut();
204
+ }
205
+
206
+ let free = free_slot(&self.slots)?;
207
+ self.slots[free] = Some(Entry { replica, count: 0 });
208
+ self.slots[free].as_mut()
209
+ }
210
+ }
211
+
212
+ impl<const R: usize> Merge for GCounter<R> {
213
+ type Error = CapacityError;
214
+
215
+ fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
216
+ for entry in other.entries() {
217
+ self.raise(entry.replica, entry.count)?;
218
+ }
219
+ Ok(())
220
+ }
221
+ }
222
+
223
+ /// Two counters are equal when every replica's count is, whatever order the
224
+ /// slots were filled in.
225
+ impl<const R: usize> PartialEq for GCounter<R> {
226
+ fn eq(&self, other: &Self) -> bool {
227
+ self.entries()
228
+ .all(|entry| other.count(entry.replica) == entry.count)
229
+ && other
230
+ .entries()
231
+ .all(|entry| self.count(entry.replica) == entry.count)
232
+ }
233
+ }
234
+
235
+ impl<const R: usize> Default for GCounter<R> {
236
+ fn default() -> Self {
237
+ Self::new()
238
+ }
239
+ }
240
+
241
+ #[cfg(test)]
242
+ mod tests;
@@ -0,0 +1,181 @@
1
+ //! A set that only grows.
2
+
3
+ #[cfg(feature = "alloc")]
4
+ use crate::boxed_frame;
5
+ use crate::{
6
+ COUNT_LEN, CapacityError, CodecError, FIELD_LEN, Merge, SHAPE_LEN, TAG, TAG_GSET, TAG_LEN,
7
+ free_slot, read_small, read_u64, same_elements, small_field, tagged_frame, write_into,
8
+ };
9
+
10
+ /// Where the shape sits in a frame.
11
+ const SHAPE: core::ops::Range<usize> = TAG_LEN..TAG_LEN + SHAPE_LEN;
12
+
13
+ /// Where the number of records carrying an element sits in a frame.
14
+ const HELD: core::ops::Range<usize> = SHAPE.end..SHAPE.end + COUNT_LEN;
15
+
16
+ /// Where the element records start in a frame.
17
+ const RECORDS: usize = HELD.end;
18
+
19
+ /// A set nothing is ever taken out of.
20
+ ///
21
+ /// ```
22
+ /// use merge_machines::{GSet, Merge};
23
+ ///
24
+ /// let mut left = GSet::<u16, 8>::new();
25
+ /// let mut right = GSet::<u16, 8>::new();
26
+ ///
27
+ /// assert_eq!(left.insert(1), Ok(()));
28
+ /// assert_eq!(left.insert(1), Ok(()));
29
+ /// assert_eq!(right.insert(2), Ok(()));
30
+ ///
31
+ /// assert_eq!(left.merge(&right), Ok(()));
32
+ /// assert_eq!(left.len(), 2);
33
+ /// assert!(left.contains(&2));
34
+ /// ```
35
+ #[derive(Debug, Clone, Copy, Eq)]
36
+ pub struct GSet<T, const N: usize> {
37
+ slots: [Option<T>; N],
38
+ }
39
+
40
+ impl<T: Copy + PartialEq, const N: usize> GSet<T, N> {
41
+ #[must_use]
42
+ pub const fn new() -> Self {
43
+ Self {
44
+ slots: [const { None }; N],
45
+ }
46
+ }
47
+
48
+ /// Adds `element`, which is a no-op if it is already in.
49
+ ///
50
+ /// # Errors
51
+ ///
52
+ /// Returns [`CapacityError`] when `element` is new and all `N` slots are
53
+ /// taken.
54
+ pub fn insert(&mut self, element: T) -> Result<(), CapacityError> {
55
+ if self.contains(&element) {
56
+ return Ok(());
57
+ }
58
+ let free = free_slot(&self.slots).ok_or(CapacityError)?;
59
+ self.slots[free] = Some(element);
60
+ Ok(())
61
+ }
62
+
63
+ #[must_use]
64
+ pub fn contains(&self, element: &T) -> bool {
65
+ self.slots.iter().flatten().any(|held| held == element)
66
+ }
67
+
68
+ #[must_use]
69
+ pub fn len(&self) -> usize {
70
+ self.slots.iter().flatten().count()
71
+ }
72
+
73
+ #[must_use]
74
+ pub fn is_empty(&self) -> bool {
75
+ self.len() == 0
76
+ }
77
+
78
+ /// The elements, in no particular order.
79
+ pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
80
+ self.slots.iter().flatten().copied()
81
+ }
82
+ }
83
+
84
+ impl<const N: usize> GSet<u64, N> {
85
+ /// Length of the [`encode`](Self::encode) frame.
86
+ pub const ENCODED_LEN: usize = RECORDS + N * FIELD_LEN;
87
+
88
+ /// Writes the set into `out`, returning the number of bytes written.
89
+ ///
90
+ /// # Errors
91
+ ///
92
+ /// [`CodecError::Truncated`] when `out` is shorter than
93
+ /// [`ENCODED_LEN`](Self::ENCODED_LEN).
94
+ pub fn encode(&self, out: &mut [u8]) -> Result<usize, CodecError> {
95
+ write_into(out, Self::ENCODED_LEN, |frame| self.write_frame(frame))
96
+ }
97
+
98
+ /// The set's frame, exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
99
+ #[cfg(feature = "alloc")]
100
+ #[must_use]
101
+ pub fn encoded(&self) -> alloc::boxed::Box<[u8]> {
102
+ boxed_frame(Self::ENCODED_LEN, |frame| self.write_frame(frame))
103
+ }
104
+
105
+ /// Fills a frame of exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
106
+ fn write_frame(&self, frame: &mut [u8]) {
107
+ frame.fill(0);
108
+ frame[TAG] = TAG_GSET;
109
+ frame[SHAPE].copy_from_slice(&small_field(N));
110
+ frame[HELD].copy_from_slice(&small_field(self.len()));
111
+
112
+ let (records, _) = frame[RECORDS..].as_chunks_mut::<FIELD_LEN>();
113
+ for (record, element) in records.iter_mut().zip(self.iter()) {
114
+ *record = element.to_le_bytes();
115
+ }
116
+ }
117
+
118
+ /// Reads a set written by [`encode`](Self::encode).
119
+ ///
120
+ /// # Errors
121
+ ///
122
+ /// [`CodecError::Truncated`] when `bytes` is too short, [`CodecError::Tag`]
123
+ /// when it holds another structure, [`CodecError::Parameters`] when it was
124
+ /// written with another capacity.
125
+ pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
126
+ let mut set = Self::new();
127
+ set.decode_into(bytes)?;
128
+ Ok(set)
129
+ }
130
+
131
+ /// Reads a frame over a set the caller already holds.
132
+ ///
133
+ /// The set is untouched when the frame is rejected.
134
+ ///
135
+ /// # Errors
136
+ ///
137
+ /// The same as [`decode`](Self::decode).
138
+ pub fn decode_into(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
139
+ let frame = tagged_frame(bytes, Self::ENCODED_LEN, TAG_GSET)?;
140
+ if read_small(&frame[SHAPE]) != N {
141
+ return Err(CodecError::Parameters);
142
+ }
143
+
144
+ let held = read_small(&frame[HELD]);
145
+ let (records, _) = frame[RECORDS..].as_chunks::<FIELD_LEN>();
146
+
147
+ self.slots = [None; N];
148
+ for (slot, record) in self.slots.iter_mut().zip(records.iter().take(held)) {
149
+ *slot = Some(read_u64(record));
150
+ }
151
+ Ok(())
152
+ }
153
+ }
154
+
155
+ impl<T: Copy + PartialEq, const N: usize> Merge for GSet<T, N> {
156
+ type Error = CapacityError;
157
+
158
+ fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
159
+ for element in other.iter() {
160
+ self.insert(element)?;
161
+ }
162
+ Ok(())
163
+ }
164
+ }
165
+
166
+ /// Two sets are equal when they hold the same elements, whatever order the
167
+ /// slots were filled in.
168
+ impl<T: PartialEq, const N: usize> PartialEq for GSet<T, N> {
169
+ fn eq(&self, other: &Self) -> bool {
170
+ same_elements(&self.slots, &other.slots)
171
+ }
172
+ }
173
+
174
+ impl<T: Copy + PartialEq, const N: usize> Default for GSet<T, N> {
175
+ fn default() -> Self {
176
+ Self::new()
177
+ }
178
+ }
179
+
180
+ #[cfg(test)]
181
+ mod tests;
@@ -0,0 +1,222 @@
1
+ //! A hybrid logical clock: timestamps near physical time that still order.
2
+
3
+ #[cfg(feature = "alloc")]
4
+ use crate::boxed_frame;
5
+ use crate::{
6
+ ClockExhausted, CodecError, FIELD_LEN, STAMP_HYBRID, Stamp, TAG, TAG_HYBRID_CLOCK, TAG_LEN,
7
+ read_u64, tagged_frame, write_into,
8
+ };
9
+
10
+ /// Bytes a [`HybridTimestamp`] occupies in a frame.
11
+ const TIMESTAMP_LEN: usize = 2 * FIELD_LEN;
12
+
13
+ /// Where the wall-clock reading sits in a timestamp field.
14
+ const WALL: core::ops::Range<usize> = 0..FIELD_LEN;
15
+
16
+ /// Where the counter sits in a timestamp field.
17
+ const COUNTER: core::ops::Range<usize> = FIELD_LEN..TIMESTAMP_LEN;
18
+
19
+ /// Where the last timestamp issued sits in a clock frame.
20
+ const LAST: core::ops::Range<usize> = TAG_LEN..TAG_LEN + TIMESTAMP_LEN;
21
+
22
+ /// A wall-clock reading paired with a counter that breaks its ties.
23
+ #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24
+ pub struct HybridTimestamp {
25
+ /// The wall-clock reading this was issued at, in nanoseconds.
26
+ pub wall_ns: u64,
27
+ /// How many timestamps have been issued at this reading.
28
+ pub counter: u64,
29
+ }
30
+
31
+ impl Stamp for HybridTimestamp {
32
+ const KIND: u8 = STAMP_HYBRID;
33
+ const ENCODED_LEN: usize = TIMESTAMP_LEN;
34
+
35
+ fn write(self, field: &mut [u8]) {
36
+ field[WALL].copy_from_slice(&self.wall_ns.to_le_bytes());
37
+ field[COUNTER].copy_from_slice(&self.counter.to_le_bytes());
38
+ }
39
+
40
+ fn read(field: &[u8]) -> Self {
41
+ Self {
42
+ wall_ns: read_u64(&field[WALL]),
43
+ counter: read_u64(&field[COUNTER]),
44
+ }
45
+ }
46
+ }
47
+
48
+ /// Issues timestamps that track physical time without ever going backwards.
49
+ ///
50
+ /// # The reading this wants
51
+ ///
52
+ /// `wall_ns` is physical time that two machines can compare: nanoseconds since
53
+ /// the Unix epoch. It is allowed to jump backwards, which is exactly why the
54
+ /// counter exists.
55
+ ///
56
+ /// It is not a monotonic reading, and nothing derived from it is a deadline, a
57
+ /// timeout or a budget. Measuring how long something took needs the monotonic
58
+ /// clock, which this crate neither reads nor models. Wiring a monotonic reading
59
+ /// in here produces timestamps that no other machine can order against its own.
60
+ ///
61
+ /// ```
62
+ /// use merge_machines::{ClockExhausted, HybridClock, HybridTimestamp};
63
+ ///
64
+ /// const MS: u64 = 1_000_000;
65
+ /// let mut clock = HybridClock::new();
66
+ ///
67
+ /// let first = clock.tick(10 * MS)?;
68
+ /// assert_eq!(first.counter, 0);
69
+ ///
70
+ /// // Same reading twice: the counter separates them.
71
+ /// let second = clock.tick(10 * MS)?;
72
+ /// assert!(second > first);
73
+ ///
74
+ /// // The wall clock is corrected backwards; the timestamps are not.
75
+ /// let third = clock.tick(4 * MS)?;
76
+ /// assert!(third > second);
77
+ /// assert_eq!(third.wall_ns, 10 * MS);
78
+ ///
79
+ /// // A reading that really did advance resets the counter.
80
+ /// let fourth = clock.tick(11 * MS)?;
81
+ /// assert_eq!(fourth.counter, 0);
82
+ ///
83
+ /// // A counter with nothing left reports it instead of repeating itself.
84
+ /// let crowded = HybridTimestamp { wall_ns: 11 * MS, counter: u64::MAX };
85
+ /// assert_eq!(clock.observe(0, crowded), Err(ClockExhausted));
86
+ /// assert_eq!(clock.last(), fourth);
87
+ /// # Ok::<(), ClockExhausted>(())
88
+ /// ```
89
+ #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
90
+ pub struct HybridClock {
91
+ last: HybridTimestamp,
92
+ }
93
+
94
+ const fn larger(left: u64, right: u64) -> u64 {
95
+ if left > right { left } else { right }
96
+ }
97
+
98
+ impl HybridClock {
99
+ #[must_use]
100
+ pub const fn new() -> Self {
101
+ Self {
102
+ last: HybridTimestamp {
103
+ wall_ns: 0,
104
+ counter: 0,
105
+ },
106
+ }
107
+ }
108
+
109
+ /// The last timestamp this clock issued.
110
+ #[must_use]
111
+ pub const fn last(&self) -> HybridTimestamp {
112
+ self.last
113
+ }
114
+
115
+ /// Stamps a local event at wall-clock reading `wall_ns`.
116
+ ///
117
+ /// # Errors
118
+ ///
119
+ /// [`ClockExhausted`] when the reading has not advanced and the counter
120
+ /// that separates two events at the same reading holds its highest value,
121
+ /// which leaves the clock where it was.
122
+ pub const fn tick(&mut self, wall_ns: u64) -> Result<HybridTimestamp, ClockExhausted> {
123
+ if wall_ns > self.last.wall_ns {
124
+ self.last = HybridTimestamp {
125
+ wall_ns,
126
+ counter: 0,
127
+ };
128
+ return Ok(self.last);
129
+ }
130
+ let Some(counter) = self.last.counter.checked_add(1) else {
131
+ return Err(ClockExhausted);
132
+ };
133
+ self.last.counter = counter;
134
+ Ok(self.last)
135
+ }
136
+
137
+ /// Stamps the receipt of `observed` at wall-clock reading `wall_ns`.
138
+ ///
139
+ /// # Errors
140
+ ///
141
+ /// The same as [`tick`](Self::tick), and the observed timestamp is not
142
+ /// adopted either.
143
+ pub const fn observe(
144
+ &mut self,
145
+ wall_ns: u64,
146
+ observed: HybridTimestamp,
147
+ ) -> Result<HybridTimestamp, ClockExhausted> {
148
+ let latest = larger(wall_ns, larger(self.last.wall_ns, observed.wall_ns));
149
+ let counter = if latest == self.last.wall_ns && latest == observed.wall_ns {
150
+ larger(self.last.counter, observed.counter).checked_add(1)
151
+ } else if latest == self.last.wall_ns {
152
+ self.last.counter.checked_add(1)
153
+ } else if latest == observed.wall_ns {
154
+ observed.counter.checked_add(1)
155
+ } else {
156
+ Some(0)
157
+ };
158
+ let Some(counter) = counter else {
159
+ return Err(ClockExhausted);
160
+ };
161
+
162
+ self.last = HybridTimestamp {
163
+ wall_ns: latest,
164
+ counter,
165
+ };
166
+ Ok(self.last)
167
+ }
168
+
169
+ /// Length of the [`encode`](Self::encode) frame.
170
+ pub const ENCODED_LEN: usize = TAG_LEN + TIMESTAMP_LEN;
171
+
172
+ /// Writes the clock into `out`, returning the number of bytes written.
173
+ ///
174
+ /// # Errors
175
+ ///
176
+ /// [`CodecError::Truncated`] when `out` is shorter than
177
+ /// [`ENCODED_LEN`](Self::ENCODED_LEN).
178
+ pub fn encode(&self, out: &mut [u8]) -> Result<usize, CodecError> {
179
+ write_into(out, Self::ENCODED_LEN, |frame| self.write_frame(frame))
180
+ }
181
+
182
+ /// The clock's frame, exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
183
+ #[cfg(feature = "alloc")]
184
+ #[must_use]
185
+ pub fn encoded(&self) -> alloc::boxed::Box<[u8]> {
186
+ boxed_frame(Self::ENCODED_LEN, |frame| self.write_frame(frame))
187
+ }
188
+
189
+ /// Fills a frame of exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
190
+ fn write_frame(&self, frame: &mut [u8]) {
191
+ frame[TAG] = TAG_HYBRID_CLOCK;
192
+ self.last.write(&mut frame[LAST]);
193
+ }
194
+
195
+ /// Reads a clock written by [`encode`](Self::encode).
196
+ ///
197
+ /// # Errors
198
+ ///
199
+ /// [`CodecError::Truncated`] when `bytes` is too short, [`CodecError::Tag`]
200
+ /// when it holds another structure.
201
+ pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
202
+ let mut clock = Self::new();
203
+ clock.decode_into(bytes)?;
204
+ Ok(clock)
205
+ }
206
+
207
+ /// Reads a frame over a clock the caller already holds.
208
+ ///
209
+ /// The clock is untouched when the frame is rejected.
210
+ ///
211
+ /// # Errors
212
+ ///
213
+ /// The same as [`decode`](Self::decode).
214
+ pub fn decode_into(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
215
+ let frame = tagged_frame(bytes, Self::ENCODED_LEN, TAG_HYBRID_CLOCK)?;
216
+ self.last = HybridTimestamp::read(&frame[LAST]);
217
+ Ok(())
218
+ }
219
+ }
220
+
221
+ #[cfg(test)]
222
+ mod tests;