@buenos-nachos/time-sync 0.4.0 → 0.4.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 +6 -0
- package/package.json +1 -1
- package/src/TimeSync.ts +49 -42
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/TimeSync.ts
CHANGED
|
@@ -230,6 +230,23 @@ interface TimeSyncApi {
|
|
|
230
230
|
actually want a completely empty function body. */
|
|
231
231
|
function noOp(..._: readonly unknown[]): void {}
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* This function is just a convenience for us to sidestep some problems around
|
|
235
|
+
* TypeScript's LSP and Object.freeze. Because Object.freeze can accept any
|
|
236
|
+
* arbitrary type, it basically acts as a "type boundary" between the left and
|
|
237
|
+
* right sides of any snapshot assignments.
|
|
238
|
+
*
|
|
239
|
+
* That means that if you rename a property a a value that is passed to
|
|
240
|
+
* Object.freeze, the LSP can't auto-rename it, and you potentially get missing
|
|
241
|
+
* properties. This is a bit hokey, but because the function is defined strictly
|
|
242
|
+
* in terms of concrete snapshots, any value passed to this function won't have
|
|
243
|
+
* to worry about mismatches.
|
|
244
|
+
*/
|
|
245
|
+
function freezeSnapshot(snap: Snapshot): Snapshot {
|
|
246
|
+
Object.freeze(snap.config);
|
|
247
|
+
return Object.freeze(snap);
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
const defaultMinimumRefreshIntervalMs = 200;
|
|
234
251
|
|
|
235
252
|
/**
|
|
@@ -363,43 +380,17 @@ export class TimeSync implements TimeSyncApi {
|
|
|
363
380
|
this.#latestSnapshot = Object.freeze(initialSnapshot);
|
|
364
381
|
}
|
|
365
382
|
|
|
366
|
-
#
|
|
367
|
-
const { date, subscriberCount, config } = this.#latestSnapshot;
|
|
368
|
-
if (config.freezeUpdates) {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Avoiding both direct property assignment or spread syntax because
|
|
373
|
-
// Object.freeze causes weird TypeScript LSP issues around assignability
|
|
374
|
-
// where trying to rename a property. If you rename a property on a
|
|
375
|
-
// type, it WON'T rename the runtime properties. Object.freeze
|
|
376
|
-
// introduces an extra type boundary that break the linking
|
|
377
|
-
const updated: Snapshot = {
|
|
378
|
-
// Always reject any new configs because trying to remove them at
|
|
379
|
-
// the type level isn't worth it for an internal implementation
|
|
380
|
-
// detail
|
|
381
|
-
config,
|
|
382
|
-
date: update.date ?? date,
|
|
383
|
-
subscriberCount: update.subscriberCount ?? subscriberCount,
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
this.#latestSnapshot = Object.freeze(updated);
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
#notifyAllSubscriptions(): void {
|
|
383
|
+
#processSubscriptionUpdate(): void {
|
|
391
384
|
// It's more important that we copy the date object into a separate
|
|
392
385
|
// variable here than normal, because need make sure the `this` context
|
|
393
386
|
// can't magically change between updates and cause subscribers to
|
|
394
|
-
// receive different values
|
|
395
|
-
// invalidate method)
|
|
387
|
+
// receive different values
|
|
396
388
|
const { date, config } = this.#latestSnapshot;
|
|
397
389
|
|
|
398
|
-
// We still need to let the logic go through if the current fastest
|
|
399
|
-
// interval is Infinity, so that we can support letting any arbitrary
|
|
400
|
-
// consumer invalidate the date immediately
|
|
401
390
|
const subscriptionsPaused =
|
|
402
|
-
config.freezeUpdates ||
|
|
391
|
+
config.freezeUpdates ||
|
|
392
|
+
this.#subscriptions.size === 0 ||
|
|
393
|
+
this.#fastestRefreshInterval === Number.POSITIVE_INFINITY;
|
|
403
394
|
if (subscriptionsPaused) {
|
|
404
395
|
return;
|
|
405
396
|
}
|
|
@@ -465,18 +456,24 @@ export class TimeSync implements TimeSyncApi {
|
|
|
465
456
|
* is one of them.
|
|
466
457
|
*/
|
|
467
458
|
readonly #onTick = (): void => {
|
|
468
|
-
|
|
469
|
-
const { config } = this.#latestSnapshot;
|
|
459
|
+
const { config, date } = this.#latestSnapshot;
|
|
470
460
|
if (config.freezeUpdates) {
|
|
461
|
+
// Defensive step to make sure that an invalid tick wasn't started
|
|
471
462
|
clearInterval(this.#intervalId);
|
|
472
463
|
this.#intervalId = undefined;
|
|
473
464
|
return;
|
|
474
465
|
}
|
|
475
466
|
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
478
|
-
|
|
467
|
+
const newDate = new ReadonlyDate();
|
|
468
|
+
if (newDate.getTime() === date.getTime()) {
|
|
469
|
+
return;
|
|
479
470
|
}
|
|
471
|
+
|
|
472
|
+
this.#latestSnapshot = freezeSnapshot({
|
|
473
|
+
...this.#latestSnapshot,
|
|
474
|
+
date: newDate,
|
|
475
|
+
});
|
|
476
|
+
this.#processSubscriptionUpdate();
|
|
480
477
|
};
|
|
481
478
|
|
|
482
479
|
#onFastestIntervalChange(): void {
|
|
@@ -497,10 +494,15 @@ export class TimeSync implements TimeSyncApi {
|
|
|
497
494
|
clearInterval(this.#intervalId);
|
|
498
495
|
|
|
499
496
|
if (timeBeforeNextUpdate <= 0) {
|
|
500
|
-
const
|
|
501
|
-
if (
|
|
502
|
-
this.#
|
|
497
|
+
const newDate = new ReadonlyDate();
|
|
498
|
+
if (newDate.getTime() !== date.getTime()) {
|
|
499
|
+
this.#latestSnapshot = freezeSnapshot({
|
|
500
|
+
...this.#latestSnapshot,
|
|
501
|
+
date: newDate,
|
|
502
|
+
});
|
|
503
|
+
this.#processSubscriptionUpdate();
|
|
503
504
|
}
|
|
505
|
+
|
|
504
506
|
this.#intervalId = setInterval(this.#onTick, fastest);
|
|
505
507
|
return;
|
|
506
508
|
}
|
|
@@ -616,7 +618,8 @@ export class TimeSync implements TimeSyncApi {
|
|
|
616
618
|
subsOnSetup.set(onUpdate, filtered);
|
|
617
619
|
}
|
|
618
620
|
|
|
619
|
-
|
|
621
|
+
this.#latestSnapshot = freezeSnapshot({
|
|
622
|
+
...this.#latestSnapshot,
|
|
620
623
|
subscriberCount: Math.max(0, this.#latestSnapshot.subscriberCount - 1),
|
|
621
624
|
});
|
|
622
625
|
|
|
@@ -639,7 +642,8 @@ export class TimeSync implements TimeSyncApi {
|
|
|
639
642
|
(e1, e2) => e1.targetRefreshIntervalMs - e2.targetRefreshIntervalMs,
|
|
640
643
|
);
|
|
641
644
|
|
|
642
|
-
|
|
645
|
+
this.#latestSnapshot = freezeSnapshot({
|
|
646
|
+
...this.#latestSnapshot,
|
|
643
647
|
subscriberCount: this.#latestSnapshot.subscriberCount + 1,
|
|
644
648
|
});
|
|
645
649
|
|
|
@@ -664,6 +668,9 @@ export class TimeSync implements TimeSyncApi {
|
|
|
664
668
|
// We swap the map out so that the unsubscribe callbacks can detect
|
|
665
669
|
// whether their functionality is still relevant
|
|
666
670
|
this.#subscriptions = new Map();
|
|
667
|
-
|
|
671
|
+
this.#latestSnapshot = freezeSnapshot({
|
|
672
|
+
...this.#latestSnapshot,
|
|
673
|
+
subscriberCount: 0,
|
|
674
|
+
});
|
|
668
675
|
}
|
|
669
676
|
}
|