y-rb 0.4.5-arm64-darwin → 0.5.0-arm64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/yrb/Cargo.toml +6 -5
- data/ext/yrb/src/lib.rs +72 -7
- data/ext/yrb/src/yawareness.rs +34 -20
- data/ext/yrb/src/ydoc.rs +12 -11
- data/ext/yrb/src/yxml_element.rs +6 -0
- data/ext/yrb/src/yxml_fragment.rs +115 -2
- data/lib/2.7/yrb.bundle +0 -0
- data/lib/3.0/yrb.bundle +0 -0
- data/lib/3.1/yrb.bundle +0 -0
- data/lib/3.2/yrb.bundle +0 -0
- data/lib/y/array.rb +16 -13
- data/lib/y/awareness.rb +17 -19
- data/lib/y/doc.rb +25 -0
- data/lib/y/map.rb +8 -8
- data/lib/y/text.rb +3 -3
- data/lib/y/version.rb +1 -1
- data/lib/y/xml.rb +263 -24
- metadata +2 -3
- data/ext/yrb/src/awareness.rs +0 -425
data/ext/yrb/src/awareness.rs
DELETED
@@ -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
|
-
}
|