@realitycollective/service-framework-iwsdk 1.0.2-preview.0 → 1.0.2-preview.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
|
@@ -10,13 +10,25 @@ A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that work
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- `FrameInfo.frame`, the binding's frame count from 1: the same number the `renderTick` context carries for that frame. The native host's frames, the IWSDK bridge and the three.js and Babylon.js owned loops all set it from the one counter they already kept for `renderTick`, and `emitFrame` takes it as an optional third argument, so a client that needs a frame number reads one clock instead of counting frames again. Additive: a frame an app emits itself without one carries none.
|
|
14
|
+
- `@realitycollective/service-framework-native` - `NativeSessionInfo.features`, the WebXR feature names the session enabled (`"hand-tracking"`, `"plane-detection"` and the rest), which the capabilities read as the IWSDK adapter reads `enabledFeatures`; `NativeHost.input`, the `input` slice's `onSourcesChanged` signal and source kinds; `NativeHost.onSessionRefused`, the app's way to say it will not start a requested session; and the `manager` option, the service manager whose focus and pause signals follow the session.
|
|
13
15
|
- `@realitycollective/service-framework-native`, the binding for a native XR app that embeds a JavaScript engine such as Hermes. The app owns the session, the frame loop, rendering and physics, and installs one host object, `globalThis.__rcHost`, before it evaluates the bundle. Everything crosses that object as plain values. `NativeRuntimeAdapter` publishes the app's frames and session through the same `RuntimeAdapter` seam as the other bindings, with the session facet, and passes `runtimeAdapterContractCases()`. It derives capabilities from the OpenXR session state, the enabled extensions, system hand tracking support and the blend mode. It keeps the same sticky `setCapabilities` override, `clearCapabilityOverrides()` and `refreshCapabilities()` the other adapters have. Given a `scheduler`, it emits `renderTick` with `source: "native"`. `createNativeHostIO()` returns a `HostIO` over the host's `io` slice. The host object's shape was proven on a Quest 3 and in the visionOS Simulator by the team that built the first native host. The package README documents it, including the slices the other families' native packages read.
|
|
14
16
|
- `HostIO` in the core, with `createWebHostIO()` over `fetch` and `DecompressionStream`. A service that reads assets takes a `HostIO` and never reaches for a global, so the same code runs on the web and on a native host. The globals are read when a method is called, so a host without them fails only if it uses them. Helpers that work on both: `fetchText`, `fetchJson`, `fetchMaybeGzipped` (gunzips only bytes that carry the gzip magic number, since hosts differ on whether they undo `Content-Encoding`), `decodeUtf8` (no `TextDecoder`, which Hermes lacks), `isGzip` and `concatBytes`. This is transport only. Decoding images, audio and models stays with the engine.
|
|
15
17
|
- A "Host requirements" section in the core package README. It names every global the core expects from its host beyond the ECMAScript language: `setTimeout` and `clearTimeout` for `resolveAsync`, `waitUntilInitialized` and the mock adapter's session request, and `setInterval` and `clearInterval` for a `TimerScheduler` built without injected timers. It names the two globals used only when present, `AbortController` (with a built-in fallback) and the `fetch` and `DecompressionStream` behind `createWebHostIO()`, and states that nothing else is assumed. A host that embeds a bare engine, such as Hermes in a native app, can now supply exactly that list. `test/host-requirements.test.ts` checks the table against the source, so a new host global fails the suite until it is listed.
|
|
16
18
|
- The capability override rule, stated in the README and on the `RuntimeAdapter` doc comment. Every adapter exposes `setCapabilities(partial)` as a sticky override over what it derives, and an adapter that derives also exposes `clearCapabilityOverrides()`. Every in-repo adapter already did, and every conformance driver already wired its `capabilities` hook to it. The rule was never written down, so an adapter written elsewhere that only derived from its host failed the capability-change case. The method stays off the `RuntimeAdapter` interface, because services must not call it. The same test checks that every in-repo adapter still exposes it.
|
|
17
|
-
|
|
18
19
|
- `hostIOContractCases()` and `renderTickContractCases()`, shared conformance suites shipped as data, like `runtimeAdapterContractCases()`. Clients only ever use the core: a service reads assets through `HostIO` and subscribes to `renderTick` on the core scheduler, and never asks which platform is underneath. That only holds if every platform behaves the same way, so every platform now runs the same checks. `hostIOContractCases()` runs against `createWebHostIO()` and `createNativeHostIO()`: served bytes come back exactly, a missing resource rejects, gunzip restores and rejects non-gzip, and the core helpers read plain and gzipped JSON alike. It needs no `CompressionStream` or `TextEncoder`, so a native app can run it on device. `renderTickContractCases()` runs against `ThreeRenderLoopBridge`, `WebXRRuntimeAdapter`, `BabylonRenderLoopBridge`, `BabylonRuntimeAdapter`, the IWSDK service bridge system and `NativeRuntimeAdapter`: one tick per frame, frames counted from 1, a stable source, and `timestamp` and `deltaTime` in milliseconds.
|
|
19
20
|
- `LifecycleContext` now documents its units: `timestamp` and `deltaTime` in milliseconds, `frame` counted from 1, and `source` naming the binding.
|
|
21
|
+
- `@realitycollective/service-framework-three` and `@realitycollective/service-framework-babylon` - the `manager` option on `WebXRRuntimeAdapter` and `BabylonRuntimeAdapter`, the service manager whose focus and pause signals follow a live session's visibility, as the `manager` option already does on the native adapter. `WebXRFocusSink` and `BabylonFocusSink` name the two methods it needs.
|
|
22
|
+
- `WebXRRuntimeAdapter.tick(timestampMs)` and `BabylonRuntimeAdapter.tick()`, now public. Each is the whole frame step `start()` already bound to the owned loop - the visibility gate, the focus/pause signals, the one frame count, `emitFrame` and `renderTick` - pulled out from under `start()` so a host that owns its OWN loop and will never call `start()` (an XR Blocks app, whose `Core` calls `setAnimationLoop` itself) can drive the exact same step by hand from whatever per-frame hook that host provides, rather than reimplementing the gate. The owned loop and a hand-driven app now share one code path; before this, a hand-driven three.js app could only reach the ungated `emitFrame`, so it had no way to reach the focus gate at all.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Behaviour change for native apps:** `@realitycollective/service-framework-native` now bridges the native frame loop as IWSDK's `ServiceBridgeSystem` bridges IWSDK's. Services tick only while the session is FOCUSED: a host frame outside focus reaches no frame listener and no `renderTick`, and does not advance the frame count. Given the new `manager` option, the adapter emits `emitFocusChange(focused)` and `emitPauseChange({ paused: !focused })` on every change of focus. Before this a native app ticked its services while the headset was off the face.
|
|
27
|
+
- **Behaviour change for three.js and Babylon.js apps with a live XR session:** `@realitycollective/service-framework-three` and `@realitycollective/service-framework-babylon` now gate their owned loop on the session's own visibility while one is live: a tick that is not `"visible"` reaches no frame listener and no `renderTick`, and does not advance the frame count, exactly as the IWSDK bridge and the native adapter gate on focus. Given the new `manager` option, the adapter emits `emitFocusChange(focused)` and `emitPauseChange({ paused: !focused })` on every change. Unlike IWSDK and native, both packages also serve a desktop page with no session at all, and gating never applies there: with no session the loop ticks exactly as it did before this existed, and a session ending while paused restores focus at once. Before this, a three.js or Babylon app kept rendering into a hidden headset.
|
|
28
|
+
- `@realitycollective/service-framework-native` - a session in the OpenXR `ready` state is live: `immersive` is true there, as the session state already read `active`. For one phase of every session start the two used to disagree.
|
|
29
|
+
- `@realitycollective/service-framework-native` - capabilities re-derive on the `input` slice's source-change signal as well as on session changes, as the IWSDK adapter re-derives on `inputsourceschange`. `handTracking` is true for the `"hand-tracking"` feature, for `XR_EXT_hand_tracking` with system support, or while a hand is among the sources; `planeDetection` is true only for the `"plane-detection"` feature, where it was always false.
|
|
30
|
+
- `@realitycollective/service-framework-native` - a refused session request resolves with the app's reason, `"denied"`, `"unsupported"` or `"error"`, at once. Before this only `"timeout"` and `"error"` could be reached, so a dismissed permission prompt waited ten seconds and read as a timeout.
|
|
31
|
+
- `@realitycollective/service-framework-native` - every member of `NativeHost` and `NativeSessionInfo` states its units and meaning: milliseconds and seconds on `onFrame`, what counts as live, what the blend mode must reflect, and what each signal triggers.
|
|
20
32
|
|
|
21
33
|
### Fixed
|
|
22
34
|
|
package/dist/iwsdk-adapter.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
57
57
|
/** Drop every manual override and fall back to the derived capabilities. */
|
|
58
58
|
clearCapabilityOverrides(): void;
|
|
59
59
|
/** Called once per frame by the ECS bridge system. */
|
|
60
|
-
emitFrame(timestamp: number, delta: number): void;
|
|
60
|
+
emitFrame(timestamp: number, delta: number, frame?: number): void;
|
|
61
61
|
/**
|
|
62
62
|
* Release everything the adapter holds: the visibility subscription, the
|
|
63
63
|
* session's own listener, any in-flight session wait, the manual overrides
|
package/dist/iwsdk-adapter.js
CHANGED
|
@@ -127,9 +127,9 @@ export class IWSDKAdapter {
|
|
|
127
127
|
this.publishCapabilities();
|
|
128
128
|
}
|
|
129
129
|
/** Called once per frame by the ECS bridge system. */
|
|
130
|
-
emitFrame(timestamp, delta) {
|
|
131
|
-
const
|
|
132
|
-
this.frameListeners.forEach((listener) => listener(
|
|
130
|
+
emitFrame(timestamp, delta, frame) {
|
|
131
|
+
const info = frame === undefined ? { timestamp, delta } : { timestamp, delta, frame };
|
|
132
|
+
this.frameListeners.forEach((listener) => listener(info));
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* Release everything the adapter holds: the visibility subscription, the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so\n * frames are pushed in rather than pulled: the `ServiceBridgeSystem` (a normal\n * IWSDK system) calls {@link IWSDKAdapter.emitFrame} once per frame and the\n * adapter notifies subscribed services.\n *\n * Capabilities are derived from the live session on construction and on every\n * visibility change, through the core's shared `deriveCapabilities`, with a\n * manual override layer on top. Session lifecycle is\n * exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and\n * `exitXR` entry points.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type FrameInfo,\n type FrameListener,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { toIWSDKFeatures } from \"./iwsdk-features.js\";\nimport type {\n IWSDKSessionEventListener,\n IWSDKSessionLike,\n IWSDKWorldLike,\n} from \"./iwsdk-host.js\";\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\n/** How often the session waits re-check the world when the signal is silent. */\nconst SESSION_POLL_INTERVAL_MS = 50;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n \"non-immersive\",\n]);\n\n/**\n * IWSDK's visibility signal carries the WebXR visibility strings. Anything this\n * adapter does not recognise is reported as `\"hidden\"`, because treating an\n * unknown state as visible would keep game logic running when it should not.\n */\nfunction toSessionVisibility(value: unknown): SessionVisibility {\n if (typeof value === \"string\" && SESSION_VISIBILITY_VALUES.has(value)) {\n return value as SessionVisibility;\n }\n\n return \"hidden\";\n}\n\n/** A `request()` or `end()` waiting for the world to reach some state. */\ninterface PendingWait {\n readonly predicate: () => boolean;\n readonly resolve: (settled: boolean) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n readonly interval: ReturnType<typeof setInterval>;\n}\n\nexport class IWSDKAdapter implements RuntimeAdapter {\n private readonly frameListeners = new Set<FrameListener>();\n private readonly capabilitiesListeners = new Set<CapabilitiesListener>();\n private readonly stateListeners = new Set<SessionStateListener>();\n private readonly visibilityListeners = new Set<SessionVisibilityListener>();\n private readonly pendingWaits = new Set<PendingWait>();\n\n private derived: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private overrides: Partial<AdapterCapabilities> = {};\n private capabilities: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private sessionState: SessionState = \"none\";\n private unsubscribeVisibility: Unsubscribe | undefined;\n private boundSession: IWSDKSessionLike | null = null;\n\n /**\n * Pre-bound, because `removeEventListener` has to be handed the same function\n * `addEventListener` got.\n */\n private readonly onInputSourcesChange: IWSDKSessionEventListener = () => {\n this.refreshCapabilities();\n };\n\n /** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n request: (mode, options) => this.requestSession(mode, options),\n end: () => this.endSession(),\n onStateChange: (listener) => {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n },\n onVisibilityChange: (listener) => {\n this.visibilityListeners.add(listener);\n return () => {\n this.visibilityListeners.delete(listener);\n };\n },\n };\n\n public constructor(private readonly world: IWSDKWorldLike) {\n this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {\n this.handleVisibilityChange(value);\n });\n\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.capabilities = this.derived;\n this.sessionState = this.hasSession() ? \"active\" : \"none\";\n }\n\n public onFrame(listener: FrameListener): Unsubscribe {\n this.frameListeners.add(listener);\n return () => {\n this.frameListeners.delete(listener);\n };\n }\n\n public getCapabilities(): AdapterCapabilities {\n return this.capabilities;\n }\n\n public onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe {\n this.capabilitiesListeners.add(listener);\n return () => {\n this.capabilitiesListeners.delete(listener);\n };\n }\n\n /** The IWSDK `World` this adapter is bound to. */\n public getWorld(): IWSDKWorldLike {\n return this.world;\n }\n\n /**\n * Re-read the live session and publish any capability change. The adapter\n * does this itself whenever the visibility signal fires and whenever the\n * session's input sources change; call it directly on a host whose signal has\n * no `subscribe`, or after the host enables a feature mid-session.\n */\n public refreshCapabilities(): void {\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number): void {\n const frame: FrameInfo = { timestamp, delta };\n this.frameListeners.forEach((listener) => listener(frame));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, the\n * session's own listener, any in-flight session wait, the manual overrides\n * and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\n this.unbindSession();\n\n for (const wait of Array.from(this.pendingWaits)) {\n this.settleWait(wait, false);\n }\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private hasSession(): boolean {\n return Boolean(this.world.session);\n }\n\n /**\n * Follow one session's `inputsourceschange` event, so `handTracking` updates\n * when the player picks the controllers up or puts them down. IWSDK's world\n * carries a real `XRSession`, which raises the event; a host that reports a\n * session without listener methods is bound all the same and simply never\n * pushes, which is what `refreshCapabilities()` remains for.\n */\n private bindSession(session: IWSDKSessionLike | null): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.unbindSession();\n this.boundSession = session;\n session?.addEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n }\n\n private unbindSession(): void {\n this.boundSession?.removeEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n this.boundSession = null;\n }\n\n private publishCapabilities(): void {\n const next: AdapterCapabilities = { ...this.derived, ...this.overrides };\n const current = this.capabilities;\n\n if (\n next.immersive === current.immersive &&\n next.handTracking === current.handTracking &&\n next.planeDetection === current.planeDetection &&\n next.passthrough === current.passthrough &&\n next.environmentBlendMode === current.environmentBlendMode\n ) {\n return;\n }\n\n this.capabilities = next;\n this.capabilitiesListeners.forEach((listener) => listener(next));\n }\n\n private handleVisibilityChange(value: unknown): void {\n this.refreshCapabilities();\n this.syncSessionState();\n this.checkWaits();\n\n const visibility = toSessionVisibility(value);\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n /**\n * Follow the world when nothing local is mid-transition. `request()` and\n * `end()` own the state while they run, so the signal does not fight them.\n */\n private syncSessionState(): void {\n if (this.sessionState === \"requesting\" || this.sessionState === \"ending\") {\n return;\n }\n\n this.setSessionState(this.hasSession() ? \"active\" : \"none\");\n }\n\n private async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n return { ok: true };\n }\n\n const launch = this.world.launchXR;\n\n if (!launch) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n const { features } = toIWSDKFeatures(options);\n\n try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged. The\n // request's feature strings become IWSDK's structured flags; `launchXR`\n // merges what it is given over the world's `xrDefaults`, so a request\n // that names no features leaves the app's defaults alone.\n launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so\n * frames are pushed in rather than pulled: the `ServiceBridgeSystem` (a normal\n * IWSDK system) calls {@link IWSDKAdapter.emitFrame} once per frame and the\n * adapter notifies subscribed services.\n *\n * Capabilities are derived from the live session on construction and on every\n * visibility change, through the core's shared `deriveCapabilities`, with a\n * manual override layer on top. Session lifecycle is\n * exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and\n * `exitXR` entry points.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type FrameInfo,\n type FrameListener,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { toIWSDKFeatures } from \"./iwsdk-features.js\";\nimport type {\n IWSDKSessionEventListener,\n IWSDKSessionLike,\n IWSDKWorldLike,\n} from \"./iwsdk-host.js\";\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\n/** How often the session waits re-check the world when the signal is silent. */\nconst SESSION_POLL_INTERVAL_MS = 50;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n \"non-immersive\",\n]);\n\n/**\n * IWSDK's visibility signal carries the WebXR visibility strings. Anything this\n * adapter does not recognise is reported as `\"hidden\"`, because treating an\n * unknown state as visible would keep game logic running when it should not.\n */\nfunction toSessionVisibility(value: unknown): SessionVisibility {\n if (typeof value === \"string\" && SESSION_VISIBILITY_VALUES.has(value)) {\n return value as SessionVisibility;\n }\n\n return \"hidden\";\n}\n\n/** A `request()` or `end()` waiting for the world to reach some state. */\ninterface PendingWait {\n readonly predicate: () => boolean;\n readonly resolve: (settled: boolean) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n readonly interval: ReturnType<typeof setInterval>;\n}\n\nexport class IWSDKAdapter implements RuntimeAdapter {\n private readonly frameListeners = new Set<FrameListener>();\n private readonly capabilitiesListeners = new Set<CapabilitiesListener>();\n private readonly stateListeners = new Set<SessionStateListener>();\n private readonly visibilityListeners = new Set<SessionVisibilityListener>();\n private readonly pendingWaits = new Set<PendingWait>();\n\n private derived: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private overrides: Partial<AdapterCapabilities> = {};\n private capabilities: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private sessionState: SessionState = \"none\";\n private unsubscribeVisibility: Unsubscribe | undefined;\n private boundSession: IWSDKSessionLike | null = null;\n\n /**\n * Pre-bound, because `removeEventListener` has to be handed the same function\n * `addEventListener` got.\n */\n private readonly onInputSourcesChange: IWSDKSessionEventListener = () => {\n this.refreshCapabilities();\n };\n\n /** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n request: (mode, options) => this.requestSession(mode, options),\n end: () => this.endSession(),\n onStateChange: (listener) => {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n },\n onVisibilityChange: (listener) => {\n this.visibilityListeners.add(listener);\n return () => {\n this.visibilityListeners.delete(listener);\n };\n },\n };\n\n public constructor(private readonly world: IWSDKWorldLike) {\n this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {\n this.handleVisibilityChange(value);\n });\n\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.capabilities = this.derived;\n this.sessionState = this.hasSession() ? \"active\" : \"none\";\n }\n\n public onFrame(listener: FrameListener): Unsubscribe {\n this.frameListeners.add(listener);\n return () => {\n this.frameListeners.delete(listener);\n };\n }\n\n public getCapabilities(): AdapterCapabilities {\n return this.capabilities;\n }\n\n public onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe {\n this.capabilitiesListeners.add(listener);\n return () => {\n this.capabilitiesListeners.delete(listener);\n };\n }\n\n /** The IWSDK `World` this adapter is bound to. */\n public getWorld(): IWSDKWorldLike {\n return this.world;\n }\n\n /**\n * Re-read the live session and publish any capability change. The adapter\n * does this itself whenever the visibility signal fires and whenever the\n * session's input sources change; call it directly on a host whose signal has\n * no `subscribe`, or after the host enables a feature mid-session.\n */\n public refreshCapabilities(): void {\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number, frame?: number): void {\n const info: FrameInfo = frame === undefined ? { timestamp, delta } : { timestamp, delta, frame };\n this.frameListeners.forEach((listener) => listener(info));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, the\n * session's own listener, any in-flight session wait, the manual overrides\n * and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\n this.unbindSession();\n\n for (const wait of Array.from(this.pendingWaits)) {\n this.settleWait(wait, false);\n }\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private hasSession(): boolean {\n return Boolean(this.world.session);\n }\n\n /**\n * Follow one session's `inputsourceschange` event, so `handTracking` updates\n * when the player picks the controllers up or puts them down. IWSDK's world\n * carries a real `XRSession`, which raises the event; a host that reports a\n * session without listener methods is bound all the same and simply never\n * pushes, which is what `refreshCapabilities()` remains for.\n */\n private bindSession(session: IWSDKSessionLike | null): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.unbindSession();\n this.boundSession = session;\n session?.addEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n }\n\n private unbindSession(): void {\n this.boundSession?.removeEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n this.boundSession = null;\n }\n\n private publishCapabilities(): void {\n const next: AdapterCapabilities = { ...this.derived, ...this.overrides };\n const current = this.capabilities;\n\n if (\n next.immersive === current.immersive &&\n next.handTracking === current.handTracking &&\n next.planeDetection === current.planeDetection &&\n next.passthrough === current.passthrough &&\n next.environmentBlendMode === current.environmentBlendMode\n ) {\n return;\n }\n\n this.capabilities = next;\n this.capabilitiesListeners.forEach((listener) => listener(next));\n }\n\n private handleVisibilityChange(value: unknown): void {\n this.refreshCapabilities();\n this.syncSessionState();\n this.checkWaits();\n\n const visibility = toSessionVisibility(value);\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n /**\n * Follow the world when nothing local is mid-transition. `request()` and\n * `end()` own the state while they run, so the signal does not fight them.\n */\n private syncSessionState(): void {\n if (this.sessionState === \"requesting\" || this.sessionState === \"ending\") {\n return;\n }\n\n this.setSessionState(this.hasSession() ? \"active\" : \"none\");\n }\n\n private async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n return { ok: true };\n }\n\n const launch = this.world.launchXR;\n\n if (!launch) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n const { features } = toIWSDKFeatures(options);\n\n try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged. The\n // request's feature strings become IWSDK's structured flags; `launchXR`\n // merges what it is given over the world's `xrDefaults`, so a request\n // that names no features leaves the app's defaults alone.\n launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
|
@@ -24,13 +24,15 @@ export function makeServiceBridgeSystem(options) {
|
|
|
24
24
|
// `FrameInfo.timestamp` and `LifecycleContext.timestamp` are
|
|
25
25
|
// milliseconds on every platform, so both are converted here.
|
|
26
26
|
const timestamp = time * MS_PER_SECOND;
|
|
27
|
-
|
|
27
|
+
// One clock: the frame listeners and renderTick carry the same count.
|
|
28
|
+
const count = ++frame;
|
|
29
|
+
adapter.emitFrame(timestamp, delta, count);
|
|
28
30
|
// IWSDK reports `delta` in seconds; LifecycleContext.deltaTime is in
|
|
29
31
|
// milliseconds, as the three.js and Babylon.js bridges emit it.
|
|
30
32
|
const context = {
|
|
31
33
|
timestamp,
|
|
32
34
|
deltaTime: delta * MS_PER_SECOND,
|
|
33
|
-
frame:
|
|
35
|
+
frame: count,
|
|
34
36
|
source: "iwsdk",
|
|
35
37
|
};
|
|
36
38
|
manager.scheduler.emit("renderTick", context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-bridge-system.js","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAwBA,2FAA2F;AAC3F,MAAM,aAAa,GAAG,IAAI,CAAC;AAmB3B;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgD;IAEhD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACxE,IAAI,WAAgC,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,MAAM,mBAAoB,SAAQ,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,KAAa,EAAE,IAAY;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,KAAK,YAAY,CAAC;YAE7D,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC5B,WAAW,GAAG,OAAO,CAAC;gBACtB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAChD,CAAC;YAED,oEAAoE;YACpE,8DAA8D;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,kEAAkE;gBAClE,6DAA6D;gBAC7D,8DAA8D;gBAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,aAAa,CAAC;gBACvC,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"service-bridge-system.js","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAwBA,2FAA2F;AAC3F,MAAM,aAAa,GAAG,IAAI,CAAC;AAmB3B;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgD;IAEhD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACxE,IAAI,WAAgC,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,MAAM,mBAAoB,SAAQ,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,KAAa,EAAE,IAAY;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,KAAK,YAAY,CAAC;YAE7D,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC5B,WAAW,GAAG,OAAO,CAAC;gBACtB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAChD,CAAC;YAED,oEAAoE;YACpE,8DAA8D;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,kEAAkE;gBAClE,6DAA6D;gBAC7D,8DAA8D;gBAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,aAAa,CAAC;gBACvC,sEAAsE;gBACtE,MAAM,KAAK,GAAG,EAAE,KAAK,CAAC;gBACtB,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBAE3C,qEAAqE;gBACrE,gEAAgE;gBAChE,MAAM,OAAO,GAAqB;oBAChC,SAAS;oBACT,SAAS,EAAE,KAAK,GAAG,aAAa;oBAChC,KAAK,EAAE,KAAK;oBACZ,MAAM,EAAE,OAAO;iBAChB,CAAC;gBAEF,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The single ECS-services bridge. A normal IWSDK system that, each frame:\n * - maps IWSDK `visibilityState` to manager focus/pause (auto-pause when the\n * headset is removed),\n * - drives the adapter's per-frame fan-out via {@link IWSDKAdapter.emitFrame},\n * and\n * - emits the scheduler's `renderTick` channel, the same one the three.js and\n * Babylon.js bridges emit, so a service written against the scheduler runs\n * unchanged under IWSDK.\n *\n * Game logic is only ticked while the session is visible/focused: in the\n * browser / 2D preview the host app shows its own gate (e.g. an \"Enter VR\"\n * overlay) and the services stay idle until the player enters VR.\n *\n * This is the only place the engine loop touches the service layer; services\n * themselves never see IWSDK. The IWSDK primitives (`createSystem` and the\n * `VisibilityState.Visible` value) are injected so this package never imports\n * `@iwsdk/core` - mirroring how the three.js / Babylon.js bridges keep their\n * engine packages at arm's length.\n */\nimport type { LifecycleContext, ServiceManager } from \"@realitycollective/service-framework\";\nimport type { IWSDKAdapter } from \"./iwsdk-adapter.js\";\nimport type { CreateSystemLike, IWSDKWorldLike } from \"./iwsdk-host.js\";\n\n/** Milliseconds per second, for the IWSDK seconds-to-scheduler-milliseconds conversion. */\nconst MS_PER_SECOND = 1000;\n\nexport interface ServiceBridgeSystemOptions<TVisibility = unknown> {\n /** The passive frame source fanned out to services. */\n readonly adapter: IWSDKAdapter;\n /** The service manager whose focus/pause signals are driven by visibility. */\n readonly manager: ServiceManager;\n /** The IWSDK world whose `visibilityState` is read each frame. */\n readonly world: IWSDKWorldLike<TVisibility>;\n /** IWSDK's `createSystem` factory (from `@iwsdk/core`). */\n readonly createSystem: CreateSystemLike;\n /**\n * The `VisibilityState` value that means the session is visible/focused\n * (IWSDK `VisibilityState.Visible`). The bridge ticks services only while\n * `world.visibilityState.value === visibleState`.\n */\n readonly visibleState: TVisibility;\n}\n\n/**\n * Builds the IWSDK `ServiceBridgeSystem` class. Register the returned class with\n * the world (`world.registerSystem(makeServiceBridgeSystem({ ... }))`); IWSDK\n * then calls its `update(delta, time)` once per frame.\n */\nexport function makeServiceBridgeSystem<TVisibility>(\n options: ServiceBridgeSystemOptions<TVisibility>,\n) {\n const { adapter, manager, world, createSystem, visibleState } = options;\n let lastFocused: boolean | undefined;\n let frame = 0;\n\n return class ServiceBridgeSystem extends createSystem({}) {\n public override update(delta: number, time: number): void {\n const focused = world.visibilityState.value === visibleState;\n\n if (focused !== lastFocused) {\n lastFocused = focused;\n manager.emitFocusChange(focused);\n manager.emitPauseChange({ paused: !focused });\n }\n\n // Only run game logic while visible/focused; idle in the 2D/browser\n // preview and while the headset is removed (visible-blurred).\n if (focused) {\n // IWSDK reports `time` as elapsed seconds (three.js `Clock`), but\n // `FrameInfo.timestamp` and `LifecycleContext.timestamp` are\n // milliseconds on every platform, so both are converted here.\n const timestamp = time * MS_PER_SECOND;\n // One clock: the frame listeners and renderTick carry the same count.\n const count = ++frame;\n adapter.emitFrame(timestamp, delta, count);\n\n // IWSDK reports `delta` in seconds; LifecycleContext.deltaTime is in\n // milliseconds, as the three.js and Babylon.js bridges emit it.\n const context: LifecycleContext = {\n timestamp,\n deltaTime: delta * MS_PER_SECOND,\n frame: count,\n source: \"iwsdk\",\n };\n\n manager.scheduler.emit(\"renderTick\", context);\n }\n }\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realitycollective/service-framework-iwsdk",
|
|
3
|
-
"version": "1.0.2-preview.
|
|
3
|
+
"version": "1.0.2-preview.1",
|
|
4
4
|
"description": "Meta IWSDK (WebXR) frame-source bindings for the Reality Collective TypeScript Service Framework.",
|
|
5
5
|
"author": "Reality Collective",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@realitycollective/service-framework": "^1.0.2-preview.
|
|
39
|
+
"@realitycollective/service-framework": "^1.0.2-preview.1"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.build.json",
|