@quenty/observablecollection 12.19.0 → 12.19.2-canary.540.8ec7d75.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,22 @@
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
+ ## [12.19.2-canary.540.8ec7d75.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.19.1...@quenty/observablecollection@12.19.2-canary.540.8ec7d75.0) (2025-03-13)
7
+
8
+ **Note:** Version bump only for package @quenty/observablecollection
9
+
10
+
11
+
12
+
13
+
14
+ ## [12.19.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.19.0...@quenty/observablecollection@12.19.1) (2025-03-09)
15
+
16
+ **Note:** Version bump only for package @quenty/observablecollection
17
+
18
+
19
+
20
+
21
+
6
22
  # [12.19.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.18.1...@quenty/observablecollection@12.19.0) (2025-02-18)
7
23
 
8
24
  **Note:** Version bump only for package @quenty/observablecollection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/observablecollection",
3
- "version": "12.19.0",
3
+ "version": "12.19.2-canary.540.8ec7d75.0",
4
4
  "description": "A set of observable collections, such as sets, maps, sorted lists, and more.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -27,24 +27,24 @@
27
27
  "Quenty"
28
28
  ],
29
29
  "dependencies": {
30
- "@quenty/baseobject": "^10.8.0",
31
- "@quenty/brio": "^14.16.0",
32
- "@quenty/ducktype": "^5.8.0",
33
- "@quenty/loader": "^10.8.0",
34
- "@quenty/maid": "^3.4.0",
35
- "@quenty/promise": "^10.10.0",
36
- "@quenty/rx": "^13.16.0",
37
- "@quenty/signal": "^7.10.0",
38
- "@quenty/steputils": "^3.5.2",
39
- "@quenty/symbol": "^3.4.0",
40
- "@quenty/table": "^3.7.0",
41
- "@quenty/valueobject": "^13.16.0"
30
+ "@quenty/baseobject": "10.8.0",
31
+ "@quenty/brio": "14.16.0",
32
+ "@quenty/ducktype": "5.8.0",
33
+ "@quenty/loader": "10.8.0",
34
+ "@quenty/maid": "3.4.0",
35
+ "@quenty/promise": "10.10.0",
36
+ "@quenty/rx": "13.16.0",
37
+ "@quenty/signal": "7.10.0",
38
+ "@quenty/steputils": "3.5.2",
39
+ "@quenty/symbol": "3.4.0",
40
+ "@quenty/table": "3.7.0",
41
+ "@quenty/valueobject": "13.16.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@quenty/blend": "^12.17.0"
44
+ "@quenty/blend": "12.17.0"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "184a407d8d7366c39009444c3c9a7023cb176471"
49
+ "gitHead": "8ec7d755dd1aa5ed15660666d676aac364d3e96b"
50
50
  }
@@ -96,6 +96,8 @@ function FilteredObservableListView:GetCount()
96
96
  return self._scoredList:GetCount()
97
97
  end
98
98
 
