@spearwolf/shadow-objects 0.32.0 → 0.34.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 +333 -10
- package/README.md +100 -20
- package/bundle.js +5 -54
- package/package.json +7 -3
- package/src/ChangeTrailRefusedError.d.ts +27 -0
- package/src/ChangeTrailRefusedError.d.ts.map +1 -0
- package/src/ChangeTrailRefusedError.js +12 -0
- package/src/ChangeTrailRefusedError.js.map +7 -0
- package/src/EntityUuidInUseError.d.ts +19 -0
- package/src/EntityUuidInUseError.d.ts.map +1 -0
- package/src/EntityUuidInUseError.js +11 -0
- package/src/EntityUuidInUseError.js.map +7 -0
- package/src/WorkerTimeoutError.d.ts +25 -0
- package/src/WorkerTimeoutError.d.ts.map +1 -0
- package/src/WorkerTimeoutError.js +12 -0
- package/src/WorkerTimeoutError.js.map +7 -0
- package/src/bundle.d.ts +1 -1
- package/src/bundle.d.ts.map +1 -1
- package/src/bundle.js +1 -1
- package/src/bundle.js.map +2 -2
- package/src/constants.d.ts +9 -2
- package/src/constants.d.ts.map +1 -1
- package/src/constants.js +1 -1
- package/src/constants.js.map +2 -2
- package/src/create-worker.bundle.d.ts +1 -1
- package/src/create-worker.bundle.d.ts.map +1 -1
- package/src/create-worker.bundle.js.map +1 -1
- package/src/create-worker.d.ts +1 -1
- package/src/create-worker.d.ts.map +1 -1
- package/src/create-worker.js.map +1 -1
- package/src/elements/ShaeElement.d.ts +70 -2
- package/src/elements/ShaeElement.d.ts.map +1 -1
- package/src/elements/ShaeElement.js +129 -25
- package/src/elements/ShaeElement.js.map +3 -3
- package/src/elements/ShaeEntElement.d.ts +43 -5
- package/src/elements/ShaeEntElement.d.ts.map +1 -1
- package/src/elements/ShaeEntElement.js +399 -170
- package/src/elements/ShaeEntElement.js.map +2 -2
- package/src/elements/ShaeLifecycleElement.d.ts +71 -0
- package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
- package/src/elements/ShaeLifecycleElement.js +83 -0
- package/src/elements/ShaeLifecycleElement.js.map +7 -0
- package/src/elements/ShaePropElement.d.ts +57 -7
- package/src/elements/ShaePropElement.d.ts.map +1 -1
- package/src/elements/ShaePropElement.js +241 -220
- package/src/elements/ShaePropElement.js.map +2 -2
- package/src/elements/ShaeWorkerElement.d.ts +49 -5
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +230 -74
- package/src/elements/ShaeWorkerElement.js.map +2 -2
- package/src/elements/constants.d.ts +6 -0
- package/src/elements/constants.d.ts.map +1 -1
- package/src/elements/constants.js +12 -0
- package/src/elements/constants.js.map +2 -2
- package/src/elements/deferredTeardown.d.ts +24 -0
- package/src/elements/deferredTeardown.d.ts.map +1 -0
- package/src/elements/deferredTeardown.js +30 -0
- package/src/elements/deferredTeardown.js.map +7 -0
- package/src/elements/displayContentsRule.d.ts +25 -0
- package/src/elements/displayContentsRule.d.ts.map +1 -0
- package/src/elements/displayContentsRule.js +37 -0
- package/src/elements/displayContentsRule.js.map +7 -0
- package/src/elements/events.d.ts +10 -7
- package/src/elements/events.d.ts.map +1 -1
- package/src/elements/forwardCustomEvents.d.ts +19 -0
- package/src/elements/forwardCustomEvents.d.ts.map +1 -0
- package/src/elements/forwardCustomEvents.js +47 -0
- package/src/elements/forwardCustomEvents.js.map +7 -0
- package/src/elements/hostedSlots.d.ts +36 -0
- package/src/elements/hostedSlots.d.ts.map +1 -0
- package/src/elements/hostedSlots.js +118 -0
- package/src/elements/hostedSlots.js.map +7 -0
- package/src/elements/parentRemoval.d.ts +18 -0
- package/src/elements/parentRemoval.d.ts.map +1 -0
- package/src/elements/parentRemoval.js +60 -0
- package/src/elements/parentRemoval.js.map +7 -0
- package/src/elements/propValueConverters.d.ts +3 -0
- package/src/elements/propValueConverters.d.ts.map +1 -0
- package/src/elements/propValueConverters.js +54 -0
- package/src/elements/propValueConverters.js.map +7 -0
- package/src/elements/requestEntAncestor.d.ts +32 -0
- package/src/elements/requestEntAncestor.d.ts.map +1 -0
- package/src/elements/requestEntAncestor.js +16 -0
- package/src/elements/requestEntAncestor.js.map +7 -0
- package/src/elements.js.map +1 -1
- package/src/in-the-dark/Entity.d.ts +113 -3
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +292 -63
- package/src/in-the-dark/Entity.js.map +3 -3
- package/src/in-the-dark/Kernel.d.ts +81 -17
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +449 -392
- package/src/in-the-dark/Kernel.js.map +3 -3
- package/src/in-the-dark/Registry.d.ts +1 -1
- package/src/in-the-dark/Registry.d.ts.map +1 -1
- package/src/in-the-dark/Registry.js +99 -26
- package/src/in-the-dark/Registry.js.map +2 -2
- package/src/in-the-dark/ShadowObject.d.ts +1 -1
- package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
- package/src/in-the-dark/ShadowObject.js +3 -2
- package/src/in-the-dark/ShadowObject.js.map +2 -2
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts +126 -0
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js +596 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
- package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
- package/src/in-the-dark/SignalsPath.js +2 -2
- package/src/in-the-dark/SignalsPath.js.map +2 -2
- package/src/in-the-dark/events.js.map +1 -1
- package/src/in-the-dark/importModule.d.ts +7 -0
- package/src/in-the-dark/importModule.d.ts.map +1 -1
- package/src/in-the-dark/importModule.js +8 -5
- package/src/in-the-dark/importModule.js.map +2 -2
- package/src/index.d.ts +7 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +5 -1
- package/src/index.js.map +2 -2
- package/src/shadow-objects.d.ts +2 -0
- package/src/shadow-objects.d.ts.map +1 -1
- package/src/shadow-objects.js +2 -0
- package/src/shadow-objects.js.map +2 -2
- package/src/shadow-objects.worker.js +0 -1
- package/src/shadow-objects.worker.js.map +2 -2
- package/src/shae-ent.js.map +1 -1
- package/src/shae-prop.js +2 -2
- package/src/shae-prop.js.map +2 -2
- package/src/shae-worker.js.map +1 -1
- package/src/types.d.ts +39 -8
- package/src/types.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.d.ts +45 -10
- package/src/utils/ConsoleLogger.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.js +54 -28
- package/src/utils/ConsoleLogger.js.map +2 -2
- package/src/utils/FrameLoop.d.ts +42 -3
- package/src/utils/FrameLoop.d.ts.map +1 -1
- package/src/utils/FrameLoop.js +71 -12
- package/src/utils/FrameLoop.js.map +2 -2
- package/src/utils/MicrotaskCollector.d.ts +45 -0
- package/src/utils/MicrotaskCollector.d.ts.map +1 -0
- package/src/utils/MicrotaskCollector.js +33 -0
- package/src/utils/MicrotaskCollector.js.map +7 -0
- package/src/utils/MicrotaskGate.d.ts +17 -0
- package/src/utils/MicrotaskGate.d.ts.map +1 -0
- package/src/utils/MicrotaskGate.js +20 -0
- package/src/utils/MicrotaskGate.js.map +7 -0
- package/src/utils/array-utils.js.map +1 -1
- package/src/utils/attr-utils.d.ts +6 -0
- package/src/utils/attr-utils.d.ts.map +1 -1
- package/src/utils/attr-utils.js +4 -2
- package/src/utils/attr-utils.js.map +2 -2
- package/src/utils/constants.js.map +1 -1
- package/src/utils/generateUUID.d.ts +12 -1
- package/src/utils/generateUUID.d.ts.map +1 -1
- package/src/utils/generateUUID.js +31 -266
- package/src/utils/generateUUID.js.map +2 -2
- package/src/utils/props-utils.d.ts +3 -2
- package/src/utils/props-utils.d.ts.map +1 -1
- package/src/utils/props-utils.js +13 -24
- package/src/utils/props-utils.js.map +2 -2
- package/src/utils/runGuarded.d.ts +27 -0
- package/src/utils/runGuarded.d.ts.map +1 -0
- package/src/utils/runGuarded.js +11 -0
- package/src/utils/runGuarded.js.map +7 -0
- package/src/utils/toMaybe.d.ts.map +1 -1
- package/src/utils/toMaybe.js.map +1 -1
- package/src/utils/toNamespace.d.ts.map +1 -1
- package/src/utils/toNamespace.js.map +1 -1
- package/src/utils/toUrlString.d.ts +12 -0
- package/src/utils/toUrlString.d.ts.map +1 -1
- package/src/utils/toUrlString.js.map +2 -2
- package/src/utils/waitForMessageOfType.d.ts +16 -1
- package/src/utils/waitForMessageOfType.d.ts.map +1 -1
- package/src/utils/waitForMessageOfType.js +28 -3
- package/src/utils/waitForMessageOfType.js.map +2 -2
- package/src/view/ComponentChanges.d.ts +91 -1
- package/src/view/ComponentChanges.d.ts.map +1 -1
- package/src/view/ComponentChanges.js +253 -69
- package/src/view/ComponentChanges.js.map +2 -2
- package/src/view/ComponentContext.d.ts +239 -7
- package/src/view/ComponentContext.d.ts.map +1 -1
- package/src/view/ComponentContext.js +648 -115
- package/src/view/ComponentContext.js.map +2 -2
- package/src/view/ComponentMemory.d.ts +3 -3
- package/src/view/ComponentMemory.d.ts.map +1 -1
- package/src/view/ComponentMemory.js +3 -1
- package/src/view/ComponentMemory.js.map +2 -2
- package/src/view/IShadowObjectEnvProxy.d.ts +33 -1
- package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts +2 -2
- package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.js +18 -11
- package/src/view/LocalShadowObjectEnv.js.map +2 -2
- package/src/view/RemoteWorkerEnv.d.ts +129 -3
- package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
- package/src/view/RemoteWorkerEnv.js +290 -60
- package/src/view/RemoteWorkerEnv.js.map +2 -2
- package/src/view/ShadowEnv.d.ts +73 -2
- package/src/view/ShadowEnv.d.ts.map +1 -1
- package/src/view/ShadowEnv.js +301 -64
- package/src/view/ShadowEnv.js.map +2 -2
- package/src/view/ViewComponent.d.ts +93 -8
- package/src/view/ViewComponent.d.ts.map +1 -1
- package/src/view/ViewComponent.js +148 -28
- package/src/view/ViewComponent.js.map +2 -2
- package/src/view/cloneChangeTrail.js.map +2 -2
- package/src/worker/MessageRouter.d.ts +14 -0
- package/src/worker/MessageRouter.d.ts.map +1 -1
- package/src/worker/MessageRouter.js +80 -18
- package/src/worker/MessageRouter.js.map +2 -2
- package/src/worker/WorkerRuntime.d.ts +22 -1
- package/src/worker/WorkerRuntime.d.ts.map +1 -1
- package/src/worker/WorkerRuntime.js +60 -8
- package/src/worker/WorkerRuntime.js.map +2 -2
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
export declare const CONSOLE_LOGGER = "ConsoleLogger";
|
|
2
2
|
export declare const CONSOLE_LOGGER_STORAGE = "ConsoleLoggerStorage";
|
|
3
|
+
declare const ConsoleLogger$: unique symbol;
|
|
4
|
+
export interface ConsoleLoggerConfig {
|
|
5
|
+
[ConsoleLogger$]?: boolean;
|
|
6
|
+
enable: boolean;
|
|
7
|
+
debug: boolean;
|
|
8
|
+
info: boolean;
|
|
9
|
+
warn: boolean;
|
|
10
|
+
'styles.debug': string;
|
|
11
|
+
'styles.info': string;
|
|
12
|
+
'styles.warn': string;
|
|
13
|
+
'styles.error': string;
|
|
14
|
+
[key: string]: string | boolean | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface ConsoleLoggerControl {
|
|
17
|
+
[ConsoleLogger$]?: boolean;
|
|
18
|
+
enable: boolean;
|
|
19
|
+
debug: boolean;
|
|
20
|
+
info: boolean;
|
|
21
|
+
warn: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The key a config value is stored under. Prefixed with {@link CONSOLE_LOGGER} when it goes
|
|
25
|
+
* into a real Storage, where it shares the namespace with everything else on this origin.
|
|
26
|
+
*/
|
|
27
|
+
export declare const consoleLoggerConfigKey: (key: string | string[]) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Reads a raw config value through whatever storage the capability probe accepted, or from the
|
|
30
|
+
* fallback object store when none did. The way to reach the config from outside this module:
|
|
31
|
+
* `globalThis.localStorage` may be missing, inert or hostile, and asking it directly throws.
|
|
32
|
+
*/
|
|
33
|
+
export declare const loadConsoleLoggerConfig: (key: string | string[], defaultValue: string) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Installs a config object as the fallback store, bypassing the storage probe. `WorkerRuntime`
|
|
36
|
+
* calls this with the config a `RemoteWorkerEnv` forwards from the main thread, where there is no
|
|
37
|
+
* `localStorage` to probe in the first place.
|
|
38
|
+
*
|
|
39
|
+
* Once a logger exists in such a thread, `ConsoleLogger.sharedConfig` *is* that store, and the
|
|
40
|
+
* values are written into it rather than a fresh object taking its place in the slot: the shared
|
|
41
|
+
* switches reach every logger of the thread, the ones already built included. One flag stays
|
|
42
|
+
* behind -- a logger reads its own `<namespace>.enable` key when it is built, and a config that
|
|
43
|
+
* arrives afterwards no longer moves it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setConsoleLoggerStorage(config: ConsoleLoggerConfig): void;
|
|
3
46
|
export declare class ConsoleLogger {
|
|
4
47
|
#private;
|
|
5
48
|
namespace?: string;
|
|
6
49
|
enable: boolean;
|
|
7
|
-
static sharedConfig:
|
|
8
|
-
enable: boolean;
|
|
9
|
-
debug: boolean;
|
|
10
|
-
info: boolean;
|
|
11
|
-
warn: boolean;
|
|
12
|
-
'styles.debug': string;
|
|
13
|
-
'styles.info': string;
|
|
14
|
-
'styles.warn': string;
|
|
15
|
-
'styles.error': string;
|
|
16
|
-
};
|
|
50
|
+
static sharedConfig: ConsoleLoggerConfig;
|
|
17
51
|
static get isEnabled(): boolean;
|
|
18
52
|
static get isDebug(): boolean;
|
|
19
53
|
static sharedStyles: {
|
|
@@ -33,4 +67,5 @@ export declare class ConsoleLogger {
|
|
|
33
67
|
warn(...args: any[]): void;
|
|
34
68
|
error(...args: any[]): void;
|
|
35
69
|
}
|
|
70
|
+
export {};
|
|
36
71
|
//# sourceMappingURL=ConsoleLogger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsoleLogger.d.ts","sourceRoot":"","sources":["../../../src/utils/ConsoleLogger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,eAAO,MAAM,sBAAsB,yBAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"ConsoleLogger.d.ts","sourceRoot":"","sources":["../../../src/utils/ConsoleLogger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,eAAO,MAAM,sBAAsB,yBAA6B,CAAC;AAmDjE,QAAA,MAAM,cAAc,EAAE,OAAO,MAAmC,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,oBAAoB;IACnC,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAgDD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,MAAM,GAAG,MAAM,EAAE,KAAG,MAAyB,CAAC;AAE1F;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAS,MAAM,GAAG,MAAM,EAAE,gBAAgB,MAAM,KAAG,MACxC,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CASzE;AAED,qBAAa,aAAa;;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,UAAQ;IAEd,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAWtC;IAEF,MAAM,KAAK,SAAS,YAEnB;IAED,MAAM,KAAK,OAAO,YAEjB;IAED,MAAM,CAAC,YAAY;eAIF,MAAM;cAMP,MAAM;cAMN,MAAM;eAML,MAAM;MAGrB;IAEF,MAAM,CAAC,UAAU,SA8DhB;IAED,YAAY,SAAS,EAAE,MAAM,EAY5B;IAED,IAAI,SAAS,YAEZ;IAED,IAAI,OAAO,YAEV;IAED,IAAI,MAAM,YAET;IAED,IAAI,MAAM,YAET;IAED,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,QAGnB;IAED,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,QAGlB;IAED,IAAI,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,QAGlB;IAID,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,QAEnB;CAKF"}
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
const CONSOLE_LOGGER = "ConsoleLogger";
|
|
2
2
|
const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
4
|
+
const IS_LOOPBACK_HOST = LOOPBACK_HOSTNAMES.has(globalThis.location?.hostname ?? "");
|
|
5
|
+
const gLocalStorage = (() => {
|
|
6
|
+
try {
|
|
7
|
+
const storage = globalThis.localStorage;
|
|
8
|
+
if (typeof storage?.getItem !== "function" || typeof storage.setItem !== "function" || typeof storage.removeItem !== "function") {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
const probeKey = `${CONSOLE_LOGGER}.probe`;
|
|
12
|
+
try {
|
|
13
|
+
storage.setItem(probeKey, probeKey);
|
|
14
|
+
} finally {
|
|
15
|
+
storage.removeItem(probeKey);
|
|
16
|
+
}
|
|
17
|
+
return storage;
|
|
18
|
+
} catch {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
const HAS_LOCAL_STORAGE = gLocalStorage != null;
|
|
5
23
|
const ConsoleLogger$ = Symbol.for(CONSOLE_LOGGER);
|
|
24
|
+
const gGlobalSlots = globalThis;
|
|
6
25
|
let gInitialized = false;
|
|
7
26
|
const asBoolean = (val) => {
|
|
8
27
|
if (typeof val === "boolean") return val;
|
|
@@ -16,22 +35,27 @@ const asBoolean = (val) => {
|
|
|
16
35
|
}
|
|
17
36
|
};
|
|
18
37
|
const getKeyPath = (key) => [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : void 0, ...Array.isArray(key) ? key : [key]].filter(Boolean).join(".");
|
|
19
|
-
function loadConfigValue(key, as
|
|
38
|
+
function loadConfigValue(key, as, defaultValue) {
|
|
20
39
|
const _key = getKeyPath(key);
|
|
21
|
-
const value =
|
|
22
|
-
|
|
40
|
+
const value = gLocalStorage ? gLocalStorage.getItem(_key) : gGlobalSlots.ConsoleLoggerStorage?.[_key];
|
|
41
|
+
if (value == void 0) return defaultValue;
|
|
42
|
+
return as ? as(value) : value;
|
|
23
43
|
}
|
|
24
44
|
function saveConfigValue(key, val) {
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
} else {
|
|
28
|
-
if (globalThis[CONSOLE_LOGGER_STORAGE] == void 0) {
|
|
29
|
-
globalThis[CONSOLE_LOGGER_STORAGE] = {};
|
|
30
|
-
console.debug(`${CONSOLE_LOGGER}: Initialize`, { [CONSOLE_LOGGER_STORAGE]: globalThis[CONSOLE_LOGGER_STORAGE] });
|
|
31
|
-
}
|
|
32
|
-
globalThis[CONSOLE_LOGGER_STORAGE][getKeyPath(key)] = val;
|
|
45
|
+
if (gLocalStorage) {
|
|
46
|
+
gLocalStorage.setItem(getKeyPath(key), val);
|
|
33
47
|
}
|
|
34
48
|
}
|
|
49
|
+
const consoleLoggerConfigKey = (key) => getKeyPath(key);
|
|
50
|
+
const loadConsoleLoggerConfig = (key, defaultValue) => loadConfigValue(key, void 0, defaultValue);
|
|
51
|
+
function setConsoleLoggerStorage(config) {
|
|
52
|
+
const store = gGlobalSlots.ConsoleLoggerStorage;
|
|
53
|
+
if (store != null && store === ConsoleLogger.sharedConfig) {
|
|
54
|
+
Object.assign(store, config, { [ConsoleLogger$]: true });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
gGlobalSlots.ConsoleLoggerStorage = config;
|
|
58
|
+
}
|
|
35
59
|
class ConsoleLogger {
|
|
36
60
|
constructor(namespace) {
|
|
37
61
|
this.enable = true;
|
|
@@ -42,11 +66,10 @@ class ConsoleLogger {
|
|
|
42
66
|
}
|
|
43
67
|
const key = [this.namespace, "enable"];
|
|
44
68
|
this.enable = loadConfigValue(key, asBoolean, this.enable);
|
|
45
|
-
saveConfigValue(key, HAS_LOCAL_STORAGE ? this.enable ? "true" : "false" : this.enable);
|
|
46
69
|
}
|
|
47
70
|
static {
|
|
48
71
|
this.sharedConfig = {
|
|
49
|
-
enable:
|
|
72
|
+
enable: IS_LOOPBACK_HOST,
|
|
50
73
|
debug: false,
|
|
51
74
|
info: true,
|
|
52
75
|
warn: true,
|
|
@@ -101,8 +124,8 @@ class ConsoleLogger {
|
|
|
101
124
|
if (ConsoleLogger.isDebug) {
|
|
102
125
|
console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);
|
|
103
126
|
}
|
|
104
|
-
if (!
|
|
105
|
-
|
|
127
|
+
if (!gGlobalSlots.ConsoleLogger?.[ConsoleLogger$]) {
|
|
128
|
+
gGlobalSlots.ConsoleLogger ??= {
|
|
106
129
|
[ConsoleLogger$]: true,
|
|
107
130
|
get enable() {
|
|
108
131
|
return ConsoleLogger.sharedConfig.enable;
|
|
@@ -135,20 +158,15 @@ class ConsoleLogger {
|
|
|
135
158
|
};
|
|
136
159
|
}
|
|
137
160
|
} else {
|
|
138
|
-
if (!
|
|
139
|
-
|
|
161
|
+
if (!gGlobalSlots.ConsoleLoggerStorage?.[ConsoleLogger$]) {
|
|
162
|
+
gGlobalSlots.ConsoleLoggerStorage = {
|
|
140
163
|
[ConsoleLogger$]: true,
|
|
141
164
|
...ConsoleLogger.sharedConfig,
|
|
142
|
-
|
|
143
|
-
...globalThis[CONSOLE_LOGGER_STORAGE]
|
|
165
|
+
...gGlobalSlots.ConsoleLoggerStorage
|
|
144
166
|
};
|
|
145
|
-
ConsoleLogger.sharedConfig =
|
|
167
|
+
ConsoleLogger.sharedConfig = gGlobalSlots.ConsoleLoggerStorage;
|
|
146
168
|
if (ConsoleLogger.isDebug) {
|
|
147
|
-
console.debug(
|
|
148
|
-
`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`,
|
|
149
|
-
// @ts-ignore
|
|
150
|
-
globalThis[CONSOLE_LOGGER_STORAGE]
|
|
151
|
-
);
|
|
169
|
+
console.debug(`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`, gGlobalSlots.ConsoleLoggerStorage);
|
|
152
170
|
}
|
|
153
171
|
}
|
|
154
172
|
}
|
|
@@ -166,14 +184,19 @@ class ConsoleLogger {
|
|
|
166
184
|
return this.isEnabled && ConsoleLogger.sharedConfig.warn;
|
|
167
185
|
}
|
|
168
186
|
debug(...args) {
|
|
187
|
+
if (!this.isDebug) return;
|
|
169
188
|
this.#print("debug", ConsoleLogger.sharedStyles.debug, args);
|
|
170
189
|
}
|
|
171
190
|
info(...args) {
|
|
191
|
+
if (!this.isInfo) return;
|
|
172
192
|
this.#print("info", ConsoleLogger.sharedStyles.info, args);
|
|
173
193
|
}
|
|
174
194
|
warn(...args) {
|
|
195
|
+
if (!this.isWarn) return;
|
|
175
196
|
this.#print("warn", ConsoleLogger.sharedStyles.warn, args);
|
|
176
197
|
}
|
|
198
|
+
// An error report names a fault in the calling code, and its author has to see it wherever
|
|
199
|
+
// the application runs -- not only on a loopback host, so `error()` asks no getter.
|
|
177
200
|
error(...args) {
|
|
178
201
|
this.#print("error", ConsoleLogger.sharedStyles.error, args);
|
|
179
202
|
}
|
|
@@ -184,6 +207,9 @@ class ConsoleLogger {
|
|
|
184
207
|
export {
|
|
185
208
|
CONSOLE_LOGGER,
|
|
186
209
|
CONSOLE_LOGGER_STORAGE,
|
|
187
|
-
ConsoleLogger
|
|
210
|
+
ConsoleLogger,
|
|
211
|
+
consoleLoggerConfigKey,
|
|
212
|
+
loadConsoleLoggerConfig,
|
|
213
|
+
setConsoleLoggerStorage
|
|
188
214
|
};
|
|
189
215
|
//# sourceMappingURL=ConsoleLogger.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/ConsoleLogger.ts"],
|
|
4
|
-
"sourcesContent": ["// https://developer.chrome.com/docs/devtools/console/format-style?hl=de\n\nexport const CONSOLE_LOGGER = 'ConsoleLogger';\nexport const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;\n\nconst IS_LOCALHOST = Boolean(globalThis.location?.host?.startsWith('localhost') ?? false);\n\nconst HAS_LOCAL_STORAGE = 'localStorage' in globalThis;\n\nconst ConsoleLogger$ = Symbol.for(CONSOLE_LOGGER);\n\nlet gInitialized = false;\n\nconst asBoolean = (val: string | boolean) => {\n if (typeof val === 'boolean') return val;\n switch (val.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n};\n\nconst getKeyPath = (key: string | string[]): string =>\n [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : undefined, ...(Array.isArray(key) ? key : [key])].filter(Boolean).join('.');\n\nfunction loadConfigValue<T>(key: string | string[], as: (val: string) => T = undefined, defaultValue: T): T {\n const _key = getKeyPath(key);\n // @ts-ignore\n const value = HAS_LOCAL_STORAGE ? localStorage.getItem(_key) : globalThis[CONSOLE_LOGGER_STORAGE]?.[_key];\n return value != undefined ? as(value) : defaultValue;\n}\n\nfunction saveConfigValue(key: string | string[], val: any) {\n if (HAS_LOCAL_STORAGE) {\n localStorage.setItem(getKeyPath(key), val);\n } else {\n // @ts-ignore\n if (globalThis[CONSOLE_LOGGER_STORAGE] == undefined) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE] = {};\n // @ts-ignore\n console.debug(`${CONSOLE_LOGGER}: Initialize`, {[CONSOLE_LOGGER_STORAGE]: globalThis[CONSOLE_LOGGER_STORAGE]});\n }\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE][getKeyPath(key)] = val;\n }\n}\n\nexport class ConsoleLogger {\n namespace?: string;\n\n enable = true;\n\n static sharedConfig = {\n enable: IS_LOCALHOST,\n\n debug: false,\n info: true,\n warn: true,\n\n 'styles.debug': 'color: #111; background: #999; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.info': 'color: #020; background: #8a8; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.warn': 'color: #fa0; background: #a98; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.error': 'color: #ff0; background: #a00; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n };\n\n static get isEnabled() {\n return ConsoleLogger.sharedConfig.enable;\n }\n\n static get isDebug() {\n return ConsoleLogger.sharedConfig.enable && ConsoleLogger.sharedConfig.debug;\n }\n\n static sharedStyles = {\n get debug() {\n return ConsoleLogger.sharedConfig['styles.debug'];\n },\n set debug(val: string) {\n ConsoleLogger.sharedConfig['styles.debug'] = val;\n },\n get info() {\n return ConsoleLogger.sharedConfig['styles.info'];\n },\n set info(val: string) {\n ConsoleLogger.sharedConfig['styles.info'] = val;\n },\n get warn() {\n return ConsoleLogger.sharedConfig['styles.warn'];\n },\n set warn(val: string) {\n ConsoleLogger.sharedConfig['styles.warn'] = val;\n },\n get error() {\n return ConsoleLogger.sharedConfig['styles.error'];\n },\n set error(val: string) {\n ConsoleLogger.sharedConfig['styles.error'] = val;\n },\n };\n\n static loadConfig() {\n if (HAS_LOCAL_STORAGE) {\n ['enable', 'debug', 'info', 'warn'].forEach((key) => {\n // @ts-ignore\n this.sharedConfig[key] = loadConfigValue(key, asBoolean, this.sharedConfig[key]);\n });\n\n ['debug', 'info', 'warn', 'error'].forEach((key) => {\n // @ts-ignore\n this.sharedStyles[key] = loadConfigValue(['styles', key], undefined, this.sharedStyles[key]);\n });\n\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);\n }\n\n // @ts-ignore\n if (!globalThis[CONSOLE_LOGGER]?.[ConsoleLogger$]) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER] ??= {\n [ConsoleLogger$]: true,\n\n get enable() {\n return ConsoleLogger.sharedConfig.enable;\n },\n set enable(val: boolean) {\n ConsoleLogger.sharedConfig.enable = val;\n saveConfigValue('enable', val ? 'true' : 'false');\n },\n get debug() {\n return ConsoleLogger.sharedConfig.debug;\n },\n set debug(val: boolean) {\n ConsoleLogger.sharedConfig.debug = val;\n saveConfigValue('debug', val ? 'true' : 'false');\n },\n get info() {\n return ConsoleLogger.sharedConfig.info;\n },\n set info(val: boolean) {\n ConsoleLogger.sharedConfig.info = val;\n saveConfigValue('info', val ? 'true' : 'false');\n },\n get warn() {\n return ConsoleLogger.sharedConfig.warn;\n },\n set warn(val: boolean) {\n ConsoleLogger.sharedConfig.warn = val;\n saveConfigValue('warn', val ? 'true' : 'false');\n },\n };\n }\n } else {\n // no localStorage available: in this case we just use globalThis[STORAGE_KEY]\n // @ts-ignore\n if (!globalThis[CONSOLE_LOGGER_STORAGE]?.[ConsoleLogger$]) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE] = {\n [ConsoleLogger$]: true,\n ...ConsoleLogger.sharedConfig,\n // @ts-ignore\n ...globalThis[CONSOLE_LOGGER_STORAGE],\n };\n // @ts-ignore\n ConsoleLogger.sharedConfig = globalThis[CONSOLE_LOGGER_STORAGE];\n if (ConsoleLogger.isDebug) {\n console.debug(\n `${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`,\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE],\n );\n }\n }\n }\n }\n\n constructor(namespace: string) {\n this.namespace = (namespace || '').trim() || CONSOLE_LOGGER;\n\n if (!gInitialized) {\n ConsoleLogger.loadConfig();\n gInitialized = true;\n }\n\n const key = [this.namespace, 'enable'];\n this.enable = loadConfigValue(key, asBoolean, this.enable);\n saveConfigValue(key, HAS_LOCAL_STORAGE ? (this.enable ? 'true' : 'false') : this.enable);\n }\n\n get isEnabled() {\n return this.enable && ConsoleLogger.sharedConfig.enable;\n }\n\n get isDebug() {\n return this.isEnabled && ConsoleLogger.sharedConfig.debug;\n }\n\n get isInfo() {\n return this.isEnabled && ConsoleLogger.sharedConfig.info;\n }\n\n get isWarn() {\n return this.isEnabled && ConsoleLogger.sharedConfig.warn;\n }\n\n debug(...args: any[]) {\n this.#print('debug', ConsoleLogger.sharedStyles.debug, args);\n }\n\n info(...args: any[]) {\n this.#print('info', ConsoleLogger.sharedStyles.info, args);\n }\n\n warn(...args: any[]) {\n this.#print('warn', ConsoleLogger.sharedStyles.warn, args);\n }\n\n error(...args: any[]) {\n this.#print('error', ConsoleLogger.sharedStyles.error, args);\n }\n\n #print(level: 'debug' | 'info' | 'warn' | 'error', styles: string, args: any[]) {\n console[level](`%c${this.namespace}`, styles, ...args);\n }\n}"],
|
|
5
|
-
"mappings": "AAEO,MAAM,iBAAiB;AACvB,MAAM,yBAAyB,GAAG,cAAc;
|
|
4
|
+
"sourcesContent": ["// https://developer.chrome.com/docs/devtools/console/format-style?hl=de\n\nexport const CONSOLE_LOGGER = 'ConsoleLogger';\nexport const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;\n\n// A loopback host is the one place this library talks by default. `host` carries the port,\n// which a prefix test would have to get past -- and a prefix test says yes to every name that\n// merely begins with the word, `localhost.example.com` among them, while saying no to the\n// addresses that carry no name at all. So the port-free `hostname` is compared against the\n// exact set. A browser hands an IPv6 host over in brackets; the bare form is there for a\n// realm that does not.\nconst LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);\n\nconst IS_LOOPBACK_HOST = LOOPBACK_HOSTNAMES.has(globalThis.location?.hostname ?? '');\n\n// The name `localStorage` says nothing about a usable Storage behind it: node defines an inert\n// object on `globalThis`, and a browser with disabled cookies throws a `SecurityError` -- on the\n// property access or on the first write. So the capability is probed, not looked up, and the\n// storage that passes is kept by reference. Whatever fails here falls back to the plain object\n// under `globalThis.ConsoleLoggerStorage`.\nconst gLocalStorage: Storage | undefined = (() => {\n try {\n const storage = (globalThis as {localStorage?: Storage}).localStorage;\n if (\n typeof storage?.getItem !== 'function' ||\n typeof storage.setItem !== 'function' ||\n typeof storage.removeItem !== 'function'\n ) {\n return undefined;\n }\n // The write is what separates a Storage that only looks usable from one that is: cookies\n // may be off, or the quota full. This probe key is the only thing the library writes into a\n // host's storage on its own, and it is removed in the same breath -- everything past this\n // point only reads, until a caller reaches through the handle under `globalThis.ConsoleLogger`.\n // Two costs are accepted for the probe itself: one `storage` event pair for the other tabs of\n // this origin, and a full quota that sends an otherwise readable Storage into the fallback.\n const probeKey = `${CONSOLE_LOGGER}.probe`;\n try {\n storage.setItem(probeKey, probeKey);\n } finally {\n // reached even when `setItem` throws: a storage that takes the write and then fails would\n // otherwise fall through to the fallback with the probe key left behind. One way past\n // \"removed in the same breath\" stays open -- a `removeItem` that throws is the very call\n // this `finally` makes, and the key survives.\n storage.removeItem(probeKey);\n }\n return storage;\n } catch {\n return undefined;\n }\n})();\n\nconst HAS_LOCAL_STORAGE = gLocalStorage != null;\n\nconst ConsoleLogger$: unique symbol = Symbol.for(CONSOLE_LOGGER);\n\nexport interface ConsoleLoggerConfig {\n [ConsoleLogger$]?: boolean;\n enable: boolean;\n debug: boolean;\n info: boolean;\n warn: boolean;\n 'styles.debug': string;\n 'styles.info': string;\n 'styles.warn': string;\n 'styles.error': string;\n [key: string]: string | boolean | undefined;\n}\n\nexport interface ConsoleLoggerControl {\n [ConsoleLogger$]?: boolean;\n enable: boolean;\n debug: boolean;\n info: boolean;\n warn: boolean;\n}\n\n// A module-local view of the two globals this library touches, in place of a `declare global`:\n// the re-exports in this package's emitted `dist/src/index.d.ts`, published as `src/index.d.ts`,\n// reach every consumer's global type space through `ShadowEnv.d.ts`'s import of this module, and\n// `ConsoleLogger` is too common a name to claim there uninvited -- an ambient `var ConsoleLogger`\n// would collide with a consumer's own global of that name (`TS2403`) and turn a forgotten import\n// of this class into a silent pass against the handle instead of a compile error.\nconst gGlobalSlots = globalThis as typeof globalThis & {\n ConsoleLogger?: ConsoleLoggerControl;\n ConsoleLoggerStorage?: ConsoleLoggerConfig;\n};\n\nlet gInitialized = false;\n\nconst asBoolean = (val: string | boolean) => {\n if (typeof val === 'boolean') return val;\n switch (val.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n};\n\nconst getKeyPath = (key: string | string[]): string =>\n [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : undefined, ...(Array.isArray(key) ? key : [key])].filter(Boolean).join('.');\n\nfunction loadConfigValue<T>(key: string | string[], as: ((val: string | boolean) => T) | undefined, defaultValue: T): T {\n const _key = getKeyPath(key);\n const value = gLocalStorage ? gLocalStorage.getItem(_key) : gGlobalSlots.ConsoleLoggerStorage?.[_key];\n if (value == undefined) return defaultValue;\n // without a converter the stored value is the value: that is how the styles are read\n return as ? as(value) : (value as T);\n}\n\n// The four setters of the `globalThis.ConsoleLogger` handle below are the only callers, and those\n// exist only inside the `HAS_LOCAL_STORAGE` branch of `loadConfig()` -- so `gLocalStorage` is\n// always set whenever this runs. The fallback store under `globalThis.ConsoleLoggerStorage` is\n// written by `loadConfig()` alone, and in the worker through `setConsoleLoggerStorage()`.\nfunction saveConfigValue(key: string | string[], val: any) {\n if (gLocalStorage) {\n gLocalStorage.setItem(getKeyPath(key), val);\n }\n}\n\n/**\n * The key a config value is stored under. Prefixed with {@link CONSOLE_LOGGER} when it goes\n * into a real Storage, where it shares the namespace with everything else on this origin.\n */\nexport const consoleLoggerConfigKey = (key: string | string[]): string => getKeyPath(key);\n\n/**\n * Reads a raw config value through whatever storage the capability probe accepted, or from the\n * fallback object store when none did. The way to reach the config from outside this module:\n * `globalThis.localStorage` may be missing, inert or hostile, and asking it directly throws.\n */\nexport const loadConsoleLoggerConfig = (key: string | string[], defaultValue: string): string =>\n loadConfigValue(key, undefined, defaultValue);\n\n/**\n * Installs a config object as the fallback store, bypassing the storage probe. `WorkerRuntime`\n * calls this with the config a `RemoteWorkerEnv` forwards from the main thread, where there is no\n * `localStorage` to probe in the first place.\n *\n * Once a logger exists in such a thread, `ConsoleLogger.sharedConfig` *is* that store, and the\n * values are written into it rather than a fresh object taking its place in the slot: the shared\n * switches reach every logger of the thread, the ones already built included. One flag stays\n * behind -- a logger reads its own `<namespace>.enable` key when it is built, and a config that\n * arrives afterwards no longer moves it.\n */\nexport function setConsoleLoggerStorage(config: ConsoleLoggerConfig): void {\n const store = gGlobalSlots.ConsoleLoggerStorage;\n if (store != null && store === ConsoleLogger.sharedConfig) {\n // the marker travels with the merge: it says this object is the live config, and a config\n // that crossed a worker boundary carries no symbol key to say so for itself\n Object.assign(store, config, {[ConsoleLogger$]: true});\n return;\n }\n gGlobalSlots.ConsoleLoggerStorage = config;\n}\n\nexport class ConsoleLogger {\n namespace?: string;\n\n enable = true;\n\n static sharedConfig: ConsoleLoggerConfig = {\n enable: IS_LOOPBACK_HOST,\n\n debug: false,\n info: true,\n warn: true,\n\n 'styles.debug': 'color: #111; background: #999; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.info': 'color: #020; background: #8a8; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.warn': 'color: #fa0; background: #a98; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.error': 'color: #ff0; background: #a00; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n };\n\n static get isEnabled() {\n return ConsoleLogger.sharedConfig.enable;\n }\n\n static get isDebug() {\n return ConsoleLogger.sharedConfig.enable && ConsoleLogger.sharedConfig.debug;\n }\n\n static sharedStyles = {\n get debug() {\n return ConsoleLogger.sharedConfig['styles.debug'];\n },\n set debug(val: string) {\n ConsoleLogger.sharedConfig['styles.debug'] = val;\n },\n get info() {\n return ConsoleLogger.sharedConfig['styles.info'];\n },\n set info(val: string) {\n ConsoleLogger.sharedConfig['styles.info'] = val;\n },\n get warn() {\n return ConsoleLogger.sharedConfig['styles.warn'];\n },\n set warn(val: string) {\n ConsoleLogger.sharedConfig['styles.warn'] = val;\n },\n get error() {\n return ConsoleLogger.sharedConfig['styles.error'];\n },\n set error(val: string) {\n ConsoleLogger.sharedConfig['styles.error'] = val;\n },\n };\n\n static loadConfig() {\n if (HAS_LOCAL_STORAGE) {\n (['enable', 'debug', 'info', 'warn'] as const).forEach((key) => {\n this.sharedConfig[key] = loadConfigValue(key, asBoolean, this.sharedConfig[key]);\n });\n\n (['debug', 'info', 'warn', 'error'] as const).forEach((key) => {\n this.sharedStyles[key] = loadConfigValue(['styles', key], undefined, this.sharedStyles[key]);\n });\n\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);\n }\n\n if (!gGlobalSlots.ConsoleLogger?.[ConsoleLogger$]) {\n gGlobalSlots.ConsoleLogger ??= {\n [ConsoleLogger$]: true,\n\n get enable() {\n return ConsoleLogger.sharedConfig.enable;\n },\n set enable(val: boolean) {\n ConsoleLogger.sharedConfig.enable = val;\n saveConfigValue('enable', val ? 'true' : 'false');\n },\n get debug() {\n return ConsoleLogger.sharedConfig.debug;\n },\n set debug(val: boolean) {\n ConsoleLogger.sharedConfig.debug = val;\n saveConfigValue('debug', val ? 'true' : 'false');\n },\n get info() {\n return ConsoleLogger.sharedConfig.info;\n },\n set info(val: boolean) {\n ConsoleLogger.sharedConfig.info = val;\n saveConfigValue('info', val ? 'true' : 'false');\n },\n get warn() {\n return ConsoleLogger.sharedConfig.warn;\n },\n set warn(val: boolean) {\n ConsoleLogger.sharedConfig.warn = val;\n saveConfigValue('warn', val ? 'true' : 'false');\n },\n };\n }\n } else {\n // no localStorage available: in this case we just use gGlobalSlots.ConsoleLoggerStorage\n if (!gGlobalSlots.ConsoleLoggerStorage?.[ConsoleLogger$]) {\n gGlobalSlots.ConsoleLoggerStorage = {\n [ConsoleLogger$]: true,\n ...ConsoleLogger.sharedConfig,\n ...gGlobalSlots.ConsoleLoggerStorage,\n };\n ConsoleLogger.sharedConfig = gGlobalSlots.ConsoleLoggerStorage;\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`, gGlobalSlots.ConsoleLoggerStorage);\n }\n }\n }\n }\n\n constructor(namespace: string) {\n this.namespace = (namespace || '').trim() || CONSOLE_LOGGER;\n\n if (!gInitialized) {\n ConsoleLogger.loadConfig();\n gInitialized = true;\n }\n\n // reads only: the storage of the host is never written here. The only way this library\n // writes to it is through the four setters of the `globalThis.ConsoleLogger` handle above.\n const key = [this.namespace, 'enable'];\n this.enable = loadConfigValue(key, asBoolean, this.enable);\n }\n\n get isEnabled() {\n return this.enable && ConsoleLogger.sharedConfig.enable;\n }\n\n get isDebug() {\n return this.isEnabled && ConsoleLogger.sharedConfig.debug;\n }\n\n get isInfo() {\n return this.isEnabled && ConsoleLogger.sharedConfig.info;\n }\n\n get isWarn() {\n return this.isEnabled && ConsoleLogger.sharedConfig.warn;\n }\n\n debug(...args: any[]) {\n if (!this.isDebug) return;\n this.#print('debug', ConsoleLogger.sharedStyles.debug, args);\n }\n\n info(...args: any[]) {\n if (!this.isInfo) return;\n this.#print('info', ConsoleLogger.sharedStyles.info, args);\n }\n\n warn(...args: any[]) {\n if (!this.isWarn) return;\n this.#print('warn', ConsoleLogger.sharedStyles.warn, args);\n }\n\n // An error report names a fault in the calling code, and its author has to see it wherever\n // the application runs -- not only on a loopback host, so `error()` asks no getter.\n error(...args: any[]) {\n this.#print('error', ConsoleLogger.sharedStyles.error, args);\n }\n\n #print(level: 'debug' | 'info' | 'warn' | 'error', styles: string, args: any[]) {\n console[level](`%c${this.namespace}`, styles, ...args);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,iBAAiB;AACvB,MAAM,yBAAyB,GAAG,cAAc;AAQvD,MAAM,qBAAqB,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,OAAO,CAAC;AAE7E,MAAM,mBAAmB,mBAAmB,IAAI,WAAW,UAAU,YAAY,EAAE;AAOnF,MAAM,iBAAsC,MAAM;AAChD,MAAI;AACF,UAAM,UAAW,WAAwC;AACzD,QACE,OAAO,SAAS,YAAY,cAC5B,OAAO,QAAQ,YAAY,cAC3B,OAAO,QAAQ,eAAe,YAC9B;AACA,aAAO;AAAA,IACT;AAOA,UAAM,WAAW,GAAG,cAAc;AAClC,QAAI;AACF,cAAQ,QAAQ,UAAU,QAAQ;AAAA,IACpC,UAAE;AAKA,cAAQ,WAAW,QAAQ;AAAA,IAC7B;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF,GAAG;AAEH,MAAM,oBAAoB,iBAAiB;AAE3C,MAAM,iBAAgC,OAAO,IAAI,cAAc;AA6B/D,MAAM,eAAe;AAKrB,IAAI,eAAe;AAEnB,MAAM,YAAY,CAAC,QAA0B;AAC3C,MAAI,OAAO,QAAQ,UAAW,QAAO;AACrC,UAAQ,IAAI,YAAY,GAAG;AAAA,IACzB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,MAAM,aAAa,CAAC,QAClB,CAAC,oBAAoB,iBAAiB,QAAW,GAAI,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,CAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAElH,SAAS,gBAAmB,KAAwB,IAAgD,cAAoB;AACtH,QAAM,OAAO,WAAW,GAAG;AAC3B,QAAM,QAAQ,gBAAgB,cAAc,QAAQ,IAAI,IAAI,aAAa,uBAAuB,IAAI;AACpG,MAAI,SAAS,OAAW,QAAO;AAE/B,SAAO,KAAK,GAAG,KAAK,IAAK;AAC3B;AAMA,SAAS,gBAAgB,KAAwB,KAAU;AACzD,MAAI,eAAe;AACjB,kBAAc,QAAQ,WAAW,GAAG,GAAG,GAAG;AAAA,EAC5C;AACF;AAMO,MAAM,yBAAyB,CAAC,QAAmC,WAAW,GAAG;AAOjF,MAAM,0BAA0B,CAAC,KAAwB,iBAC9D,gBAAgB,KAAK,QAAW,YAAY;AAavC,SAAS,wBAAwB,QAAmC;AACzE,QAAM,QAAQ,aAAa;AAC3B,MAAI,SAAS,QAAQ,UAAU,cAAc,cAAc;AAGzD,WAAO,OAAO,OAAO,QAAQ,EAAC,CAAC,cAAc,GAAG,KAAI,CAAC;AACrD;AAAA,EACF;AACA,eAAa,uBAAuB;AACtC;AAEO,MAAM,cAAc;AAAA,EAqHzB,YAAY,WAAmB;AAlH/B,kBAAS;AAmHP,SAAK,aAAa,aAAa,IAAI,KAAK,KAAK;AAE7C,QAAI,CAAC,cAAc;AACjB,oBAAc,WAAW;AACzB,qBAAe;AAAA,IACjB;AAIA,UAAM,MAAM,CAAC,KAAK,WAAW,QAAQ;AACrC,SAAK,SAAS,gBAAgB,KAAK,WAAW,KAAK,MAAM;AAAA,EAC3D;AAAA,EA5HA;AAAA,SAAO,eAAoC;AAAA,MACzC,QAAQ;AAAA,MAER,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MAEN,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,eAAe;AAAA,MACf,gBAAgB;AAAA,IAClB;AAAA;AAAA,EAEA,WAAW,YAAY;AACrB,WAAO,cAAc,aAAa;AAAA,EACpC;AAAA,EAEA,WAAW,UAAU;AACnB,WAAO,cAAc,aAAa,UAAU,cAAc,aAAa;AAAA,EACzE;AAAA,EAEA;AAAA,SAAO,eAAe;AAAA,MACpB,IAAI,QAAQ;AACV,eAAO,cAAc,aAAa,cAAc;AAAA,MAClD;AAAA,MACA,IAAI,MAAM,KAAa;AACrB,sBAAc,aAAa,cAAc,IAAI;AAAA,MAC/C;AAAA,MACA,IAAI,OAAO;AACT,eAAO,cAAc,aAAa,aAAa;AAAA,MACjD;AAAA,MACA,IAAI,KAAK,KAAa;AACpB,sBAAc,aAAa,aAAa,IAAI;AAAA,MAC9C;AAAA,MACA,IAAI,OAAO;AACT,eAAO,cAAc,aAAa,aAAa;AAAA,MACjD;AAAA,MACA,IAAI,KAAK,KAAa;AACpB,sBAAc,aAAa,aAAa,IAAI;AAAA,MAC9C;AAAA,MACA,IAAI,QAAQ;AACV,eAAO,cAAc,aAAa,cAAc;AAAA,MAClD;AAAA,MACA,IAAI,MAAM,KAAa;AACrB,sBAAc,aAAa,cAAc,IAAI;AAAA,MAC/C;AAAA,IACF;AAAA;AAAA,EAEA,OAAO,aAAa;AAClB,QAAI,mBAAmB;AACrB,MAAC,CAAC,UAAU,SAAS,QAAQ,MAAM,EAAY,QAAQ,CAAC,QAAQ;AAC9D,aAAK,aAAa,GAAG,IAAI,gBAAgB,KAAK,WAAW,KAAK,aAAa,GAAG,CAAC;AAAA,MACjF,CAAC;AAED,MAAC,CAAC,SAAS,QAAQ,QAAQ,OAAO,EAAY,QAAQ,CAAC,QAAQ;AAC7D,aAAK,aAAa,GAAG,IAAI,gBAAgB,CAAC,UAAU,GAAG,GAAG,QAAW,KAAK,aAAa,GAAG,CAAC;AAAA,MAC7F,CAAC;AAED,UAAI,cAAc,SAAS;AACzB,gBAAQ,MAAM,GAAG,cAAc,mCAAmC,cAAc,YAAY;AAAA,MAC9F;AAEA,UAAI,CAAC,aAAa,gBAAgB,cAAc,GAAG;AACjD,qBAAa,kBAAkB;AAAA,UAC7B,CAAC,cAAc,GAAG;AAAA,UAElB,IAAI,SAAS;AACX,mBAAO,cAAc,aAAa;AAAA,UACpC;AAAA,UACA,IAAI,OAAO,KAAc;AACvB,0BAAc,aAAa,SAAS;AACpC,4BAAgB,UAAU,MAAM,SAAS,OAAO;AAAA,UAClD;AAAA,UACA,IAAI,QAAQ;AACV,mBAAO,cAAc,aAAa;AAAA,UACpC;AAAA,UACA,IAAI,MAAM,KAAc;AACtB,0BAAc,aAAa,QAAQ;AACnC,4BAAgB,SAAS,MAAM,SAAS,OAAO;AAAA,UACjD;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,cAAc,aAAa;AAAA,UACpC;AAAA,UACA,IAAI,KAAK,KAAc;AACrB,0BAAc,aAAa,OAAO;AAClC,4BAAgB,QAAQ,MAAM,SAAS,OAAO;AAAA,UAChD;AAAA,UACA,IAAI,OAAO;AACT,mBAAO,cAAc,aAAa;AAAA,UACpC;AAAA,UACA,IAAI,KAAK,KAAc;AACrB,0BAAc,aAAa,OAAO;AAClC,4BAAgB,QAAQ,MAAM,SAAS,OAAO;AAAA,UAChD;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AAEL,UAAI,CAAC,aAAa,uBAAuB,cAAc,GAAG;AACxD,qBAAa,uBAAuB;AAAA,UAClC,CAAC,cAAc,GAAG;AAAA,UAClB,GAAG,cAAc;AAAA,UACjB,GAAG,aAAa;AAAA,QAClB;AACA,sBAAc,eAAe,aAAa;AAC1C,YAAI,cAAc,SAAS;AACzB,kBAAQ,MAAM,GAAG,cAAc,sBAAsB,sBAAsB,IAAI,aAAa,oBAAoB;AAAA,QAClH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAgBA,IAAI,YAAY;AACd,WAAO,KAAK,UAAU,cAAc,aAAa;AAAA,EACnD;AAAA,EAEA,IAAI,UAAU;AACZ,WAAO,KAAK,aAAa,cAAc,aAAa;AAAA,EACtD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,aAAa,cAAc,aAAa;AAAA,EACtD;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,aAAa,cAAc,aAAa;AAAA,EACtD;AAAA,EAEA,SAAS,MAAa;AACpB,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,OAAO,SAAS,cAAc,aAAa,OAAO,IAAI;AAAA,EAC7D;AAAA,EAEA,QAAQ,MAAa;AACnB,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,OAAO,QAAQ,cAAc,aAAa,MAAM,IAAI;AAAA,EAC3D;AAAA,EAEA,QAAQ,MAAa;AACnB,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,OAAO,QAAQ,cAAc,aAAa,MAAM,IAAI;AAAA,EAC3D;AAAA;AAAA;AAAA,EAIA,SAAS,MAAa;AACpB,SAAK,OAAO,SAAS,cAAc,aAAa,OAAO,IAAI;AAAA,EAC7D;AAAA,EAEA,OAAO,OAA4C,QAAgB,MAAa;AAC9E,YAAQ,KAAK,EAAE,KAAK,KAAK,SAAS,IAAI,QAAQ,GAAG,IAAI;AAAA,EACvD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/src/utils/FrameLoop.d.ts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
+
import { type ListenerFuncType } from '@spearwolf/eventize';
|
|
2
|
+
/**
|
|
3
|
+
* The payload of every {@link FrameLoop.OnFrame} event. All times are in seconds.
|
|
4
|
+
*/
|
|
5
|
+
export interface FrameData {
|
|
6
|
+
/** the timestamp of this frame */
|
|
7
|
+
now: number;
|
|
8
|
+
/** the timestamp of the frame before this one — equal to {@link FrameData.now} on the first frame */
|
|
9
|
+
lastNow: number;
|
|
10
|
+
/** the number of this frame, counted from one at the start of every run */
|
|
11
|
+
frameNo: number;
|
|
12
|
+
/** the time elapsed since the frame before this one — zero on the first frame */
|
|
13
|
+
deltaTime: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A loop that emits a frame to all of its targets as long as at least one of them listens.
|
|
17
|
+
*
|
|
18
|
+
* While no target listens, the loop asks for no frames at all.
|
|
19
|
+
*
|
|
20
|
+
* A target that throws costs itself its frame and nothing else: the frame still reaches every
|
|
21
|
+
* other target, and the loop asks for the frame behind it. The failure is reported through
|
|
22
|
+
* `console.warn` and reaches no caller -- see `#onFrame()` for why this loop answers a failing
|
|
23
|
+
* target that way and not through the `ConsoleLogger` the rest of this package reports through.
|
|
24
|
+
*/
|
|
1
25
|
export declare class FrameLoop {
|
|
2
26
|
#private;
|
|
3
27
|
static OnFrame: symbol;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The loop that every caller shares. It is created on first read and answers the same instance
|
|
30
|
+
* afterwards. It has no cap on the frame rate.
|
|
31
|
+
*/
|
|
32
|
+
static get(): FrameLoop;
|
|
33
|
+
constructor(maxFps?: number);
|
|
34
|
+
/** The upper bound on the frame rate. Zero means: no bound. */
|
|
35
|
+
get maxFps(): number;
|
|
36
|
+
set maxFps(fps: number);
|
|
37
|
+
/**
|
|
38
|
+
* How many targets listen for frames.
|
|
39
|
+
*
|
|
40
|
+
* OnFrame is the only event this object knows, so the total number of subscriptions is the
|
|
41
|
+
* number of frame targets.
|
|
42
|
+
*/
|
|
43
|
+
get subscriptionCount(): number;
|
|
44
|
+
start(target: object | ListenerFuncType): (() => void) | undefined;
|
|
45
|
+
stop(target: object | ListenerFuncType): void;
|
|
7
46
|
}
|
|
8
47
|
//# sourceMappingURL=FrameLoop.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FrameLoop.d.ts","sourceRoot":"","sources":["../../../src/utils/FrameLoop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FrameLoop.d.ts","sourceRoot":"","sources":["../../../src/utils/FrameLoop.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,gBAAgB,EAAU,MAAM,qBAAqB,CAAC;AAI7G;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,qGAAqG;IACrG,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,qBAAa,SAAS;;IACpB,MAAM,CAAC,OAAO,SAAqB;IAEnC;;;OAGG;IACH,MAAM,CAAC,GAAG,IAAI,SAAS,CAGtB;IAsBD,YAAY,MAAM,SAAI,EAGrB;IAED,+DAA+D;IAC/D,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,EAErB;IAED;;;;;OAKG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,4BAUtC;IAED,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,QAYrC;CA8DF"}
|
package/src/utils/FrameLoop.js
CHANGED
|
@@ -1,41 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitSafe, eventize, getSubscriptionCount, off, on } from "@spearwolf/eventize";
|
|
2
2
|
let gUniqInstance = null;
|
|
3
3
|
class FrameLoop {
|
|
4
4
|
static {
|
|
5
5
|
this.OnFrame = /* @__PURE__ */ Symbol("onFrame");
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The loop that every caller shares. It is created on first read and answers the same instance
|
|
9
|
+
* afterwards. It has no cap on the frame rate.
|
|
10
|
+
*/
|
|
11
|
+
static get() {
|
|
12
|
+
gUniqInstance ??= new FrameLoop();
|
|
13
|
+
return gUniqInstance;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The share of the nominal frame time after which the next frame is due.
|
|
17
|
+
*
|
|
18
|
+
* Animation frames do not arrive at the nominal distance, and the browsers differ in how coarsely
|
|
19
|
+
* they round the timestamps they hand out. With `D` for the distance between real frames and `j`
|
|
20
|
+
* for the jitter on top of it, the factor has to sit between two bounds:
|
|
21
|
+
*
|
|
22
|
+
* - a cap at or above the refresh rate must lose no frame: `factor <= 1 - j / D`
|
|
23
|
+
* - a cap at half the refresh rate must drop every second frame: `factor > 0.5 + j / (2 * D)`
|
|
24
|
+
*
|
|
25
|
+
* At 60 Hz with the coarsest rounding measured — around 0.6 ms of jitter on a 16.67 ms
|
|
26
|
+
* distance — that leaves the range 0.52 to 0.96, and this value keeps its distance to both ends.
|
|
27
|
+
*/
|
|
28
|
+
static #DueFactor = 0.75;
|
|
7
29
|
#rafID = 0;
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
#maxFps = 0;
|
|
31
|
+
#frameNo = 0;
|
|
32
|
+
#lastNow = void 0;
|
|
33
|
+
constructor(maxFps = 0) {
|
|
11
34
|
eventize(this);
|
|
12
|
-
|
|
35
|
+
this.maxFps = maxFps;
|
|
36
|
+
}
|
|
37
|
+
/** The upper bound on the frame rate. Zero means: no bound. */
|
|
38
|
+
get maxFps() {
|
|
39
|
+
return this.#maxFps;
|
|
40
|
+
}
|
|
41
|
+
set maxFps(fps) {
|
|
42
|
+
this.#maxFps = Number.isFinite(fps) && fps > 0 ? fps : 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* How many targets listen for frames.
|
|
46
|
+
*
|
|
47
|
+
* OnFrame is the only event this object knows, so the total number of subscriptions is the
|
|
48
|
+
* number of frame targets.
|
|
49
|
+
*/
|
|
50
|
+
get subscriptionCount() {
|
|
51
|
+
return getSubscriptionCount(this);
|
|
13
52
|
}
|
|
14
53
|
start(target) {
|
|
15
54
|
if (target == null) return;
|
|
16
|
-
if (getSubscriptionCount(this) === 0) {
|
|
17
|
-
this.#requestAnimationFrame();
|
|
18
|
-
}
|
|
19
55
|
on(this, FrameLoop.OnFrame, target);
|
|
20
|
-
this.#
|
|
56
|
+
this.#requestAnimationFrame();
|
|
21
57
|
return () => {
|
|
22
58
|
this.stop(target);
|
|
23
59
|
};
|
|
24
60
|
}
|
|
25
61
|
stop(target) {
|
|
62
|
+
if (target == null) return;
|
|
26
63
|
off(this, FrameLoop.OnFrame, target);
|
|
27
|
-
if (
|
|
64
|
+
if (this.subscriptionCount === 0) {
|
|
28
65
|
this.#cancelAnimationFrame();
|
|
66
|
+
this.#lastNow = void 0;
|
|
67
|
+
this.#frameNo = 0;
|
|
29
68
|
}
|
|
30
69
|
}
|
|
31
70
|
#onFrame = (now) => {
|
|
32
|
-
|
|
33
|
-
this.#
|
|
71
|
+
this.#rafID = 0;
|
|
72
|
+
if (this.#isDue(now)) {
|
|
73
|
+
const lastNow = this.#lastNow ?? now;
|
|
74
|
+
this.#lastNow = now;
|
|
75
|
+
this.#frameNo += 1;
|
|
76
|
+
const frameData = {
|
|
77
|
+
now: now / 1e3,
|
|
78
|
+
lastNow: lastNow / 1e3,
|
|
79
|
+
frameNo: this.#frameNo,
|
|
80
|
+
deltaTime: (now - lastNow) / 1e3
|
|
81
|
+
};
|
|
82
|
+
emitSafe(this, FrameLoop.OnFrame, frameData);
|
|
83
|
+
}
|
|
84
|
+
if (this.subscriptionCount > 0) {
|
|
85
|
+
this.#requestAnimationFrame();
|
|
86
|
+
}
|
|
34
87
|
};
|
|
88
|
+
#isDue(now) {
|
|
89
|
+
if (this.#maxFps === 0 || this.#lastNow === void 0) return true;
|
|
90
|
+
return now - this.#lastNow >= FrameLoop.#DueFactor * (1e3 / this.#maxFps);
|
|
91
|
+
}
|
|
35
92
|
#requestAnimationFrame() {
|
|
93
|
+
if (this.#rafID !== 0) return;
|
|
36
94
|
this.#rafID = requestAnimationFrame(this.#onFrame);
|
|
37
95
|
}
|
|
38
96
|
#cancelAnimationFrame() {
|
|
97
|
+
if (this.#rafID === 0) return;
|
|
39
98
|
cancelAnimationFrame(this.#rafID);
|
|
40
99
|
this.#rafID = 0;
|
|
41
100
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/FrameLoop.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,SAAQ,
|
|
4
|
+
"sourcesContent": ["import {emitSafe, eventize, getSubscriptionCount, type ListenerFuncType, off, on} from '@spearwolf/eventize';\n\nlet gUniqInstance: FrameLoop | null = null;\n\n/**\n * The payload of every {@link FrameLoop.OnFrame} event. All times are in seconds.\n */\nexport interface FrameData {\n /** the timestamp of this frame */\n now: number;\n /** the timestamp of the frame before this one \u2014 equal to {@link FrameData.now} on the first frame */\n lastNow: number;\n /** the number of this frame, counted from one at the start of every run */\n frameNo: number;\n /** the time elapsed since the frame before this one \u2014 zero on the first frame */\n deltaTime: number;\n}\n\n/**\n * A loop that emits a frame to all of its targets as long as at least one of them listens.\n *\n * While no target listens, the loop asks for no frames at all.\n *\n * A target that throws costs itself its frame and nothing else: the frame still reaches every\n * other target, and the loop asks for the frame behind it. The failure is reported through\n * `console.warn` and reaches no caller -- see `#onFrame()` for why this loop answers a failing\n * target that way and not through the `ConsoleLogger` the rest of this package reports through.\n */\nexport class FrameLoop {\n static OnFrame = Symbol('onFrame');\n\n /**\n * The loop that every caller shares. It is created on first read and answers the same instance\n * afterwards. It has no cap on the frame rate.\n */\n static get(): FrameLoop {\n gUniqInstance ??= new FrameLoop();\n return gUniqInstance;\n }\n\n /**\n * The share of the nominal frame time after which the next frame is due.\n *\n * Animation frames do not arrive at the nominal distance, and the browsers differ in how coarsely\n * they round the timestamps they hand out. With `D` for the distance between real frames and `j`\n * for the jitter on top of it, the factor has to sit between two bounds:\n *\n * - a cap at or above the refresh rate must lose no frame: `factor <= 1 - j / D`\n * - a cap at half the refresh rate must drop every second frame: `factor > 0.5 + j / (2 * D)`\n *\n * At 60 Hz with the coarsest rounding measured \u2014 around 0.6 ms of jitter on a 16.67 ms\n * distance \u2014 that leaves the range 0.52 to 0.96, and this value keeps its distance to both ends.\n */\n static #DueFactor = 0.75;\n\n #rafID = 0;\n #maxFps = 0;\n #frameNo = 0;\n #lastNow: number | undefined = undefined;\n\n constructor(maxFps = 0) {\n eventize(this);\n this.maxFps = maxFps;\n }\n\n /** The upper bound on the frame rate. Zero means: no bound. */\n get maxFps(): number {\n return this.#maxFps;\n }\n\n set maxFps(fps: number) {\n this.#maxFps = Number.isFinite(fps) && fps > 0 ? fps : 0;\n }\n\n /**\n * How many targets listen for frames.\n *\n * OnFrame is the only event this object knows, so the total number of subscriptions is the\n * number of frame targets.\n */\n get subscriptionCount(): number {\n return getSubscriptionCount(this);\n }\n\n start(target: object | ListenerFuncType) {\n if (target == null) return;\n\n on(this as FrameLoop, FrameLoop.OnFrame, target);\n\n this.#requestAnimationFrame();\n\n return () => {\n this.stop(target);\n };\n }\n\n stop(target: object | ListenerFuncType) {\n if (target == null) return;\n\n off(this, FrameLoop.OnFrame, target);\n\n if (this.subscriptionCount === 0) {\n this.#cancelAnimationFrame();\n // the next target to arrive starts a fresh run of frames: its first frame is frame one and\n // reports no delta, rather than the whole length of the pause\n this.#lastNow = undefined;\n this.#frameNo = 0;\n }\n }\n\n #onFrame = (now: number) => {\n // the frame this id stands for has fired, nothing is pending any more \u2014 a target that leaves\n // during the emit below must be able to see that\n this.#rafID = 0;\n\n if (this.#isDue(now)) {\n const lastNow = this.#lastNow ?? now;\n\n this.#lastNow = now;\n this.#frameNo += 1;\n\n const frameData: FrameData = {\n now: now / 1000,\n lastNow: lastNow / 1000,\n frameNo: this.#frameNo,\n deltaTime: (now - lastNow) / 1000,\n };\n\n // `emitSafe()` rather than `emit()`, and the reason is the singleton above: one loop serves\n // every target in the realm, so a target that throws may cost neither the targets behind it\n // their frame nor the loop its next one. The plain dispatch would do both -- it ends at the\n // first listener that throws and hands the error back to the `requestAnimationFrame()`\n // callback, past the line below that asks for the frame after this one. `#rafID` is already\n // back at 0 at that point, so nothing would ever ask again and the loop would stand still\n // for the whole page without a word.\n //\n // What a target loses in exchange is the report: `emitSafe()` writes to `console.warn` and\n // hands out no error. That is the right trade here. `FrameLoop` is a singleton with no\n // `ConsoleLogger` of its own, and a frame target that throws does so at the frame rate --\n // whichever channel carries it, the first report says everything the hundredth does.\n emitSafe(this as FrameLoop, FrameLoop.OnFrame, frameData);\n }\n\n if (this.subscriptionCount > 0) {\n // a skipped frame arms again just like an emitted one, otherwise the cap would bring the\n // loop to a halt after the first frame it holds back\n this.#requestAnimationFrame();\n }\n };\n\n #isDue(now: number): boolean {\n if (this.#maxFps === 0 || this.#lastNow === undefined) return true;\n\n return now - this.#lastNow >= FrameLoop.#DueFactor * (1000 / this.#maxFps);\n }\n\n #requestAnimationFrame() {\n // idempotent: two pending frames would mean two loops, and only the second of them could ever\n // be cancelled again\n if (this.#rafID !== 0) return;\n\n this.#rafID = requestAnimationFrame(this.#onFrame);\n }\n\n #cancelAnimationFrame() {\n if (this.#rafID === 0) return;\n\n cancelAnimationFrame(this.#rafID);\n this.#rafID = 0;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAQ,UAAU,UAAU,sBAA6C,KAAK,UAAS;AAEvF,IAAI,gBAAkC;AA0B/B,MAAM,UAAU;AAAA,EACrB;AAAA,SAAO,UAAU,uBAAO,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjC,OAAO,MAAiB;AACtB,sBAAkB,IAAI,UAAU;AAChC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAO,aAAa;AAAA,EAEpB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAA+B;AAAA,EAE/B,YAAY,SAAS,GAAG;AACtB,aAAS,IAAI;AACb,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO,KAAa;AACtB,SAAK,UAAU,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,oBAA4B;AAC9B,WAAO,qBAAqB,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,QAAmC;AACvC,QAAI,UAAU,KAAM;AAEpB,OAAG,MAAmB,UAAU,SAAS,MAAM;AAE/C,SAAK,uBAAuB;AAE5B,WAAO,MAAM;AACX,WAAK,KAAK,MAAM;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,KAAK,QAAmC;AACtC,QAAI,UAAU,KAAM;AAEpB,QAAI,MAAM,UAAU,SAAS,MAAM;AAEnC,QAAI,KAAK,sBAAsB,GAAG;AAChC,WAAK,sBAAsB;AAG3B,WAAK,WAAW;AAChB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,WAAW,CAAC,QAAgB;AAG1B,SAAK,SAAS;AAEd,QAAI,KAAK,OAAO,GAAG,GAAG;AACpB,YAAM,UAAU,KAAK,YAAY;AAEjC,WAAK,WAAW;AAChB,WAAK,YAAY;AAEjB,YAAM,YAAuB;AAAA,QAC3B,KAAK,MAAM;AAAA,QACX,SAAS,UAAU;AAAA,QACnB,SAAS,KAAK;AAAA,QACd,YAAY,MAAM,WAAW;AAAA,MAC/B;AAcA,eAAS,MAAmB,UAAU,SAAS,SAAS;AAAA,IAC1D;AAEA,QAAI,KAAK,oBAAoB,GAAG;AAG9B,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,OAAO,KAAsB;AAC3B,QAAI,KAAK,YAAY,KAAK,KAAK,aAAa,OAAW,QAAO;AAE9D,WAAO,MAAM,KAAK,YAAY,UAAU,cAAc,MAAO,KAAK;AAAA,EACpE;AAAA,EAEA,yBAAyB;AAGvB,QAAI,KAAK,WAAW,EAAG;AAEvB,SAAK,SAAS,sBAAsB,KAAK,QAAQ;AAAA,EACnD;AAAA,EAEA,wBAAwB;AACtB,QAAI,KAAK,WAAW,EAAG;AAEvB,yBAAqB,KAAK,MAAM;AAChC,SAAK,SAAS;AAAA,EAChB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arguments of {@link MicrotaskCollector.add}: a collector without a payload is asked for the
|
|
3
|
+
* key alone, one that carries a payload is asked for both.
|
|
4
|
+
*/
|
|
5
|
+
type AddArgs<K, V> = undefined extends V ? [key: K, value?: V] : [key: K, value: V];
|
|
6
|
+
/**
|
|
7
|
+
* Collects what is written in one task and hands it over a microtask later, in one batch.
|
|
8
|
+
*
|
|
9
|
+
* A second value for the same key replaces the one waiting, so a key written twice in a task
|
|
10
|
+
* reaches the delivery once, with the value that stood at the end of it.
|
|
11
|
+
*
|
|
12
|
+
* Two rules live here so that no site has to decide them again:
|
|
13
|
+
*
|
|
14
|
+
* The batch is taken out and emptied **before** the first delivery. A delivery can write to this
|
|
15
|
+
* collector -- a reader of a context value writes a context of its own, an entity that arrives
|
|
16
|
+
* while a round goes out asks for a round -- and that write belongs to the round behind this one
|
|
17
|
+
* rather than to the batch being walked. Emptying afterwards would also lose the batch to a
|
|
18
|
+
* delivery that throws: the flag is already down by then, so the next write queues a fresh
|
|
19
|
+
* microtask and hands the stale entries over a second time.
|
|
20
|
+
*
|
|
21
|
+
* Every delivery a site makes runs under `runGuarded()`. Entries that arrived in the same task are
|
|
22
|
+
* independent of one another, and one that fails costs itself and nothing behind it -- the promise
|
|
23
|
+
* `docs/api-reference.md` already spells out for context values. What counts as one delivery is
|
|
24
|
+
* the site's to say: `Entity` delivers per entry, `ComponentContext` groups its senders
|
|
25
|
+
* into rounds first and delivers per round.
|
|
26
|
+
*
|
|
27
|
+
* For a booking that has to be callable off again, see `DeferredTeardown`; for a single
|
|
28
|
+
* deferred action without a batch, `MicrotaskGate`.
|
|
29
|
+
*/
|
|
30
|
+
export declare class MicrotaskCollector<K, V = undefined> {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(deliver: (entries: Map<K, V>) => void);
|
|
33
|
+
/** Put an entry into the batch and make sure someone comes to fetch it. */
|
|
34
|
+
add(...[key, value]: AddArgs<K, V>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Hand the batch over now.
|
|
37
|
+
*
|
|
38
|
+
* For a caller that needs the batch delivered at a point of its own choosing rather than
|
|
39
|
+
* whenever the microtask comes round. A queued microtask that finds nothing left returns without
|
|
40
|
+
* a delivery, so an early flush costs nothing but the ordering it was asked for.
|
|
41
|
+
*/
|
|
42
|
+
flush(): void;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=MicrotaskCollector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MicrotaskCollector.d.ts","sourceRoot":"","sources":["../../../src/utils/MicrotaskCollector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,SAAS,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS;;IAM9C,YAAY,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAEhD;IAED,2EAA2E;IAC3E,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAOxC;IAED;;;;;;OAMG;IACH,KAAK,IAAI,IAAI,CASZ;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class MicrotaskCollector {
|
|
2
|
+
#entries = /* @__PURE__ */ new Map();
|
|
3
|
+
#scheduled = false;
|
|
4
|
+
#deliver;
|
|
5
|
+
constructor(deliver) {
|
|
6
|
+
this.#deliver = deliver;
|
|
7
|
+
}
|
|
8
|
+
/** Put an entry into the batch and make sure someone comes to fetch it. */
|
|
9
|
+
add(...[key, value]) {
|
|
10
|
+
this.#entries.set(key, value);
|
|
11
|
+
if (this.#scheduled) return;
|
|
12
|
+
this.#scheduled = true;
|
|
13
|
+
queueMicrotask(() => this.flush());
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hand the batch over now.
|
|
17
|
+
*
|
|
18
|
+
* For a caller that needs the batch delivered at a point of its own choosing rather than
|
|
19
|
+
* whenever the microtask comes round. A queued microtask that finds nothing left returns without
|
|
20
|
+
* a delivery, so an early flush costs nothing but the ordering it was asked for.
|
|
21
|
+
*/
|
|
22
|
+
flush() {
|
|
23
|
+
this.#scheduled = false;
|
|
24
|
+
if (this.#entries.size === 0) return;
|
|
25
|
+
const entries = this.#entries;
|
|
26
|
+
this.#entries = /* @__PURE__ */ new Map();
|
|
27
|
+
this.#deliver(entries);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
MicrotaskCollector
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=MicrotaskCollector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/MicrotaskCollector.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The arguments of {@link MicrotaskCollector.add}: a collector without a payload is asked for the\n * key alone, one that carries a payload is asked for both.\n */\ntype AddArgs<K, V> = undefined extends V ? [key: K, value?: V] : [key: K, value: V];\n\n/**\n * Collects what is written in one task and hands it over a microtask later, in one batch.\n *\n * A second value for the same key replaces the one waiting, so a key written twice in a task\n * reaches the delivery once, with the value that stood at the end of it.\n *\n * Two rules live here so that no site has to decide them again:\n *\n * The batch is taken out and emptied **before** the first delivery. A delivery can write to this\n * collector -- a reader of a context value writes a context of its own, an entity that arrives\n * while a round goes out asks for a round -- and that write belongs to the round behind this one\n * rather than to the batch being walked. Emptying afterwards would also lose the batch to a\n * delivery that throws: the flag is already down by then, so the next write queues a fresh\n * microtask and hands the stale entries over a second time.\n *\n * Every delivery a site makes runs under `runGuarded()`. Entries that arrived in the same task are\n * independent of one another, and one that fails costs itself and nothing behind it -- the promise\n * `docs/api-reference.md` already spells out for context values. What counts as one delivery is\n * the site's to say: `Entity` delivers per entry, `ComponentContext` groups its senders\n * into rounds first and delivers per round.\n *\n * For a booking that has to be callable off again, see `DeferredTeardown`; for a single\n * deferred action without a batch, `MicrotaskGate`.\n */\nexport class MicrotaskCollector<K, V = undefined> {\n #entries = new Map<K, V>();\n #scheduled = false;\n\n readonly #deliver: (entries: Map<K, V>) => void;\n\n constructor(deliver: (entries: Map<K, V>) => void) {\n this.#deliver = deliver;\n }\n\n /** Put an entry into the batch and make sure someone comes to fetch it. */\n add(...[key, value]: AddArgs<K, V>): void {\n this.#entries.set(key, value as V);\n\n if (this.#scheduled) return;\n this.#scheduled = true;\n\n queueMicrotask(() => this.flush());\n }\n\n /**\n * Hand the batch over now.\n *\n * For a caller that needs the batch delivered at a point of its own choosing rather than\n * whenever the microtask comes round. A queued microtask that finds nothing left returns without\n * a delivery, so an early flush costs nothing but the ordering it was asked for.\n */\n flush(): void {\n this.#scheduled = false;\n\n if (this.#entries.size === 0) return;\n\n const entries = this.#entries;\n this.#entries = new Map();\n\n this.#deliver(entries);\n }\n}\n"],
|
|
5
|
+
"mappings": "AA8BO,MAAM,mBAAqC;AAAA,EAChD,WAAW,oBAAI,IAAU;AAAA,EACzB,aAAa;AAAA,EAEJ;AAAA,EAET,YAAY,SAAuC;AACjD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,OAAO,CAAC,KAAK,KAAK,GAAwB;AACxC,SAAK,SAAS,IAAI,KAAK,KAAU;AAEjC,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,mBAAe,MAAM,KAAK,MAAM,CAAC;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAc;AACZ,SAAK,aAAa;AAElB,QAAI,KAAK,SAAS,SAAS,EAAG;AAE9B,UAAM,UAAU,KAAK;AACrB,SAAK,WAAW,oBAAI,IAAI;AAExB,SAAK,SAAS,OAAO;AAAA,EACvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one action a microtask after it was asked for, however often it is asked for.
|
|
3
|
+
*
|
|
4
|
+
* The flag falls before the action rather than behind it: an action that asks again books a fresh
|
|
5
|
+
* round instead of writing into a flag the line behind it would clear.
|
|
6
|
+
*
|
|
7
|
+
* No batch and no guard: there is one action, so there are no neighbours a failure could cost --
|
|
8
|
+
* what it throws goes where an uncaught microtask goes. A gate that carries entries is
|
|
9
|
+
* `MicrotaskCollector`, a booking that can be called off again is `DeferredTeardown`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MicrotaskGate {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(run: () => void);
|
|
14
|
+
/** Ask for the action. */
|
|
15
|
+
schedule(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=MicrotaskGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MicrotaskGate.d.ts","sourceRoot":"","sources":["../../../src/utils/MicrotaskGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,aAAa;;IAKxB,YAAY,GAAG,EAAE,MAAM,IAAI,EAE1B;IAED,0BAA0B;IAC1B,QAAQ,IAAI,IAAI,CAQf;CACF"}
|