@quenty/blend 12.7.0 → 12.8.1

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,31 @@
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.8.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@12.8.0...@quenty/blend@12.8.1) (2024-10-04)
7
+
8
+ **Note:** Version bump only for package @quenty/blend
9
+
10
+
11
+
12
+
13
+
14
+ # [12.8.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@12.7.0...@quenty/blend@12.8.0) (2024-10-04)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * Avoid additional table construction in Blend.Computed for performance gain ([40ff0c0](https://github.com/Quenty/NevermoreEngine/commit/40ff0c05ed62fec4d6033bdabd477223bbc96a6a))
20
+ * Blend unparents children before destruction saving any instances that need to be reparented ([2728331](https://github.com/Quenty/NevermoreEngine/commit/272833178a1cbe833b35121c707ec8061c3891aa))
21
+
22
+
23
+ ### Performance Improvements
24
+
25
+ * Avoid connecting to Rx if not needed in SpringObject ([a5ca4b8](https://github.com/Quenty/NevermoreEngine/commit/a5ca4b8cd4e4855b58a61478c9ecf9ff4a8a2cfe))
26
+
27
+
28
+
29
+
30
+
6
31
  # [12.7.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/blend@12.6.0...@quenty/blend@12.7.0) (2024-09-25)
7
32
 
8
33
  **Note:** Version bump only for package @quenty/blend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/blend",
3
- "version": "12.7.0",
3
+ "version": "12.8.1",
4
4
  "description": "Declarative UI system.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -28,23 +28,23 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@quenty/acceltween": "^2.5.0",
31
- "@quenty/brio": "^14.7.0",
32
- "@quenty/ducktype": "^5.5.0",
33
- "@quenty/instanceutils": "^13.7.0",
34
- "@quenty/loader": "^10.5.0",
35
- "@quenty/maid": "^3.3.0",
36
- "@quenty/promise": "^10.5.0",
37
- "@quenty/rx": "^13.7.0",
38
- "@quenty/signal": "^7.6.0",
39
- "@quenty/spring": "^10.5.0",
40
- "@quenty/steputils": "^3.5.0",
31
+ "@quenty/brio": "^14.8.1",
32
+ "@quenty/ducktype": "^5.6.0",
33
+ "@quenty/instanceutils": "^13.8.1",
34
+ "@quenty/loader": "^10.6.0",
35
+ "@quenty/maid": "^3.4.0",
36
+ "@quenty/promise": "^10.6.0",
37
+ "@quenty/rx": "^13.8.0",
38
+ "@quenty/signal": "^7.7.0",
39
+ "@quenty/spring": "^10.6.0",
40
+ "@quenty/steputils": "^3.5.1",
41
41
  "@quenty/string": "^3.3.0",
42
- "@quenty/valuebaseutils": "^13.7.0",
43
- "@quenty/valueobject": "^13.7.0"
42
+ "@quenty/valuebaseutils": "^13.8.1",
43
+ "@quenty/valueobject": "^13.8.1"
44
44
  },
45
45
  "devDependencies": {
46
- "@quenty/contentproviderutils": "^12.7.0",
47
- "@quenty/playerthumbnailutils": "^10.5.0"
46
+ "@quenty/contentproviderutils": "^12.8.1",
47
+ "@quenty/playerthumbnailutils": "^10.6.0"
48
48
  },
49
- "gitHead": "9b17fe79cddd071f0f06a9d35184e76b44bd6fe6"
49
+ "gitHead": "539802fea720a92f81ad48d6d5579605d8844d0a"
50
50
  }
@@ -20,7 +20,6 @@ local Signal = require("Signal")
20
20
  local StepUtils = require("StepUtils")
21
21
  local ValueBaseUtils = require("ValueBaseUtils")
22
22
  local ValueObject = require("ValueObject")
23
- local ValueObjectUtils = require("ValueObjectUtils")
24
23
  local RxBrioUtils = require("RxBrioUtils")
25
24
  local SpringObject
26
25
 
@@ -49,23 +48,23 @@ local Blend = {}
49
48
  @param className string
50
49
  @return (props: { [string]: any; }) -> Observable<Instance>
51
50
  ]=]
