@quenty/observablecollection 3.2.0 → 3.3.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
+ # [3.3.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@3.2.0...@quenty/observablecollection@3.3.0) (2022-07-31)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Prevent observables from being removed during insertion of observable ([27a3594](https://github.com/Quenty/NevermoreEngine/commit/27a35941f457f63a5f1dc84169448a750308fdc2))
12
+
13
+
14
+ ### Features
15
+
16
+ * Add ObservableSortedList ([cca209f](https://github.com/Quenty/NevermoreEngine/commit/cca209fd8a6c2cfeb1ee6e39d2aabce0202b3072))
17
+
18
+
19
+
20
+
21
+
6
22
  # [3.2.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@3.1.0...@quenty/observablecollection@3.2.0) (2022-07-02)
7
23
 
8
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/observablecollection",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "An observable set",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -27,17 +27,17 @@
27
27
  "Quenty"
28
28
  ],
29
29
  "dependencies": {
30
- "@quenty/brio": "^6.1.0",
30
+ "@quenty/brio": "^6.2.0",
31
31
  "@quenty/loader": "^5.0.0",
32
- "@quenty/maid": "^2.3.0",
33
- "@quenty/promise": "^5.0.0",
34
- "@quenty/rx": "^5.1.0",
32
+ "@quenty/maid": "^2.4.0",
33
+ "@quenty/promise": "^5.1.0",
34
+ "@quenty/rx": "^5.2.0",
35
35
  "@quenty/signal": "^2.2.0",
36
36
  "@quenty/symbol": "^2.1.0",
37
- "@quenty/valuebaseutils": "^5.1.0"
37
+ "@quenty/valuebaseutils": "^5.2.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "gitHead": "de33c83e7f897e2b0887c77aeb1fc7963756f234"
42
+ "gitHead": "e31b3a35aa475bb5699a24898a8639e107165b36"
43
43
  }
@@ -131,7 +131,7 @@ end
131
131
  @return Observable<number>
132
132
  ]=]
133
133
  function ObservableList:ObserveIndexByKey(key)
134
- assert(key, "Bad key")
134
+ assert(type(key) == "userdata", "Bad key")
135
135
 
