y-rb 0.4.6-x86_64-linux → 0.5.0-x86_64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c5deaf53bc4859a4f4765b0e263d4c5993b0f707529e20e8d61ea5ad1eecea0
4
- data.tar.gz: 004320cb2be843111b9243f5ac3d93ce1a5165d58754c9d3a217c832e191a2ee
3
+ metadata.gz: fd77db92508918fc11424cd830c80bfdecb435ba5623ebd0d0d8b9ed1adb7a37
4
+ data.tar.gz: a4b40ee84fde7646056ef81e67f8d7f978d43e1e22966f7a500633d014ca9381
5
5
  SHA512:
6
- metadata.gz: 6bbdcf2ca893466bae039dd257103529aab5b71d622c0bf1075c06a90c95a3970f00613138bcf2e90588263f55186d1f0b08105daef2d9584490c73533a6f87c
7
- data.tar.gz: c2744c9f4fd20d97751edce6feb0e030e1f0a5ff4b7407ae85464bf13917c79e7a45a253210e11822fb730865e20d21cf43de3be71083c9e62af0fbee9065086
6
+ metadata.gz: '08253c1cac2a5a1935fafb64a2f8891356880623cbfa43772290e8ca33359fdd8df2d323a10ae3de15e8e784d4d65484a6df35ab6cb3986b5af342535bbdfacb'
7
+ data.tar.gz: 1f9cdc2910c11c06d3c76c8bed8b79227c6684a9a5f3e286762b858c4f3202a29cacbe1593141f849f1598d9dbc9c16f57c65ced7ebe874fef29fc30d2056112
data/ext/yrb/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "yrb"
3
- version = "0.4.6"
3
+ version = "0.5.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"
@@ -11,6 +11,7 @@ lib0 = "0.16.2" # must match yrs version
11
11
  magnus = { git = "https://github.com/matsadler/magnus", rev = "2c2024920a403daadbe23fe63270440dfac86288" }
12
12
  thiserror = "1.0.38"
13
13
  yrs = "0.16.2"
14
+ y-sync = "0.2.0"
14
15
 
15
16
  [dev-dependencies]
16
17
  magnus = { git = "https://github.com/matsadler/magnus", rev = "2c2024920a403daadbe23fe63270440dfac86288", features = ["embed"] }
data/ext/yrb/src/lib.rs CHANGED
@@ -11,7 +11,6 @@ use crate::yxml_fragment::YXmlFragment;
11
11
  use crate::yxml_text::YXmlText;
12
12
  use magnus::{define_module, function, method, Error, Module, Object};
13
13
 
14
- mod awareness;
15
14
  mod utils;
16
15
  mod yany;
17
16
  mod yarray;
@@ -578,12 +577,6 @@ fn init() -> Result<(), Error> {
578
577
  method!(YAwareness::yawareness_on_update, 1),
579
578
  )
580
579
  .expect("cannot define private method: yawareness_on_update");
581
- yawareness
582
- .define_private_method(
583
- "yawareness_remove_on_update",
584
- method!(YAwareness::yawareness_remove_on_update, 1),
585
- )
586
- .expect("cannot define private method: yawareness_remove_on_update");
587
580
  yawareness
