@quenty/observablecollection 12.11.1 → 12.12.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 +27 -0
- package/package.json +10 -9
- package/src/Shared/ObservableCountingMap.lua +9 -0
- package/src/Shared/ObservableList.lua +37 -28
- package/src/Shared/ObservableMap.lua +10 -0
- package/src/Shared/ObservableSet.lua +9 -0
- package/src/Shared/SortedList/ObservableSortedList.lua +586 -0
- package/src/Shared/{ObservableSortedList.story.lua → SortedList/ObservableSortedList.story.lua} +15 -12
- package/src/Shared/{ObservableSortedList.lua → SortedList/ObservableSortedListOld.lua} +37 -74
- package/src/Shared/SortedList/ObservableSortedList_Performance.story.lua +74 -0
- package/src/Shared/SortedList/ObservableSortedList_Print.story.lua +65 -0
- package/src/Shared/SortedList/SortFunctionUtils.lua +31 -0
- package/src/Shared/SortedList/SortedNode.lua +1171 -0
- package/src/Shared/SortedList/SortedNodeValue.lua +53 -0
- package/src/Shared/Utils/ListIndexUtils.lua +39 -0
- package/test/default.project.json +1 -7
- /package/src/Shared/{ObservableSortedList.spec.lua → SortedList/ObservableSortedList.spec.lua} +0 -0
|
@@ -60,13 +60,12 @@ function ObservableSortedList.new(isReversed, compare)
|
|
|
60
60
|
|
|
61
61
|
self._indexObservers = self._maid:Add(ObservableSubscriptionTable.new())
|
|
62
62
|
self._contentIndexObservers = self._maid:Add(ObservableSubscriptionTable.new())
|
|
63
|
+
self._keyObservables = self._maid:Add(ObservableSubscriptionTable.new())
|
|
63
64
|
|
|
64
65
|
self._sortValue = {} -- { [Symbol]: number }
|
|
65
66
|
self._contents = {} -- { [Symbol]: T }
|
|
66
67
|
self._indexes = {} -- { [Symbol]: number }
|
|
67
68
|
|
|
68
|
-
self._keyObservables = {} -- { [Symbol]: { Subscription } }
|
|
69
|
-
|
|
70
69
|
self._isReversed = isReversed or false
|
|
71
70
|
self._compare = compare or defaultCompare
|
|
72
71
|
|
|
@@ -124,6 +123,19 @@ function ObservableSortedList:Observe()
|
|
|
124
123
|
})
|
|
125
124
|
end
|
|
126
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
|
+
|
|
127
139
|
function ObservableSortedList:Contains(value)
|
|
128
140
|
-- TODO: Binary search
|
|
129
141
|
for _, item in pairs(self._contents) do
|
|
@@ -239,33 +251,16 @@ end
|
|
|
239
251
|
function ObservableSortedList:ObserveIndexByKey(key)
|
|
240
252
|
assert(Symbol.isSymbol(key), "Bad key")
|
|
241
253
|
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
sub:Fire(currentIndex)
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
maid:GiveTask(function()
|
|
253
|
-
local list = self._keyObservables[key]
|
|
254
|
-
if not list then
|
|
255
|
-
return
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
local index = table.find(list, sub)
|
|
259
|
-
if index then
|
|
260
|
-
table.remove(list, index)
|
|
261
|
-
if #list == 0 then
|
|
262
|
-
self._keyObservables[key] = nil
|
|
263
|
-
end
|
|
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 {}
|
|
264
261
|
end
|
|
265
|
-
end)
|
|
266
|
-
|
|
267
|
-
return maid
|
|
268
|
-
end)
|
|
262
|
+
end);
|
|
263
|
+
})
|
|
269
264
|
end
|
|
270
265
|
|
|
271
266
|
--[=[
|
|
@@ -337,15 +332,10 @@ function ObservableSortedList:Add(item, observeValue)
|
|
|
337
332
|
end
|
|
338
333
|
|
|
339
334
|
maid:GiveTask(function()
|
|
340
|
-
local observableSubs = self._keyObservables[key]
|
|
341
|
-
self._keyObservables[key] = nil
|
|
342
|
-
|
|
343
335
|
self:_removeItemByKey(key, item)
|
|
344
336
|
|
|
345
337
|
-- Fire off the index change on the value
|
|
346
|
-
|
|
347
|
-
self:_completeSubs(observableSubs)
|
|
348
|
-
end
|
|
338
|
+
self._keyObservables:Complete(key)
|
|
349
339
|
|
|
350
340
|
self._contents[key] = nil
|
|
351
341
|
self._sortValue[key] = nil
|
|
@@ -359,7 +349,7 @@ function ObservableSortedList:Add(item, observeValue)
|
|
|
359
349
|
end
|
|
360
350
|
|
|
361
351
|
function ObservableSortedList:_assignSortValue(key, item, sortValue)
|
|
362
|
-
self:_debugVerifyIntegrity()
|
|
352
|
+
-- self:_debugVerifyIntegrity()
|
|
363
353
|
|
|
364
354
|
if sortValue ~= nil then
|
|
365
355
|
local currentIndex = self._indexes[key]
|
|
@@ -368,18 +358,12 @@ function ObservableSortedList:_assignSortValue(key, item, sortValue)
|
|
|
368
358
|
self._sortValue[key] = sortValue
|
|
369
359
|
self:_updateIndex(key, item, targetIndex, sortValue)
|
|
370
360
|
else
|
|
371
|
-
local observableSubs = self._keyObservables[key]
|
|
372
|
-
|
|
373
361
|
-- calling this also may unsubscribe some observables.
|
|
374
362
|
self:_removeItemByKey(key, item)
|
|
375
|
-
|
|
376
|
-
if observableSubs then
|
|
377
|
-
-- fire nil index
|
|
378
|
-
self:_fireSubs(observableSubs, nil)
|
|
379
|
-
end
|
|
363
|
+
self._keyObservables:Complete(key)
|
|
380
364
|
end
|
|
381
365
|
|
|
382
|
-
self:_debugVerifyIntegrity()
|
|
366
|
+
-- self:_debugVerifyIntegrity()
|
|
383
367
|
end
|
|
384
368
|
|
|
385
369
|
|
|
@@ -596,11 +580,7 @@ function ObservableSortedList:_queueDeferredChange()
|
|
|
596
580
|
if self._indexes[lastChange.key] == lastChange.newIndex then
|
|
597
581
|
changed = true
|
|
598
582
|
|
|
599
|
-
|
|
600
|
-
if subs then
|
|
601
|
-
self:_fireSubs(subs, lastChange.newIndex)
|
|
602
|
-
end
|
|
603
|
-
|
|
583
|
+
self._keyObservables:Fire(lastChange.key, lastChange.newIndex)
|
|
604
584
|
self._indexObservers:Fire(lastChange.newIndex, self._contents[lastChange.key])
|
|
605
585
|
end
|
|
606
586
|
end
|
|
@@ -682,7 +662,9 @@ function ObservableSortedList:_lowBinarySearch(sortValue)
|
|
|
682
662
|
while true do
|
|
683
663
|
local mid = math.floor((minIndex + maxIndex) / 2)
|
|
684
664
|
local compareValue = self._compare(self._sortValue[self._keyList[mid]], sortValue)
|
|
685
|
-
|
|
665
|
+
if type(compareValue) ~= "number" then
|
|
666
|
+
error(string.format("Bad compareValue, expected number, got %q", type(compareValue)))
|
|
667
|
+
end
|
|
686
668
|
|
|
687
669
|
if self._isReversed then
|
|
688
670
|
compareValue = -compareValue
|
|
@@ -702,16 +684,6 @@ function ObservableSortedList:_lowBinarySearch(sortValue)
|
|
|
702
684
|
end
|
|
703
685
|
end
|
|
704
686
|
|
|
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
687
|
function ObservableSortedList:_debugVerifyIntegrity()
|
|
716
688
|
for i=2, #self._keyList do
|
|
717
689
|
local compare = self._compare(self._sortValue[self._keyList[i-1]], self._sortValue[self._keyList[i]])
|
|
@@ -730,23 +702,14 @@ function ObservableSortedList:_debugVerifyIntegrity()
|
|
|
730
702
|
end
|
|
731
703
|
end
|
|
732
704
|
|
|
733
|
-
function ObservableSortedList:
|
|
734
|
-
|
|
735
|
-
if sub:IsPending() then
|
|
736
|
-
task.spawn(function()
|
|
737
|
-
sub:Fire(index)
|
|
738
|
-
end)
|
|
739
|
-
end
|
|
740
|
-
end
|
|
741
|
-
end
|
|
705
|
+
function ObservableSortedList:_debugSortValuesToString()
|
|
706
|
+
local values = {}
|
|
742
707
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if sub:IsPending() then
|
|
746
|
-
sub:Fire(nil)
|
|
747
|
-
sub:Complete()
|
|
748
|
-
end
|
|
708
|
+
for _, key in pairs(self._keyList) do
|
|
709
|
+
table.insert(values, string.format("%4d", self._sortValue[key]))
|
|
749
710
|
end
|
|
711
|
+
|
|
712
|
+
return table.concat(values, ", ")
|
|
750
713
|
end
|
|
751
714
|
|
|
752
715
|
--[=[
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ObservableSortedList.story
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local require = require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).bootstrapStory(script)
|
|
6
|
+
|
|
7
|
+
local Maid = require("Maid")
|
|
8
|
+
local ObservableSortedList = require("ObservableSortedList")
|
|
9
|
+
local ObservableSortedListOld = require("ObservableSortedListOld")
|
|
10
|
+
|
|
11
|
+
return function(_target)
|
|
12
|
+
local maid = Maid.new()
|
|
13
|
+
|
|
14
|
+
local function test(n, label, sortedList, getElement)
|
|
15
|
+
local function add(number)
|
|
16
|
+
sortedList:Add(tostring(number), number)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
local startTime = os.clock()
|
|
20
|
+
for i=1, n do
|
|
21
|
+
add(getElement(i))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
print(string.format("%25s %0.2f ms", label .. " construction", (os.clock() - startTime)*1000))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
local function cleanup(label, sortedList)
|
|
29
|
+
local startTime = os.clock()
|
|
30
|
+
|
|
31
|
+
sortedList:Destroy()
|
|
32
|
+
|
|
33
|
+
print(string.format("%25s %0.2f ms", label .. " destruction", (os.clock() - startTime)*1000))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
local function getRandomElement()
|
|
38
|
+
return math.random()
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local function inOrder(i)
|
|
42
|
+
return i
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
local function same()
|
|
46
|
+
return 0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
local function runTest(label, n, getElement)
|
|
50
|
+
local observableSortedList = maid:Add(ObservableSortedList.new())
|
|
51
|
+
local observableSortedListOld = maid:Add(ObservableSortedListOld.new())
|
|
52
|
+
|
|
53
|
+
print(string.format("%25s n = %d", label, n))
|
|
54
|
+
print(string.format("%25s %8s", string.rep("-", 25), string.rep("-", 10)))
|
|
55
|
+
|
|
56
|
+
test(n, "prev impl", observableSortedListOld, getElement)
|
|
57
|
+
cleanup("prev impl", observableSortedListOld)
|
|
58
|
+
|
|
59
|
+
test(n, "new impl", observableSortedList, getElement)
|
|
60
|
+
cleanup("new impl", observableSortedList)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
print("\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
local n = 1000
|
|
67
|
+
runTest("test random_order", n, getRandomElement)
|
|
68
|
+
runTest("test in_order", n, inOrder)
|
|
69
|
+
runTest("same", n, same)
|
|
70
|
+
|
|
71
|
+
return function()
|
|
72
|
+
maid:DoCleaning()
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ObservableSortedList.story
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local require = require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).bootstrapStory(script)
|
|
6
|
+
|
|
7
|
+
local Maid = require("Maid")
|
|
8
|
+
local ObservableSortedList = require("ObservableSortedList")
|
|
9
|
+
|
|
10
|
+
return function(_target)
|
|
11
|
+
local maid = Maid.new()
|
|
12
|
+
|
|
13
|
+
print("----")
|
|
14
|
+
|
|
15
|
+
task.spawn(function()
|
|
16
|
+
local observableSortedList = maid:Add(ObservableSortedList.new())
|
|
17
|
+
|
|
18
|
+
local toRemove = {}
|
|
19
|
+
|
|
20
|
+
local function add(number)
|
|
21
|
+
table.insert(toRemove, observableSortedList:Add(tostring(number), number))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
-- local random = Random.new(5000)
|
|
25
|
+
-- for i=1, 10 do
|
|
26
|
+
-- add(random:NextNumber())
|
|
27
|
+
-- end
|
|
28
|
+
|
|
29
|
+
local random = Random.new()
|
|
30
|
+
for _i=1, 10 do
|
|
31
|
+
add(math.floor(100*random:NextNumber()))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
for index, node in observableSortedList._root:IterateNodesRange(3, 7) do
|
|
35
|
+
print(index, node:GetIndex())
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
observableSortedList:PrintDebug()
|
|
39
|
+
|
|
40
|
+
for _, item in toRemove do
|
|
41
|
+
item()
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
-- observableSortedList:PrintDebug()
|
|
45
|
+
observableSortedList:Destroy()
|
|
46
|
+
end)
|
|
47
|
+
|
|
48
|
+
-- for i=1, 10 do
|
|
49
|
+
-- add(-i)
|
|
50
|
+
-- add(i)
|
|
51
|
+
-- end
|
|
52
|
+
|
|
53
|
+
-- add(2)
|
|
54
|
+
-- add(1)
|
|
55
|
+
-- add(3)
|
|
56
|
+
-- add(4)
|
|
57
|
+
-- add(5)
|
|
58
|
+
-- add(0)
|
|
59
|
+
|
|
60
|
+
-- print(observableSortedList:GetList())
|
|
61
|
+
|
|
62
|
+
return function()
|
|
63
|
+
maid:DoCleaning()
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class SortFunctionUtils
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local SortFunctionUtils = {}
|
|
8
|
+
|
|
9
|
+
function SortFunctionUtils.reverse(compare)
|
|
10
|
+
compare = compare or SortFunctionUtils.default
|
|
11
|
+
return function(a, b)
|
|
12
|
+
return compare(b, a)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
-- Higher numbers last
|
|
17
|
+
function SortFunctionUtils.default(a, b)
|
|
18
|
+
-- equivalent of `return a - b` except it supports comparison of strings and stuff
|
|
19
|
+
if b > a then
|
|
20
|
+
return -1
|
|
21
|
+
elseif b < a then
|
|
22
|
+
return 1
|
|
23
|
+
else
|
|
24
|
+
return 0
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
function SortFunctionUtils.emptyIterator()
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return SortFunctionUtils
|