@quenty/rx 5.2.0 → 6.0.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,17 @@
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
+ # [6.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/rx@5.2.0...@quenty/rx@6.0.0) (2022-08-14)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add Rx.share, Rx.shareReplay and Rx.cache ([6f1268f](https://github.com/Quenty/NevermoreEngine/commit/6f1268f8f70908f04dbebff7f2a267fc24eafc19))
12
+
13
+
14
+
15
+
16
+
6
17
  # [5.2.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/rx@5.1.0...@quenty/rx@5.2.0) (2022-07-31)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/rx",
3
- "version": "5.2.0",
3
+ "version": "6.0.0",
4
4
  "description": "Quenty's reactive library for Roblox",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -40,5 +40,5 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "e31b3a35aa475bb5699a24898a8639e107165b36"
43
+ "gitHead": "dbb62609f980983cc32da90acfef13e30ed41113"
44
44
  }
package/src/Shared/Rx.lua CHANGED
@@ -307,6 +307,205 @@ function Rx.start(callback)
307
307
  end
308
308
  end
309
309
 
310
+ --[=[
311
+ Returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one Subscriber this Observable will be subscribed and emitting data.
312
+ When all subscribers have unsubscribed it will unsubscribe from the source Observable.
313
+
314
+ https://rxjs.dev/api/operators/share
315
+
316
+ @return (source: Observable) -> Observable
317
+ ]=]
318
+ function Rx.share()
319
+ return function(source)
320
+ local shareMaid = Maid.new()
321
+ local subs = {}
322
+
323
+ local lastFail = UNSET_VALUE
324
+ local lastComplete = UNSET_VALUE
325
+
326
+ local function connectToSourceIfNeeded()
327
+ if not shareMaid._currentSub then
328
+ lastFail = UNSET_VALUE
329
+ lastComplete = UNSET_VALUE
330
+
331
+ shareMaid._currentSub = source:Subscribe(function(...)
332
+ for _, sub in pairs(subs) do
333
+ sub:Fire(...)
334
+ end
335
+ end, function(...)
336
+ lastFail = table.pack(...)
337
+ for _, sub in pairs(subs) do
338
+ sub:Fail(...)
339
+ end
340
+ end, function(...)
341
+ lastComplete = table.pack(...)
342
+ for _, sub in pairs(subs) do
343
+ sub:Complete(...)
344
+ end
345
+ end)
346
+ end
347
+ end
348
+
349
+ local function disconnectFromSource()
350
+ shareMaid._currentSub = nil
351
+
352
+ lastFail = UNSET_VALUE
353
+ lastComplete = UNSET_VALUE
354
+ end
355
+
356
+ assert(Observable.isObservable(source), "Bad observable")
357
+
358
+ return Observable.new(function(sub)
359
+ if lastFail ~= UNSET_VALUE then
360
+ sub:Fail(table.unpack(lastFail, 1, lastFail.n))
361
+ return
362
+ end
363
+
364
+ if lastComplete ~= UNSET_VALUE then
365
+ sub:Fail(table.unpack(lastComplete, 1, lastComplete.n))
366
+ return
367
+ end
368
+
369
+ table.insert(subs, sub)
370
+ connectToSourceIfNeeded()
371
+
372
+ return function()
373
+ local index = table.find(subs, sub)
374
+ if index then
375
+ table.remove(subs, index)
376
+
377
+ if #subs == 0 then
378
+ disconnectFromSource()
379
+ end
380
+ end
381
+ end
382
+ end)
383
+ end
384
+ end
385
+
386
+ --[=[
387
+ Same as [Rx.share] except it also replays the value
388
+
389
+ @param bufferSize number -- Number of entries to cache
390
+ @param windowTimeSeconds number -- Time
391
+ @return (source: Observable) -> Observable
392
+ ]=]
393
+ function Rx.shareReplay(bufferSize, windowTimeSeconds)
394
+ assert(type(bufferSize) == "number" or bufferSize == nil, "Bad bufferSize")
395
+ assert(type(windowTimeSeconds) == "number" or windowTimeSeconds == nil, "Bad windowTimeSeconds")
396
+
397
+ bufferSize = bufferSize or math.huge
398
+ windowTimeSeconds = windowTimeSeconds or math.huge
399
+
400
+ return function(source)
401
+ local shareMaid = Maid.new()
402
+ local subs = {}
403
+
404
+ local buffer = {}
405
+ local lastFail = UNSET_VALUE
406
+ local lastComplete = UNSET_VALUE
407
+
408
+ local function getEventsCopy()
409
+ local now = os.clock()
410
+ local events = {}
411
+
412
+ for _, event in pairs(buffer) do
413
+ if (now - event.timestamp) <= windowTimeSeconds then
414
+ table.insert(events, event)
415
+ end
416
+ end
417
+
418
+ return events
419
+ end
420
+
421
+ local function connectToSourceIfNeeded()
422
+ if not shareMaid._currentSub then
423
+ buffer = {}
424
+ lastFail = UNSET_VALUE
425
+ lastComplete = UNSET_VALUE
426
+
427
+ shareMaid._currentSub = source:Subscribe(function(...)
428
+ -- TODO: also prune events by timestamp
429
+
430
+ if #buffer + 1 > bufferSize then
431
+ table.remove(buffer, 1) -- O(n), not great.
432
+ end
433
+
434
+ -- Queue before we start
435
+ local event = table.pack(...)
436
+ event.timestamp = os.clock()
437
+ table.insert(buffer, event)
438
+
439
+ for _, sub in pairs(subs) do
440
+ sub:Fire(...)
441
+ end
442
+ end, function(...)
443
+ lastFail = table.pack(...)
444
+ for _, sub in pairs(subs) do
445
+ sub:Fail(...)
446
+ end
447
+ end, function(...)
448
+ lastComplete = table.pack(...)
449
+ for _, sub in pairs(subs) do
450
+ sub:Complete(...)
451
+ end
452
+ end)
453
+ end
454
+ end
455
+
456
+ local function disconnectFromSource()
457
+ shareMaid._currentSub = nil
458
+
459
+ buffer = {}
460
+ lastFail = UNSET_VALUE
461
+ lastComplete = UNSET_VALUE
462
+ end
463
+
464
+ assert(Observable.isObservable(source), "Bad observable")
465
+
466
+ return Observable.new(function(sub)
467
+ if lastFail ~= UNSET_VALUE then
468
+ sub:Fail(table.unpack(lastFail, 1, lastFail.n))
469
+ return
470
+ end
471
+
472
+ if lastComplete ~= UNSET_VALUE then
473
+ sub:Fail(table.unpack(lastComplete, 1, lastComplete.n))
474
+ return
475
+ end
476
+
477
+ table.insert(subs, sub)
478
+
479
+ -- Firing could lead to re-entrance. Lets just use the buffer as-is.
480
+ for _, item in pairs(getEventsCopy()) do
481
+ sub:Fire(table.unpack(item, 1, item.n))
482
+ end
483
+
484
+ connectToSourceIfNeeded()
485
+
486
+ return function()
487
+ local index = table.find(subs, sub)
488
+ if index then
489
+ table.remove(subs, index)
490
+
491
+ if #subs == 0 then
492
+ disconnectFromSource()
493
+ end
494
+ end
495
+ end
496
+ end)
497
+ end
498
+ end
499
+
500
+ --[=[
501
+ Caches the current value
502
+
503
+ @return (source: Observable) -> Observable
504
+ ]=]
505
+ function Rx.cache()
506
+ return Rx.shareReplay(1)
507
+ end
508
+
310
509
  --[=[
311
510
  Like start, but also from (list!)
312
511
 
@@ -1436,7 +1635,7 @@ function Rx.throttleDefer()
1436
1635
  lastResult = table.pack(...)
1437
1636
 
1438
1637
  -- Queue up our result
1439
- task.defer(function()
1638
+ maid._currentQueue = task.defer(function()
1440
1639
  local current = lastResult
1441
1640
  lastResult = nil
1442
1641