136
136
  return Observable.new(function(sub)
137
137
  local currentIndex = self._indexes[key]
@@ -266,18 +266,17 @@ function ObservableList:InsertAt(item, index)
266
266
 
267
267
  -- Fire off the index change on the value
268
268
  do
269
- local list = self._keyObservables[key]
270
- if list then
271
- self._keyObservables[key] = nil
272
-
273
- for _, sub in pairs(list) do
274
- if sub:IsPending() then
275
- sub:Fire(index)
276
- end
277
- end
269
+ local subs = self._keyObservables[key]
270
+ if subs then
271
+ table.insert(changed, {
272
+ key = key;
273
+ newIndex = index;
274
+ subs = subs;
275
+ })
278
276
  end
279
277
  end
280
278
 
279
+
281
280
  -- Fire off index change on each key list (if the data isn't stale)
282
281
  for _, data in pairs(changed) do
283
282
  if self._indexes[data.key] == data.newIndex then
@@ -322,7 +321,7 @@ function ObservableList:RemoveByKey(key)
322
321
  end
323
322
 
324
323
  local item = self._contents[key]
325
- if not item then
324
+ if item == nil then
326
325
  return nil
327
326
  end
328
327
 
@@ -0,0 +1,526 @@
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
+ @class ObservableSortedList
13
+ ]=]
14
+
15
+ local require = require(script.Parent.loader).load(script)
16
+
17
+ local Signal = require("Signal")
18
+ local Observable = require("Observable")
19
+ local Maid = require("Maid")
20
+ local Brio = require("Brio")
21
+ local RxValueBaseUtils = require("RxValueBaseUtils")
22
+ local Symbol = require("Symbol")
23
+
24
+ local function defaultCompare(a, b)
25
+ return a < b
26
+ end
27
+
28
+ local ObservableSortedList = {}
29
+ ObservableSortedList.ClassName = "ObservableSortedList"
30
+ ObservableSortedList.__index = ObservableSortedList
31
+
32
+ --[=[
33
+ Constructs a new ObservableSortedList
34
+ @param compare callback?
35
+ @return ObservableSortedList<T>
36
+ ]=]
37
+ function ObservableSortedList.new(compare)
38
+ local self = setmetatable({}, ObservableSortedList)
39
+
40
+ self._maid = Maid.new()
41
+
42
+ self._keyList = {} -- { [number]: Symbol } -- immutable
43
+
44
+ self._sortValue = {} -- { [Symbol]: number }
45
+ self._contents = {} -- { [Symbol]: T }
46
+ self._indexes = {} -- { [Symbol]: number }
47
+
48
+ self._keyObservables = {} -- { [Symbol]: { Subscription } }
49
+
50
+ self._compare = compare or defaultCompare
51
+ self._countValue = Instance.new("IntValue")
52
+ self._countValue.Value = 0
53
+ self._maid:GiveTask(self._countValue)
54
+
55
+ --[=[
56
+ Fires when an item is added
57
+ @readonly
58
+ @prop ItemAdded Signal<T, number, Symbol>
59
+ @within ObservableSortedList
60
+ ]=]
61
+ self.ItemAdded = Signal.new()
62
+ self._maid:GiveTask(self.ItemAdded)
63
+
64
+ --[=[
65
+ Fires when an item is removed.
66
+ @readonly
67
+ @prop ItemRemoved Signal<T, Symbol>
68
+ @within ObservableSortedList
69
+ ]=]
70
+ self.ItemRemoved = Signal.new()
71
+ self._maid:GiveTask(self.ItemRemoved)
72
+
73
+ --[=[
74
+ Fires when the count changes.
75
+ @prop CountChanged RBXScriptSignal
76
+ @within ObservableSortedList
77
+ ]=]
78
+ self.CountChanged = self._countValue.Changed
79
+
80
+ return self
81
+ end
82
+
83
+ --[=[
84
+ Returns whether the value is an observable list
85
+ @param value any
86
+ @return boolean
87
+ ]=]
88
+ function ObservableSortedList.isObservableSortedList(value)
89
+ return type(value) == "table" and getmetatable(value) == ObservableSortedList
90
+ end
91
+
92
+ --[=[
93
+ Observes all items in the list
94
+ @return Observable<Brio<T>>
95
+ ]=]
96
+ function ObservableSortedList:ObserveItemsBrio()
97
+ return Observable.new(function(sub)
98
+ local maid = Maid.new()
99
+
100
+ local function handleItem(item, _index, includeKey)
101
+ local brio = Brio.new(item, includeKey)
102
+ maid[includeKey] = brio
103
+ sub:Fire(brio)
104
+ end
105
+
106
+ for index, key in pairs(self._keyList) do
107
+ handleItem(self._contents[key], index, key)
108
+ end
109
+
110
+ maid:GiveTask(self.ItemAdded:Connect(handleItem))
111
+ maid:GiveTask(self.ItemRemoved:Connect(function(_item, includeKey)
112
+ maid[includeKey] = nil
113
+ end))
114
+
115
+ self._maid[sub] = maid
116
+ maid:GiveTask(function()
117
+ self._maid[sub] = nil
118
+ sub:Complete()
119
+ end)
120
+
121
+ return maid
122
+ end)
123
+ end
124
+
125
+ --[=[
126
+ Observes the index as it changes, until the entry at the existing
127
+ index is removed.
128
+
129
+ @param indexToObserve number
130
+ @return Observable<number>
131
+ ]=]
132
+ function ObservableSortedList:ObserveIndex(indexToObserve)
133
+ assert(type(indexToObserve) == "number", "Bad indexToObserve")
134
+
135
+ local key = self._keyList[indexToObserve]
136
+ if not key then
137
+ error(("No entry at index %q, cannot observe changes"):format(indexToObserve))
138
+ end
139
+
140
+ return self:ObserveIndexByKey(key)
141
+ end
142
+
143
+ --[=[
144
+ Observes the index as it changes, until the entry at the existing
145
+ key is removed.
146
+
147
+ @param key Symbol
148
+ @return Observable<number>
149
+ ]=]
150
+ function ObservableSortedList:ObserveIndexByKey(key)
151
+ assert(type(key) == "userdata", "Bad key")
152
+
153
+ return Observable.new(function(sub)
154
+ local maid = Maid.new()
155
+ self._keyObservables[key] = self._keyObservables[key] or {}
156
+ table.insert(self._keyObservables[key], sub)
157
+
158
+ local currentIndex = self._indexes[key]
159
+ if currentIndex then
160
+ sub:Fire(currentIndex)
161
+ end
162
+
163
+ maid:GiveTask(function()
164
+ local list = self._keyObservables[key]
165
+ if not list then
166
+ return
167
+ end
168
+
169
+ local index = table.find(list, sub)
170
+ if index then
171
+ table.remove(list, index)
172
+ if #list == 0 then
173
+ self._keyObservables[key] = nil
174
+ end
175
+ end
176
+ end)
177
+
178
+ return maid
179
+ end)
180
+ end
181
+
182
+ --[=[
183
+ Gets the current index from the key
184
+
185
+ @param key Symbol
186
+ @return number
187
+ ]=]
188
+ function ObservableSortedList:GetIndexByKey(key)
189
+ local currentIndex = self._indexes[key]
190
+ if currentIndex then
191
+ return currentIndex
192
+ else
193
+ return nil
194
+ end
195
+ end
196
+
197
+ --[=[
198
+ Gets the count of items in the list
199
+ @return number
200
+ ]=]
201
+ function ObservableSortedList:GetCount()
202
+ return self._countValue.Value
203
+ end
204
+
205
+ --[=[
206
+ Gets a list of all entries.
207
+ @return { T }
208
+ ]=]
209
+ function ObservableSortedList:GetList()
210
+ local list = {}
211
+ for _, key in pairs(self._keyList) do
212
+ table.insert(list, self._contents[key])
213
+ end
214
+ return list
215
+ end
216
+
217
+ --[=[
218
+ Observes the count of the list
219
+ @return Observable<number>
220
+ ]=]
221
+ function ObservableSortedList:ObserveCount()
222
+ return RxValueBaseUtils.observeValue(self._countValue)
223
+ end
224
+
225
+ --[=[
226
+ Adds the item to the list at the specified index
227
+ @param item T
228
+ @param observeValue Observable<Comparable>
229
+ @return callback -- Call to remove
230
+ ]=]
231
+ function ObservableSortedList:Add(item, observeValue)
232
+ assert(item ~= nil, "Bad item")
233
+ assert(Observable.isObservable(observeValue), "Bad observeValue")
234
+
235
+ local key = Symbol.named("entryKey")
236
+ local maid = Maid.new()
237
+
238
+ self._contents[key] = item
239
+
240
+ maid:GiveTask(observeValue:Subscribe(function(sortValue)
241
+ self._sortValue[key] = sortValue
242
+
243
+ if sortValue ~= nil then
244
+ local currentIndex = self._indexes[key]
245
+ local targetIndex = self:_findCorrectIndex(sortValue, currentIndex)
246
+ self:_updateIndex(key, item, targetIndex)
247
+ else
248
+ local observableSubs = self._keyObservables[key]
249
+
250
+ -- calling this also may unsubscribe some observables.
251
+ self:_removeItemByKey(key, item)
252
+
253
+ if observableSubs then
254
+ -- fire nil index
255
+ self:_fireSubs(observableSubs, nil)
256
+ end
257
+ end
258
+ end))
259
+
260
+ maid:GiveTask(function()
261
+ local observableSubs = self._keyObservables[key]
262
+ self._keyObservables[key] = nil
263
+
264
+ self:_removeItemByKey(key, item)
265
+
266
+ -- Fire off the index change on the value
267
+ if observableSubs then
268
+ self:_completeSubs(observableSubs)
269
+ end
270
+
271
+ self._contents[key] = nil
272
+ self._sortValue[key] = nil
273
+ end)
274
+
275
+ self._maid[key] = maid
276
+
277
+ return function()
278
+ self._maid[key] = nil
279
+ end
280
+ end
281
+
282
+ --[=[
283
+ Gets the current item at the index, or nil if it is not defined.
284
+ @param index number
285
+ @return T?
286
+ ]=]
287
+ function ObservableSortedList:Get(index)
288
+ assert(type(index) == "number", "Bad index")
289
+
290
+ local key = self._keyList[index]
291
+ if not key then
292
+ return nil
293
+ end
294
+
295
+ return self._contents[key]
296
+ end
297
+
298
+ --[=[
299
+ Removes the item from the list if it exists.
300
+ @param key Symbol
301
+ @return T
302
+ ]=]
303
+ function ObservableSortedList:RemoveByKey(key)
304
+ assert(key ~= nil, "Bad key")
305
+
306
+ self._maid[key] = nil
307
+ end
308
+
309
+ function ObservableSortedList:_updateIndex(key, item, index)
310
+ assert(item ~= nil, "Bad item")
311
+ assert(type(index) == "number", "Bad index")
312
+
313
+ local pastIndex = self._indexes[key]
314
+ if pastIndex == index then
315
+ return
316
+ end
317
+
318
+ self._indexes[key] = index
319
+
320
+ local changed = {}
321
+
322
+ if not pastIndex then
323
+ -- shift everything up to fit this space
324
+ local n = #self._keyList
325
+ for i=n, index, -1 do
326
+ local nextKey = self._keyList[i]
327
+ self._indexes[nextKey] = i + 1
328
+ self._keyList[i + 1] = nextKey
329
+
330
+ table.insert(changed, {
331
+ key = nextKey;
332
+ newIndex = i + 1;
333
+ })
334
+ end
335
+ elseif index > pastIndex then
336
+ -- we're moving up (3 -> 5), so everything shifts down to fill up the pastIndex
337
+ for i=pastIndex + 1, index do
338
+ local nextKey = self._keyList[i]
339
+ self._indexes[nextKey] = i - 1
340
+ self._keyList[i - 1] = nextKey
341
+
342
+ table.insert(changed, {
343
+ key = nextKey;
344
+ newIndex = i - 1;
345
+ })
346
+ end
347
+ else
348
+ -- if index < pastIndex then
349
+ -- we're moving down (5 -> 3) so everything shifts up to fit this space
350
+ for i=pastIndex-1, index, -1 do
351
+ local belowKey = self._keyList[i]
352
+ self._indexes[belowKey] = i + 1
353
+ self._keyList[i + 1] = belowKey
354
+ table.insert(changed, {
355
+ key = belowKey;
356
+ newIndex = i + 1;
357
+ })
358
+ end
359
+ end
360
+
361
+ local itemAdded = {
362
+ key = key;
363
+ newIndex = index;
364
+ item = item;
365
+ }
366
+
367
+ -- ensure ourself is considered changed
368
+ table.insert(changed, itemAdded)
369
+
370
+
371
+ self._keyList[index] = key
372
+
373
+ -- Fire off our count value changed
374
+ -- still O(n^2) but at least we prevent emitting O(n^2) events
375
+ if pastIndex == nil then
376
+ self:_deferChange(1, itemAdded, nil, changed)
377
+ else
378
+ self:_deferChange(0, nil, nil, changed)
379
+ end
380
+ end
381
+
382
+ function ObservableSortedList:_removeItemByKey(key, item)
383
+ assert(key ~= nil, "Bad key")
384
+
385
+ local index = self._indexes[key]
386
+ if not index then
387
+ return
388
+ end
389
+
390
+ self._indexes[key] = nil
391
+ self._sortValue[key] = nil
392
+
393
+ local changed = {}
394
+
395
+ -- shift everything down
396
+ local n = #self._keyList
397
+ for i=index, n - 1 do
398
+ local nextKey = self._keyList[i+1]
399
+ self._indexes[nextKey] = i
400
+ self._keyList[i] = nextKey
401
+
402
+ table.insert(changed, {
403
+ key = nextKey;
404
+ newIndex = i;
405
+ })
406
+ end
407
+ self._keyList[n] = nil
408
+
409
+ local itemRemoved = {
410
+ key = key;
411
+ item = item;
412
+ }
413
+
414
+ -- TODO: Defer item removed as a changed event?
415
+
416
+ -- still O(n^2) but at least we prevent emitting O(n^2) events
417
+ self:_deferChange(-1, nil, itemRemoved, changed)
418
+ end
419
+
420
+ function ObservableSortedList:_deferChange(countChange, itemAdded, itemRemoved, indexChanges)
421
+ self:_queueDeferredChange()
422
+
423
+ if itemAdded then
424
+ self._deferredChange.itemsRemoved[itemAdded.key] = nil
425
+ self._deferredChange.itemsAdded[itemAdded.key] = itemAdded
426
+ end
427
+
428
+ if itemRemoved then
429
+ self._deferredChange.itemsAdded[itemRemoved.key] = nil
430
+ self._deferredChange.itemsRemoved[itemRemoved.key] = itemRemoved
431
+ end
432
+
433
+ self._deferredChange.countChange += countChange
434
+
435
+ for _, data in pairs(indexChanges) do
436
+ self._deferredChange.indexChanges[data.key] = data
437
+ end
438
+ end
439
+
440
+ function ObservableSortedList:_queueDeferredChange()
441
+ if not self._deferredChange then
442
+ self._deferredChange = {
443
+ countChange = 0;
444
+ indexChanges = {};
445
+ itemsAdded = {};
446
+ itemsRemoved = {};
447
+ }
448
+
449
+ task.defer(function()
450
+ local snapshot = self._deferredChange
451
+ self._deferredChange = nil
452
+
453
+ self._countValue.Value = self._countValue.Value + snapshot.countChange
454
+
455
+ if self.Destroy then
456
+ -- Fire off last adds
457
+ for _, lastAdded in pairs(snapshot.itemsAdded) do
458
+ self.ItemAdded:Fire(lastAdded.item, lastAdded.newIndex, lastAdded.key)
459
+ end
460
+
461
+ for _, lastRemoved in pairs(snapshot.itemsRemoved) do
462
+ self.ItemRemoved:Fire(lastRemoved.item, lastRemoved.key)
463
+ end
464
+ end
465
+
466
+ -- Fire off index change on each key list (if the data isn't stale)
467
+ for _, lastChange in pairs(snapshot.indexChanges) do
468
+ if self._indexes[lastChange.key] == lastChange.newIndex then
469
+ local subs = self._keyObservables[lastChange.key]
470
+ if subs then
471
+ self:_fireSubs(subs, lastChange.newIndex)
472
+ end
473
+ end
474
+ end
475
+ end)
476
+ end
477
+ end
478
+
479
+ function ObservableSortedList:_findCorrectIndex(sortValue, currentIndex)
480
+ -- todo: binary search
481
+ -- todo: stable
482
+
483
+ for i=#self._keyList, 1, -1 do
484
+ local currentKey = self._keyList[i]
485
+ if self._compare(self._sortValue[currentKey], sortValue) then
486
+
487
+ -- include index in this
488
+ if currentIndex and currentIndex <= i then
489
+ return i
490
+ end
491
+
492
+ return i + 1
493
+ end
494
+ end
495
+
496
+ return 1
497
+ end
498
+
499
+ function ObservableSortedList:_fireSubs(list, index)
500
+ for _, sub in pairs(list) do
501
+ if sub:IsPending() then
502
+ task.spawn(function()
503
+ sub:Fire(index)
504
+ end)
505
+ end
506
+ end
507
+ end
508
+
509
+ function ObservableSortedList:_completeSubs(list)
510
+ for _, sub in pairs(list) do
511
+ if sub:IsPending() then
512
+ sub:Fire(nil)
513
+ sub:Complete()
514
+ end
515
+ end
516
+ end
517
+
518
+ --[=[
519
+ Cleans up the ObservableSortedList and sets the metatable to nil.
520
+ ]=]
521
+ function ObservableSortedList:Destroy()
522
+ self._maid:DoCleaning()
523
+ setmetatable(self, nil)
524
+ end
525
+
526
+ return ObservableSortedList
@@ -0,0 +1,37 @@
1
+ --[[
2
+ @class ObservableSortedList.spec.lua
3
+ ]]
4
+
5
+ local require = require(game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent).load(script)
6
+
7
+ local ObservableSortedList = require("ObservableSortedList")
8
+ local Rx = require("Rx")
9
+
10
+ return function()
11
+ describe("ObservableSortedList.new()", function()
12
+ local observableSortedList = ObservableSortedList.new()
13
+
14
+ it("should return nil for unset values", function()
15
+ expect(observableSortedList:Get(1)).to.equal(nil)
16
+ end)
17
+
18
+ it("should allow inserting an value", function()
19
+ expect(observableSortedList:GetCount()).to.equal(0)
20
+
21
+ observableSortedList:Add("b", Rx.of("b"))
22
+
23
+ expect(observableSortedList:Get(1)).to.equal("b")
24
+ expect(observableSortedList:GetCount()).to.equal(1)
25
+ end)
26
+
27
+ it("should sort the items", function()
28
+ expect(observableSortedList:GetCount()).to.equal(1)
29
+
30
+ observableSortedList:Add("a", Rx.of("a"))
31
+
32
+ expect(observableSortedList:Get(1)).to.equal("a")
33
+ expect(observableSortedList:Get(2)).to.equal("b")
34
+ expect(observableSortedList:GetCount()).to.equal(2)
35
+ end)
36
+ end)
37
+ end
@@ -4,7 +4,16 @@
4
4
  "$className": "DataModel",
5
5
  "ServerScriptService": {
6
6
  "observablecollection": {
7
- "$path": ".."
7
+ "$className": "Folder",
8
+ "observablecollection": {
9
+ "$path": ".."
10
+ },
11
+ "instanceutils": {
12
+ "$path": "../../instanceutils"
13
+ }
14
+ },
15
+ "Script": {
16
+ "$path": "scripts/Server"
8
17
  }
9
18
  }
10
19
  }
