y-rb 0.4.6 → 0.5.0

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: aac2e9a4681a6cc2c10832d28525bc34627faeb0dcef982f13a9fd1076de36f6
4
- data.tar.gz: 1c8c5ce63cf8933027b0388b481825d958c20b6ba58eb1b95ceef60e7fd178b7
3
+ metadata.gz: 628f5eba6c401bed73e8e1ebde0d487b135535e60d659421bbea9b0474ca022e
4
+ data.tar.gz: 212aa302985cce60669f2f7f517900bc91a99739180a97ac13a502ae38aec0a1
5
5
  SHA512:
6
- metadata.gz: 78b6951e2972f5bc55982a79fe2dfa0b1a24ff78afef1fdb1a5f0dcb186a0ac2b29f5fceb0cc0e0dabecd523b19affd7ff3d7171e257f4656e62acd4bf06645d
7
- data.tar.gz: 1a8ee7df7dfc7ec867a24c888880b97e61061441bc509697978c3fb48dd51434d07f0a97f63b09539d6ad7942771f9ed2ae9c1ec2357c9e0e691b5341132d4e3
6
+ metadata.gz: 43772fceb0b399dfa08500dfbe0b7fa0138b122d94b7d5bf9651f5c6a55024ff23bc9b40890619a7a9ef479012dcfbcf733b11d033047c12bddfd382a4ef7aae
7
+ data.tar.gz: d7e6eff2edad549f89b24f55e0427d9f8ad5b5e7512bd81c699b9825def1355c7567c03e5732d35622858f930b545f3a32dbad07a3f121ec9d0d03e3ec2a19df
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/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: ruby
6
6
  authors:
7
7
  - Hannes Moser
@@ -77,7 +77,6 @@ extra_rdoc_files: []
77
77
  files:
78
78
  - ext/yrb/Cargo.toml
79
79
  - ext/yrb/extconf.rb
80
- - ext/yrb/src/awareness.rs
81
80
  - ext/yrb/src/lib.rs
82
81
  - ext/yrb/src/utils.rs
83
82
  - 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
- }