y-rb 0.1.7-arm64-darwin → 0.2.0-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c7ae82dbb88d508d98b1e87f7f7e67a102bb97ca228a8deea669c10e38ba2e1
4
- data.tar.gz: b72afe1360223ef130a5b9861766bbace0e9faf56ffa66d174d933814de91362
3
+ metadata.gz: cd1b418e9e3364905d97ce0738d366214c90de9ba04c9718f3ff33d8b1a96c18
4
+ data.tar.gz: 59b503f6eee7cf2f53aef6a1ab947957192cec3efc872f3fbf800f452e910d7d
5
5
  SHA512:
6
- metadata.gz: df0ea4eea521ea3a0c49519410f416b30f4e51667c7777274079391f99bdc9c07aaa4ec8da18dace0964cf187d57787b45fc15245e675a707cee0dc781d30467
7
- data.tar.gz: 920bf7721948ee98a2711a358e6bac813a360acba8158dd1371024f1d83186344a6639ab432dfcedc359cfff9151ea9dbad05055347a7d39d1c95a867b54b744
6
+ metadata.gz: b8bff25911b00e3bad728b0e5f8a1d38a7371e3bcc07fa651d48524b3bdc8df25549e0a9a3ee5af6908940d682beb2f88de1b4cbf24184a7382bbbeb998d5617
7
+ data.tar.gz: 9f1f811cc68c2c950204a3d6cc4b158114abf55ad5b1ecaa09b665f2fc51052796b3c66b2a35925963e983011a82119ae74f05911b506ffc955ecfd7f36b0ac1
data/ext/yrb/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "yrb"
3
- version = "0.1.7"
3
+ version = "0.2.0"
4
4
  authors = ["Hannes Moser <box@hannesmoser.at>", "Hannes Moser <hmoser@gitlab.com>"]
5
5
  edition = "2021"
6
6
  homepage = "https://github.com/y-crdt/yrb"
@@ -9,6 +9,7 @@ repository = "https://github.com/y-crdt/yrb"
9
9
  [dependencies]
10
10
  lib0 = "0.12.0" # must match yrs version
11
11
  magnus = { git = "https://github.com/matsadler/magnus", rev = "cc852bfa31992d882d42509b1165eb5f67f9dc2c" } # waiting for release with full rb-sys backend
12
+ thiserror = "1.0.37"
12
13
  yrs = "0.12.0"
13
14
 
14
15
  [dev-dependencies]
