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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +57 -0
- data/Cargo.lock +294 -0
- data/Cargo.toml +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -0
- data/ext/merge_machines_native/core/Cargo.toml +24 -0
- data/ext/merge_machines_native/core/src/gcounter.rs +242 -0
- data/ext/merge_machines_native/core/src/gset.rs +181 -0
- data/ext/merge_machines_native/core/src/hybrid.rs +222 -0
- data/ext/merge_machines_native/core/src/lib.rs +221 -0
- data/ext/merge_machines_native/core/src/logical.rs +139 -0
- data/ext/merge_machines_native/core/src/lww.rs +213 -0
- data/ext/merge_machines_native/core/src/orset.rs +334 -0
- data/ext/merge_machines_native/core/src/pncounter.rs +165 -0
- data/ext/merge_machines_native/extconf.rb +3 -0
- data/ext/merge_machines_native/ffi/Cargo.toml +20 -0
- data/ext/merge_machines_native/ffi/extconf.rb +21 -0
- data/ext/merge_machines_native/ffi/src/data.rs +44 -0
- data/ext/merge_machines_native/ffi/src/gcounter.rs +138 -0
- data/ext/merge_machines_native/ffi/src/gset.rs +187 -0
- data/ext/merge_machines_native/ffi/src/hash.rs +33 -0
- data/ext/merge_machines_native/ffi/src/hybrid.rs +110 -0
- data/ext/merge_machines_native/ffi/src/lib.rs +221 -0
- data/ext/merge_machines_native/ffi/src/logical.rs +89 -0
- data/ext/merge_machines_native/ffi/src/lww.rs +360 -0
- data/ext/merge_machines_native/ffi/src/names.rs +82 -0
- data/ext/merge_machines_native/ffi/src/orset.rs +240 -0
- data/ext/merge_machines_native/ffi/src/pncounter.rs +143 -0
- data/ext/merge_machines_native/ffi/src/shape.rs +116 -0
- data/ext/merge_machines_native/ffi/src/wall.rs +37 -0
- data/lib/merge_machines/version.rb +5 -0
- data/lib/merge_machines.rb +8 -0
- data/sig/merge_machines.rbs +238 -0
- 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;
|