99
+ FilteredObservableListView.__len = FilteredObservableListView.GetCount
100
+
99
101
  --[=[
100
102
  Observes the count of the list
101
103
  @return Observable<number>
@@ -255,6 +255,8 @@ function ObservableCountingMap:GetTotalKeyCount()
255
255
  return self._totalKeyCountValue.Value
256
256
  end
257
257
 
258
+ ObservableCountingMap.__len = ObservableCountingMap.GetTotalKeyCount
259
+
258
260
  --[=[
259
261
  Observes the count of the keys in the map
260
262
  @return Observable<number>
@@ -263,6 +263,8 @@ function ObservableList:GetCount()
263
263
  return self._countValue.Value or 0
264
264
  end
265
265
 
266
+ ObservableList.__len = ObservableList.GetCount
267
+
266
268
  --[=[
267
269
  Observes the count of the list
268
270
  @return Observable<number>
@@ -175,6 +175,8 @@ function ObservableMap:GetCount()
175
175
  return self._countValue.Value or 0
176
176
  end
177
177
 
178
+ ObservableMap.__len = ObservableMap.GetCount
179
+
178
180
  --[=[
179
181
  Observes the count of the set
180
182
 
@@ -146,6 +146,8 @@ function ObservableMapList:GetListCount()
146
146
  return self._observableMapOfLists:GetCount()
147
147
  end
148
148
 
149
+ ObservableMapList.__len = ObservableMapList.GetListCount
150
+
149
151
  --[=[
150
152
  Observes how many lists exist
151
153
 
@@ -150,6 +150,8 @@ function ObservableMapSet:GetSetCount()
150
150
  return self._observableMapOfSets:GetCount()
151
151
  end
152
152
 
153
+ ObservableMapSet.__len = ObservableMapSet.GetSetCount
154
+
153
155
  --[=[
154
156
  Observes how many sets exist
155
157
  @return Observable<number>
@@ -173,6 +173,8 @@ function ObservableSet:GetCount()
173
173
  return self._countValue.Value or 0
174
174
  end
175
175
 
176
+ ObservableSet.__len = ObservableSet.GetCount
177
+
176
178
  --[=[
177
179
  Observes the count of the set
178
180
  @return Observable<number>
@@ -346,6 +346,8 @@ function ObservableSortedList:GetCount()
346
346
  return self._countValue.Value or 0
347
347
  end
348
348
 
349
+ ObservableSortedList.__len = ObservableSortedList.GetCount
350
+
349
351
  --[=[
350
352
  Gets a list of all entries.
351
353
  @return { T }
@@ -2,11 +2,11 @@
2
2
  @class ObservableSortedList.story
3
3
  ]]
4
4
 
5
- local require = require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).bootstrapStory(script)
5
+ local require =
6
+ require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).bootstrapStory(script)
6
7
 
7
8
  local Maid = require("Maid")
8
9
  local ObservableSortedList = require("ObservableSortedList")
9
- local ObservableSortedListOld = require("ObservableSortedListOld")
10
10
 
11
11
  return function(_target)
12
12
  local maid = Maid.new()
@@ -17,23 +17,21 @@ return function(_target)
17
17
  end
18
18
 
19
19
  local startTime = os.clock()
20
- for i=1, n do
20
+ for i = 1, n do
21
21
  add(getElement(i))
22
22
  end
23
23
 
24
- print(string.format("%25s %0.2f ms", label .. " construction", (os.clock() - startTime)*1000))
24
+ print(string.format("%25s %0.2f ms", label .. " construction", (os.clock() - startTime) * 1000))
25
25
  end
26
26
 
27
-
28
27
  local function cleanup(label, sortedList)
29
28
  local startTime = os.clock()
30
29
 
31
30
  sortedList:Destroy()
32
31
 
33
- print(string.format("%25s %0.2f ms", label .. " destruction", (os.clock() - startTime)*1000))
32
+ print(string.format("%25s %0.2f ms", label .. " destruction", (os.clock() - startTime) * 1000))
34
33
  end
35
34
 
36
-
37
35
  local function getRandomElement()
38
36
  return math.random()
39
37
  end
@@ -48,18 +46,13 @@ return function(_target)
48
46
 
49
47
  local function runTest(label, n, getElement)
50
48
  local observableSortedList = maid:Add(ObservableSortedList.new())
51
- local observableSortedListOld = maid:Add(ObservableSortedListOld.new())
52
49
 
53
50
  print(string.format("%25s n = %d", label, n))
54
51
  print(string.format("%25s %8s", string.rep("-", 25), string.rep("-", 10)))
55
52
 
56
- test(n, "prev impl", observableSortedListOld, getElement)
57
- cleanup("prev impl", observableSortedListOld)
58
-
59
53
  test(n, "new impl", observableSortedList, getElement)
60
54
  cleanup("new impl", observableSortedList)
61
55
 
62
-
63
56
  print("\n")
64
57
  end
65
58
 
@@ -71,4 +64,4 @@ return function(_target)
71
64
  return function()
72
65
  maid:DoCleaning()
73
66
  end
74
- end
67
+ end
@@ -1,723 +0,0 @@
1
- --[=[
2
- A list that can be observed for blend and other components and maintains sorting order.
3
-
4
- This class is very expensive to use as it enforces maintaining order on the object. Each entries produces
5
- what is most likely 4-5 tables, and changing can result in O(n) table construction and deltas.
6
-
7
- However, for small lists that don't change frequently, such as a global leaderboard, this can be
8
- a nice small interactive class.
9
-
10
- For performance reasons this class defers firing events until the next defer() event frame.
11
-
12
- This class always prefers to add equivalent elements to the end of the list if they're not in the list.
13
- Otherwise it prefers minimal movement.
14
-
15
- @class ObservableSortedList
16
- ]=]
17
-
18
- local require = require(script.Parent.loader).load(script)
19
-
20
- local Brio = require("Brio")
21
- local Maid = require("Maid")
22
- local Observable = require("Observable")
23
- local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
24
- local Rx = require("Rx")
25
- local Signal = require("Signal")
26
- local Symbol = require("Symbol")
27
- local ValueObject = require("ValueObject")
28
- local DuckTypeUtils = require("DuckTypeUtils")
29
-
30
- -- Higher numbers last
31
- local function defaultCompare(a, b)
32
- -- equivalent of `return a - b` except it supports comparison of strings and stuff
33
- if b > a then
34
- return -1
35
- elseif b < a then
36
- return 1
37
- else
38
- return 0
39
- end
40
- end
41
-
42
- local ObservableSortedList = {}
43
- ObservableSortedList.ClassName = "ObservableSortedList"
44
- ObservableSortedList.__index = ObservableSortedList
45
-
46
- --[=[
47
- Constructs a new ObservableSortedList
48
- @param isReversed boolean
49
- @param compare function
50
- @return ObservableSortedList<T>
51
- ]=]
52
- function ObservableSortedList.new(isReversed, compare)
53
- assert(type(isReversed) == "boolean" or isReversed == nil, "Bad isReversed")
54
-
55
- local self = setmetatable({}, ObservableSortedList)
56
-
57
- self._maid = Maid.new()
58
-
59
- self._keyList = {} -- { [number]: Symbol } -- immutable
60
-
61
- self._indexObservers = self._maid:Add(ObservableSubscriptionTable.new())
62
- self._contentIndexObservers = self._maid:Add(ObservableSubscriptionTable.new())
63
- self._keyObservables = self._maid:Add(ObservableSubscriptionTable.new())
64
-
65
- self._sortValue = {} -- { [Symbol]: number }
66
- self._contents = {} -- { [Symbol]: T }
67
- self._indexes = {} -- { [Symbol]: number }
68
-
69
- self._isReversed = isReversed or false
70
- self._compare = compare or defaultCompare
71
-
72
- self._countValue = self._maid:Add(ValueObject.new(0, "number"))
73
-
74
- --[=[
75
- Fires when an item is added
76
- @readonly
77
- @prop ItemAdded Signal<T, number, Symbol>
78
- @within ObservableSortedList
79
- ]=]
80
- self.ItemAdded = self._maid:Add(Signal.new())
81
-
82
- --[=[
83
- Fires when an item is removed.
84
- @readonly
85
- @prop ItemRemoved self._maid:Add(Signal<T, Symbol>)
86
- @within ObservableSortedList
87
- ]=]
88
- self.ItemRemoved = self._maid:Add(Signal.new())
89
-
90
- --[=[
91
- Fires when an item's order changes.
92
- @readonly
93
- @prop OrderChanged self._maid:Add(Signal<T, Symbol>)
94
- @within ObservableSortedList
95
- ]=]
96
- self.OrderChanged = self._maid:Add(Signal.new())
97
-
98
- --[=[
99
- Fires when the count changes.
100
- @prop CountChanged RBXScriptSignal
101
- @within ObservableSortedList
102
- ]=]
103
- self.CountChanged = self._countValue.Changed
104
-
105
- return self
106
- end
107
-
108
- --[=[
109
- Observes the list, allocating a new list in the process.
110
-
111
- @return Observable<{ T }>
112
- ]=]
113
- function ObservableSortedList:Observe()
114
- return Rx.combineLatest({
115
- Rx.fromSignal(self.ItemAdded):Pipe({ Rx.startWith({ true }) });
116
- Rx.fromSignal(self.ItemRemoved):Pipe({ Rx.startWith({ true }) });
117
- Rx.fromSignal(self.OrderChanged):Pipe({ Rx.startWith({ true }) });
118
- }):Pipe({
119
- Rx.throttleDefer();
120
- Rx.map(function()
121
- return self:GetList();
122
- end);
123
- })
124
- end
125
-
126
- --[=[
127
- Allows iteration over the observable map
128
-
129
- @return (T) -> ((T, nextIndex: any) -> ...any, T?)
130
- ]=]
131
- function ObservableSortedList:__iter()
132
- return coroutine.wrap(function()
133
- for index, value in self._keyList do
134
- coroutine.yield(index, self._contents[value])
135
- end
136
- end)
137
- end
138
-
139
- function ObservableSortedList:Contains(value)
140
- -- TODO: Binary search
141
- for _, item in pairs(self._contents) do
142
- if item == value then
143
- return true
144
- end
145
- end
146
-
147
- return false
148
- end
149
-
150
- --[=[
151
- Returns whether the value is an observable list
152
- @param value any
153
- @return boolean
154
- ]=]
155
- function ObservableSortedList.isObservableSortedList(value)
156
- return DuckTypeUtils.isImplementation(ObservableSortedList, value)
157
- end
158
-
159
- --[=[
160
- Observes all items in the list
161
- @return Observable<Brio<T, Symbol>>
162
- ]=]
163
- function ObservableSortedList:ObserveItemsBrio()
164
- return Observable.new(function(sub)
165
- local maid = Maid.new()
166
-
167
- local function handleItem(item, _index, includeKey)
168
- local brio = Brio.new(item, includeKey)
169
- maid[includeKey] = brio
170
- sub:Fire(brio)
171
- end
172
-
173
- for index, key in pairs(self._keyList) do
174
- handleItem(self._contents[key], index, key)
175
- end
176
-
177
- maid:GiveTask(self.ItemAdded:Connect(handleItem))
178
- maid:GiveTask(self.ItemRemoved:Connect(function(_item, includeKey)
179
- maid[includeKey] = nil
180
- end))
181
-
182
- self._maid[sub] = maid
183
- maid:GiveTask(function()
184
- self._maid[sub] = nil
185
- sub:Complete()
186
- end)
187
-
188
- return maid
189
- end)
190
- end
191
-
192
- --[=[
193
- Gets the first key for a given symbol
194
-
195
- @param content T
196
- @return Symbol
197
- ]=]
198
- function ObservableSortedList:FindFirstKey(content)
199
- for key, item in pairs(self._contents) do
200
- if item == content then
201
- return key
202
- end
203
- end
204
-
205
- return nil
206
- end
207
-
208
- --[=[
209
- Observes the index as it changes, until the entry at the existing
210
- index is removed.
211
-
212
- @param indexToObserve number
213
- @return Observable<number>
214
- ]=]
215
- function ObservableSortedList:ObserveIndex(indexToObserve)
216
- assert(type(indexToObserve) == "number", "Bad indexToObserve")
217
-
218
- local key = self._keyList[indexToObserve]
219
- if not key then
220
- error(string.format("No entry at index %q, cannot observe changes", indexToObserve))
221
- end
222
-
223
- return self:ObserveIndexByKey(key)
224
- end
225
-
226
- --[=[
227
- Observes the current value at a given index. This can be useful for observing
228
- the first entry, or matching stuff up to a given slot.
229
-
230
- @param indexToObserve number
231
- @return Observable<T>
232
- ]=]
233
- function ObservableSortedList:ObserveAtIndex(indexToObserve)
234
- assert(type(indexToObserve) == "number", "Bad indexToObserve")
235
-
236
- return self._indexObservers:Observe(indexToObserve)
237
- :Pipe({
238
- Rx.start(function()
239
- return self:Get(indexToObserve)
240
- end);
241
- })
242
- end
243
-
244
- --[=[
245
- Observes the index as it changes, until the entry at the existing
246
- key is removed.
247
-
248
- @param key Symbol
249
- @return Observable<number>
250
- ]=]
251
- function ObservableSortedList:ObserveIndexByKey(key)
252
- assert(Symbol.isSymbol(key), "Bad key")
253
-
254
- return self._keyObservables:Observe(key):Pipe({
255
- Rx.startFrom(function()
256
- local currentIndex = self._indexes[key]
257
- if currentIndex then
258
- return { currentIndex }
259
- else
260
- return {}
261
- end
262
- end);
263
- })
264
- end
265
-
266
- --[=[
267
- Gets the current index from the key
268
-
269
- @param key Symbol
270
- @return number
271
- ]=]
272
- function ObservableSortedList:GetIndexByKey(key)
273
- local currentIndex = self._indexes[key]
274
- if currentIndex then
275
- return currentIndex
276
- else
277
- return nil
278
- end
279
- end
280
-
281
- --[=[
282
- Gets the count of items in the list
283
- @return number
284
- ]=]
285
- function ObservableSortedList:GetCount()
286
- return self._countValue.Value or 0
287
- end
288
-
289
- --[=[
290
- Gets a list of all entries.
291
- @return { T }
292
- ]=]
293
- function ObservableSortedList:GetList()
294
- local list = table.create(#self._keyList)
295
- for index, key in pairs(self._keyList) do
296
- list[index] = self._contents[key]
297
- end
298
- return list
299
- end
300
-
301
- --[=[
302
- Observes the count of the list
303
- @return Observable<number>
304
- ]=]
305
- function ObservableSortedList:ObserveCount()
306
- return self._countValue:Observe()
307
- end
308
-
309
- --[=[
310
- Adds the item to the list at the specified index
311
- @param item T
312
- @param observeValue Observable<Comparable> | Comparable
313
- @return callback -- Call to remove
314
- ]=]
315
- function ObservableSortedList:Add(item, observeValue)
316
- assert(item ~= nil, "Bad item")
317
- assert(Observable.isObservable(observeValue) or observeValue ~= nil, "Bad observeValue")
318
-
319
- local key = Symbol.named("entryKey")
320
- local maid = Maid.new()
321
-
322
- self._contents[key] = item
323
-
324
- if Observable.isObservable(observeValue) then
325
- maid:GiveTask(observeValue:Subscribe(function(sortValue)
326
- self:_assignSortValue(key, item, sortValue)
327
- end))
328
- elseif observeValue ~= nil then
329
- self:_assignSortValue(key, item, observeValue)
330
- else
331
- error("Bad observeValue")
332
- end
333
-
334
- maid:GiveTask(function()
335
- self:_removeItemByKey(key, item)
336
-
337
- -- Fire off the index change on the value
338
- self._keyObservables:Complete(key)
339
-
340
- self._contents[key] = nil
341
- self._sortValue[key] = nil
342
- end)
343
-
344
- self._maid[key] = maid
345
-
346
- return function()
347
- self._maid[key] = nil
348
- end
349
- end
350
-
351
- function ObservableSortedList:_assignSortValue(key, item, sortValue)
352
- -- self:_debugVerifyIntegrity()
353
-
354
- if sortValue ~= nil then
355
- local currentIndex = self._indexes[key]
356
- local targetIndex = self:_findCorrectIndex(sortValue, currentIndex)
357
-
358
- self._sortValue[key] = sortValue
359
- self:_updateIndex(key, item, targetIndex, sortValue)
360
- else
361
- -- calling this also may unsubscribe some observables.
362
- self:_removeItemByKey(key, item)
363
- self._keyObservables:Complete(key)
364
- end
365
-
366
- -- self:_debugVerifyIntegrity()
367
- end
368
-
369
-
370
- --[=[
371
- Gets the current item at the index, or nil if it is not defined.
372
- @param index number
373
- @return T?
374
- ]=]
375
- function ObservableSortedList:Get(index)
376
- assert(type(index) == "number", "Bad index")
377
-
378
- local key = self._keyList[index]
379
- if not key then
380
- return nil
381
- end
382
-
383
- return self._contents[key]
384
- end
385
-
386
- --[=[
387
- Removes the item from the list if it exists.
388
- @param key Symbol
389
- @return T
390
- ]=]
391
- function ObservableSortedList:RemoveByKey(key)
392
- assert(key ~= nil, "Bad key")
393
-
394
- self._maid[key] = nil
395
- end
396
-
397
- function ObservableSortedList:_updateIndex(key, item, newIndex)
398
- assert(item ~= nil, "Bad item")
399
- assert(type(newIndex) == "number", "Bad newIndex")
400
-
401
- local prevIndex = self._indexes[key]
402
- if prevIndex == newIndex then
403
- return
404
- end
405
-
406
- self._indexes[key] = newIndex
407
-
408
- local changed = {}
409
-
410
- if not prevIndex then
411
- -- shift everything up to fit this space
412
- local n = #self._keyList
413
- for i=n, newIndex, -1 do
414
- local nextKey = self._keyList[i]
415
- self._indexes[nextKey] = i + 1
416
- self._keyList[i + 1] = nextKey
417
-
418
- table.insert(changed, {
419
- key = nextKey;
420
- newIndex = i + 1;
421
- })
422
- end
423
- elseif newIndex > prevIndex then
424
- -- we're shifting down
425
- for i=prevIndex + 1, newIndex do
426
- local nextKey = self._keyList[i]
427
- self._indexes[nextKey] = i - 1
428
- self._keyList[i - 1] = nextKey
429
-
430
- table.insert(changed, {
431
- key = nextKey;
432
- newIndex = i - 1;
433
- })
434
- end
435
- elseif newIndex < prevIndex then
436
- -- we're shifting up
437
-
438
- for i=prevIndex-1, newIndex, -1 do
439
- local belowKey = self._keyList[i]
440
- self._indexes[belowKey] = i + 1
441
- self._keyList[i + 1] = belowKey
442
- table.insert(changed, {
443
- key = belowKey;
444
- newIndex = i + 1;
445
- })
446
- end
447
- else
448
- error("Bad state")
449
- end
450
-
451
- local itemAdded = table.freeze({
452
- key = key;
453
- newIndex = newIndex;
454
- item = item;
455
- })
456
-
457
- -- ensure ourself is considered changed
458
- table.insert(changed, itemAdded)
459
-
460
- self._keyList[newIndex] = key
461
-
462
- -- Fire off our count value changed
463
- -- still O(n^2) but at least we prevent emitting O(n^2) events
464
- if prevIndex == nil then
465
- self:_deferChange(1, itemAdded, nil, changed)
466
- else
467
- self:_deferChange(0, nil, nil, changed)
468
- end
469
- end
470
-
471
- function ObservableSortedList:_removeItemByKey(key, item)
472
- assert(key ~= nil, "Bad key")
473
-
474
- local index = self._indexes[key]
475
- if not index then
476
- return
477
- end
478
-
479
- self._indexes[key] = nil
480
- self._sortValue[key] = nil
481
-
482
- local changed = {}
483
-
484
- -- shift everything down
485
- local n = #self._keyList
486
- for i=index, n - 1 do
487
- local nextKey = self._keyList[i+1]
488
- self._indexes[nextKey] = i
489
- self._keyList[i] = nextKey
490
-
491
- table.insert(changed, {
492
- key = nextKey;
493
- newIndex = i;
494
- })
495
- end
496
- self._keyList[n] = nil
497
-
498
- local itemRemoved = table.freeze({
499
- key = key;
500
- item = item;
501
- previousIndex = index;
502
- })
503
-
504
- -- TODO: Defer item removed as a changed event?
505
-
506
- -- still O(n^2) but at least we prevent emitting O(n^2) events
507
- self:_deferChange(-1, nil, itemRemoved, changed)
508
- end
509
-
510
- function ObservableSortedList:_deferChange(countChange, itemAdded, itemRemoved, indexChanges)
511
- self:_queueDeferredChange()
512
-
513
- if itemAdded then
514
- self._deferredChange.itemsRemoved[itemAdded.key] = nil
515
- self._deferredChange.itemsAdded[itemAdded.key] = itemAdded
516
- end
517
-
518
- if itemRemoved then
519
- self._deferredChange.itemsAdded[itemRemoved.key] = nil
520
- self._deferredChange.itemsRemoved[itemRemoved.key] = itemRemoved
521
- end
522
-
523
- self._deferredChange.countChange += countChange
524
-
525
- for _, data in pairs(indexChanges) do
526
- self._deferredChange.indexChanges[data.key] = data
527
- end
528
- end
529
-
530
- function ObservableSortedList:_queueDeferredChange()
531
- if self._deferredChange then
532
- return
533
- end
534
-
535
- self._deferredChange = {
536
- countChange = 0;
537
- indexChanges = {};
538
- itemsAdded = {};
539
- itemsRemoved = {};
540
- }
541
-
542
- self._maid._currentDefer = task.defer(function()
543
- local snapshot = self._deferredChange
544
- self._deferredChange = nil
545
-
546
- task.spawn(function()
547
- self._maid._currentDefer = nil
548
- local changed = false
549
-
550
- self._countValue.Value = self._countValue.Value + snapshot.countChange
551
-
552
- -- Fire off last adds
553
- for _, lastAdded in pairs(snapshot.itemsAdded) do
554
- if not self.ItemAdded.Destroy then
555
- break
556
- end
557
-
558
- changed = true
559
- self.ItemAdded:Fire(lastAdded.item, lastAdded.newIndex, lastAdded.key)
560
-
561
- -- Item adds are included in indexChanges.
562
- end
563
-
564
- for _, lastRemoved in pairs(snapshot.itemsRemoved) do
565
- if not self.ItemRemoved.Destroy then
566
- break
567
- end
568
-
569
- changed = true
570
- self.ItemRemoved:Fire(lastRemoved.item, lastRemoved.key)
571
-
572
- -- Fire only if we aren't handled by an index change.
573
- if self._keyList[lastRemoved.previousIndex] == nil then
574
- self._indexObservers:Fire(lastRemoved.previousIndex, nil)
575
- end
576
- end
577
-
578
- -- Fire off index change on each key list (if the data isn't stale)
579
- for _, lastChange in pairs(snapshot.indexChanges) do
580
- if self._indexes[lastChange.key] == lastChange.newIndex then
581
- changed = true
582
-
583
- self._keyObservables:Fire(lastChange.key, lastChange.newIndex)
584
- self._indexObservers:Fire(lastChange.newIndex, self._contents[lastChange.key])
585
- end
586
- end
587
-
588
- if changed then
589
- self.OrderChanged:Fire()
590
- end
591
- end)
592
- end)
593
- end
594
-
595
- function ObservableSortedList:_findCorrectIndex(sortValue, currentIndex)
596
- local highInsertionIndex = self:_highBinarySearch(sortValue)
597
-
598
- -- we're inserting, so always insert at end
599
- if not currentIndex then
600
- return highInsertionIndex
601
- end
602
-
603
- local lowInsertionIndex = self:_lowBinarySearch(sortValue)
604
-
605
- -- remember we get insertion index so we need to subtract one
606
- if highInsertionIndex > currentIndex then
607
- highInsertionIndex = highInsertionIndex - 1
608
- end
609
- if lowInsertionIndex > currentIndex then
610
- lowInsertionIndex = lowInsertionIndex - 1
611
- end
612
-
613
- -- prioritize the smallest potential movement
614
- if currentIndex < lowInsertionIndex then
615
- return lowInsertionIndex
616
- elseif currentIndex > highInsertionIndex then
617
- return highInsertionIndex
618
- else
619
- return currentIndex
620
- end
621
- end
622
-
623
- function ObservableSortedList:_highBinarySearch(sortValue)
624
- if #self._keyList == 0 then
625
- return 1
626
- end
627
-
628
- local minIndex = 1
629
- local maxIndex = #self._keyList
630
- while true do
631
- local mid = math.floor((minIndex + maxIndex) / 2)
632
- local compareValue = self._compare(self._sortValue[self._keyList[mid]], sortValue)
633
- if type(compareValue) ~= "number" then
634
- error(string.format("Bad compareValue, expected number, got %q", type(compareValue)))
635
- end
636
-
637
- if self._isReversed then
638
- compareValue = -compareValue
639
- end
640
-
641
- if compareValue > 0 then
642
- maxIndex = mid - 1
643
- if minIndex > maxIndex then
644
- return mid
645
- end
646
- else
647
- minIndex = mid + 1
648
- if minIndex > maxIndex then
649
- return mid + 1
650
- end
651
- end
652
- end
653
- end
654
-
655
- function ObservableSortedList:_lowBinarySearch(sortValue)
656
- if #self._keyList == 0 then
657
- return 1
658
- end
659
-
660
- local minIndex = 1
661
- local maxIndex = #self._keyList
662
- while true do
663
- local mid = math.floor((minIndex + maxIndex) / 2)
664
- local compareValue = self._compare(self._sortValue[self._keyList[mid]], sortValue)
665
- if type(compareValue) ~= "number" then
666
- error(string.format("Bad compareValue, expected number, got %q", type(compareValue)))
667
- end
668
-
669
- if self._isReversed then
670
- compareValue = -compareValue
671
- end
672
-
673
- if compareValue < 0 then
674
- minIndex = mid + 1
675
- if minIndex > maxIndex then
676
- return mid + 1
677
- end
678
- else
679
- maxIndex = mid - 1
680
- if minIndex > maxIndex then
681
- return mid
682
- end
683
- end
684
- end
685
- end
686
-
687
- function ObservableSortedList:_debugVerifyIntegrity()
688
- for i=2, #self._keyList do
689
- local compare = self._compare(self._sortValue[self._keyList[i-1]], self._sortValue[self._keyList[i]])
690
- if self._isReversed then
691
- compare = -compare
692
- end
693
- if compare > 0 then
694
- warn(string.format("Bad sorted list state %s at index %d", self:_debugSortValuesToString(), i))
695
- end
696
- end
697
-
698
- for i=1, #self._keyList do
699
- if self._indexes[self._keyList[i]] ~= i then
700
- warn(string.format("Index is out of date for %d for %s", i, self:_debugSortValuesToString()))
701
- end
702
- end
703
- end
704
-
705
- function ObservableSortedList:_debugSortValuesToString()
706
- local values = {}
707
-
708
- for _, key in pairs(self._keyList) do
709
- table.insert(values, string.format("%4d", self._sortValue[key]))
710
- end
711
-
712
- return table.concat(values, ", ")
713
- end
714
-
715
- --[=[
716
- Cleans up the ObservableSortedList and sets the metatable to nil.
717
- ]=]
718
- function ObservableSortedList:Destroy()
719
- self._maid:DoCleaning()
720
- setmetatable(self, nil)
721
- end
722
-
723
- return ObservableSortedList