@@ -0,0 +1,440 @@
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(
95
+ self,
96
+ &Event::new(vec![], vec![client_id], vec![])
97
+ );
98
+ }
99
+ }
100
+ Entry::Vacant(e) => {
101
+ e.insert(new);
102
+ if let Some(eh) = self.on_update.as_ref() {
103
+ eh.trigger(
104
+ self,
105
+ &Event::new(vec![client_id], vec![], vec![])
106
+ );
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ /// Clears out a state of a given client, effectively marking it as disconnected.
113
+ pub fn remove_state(&mut self, client_id: ClientID) {
114
+ let prev_state = self.states.remove(&client_id);
115
+ self.update_meta(client_id);
116
+ if let Some(eh) = self.on_update.as_ref() {
117
+ if prev_state.is_some() {
118
+ eh.trigger(
119
+ self,
120
+ &Event::new(
121
+ Vec::default(),
122
+ Vec::default(),
123
+ vec![client_id]
124
+ )
125
+ );
126
+ }
127
+ }
128
+ }
129
+
130
+ /// Clears out a state of a current client (see: [Awareness::client_id]),
131
+ /// effectively marking it as disconnected.
132
+ pub fn clean_local_state(&mut self) {
133
+ let client_id = self.doc.client_id;
134
+ self.remove_state(client_id);
135
+ }
136
+
137
+ fn update_meta(&mut self, client_id: ClientID) {
138
+ match self.meta.entry(client_id) {
139
+ Entry::Occupied(mut e) => {
140
+ let clock = e.get().clock + 1;
141
+ let meta = MetaClientState::new(clock, Instant::now());
142
+ e.insert(meta);
143
+ }
144
+ Entry::Vacant(e) => {
145
+ e.insert(MetaClientState::new(1, Instant::now()));
146
+ }
147
+ }
148
+ }
149
+
150
+ /// Returns a serializable update object which is representation of a current Awareness state.
151
+ pub fn update(&self) -> Result<AwarenessUpdate, Error> {
152
+ let clients = self.states.keys().cloned();
153
+ self.update_with_clients(clients)
154
+ }
155
+
156
+ /// Returns a serializable update object which is representation of a current Awareness state.
157
+ /// Unlike [Awareness::update], this method variant allows to prepare update only for a subset
158
+ /// of known clients. These clients must all be known to a current [Awareness] instance,
159
+ /// otherwise a [Error::ClientNotFound] error will be returned.
160
+ pub fn update_with_clients<I: IntoIterator<Item = ClientID>>(
161
+ &self,
162
+ clients: I
163
+ ) -> Result<AwarenessUpdate, Error> {
164
+ let mut res = HashMap::new();
165
+ for client_id in clients {
166
+ let clock = if let Some(meta) = self.meta.get(&client_id) {
167
+ meta.clock
168
+ } else {
169
+ return Err(Error::ClientNotFound(client_id));
170
+ };
171
+ let json = if let Some(json) = self.states.get(&client_id) {
172
+ json.clone()
173
+ } else {
174
+ String::from(NULL_STR)
175
+ };
176
+ res.insert(client_id, AwarenessUpdateEntry { clock, json });
177
+ }
178
+ Ok(AwarenessUpdate { clients: res })
179
+ }
180
+
181
+ /// Applies an update (incoming from remote channel or generated using [Awareness::update] /
182
+ /// [Awareness::update_with_clients] methods) and modifies a state of a current instance.
183
+ ///
184
+ /// If current instance has an observer channel (see: [Awareness::with_observer]), applied
185
+ /// changes will also be emitted as events.
186
+ pub fn apply_update(
187
+ &mut self,
188
+ update: AwarenessUpdate
189
+ ) -> Result<(), Error> {
190
+ let now = Instant::now();
191
+
192
+ let mut added = Vec::new();
193
+ let mut updated = Vec::new();
194
+ let mut removed = Vec::new();
195
+
196
+ for (client_id, entry) in update.clients {
197
+ let mut clock = entry.clock;
198
+ let is_null = entry.json.as_str() == NULL_STR;
199
+ match self.meta.entry(client_id) {
200
+ Entry::Occupied(mut e) => {
201
+ let prev = e.get();
202
+ let is_removed = prev.clock == clock
203
+ && is_null
204
+ && self.states.contains_key(&client_id);
205
+ let is_new = prev.clock < clock;
206
+ if is_new || is_removed {
207
+ if is_null {
208
+ // never let a remote client remove this local state
209
+ if client_id == self.doc.client_id
210
+ && self.states.get(&client_id).is_some()
211
+ {
212
+ // remote client removed the local state. Do not remote state. Broadcast a message indicating
213
+ // that this client still exists by increasing the clock
214
+ clock += 1;
215
+ } else {
216
+ self.states.remove(&client_id);
217
+ if self.on_update.is_some() {
218
+ removed.push(client_id);
219
+ }
220
+ }
221
+ } else {
222
+ match self.states.entry(client_id) {
223
+ Entry::Occupied(mut e) => {
224
+ if self.on_update.is_some() {
225
+ updated.push(client_id);
226
+ }
227
+ e.insert(entry.json);
228
+ }
229
+ Entry::Vacant(e) => {
230
+ e.insert(entry.json);
231
+ if self.on_update.is_some() {
232
+ updated.push(client_id);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ e.insert(MetaClientState::new(clock, now));
238
+ true
239
+ } else {
240
+ false
241
+ }
242
+ }
243
+ Entry::Vacant(e) => {
244
+ e.insert(MetaClientState::new(clock, now));
245
+ self.states.insert(client_id, entry.json);
246
+ if self.on_update.is_some() {
247
+ added.push(client_id);
248
+ }
249
+ true
250
+ }
251
+ };
252
+ }
253
+
254
+ if let Some(eh) = self.on_update.as_ref() {
255
+ if !added.is_empty() || !updated.is_empty() || !removed.is_empty() {
256
+ eh.trigger(self, &Event::new(added, updated, removed));
257
+ }
258
+ }
259
+
260
+ Ok(())
261
+ }
262
+ }
263
+
264
+ impl Default for Awareness {
265
+ fn default() -> Self {
266
+ Awareness::new(Doc::new())
267
+ }
268
+ }
269
+
270
+ struct EventHandler<T> {
271
+ seq_nr: u32,
272
+ subscribers: Rc<RefCell<HashMap<u32, Box<dyn Fn(&Awareness, &T) -> ()>>>>
273
+ }
274
+
275
+ impl<T> EventHandler<T> {
276
+ pub fn subscribe<F>(&mut self, f: F) -> Subscription<T>
277
+ where
278
+ F: Fn(&Awareness, &T) -> () + 'static
279
+ {
280
+ let subscription_id = self.seq_nr;
281
+ self.seq_nr += 1;
282
+ {
283
+ let func = Box::new(f);
284
+ let mut subs = self.subscribers.borrow_mut();
285
+ subs.insert(subscription_id, func);
286
+ }
287
+ Subscription {
288
+ subscription_id,
289
+ subscribers: Rc::downgrade(&self.subscribers)
290
+ }
291
+ }
292
+
293
+ pub fn unsubscribe(&mut self, subscription_id: u32) {
294
+ let mut subs = self.subscribers.borrow_mut();
295
+ subs.remove(&subscription_id);
296
+ }
297
+
298
+ pub fn trigger(&self, awareness: &Awareness, arg: &T) {
299
+ let subs = self.subscribers.borrow();
300
+ for func in subs.values() {
301
+ func(awareness, arg);
302
+ }
303
+ }
304
+ }
305
+
306
+ impl<T> Default for EventHandler<T> {
307
+ fn default() -> Self {
308
+ EventHandler {
309
+ seq_nr: 0,
310
+ subscribers: Rc::new(RefCell::new(HashMap::new()))
311
+ }
312
+ }
313
+ }
314
+
315
+ /// Whenever a new callback is being registered, a [Subscription] is made. Whenever this
316
+ /// subscription a registered callback is cancelled and will not be called any more.
317
+ pub struct Subscription<T> {
318
+ subscription_id: u32,
319
+ subscribers: Weak<RefCell<HashMap<u32, Box<dyn Fn(&Awareness, &T) -> ()>>>>
320
+ }
321
+
322
+ impl<T> Into<SubscriptionId> for Subscription<T> {
323
+ fn into(self) -> SubscriptionId {
324
+ let id = self.subscription_id;
325
+ std::mem::forget(self);
326
+ id
327
+ }
328
+ }
329
+
330
+ impl<T> Drop for Subscription<T> {
331
+ fn drop(&mut self) {
332
+ if let Some(subs) = self.subscribers.upgrade() {
333
+ let mut s = subs.borrow_mut();
334
+ s.remove(&self.subscription_id);
335
+ }
336
+ }
337
+ }
338
+
339
+ /// A structure that represents an encodable state of an [Awareness] struct.
340
+ #[derive(Debug, Eq, PartialEq)]
341
+ pub struct AwarenessUpdate {
342
+ clients: HashMap<ClientID, AwarenessUpdateEntry>
343
+ }
344
+
345
+ impl Encode for AwarenessUpdate {
346
+ fn encode<E: Encoder>(&self, encoder: &mut E) {
347
+ encoder.write_var(self.clients.len());
348
+ for (&client_id, e) in self.clients.iter() {
349
+ encoder.write_var(client_id);
350
+ encoder.write_var(e.clock);
351
+ encoder.write_string(&e.json);
352
+ }
353
+ }
354
+ }
355
+
356
+ impl Decode for AwarenessUpdate {
357
+ fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, lib0::error::Error> {
358
+ let len: usize = decoder.read_var()?;
359
+ let mut clients = HashMap::with_capacity(len);
360
+ for _ in 0..len {
361
+ let client_id: ClientID = decoder.read_var()?;
362
+ let clock: u32 = decoder.read_var()?;
363
+ let json = decoder.read_string()?.to_string();
364
+ clients.insert(client_id, AwarenessUpdateEntry { clock, json });
365
+ }
366
+
367
+ Ok(AwarenessUpdate { clients })
368
+ }
369
+ }
370
+
371
+ /// A single client entry of an [AwarenessUpdate]. It consists of logical clock and JSON client
372
+ /// state represented as a string.
373
+ #[derive(Debug, Eq, PartialEq)]
374
+ pub struct AwarenessUpdateEntry {
375
+ clock: u32,
376
+ json: String
377
+ }
378
+
379
+ /// Errors generated by an [Awareness] struct methods.
380
+ #[derive(Error, Debug)]
381
+ pub enum Error {
382
+ /// Client ID was not found in [Awareness] metadata.
383
+ #[error("client ID `{0}` not found")]
384
+ ClientNotFound(ClientID)
385
+ }
386
+
387
+ #[derive(Debug, Clone, PartialEq, Eq)]
388
+ struct MetaClientState {
389
+ clock: u32,
390
+ last_updated: Instant
391
+ }
392
+
393
+ impl MetaClientState {
394
+ fn new(clock: u32, last_updated: Instant) -> Self {
395
+ MetaClientState {
396
+ clock,
397
+ last_updated
398
+ }
399
+ }
400
+ }
401
+
402
+ /// Event type emitted by an [Awareness] struct.
403
+ #[derive(Debug, Default, Clone, Eq, PartialEq)]
404
+ pub struct Event {
405
+ added: Vec<ClientID>,
406
+ updated: Vec<ClientID>,
407
+ removed: Vec<ClientID>
408
+ }
409
+
410
+ impl Event {
411
+ pub fn new(
412
+ added: Vec<ClientID>,
413
+ updated: Vec<ClientID>,
414
+ removed: Vec<ClientID>
415
+ ) -> Self {
416
+ Event {
417
+ added,
418
+ updated,
419
+ removed
420
+ }
421
+ }
422
+
423
+ /// Collection of new clients that have been added to an [Awareness] struct, that was not known
424
+ /// before. Actual client state can be accessed via `awareness.clients().get(client_id)`.
425
+ pub fn added(&self) -> &[ClientID] {
426
+ &self.added
427
+ }
428
+
429
+ /// Collection of new clients that have been updated within an [Awareness] struct since the last
430
+ /// update. Actual client state can be accessed via `awareness.clients().get(client_id)`.
431
+ pub fn updated(&self) -> &[ClientID] {
432
+ &self.updated
433
+ }
434
+
435
+ /// Collection of new clients that have been removed from [Awareness] struct since the last
436
+ /// update.
437
+ pub fn removed(&self) -> &[ClientID] {
438
+ &self.removed
439
+ }
440
+ }
data/ext/yrb/src/lib.rs CHANGED
@@ -1,4 +1,7 @@
1
+ extern crate core;
2
+
1
3
  use crate::yarray::YArray;
4
+ use crate::yawareness::{YAwareness, YAwarenessEvent, YAwarenessUpdate};
2
5
  use crate::ydoc::YDoc;
3
6
  use crate::ymap::YMap;
4
7
  use crate::ytext::YText;
@@ -7,10 +10,12 @@ use crate::yxml_element::YXmlElement;
7
10
  use crate::yxml_text::YXmlText;
8
11
  use magnus::{define_module, function, method, Error, Module, Object};
9
12
 
13
+ mod awareness;
10
14
  mod utils;
11
15
  mod yany;
12
16
  mod yarray;
13
17
  mod yattrs;
18
+ mod yawareness;
14
19
  mod ydoc;
15
20
  mod ymap;
16
21
  mod ytext;
@@ -472,5 +477,104 @@ fn init() -> Result<(), Error> {
472
477
  )
473
478
  .expect("cannot define private method: yxml_text_to_s");
474
479
 
480
+ let yawareness = module
481
+ .define_class("Awareness", Default::default())
482
+ .expect("cannot define class Y::Awareness");
483
+ yawareness
484
+ .define_singleton_method(
485
+ "new",
486
+ function!(YAwareness::yawareness_new, 0)
487
+ )
488
+ .expect("cannot define singleton method: yawareness_new");
489
+ yawareness
490
+ .define_private_method(
491
+ "yawareness_apply_update",
492
+ method!(YAwareness::yawareness_apply_update, 1)
493
+ )
494
+ .expect("cannot define private method: yawareness_apply_update");
495
+ yawareness
496
+ .define_private_method(
497
+ "yawareness_clean_local_state",
498
+ method!(YAwareness::yawareness_clean_local_state, 0)
499
+ )
500
+ .expect("cannot define private method: yawareness_clean_local_state");
501
+ yawareness
502
+ .define_private_method(
503
+ "yawareness_clients",
504
+ method!(YAwareness::yawareness_clients, 0)
505
+ )
506
+ .expect("cannot define private method: yawareness_clients");
507
+ yawareness
508
+ .define_private_method(
509
+ "yawareness_client_id",
510
+ method!(YAwareness::yawareness_client_id, 0)
511
+ )
512
+ .expect("cannot define private method: yawareness_client_id");
513
+ yawareness
514
+ .define_private_method(
515
+ "yawareness_local_state",
516
+ method!(YAwareness::yawareness_local_state, 0)
517
+ )
518
+ .expect("cannot define private method: yawareness_local_state");
519
+ yawareness
520
+ .define_private_method(
521
+ "yawareness_on_update",
522
+ method!(YAwareness::yawareness_on_update, 1)
523
+ )
524
+ .expect("cannot define private method: yawareness_on_update");
525
+ yawareness
526
+ .define_private_method(
527
+ "yawareness_remove_on_update",
528
+ method!(YAwareness::yawareness_remove_on_update, 1)
529
+ )
530
+ .expect("cannot define private method: yawareness_remove_on_update");
531
+ yawareness
532
+ .define_private_method(
533
+ "yawareness_remove_state",
534
+ method!(YAwareness::yawareness_remove_state, 1)
535
+ )
536
+ .expect("cannot define private method: yawareness_remove_state");
537
+ yawareness
538
+ .define_private_method(
539
+ "yawareness_set_local_state",
540
+ method!(YAwareness::yawareness_set_local_state, 1)
541
+ )
542
+ .expect("cannot define private method: yawareness_set_local_state");
543
+ yawareness
544
+ .define_private_method(
545
+ "yawareness_update",
546
+ method!(YAwareness::yawareness_update, 0)
547
+ )
548
+ .expect("cannot define private method: yawareness_update");
549
+ yawareness
550
+ .define_private_method(
551
+ "yawareness_update_with_clients",
552
+ method!(YAwareness::yawareness_update_with_clients, 1)
553
+ )
554
+ .expect("cannot define private method: yawareness_update_with_clients");
555
+
556
+ let yawareness_update = module
557
+ .define_class("AwarenessUpdate", Default::default())
558
+ .expect("cannot define class Y:AwarenessUpdate");
559
+ yawareness_update
560
+ .define_private_method(
561
+ "yawareness_update_encode",
562
+ method!(YAwarenessUpdate::yawareness_update_encode, 0)
563
+ )
564
+ .expect("cannot define private method: yawareness_update_encode");
565
+
566
+ let yawareness_event = module
567
+ .define_class("AwarenessEvent", Default::default())
568
+ .expect("cannot define class Y:AwarenessEvent");
569
+ yawareness_event
570
+ .define_method("added", method!(YAwarenessEvent::added, 0))
571
+ .expect("cannot define private method: added");
572
+ yawareness_event
573
+ .define_method("updated", method!(YAwarenessEvent::updated, 0))
574
+ .expect("cannot define private method: updated");
575
+ yawareness_event
576
+ .define_method("removed", method!(YAwarenessEvent::removed, 0))
577
+ .expect("cannot define private method: removed");
578
+
475
579
  Ok(())
476
580
  }
@@ -15,7 +15,7 @@ pub(crate) struct YArray(pub(crate) RefCell<Array>);
15
15
  unsafe impl Send for YArray {}
16
16
 
17
17
  impl YArray {
18
- pub(crate) fn yarray_each(&self, block: Proc) -> () {
18
+ pub(crate) fn yarray_each(&self, block: Proc) {
19
19
  self.0.borrow_mut().iter().for_each(|val| {
20
20
  let yvalue = YValue::from(val);
21
21
  let args = (yvalue.into(),);
@@ -31,7 +31,7 @@ impl YArray {
31
31
  transaction: &YTransaction,
32
32
  index: u32,
33
33
  value: Value
34
- ) -> () {
34
+ ) {
35
35
  let yvalue = YValue::from(value);
36
36
  let avalue = Any::from(yvalue);
37
37
  self.0.borrow_mut().insert(
@@ -45,7 +45,7 @@ impl YArray {
45
45
  transaction: &YTransaction,
46
46
  index: u32,
47
47
  values: RArray
48
- ) -> () {
48
+ ) {
49
49
  let arr: Vec<Any> = values
50
50
  .each()
51
51
  .into_iter()
@@ -103,7 +103,7 @@ impl YArray {
103
103
  })
104
104
  .partition(Result::is_ok);
105
105
 
106
- if errors.len() == 0 {
106
+ if errors.is_empty() {
107
107
  let args = (RArray::from_vec(
108
108
  changes.into_iter().map(Result::unwrap).collect()
109
109
  ),);
@@ -123,7 +123,7 @@ impl YArray {
123
123
  &self,
124
124
  transaction: &YTransaction,
125
125
  value: Value
126
- ) -> () {
126
+ ) {
127
127
  let yvalue = YValue::from(value);
128
128
  let avalue = Any::from(yvalue);
129
129
  self.0
@@ -134,7 +134,7 @@ impl YArray {
134
134
  &self,
135
135
  transaction: &YTransaction,
136
136
  value: Value
137
- ) -> () {
137
+ ) {
138
138
  let yvalue = YValue::from(value);
139
139
  let avalue = Any::from(yvalue);
140
140
  self.0
@@ -145,7 +145,7 @@ impl YArray {
145
145
  &self,
146
146
  transaction: &YTransaction,
147
147
  index: u32
148
- ) -> () {
148
+ ) {
149
149
  self.0
150
150
  .borrow_mut()
151
151
  .remove(&mut transaction.0.borrow_mut(), index)
@@ -155,7 +155,7 @@ impl YArray {
155
155
  transaction: &YTransaction,
156
156
  index: u32,
157
157
  len: u32
158
- ) -> () {
158
+ ) {
159
159
  self.0.borrow_mut().remove_range(
160
160
  &mut transaction.0.borrow_mut(),
161
161
  index,
@@ -170,9 +170,9 @@ impl YArray {
170
170
  .map(|v| YValue::from(v).into())
171
171
  .collect::<Vec<Value>>();
172
172
 
173
- return RArray::from_vec(arr);
173
+ RArray::from_vec(arr)
174
174
  }
175
- pub(crate) fn yarray_unobserve(&self, subscription_id: u32) -> () {
175
+ pub(crate) fn yarray_unobserve(&self, subscription_id: u32) {
176
176
  self.0.borrow_mut().unobserve(subscription_id);
177
177
  }
178
178
  }
@@ -10,7 +10,7 @@ pub(crate) struct YAttrs(pub(crate) Attrs);
10
10
 
11
11
  impl From<Attrs> for YAttrs {
12
12
  fn from(value: Attrs) -> Self {
13
- YAttrs { 0: value }
13
+ YAttrs(value)
14
14
  }
15
15
  }
16
16
 
@@ -29,7 +29,7 @@ impl From<RHash> for YAttrs {
29
29
  })
30
30
  .expect("cannot iterate attributes hash");
31
31
 
32
- YAttrs { 0: attrs }
32
+ YAttrs(attrs)
33
33
  }
34
34
  }
35
35
 
@@ -0,0 +1,163 @@
1
+ use crate::awareness::{Awareness, AwarenessUpdate, Event};
2
+ use magnus::block::Proc;
3
+ use magnus::{Error, Value};
4
+ use std::borrow::Borrow;
5
+ use std::cell::RefCell;
6
+ use std::collections::HashMap;
7
+ use yrs::block::ClientID;
8
+ use yrs::updates::decoder::Decode;
9
+ use yrs::updates::encoder::Encode;
10
+ use yrs::Doc;
11
+
12
+ #[magnus::wrap(class = "Y::Awareness")]
13
+ pub(crate) struct YAwareness(pub(crate) RefCell<Awareness>);
14
+
15
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
16
+ unsafe impl Send for YAwareness {}
17
+
18
+ impl YAwareness {
19
+ pub(crate) fn yawareness_new() -> Self {
20
+ let doc = Doc::new();
21
+ let awareness = Awareness::new(doc);
22
+
23
+ Self(RefCell::new(awareness))
24
+ }
25
+
26
+ pub(crate) fn yawareness_apply_update(
27
+ &self,
28
+ update: &YAwarenessUpdate
29
+ ) -> Result<(), Error> {
30
+ update.decode().and_then(|value| {
31
+ self.0.borrow_mut().apply_update(value).map_err(|_error| {
32
+ Error::runtime_error("cannot decode awareness update")
33
+ })
34
+ })
35
+ }
36
+
37
+ pub(crate) fn yawareness_clean_local_state(&self) {
38
+ self.0.borrow_mut().clean_local_state();
39
+ }
40
+
41
+ pub(crate) fn yawareness_client_id(&self) -> ClientID {
42
+ self.0.borrow().client_id()
43
+ }
44
+
45
+ pub(crate) fn yawareness_clients(&self) -> HashMap<ClientID, String> {
46
+ self.0.borrow().clients().to_owned()
47
+ }
48
+
49
+ pub(crate) fn yawareness_local_state(&self) -> Option<String> {
50
+ self.0.borrow().local_state().map(|value| value.to_string())
51
+ }
52
+
53
+ pub(crate) fn yawareness_on_update(
54
+ &self,
55
+ block: Proc
56
+ ) -> Result<u32, Error> {
57
+ let subscription_id = self
58
+ .0
59
+ .borrow_mut()
60
+ .on_update(move |_awareness, event| {
61
+ let awareness_event = YAwarenessEvent::from(event);
62
+ let args = (awareness_event,);
63
+ block
64
+ .call::<(YAwarenessEvent,), Value>(args)
65
+ .expect("cannot call block: on_update");
66
+ })
67
+ .into();
68
+
69
+ Ok(subscription_id)
70
+ }
71
+
72
+ pub(crate) fn yawareness_remove_on_update(&self, subscription_id: u32) {
73
+ self.0.borrow_mut().remove_on_update(subscription_id)
74
+ }
75
+
76
+ pub(crate) fn yawareness_remove_state(&self, client_id: ClientID) {
77
+ self.0.borrow_mut().remove_state(client_id)
78
+ }
79
+
80
+ pub(crate) fn yawareness_set_local_state(&self, json: String) {
81
+ self.0.borrow_mut().set_local_state(json)
82
+ }
83
+
84
+ pub(crate) fn yawareness_update(&self) -> Result<YAwarenessUpdate, Error> {
85
+ self.0
86
+ .borrow_mut()
87
+ .update()
88
+ .map(YAwarenessUpdate::from)
89
+ .map_err(|_error| Error::runtime_error("cannot update awareness"))
90
+ }
91
+
92
+ pub(crate) fn yawareness_update_with_clients(
93
+ &self,
94
+ clients: Vec<ClientID>
95
+ ) -> Result<YAwarenessUpdate, Error> {
96
+ self.0
97
+ .borrow_mut()
98
+ .update_with_clients(clients)
99
+ .map(YAwarenessUpdate::from)
100
+ .map_err(|_error| {
101
+ Error::runtime_error("cannot update awareness with clients")
102
+ })
103
+ }
104
+ }
105
+
106
+ impl From<Awareness> for YAwareness {
107
+ fn from(value: Awareness) -> Self {
108
+ Self(RefCell::from(value))
109
+ }
110
+ }
111
+
112
+ #[magnus::wrap(class = "Y::AwarenessUpdate")]
113
+ pub(crate) struct YAwarenessUpdate(pub(crate) Vec<u8>);
114
+
115
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
116
+ unsafe impl Send for YAwarenessUpdate {}
117
+
118
+ impl YAwarenessUpdate {
119
+ pub(crate) fn decode(&self) -> Result<AwarenessUpdate, Error> {
120
+ AwarenessUpdate::decode_v1(self.0.borrow()).map_err(|_error| {
121
+ Error::runtime_error("cannot decode awareness update")
122
+ })
123
+ }
124
+ pub(crate) fn yawareness_update_encode(&self) -> Vec<u8> {
125
+ self.0.to_vec()
126
+ }
127
+ }
128
+
129
+ impl From<AwarenessUpdate> for YAwarenessUpdate {
130
+ fn from(value: AwarenessUpdate) -> Self {
131
+ YAwarenessUpdate(value.encode_v1())
132
+ }
133
+ }
134
+
135
+ impl From<Vec<u8>> for YAwarenessUpdate {
136
+ fn from(value: Vec<u8>) -> Self {
137
+ YAwarenessUpdate(value)
138
+ }
139
+ }
140
+
141
+ #[magnus::wrap(class = "Y::AwarenessEvent")]
142
+ pub(crate) struct YAwarenessEvent(Event);
143
+
144
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
145
+ unsafe impl Send for YAwarenessEvent {}
146
+
147
+ impl YAwarenessEvent {
148
+ pub(crate) fn added(&self) -> Vec<ClientID> {
149
+ self.0.borrow().added().to_vec()
150
+ }
151
+ pub(crate) fn updated(&self) -> Vec<ClientID> {
152
+ self.0.borrow().updated().to_vec()
153
+ }
154
+ pub(crate) fn removed(&self) -> Vec<ClientID> {
155
+ self.0.borrow().removed().to_vec()
156
+ }
157
+ }
158
+
159
+ impl From<&Event> for YAwarenessEvent {
160
+ fn from(value: &Event) -> Self {
161
+ Self(value.clone())
162
+ }
163
+ }
data/ext/yrb/src/ydoc.rs CHANGED
@@ -26,14 +26,14 @@ impl YDoc {
26
26
  pub(crate) fn ydoc_transact(&self) -> YTransaction {
27
27
  let transaction = self.0.borrow().transact();
28
28
 
29
- return YTransaction(RefCell::new(transaction));
29
+ YTransaction(RefCell::new(transaction))
30
30
  }
31
31
  pub(crate) fn ydoc_encode_diff_v1(
32
32
  &self,
33
- state_vector: Vec<u8>,
33
+ state_vector: Vec<u8>
34
34
  ) -> Result<Vec<u8>, Error> {
35
- return StateVector::decode_v1(&*state_vector)
35
+ StateVector::decode_v1(&*state_vector)
36
36
  .map(|sv| self.0.borrow().encode_state_as_update_v1(&sv))
37
- .map_err(|_e| Error::runtime_error("cannot encode diff"));
37
+ .map_err(|_e| Error::runtime_error("cannot encode diff"))
38
38
  }
39
39
  }
data/ext/yrb/src/ymap.rs CHANGED
@@ -76,7 +76,7 @@ impl YMap {
76
76
  EntryChange::Inserted(v) => {
77
77
  let h = RHash::new();
78
78
  h.aset(
79
- Symbol::new(&key.to_string()),
79
+ Symbol::new(key),
80
80
  *YValue::from(v.clone()).0.borrow()
81
81
  )
82
82
  .expect("cannot add change::inserted");
@@ -100,7 +100,7 @@ impl YMap {
100
100
  .expect("cannot push change::updated");
101
101
 
102
102
  let h = RHash::new();
103
- h.aset(Symbol::new(&key.to_string()), values)
103
+ h.aset(Symbol::new(key), values)
104
104
  .expect("cannot push change::updated");
105
105
 
106
106
  let payload = RHash::new();
@@ -115,7 +115,7 @@ impl YMap {
115
115
  EntryChange::Removed(v) => {
116
116
  let h = RHash::new();
117
117
  h.aset(
118
- Symbol::new(&key.to_string()),
118
+ Symbol::new(key),
119
119
  *YValue::from(v.clone()).0.borrow()
120
120
  )
121
121
  .expect("cannot push change::removed");
data/ext/yrb/src/ytext.rs CHANGED
@@ -153,7 +153,7 @@ impl YText {
153
153
  Delta::Retain(index, attrs) => {
154
154
  let payload = RHash::new();
155
155
 
156
- let yvalue = YValue::from(index.clone());
156
+ let yvalue = YValue::from(*index);
157
157
 
158
158
  payload
159
159
  .aset(delta_retain, yvalue.0.into_inner())
@@ -183,7 +183,7 @@ impl YText {
183
183
  Delta::Deleted(index) => {
184
184
  let payload = RHash::new();
185
185
 
186
- let yvalue = YValue::from(index.clone());
186
+ let yvalue = YValue::from(*index);
187
187
 
188
188
  payload
189
189
  .aset(delta_delete, yvalue.0.into_inner())
@@ -194,7 +194,7 @@ impl YText {
194
194
  })
195
195
  .partition(Result::is_ok);
196
196
 
197
- if errors.len() > 0 {
197
+ if !errors.is_empty() {
198
198
  // todo: make sure we respect errors and let the method fail by
199
199
  // by returning a Result containing an Error
200
200
  }
@@ -30,17 +30,17 @@ impl YTransaction {
30
30
  pub(crate) fn ytransaction_get_array(&self, name: String) -> YArray {
31
31
  let a = self.0.borrow_mut().get_array(&*name);
32
32
 
33
- return YArray(RefCell::from(a));
33
+ YArray(RefCell::from(a))
34
34
  }
35
35
  pub(crate) fn ytransaction_get_map(&self, name: String) -> YMap {
36
36
  let m = self.0.borrow_mut().get_map(&*name);
37
37
 
38
- return YMap(RefCell::from(m));
38
+ YMap(RefCell::from(m))
39
39
  }
40
40
  pub(crate) fn ytransaction_get_text(&self, name: String) -> YText {
41
41
  let t = self.0.borrow_mut().get_text(&*name);
42
42
 
43
- return YText(RefCell::new(t));
43
+ YText(RefCell::new(t))
44
44
  }
45
45
  pub(crate) fn ytransaction_get_xml_element(
46
46
  &self,
@@ -48,12 +48,12 @@ impl YTransaction {
48
48
  ) -> YXmlElement {
49
49
  let el = self.0.borrow_mut().get_xml_element(&*name);
50
50
 
51
- return YXmlElement(RefCell::new(el));
51
+ YXmlElement(RefCell::new(el))
52
52
  }
53
53
  pub(crate) fn ytransaction_get_xml_text(&self, name: String) -> YXmlText {
54
54
  let t = self.0.borrow_mut().get_xml_text(&*name);
55
55
 
56
- return YXmlText(RefCell::new(t));
56
+ YXmlText(RefCell::new(t))
57
57
  }
58
58
  pub(crate) fn ytransaction_state_vector(&self) -> Vec<u8> {
59
59
  return self.0.borrow_mut().state_vector().encode_v1();
@@ -15,7 +15,7 @@ unsafe impl Send for YXmlElement {}
15
15
 
16
16
  impl YXmlElement {
17
17
  pub(crate) fn yxml_element_attributes(&self) -> RHash {
18
- RHash::from_iter(self.0.borrow().attributes().into_iter())
18
+ RHash::from_iter(self.0.borrow().attributes())
19
19
  }
20
20
  pub(crate) fn yxml_element_first_child(&self) -> Option<Value> {
21
21
  self.yxml_element_get(0)
@@ -14,7 +14,7 @@ unsafe impl Send for YXmlText {}
14
14
 
15
15
  impl YXmlText {
16
16
  pub(crate) fn yxml_text_attributes(&self) -> RHash {
17
- RHash::from_iter(self.0.borrow().attributes().into_iter())
17
+ RHash::from_iter(self.0.borrow().attributes())
18
18
  }
19
19
  pub(crate) fn yxml_text_format(
20
20
  &self,
data/lib/2.7/yrb.bundle CHANGED
Binary file
data/lib/3.0/yrb.bundle CHANGED
Binary file
data/lib/3.1/yrb.bundle CHANGED
Binary file
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Y
4
+ class Awareness
5
+ def apply_update(update)
6
+ yawareness_apply_update(update)
7
+ end
8
+
9
+ def clean_local_state
10
+ yawareness_clean_local_state
11
+ end
12
+
13
+ def client_id
14
+ yawareness_client_id
15
+ end
16
+
17
+ def clients
18
+ yawareness_clients
19
+ end
20
+
21
+ def local_state
22
+ yawareness_local_state
23
+ end
24
+
25
+ def local_state=(json)
26
+ yawareness_set_local_state(json)
27
+ end
28
+
29
+ def attach(callback, &block)
30
+ return yawareness_on_update(callback) unless callback.nil?
31
+
32
+ yawareness_on_update(block.to_proc) unless block.nil?
33
+ end
34
+
35
+ def detach(subscription_id)
36
+ yawareness_remove_on_update(subscription_id)
37
+ end
38
+
39
+ def remove_state(client_id)
40
+ yawareness_remove_state(client_id)
41
+ end
42
+
43
+ def update
44
+ yawareness_update
45
+ end
46
+
47
+ def update_with_clients(clients)
48
+ yawareness_update_with_clients(clients)
49
+ end
50
+
51
+ private
52
+
53
+ # @!method yawareness_apply_update(update)
54
+ # Applies an update
55
+ #
56
+ # @param [Y::AwarenessUpdate] A structure that represents an encodable state
57
+ # of an Awareness struct.
58
+
59
+ # @!method yawareness_clean_local_state
60
+ # Clears out a state of a current client , effectively marking it as
61
+ # disconnected.
62
+
63
+ # @!method yawareness_client_id
64
+ # Returns a globally unique client ID of an underlying Doc.
65
+ # @return [Integer] The Client ID
66
+
67
+ # @!method yawareness_clients
68
+ # Returns a state map of all of the clients
69
+ # tracked by current Awareness instance. Those states are identified by
70
+ # their corresponding ClientIDs. The associated state is represented and
71
+ # replicated to other clients as a JSON string.
72
+ #
73
+ # @return [Hash<Integer, String>] Map of clients
74
+
75
+ # @!method yawareness_local_state
76
+ #
77
+ # @return [String|nil] Returns a JSON string state representation of a
78
+ # current Awareness instance.
79
+
80
+ # @!method yawareness_on_update(callback, &block)
81
+ #
82
+ # @param [Proc] A callback handler for updates
83
+ # @return [Integer] The subscription ID
84
+
85
+ # @!method yawareness_remove_on_update(subscription_id)
86
+ #
87
+ # @param [Integer] subscription_id The subscription id to remove
88
+
89
+ # @!method yawareness_remove_state(client_id)
90
+ # Clears out a state of a given client, effectively marking it as
91
+ # disconnected.
92
+ #
93
+ # @param [Integer] A Client ID
94
+ # @return [String|nil] Returns a JSON string state representation of a
95
+ # current Awareness instance.
96
+
97
+ # @!method yawareness_set_local_state(state)
98
+ # Sets a current Awareness instance state to a corresponding JSON string.
99
+ # This state will be replicated to other clients as part of the
100
+ # AwarenessUpdate and it will trigger an event to be emitted if current
101
+ # instance was created using [Awareness::with_observer] method.
102
+ #
103
+ # @param [String] Returns a state map of all of the clients tracked by
104
+ # current Awareness instance. Those states are identified by their
105
+ # corresponding ClientIDs. The associated state is represented and
106
+ # replicated to other clients as a JSON string.
107
+
108
+ # @!method yawareness_update
109
+ # Returns a serializable update object which is representation of a
110
+ # current Awareness state.
111
+ #
112
+ # @return [Y::AwarenessUpdate] The update object
113
+
114
+ # @!method yawareness_update_with_clients(clients)
115
+ # Returns a serializable update object which is representation of a
116
+ # current Awareness state. Unlike [Y::Awareness#update], this method
117
+ # variant allows to prepare update only for a subset of known clients.
118
+ # These clients must all be known to a current Awareness instance,
119
+ # otherwise an error will be returned.
120
+ #
121
+ # @param [Array<Integer>]
122
+ # @return [Y::AwarenessUpdate] The update object
123
+ end
124
+
125
+ # rubocop:disable Lint/EmptyClass
126
+ class AwarenessEvent
127
+ private
128
+
129
+ # @!method added
130
+ # @return [Array<Integer>] Added clients
131
+
132
+ # @!method updated
133
+ # @return [Array<Integer>] Updated clients
134
+
135
+ # @!method removed
136
+ # @return [Array<Integer>] Removed clients
137
+ end
138
+ # rubocop:enable Lint/EmptyClass
139
+
140
+ class AwarenessUpdate
141
+ def encode
142
+ yawareness_update_encode
143
+ end
144
+
145
+ private
146
+
147
+ # @!method yawareness_update_encode
148
+ # Encode the awareness state for simple transport
149
+ #
150
+ # @return [Array<Integer>] Encoded update
151
+ end
152
+ end
data/lib/y/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Y
4
- VERSION = "0.1.7"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/y-rb.rb CHANGED
@@ -9,6 +9,7 @@ rescue LoadError
9
9
  end
10
10
 
11
11
  require_relative "y/array"
12
+ require_relative "y/awareness"
12
13
  require_relative "y/doc"
13
14
  require_relative "y/map"
14
15
  require_relative "y/text"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: y-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Hannes Moser
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-10 00:00:00.000000000 Z
11
+ date: 2022-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -76,11 +76,13 @@ extra_rdoc_files: []
76
76
  files:
77
77
  - ext/yrb/Cargo.toml
78
78
  - ext/yrb/extconf.rb
79
+ - ext/yrb/src/awareness.rs
79
80
  - ext/yrb/src/lib.rs
80
81
  - ext/yrb/src/utils.rs
81
82
  - ext/yrb/src/yany.rs
82
83
  - ext/yrb/src/yarray.rs
83
84
  - ext/yrb/src/yattrs.rs
85
+ - ext/yrb/src/yawareness.rs
84
86
  - ext/yrb/src/ydoc.rs
85
87
  - ext/yrb/src/ymap.rs
86
88
  - ext/yrb/src/ytext.rs
@@ -94,6 +96,7 @@ files:
94
96
  - lib/y-rb.rb
95
97
  - lib/y.rb
96
98
  - lib/y/array.rb
99
+ - lib/y/awareness.rb
97
100
  - lib/y/doc.rb
98
101
  - lib/y/map.rb
99
102
  - lib/y/text.rb