52
- function Blend.New(className)
51
+ function Blend.New(className: string)
53
52
  assert(type(className) == "string", "Bad className")
54
53
 
55
- local defaults = BlendDefaultProps[className]
56
-
57
54
  return function(props)
58
55
  return Observable.new(function(sub)
59
56
  local instance = Instance.new(className)
60
57
 
61
- if defaults then
62
- for key, value in pairs(defaults) do
58
+ if BlendDefaultProps[className] then
59
+ for key, value in pairs(BlendDefaultProps[className]) do
63
60
  instance[key] = value
64
61
  end
65
62
  end
66
63
 
67
64
  local maid = Blend.mount(instance, props)
68
- maid:GiveTask(instance)
65
+ maid:GiveTask(function()
66
+ Blend._safeCleanupInstance(instance)
67
+ end)
69
68
 
70
69
  sub:Fire(instance)
71
70
 
@@ -159,30 +158,32 @@ end
159
158
  @return Observable<T>
160
159
  ]=]
161
160
  function Blend.Computed(...)
162
- local values = {...}
163
161
  local n = select("#", ...)
164
- local compute = values[n]
165
-
162
+ local compute = select(n, ...)
166
163
  assert(type(compute) == "function", "Bad compute")
167
164
 
168
- local args = {}
169
- for i=1, n - 1 do
170
- local observable = Blend.toPropertyObservable(values[i])
171
- if observable then
172
- args[i] = observable
173
- else
174
- args[i] = Rx.of(values[i])
175
- end
176
- end
177
-
178
- if #args == 0 then
165
+ if n == 1 then
179
166
  -- static value?
180
167
  return Observable.new(function(sub)
181
168
  sub:Fire(compute())
182
169
  end)
183
- elseif #args == 1 then
184
- return Rx.map(compute)(args[1])
170
+ elseif n == 2 then
171
+ local arg = ...
172
+ local observable = Blend.toPropertyObservable(arg) or Rx.of(arg)
173
+ return Rx.map(compute)(observable)
185
174
  else
175
+ local args = table.create(n - 1)
176
+
177
+ for i=1, n - 1 do
178
+ local found = select(i, ...)
179
+ local observable = Blend.toPropertyObservable(found)
180
+ if observable then
181
+ args[i] = observable
182
+ else
183
+ args[i] = found
184
+ end
185
+ end
186
+
186
187
  return Rx.combineLatest(args)
