y-rb 0.3.2-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1f36fedaf73d9ee6ca57cdffd3aaa8ab929125ddcb02dbf4f2f8ef9d955fc3ff
4
+ data.tar.gz: 33ee25caaad2fba8502209f1b9235c910fb9824c2dbd02fff3de0fee1370d07b
5
+ SHA512:
6
+ metadata.gz: 0e2731b2d8ccf556eb1b326102dd91f72b607bb952eb90f64b120d5cab753803a9670733d8ce1d6f6eb0e96e49492200588ae85877a955193b2b114be2710604
7
+ data.tar.gz: ebc4756e03e9757e2e38269f393733fe6852e62939636b28adc4091912f8a320b95f811c57c6da05993b46f11e810bdf74c43fc4aca485108fb1e9325a69d3f6
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "yrb"
3
+ version = "0.3.2"
4
+ authors = ["Hannes Moser <box@hannesmoser.at>", "Hannes Moser <hmoser@gitlab.com>"]
5
+ edition = "2021"
6
+ homepage = "https://github.com/y-crdt/yrb"
7
+ repository = "https://github.com/y-crdt/yrb"
8
+
9
+ [dependencies]
10
+ lib0 = "0.12.2" # must match yrs version
11
+ magnus = { git = "https://github.com/matsadler/magnus", rev = "4c9857cecabec2df6cc230763560815520de20b7"} # waiting for release with full rb-sys backend
12
+ rb-sys = { version = "~0.9.35", features = ["link-ruby"] }
13
+ thiserror = "1.0.37"
14
+ yrs = "0.12.2"
15
+
16
+ [dev-dependencies]
17
+ magnus = { git = "https://github.com/matsadler/magnus", rev = "4c9857cecabec2df6cc230763560815520de20b7", features = ["embed"] } # waiting for release with full rb-sys backend
18
+
19
+ [lib]
20
+ name = "yrb"
21
+ crate-type = ["cdylib"]
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("yrb")
@@ -0,0 +1,425 @@
1
+ use std::cell::RefCell;
2
+ use std::collections::hash_map::Entry;
3
+ use std::collections::HashMap;
4
+ use std::rc::{Rc, Weak};
5
+ use std::time::Instant;
6
+ use thiserror::Error;
7
+ use yrs::block::ClientID;
8
+ use yrs::updates::decoder::{Decode, Decoder};
9
+ use yrs::updates::encoder::{Encode, Encoder};
10
+ use yrs::{Doc, SubscriptionId};
11
+
12
+ const NULL_STR: &str = "null";
13
+
14
+ /// The Awareness class implements a simple shared state protocol that can be used for non-persistent
15
+ /// data like awareness information (cursor, username, status, ..). Each client can update its own
16
+ /// local state and listen to state changes of remote clients.
17
+ ///
18
+ /// Each client is identified by a unique client id (something we borrow from `doc.clientID`).
19
+ /// A client can override its own state by propagating a message with an increasing timestamp
20
+ /// (`clock`). If such a message is received, it is applied if the known state of that client is
21
+ /// older than the new state (`clock < new_clock`). If a client thinks that a remote client is
22
+ /// offline, it may propagate a message with `{ clock, state: null, client }`. If such a message is
23
+ /// received, and the known clock of that client equals the received clock, it will clean the state.
24
+ ///
25
+ /// Before a client disconnects, it should propagate a `null` state with an updated clock.
26
+ pub struct Awareness {
27
+ doc: Doc,
28
+ states: HashMap<ClientID, String>,
29
+ meta: HashMap<ClientID, MetaClientState>,
30
+ on_update: Option<EventHandler<Event>>,
31
+ }
32
+
33
+ unsafe impl Send for Awareness {}
34
+ unsafe impl Sync for Awareness {}
35
+
36
+ impl Awareness {
37
+ /// Creates a new instance of [Awareness] struct, which operates over a given document.
38
+ /// Awareness instance has full ownership of that document. If necessary it can be accessed
39
+ /// using either [Awareness::doc] or [Awareness::doc_mut] methods.
40
+ pub fn new(doc: Doc) -> Self {
41
+ Awareness {
42
+ doc,
43
+ on_update: None,
44
+ states: HashMap::new(),
45
+ meta: HashMap::new(),
46
+ }
47
+ }
48
+
49
+ /// Returns a channel receiver for an incoming awareness events. This channel can be cloned.
50
+ pub fn on_update<F>(&mut self, f: F) -> Subscription<Event>
51
+ where
52
+ F: Fn(&Awareness, &Event) + 'static,
53
+ {
54
+ let eh = self.on_update.get_or_insert_with(EventHandler::default);
55
+ eh.subscribe(f)
56
+ }
57
+
58
+ /// Removes a receiver for incoming awareness events.
59
+ pub fn remove_on_update(&mut self, subscription_id: u32) {
60
+ if let Some(eh) = self.on_update.as_mut() {
61
+ eh.unsubscribe(subscription_id);
62
+ }
63
+ }
64
+
65
+ /// Returns a globally unique client ID of an underlying [Doc].
66
+ pub fn client_id(&self) -> ClientID {
67
+ self.doc.client_id
68
+ }
69
+
70
+ /// Returns a state map of all of the clients tracked by current [Awareness] instance. Those
71
+ /// states are identified by their corresponding [ClientID]s. The associated state is
72
+ /// represented and replicated to other clients as a JSON string.
73
+ pub fn clients(&self) -> &HashMap<ClientID, String> {
74
+ &self.states
75
+ }
76
+
77
+ /// Returns a JSON string state representation of a current [Awareness] instance.
78
+ pub fn local_state(&self) -> Option<&str> {
79
+ Some(self.states.get(&self.doc.client_id)?.as_str())
80
+ }
81
+
82
+ /// Sets a current [Awareness] instance state to a corresponding JSON string. This state will
83
+ /// be replicated to other clients as part of the [AwarenessUpdate] and it will trigger an event
84
+ /// to be emitted if current instance was created using [Awareness::with_observer] method.
85
+ ///
86
+ pub fn set_local_state<S: Into<String>>(&mut self, json: S) {
87
+ let client_id = self.doc.client_id;
88
+ self.update_meta(client_id);
89
+ let new: String = json.into();
90
+ match self.states.entry(client_id) {
91
+ Entry::Occupied(mut e) => {
92
+ e.insert(new);
93
+ if let Some(eh) = self.on_update.as_ref() {
94
+ eh.trigger(self, &Event::new(vec![], vec![client_id], vec![]));
95
+ }
96
+ }
97
+ Entry::Vacant(e) => {
98
+ e.insert(new);
99
+ if let Some(eh) = self.on_update.as_ref() {
100
+ eh.trigger(self, &Event::new(vec![client_id], vec![], vec![]));
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ /// Clears out a state of a given client, effectively marking it as disconnected.
107
+ pub fn remove_state(&mut self, client_id: ClientID) {
108
+ let prev_state = self.states.remove(&client_id);
109
+ self.update_meta(client_id);
110
+ if let Some(eh) = self.on_update.as_ref() {
111
+ if prev_state.is_some() {
112
+ eh.trigger(
113
+ self,
114
+ &Event::new(Vec::default(), Vec::default(), vec![client_id]),
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ /// Clears out a state of a current client (see: [Awareness::client_id]),
121
+ /// effectively marking it as disconnected.
122
+ pub fn clean_local_state(&mut self) {
123
+ let client_id = self.doc.client_id;
124
+ self.remove_state(client_id);
125
+ }
126
+
127
+ fn update_meta(&mut self, client_id: ClientID) {
128
+ match self.meta.entry(client_id) {
129
+ Entry::Occupied(mut e) => {
130
+ let clock = e.get().clock + 1;
131
+ let meta = MetaClientState::new(clock, Instant::now());
132
+ e.insert(meta);
133
+ }
134
+ Entry::Vacant(e) => {
135
+ e.insert(MetaClientState::new(1, Instant::now()));
136
+ }
137
+ }
138
+ }
139
+
140
+ /// Returns a serializable update object which is representation of a current Awareness state.
141
+ pub fn update(&self) -> Result<AwarenessUpdate, Error> {
142
+ let clients = self.states.keys().cloned();
143
+ self.update_with_clients(clients)
144
+ }
145
+
146
+ /// Returns a serializable update object which is representation of a current Awareness state.
147
+ /// Unlike [Awareness::update], this method variant allows to prepare update only for a subset
148
+ /// of known clients. These clients must all be known to a current [Awareness] instance,
149
+ /// otherwise a [Error::ClientNotFound] error will be returned.
150
+ pub fn update_with_clients<I: IntoIterator<Item = ClientID>>(
151
+ &self,
152
+ clients: I,
153
+ ) -> Result<AwarenessUpdate, Error> {
154
+ let mut res = HashMap::new();
155
+ for client_id in clients {
156
+ let clock = if let Some(meta) = self.meta.get(&client_id) {
157
+ meta.clock
158
+ } else {
159
+ return Err(Error::ClientNotFound(client_id));
160
+ };
161
+ let json = if let Some(json) = self.states.get(&client_id) {
162
+ json.clone()
163
+ } else {
164
+ String::from(NULL_STR)
165
+ };
166
+ res.insert(client_id, AwarenessUpdateEntry { clock, json });
167
+ }
168
+ Ok(AwarenessUpdate { clients: res })
169
+ }
170
+
171
+ /// Applies an update (incoming from remote channel or generated using [Awareness::update] /
172
+ /// [Awareness::update_with_clients] methods) and modifies a state of a current instance.
173
+ ///
174
+ /// If current instance has an observer channel (see: [Awareness::with_observer]), applied
175
+ /// changes will also be emitted as events.
176
+ pub fn apply_update(&mut self, update: AwarenessUpdate) -> Result<(), Error> {
177
+ let now = Instant::now();
178
+
179
+ let mut added = Vec::new();
180
+ let mut updated = Vec::new();
181
+ let mut removed = Vec::new();
182
+
183
+ for (client_id, entry) in update.clients {
184
+ let mut clock = entry.clock;
185
+ let is_null = entry.json.as_str() == NULL_STR;
186
+ match self.meta.entry(client_id) {
187
+ Entry::Occupied(mut e) => {
188
+ let prev = e.get();
189
+ let is_removed =
190
+ prev.clock == clock && is_null && self.states.contains_key(&client_id);
191
+ let is_new = prev.clock < clock;
192
+ if is_new || is_removed {
193
+ if is_null {
194
+ // never let a remote client remove this local state
195
+ if client_id == self.doc.client_id
196
+ && self.states.get(&client_id).is_some()
197
+ {
198
+ // remote client removed the local state. Do not remote state. Broadcast a message indicating
199
+ // that this client still exists by increasing the clock
200
+ clock += 1;
201
+ } else {
202
+ self.states.remove(&client_id);
203
+ if self.on_update.is_some() {
204
+ removed.push(client_id);
205
+ }
206
+ }
207
+ } else {
208
+ match self.states.entry(client_id) {
209
+ Entry::Occupied(mut e) => {
210
+ if self.on_update.is_some() {
211
+ updated.push(client_id);
212
+ }
213
+ e.insert(entry.json);
214
+ }
215
+ Entry::Vacant(e) => {
216
+ e.insert(entry.json);
217
+ if self.on_update.is_some() {
218
+ updated.push(client_id);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ e.insert(MetaClientState::new(clock, now));
224
+ true
225
+ } else {
226
+ false
227
+ }
228
+ }
229
+ Entry::Vacant(e) => {
230
+ e.insert(MetaClientState::new(clock, now));
231
+ self.states.insert(client_id, entry.json);
232
+ if self.on_update.is_some() {
233
+ added.push(client_id);
234
+ }
235
+ true
236
+ }
237
+ };
238
+ }
239
+
240
+ if let Some(eh) = self.on_update.as_ref() {
241
+ if !added.is_empty() || !updated.is_empty() || !removed.is_empty() {
242
+ eh.trigger(self, &Event::new(added, updated, removed));
243
+ }
244
+ }
245
+
246
+ Ok(())
247
+ }
248
+ }
249
+
250
+ impl Default for Awareness {
251
+ fn default() -> Self {
252
+ Awareness::new(Doc::new())
253
+ }
254
+ }
255
+
256
+ #[allow(clippy::type_complexity)]
257
+ struct EventHandler<T> {
258
+ seq_nr: u32,
259
+ subscribers: Rc<RefCell<HashMap<u32, Box<dyn Fn(&Awareness, &T)>>>>,
260
+ }
261
+
262
+ impl<T> EventHandler<T> {
263
+ pub fn subscribe<F>(&mut self, f: F) -> Subscription<T>
264
+ where
265
+ F: Fn(&Awareness, &T) + 'static,
266
+ {
267
+ let subscription_id = self.seq_nr;
268
+ self.seq_nr += 1;
269
+ {
270
+ let func = Box::new(f);
271
+ let mut subs = self.subscribers.borrow_mut();
272
+ subs.insert(subscription_id, func);
273
+ }
274
+ Subscription {
275
+ subscription_id,
276
+ subscribers: Rc::downgrade(&self.subscribers),
277
+ }
278
+ }
279
+
280
+ pub fn unsubscribe(&mut self, subscription_id: u32) {
281
+ let mut subs = self.subscribers.borrow_mut();
282
+ subs.remove(&subscription_id);
283
+ }
284
+
285
+ pub fn trigger(&self, awareness: &Awareness, arg: &T) {
286
+ let subs = self.subscribers.borrow();
287
+ for func in subs.values() {
288
+ func(awareness, arg);
289
+ }
290
+ }
291
+ }
292
+
293
+ impl<T> Default for EventHandler<T> {
294
+ fn default() -> Self {
295
+ EventHandler {
296
+ seq_nr: 0,
297
+ subscribers: Rc::new(RefCell::new(HashMap::new())),
298
+ }
299
+ }
300
+ }
301
+
302
+ /// Whenever a new callback is being registered, a [Subscription] is made. Whenever this
303
+ /// subscription a registered callback is cancelled and will not be called any more.
304
+ #[allow(clippy::type_complexity)]
305
+ pub struct Subscription<T> {
306
+ subscription_id: u32,
307
+ subscribers: Weak<RefCell<HashMap<u32, Box<dyn Fn(&Awareness, &T)>>>>,
308
+ }
309
+
310
+ #[allow(clippy::from_over_into)]
311
+ impl<T> Into<SubscriptionId> for Subscription<T> {
312
+ fn into(self) -> SubscriptionId {
313
+ let id = self.subscription_id;
314
+ std::mem::forget(self);
315
+ id
316
+ }
317
+ }
318
+
319
+ impl<T> Drop for Subscription<T> {
320
+ fn drop(&mut self) {
321
+ if let Some(subs) = self.subscribers.upgrade() {
322
+ let mut s = subs.borrow_mut();
323
+ s.remove(&self.subscription_id);
324
+ }
325
+ }
326
+ }
327
+
328
+ /// A structure that represents an encodable state of an [Awareness] struct.
329
+ #[derive(Debug, Eq, PartialEq)]
330
+ pub struct AwarenessUpdate {
331
+ clients: HashMap<ClientID, AwarenessUpdateEntry>,
332
+ }
333
+
334
+ impl Encode for AwarenessUpdate {
335
+ fn encode<E: Encoder>(&self, encoder: &mut E) {
336
+ encoder.write_var(self.clients.len());
337
+ for (&client_id, e) in self.clients.iter() {
338
+ encoder.write_var(client_id);
339
+ encoder.write_var(e.clock);
340
+ encoder.write_string(&e.json);
341
+ }
342
+ }
343
+ }
344
+
345
+ impl Decode for AwarenessUpdate {
346
+ fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, lib0::error::Error> {
347
+ let len: usize = decoder.read_var()?;
348
+ let mut clients = HashMap::with_capacity(len);
349
+ for _ in 0..len {
350
+ let client_id: ClientID = decoder.read_var()?;
351
+ let clock: u32 = decoder.read_var()?;
352
+ let json = decoder.read_string()?.to_string();
353
+ clients.insert(client_id, AwarenessUpdateEntry { clock, json });
354
+ }
355
+
356
+ Ok(AwarenessUpdate { clients })
357
+ }
358
+ }
359
+
360
+ /// A single client entry of an [AwarenessUpdate]. It consists of logical clock and JSON client
361
+ /// state represented as a string.
362
+ #[derive(Debug, Eq, PartialEq)]
363
+ pub struct AwarenessUpdateEntry {
364
+ clock: u32,
365
+ json: String,
366
+ }
367
+
368
+ /// Errors generated by an [Awareness] struct methods.
369
+ #[derive(Error, Debug)]
370
+ pub enum Error {
371
+ /// Client ID was not found in [Awareness] metadata.
372
+ #[error("client ID `{0}` not found")]
373
+ ClientNotFound(ClientID),
374
+ }
375
+
376
+ #[derive(Debug, Clone, PartialEq, Eq)]
377
+ struct MetaClientState {
378
+ clock: u32,
379
+ last_updated: Instant,
380
+ }
381
+
382
+ impl MetaClientState {
383
+ fn new(clock: u32, last_updated: Instant) -> Self {
384
+ MetaClientState {
385
+ clock,
386
+ last_updated,
387
+ }
388
+ }
389
+ }
390
+
391
+ /// Event type emitted by an [Awareness] struct.
392
+ #[derive(Debug, Default, Clone, Eq, PartialEq)]
393
+ pub struct Event {
394
+ added: Vec<ClientID>,
395
+ updated: Vec<ClientID>,
396
+ removed: Vec<ClientID>,
397
+ }
398
+
399
+ impl Event {
400
+ pub fn new(added: Vec<ClientID>, updated: Vec<ClientID>, removed: Vec<ClientID>) -> Self {
401
+ Event {
402
+ added,
403
+ updated,
404
+ removed,
405
+ }
406
+ }
407
+
408
+ /// Collection of new clients that have been added to an [Awareness] struct, that was not known
409
+ /// before. Actual client state can be accessed via `awareness.clients().get(client_id)`.
410
+ pub fn added(&self) -> &[ClientID] {
411
+ &self.added
412
+ }
413
+
414
+ /// Collection of new clients that have been updated within an [Awareness] struct since the last
415
+ /// update. Actual client state can be accessed via `awareness.clients().get(client_id)`.
416
+ pub fn updated(&self) -> &[ClientID] {
417
+ &self.updated
418
+ }
419
+
420
+ /// Collection of new clients that have been removed from [Awareness] struct since the last
421
+ /// update.
422
+ pub fn removed(&self) -> &[ClientID] {
423
+ &self.removed
424
+ }
425
+ }