@quenty/observablecollection 5.14.0 → 5.15.0
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.
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.15.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@5.14.0...@quenty/observablecollection@5.15.0) (2023-05-08)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Fix removal firing events to fire correctly ([10f98c3](https://github.com/Quenty/NevermoreEngine/commit/10f98c329e7bab203ffc056d1ffb0c2c715392a8))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* Add ObservableSet:ObserveContains(item) ([df5beba](https://github.com/Quenty/NevermoreEngine/commit/df5beba6b196933653365719c26e6ed9fa21d4cc))
|
|
17
|
+
* Add ObservableSortedList:ObserveAtIndex(indexToObserve) and ObservableSortedList:FindFirstKey(content) ([4d9f4d8](https://github.com/Quenty/NevermoreEngine/commit/4d9f4d8d34eafa0899157e4960ea87c9c812cf48))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
# [5.14.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@5.13.0...@quenty/observablecollection@5.14.0) (2023-04-20)
|
|
7
24
|
|
|
8
25
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/observablecollection",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.0",
|
|
4
4
|
"description": "A set of observable collections, such as sets, maps, sorted lists, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -27,17 +27,17 @@
|
|
|
27
27
|
"Quenty"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@quenty/brio": "^8.
|
|
30
|
+
"@quenty/brio": "^8.12.0",
|
|
31
31
|
"@quenty/loader": "^6.2.1",
|
|
32
32
|
"@quenty/maid": "^2.5.0",
|
|
33
33
|
"@quenty/promise": "^6.5.0",
|
|
34
34
|
"@quenty/rx": "^7.10.0",
|
|
35
35
|
"@quenty/signal": "^2.3.0",
|
|
36
36
|
"@quenty/symbol": "^2.2.0",
|
|
37
|
-
"@quenty/valuebaseutils": "^7.
|
|
37
|
+
"@quenty/valuebaseutils": "^7.13.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "2ad8cea7dd3ad79a39afd7d7b785b489b90553fd"
|
|
43
43
|
}
|
|
@@ -214,7 +214,7 @@ function ObservableCountingMap:Set(key, amount)
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
if current < amount then
|
|
217
|
-
self:
|
|
217
|
+
self:Add(-(amount - current))
|
|
218
218
|
return
|
|
219
219
|
elseif current == amount then
|
|
220
220
|
return
|
|
@@ -237,29 +237,50 @@ function ObservableCountingMap:Add(key, amount)
|
|
|
237
237
|
|
|
238
238
|
if amount == 0 then
|
|
239
239
|
return
|
|
240
|
-
elseif amount < 0 then
|
|
241
|
-
self:Remove(key, -amount)
|
|
242
|
-
return
|
|
243
240
|
end
|
|
244
241
|
|
|
245
|
-
if
|
|
242
|
+
if self._map[key] then
|
|
243
|
+
local newValue = self._map[key] + amount
|
|
244
|
+
if newValue == 0 then
|
|
245
|
+
-- Remove item
|
|
246
|
+
self._map[key] = nil
|
|
247
|
+
|
|
248
|
+
-- Fire events
|
|
249
|
+
self._totalKeyCountValue.Value = self._totalKeyCountValue.Value - 1
|
|
250
|
+
|
|
251
|
+
if self.Destroy then
|
|
252
|
+
self.KeyRemoved:Fire(key)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if self.Destroy then
|
|
256
|
+
self.KeyChanged:Fire(key, 0)
|
|
257
|
+
end
|
|
258
|
+
else
|
|
259
|
+
-- Update item
|
|
260
|
+
self._map[key] = newValue
|
|
261
|
+
self.KeyChanged:Fire(key, newValue)
|
|
262
|
+
end
|
|
263
|
+
else
|
|
264
|
+
-- Add item
|
|
246
265
|
self._map[key] = amount
|
|
266
|
+
|
|
267
|
+
-- Fire events
|
|
247
268
|
self._totalKeyCountValue.Value = self._totalKeyCountValue.Value + 1
|
|
248
269
|
|
|
249
270
|
if self.Destroy then
|
|
250
271
|
self.KeyAdded:Fire(key)
|
|
251
272
|
end
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
273
|
+
|
|
274
|
+
if self.Destroy then
|
|
275
|
+
self.KeyChanged:Fire(key, amount)
|
|
276
|
+
end
|
|
256
277
|
end
|
|
257
278
|
|
|
258
279
|
local removed = false
|
|
259
280
|
return function()
|
|
260
281
|
if self.Destroy and not removed then
|
|
261
282
|
removed = true
|
|
262
|
-
self:
|
|
283
|
+
self:Add(key, -amount)
|
|
263
284
|
end
|
|
264
285
|
end
|
|
265
286
|
end
|
|
@@ -270,40 +291,12 @@ end
|
|
|
270
291
|
@param amount number?
|
|
271
292
|
@return callback
|
|
272
293
|
]=]
|
|
273
|
-
function ObservableCountingMap:
|
|
294
|
+
function ObservableCountingMap:Remove(key, amount)
|
|
274
295
|
assert(key ~= nil, "Bad key")
|
|
275
296
|
assert(type(amount) == "number" or amount == nil, "Bad amount")
|
|
276
297
|
amount = amount or 1
|
|
277
298
|
|
|
278
|
-
|
|
279
|
-
return
|
|
280
|
-
elseif amount < 0 then
|
|
281
|
-
self:Add(key, -amount)
|
|
282
|
-
return
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
local current = self._map[key]
|
|
286
|
-
if not current then
|
|
287
|
-
return
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
local newValue = current - amount
|
|
291
|
-
if newValue > 0 then
|
|
292
|
-
self._map[key] = newValue
|
|
293
|
-
self.KeyChanged:Fire(key, newValue)
|
|
294
|
-
else
|
|
295
|
-
self._map[key] = nil
|
|
296
|
-
|
|
297
|
-
self._totalKeyCountValue.Value = self._totalKeyCountValue.Value - 1
|
|
298
|
-
|
|
299
|
-
if self.Destroy then
|
|
300
|
-
self.KeyRemoved:Fire(key)
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
if self.Destroy then
|
|
304
|
-
self.KeyChanged:Fire(key, 0)
|
|
305
|
-
end
|
|
306
|
-
end
|
|
299
|
+
self:Add(key, -amount)
|
|
307
300
|
end
|
|
308
301
|
|
|
309
302
|
--[=[
|
|
@@ -10,6 +10,7 @@ local Observable = require("Observable")
|
|
|
10
10
|
local Maid = require("Maid")
|
|
11
11
|
local Brio = require("Brio")
|
|
12
12
|
local RxValueBaseUtils = require("RxValueBaseUtils")
|
|
13
|
+
local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
|
|
13
14
|
|
|
14
15
|
local ObservableSet = {}
|
|
15
16
|
ObservableSet.ClassName = "ObservableSet"
|
|
@@ -25,6 +26,9 @@ function ObservableSet.new()
|
|
|
25
26
|
self._maid = Maid.new()
|
|
26
27
|
self._set = {}
|
|
27
28
|
|
|
29
|
+
self._containsObservables = ObservableSubscriptionTable.new()
|
|
30
|
+
self._maid:GiveTask(self._containsObservables)
|
|
31
|
+
|
|
28
32
|
self._countValue = Instance.new("IntValue")
|
|
29
33
|
self._countValue.Value = 0
|
|
30
34
|
self._maid:GiveTask(self._countValue)
|
|
@@ -99,6 +103,39 @@ function ObservableSet:ObserveItemsBrio()
|
|
|
99
103
|
end)
|
|
100
104
|
end
|
|
101
105
|
|
|
106
|
+
--[=[
|
|
107
|
+
Observes the current value at a given index. This can be useful for observing
|
|
108
|
+
the first entry, or matching stuff up to a given slot.
|
|
109
|
+
|
|
110
|
+
@param item T
|
|
111
|
+
@return Observable<boolean>
|
|
112
|
+
]=]
|
|
113
|
+
function ObservableSet:ObserveContains(item)
|
|
114
|
+
assert(item ~= nil, "Bad item")
|
|
115
|
+
|
|
116
|
+
return Observable.new(function(sub)
|
|
117
|
+
local maid = Maid.new()
|
|
118
|
+
|
|
119
|
+
if self._set[item] then
|
|
120
|
+
sub:Fire(true)
|
|
121
|
+
else
|
|
122
|
+
sub:Fire(false)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
maid:GiveTask(self._containsObservables:Observe(item):Subscribe(function(doesContain)
|
|
126
|
+
sub:Fire(doesContain)
|
|
127
|
+
end))
|
|
128
|
+
|
|
129
|
+
self._maid[sub] = maid
|
|
130
|
+
maid:GiveTask(function()
|
|
131
|
+
self._maid[sub] = nil
|
|
132
|
+
sub:Complete()
|
|
133
|
+
end)
|
|
134
|
+
|
|
135
|
+
return maid
|
|
136
|
+
end)
|
|
137
|
+
end
|
|
138
|
+
|
|
102
139
|
--[=[
|
|
103
140
|
Returns whether the set contains the item
|
|
104
141
|
@param item T
|
|
@@ -135,9 +172,12 @@ function ObservableSet:Add(item)
|
|
|
135
172
|
assert(item ~= nil, "Bad item")
|
|
136
173
|
|
|
137
174
|
if not self._set[item] then
|
|
138
|
-
self._countValue.Value = self._countValue.Value + 1
|
|
139
175
|
self._set[item] = true
|
|
176
|
+
|
|
177
|
+
-- Fire events
|
|
178
|
+
self._countValue.Value = self._countValue.Value + 1
|
|
140
179
|
self.ItemAdded:Fire(item)
|
|
180
|
+
self._containsObservables:Fire(item, true)
|
|
141
181
|
end
|
|
142
182
|
|
|
143
183
|
return function()
|
|
@@ -155,12 +195,14 @@ function ObservableSet:Remove(item)
|
|
|
155
195
|
assert(item ~= nil, "Bad item")
|
|
156
196
|
|
|
157
197
|
if self._set[item] then
|
|
158
|
-
self._countValue.Value = self._countValue.Value - 1
|
|
159
198
|
self._set[item] = nil
|
|
160
199
|
|
|
200
|
+
-- Fire in reverse order
|
|
201
|
+
self._containsObservables:Fire(item, false)
|
|
161
202
|
if self.Destroy then
|
|
162
203
|
self.ItemRemoved:Fire(item)
|
|
163
204
|
end
|
|
205
|
+
self._countValue.Value = self._countValue.Value - 1
|
|
164
206
|
end
|
|
165
207
|
end
|
|
166
208
|
|
|
@@ -20,10 +20,11 @@ local Maid = require("Maid")
|
|
|
20
20
|
local Brio = require("Brio")
|
|
21
21
|
local RxValueBaseUtils = require("RxValueBaseUtils")
|
|
22
22
|
local Symbol = require("Symbol")
|
|
23
|
+
local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
|
|
23
24
|
|
|
24
|
-
-- Higher numbers last
|
|
25
|
+
-- Higher numbers last. Using <= ensures insertion at end on ties.
|
|
25
26
|
local function defaultCompare(a, b)
|
|
26
|
-
return a
|
|
27
|
+
return a <= b
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
local ObservableSortedList = {}
|
|
@@ -42,6 +43,12 @@ function ObservableSortedList.new(compare)
|
|
|
42
43
|
|
|
43
44
|
self._keyList = {} -- { [number]: Symbol } -- immutable
|
|
44
45
|
|
|
46
|
+
self._indexObservers = ObservableSubscriptionTable.new()
|
|
47
|
+
self._maid:GiveTask(self._indexObservers)
|
|
48
|
+
|
|
49
|
+
self._contentIndexObservers = ObservableSubscriptionTable.new()
|
|
50
|
+
self._maid:GiveTask(self._contentIndexObservers)
|
|
51
|
+
|
|
45
52
|
self._sortValue = {} -- { [Symbol]: number }
|
|
46
53
|
self._contents = {} -- { [Symbol]: T }
|
|
47
54
|
self._indexes = {} -- { [Symbol]: number }
|
|
@@ -123,6 +130,22 @@ function ObservableSortedList:ObserveItemsBrio()
|
|
|
123
130
|
end)
|
|
124
131
|
end
|
|
125
132
|
|
|
133
|
+
--[=[
|
|
134
|
+
Gets the first key for a given symbol
|
|
135
|
+
|
|
136
|
+
@param content T
|
|
137
|
+
@return Symbol
|
|
138
|
+
]=]
|
|
139
|
+
function ObservableSortedList:FindFirstKey(content)
|
|
140
|
+
for key, item in pairs(self._contents) do
|
|
141
|
+
if item == content then
|
|
142
|
+
return key
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
return nil
|
|
147
|
+
end
|
|
148
|
+
|
|
126
149
|
--[=[
|
|
127
150
|
Observes the index as it changes, until the entry at the existing
|
|
128
151
|
index is removed.
|
|
@@ -141,6 +164,34 @@ function ObservableSortedList:ObserveIndex(indexToObserve)
|
|
|
141
164
|
return self:ObserveIndexByKey(key)
|
|
142
165
|
end
|
|
143
166
|
|
|
167
|
+
--[=[
|
|
168
|
+
Observes the current value at a given index. This can be useful for observing
|
|
169
|
+
the first entry, or matching stuff up to a given slot.
|
|
170
|
+
|
|
171
|
+
@param indexToObserve number
|
|
172
|
+
@return Observable<T>
|
|
173
|
+
]=]
|
|
174
|
+
function ObservableSortedList:ObserveAtIndex(indexToObserve)
|
|
175
|
+
assert(type(indexToObserve) == "number", "Bad indexToObserve")
|
|
176
|
+
|
|
177
|
+
return self._indexObservers:Observe(indexToObserve)
|
|
178
|
+
:Pipe({
|
|
179
|
+
function(source)
|
|
180
|
+
return Observable.new(function(sub)
|
|
181
|
+
local key = self._keyList[indexToObserve]
|
|
182
|
+
|
|
183
|
+
if key then
|
|
184
|
+
sub:Fire(self._contents[key]) -- Look up the content
|
|
185
|
+
else
|
|
186
|
+
sub:Fire(nil)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
return source:Subscribe(sub:GetFireFailComplete())
|
|
190
|
+
end)
|
|
191
|
+
end
|
|
192
|
+
})
|
|
193
|
+
end
|
|
194
|
+
|
|
144
195
|
--[=[
|
|
145
196
|
Observes the index as it changes, until the entry at the existing
|
|
146
197
|
key is removed.
|
|
@@ -410,6 +461,7 @@ function ObservableSortedList:_removeItemByKey(key, item)
|
|
|
410
461
|
local itemRemoved = {
|
|
411
462
|
key = key;
|
|
412
463
|
item = item;
|
|
464
|
+
previousIndex = index;
|
|
413
465
|
}
|
|
414
466
|
|
|
415
467
|
-- TODO: Defer item removed as a changed event?
|
|
@@ -453,14 +505,26 @@ function ObservableSortedList:_queueDeferredChange()
|
|
|
453
505
|
|
|
454
506
|
self._countValue.Value = self._countValue.Value + snapshot.countChange
|
|
455
507
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
508
|
+
-- Fire off last adds
|
|
509
|
+
for _, lastAdded in pairs(snapshot.itemsAdded) do
|
|
510
|
+
if not self.ItemAdded.Destroy then
|
|
511
|
+
break
|
|
460
512
|
end
|
|
513
|
+
self.ItemAdded:Fire(lastAdded.item, lastAdded.newIndex, lastAdded.key)
|
|
514
|
+
|
|
515
|
+
-- Item adds are included in indexChanges.
|
|
516
|
+
end
|
|
461
517
|
|
|
462
|
-
|
|
463
|
-
|
|
518
|
+
for _, lastRemoved in pairs(snapshot.itemsRemoved) do
|
|
519
|
+
if not self.ItemRemoved.Destroy then
|
|
520
|
+
break
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
self.ItemRemoved:Fire(lastRemoved.item, lastRemoved.key)
|
|
524
|
+
|
|
525
|
+
-- Fire only if we aren't handled by an index change.
|
|
526
|
+
if self._keyList[lastRemoved.previousIndex] == nil then
|
|
527
|
+
self._indexObservers:Fire(lastRemoved.previousIndex, nil)
|
|
464
528
|
end
|
|
465
529
|
end
|
|
466
530
|
|
|
@@ -471,6 +535,8 @@ function ObservableSortedList:_queueDeferredChange()
|
|
|
471
535
|
if subs then
|
|
472
536
|
self:_fireSubs(subs, lastChange.newIndex)
|
|
473
537
|
end
|
|
538
|
+
|
|
539
|
+
self._indexObservers:Fire(lastChange.newIndex, self._contents[lastChange.key])
|
|
474
540
|
end
|
|
475
541
|
end
|
|
476
542
|
end)
|