187
188
  :Pipe({
188
189
  Rx.map(function(result)
@@ -269,17 +270,16 @@ end
269
270
  function Blend.Attached(constructor)
270
271
  return function(parent)
271
272
  return Observable.new(function(sub)
272
- local maid = Maid.new()
273
-
274
273
  local resource = constructor(parent)
275
274
 
275
+ local cleanup = nil
276
276
  if MaidTaskUtils.isValidTask(resource) then
277
- maid:GiveTask(resource)
277
+ cleanup = resource
278
278
  end
279
279
 
280
280
  sub:Fire(resource)
281
281
 
282
- return maid
282
+ return cleanup
283
283
  end)
284
284
  end;
285
285
  end
@@ -414,14 +414,12 @@ function Blend.Spring(source, speed, damper)
414
414
  end
415
415
 
416
416
  return Observable.new(function(sub)
417
- local maid = Maid.new()
418
-
419
- local spring = maid:Add(SpringObject.new(source, speed, damper))
417
+ local spring = SpringObject.new(source, speed, damper)
420
418
  spring.Epsilon = 1e-3
421
419
 
422
- maid:GiveTask(spring:Observe():Subscribe(sub:GetFireFailComplete()))
420
+ spring._maid:GiveTask(spring:Observe():Subscribe(sub:GetFireFailComplete()))
423
421
 
424
- return maid
422
+ return spring
425
423
  end)
426
424
  end
427
425
 
@@ -432,24 +430,26 @@ end
432
430
  @return Observable?
433
431
  ]=]
434
432
  function Blend.toPropertyObservable(value)
435
- if Observable.isObservable(value) then
436
- return value
437
- elseif typeof(value) == "Instance" then
438
- -- IntValue, ObjectValue, et cetera
439
- if ValueBaseUtils.isValueBase(value) then
440
- return RxValueBaseUtils.observeValue(value)
441
- end
442
- elseif type(value) == "table" then
443
- if ValueObject.isValueObject(value) then
444
- return ValueObjectUtils.observeValue(value)
433
+ if type(value) == "table" then
434
+ if Observable.isObservable(value) then
435
+ return value
445
436
  elseif Promise.isPromise(value) then
446
437
  return Rx.fromPromise(value)
447
438
  elseif value.Observe then
448
439
  return value:Observe()
440
+ else
441
+ return nil
449
442
  end
443
+ elseif typeof(value) == "Instance" then
444
+ -- IntValue, ObjectValue, et cetera
445
+ if ValueBaseUtils.isValueBase(value) then
446
+ return RxValueBaseUtils.observeValue(value)
447
+ else
448
+ return nil
449
+ end
450
+ else
451
+ return nil
450
452
  end
451
-
452
- return nil
453
453
  end
454
454
 
455
455
  --[=[
@@ -578,11 +578,11 @@ function Blend.Children(parent, value)
578
578
  local observe = Blend._observeChildren(value, parent)
579
579
 
580
580
  if observe then
581
- return observe:Pipe({
582
- Rx.tap(function(child)
583
- child.Parent = parent;
584
- end);
585
- })
581
+ return Observable.new(function(_sub)
582
+ return observe:Subscribe(function(child)
583
+ child.Parent = parent
584
+ end)
585
+ end)
586
586
  else
587
587
  return Rx.EMPTY
588
588
  end
@@ -682,35 +682,42 @@ function Blend.Find(className)
682
682
  -- Return observable and assume we're being used in anexternal context
683
683
  -- TODO: Maybe not this
684
684
  if props.Parent then
685
- local propertyObservable = Blend.toPropertyObservable(props.Parent) or Rx.of(props.Parent)
685
+ local propertyObservable = Blend.toPropertyObservable(props.Parent)
686
686
 
687
- return propertyObservable:Pipe({
688
- RxBrioUtils.toBrio();
689
- RxBrioUtils.where(function(parent)
690
- return parent ~= nil
691
- end);
692
- RxBrioUtils.switchMapBrio(function(parent)
693
- assert(typeof(parent) == "Instance", "Bad parent retrieved during find spec")
687
+ local function handleChildBrio(brio)
688
+ if brio:IsDead() then
689
+ return
690
+ end
694
691
 
695
- return RxInstanceUtils.observeChildrenOfNameBrio(parent, className, props.Name)
696
- end);
697
- Rx.flatMap(function(brio)
698
- if brio:IsDead() then
699
- return
700
- end
692
+ local maid, instance = brio:ToMaidAndValue()
701
693
 
702
- local maid = brio:ToMaid()
703
- local instance = brio:GetValue()
704
- maid:GiveTask(Blend.mount(instance, props))
694
+ maid:GiveTask(Blend.mount(instance, props))
705
695
 
706
- if brio:IsDead() then
707
- maid:DoCleaning()
708
- end
696
+ if brio:IsDead() then
697
+ maid:DoCleaning()
698
+ end
709
699
 
710
- -- Emit back found value (we're used in property scenario)
711
- return Rx.of(instance)
712
- end);
713
- })
700
+ -- Emit back found value (we're used in property scenario)
701
+ return Rx.of(instance)
702
+ end
703
+
704
+ if propertyObservable then
705
+ return propertyObservable:Pipe({
706
+ RxBrioUtils.switchToBrio(function(parent)
707
+ return parent ~= nil
708
+ end);
709
+ RxBrioUtils.switchMapBrio(function(parent)
710
+ assert(typeof(parent) == "Instance", "Bad parent retrieved during find spec")
711
+
712
+ return RxInstanceUtils.observeChildrenOfNameBrio(parent, className, props.Name)
713
+ end);
714
+ Rx.flatMap(handleChildBrio);
715
+ })
716
+ else
717
+ return RxInstanceUtils.observeChildrenOfNameBrio(props.Parent, className, props.Name):Pipe({
718
+ Rx.flatMap(handleChildBrio);
719
+ })
720
+ end
714
721
  end
715
722
 
716
723
  -- Return callback
@@ -730,8 +737,8 @@ function Blend._mountToFinding(props)
730
737
  return
731
738
  end
732
739
 
733
- local maid = brio:ToMaid()
734
- local instance = brio:GetValue()
740
+ local maid, instance = brio:ToMaidAndValue()
741
+
735
742
  maid:GiveTask(Blend.mount(instance, props))
736
743
 
737
744
  -- Dead after mounting? Clean up...
@@ -858,6 +865,14 @@ function Blend.Single(observable)
858
865
  end)
859
866
  end
860
867
 
868
+ function Blend._safeCleanupInstance(result)
869
+ -- Unparent all children incase we want to resurrect them
870
+ for _, child in pairs(result:GetChildren()) do
871
+ child.Parent = nil
872
+ end
873
+ result:Destroy()
874
+ end
875
+
861
876
  --[=[
862
877
  Observes children and ensures that the value is cleaned up
863
878
  afterwards.
@@ -943,7 +958,9 @@ function Blend._observeChildren(value, parent)
943
958
  local result = value:GetValue()
944
959
  if typeof(result) == "Instance" then
945
960
  local maid = value:ToMaid()
946
- maid:GiveTask(result)
961
+ maid:GiveTask(function()
962
+ Blend._safeCleanupInstance(result)
963
+ end)
947
964
  sub:Fire(result)
948
965
 
949
966
  return maid
@@ -990,7 +1007,9 @@ function Blend._observeChildren(value, parent)
990
1007
  maid:GiveTask(value:Subscribe(function(result)
991
1008
  if typeof(result) == "Instance" then
992
1009
  -- lifetime of subscription
993
- maid:GiveTask(result)
1010
+ maid:GiveTask(function()
1011
+ Blend._safeCleanupInstance(result)
1012
+ end)
994
1013
  sub:Fire(result)
995
1014
  return
996
1015
  end
@@ -1089,6 +1108,7 @@ function Blend.mount(instance, props)
1089
1108
 
1090
1109
  local parent = nil
1091
1110
  local dependentObservables = {}
1111
+ local children = {}
1092
1112
 
1093
1113
  for key, value in pairs(props) do
1094
1114
  if type(key) == "string" then
@@ -1098,14 +1118,16 @@ function Blend.mount(instance, props)
1098
1118
  local observable = Blend.toPropertyObservable(value)
1099
1119
  if observable then
1100
1120
  maid:GiveTask(observable:Subscribe(function(result)
1101
- task.spawn(function()
1102
- instance[key] = result
1103
- end)
1121
+ instance[key] = result
1122
+ -- task.spawn(function()
1123
+ -- instance[key] = result
1124
+ -- end)
1104
1125
  end))
1105
1126
  else
1106
- task.spawn(function()
1107
- instance[key] = value
1108
- end)
1127
+ -- task.spawn(function()
1128
+ -- instance[key] = value
1129
+ -- end)
1130
+ instance[key] = value
1109
1131
  end
1110
1132
  end
1111
1133
  elseif type(key) == "function" then
@@ -1119,12 +1141,17 @@ function Blend.mount(instance, props)
1119
1141
  elseif type(key) == "number" then
1120
1142
  -- Treat this as an implicit children contract
1121
1143
  -- Thus, we don't need an explicit [Blend.Children] call.
1122
- table.insert(dependentObservables, { Blend.Children(instance, value), value })
1144
+ table.insert(children, value)
1123
1145
  else
1124
1146
  warn(string.format("Unable to apply property %q", tostring(key)))
1125
1147
  end
1126
1148
  end
1127
1149
 
1150
+
1151
+ if #children > 0 then
1152
+ maid:GiveTask(Blend.Children(instance, children):Subscribe())
1153
+ end
1154
+
1128
1155
  -- Subscribe dependentObservables (which includes adding children)
1129
1156
  for _, event in pairs(dependentObservables) do
1130
1157
  maid:GiveTask(event[1]:Subscribe(Blend.toEventHandler(event[2])))
@@ -13,7 +13,6 @@ local DuckTypeUtils = require("DuckTypeUtils")
13
13
  local Maid = require("Maid")
14
14
  local Observable = require("Observable")
15
15
  local Promise = require("Promise")
16
- local Rx = require("Rx")
17
16
  local Signal = require("Signal")
18
17
  local Spring = require("Spring")
19
18
  local SpringUtils = require("SpringUtils")
@@ -265,30 +264,36 @@ end
265
264
  function SpringObject:SetTarget(target, doNotAnimate)
266
265
  assert(target ~= nil, "Bad target")
267
266
 
268
- local observable = Blend.toPropertyObservable(target) or Rx.of(target)
267
+ local observable = Blend.toPropertyObservable(target)
268
+ if not observable then
269
+ self._maid._targetSub = nil
270
+ self:_applyTarget(target, doNotAnimate)
271
+ return
272
+ end
269
273
 
270
274
  if doNotAnimate then
271
275
  local isFirst = true
272
-
273
276
  self._maid._targetSub = observable:Subscribe(function(unconverted)
274
277
  local converted = SpringUtils.toLinearIfNeeded(unconverted)
275
278
  assert(converted, "Not a valid converted target")
276
279
 
277
- local spring = self:_getSpringForType(converted)
278
- spring:SetTarget(converted, isFirst)
280
+ local wasFirst = isFirst
279
281
  isFirst = false
280
-
281
- self.Changed:Fire()
282
+ self:_applyTarget(unconverted, wasFirst)
282
283
  end)
283
284
  else
284
285
  self._maid._targetSub = observable:Subscribe(function(unconverted)
285
- local converted = SpringUtils.toLinearIfNeeded(unconverted)
286
- self:_getSpringForType(converted).Target = converted
287
- self.Changed:Fire()
286
+ self:_applyTarget(unconverted, doNotAnimate)
288
287
  end)
289
288
  end
290
289
  end
291
290
 
291
+ function SpringObject:_applyTarget(unconverted, doNotAnimate)
292
+ local converted = SpringUtils.toLinearIfNeeded(unconverted)
293
+ self:_getSpringForType(converted):SetTarget(converted, doNotAnimate)
294
+ self.Changed:Fire()
295
+ end
296
+
292
297
  --[=[
293
298
  Sets the velocity for the spring
294
299
 
@@ -297,14 +302,22 @@ end
297
302
  function SpringObject:SetVelocity(velocity)
298
303
  assert(velocity ~= nil, "Bad velocity")
299
304
 
300
- local observable = Blend.toPropertyObservable(velocity) or Rx.of(velocity)
305
+ local observable = Blend.toPropertyObservable(velocity)
306
+ if not observable then
307
+ self._maid._velocitySub = nil
308
+ self:_applyVelocity(velocity)
309
+ else
310
+ self._maid._velocitySub = observable:Subscribe(function(unconverted)
311
+ self:_applyVelocity(unconverted)
312
+ end)
313
+ end
314
+ end
301
315
 
302
- self._maid._velocitySub = observable:Subscribe(function(unconverted)
303
- local converted = SpringUtils.toLinearIfNeeded(unconverted)
316
+ function SpringObject:_applyVelocity(unconverted)
317
+ local converted = SpringUtils.toLinearIfNeeded(unconverted)
304
318
 
305
- self:_getSpringForType(0*converted).Velocity = converted
306
- self.Changed:Fire()
307
- end)
319
+ self:_getSpringForType(0*converted).Velocity = converted
320
+ self.Changed:Fire()
308
321
  end
309
322
 
310
323
  --[=[
@@ -315,13 +328,21 @@ end
315
328
  function SpringObject:SetPosition(position)
316
329
  assert(position ~= nil, "Bad position")
317
330
 
318
- local observable = Blend.toPropertyObservable(position) or Rx.of(position)
331
+ local observable = Blend.toPropertyObservable(position)
332
+ if not observable then
333
+ self._maid._positionSub = nil
334
+ self:_applyPosition(position)
335
+ else
336
+ self._maid._positionSub = observable:Subscribe(function(unconverted)
337
+ self:_applyPosition(unconverted)
338
+ end)
339
+ end
340
+ end
319
341
 
320
- self._maid._positionSub = observable:Subscribe(function(unconverted)
321
- local converted = SpringUtils.toLinearIfNeeded(unconverted)
322
- self:_getSpringForType(converted).Value = converted
323
- self.Changed:Fire()
324
- end)
342
+ function SpringObject:_applyPosition(unconverted)
343
+ local converted = SpringUtils.toLinearIfNeeded(unconverted)
344
+ self:_getSpringForType(converted).Value = converted
345
+ self.Changed:Fire()
325
346
  end
326
347
 
327
348
  --[=[
@@ -332,20 +353,29 @@ end
332
353
  function SpringObject:SetDamper(damper)
333
354
  assert(damper ~= nil, "Bad damper")
334
355
 
335
- local observable = assert(Blend.toNumberObservable(damper), "Invalid damper")
356
+ if type(damper) == "number" then
357
+ self._maid._damperSub = nil
358
+ self:_applyDamper(damper)
359
+ else
360
+ local observable = assert(Blend.toPropertyObservable(damper), "Invalid damper")
336
361
 
337
- self._maid._damperSub = observable:Subscribe(function(unconverted)
338
- assert(type(unconverted) == "number", "Bad damper")
362
+ self._maid._damperSub = observable:Subscribe(function(unconverted)
363
+ self:_applyDamper(unconverted)
364
+ end)
365
+ end
366
+ end
339
367
 
340
- local currentSpring = rawget(self, "_currentSpring")
341
- if currentSpring then
342
- currentSpring.Damper = unconverted
343
- else
344
- self:_getInitInfo().Damper = unconverted
345
- end
368
+ function SpringObject:_applyDamper(unconverted)
369
+ assert(type(unconverted) == "number", "Bad damper")
346
370
 
347
- self.Changed:Fire()
348
- end)
371
+ local currentSpring = rawget(self, "_currentSpring")
372
+ if currentSpring then
373
+ currentSpring.Damper = unconverted
374
+ else
375
+ self:_getInitInfo().Damper = unconverted
376
+ end
377
+
378
+ self.Changed:Fire()
349
379
  end
350
380
 
351
381
  --[=[
@@ -356,20 +386,29 @@ end
356
386
  function SpringObject:SetSpeed(speed)
357
387
  assert(speed ~= nil, "Bad speed")
358
388
 
359
- local observable = assert(Blend.toNumberObservable(speed), "Invalid speed")
389
+ if type(speed) == "number" then
390
+ self._maid._speedSub = nil
391
+ self:_applySpeed(speed)
392
+ else
393
+ local observable = assert(Blend.toPropertyObservable(speed), "Invalid speed")
360
394
 
361
- self._maid._speedSub = observable:Subscribe(function(unconverted)
362
- assert(type(unconverted) == "number", "Bad damper")
395
+ self._maid._speedSub = observable:Subscribe(function(unconverted)
396
+ self:_applySpeed(unconverted)
397
+ end)
398
+ end
399
+ end
363
400
 
364
- local currentSpring = rawget(self, "_currentSpring")
365
- if currentSpring then
366
- currentSpring.Speed = unconverted
367
- else
368
- self:_getInitInfo().Speed = unconverted
369
- end
401
+ function SpringObject:_applySpeed(unconverted)
402
+ assert(type(unconverted) == "number", "Bad damper")
370
403
 
371
- self.Changed:Fire()
372
- end)
404
+ local currentSpring = rawget(self, "_currentSpring")
405
+ if currentSpring then
406
+ currentSpring.Speed = unconverted
407
+ else
408
+ self:_getInitInfo().Speed = unconverted
409
+ end
410
+
411
+ self.Changed:Fire()
373
412
  end
374
413
 
375
414
  --[=[