@quenty/observablecollection 12.19.1 → 12.19.2

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,14 @@
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](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.19.1...@quenty/observablecollection@12.19.2) (2025-03-13)
7
+
8
+ **Note:** Version bump only for package @quenty/observablecollection
9
+
10
+
11
+
12
+
13
+
6
14
  ## [12.19.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.19.0...@quenty/observablecollection@12.19.1) (2025-03-09)
7
15
 
8
16
  **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.1",
3
+ "version": "12.19.2",
4
4
  "description": "A set of observable collections, such as sets, maps, sorted lists, and more.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -46,5 +46,5 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "6721857f84fe0a63aa9eda4eb40e084921ba8534"
49
+ "gitHead": "454a991b6f45e1db7b8030e216a1a8d6efc34083"
50
50
  }
@@ -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,725 +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
- ObservableSortedList.__len = ObservableSortedList.GetCount
290
-
291
- --[=[
292
- Gets a list of all entries.
293
- @return { T }
294
- ]=]
295
- function ObservableSortedList:GetList()
296
- local list = table.create(#self._keyList)
297
- for index, key in pairs(self._keyList) do
298
- list[index] = self._contents[key]
299
- end
300
- return list
301
- end
302
-
303
- --[=[
304
- Observes the count of the list
305
- @return Observable<number>
306
- ]=]
307
- function ObservableSortedList:ObserveCount()
308
- return self._countValue:Observe()
309
- end
310
-
311
- --[=[
312
- Adds the item to the list at the specified index
313
- @param item T
314
- @param observeValue Observable<Comparable> | Comparable
315
- @return callback -- Call to remove
316
- ]=]
317
- function ObservableSortedList:Add(item, observeValue)
318
- assert(item ~= nil, "Bad item")
319
- assert(Observable.isObservable(observeValue) or observeValue ~= nil, "Bad observeValue")
320
-
321
- local key = Symbol.named("entryKey")
322
- local maid = Maid.new()
323
-
324
- self._contents[key] = item
325
-
326
- if Observable.isObservable(observeValue) then
327
- maid:GiveTask(observeValue:Subscribe(function(sortValue)
328
- self:_assignSortValue(key, item, sortValue)
329
- end))
330
- elseif observeValue ~= nil then
331
- self:_assignSortValue(key, item, observeValue)
332
- else
333
- error("Bad observeValue")
334
- end
335
-
336
- maid:GiveTask(function()
337
- self:_removeItemByKey(key, item)
338
-
339
- -- Fire off the index change on the value
340
- self._keyObservables:Complete(key)
341
-
342
- self._contents[key] = nil
343
- self._sortValue[key] = nil
344
- end)
345
-
346
- self._maid[key] = maid
347
-
348
- return function()
349
- self._maid[key] = nil
350
- end
351
- end
352
-
353
- function ObservableSortedList:_assignSortValue(key, item, sortValue)
354
- -- self:_debugVerifyIntegrity()
355
-
356
- if sortValue ~= nil then
357
- local currentIndex = self._indexes[key]
358
- local targetIndex = self:_findCorrectIndex(sortValue, currentIndex)
359
-
360
- self._sortValue[key] = sortValue
361
- self:_updateIndex(key, item, targetIndex, sortValue)
362
- else
363
- -- calling this also may unsubscribe some observables.
364
- self:_removeItemByKey(key, item)
365
- self._keyObservables:Complete(key)
366
- end
367
-
368
- -- self:_debugVerifyIntegrity()
369
- end
370
-
371
-
372
- --[=[
373
- Gets the current item at the index, or nil if it is not defined.
374
- @param index number
375
- @return T?
376
- ]=]
377
- function ObservableSortedList:Get(index)
378
- assert(type(index) == "number", "Bad index")
379
-
380
- local key = self._keyList[index]
381
- if not key then
382
- return nil
383
- end
384
-
385
- return self._contents[key]
386
- end
387
-
388
- --[=[
389
- Removes the item from the list if it exists.
390
- @param key Symbol
391
- @return T
392
- ]=]
393
- function ObservableSortedList:RemoveByKey(key)
394
- assert(key ~= nil, "Bad key")
395
-
396
- self._maid[key] = nil
397
- end
398
-
399
- function ObservableSortedList:_updateIndex(key, item, newIndex)
400
- assert(item ~= nil, "Bad item")
401
- assert(type(newIndex) == "number", "Bad newIndex")
402
-
403
- local prevIndex = self._indexes[key]
404
- if prevIndex == newIndex then
405
- return
406
- end
407
-
408
- self._indexes[key] = newIndex
409
-
410
- local changed = {}
411
-
412
- if not prevIndex then
413
- -- shift everything up to fit this space
414
- local n = #self._keyList
415
- for i=n, newIndex, -1 do
416
- local nextKey = self._keyList[i]
417
- self._indexes[nextKey] = i + 1
418
- self._keyList[i + 1] = nextKey
419
-
420
- table.insert(changed, {
421
- key = nextKey;
422
- newIndex = i + 1;
423
- })
424
- end
425
- elseif newIndex > prevIndex then
426
- -- we're shifting down
427
- for i=prevIndex + 1, newIndex do
428
- local nextKey = self._keyList[i]
429
- self._indexes[nextKey] = i - 1
430
- self._keyList[i - 1] = nextKey
431
-
432
- table.insert(changed, {
433
- key = nextKey;
434
- newIndex = i - 1;
435
- })
436
- end
437
- elseif newIndex < prevIndex then
438
- -- we're shifting up
439
-
440
- for i=prevIndex-1, newIndex, -1 do
441
- local belowKey = self._keyList[i]
442
- self._indexes[belowKey] = i + 1
443
- self._keyList[i + 1] = belowKey
444
- table.insert(changed, {
445
- key = belowKey;
446
- newIndex = i + 1;
447
- })
448
- end
449
- else
450
- error("Bad state")
451
- end
452
-
453
- local itemAdded = table.freeze({
454
- key = key;
455
- newIndex = newIndex;
456
- item = item;
457
- })
458
-
459
- -- ensure ourself is considered changed
460
- table.insert(changed, itemAdded)
461
-
462
- self._keyList[newIndex] = key
463
-
464
- -- Fire off our count value changed
465
- -- still O(n^2) but at least we prevent emitting O(n^2) events
466
- if prevIndex == nil then
467
- self:_deferChange(1, itemAdded, nil, changed)
468
- else
469
- self:_deferChange(0, nil, nil, changed)
470
- end
471
- end
472
-
473
- function ObservableSortedList:_removeItemByKey(key, item)
474
- assert(key ~= nil, "Bad key")
475
-
476
- local index = self._indexes[key]
477
- if not index then
478
- return
479
- end
480
-
481
- self._indexes[key] = nil
482
- self._sortValue[key] = nil
483
-
484
- local changed = {}
485
-
486
- -- shift everything down
487
- local n = #self._keyList
488
- for i=index, n - 1 do
489
- local nextKey = self._keyList[i+1]
490
- self._indexes[nextKey] = i
491
- self._keyList[i] = nextKey
492
-
493
- table.insert(changed, {
494
- key = nextKey;
495
- newIndex = i;
496
- })
497
- end
498
- self._keyList[n] = nil
499
-
500
- local itemRemoved = table.freeze({
501
- key = key;
502
- item = item;
503
- previousIndex = index;
504
- })
505
-
506
- -- TODO: Defer item removed as a changed event?
507
-
508
- -- still O(n^2) but at least we prevent emitting O(n^2) events
509
- self:_deferChange(-1, nil, itemRemoved, changed)
510
- end
511
-
512
- function ObservableSortedList:_deferChange(countChange, itemAdded, itemRemoved, indexChanges)
513
- self:_queueDeferredChange()
514
-
515
- if itemAdded then
516
- self._deferredChange.itemsRemoved[itemAdded.key] = nil
517
- self._deferredChange.itemsAdded[itemAdded.key] = itemAdded
518
- end
519
-
520
- if itemRemoved then
521
- self._deferredChange.itemsAdded[itemRemoved.key] = nil
522
- self._deferredChange.itemsRemoved[itemRemoved.key] = itemRemoved
523
- end
524
-
525
- self._deferredChange.countChange += countChange
526
-
527
- for _, data in pairs(indexChanges) do
528
- self._deferredChange.indexChanges[data.key] = data
529
- end
530
- end
531
-
532
- function ObservableSortedList:_queueDeferredChange()
533
- if self._deferredChange then
534
- return
535
- end
536
-
537
- self._deferredChange = {
538
- countChange = 0;
539
- indexChanges = {};
540
- itemsAdded = {};
541
- itemsRemoved = {};
542
- }
543
-
544
- self._maid._currentDefer = task.defer(function()
545
- local snapshot = self._deferredChange
546
- self._deferredChange = nil
547
-
548
- task.spawn(function()
549
- self._maid._currentDefer = nil
550
- local changed = false
551
-
552
- self._countValue.Value = self._countValue.Value + snapshot.countChange
553
-
554
- -- Fire off last adds
555
- for _, lastAdded in pairs(snapshot.itemsAdded) do
556
- if not self.ItemAdded.Destroy then
557
- break
558
- end
559
-
560
- changed = true
561
- self.ItemAdded:Fire(lastAdded.item, lastAdded.newIndex, lastAdded.key)
562
-
563
- -- Item adds are included in indexChanges.
564
- end
565
-
566
- for _, lastRemoved in pairs(snapshot.itemsRemoved) do
567
- if not self.ItemRemoved.Destroy then
568
- break
569
- end
570
-
571
- changed = true
572
- self.ItemRemoved:Fire(lastRemoved.item, lastRemoved.key)
573
-
574
- -- Fire only if we aren't handled by an index change.
575
- if self._keyList[lastRemoved.previousIndex] == nil then
576
- self._indexObservers:Fire(lastRemoved.previousIndex, nil)
577
- end
578
- end
579
-
580
- -- Fire off index change on each key list (if the data isn't stale)
581
- for _, lastChange in pairs(snapshot.indexChanges) do
582
- if self._indexes[lastChange.key] == lastChange.newIndex then
583
- changed = true
584
-
585
- self._keyObservables:Fire(lastChange.key, lastChange.newIndex)
586
- self._indexObservers:Fire(lastChange.newIndex, self._contents[lastChange.key])
587
- end
588
- end
589
-
590
- if changed then
591
- self.OrderChanged:Fire()
592
- end
593
- end)
594
- end)
595
- end
596
-
597
- function ObservableSortedList:_findCorrectIndex(sortValue, currentIndex)
598
- local highInsertionIndex = self:_highBinarySearch(sortValue)
599
-
600
- -- we're inserting, so always insert at end
601
- if not currentIndex then
602
- return highInsertionIndex
603
- end
604
-
605
- local lowInsertionIndex = self:_lowBinarySearch(sortValue)
606
-
607
- -- remember we get insertion index so we need to subtract one
608
- if highInsertionIndex > currentIndex then
609
- highInsertionIndex = highInsertionIndex - 1
610
- end
611
- if lowInsertionIndex > currentIndex then
612
- lowInsertionIndex = lowInsertionIndex - 1
613
- end
614
-
615
- -- prioritize the smallest potential movement
616
- if currentIndex < lowInsertionIndex then
617
- return lowInsertionIndex
618
- elseif currentIndex > highInsertionIndex then
619
- return highInsertionIndex
620
- else
621
- return currentIndex
622
- end
623
- end
624
-
625
- function ObservableSortedList:_highBinarySearch(sortValue)
626
- if #self._keyList == 0 then
627
- return 1
628
- end
629
-
630
- local minIndex = 1
631
- local maxIndex = #self._keyList
632
- while true do
633
- local mid = math.floor((minIndex + maxIndex) / 2)
634
- local compareValue = self._compare(self._sortValue[self._keyList[mid]], sortValue)
635
- if type(compareValue) ~= "number" then
636
- error(string.format("Bad compareValue, expected number, got %q", type(compareValue)))
637
- end
638
-
639
- if self._isReversed then
640
- compareValue = -compareValue
641
- end
642
-
643
- if compareValue > 0 then
644
- maxIndex = mid - 1
645
- if minIndex > maxIndex then
646
- return mid
647
- end
648
- else
649
- minIndex = mid + 1
650
- if minIndex > maxIndex then
651
- return mid + 1
652
- end
653
- end
654
- end
655
- end
656
-
657
- function ObservableSortedList:_lowBinarySearch(sortValue)
658
- if #self._keyList == 0 then
659
- return 1
660
- end
661
-
662
- local minIndex = 1
663
- local maxIndex = #self._keyList
664
- while true do
665
- local mid = math.floor((minIndex + maxIndex) / 2)
666
- local compareValue = self._compare(self._sortValue[self._keyList[mid]], sortValue)
667
- if type(compareValue) ~= "number" then
668
- error(string.format("Bad compareValue, expected number, got %q", type(compareValue)))
669
- end
670
-
671
- if self._isReversed then
672
- compareValue = -compareValue
673
- end
674
-
675
- if compareValue < 0 then
676
- minIndex = mid + 1
677
- if minIndex > maxIndex then
678
- return mid + 1
679
- end
680
- else
681
- maxIndex = mid - 1
682
- if minIndex > maxIndex then
683
- return mid
684
- end
685
- end
686
- end
687
- end
688
-
689
- function ObservableSortedList:_debugVerifyIntegrity()
690
- for i=2, #self._keyList do
691
- local compare = self._compare(self._sortValue[self._keyList[i-1]], self._sortValue[self._keyList[i]])
692
- if self._isReversed then
693
- compare = -compare
694
- end
695
- if compare > 0 then
696
- warn(string.format("Bad sorted list state %s at index %d", self:_debugSortValuesToString(), i))
697
- end
698
- end
699
-
700
- for i=1, #self._keyList do
701
- if self._indexes[self._keyList[i]] ~= i then
702
- warn(string.format("Index is out of date for %d for %s", i, self:_debugSortValuesToString()))
703
- end
704
- end
705
- end
706
-
707
- function ObservableSortedList:_debugSortValuesToString()
708
- local values = {}
709
-
710
- for _, key in pairs(self._keyList) do
711
- table.insert(values, string.format("%4d", self._sortValue[key]))
712
- end
713
-
714
- return table.concat(values, ", ")
715
- end
716
-
717
- --[=[
718
- Cleans up the ObservableSortedList and sets the metatable to nil.
719
- ]=]
720
- function ObservableSortedList:Destroy()
721
- self._maid:DoCleaning()
722
- setmetatable(self, nil)
723
- end
724
-
725
- return ObservableSortedList