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,240 @@
|
|
|
1
|
+
//! `MergeMachines::OrSet`: a set elements can leave, where a concurrent add
|
|
2
|
+
//! survives a remove.
|
|
3
|
+
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
6
|
+
|
|
7
|
+
use bytes::Bytes;
|
|
8
|
+
use magnus::scan_args::scan_args;
|
|
9
|
+
use magnus::typed_data::Obj;
|
|
10
|
+
use magnus::{Error, RArray, RHash, RModule, RString, Ruby, Value, function, method, prelude::*};
|
|
11
|
+
use merge_machines::{OrSet, ReplicaId, Tag};
|
|
12
|
+
|
|
13
|
+
use crate::hash::hash64;
|
|
14
|
+
use crate::names::{self, Names};
|
|
15
|
+
use crate::shape::{frame_len_of, shaped};
|
|
16
|
+
use crate::{
|
|
17
|
+
CAPACITIES, Whole, blob_shape, capacity, mismatch, required_kwarg, tags_exhausted, unsupported,
|
|
18
|
+
unsupported_blob,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/// Tags minted in this process, so no two sets allocate the same one.
|
|
22
|
+
static MINTED: AtomicU64 = AtomicU64::new(0);
|
|
23
|
+
|
|
24
|
+
/// The set at a single capacity, over the 64-bit hash each key becomes.
|
|
25
|
+
type Held<const N: usize> = OrSet<u64, N>;
|
|
26
|
+
|
|
27
|
+
shaped!(Set over Held { N64 = 64, N256 = 256, N1024 = 1024 });
|
|
28
|
+
|
|
29
|
+
/// Runs `body` against whichever variant is held.
|
|
30
|
+
macro_rules! set {
|
|
31
|
+
($set:expr, |$inner:ident| $body:expr) => {
|
|
32
|
+
match $set {
|
|
33
|
+
Set::N64($inner) => $body,
|
|
34
|
+
Set::N256($inner) => $body,
|
|
35
|
+
Set::N1024($inner) => $body,
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// The set holds 64-bit key hashes, and `names` holds the string each one came
|
|
41
|
+
/// from.
|
|
42
|
+
#[magnus::wrap(class = "MergeMachines::OrSet", free_immediately, size)]
|
|
43
|
+
struct Wrapper {
|
|
44
|
+
set: RefCell<Set>,
|
|
45
|
+
names: RefCell<Names>,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const fn capacity_of<const N: usize>(_: &Held<N>) -> usize {
|
|
49
|
+
N
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl Wrapper {
|
|
53
|
+
fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
|
|
54
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
55
|
+
let Whole(capacity): Whole<usize> = required_kwarg(args.keywords, "capacity")?;
|
|
56
|
+
Ok(Self {
|
|
57
|
+
set: RefCell::new(dispatch(ruby, capacity, None)?),
|
|
58
|
+
names: RefCell::new(Names::new()),
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Reads a blob written by [`dump`](Self::dump).
|
|
63
|
+
fn load(ruby: &Ruby, blob: Bytes) -> Result<Self, Error> {
|
|
64
|
+
let set = dispatch(ruby, blob_shape(ruby, &blob)?, Some(&blob))?;
|
|
65
|
+
let frame_len = set!(&set, |inner| frame_len_of(&**inner));
|
|
66
|
+
let names = names::read(ruby, &blob[frame_len..])?;
|
|
67
|
+
let elements = set!(&set, |inner| inner
|
|
68
|
+
.adds()
|
|
69
|
+
.map(|add| add.element)
|
|
70
|
+
.collect::<Vec<u64>>());
|
|
71
|
+
names::complete(ruby, &names, elements.into_iter())?;
|
|
72
|
+
|
|
73
|
+
let wrapper = Self {
|
|
74
|
+
set: RefCell::new(set),
|
|
75
|
+
names: RefCell::new(names),
|
|
76
|
+
};
|
|
77
|
+
wrapper.prune();
|
|
78
|
+
Ok(wrapper)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn capacity(&self) -> usize {
|
|
82
|
+
set!(&*self.set.borrow(), |set| capacity_of(&**set))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Adds `key` under a tag of `replica:` and the next counter that replica
|
|
86
|
+
/// has not used here.
|
|
87
|
+
fn add(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<(), Error> {
|
|
88
|
+
let args = scan_args::<(String,), (), (), (), RHash, ()>(args)?;
|
|
89
|
+
let (key,) = args.required;
|
|
90
|
+
let Whole(replica): Whole<ReplicaId> = required_kwarg(args.keywords, "replica")?;
|
|
91
|
+
|
|
92
|
+
let element = hash64(key.as_bytes());
|
|
93
|
+
let Some(counter) = rb_self.next_counter(replica) else {
|
|
94
|
+
return Err(tags_exhausted(ruby));
|
|
95
|
+
};
|
|
96
|
+
let tag = Tag::new(replica, counter);
|
|
97
|
+
capacity(
|
|
98
|
+
ruby,
|
|
99
|
+
set!(&mut *rb_self.set.borrow_mut(), |set| set.add(element, tag)),
|
|
100
|
+
)?;
|
|
101
|
+
rb_self.names.borrow_mut().insert(element, key);
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Tombstones every tag this set has seen for `key`.
|
|
106
|
+
fn remove(ruby: &Ruby, rb_self: &Self, key: String) -> Result<(), Error> {
|
|
107
|
+
let element = hash64(key.as_bytes());
|
|
108
|
+
capacity(
|
|
109
|
+
ruby,
|
|
110
|
+
set!(&mut *rb_self.set.borrow_mut(), |set| set.remove(&element)),
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn includes(&self, key: String) -> bool {
|
|
115
|
+
let element = hash64(key.as_bytes());
|
|
116
|
+
set!(&*self.set.borrow(), |set| set.contains(&element))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// The keys still in the set, each once, in no particular order.
|
|
120
|
+
fn members(ruby: &Ruby, rb_self: &Self) -> Result<RArray, Error> {
|
|
121
|
+
let elements = set!(&*rb_self.set.borrow(), |set| set
|
|
122
|
+
.iter()
|
|
123
|
+
.collect::<Vec<u64>>());
|
|
124
|
+
names::members(ruby, &rb_self.names.borrow(), &elements)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn size(&self) -> usize {
|
|
128
|
+
set!(&*self.set.borrow(), |set| set.len())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fn is_empty(&self) -> bool {
|
|
132
|
+
set!(&*self.set.borrow(), |set| set.is_empty())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn merge(ruby: &Ruby, rb_self: Obj<Self>, other: &Self) -> Result<Obj<Self>, Error> {
|
|
136
|
+
let source = other.set.borrow().clone();
|
|
137
|
+
let names = other.names.borrow().clone();
|
|
138
|
+
let Some(folded) = rb_self.set.borrow_mut().absorb(&source) else {
|
|
139
|
+
return Err(mismatch(ruby, "set"));
|
|
140
|
+
};
|
|
141
|
+
rb_self.names.borrow_mut().extend(names);
|
|
142
|
+
rb_self.prune();
|
|
143
|
+
capacity(ruby, folded)?;
|
|
144
|
+
Ok(rb_self)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// The core frame followed by the names of every key it has seen added.
|
|
148
|
+
///
|
|
149
|
+
/// ```text
|
|
150
|
+
/// core frame OrSet::<u64, capacity>::ENCODED_LEN bytes, tag 0x82
|
|
151
|
+
/// name record u64 little-endian byte length, then that many UTF-8 bytes
|
|
152
|
+
/// ```
|
|
153
|
+
///
|
|
154
|
+
/// One record per add, tombstoned or not, in the core frame's order,
|
|
155
|
+
/// filling the rest of the blob. The frame holds 64-bit hashes, so
|
|
156
|
+
/// [`load`](Self::load) rehashes each name to rebuild what
|
|
157
|
+
/// [`members`](Self::members) reads.
|
|
158
|
+
fn dump(ruby: &Ruby, rb_self: &Self) -> RString {
|
|
159
|
+
let (frame, elements) = set!(&*rb_self.set.borrow(), |set| (
|
|
160
|
+
set.encoded(),
|
|
161
|
+
set.adds().map(|add| add.element).collect::<Vec<u64>>()
|
|
162
|
+
));
|
|
163
|
+
let mut blob = Vec::from(frame);
|
|
164
|
+
names::write(&mut blob, &rb_self.names.borrow(), elements.into_iter());
|
|
165
|
+
ruby.str_from_slice(&blob)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn inspect(&self) -> String {
|
|
169
|
+
format!(
|
|
170
|
+
"#<MergeMachines::OrSet capacity={} size={}>",
|
|
171
|
+
self.capacity(),
|
|
172
|
+
self.size()
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Leaves a name for every add the core holds, tombstoned or not, and for
|
|
177
|
+
/// nothing else.
|
|
178
|
+
///
|
|
179
|
+
/// A merge extends the table before the core reports whether everything
|
|
180
|
+
/// fit, because a merge that overran capacity still took whatever it had
|
|
181
|
+
/// room for and those keys need their names. Reconciling afterwards keeps
|
|
182
|
+
/// the names of the adds that landed and drops the ones that did not.
|
|
183
|
+
fn prune(&self) {
|
|
184
|
+
let set = self.set.borrow();
|
|
185
|
+
self.names.borrow_mut().retain(|element, _| {
|
|
186
|
+
set!(&*set, |held| held.adds().any(|add| add.element == *element))
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// A counter no tag in this process has used, and above anything this
|
|
191
|
+
/// replica already used in this set.
|
|
192
|
+
///
|
|
193
|
+
/// `None` once either counter has nothing left to count: reusing a tag
|
|
194
|
+
/// would make the core take the add for one it already holds and drop the
|
|
195
|
+
/// key without a word.
|
|
196
|
+
fn next_counter(&self, replica: ReplicaId) -> Option<u64> {
|
|
197
|
+
let used = |tag: Tag| (tag.replica == replica).then_some(tag.counter);
|
|
198
|
+
let highest = set!(&*self.set.borrow(), |set| set
|
|
199
|
+
.adds()
|
|
200
|
+
.map(|add| add.tag)
|
|
201
|
+
.chain(set.removes())
|
|
202
|
+
.filter_map(used)
|
|
203
|
+
.max())
|
|
204
|
+
.unwrap_or(0);
|
|
205
|
+
|
|
206
|
+
let minted = MINTED
|
|
207
|
+
.try_update(Ordering::Relaxed, Ordering::Relaxed, |minted| {
|
|
208
|
+
minted.checked_add(1)
|
|
209
|
+
})
|
|
210
|
+
.ok()?;
|
|
211
|
+
highest.max(minted).checked_add(1)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// The variant for `capacity`, read from `blob` when there is one.
|
|
216
|
+
fn dispatch(ruby: &Ruby, capacity: usize, blob: Option<&[u8]>) -> Result<Set, Error> {
|
|
217
|
+
let Some(frame) = blob else {
|
|
218
|
+
return Set::blank(capacity)
|
|
219
|
+
.ok_or_else(|| unsupported(ruby, "capacity", capacity, &CAPACITIES));
|
|
220
|
+
};
|
|
221
|
+
Set::decoded(ruby, capacity, frame)
|
|
222
|
+
.unwrap_or_else(|| Err(unsupported_blob(ruby, "capacity", &CAPACITIES)))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
pub fn define(ruby: &Ruby, module: RModule) -> Result<(), Error> {
|
|
226
|
+
let class = module.define_class("OrSet", ruby.class_object())?;
|
|
227
|
+
class.define_singleton_method("new", function!(Wrapper::new, -1))?;
|
|
228
|
+
class.define_singleton_method("load", function!(Wrapper::load, 1))?;
|
|
229
|
+
class.define_method("capacity", method!(Wrapper::capacity, 0))?;
|
|
230
|
+
class.define_method("add", method!(Wrapper::add, -1))?;
|
|
231
|
+
class.define_method("remove", method!(Wrapper::remove, 1))?;
|
|
232
|
+
class.define_method("include?", method!(Wrapper::includes, 1))?;
|
|
233
|
+
class.define_method("members", method!(Wrapper::members, 0))?;
|
|
234
|
+
class.define_method("size", method!(Wrapper::size, 0))?;
|
|
235
|
+
class.define_method("empty?", method!(Wrapper::is_empty, 0))?;
|
|
236
|
+
class.define_method("merge", method!(Wrapper::merge, 1))?;
|
|
237
|
+
class.define_method("dump", method!(Wrapper::dump, 0))?;
|
|
238
|
+
class.define_method("inspect", method!(Wrapper::inspect, 0))?;
|
|
239
|
+
Ok(())
|
|
240
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
//! `MergeMachines::PNCounter`: a counter replicas can lower as well as raise.
|
|
2
|
+
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
|
|
5
|
+
use bytes::Bytes;
|
|
6
|
+
use magnus::scan_args::scan_args;
|
|
7
|
+
use magnus::typed_data::Obj;
|
|
8
|
+
use magnus::{Error, RHash, RModule, RString, Ruby, Value, function, method, prelude::*};
|
|
9
|
+
use merge_machines::PNCounter;
|
|
10
|
+
|
|
11
|
+
use crate::gcounter::increment_args;
|
|
12
|
+
use crate::shape::shaped;
|
|
13
|
+
use crate::{
|
|
14
|
+
REPLICAS, Whole, blob_shape, capacity, mismatch, required_kwarg, unsupported, unsupported_blob,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// The counter at a single replica count.
|
|
18
|
+
type Held<const R: usize> = PNCounter<R>;
|
|
19
|
+
|
|
20
|
+
shaped!(Counter over Held { R4 = 4, R8 = 8, R16 = 16, R32 = 32 });
|
|
21
|
+
|
|
22
|
+
/// Runs `body` against whichever variant is held.
|
|
23
|
+
macro_rules! counter {
|
|
24
|
+
($counter:expr, |$inner:ident| $body:expr) => {
|
|
25
|
+
match $counter {
|
|
26
|
+
Counter::R4($inner) => $body,
|
|
27
|
+
Counter::R8($inner) => $body,
|
|
28
|
+
Counter::R16($inner) => $body,
|
|
29
|
+
Counter::R32($inner) => $body,
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[magnus::wrap(class = "MergeMachines::PNCounter", free_immediately, size)]
|
|
35
|
+
struct Wrapper(RefCell<Counter>);
|
|
36
|
+
|
|
37
|
+
const fn replicas_of<const R: usize>(_: &Held<R>) -> usize {
|
|
38
|
+
R
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl Wrapper {
|
|
42
|
+
fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
|
|
43
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
44
|
+
let Whole(replicas): Whole<usize> = required_kwarg(args.keywords, "replicas")?;
|
|
45
|
+
Ok(Self(RefCell::new(dispatch(ruby, replicas, None)?)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Reads a blob written by [`dump`](Self::dump).
|
|
49
|
+
fn load(ruby: &Ruby, blob: Bytes) -> Result<Self, Error> {
|
|
50
|
+
let replicas = blob_shape(ruby, &blob)?;
|
|
51
|
+
Ok(Self(RefCell::new(dispatch(ruby, replicas, Some(&blob))?)))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn replicas(&self) -> usize {
|
|
55
|
+
counter!(&*self.0.borrow(), |counter| replicas_of(&**counter))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Adds `by:` to `replica:`'s own count.
|
|
59
|
+
fn increment(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<(), Error> {
|
|
60
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
61
|
+
let (replica, by) = increment_args(args.keywords)?;
|
|
62
|
+
capacity(
|
|
63
|
+
ruby,
|
|
64
|
+
counter!(&mut *rb_self.0.borrow_mut(), |counter| counter
|
|
65
|
+
.increment(replica, by)),
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Takes `by:` away on `replica:`'s own account.
|
|
70
|
+
fn decrement(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<(), Error> {
|
|
71
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
72
|
+
let (replica, by) = increment_args(args.keywords)?;
|
|
73
|
+
capacity(
|
|
74
|
+
ruby,
|
|
75
|
+
counter!(&mut *rb_self.0.borrow_mut(), |counter| counter
|
|
76
|
+
.decrement(replica, by)),
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Everything added, minus everything taken away.
|
|
81
|
+
fn value(&self) -> i128 {
|
|
82
|
+
counter!(&*self.0.borrow(), |counter| counter.value())
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Everything added, by every replica.
|
|
86
|
+
fn added(&self) -> u128 {
|
|
87
|
+
counter!(&*self.0.borrow(), |counter| counter.added())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Everything taken away, by every replica.
|
|
91
|
+
fn removed(&self) -> u128 {
|
|
92
|
+
counter!(&*self.0.borrow(), |counter| counter.removed())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn merge(ruby: &Ruby, rb_self: Obj<Self>, other: &Self) -> Result<Obj<Self>, Error> {
|
|
96
|
+
let source = other.0.borrow().clone();
|
|
97
|
+
let Some(folded) = rb_self.0.borrow_mut().absorb(&source) else {
|
|
98
|
+
return Err(mismatch(ruby, "counter"));
|
|
99
|
+
};
|
|
100
|
+
capacity(ruby, folded)?;
|
|
101
|
+
Ok(rb_self)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// The core frame, tag 0x62.
|
|
105
|
+
fn dump(ruby: &Ruby, rb_self: &Self) -> RString {
|
|
106
|
+
let frame = counter!(&*rb_self.0.borrow(), |counter| counter.encoded());
|
|
107
|
+
ruby.str_from_slice(&frame)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn inspect(&self) -> String {
|
|
111
|
+
format!(
|
|
112
|
+
"#<MergeMachines::PNCounter replicas={} value={}>",
|
|
113
|
+
self.replicas(),
|
|
114
|
+
self.value()
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// The variant for `replicas`, read from `blob` when there is one.
|
|
120
|
+
fn dispatch(ruby: &Ruby, replicas: usize, blob: Option<&[u8]>) -> Result<Counter, Error> {
|
|
121
|
+
let Some(frame) = blob else {
|
|
122
|
+
return Counter::blank(replicas)
|
|
123
|
+
.ok_or_else(|| unsupported(ruby, "replicas", replicas, &REPLICAS));
|
|
124
|
+
};
|
|
125
|
+
Counter::decoded(ruby, replicas, frame)
|
|
126
|
+
.unwrap_or_else(|| Err(unsupported_blob(ruby, "replica count", &REPLICAS)))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub fn define(ruby: &Ruby, module: RModule) -> Result<(), Error> {
|
|
130
|
+
let class = module.define_class("PNCounter", ruby.class_object())?;
|
|
131
|
+
class.define_singleton_method("new", function!(Wrapper::new, -1))?;
|
|
132
|
+
class.define_singleton_method("load", function!(Wrapper::load, 1))?;
|
|
133
|
+
class.define_method("replicas", method!(Wrapper::replicas, 0))?;
|
|
134
|
+
class.define_method("increment", method!(Wrapper::increment, -1))?;
|
|
135
|
+
class.define_method("decrement", method!(Wrapper::decrement, -1))?;
|
|
136
|
+
class.define_method("value", method!(Wrapper::value, 0))?;
|
|
137
|
+
class.define_method("added", method!(Wrapper::added, 0))?;
|
|
138
|
+
class.define_method("removed", method!(Wrapper::removed, 0))?;
|
|
139
|
+
class.define_method("merge", method!(Wrapper::merge, 1))?;
|
|
140
|
+
class.define_method("dump", method!(Wrapper::dump, 0))?;
|
|
141
|
+
class.define_method("inspect", method!(Wrapper::inspect, 0))?;
|
|
142
|
+
Ok(())
|
|
143
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//! The shape a class is given, resolved to a monomorphised state.
|
|
2
|
+
|
|
3
|
+
use magnus::{Error, Ruby};
|
|
4
|
+
use merge_machines::CodecError;
|
|
5
|
+
|
|
6
|
+
use crate::codec_error;
|
|
7
|
+
|
|
8
|
+
/// A state at one const generic, and the core frame it encodes to.
|
|
9
|
+
pub trait Framed: Sized {
|
|
10
|
+
/// Length of the core frame this state encodes to.
|
|
11
|
+
const FRAME_LEN: usize;
|
|
12
|
+
|
|
13
|
+
/// The state a class starts out holding.
|
|
14
|
+
fn blank() -> Self;
|
|
15
|
+
|
|
16
|
+
/// Reads a core frame over a state the caller already holds.
|
|
17
|
+
///
|
|
18
|
+
/// # Errors
|
|
19
|
+
///
|
|
20
|
+
/// [`CodecError::Truncated`] when `frame` is too short,
|
|
21
|
+
/// [`CodecError::Tag`] when it holds another structure,
|
|
22
|
+
/// [`CodecError::Parameters`] when it was written at another shape.
|
|
23
|
+
fn read_frame(&mut self, frame: &[u8]) -> Result<(), CodecError>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Length of the core frame `state` encodes to.
|
|
27
|
+
pub const fn frame_len_of<T: Framed>(_state: &T) -> usize {
|
|
28
|
+
T::FRAME_LEN
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Each shape is constructed behind its own call frame.
|
|
32
|
+
#[inline(never)]
|
|
33
|
+
pub fn boxed_new<T: Framed>() -> Box<T> {
|
|
34
|
+
Box::new(T::blank())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Each shape is read behind its own call frame.
|
|
38
|
+
#[inline(never)]
|
|
39
|
+
pub fn boxed_decode<T: Framed>(ruby: &Ruby, frame: &[u8]) -> Result<Box<T>, Error> {
|
|
40
|
+
let mut state = boxed_new::<T>();
|
|
41
|
+
state
|
|
42
|
+
.read_frame(frame)
|
|
43
|
+
.map_err(|error| codec_error(ruby, error))?;
|
|
44
|
+
Ok(state)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Defines the enum a class holds: one boxed variant per shape `held` is
|
|
48
|
+
/// monomorphised at, where `held` is that state at a single shape, built by
|
|
49
|
+
/// `held::new` and read by `held::decode_into`.
|
|
50
|
+
///
|
|
51
|
+
/// A class is a pointer wide whatever shape it was given, and every variant is
|
|
52
|
+
/// built and read through [`boxed_new`] and [`boxed_decode`]. A shape no
|
|
53
|
+
/// variant was monomorphised at, and a pairing of two different shapes, come
|
|
54
|
+
/// back as `None` for the class to turn into the error its own callers expect.
|
|
55
|
+
macro_rules! shaped {
|
|
56
|
+
($name:ident over $held:ident { $($variant:ident = $shape:literal),+ $(,)? }) => {
|
|
57
|
+
impl<const SHAPE: usize> $crate::shape::Framed for $held<SHAPE> {
|
|
58
|
+
const FRAME_LEN: usize = Self::ENCODED_LEN;
|
|
59
|
+
|
|
60
|
+
fn blank() -> Self {
|
|
61
|
+
Self::new()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn read_frame(&mut self, frame: &[u8]) -> Result<(), ::merge_machines::CodecError> {
|
|
65
|
+
self.decode_into(frame)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// A state at one of the monomorphised shapes.
|
|
70
|
+
#[derive(Clone)]
|
|
71
|
+
enum $name {
|
|
72
|
+
$($variant(Box<$held<$shape>>),)+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl $name {
|
|
76
|
+
/// The variant for `shape`, holding nothing, or `None` when no
|
|
77
|
+
/// variant was monomorphised at `shape`.
|
|
78
|
+
fn blank(shape: usize) -> Option<Self> {
|
|
79
|
+
match shape {
|
|
80
|
+
$($shape => Some(Self::$variant($crate::shape::boxed_new())),)+
|
|
81
|
+
_ => None,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// The variant for `shape`, read from `frame`, or `None` when no
|
|
86
|
+
/// variant was monomorphised at `shape`.
|
|
87
|
+
fn decoded(
|
|
88
|
+
ruby: &::magnus::Ruby,
|
|
89
|
+
shape: usize,
|
|
90
|
+
frame: &[u8],
|
|
91
|
+
) -> Option<Result<Self, ::magnus::Error>> {
|
|
92
|
+
match shape {
|
|
93
|
+
$($shape => Some(
|
|
94
|
+
$crate::shape::boxed_decode(ruby, frame).map(Self::$variant),
|
|
95
|
+
),)+
|
|
96
|
+
_ => None,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Folds `other` in, or `None` when it holds another shape.
|
|
101
|
+
fn absorb(
|
|
102
|
+
&mut self,
|
|
103
|
+
other: &Self,
|
|
104
|
+
) -> Option<Result<(), ::merge_machines::CapacityError>> {
|
|
105
|
+
match (self, other) {
|
|
106
|
+
$((Self::$variant(target), Self::$variant(source)) => Some(
|
|
107
|
+
<$held<$shape> as ::merge_machines::Merge>::merge(target, source),
|
|
108
|
+
),)+
|
|
109
|
+
_ => None,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub(crate) use shaped;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//! The wall-clock reading a hybrid timestamp is stamped at.
|
|
2
|
+
//!
|
|
3
|
+
//! `Process::CLOCK_REALTIME` is physical time two machines can compare. It
|
|
4
|
+
//! steps backwards when the host's clock is corrected, and no deadline may be
|
|
5
|
+
//! derived from it.
|
|
6
|
+
|
|
7
|
+
use std::sync::OnceLock;
|
|
8
|
+
|
|
9
|
+
use magnus::value::Opaque;
|
|
10
|
+
use magnus::{Error, RModule, Ruby, Value, prelude::*};
|
|
11
|
+
|
|
12
|
+
use crate::{defined_twice, uninitialised};
|
|
13
|
+
|
|
14
|
+
static PROCESS: OnceLock<Opaque<RModule>> = OnceLock::new();
|
|
15
|
+
static REALTIME: OnceLock<Opaque<Value>> = OnceLock::new();
|
|
16
|
+
|
|
17
|
+
pub fn define(ruby: &Ruby) -> Result<(), Error> {
|
|
18
|
+
let process: RModule = ruby.class_object().const_get("Process")?;
|
|
19
|
+
let realtime: Value = process.const_get("CLOCK_REALTIME")?;
|
|
20
|
+
PROCESS
|
|
21
|
+
.set(process.into())
|
|
22
|
+
.map_err(|_| defined_twice(ruby, "Process"))?;
|
|
23
|
+
REALTIME
|
|
24
|
+
.set(realtime.into())
|
|
25
|
+
.map_err(|_| defined_twice(ruby, "Process::CLOCK_REALTIME"))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// `Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)`.
|
|
29
|
+
pub fn now_ns(ruby: &Ruby) -> Result<u64, Error> {
|
|
30
|
+
let (Some(process), Some(realtime)) = (PROCESS.get(), REALTIME.get()) else {
|
|
31
|
+
return Err(uninitialised(ruby));
|
|
32
|
+
};
|
|
33
|
+
ruby.get_inner(*process).funcall(
|
|
34
|
+
"clock_gettime",
|
|
35
|
+
(ruby.get_inner(*realtime), ruby.to_symbol("nanosecond")),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'merge_machines/version'
|
|
4
|
+
|
|
5
|
+
# GCounter, PNCounter, LwwRegister, GSet, OrSet, LogicalClock and HybridClock
|
|
6
|
+
# are defined by the native extension; there is no pure-Ruby implementation to
|
|
7
|
+
# fall back to.
|
|
8
|
+
require 'merge_machines_native/merge_machines_native'
|