y-rb 0.4.6-x64-mingw32 → 0.5.1-x64-mingw32
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 +8 -6
- data/ext/yrb/src/lib.rs +0 -7
- data/ext/yrb/src/yawareness.rs +34 -20
- data/lib/2.7/yrb.so +0 -0
- data/lib/3.0/yrb.so +0 -0
- data/lib/y/array.rb +10 -10
- data/lib/y/awareness.rb +22 -24
- data/lib/y/map.rb +8 -8
- data/lib/y/text.rb +2 -2
- data/lib/y/version.rb +1 -1
- metadata +4 -5
- data/ext/yrb/src/awareness.rs +0 -425
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dae5f0cfc5942f3b529ba5e5c834e17a34b8f8fcaa9961bfd213d2b680ef2dca
|
4
|
+
data.tar.gz: 7de88defac6445c6755d334ba3a002661c25b85f83e29275c9c1ba0ee4781604
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ef907cf8150c944f830ebcb10f652090b7adb46c40f3b44be0e35b68b83e86f34d83beb62d2eeda65c8a229984b7740f96c189c01f762db5a8eddcdd365b470
|
7
|
+
data.tar.gz: 99e2ec1b3db9e987e5ba911a52fc68a9df6f167848a4b3344a490d1270922dc02be53de2b855b9c06634a350315175d3085a17fe349e9788f4051c65b854a33b
|
data/ext/yrb/Cargo.toml
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
[package]
|
2
2
|
name = "yrb"
|
3
|
-
version = "0.
|
3
|
+
version = "0.5.1"
|
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"
|
7
7
|
repository = "https://github.com/y-crdt/yrb"
|
8
8
|
|
9
9
|
[dependencies]
|
10
|
-
lib0 = "0.16.
|
11
|
-
magnus =
|
12
|
-
thiserror = "1.0.
|
13
|
-
yrs = "0.16.
|
10
|
+
lib0 = "0.16.5" # must match yrs version
|
11
|
+
magnus = "0.5.2"
|
12
|
+
thiserror = "1.0.40"
|
13
|
+
yrs = "0.16.5"
|
14
|
+
y-sync = "0.3.0"
|
15
|
+
rb-sys = "0.9.71"
|
14
16
|
|
15
17
|
[dev-dependencies]
|
16
|
-
magnus = {
|
18
|
+
magnus = { version = "0.5.2", features = ["embed"] }
|
17
19
|
|
18
20
|
[lib]
|
19
21
|
name = "yrb"
|
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",
|
data/ext/yrb/src/yawareness.rs
CHANGED
@@ -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
|
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) ->
|
52
|
-
let
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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/y/array.rb
CHANGED
@@ -38,7 +38,7 @@ module Y
|
|
38
38
|
|
39
39
|
# Retrieves element at position
|
40
40
|
#
|
41
|
-
# @return [true
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
@@ -25,19 +25,19 @@ module Y
|
|
25
25
|
# local_state = {
|
26
26
|
# editing: { field: "description", pos: 0 },
|
27
27
|
# name: "Hannes Moser"
|
28
|
-
# }
|
28
|
+
# }
|
29
29
|
#
|
30
30
|
# awareness = Y::Awareness.new
|
31
31
|
# awareness.local_state = local_state
|
32
32
|
# awareness.diff # [1,227,245,175,195,11,1,65,123, …]
|
33
33
|
#
|
34
34
|
# @example Two connected clients
|
35
|
-
# local_state_a = { name: "User A" }
|
35
|
+
# local_state_a = { name: "User A" }
|
36
36
|
#
|
37
37
|
# client_a = Y::Awareness.new
|
38
38
|
# client_a.local_state = local_state
|
39
39
|
#
|
40
|
-
# local_state_b = { name: "User B" }
|
40
|
+
# local_state_b = { name: "User B" }
|
41
41
|
#
|
42
42
|
# client_b = Y::Awareness.new
|
43
43
|
# client_b.local_state = local_state_b
|
@@ -85,7 +85,7 @@ module Y
|
|
85
85
|
# local_state = {
|
86
86
|
# editing: { field: "description", pos: 0 },
|
87
87
|
# name: "Hannes Moser"
|
88
|
-
# }
|
88
|
+
# }
|
89
89
|
#
|
90
90
|
# awareness = Y::Awareness.new
|
91
91
|
# awareness.local_state = local_state
|
@@ -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
|
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
|
-
# }
|
108
|
+
# }
|
107
109
|
#
|
108
110
|
# awareness = Y::Awareness.new
|
109
111
|
# awareness.local_state = local_state
|
110
|
-
# local_state #
|
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.
|
@@ -122,33 +125,28 @@ module Y
|
|
122
125
|
# local_state = {
|
123
126
|
# editing: { field: "description", pos: 0 },
|
124
127
|
# name: "Hannes Moser"
|
125
|
-
# }
|
128
|
+
# }
|
126
129
|
#
|
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=(
|
132
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
161
|
-
# @return [Object
|
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
|
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
|
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
|
120
|
-
# @param attrs [Hash
|
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
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.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: x64-mingw32
|
6
6
|
authors:
|
7
7
|
- Hannes Moser
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -31,14 +31,14 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 0.9.
|
34
|
+
version: 0.9.71
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 0.9.
|
41
|
+
version: 0.9.71
|
42
42
|
force_ruby_platform: false
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: rake-compiler
|
@@ -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
|
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
|
-
}
|