@@ -0,0 +1,112 @@
1
+ --[[
2
+ @class ServerMain
3
+ ]]
4
+ local ServerScriptService = game:GetService("ServerScriptService")
5
+ local TweenService = game:GetService("TweenService")
6
+
7
+ local loader = ServerScriptService:FindFirstChild("LoaderUtils", true).Parent
8
+ local packages = require(loader).bootstrapGame(ServerScriptService.observablecollection)
9
+
10
+ local ObservableSortedList = require(packages.ObservableSortedList)
11
+ local RxInstanceUtils = require(packages.RxInstanceUtils)
12
+ local Rx = require(packages.Rx)
13
+
14
+ local observableSortedList = ObservableSortedList.new()
15
+
16
+ observableSortedList.CountChanged:Connect(function(count)
17
+ print("Count", count)
18
+ end)
19
+
20
+ observableSortedList:ObserveItemsBrio():Subscribe(function(brio)
21
+ if brio:IsDead() then
22
+ return
23
+ end
24
+
25
+ local part, key = brio:GetValue()
26
+ local maid = brio:ToMaid()
27
+
28
+ local currentTween
29
+ local function setCFrame(cframe, doNotAnimate)
30
+ if currentTween then
31
+ currentTween:Cancel()
32
+ currentTween = nil
33
+ end
34
+
35
+ if doNotAnimate then
36
+ part.CFrame = cframe
37
+ else
38
+ local tweenInfo = TweenInfo.new(0.2)
39
+ local tween = TweenService:Create(part, tweenInfo, {
40
+ CFrame = cframe;
41
+ })
42
+ currentTween = tween
43
+ tween:Play()
44
+ end
45
+ end
46
+
47
+ local first = true
48
+
49
+ maid:GiveTask(observableSortedList:ObserveIndexByKey(key):Subscribe(function(index)
50
+ print("change")
51
+
52
+ if index then
53
+ part:SetAttribute("CurrentIndex", index)
54
+ setCFrame(CFrame.new(-5*index, 5, 0) * CFrame.Angles(0, math.pi/2, 0), first)
55
+ first = false
56
+ else
57
+ part:SetAttribute("CurrentIndex", "nil")
58
+ setCFrame(CFrame.new(part.CFrame.x, 10, 0) * CFrame.Angles(0, math.pi/2, 0), first)
59
+ first = false
60
+ end
61
+ end))
62
+
63
+ maid:GiveTask(function()
64
+ part:SetAttribute("CurrentIndex", "nil")
65
+ setCFrame(CFrame.new(part.CFrame.x, 5, 5) * CFrame.Angles(0, math.pi/2, 0), first)
66
+ first = false
67
+ end)
68
+ end)
69
+
70
+ local parts = {}
71
+ for i=9, 1, -1 do
72
+ local part = Instance.new("Part")
73
+ part.TopSurface = Enum.SurfaceType.Smooth
74
+ part.BottomSurface = Enum.SurfaceType.Smooth
75
+ part.Anchored = true
76
+ part.Size = Vector3.new(3, 3, 3)
77
+ part.Name = i
78
+
79
+ local surfaceGui = Instance.new("SurfaceGui")
80
+ surfaceGui.Name = "SurfaceGui"
81
+ surfaceGui.Face = Enum.NormalId.Top
82
+ surfaceGui.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
83
+ surfaceGui.Adornee = part
84
+ surfaceGui.Parent = part
85
+
86
+ local textLabel = Instance.new("TextLabel")
87
+ textLabel.Name = "TextLabel"
88
+ textLabel.Size = UDim2.new(1, 0, 1, 0)
89
+ textLabel.TextScaled = true
90
+ textLabel.BackgroundTransparency = 1
91
+ textLabel.TextColor3 = Color3.new(0, 0, 0)
92
+ textLabel.BorderSizePixel = 0
93
+ textLabel.Parent = surfaceGui
94
+
95
+ RxInstanceUtils.observeProperty(part, "Name", nil):Subscribe(function(value)
96
+ textLabel.Text = tostring(value)
97
+ end)
98
+
99
+ parts[i] = part
100
+ part.Parent = workspace
101
+
102
+ observableSortedList:Add(part, RxInstanceUtils.observeProperty(part, "Name", nil):Pipe({
103
+ Rx.map(function(name)
104
+ return tonumber(name)
105
+ end)
106
+ }))
107
+ end
108
+
109
+ parts[5].Name = "25"
110
+ parts[9].Name = "3.1"
111
+ parts[2].Name = "remove"
112
+