588
581
  .define_private_method(
589
582
  "yawareness_remove_state",
@@ -1,12 +1,12 @@
1
- use crate::awareness::{Awareness, AwarenessUpdate, Event};
2
1
  use magnus::{block::Proc, exception, Error, Value};
3
2
  use std::borrow::Borrow;
4
3
  use std::cell::RefCell;
5
4
  use std::collections::HashMap;
5
+ use y_sync::awareness::{Awareness, AwarenessUpdate, Event, Subscription};
6
6
  use yrs::block::ClientID;
7
7
  use yrs::updates::decoder::Decode;
8
8
  use yrs::updates::encoder::Encode;
9
- use yrs::Doc;
9
+ use yrs::{Doc, OffsetKind, Options};
10
10
 
11
11
  #[magnus::wrap(class = "Y::Awareness")]
12
12
  pub(crate) struct YAwareness(pub(crate) RefCell<Awareness>);
@@ -16,7 +16,14 @@ unsafe impl Send for YAwareness {}
16
16
 
17
17
  impl YAwareness {
18
18
  pub(crate) fn yawareness_new() -> Self {
19
- let doc = Doc::new();
19
+ let mut options = Options {
20
+ offset_kind: OffsetKind::Utf32,
21
+ ..Default::default()
22
+ };
23
+ options.offset_kind = OffsetKind::Utf32;
24
+
25
+ let doc = Doc::with_options(options);
26
+
20
27
  let awareness = Awareness::new(doc);
21
28
 
22
29
  Self(RefCell::new(awareness))
@@ -48,24 +55,18 @@ impl YAwareness {
48
55
  self.0.borrow().local_state().map(|value| value.to_string())
49
56
  }
50
57
 
51
- pub(crate) fn yawareness_on_update(&self, block: Proc) -> Result<u32, Error> {
52
- let subscription_id = self
53
- .0
54
- .borrow_mut()
55
- .on_update(move |_awareness, event| {
56
- let awareness_event = YAwarenessEvent::from(event);
57
- let args = (awareness_event,);
58
- block
59
- .call::<(YAwarenessEvent,), Value>(args)
60
- .expect("cannot call block: on_update");
61
- })
62
- .into();
58
+ pub(crate) fn yawareness_on_update(&self, block: Proc) -> YAwarenessSubscription {
59
+ let subscription = self.0.borrow_mut().on_update(move |_awareness, event| {
60
+ let awareness_event = YAwarenessEvent::from(event);
61
+ let args = (awareness_event,);
62
+ block
63
+ .call::<(YAwarenessEvent,), Value>(args)
64
+ .expect("cannot call block: on_update");
65
+ });
63
66
 
64
- Ok(subscription_id)
65
- }
66
-
67
- pub(crate) fn yawareness_remove_on_update(&self, subscription_id: u32) {
68
- self.0.borrow_mut().remove_on_update(subscription_id)
67
+ // we need to make sure the event handler "survives" and is not being
68
+ // dropped after leaving this scope, so we pass it back to Ruby.
69
+ YAwarenessSubscription::from(subscription)
69
70
  }
70
71
 
71
72
  pub(crate) fn yawareness_remove_state(&self, client_id: ClientID) {
@@ -135,3 +136,16 @@ impl From<&Event> for YAwarenessEvent {
135
136
  Self(value.clone())
136
137
  }
137
138
  }
139
+
140
+ #[magnus::wrap(class = "Y::AwarenessEvent")]
141
+ pub(crate) struct YAwarenessSubscription(Subscription<Event>);
142
+
143
+ unsafe impl Send for YAwarenessSubscription {}
144
+
145
+ impl YAwarenessSubscription {}
146
+
147
+ impl From<Subscription<Event>> for YAwarenessSubscription {
148
+ fn from(v: Subscription<Event>) -> Self {
149
+ YAwarenessSubscription(v)
150
+ }
151
+ }
data/lib/2.7/yrb.so CHANGED
Binary file
data/lib/3.0/yrb.so CHANGED
Binary file
data/lib/3.1/yrb.so CHANGED
Binary file
data/lib/3.2/yrb.so CHANGED
Binary file
data/lib/y/array.rb CHANGED
@@ -38,7 +38,7 @@ module Y
38
38
 
39
39
  # Retrieves element at position
40
40
  #
41
- # @return [true|false|Float|Integer|String|Array|Hash]
41
+ # @return [true, false, Float, Integer, String, Array, Hash]
42
42
  def [](index)
43
43
  document.current_transaction { |tx| yarray_get(tx, index) }
44
44
  end
@@ -46,7 +46,7 @@ module Y
46
46
  # Inserts value at position
47
47
  #
48
48
  # @param index [Integer]
49
- # @param value [true|false|Float|Integer|String|Array|Hash]
49
+ # @param value [true, false, Float, Integer, String, Array, Hash]
50
50
  # @return [void]
51
51
  def []=(index, value)
52
52
  document.current_transaction { |tx| yarray_insert(tx, index, value) }
@@ -54,7 +54,7 @@ module Y
54
54
 
55
55
  # Adds an element to the end of the array
56
56
  #
57
- # @param value [true|false|Float|Integer|String|::Array|Hash]
57
+ # @param value [true, false, Float, Integer, String, ::Array, Hash]
58
58
  # @return [void]
59
59
  def <<(value, *values)
60
60
  document.current_transaction do |tx|
@@ -121,21 +121,21 @@ module Y
121
121
 
122
122
  # Check if the array is empty
123
123
  #
124
- # @return [true|false]
124
+ # @return [true, false]
125
125
  def empty?
126
126
  size.zero?
127
127
  end
128
128
 
129
129
  # Returns first element in array if there is at least one
130
130
  #
131
- # @return [true|false|Float|Integer|String|::Array|Hash|nil]
131
+ # @return [true, false, Float, Integer, String, ::Array, Hash, nil]
132
132
  def first
133
133
  document.current_transaction { |tx| yarray_get(tx, 0) }
134
134
  end
135
135
 
136
136
  # Returns last element in array if there is at least one element
137
137
  #
138
- # @return [true|false|Float|Integer|String|::Array|Hash|nil]
138
+ # @return [true, false, Float, Integer, String, ::Array, Hash, nil]
139
139
  def last
140
140
  document.current_transaction do |tx|
141
141
  len = yarray_length(tx)
@@ -149,7 +149,7 @@ module Y
149
149
 
150
150
  # Removes last (n) element(s) from array
151
151
  #
152
- # @param n [Integer|nil] Number of elements to remove
152
+ # @param n [Integer, nil] Number of elements to remove
153
153
  # @return [void]
154
154
  def pop(n = nil)
155
155
  document.current_transaction do |tx|
@@ -167,7 +167,7 @@ module Y
167
167
 
168
168
  # Removes first (n) element(s) from array
169
169
  #
170
- # @param n [Integer|nil] Number of elements to remove
170
+ # @param n [Integer, nil] Number of elements to remove
171
171
  # @return [void]
172
172
  def shift(n = nil)
173
173
  document.current_transaction do |tx|
@@ -264,7 +264,7 @@ module Y
264
264
 
265
265
  # Convert this array to a Ruby Array
266
266
  #
267
- # @return [Array<true|false|Float|Integer|String|::Array|Hash>]
267
+ # @return [Array<true, false, Float, Integer, String, ::Array, Hash>]
268
268
  def to_a
269
269
  document.current_transaction { |tx| yarray_to_a(tx) }
270
270
  end
@@ -306,7 +306,7 @@ module Y
306
306
  #
307
307
  # @param transaction [Y::Transaction]
308
308
  # @param index [Integer]
309
- # @param arr [Array<Boolean|Float|Integer|Array|Hash|Text>]
309
+ # @param arr [Array<Boolean, Float, Integer, Array, Hash, Text>]
310
310
  # @return [void]
311
311
  # @!visibility private
312
312
 
data/lib/y/awareness.rb CHANGED
@@ -93,25 +93,28 @@ module Y
93
93
  #
94
94
  # @return [Hash] All clients and their current state
95
95
  def clients
96
- yawareness_clients
96
+ transform = yawareness_clients.map do |client_id, state|
97
+ [client_id, JSON.parse!(state)]
98
+ end
99
+ transform.to_h
97
100
  end
98
101
 
99
- # Returns a JSON string state representation of a current Awareness
100
- # instance.
102
+ # Returns the state of the local Awareness instance.
101
103
  #
102
104
  # @example Create local state and inspect it
103
105
  # local_state = {
104
106
  # editing: { field: "description", pos: 0 },
105
107
  # name: "Hannes Moser"
106
- # }.to_json
108
+ # }
107
109
  #
108
110
  # awareness = Y::Awareness.new
109
111
  # awareness.local_state = local_state
110
- # local_state # "{\"editing\":{\"field\":\"description\",\"pos\":0},
112
+ # awareness.local_state # { editing: { field: "description", ...
111
113
  #
112
114
  # @return [String] The current state of the local client
113
115
  def local_state
114
- yawareness_local_state
116
+ json = yawareness_local_state
117
+ JSON.parse!(json) if json
115
118
  end
116
119
 
117
120
  # Sets a current Awareness instance state to a corresponding JSON string.
@@ -127,28 +130,23 @@ module Y
127
130
  # awareness = Y::Awareness.new
128
131
  # awareness.local_state = local_state
129
132
  #
133
+ # @param [#to_json] state
130
134
  # @return [void]
131
- def local_state=(json)
132
- yawareness_set_local_state(json)
135
+ def local_state=(state)
136
+ raise "state cannot be encoded to JSON" unless state.respond_to? :to_json
137
+
138
+ yawareness_set_local_state(state.to_json)
133
139
  end
134
140
 
135
141
  # Subscribes to changes
136
142
  #
137
143
  # @return [Integer] The subscription ID
138
- def attach(callback, &block)
144
+ def attach(callback = nil, &block)
139
145
  return yawareness_on_update(callback) unless callback.nil?
140
146
 
141
147
  yawareness_on_update(block.to_proc) unless block.nil?
142
148
  end
143
149
 
144
- # Unsubscribe from changes
145
- #
146
- # @param subscription_id [Integer]
147
- # @return [void]
148
- def detach(subscription_id)
149
- yawareness_remove_on_update(subscription_id)
150
- end
151
-
152
150
  # Clears out a state of a given client, effectively marking it as
153
151
  # disconnected.
154
152
  #
@@ -216,7 +214,7 @@ module Y
216
214
 
217
215
  # @!method yawareness_local_state
218
216
  #
219
- # @return [String|nil] Returns a JSON string state representation of a
217
+ # @return [String, nil] Returns a JSON string state representation of a
220
218
  # current Awareness instance.
221
219
  # @!visibility private
222
220
 
@@ -236,7 +234,7 @@ module Y
236
234
  # disconnected.
237
235
  #
238
236
  # @param client_id [Integer] A Client ID
239
- # @return [String|nil] Returns a JSON string state representation of a
237
+ # @return [String, nil] Returns a JSON string state representation of a
240
238
  # current Awareness instance.
241
239
  # @!visibility private
242
240
 
data/lib/y/map.rb CHANGED
@@ -70,7 +70,7 @@ module Y
70
70
  # m.delete(:nosuch) { |key| "Key #{key} not found" }# => "Key nosuch not found"
71
71
  # m # => {}
72
72
  #
73
- # @param key [String|Symbol]
73
+ # @param key [String, Symbol]
74
74
  # @return [void]
75
75
  def delete(key)
76
76
  value = document.current_transaction { |tx| ymap_remove(tx, key) }
@@ -96,7 +96,7 @@ module Y
96
96
  document.current_transaction { |tx| ymap_each(tx, block) }
97
97
  end
98
98
 
99
- # @return [true|false]
99
+ # @return [true, false]
100
100
  def key?(key)
101
101
  document.current_transaction { |tx| ymap_contains(tx, key) }
102
102
  end
@@ -143,7 +143,7 @@ module Y
143
143
  # Check if a certain key is in the Map
144
144
  #
145
145
  # @param tx [Y::Transaction]
146
- # @param key [String|Symbol]
146
+ # @param key [String, Symbol]
147
147
  # @return [Boolean] True, if and only if the key exists
148
148
 
149
149
  # @!method ymap_each(tx, proc)
@@ -151,21 +151,21 @@ module Y
151
151
  # with the key and the value as arguments.
152
152
  #
153
153
  # @param tx [Y::Transaction]
154
- # @param proc [Proc<String|Any>] A proc that is called for every element
154
+ # @param proc [Proc<String, Any>] A proc that is called for every element
155
155
 
156
156
  # @!method ymap_get(tx, key)
157
157
  # Returns stored value for key or nil if none is present
158
158
  #
159
159
  # @param tx [Y::Transaction]
160
- # @param key [String|Symbol]
161
- # @return [Object|nil] Value or nil
160
+ # @param key [String, Symbol]
161
+ # @return [Object, nil] Value or nil
162
162
 
163
163
  # @!method ymap_insert(tx, key, value)
164
164
  # Insert value for key. In case the key already exists, the previous value
165
165
  # will be overwritten.
166
166
  #
167
167
  # @param tx [Y::Transaction]
168
- # @param key [String|Symbol]
168
+ # @param key [String, Symbol]
169
169
  # @param value [Object]
170
170
 
171
171
  # @!method ymap_observe(callback)
@@ -177,7 +177,7 @@ module Y
177
177
  # Removes key-value pair from Map if key exists.
178
178
  #
179
179
  # @param tx [Y::Transaction]
180
- # @param key [String|Symbol]
180
+ # @param key [String, Symbol]
181
181
 
182
182
  # @!method ymap_size(tx)
183
183
  # Returns number of key-value pairs stored in map
data/lib/y/text.rb CHANGED
@@ -116,8 +116,8 @@ module Y
116
116
  # - Hash (where the the types of key and values must be supported)
117
117
  #
118
118
  # @param index [Integer]
119
- # @param value [String|Numeric|Array|Hash]
120
- # @param attrs [Hash|nil]
119
+ # @param value [String, Numeric, Array, Hash]
120
+ # @param attrs [Hash, nil]
121
121
  # @return [void]
122
122
  def insert(index, value, attrs = nil)
123
123
  document.current_transaction do |tx|
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.4.6"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: y-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.6
4
+ version: 0.5.0
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Hannes Moser
@@ -78,7 +78,6 @@ extra_rdoc_files: []
78
78
  files:
79
79
  - ext/yrb/Cargo.toml
80
80
  - ext/yrb/extconf.rb
81
- - ext/yrb/src/awareness.rs
82
81
  - ext/yrb/src/lib.rs
83
82
  - ext/yrb/src/utils.rs
84
83
  - ext/yrb/src/yany.rs
@@ -1,425 +0,0 @@
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
- }