@realitycollective/service-framework-three 1.0.2-preview.0 → 1.0.2-preview.2

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,31 @@ 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
+ - `SESSION_REFERENCE_SPACE` (`"local-floor"`) in `@realitycollective/service-framework`, the WebXR reference space every binding requests: origin where the session began, +Y up with the floor at y = 0, -Z the initial forward direction. `SessionFacet` gains `getMode()` (the live session's mode, or null), `isSupported(mode)` (answers whether the host could start a mode, without changing state, never rejecting) and `recentre()` (moves the viewer's current floor position and yaw to the origin, y untouched, a no-op with no live session). `request()` now documents "end-and-request": asking for a different mode while a session is active ends the live session first and then requests the new one, walking `active`, `ending`, `none`, `requesting`, `active`; asking for the mode already active is a no-op; a host that cannot end its own session refuses with `{ ok: false, reason: "unsupported" }` and leaves the live session running. The pure helpers `recentreOffset(viewer)` and `recentreRig(rig, headLocal)` compute the rigid transform a platform applies, for a reference-space offset and for a moved player rig respectively, and are unit-tested in the core. `runtimeAdapterContractCases()` gains four cases proving every adapter implements all of this the same way, and `MockRuntimeAdapter` implements it in memory with a configurable `supportedModes` option and a `recentreCount`.
24
+ - `@realitycollective/service-framework-three` - `WebXRRuntimeAdapter.session.isSupported` reads `navigator.xr.isSessionSupported`, resolving `false` with no system or on a rejection; `getMode()` and end-and-request now work as the core documents; `recentre()` offsets the renderer's reference space through the new optional `WebXRManagerLike` members `getReferenceSpace`, `setReferenceSpace` and `getFrame`, building the offset with a `rigidTransform` factory that defaults to `globalThis.XRRigidTransform`, and does nothing when any of these is absent. A session request now also calls `setReferenceSpaceType(SESSION_REFERENCE_SPACE)` when the manager carries one, before handing the session to the renderer.
25
+ - `@realitycollective/service-framework-iwsdk` - `IWSDKAdapter` takes an optional second `IWSDKAdapterOptions` argument with `xrSystem`, the `navigator.xr` slice `session.isSupported` reads (defaulting to the global, `null` in Node). `getMode()` and end-and-request now work as the core documents; a world with no `exitXR` refuses a mode switch with `{ ok: false, reason: "unsupported" }` rather than waiting out the session timeout. `recentre()` moves the player rig through the new optional `IWSDKWorldLike` members `player.object3D` and `playerSpaceEntities.head.object3D`, leaving the head's pose local to the rig untouched, and does nothing when either is absent.
26
+ - `@realitycollective/service-framework-babylon` - `BabylonRuntimeAdapter.session.isSupported` is now public, resolving `false` with no experience or on a rejection rather than throwing. `getMode()` and end-and-request now work as the core documents. `recentre()` moves the XR camera through the new optional `BabylonXRExperienceLike.camera` member (`position`, `rotationQuaternion`, `devicePosition`, `deviceRotationQuaternion`), leaving the device's pose local to the camera untouched, and does nothing when the experience carries no camera.
27
+ - `@realitycollective/service-framework-native` - `NativeSessionInfo.supportedModes`, the fact `isSupported` answers from; absent, it reports both immersive modes supported and `"inline"` unsupported. `NativeHost.recentre`, the app's obligation to offset its own reference space so the head's current x, z and yaw become the origin, y untouched; `recentre()` does nothing without it. `getMode()` and end-and-request now work as the core documents: a different mode while active ends the live session first, and an app that refuses instead of ending reports `onSessionRefused("unsupported")` through the same request rather than the refusal being lost.
28
+
29
+ ### Changed
30
+
31
+ - **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.
32
+ - **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.
33
+ - `@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.
34
+ - `@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.
35
+ - `@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.
36
+ - `@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.
37
+ - **Behaviour change on every platform:** `session.request(mode)` while a session is already active is now a no-op only when `mode` matches the live session. A different mode ends the live session first and then requests the new one ("end-and-request"), instead of the previous no-op that always resolved `{ ok: true }` regardless of the mode asked for. A session the adapter did not itself request - already active at construction, or started through a host's own UI - has no known mode, so a request against it is treated as a switch and goes through the same end-and-request path.
20
38
 
21
39
  ### Fixed
22
40
 
package/README.md CHANGED
@@ -66,11 +66,17 @@ Pass the adapter to your services (through their configuration, or a token you r
66
66
 
67
67
  ### Owning the loop
68
68
 
69
- Given a `host`, the adapter owns the animation loop: `start()` binds it, `stop()` releases it, and each callback becomes one `FrameInfo` - `timestamp` in milliseconds, `delta` in seconds. Given a `scheduler` as well, the same callback also emits `renderTick` with `source: "three"` and `deltaTime` in milliseconds, exactly as `ThreeRenderLoopBridge` does, so an app needs one loop owner rather than two. Use the adapter or the bridge, not both.
69
+ Given a `host`, the adapter owns the animation loop: `start()` binds it, `stop()` releases it, and each callback runs `tick(timestamp)` - the whole frame step: the visibility gate described below, one `FrameInfo` (`timestamp` in milliseconds, `delta` in seconds), and, given a `scheduler` too, `renderTick` with `source: "three"` and `deltaTime` in milliseconds, exactly as `ThreeRenderLoopBridge` emits it, so an app needs one loop owner rather than two. Use the adapter or the bridge, not both.
70
70
 
71
71
  three.js routes `setAnimationLoop` through the session's own `requestAnimationFrame` while presenting, so one call covers both the 2D page and the headset.
72
72
 
73
- Omit `host` to keep the loop yourself and call `adapter.emitFrame(timestamp, delta)` per frame.
73
+ Omit `host` where something else already owns the loop - an XR Blocks app, whose `Core` calls `renderer.setAnimationLoop` itself, so passing it here as `host` would fight `Core` for the one callback and silently stop every XR Blocks system. Call `adapter.tick(timestamp)` by hand instead, from whatever per-frame hook that host provides (XR Blocks' `Script.update`), to get the identical gate, frame count and `renderTick` `start()` would have produced. Call `adapter.emitFrame(timestamp, delta)` instead only where you want the raw frame fan-out with no gate and no `renderTick` at all. See `test/xrblocks-contract.test.ts` for the XR Blocks wiring in full, including why `host` must never be XR Blocks' renderer.
74
+
75
+ #### Ticking only while focused
76
+
77
+ While a session is live, `tick()` gates on its own visibility, exactly as IWSDK's `ServiceBridgeSystem` and the native adapter gate on focus: a call that is not `"visible"` reaches no frame listener and no `renderTick`, and does not advance the frame count. Given a `manager` - a `WebXRFocusSink`, which `ServiceManager` already satisfies - the adapter calls `emitFocusChange(focused)` and `emitPauseChange({ paused: !focused })` on every change. This applies equally whether `tick()` runs from the owned loop or is called by hand, because both paths are the same method.
78
+
79
+ This differs from IWSDK and native, which serve nothing but a live XR session: this adapter also serves a desktop page with no session at all, and gating never applies there. With no session `tick()` runs unrestricted exactly as it did before this existed, and if a session that had paused ticking ends, focus is restored at once so the desktop page resumes unrestricted.
74
80
 
75
81
  ### Sessions
76
82
 
@@ -117,11 +123,12 @@ A desktop build with no headset needs no special case. `request` returns `{ ok:
117
123
  | `ThreeRenderLoopBridgeOptions` | interface | `{ scheduler, host }`. |
118
124
  | `AnimationLoopHostLike` | interface | Anything with `setAnimationLoop`. |
119
125
  | `FIRST_FRAME_DELTA_MS` | const | 16 - the delta reported for the first frame. |
120
- | `WebXRRuntimeAdapter` | class | `RuntimeAdapter` over WebXR; `start`, `stop`, `emitFrame`, `getSession`, `refreshCapabilities`, `setCapabilities`, `clearCapabilityOverrides`, `session`, `dispose`. |
121
- | `WebXRRuntimeAdapterOptions` | interface | `{ xr, xrSystem?, host?, scheduler?, sessionInit? }`. |
126
+ | `WebXRRuntimeAdapter` | class | `RuntimeAdapter` over WebXR; `start`, `stop`, `tick`, `emitFrame`, `getSession`, `refreshCapabilities`, `setCapabilities`, `clearCapabilityOverrides`, `session`, `dispose`. |
127
+ | `WebXRRuntimeAdapterOptions` | interface | `{ xr, xrSystem?, host?, scheduler?, sessionInit?, manager? }`. |
122
128
  | `WebXRManagerLike` | interface | The slice of `renderer.xr` the adapter drives. |
123
129
  | `WebXRSystemLike` | interface | The slice of `navigator.xr` it negotiates through. |
124
130
  | `WebXRSessionLike` | interface | The slice of `XRSession` it reads. |
131
+ | `WebXRFocusSink` | interface | `{ emitFocusChange, emitPauseChange }` - what `manager` must implement; `ServiceManager` already does. |
125
132
  | `WebXRManagerEventType` / `WebXRSessionEventType` / `WebXREventListener` | types | The host events it subscribes to. |
126
133
 
127
134
  ## Live examples
package/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  export { FIRST_FRAME_DELTA_MS, ThreeRenderLoopBridge } from "./three-render-loop-bridge.js";
7
7
  export type { AnimationLoopHostLike, ThreeRenderLoopBridgeOptions } from "./three-render-loop-bridge.js";
8
8
  export { WebXRRuntimeAdapter } from "./webxr-runtime-adapter.js";
9
- export type { WebXREventListener, WebXRManagerEventType, WebXRManagerLike, WebXRRuntimeAdapterOptions, WebXRSessionEventType, WebXRSessionLike, WebXRSystemLike } from "./webxr-runtime-adapter.js";
9
+ export type { WebXREventListener, WebXRFocusSink, WebXRManagerEventType, WebXRManagerLike, WebXRRuntimeAdapterOptions, WebXRSessionEventType, WebXRSessionLike, WebXRSystemLike } from "./webxr-runtime-adapter.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAM5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * Public entry point of the three.js binding: the render-loop bridge, and the\n * WebXR runtime adapter that gives a three.js or desktop app the same\n * `RuntimeAdapter` surface the IWSDK binding has.\n */\nexport { FIRST_FRAME_DELTA_MS, ThreeRenderLoopBridge } from \"./three-render-loop-bridge.js\";\nexport type {\n AnimationLoopHostLike,\n ThreeRenderLoopBridgeOptions\n} from \"./three-render-loop-bridge.js\";\n\nexport { WebXRRuntimeAdapter } from \"./webxr-runtime-adapter.js\";\nexport type {\n WebXREventListener,\n WebXRManagerEventType,\n WebXRManagerLike,\n WebXRRuntimeAdapterOptions,\n WebXRSessionEventType,\n WebXRSessionLike,\n WebXRSystemLike\n} from \"./webxr-runtime-adapter.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAM5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * Public entry point of the three.js binding: the render-loop bridge, and the\n * WebXR runtime adapter that gives a three.js or desktop app the same\n * `RuntimeAdapter` surface the IWSDK binding has.\n */\nexport { FIRST_FRAME_DELTA_MS, ThreeRenderLoopBridge } from \"./three-render-loop-bridge.js\";\nexport type {\n AnimationLoopHostLike,\n ThreeRenderLoopBridgeOptions\n} from \"./three-render-loop-bridge.js\";\n\nexport { WebXRRuntimeAdapter } from \"./webxr-runtime-adapter.js\";\nexport type {\n WebXREventListener,\n WebXRFocusSink,\n WebXRManagerEventType,\n WebXRManagerLike,\n WebXRRuntimeAdapterOptions,\n WebXRSessionEventType,\n WebXRSessionLike,\n WebXRSystemLike\n} from \"./webxr-runtime-adapter.js\";\n"]}
@@ -13,6 +13,23 @@
13
13
  *
14
14
  * Every host type here is structural, so this package still imports neither
15
15
  * `three` nor any WebXR type, and the adapter unit-tests headless.
16
+ *
17
+ * Given a `host` and a `manager`, the owned animation loop also gates on the
18
+ * live session's visibility, as IWSDK's `ServiceBridgeSystem` and the native
19
+ * adapter do: while a session is live, frames and `renderTick` flow only on
20
+ * `"visible"`, `emitFocusChange`/`emitPauseChange` fire on every change, and
21
+ * the frame count skips a gated tick. Unlike IWSDK and native, this adapter
22
+ * also serves a desktop page with no session at all, and gating never applies
23
+ * there - a plain three.js page keeps ticking exactly as it did before this
24
+ * existed.
25
+ *
26
+ * That whole step - the gate, the focus/pause signals, the frame count,
27
+ * `emitFrame` and `renderTick` - lives in one place, {@link
28
+ * WebXRRuntimeAdapter.tick}, which `start()` binds to the owned loop. A host
29
+ * that owns its OWN animation loop and will never call `start()` - an XR
30
+ * Blocks app, whose `Core` already calls `setAnimationLoop` itself - calls
31
+ * `tick(timestampMs)` by hand from its own per-frame hook instead, and gets
32
+ * the identical gate rather than reimplementing it.
16
33
  */
17
34
  import { type AdapterCapabilities, type CapabilitiesListener, type CapabilitySessionLike, type FrameListener, type IScheduler, type RuntimeAdapter, type SessionFacet, type SessionMode, type Unsubscribe } from "@realitycollective/service-framework";
18
35
  import { type AnimationLoopHostLike } from "./three-render-loop-bridge.js";
@@ -22,6 +39,18 @@ export type WebXRSessionEventType = "end" | "visibilitychange" | "inputsourcesch
22
39
  export type WebXRManagerEventType = "sessionstart" | "sessionend";
23
40
  /** Host event callback. The adapter reads the host, not the event object. */
24
41
  export type WebXREventListener = (event?: unknown) => void;
42
+ /**
43
+ * The two focus signals the adapter drives while a session is live: a
44
+ * `ServiceManager` is one. Declared locally, as `NativeFocusSink` is in the
45
+ * native package, rather than shared, because the two packages do not depend
46
+ * on each other.
47
+ */
48
+ export interface WebXRFocusSink {
49
+ emitFocusChange(focused: boolean): void;
50
+ emitPauseChange(context: {
51
+ readonly paused: boolean;
52
+ }): void;
53
+ }
25
54
  /**
26
55
  * The slice of an `XRSession` the adapter reads. It extends the core's
27
56
  * {@link CapabilitySessionLike}, so a live session goes straight to
@@ -40,6 +69,38 @@ export interface WebXRSystemLike {
40
69
  isSessionSupported(mode: string): Promise<boolean>;
41
70
  requestSession(mode: string, init?: unknown): Promise<WebXRSessionLike>;
42
71
  }
72
+ /** An `XRRigidTransform`-shaped position, as `recentre()` reads and builds one. */
73
+ export interface WebXRVectorLike {
74
+ readonly x: number;
75
+ readonly y: number;
76
+ readonly z: number;
77
+ }
78
+ /** An `XRRigidTransform`-shaped orientation. */
79
+ export interface WebXRQuaternionLike {
80
+ readonly x: number;
81
+ readonly y: number;
82
+ readonly z: number;
83
+ readonly w: number;
84
+ }
85
+ /** The slice of an `XRRigidTransform` `recentre()` reads off a viewer pose. */
86
+ export interface WebXRRigidTransformLike {
87
+ readonly position: WebXRVectorLike;
88
+ readonly orientation: WebXRQuaternionLike;
89
+ }
90
+ /** The slice of an `XRView`/viewer pose `recentre()` reads. */
91
+ export interface WebXRViewerPoseLike {
92
+ readonly transform: WebXRRigidTransformLike;
93
+ }
94
+ /** The slice of an `XRFrame` `recentre()` reads the viewer pose from. */
95
+ export interface WebXRFrameLike {
96
+ getViewerPose(referenceSpace: WebXRReferenceSpaceLike): WebXRViewerPoseLike | null;
97
+ }
98
+ /** The slice of an `XRReferenceSpace` `recentre()` offsets. */
99
+ export interface WebXRReferenceSpaceLike {
100
+ getOffsetReferenceSpace(originOffset: unknown): WebXRReferenceSpaceLike;
101
+ }
102
+ /** Builds the `XRRigidTransform` `recentre()` hands to `getOffsetReferenceSpace`. */
103
+ export type WebXRRigidTransformFactory = (position: WebXRVectorLike, orientation: WebXRQuaternionLike) => unknown;
43
104
  /** The slice of a three.js `renderer.xr` (`WebXRManager`) the adapter drives. */
44
105
  export interface WebXRManagerLike {
45
106
  /** Hands the renderer the session it should present. */
@@ -48,6 +109,14 @@ export interface WebXRManagerLike {
48
109
  getSession(): WebXRSessionLike | null;
49
110
  addEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;
50
111
  removeEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;
112
+ /** Sets the reference space type the manager requests a session with. */
113
+ setReferenceSpaceType?(type: string): void;
114
+ /** The reference space the manager is presenting with, if any. */
115
+ getReferenceSpace?(): WebXRReferenceSpaceLike | null;
116
+ /** Replaces the reference space the manager presents with - `recentre()`'s write. */
117
+ setReferenceSpace?(space: WebXRReferenceSpaceLike): void;
118
+ /** The current `XRFrame`, if the manager is mid-frame. */
119
+ getFrame?(): WebXRFrameLike | null;
51
120
  }
52
121
  export interface WebXRRuntimeAdapterOptions {
53
122
  /** `renderer.xr` - the renderer's WebXR manager. */
@@ -61,8 +130,11 @@ export interface WebXRRuntimeAdapterOptions {
61
130
  /**
62
131
  * The renderer, or anything else with `setAnimationLoop`. Given one, the
63
132
  * adapter owns the loop: {@link WebXRRuntimeAdapter.start} binds it and each
64
- * callback becomes a frame. Omit it to drive frames yourself with
65
- * {@link WebXRRuntimeAdapter.emitFrame}.
133
+ * callback runs {@link WebXRRuntimeAdapter.tick}. Omit it where something
134
+ * else already owns the loop: call {@link WebXRRuntimeAdapter.tick} by hand
135
+ * from whatever per-frame hook that host provides for the same gate, frame
136
+ * count and `renderTick`, or {@link WebXRRuntimeAdapter.emitFrame} directly
137
+ * for the frame fan-out alone.
66
138
  */
67
139
  readonly host?: AnimationLoopHostLike;
68
140
  /**
@@ -76,6 +148,23 @@ export interface WebXRRuntimeAdapterOptions {
76
148
  * Called once per request; the default sends no init at all.
77
149
  */
78
150
  readonly sessionInit?: (mode: SessionMode) => unknown;
151
+ /**
152
+ * The service manager whose focus and pause signals follow the session's
153
+ * visibility, as `ServiceBridgeSystem`'s `manager` option does on IWSDK and
154
+ * the native adapter's `manager` option does on a native host: both fire on
155
+ * every change of focus, `paused` being `!focused`. Gated ONLY while a
156
+ * session is live - `XRSession.visibilityState` via `visibilitychange` -
157
+ * because unlike IWSDK and native, this adapter also serves a desktop page
158
+ * with no session at all, and that page keeps ticking exactly as it did
159
+ * before this option existed.
160
+ */
161
+ readonly manager?: WebXRFocusSink;
162
+ /**
163
+ * Builds the `XRRigidTransform` {@link WebXRRuntimeAdapter.recentre} hands to
164
+ * `getOffsetReferenceSpace`. Defaults to `globalThis.XRRigidTransform` when
165
+ * present; with neither, `recentre()` does nothing.
166
+ */
167
+ readonly rigidTransform?: WebXRRigidTransformFactory;
79
168
  }
80
169
  export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
81
170
  private readonly frameListeners;
@@ -88,15 +177,25 @@ export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
88
177
  private readonly host;
89
178
  private readonly scheduler;
90
179
  private readonly sessionInit;
180
+ private readonly manager;
181
+ private readonly rigidTransform;
91
182
  private derived;
92
183
  private overrides;
93
184
  private capabilities;
94
185
  private sessionState;
186
+ private mode;
95
187
  private boundSession;
96
188
  private sessionListeners;
97
189
  private animationLoopBound;
98
190
  private frame;
99
191
  private lastTimestamp;
192
+ /**
193
+ * Whether the current session is visible/focused; `undefined` while there is
194
+ * no session, meaning ticking is not gated at all. Reset to `undefined` on
195
+ * session end, so a desktop page resumes exactly as before a session ever
196
+ * existed.
197
+ */
198
+ private focused;
100
199
  private readonly onManagerSessionStart;
101
200
  private readonly onManagerSessionEnd;
102
201
  /** Session lifecycle over `navigator.xr` and the renderer's XR manager. */
@@ -108,10 +207,10 @@ export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
108
207
  /** The session the renderer is presenting, or null in 2D. */
109
208
  getSession(): WebXRSessionLike | null;
110
209
  /**
111
- * Bind the animation loop, if this adapter was given a host. Each callback
112
- * becomes one {@link FrameInfo} and, where a scheduler was supplied, one
113
- * `renderTick`. With no host this does nothing: the app owns the loop and
114
- * calls {@link WebXRRuntimeAdapter.emitFrame} itself.
210
+ * Bind the animation loop, if this adapter was given a host: each callback
211
+ * runs {@link WebXRRuntimeAdapter.tick}. With no host this does nothing -
212
+ * something else owns the loop, and drives {@link WebXRRuntimeAdapter.tick}
213
+ * or {@link WebXRRuntimeAdapter.emitFrame} itself.
115
214
  *
116
215
  * three.js routes `setAnimationLoop` through the session's own
117
216
  * `requestAnimationFrame` while presenting, so one call covers both the 2D
@@ -121,7 +220,7 @@ export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
121
220
  /** Release the animation loop. Safe to call when it was never bound. */
122
221
  stop(): void;
123
222
  /** Push one frame to every subscriber. Call this when you own the loop. */
124
- emitFrame(timestamp: number, delta: number): void;
223
+ emitFrame(timestamp: number, delta: number, frame?: number): void;
125
224
  /**
126
225
  * Re-read the renderer's session and publish any capability change. The
127
226
  * adapter does this itself on every session and input-source event; call it
@@ -145,7 +244,22 @@ export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
145
244
  * listener. It does not end the session - the app owns that decision.
146
245
  */
147
246
  dispose(): void;
148
- private handleAnimationFrame;
247
+ /**
248
+ * Run one frame step: the visibility gate, the focus/pause signals, the one
249
+ * frame count, {@link WebXRRuntimeAdapter.emitFrame} and `renderTick`. This
250
+ * is what {@link WebXRRuntimeAdapter.start} binds to the owned animation
251
+ * loop, and it is public so a host that owns its OWN loop - one that will
252
+ * never call `start()` because it already calls `setAnimationLoop` itself,
253
+ * such as an XR Blocks app - can drive the exact same step by hand from
254
+ * whatever per-frame hook that host provides, rather than reimplementing the
255
+ * gate, the frame count or the `renderTick` shape.
256
+ *
257
+ * `timestampMs` is the loop's own clock reading, the same value `start()`
258
+ * passes from `setAnimationLoop`'s callback.
259
+ */
260
+ tick(timestampMs: number): void;
261
+ /** Emit focus and pause on a change, as `ServiceBridgeSystem` does. */
262
+ private setFocused;
149
263
  /**
150
264
  * Subscribe to one session's own events. The listeners close over the session
151
265
  * they belong to, so nothing here has to re-check which session is live.
@@ -161,6 +275,16 @@ export declare class WebXRRuntimeAdapter implements RuntimeAdapter {
161
275
  private handleSessionEnd;
162
276
  private updateDerived;
163
277
  private publishCapabilities;
278
+ /** Answers `isSupported()` off `navigator.xr`; never rejects, unlike `openSession`'s own check. */
279
+ private checkSupported;
280
+ /**
281
+ * Make the viewer's current floor position and yaw the new origin, by
282
+ * offsetting the manager's reference space. Does nothing when the manager
283
+ * carries none of the optional members this needs, when there is no live
284
+ * frame or reference space, or when no `XRRigidTransform` constructor is
285
+ * available - see {@link WebXRRuntimeAdapterOptions.rigidTransform}.
286
+ */
287
+ private recentreViewer;
164
288
  private requestSession;
165
289
  /**
166
290
  * Negotiate the session and hand it to the renderer. Nothing here throws at
@@ -13,9 +13,32 @@
13
13
  *
14
14
  * Every host type here is structural, so this package still imports neither
15
15
  * `three` nor any WebXR type, and the adapter unit-tests headless.
16
+ *
17
+ * Given a `host` and a `manager`, the owned animation loop also gates on the
18
+ * live session's visibility, as IWSDK's `ServiceBridgeSystem` and the native
19
+ * adapter do: while a session is live, frames and `renderTick` flow only on
20
+ * `"visible"`, `emitFocusChange`/`emitPauseChange` fire on every change, and
21
+ * the frame count skips a gated tick. Unlike IWSDK and native, this adapter
22
+ * also serves a desktop page with no session at all, and gating never applies
23
+ * there - a plain three.js page keeps ticking exactly as it did before this
24
+ * existed.
25
+ *
26
+ * That whole step - the gate, the focus/pause signals, the frame count,
27
+ * `emitFrame` and `renderTick` - lives in one place, {@link
28
+ * WebXRRuntimeAdapter.tick}, which `start()` binds to the owned loop. A host
29
+ * that owns its OWN animation loop and will never call `start()` - an XR
30
+ * Blocks app, whose `Core` already calls `setAnimationLoop` itself - calls
31
+ * `tick(timestampMs)` by hand from its own per-frame hook instead, and gets
32
+ * the identical gate rather than reimplementing it.
16
33
  */
17
- import { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, deriveCapabilities, mergeSessionInit, } from "@realitycollective/service-framework";
34
+ import { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, SESSION_REFERENCE_SPACE, deriveCapabilities, mergeSessionInit, recentreOffset, } from "@realitycollective/service-framework";
18
35
  import { FIRST_FRAME_DELTA_MS } from "./three-render-loop-bridge.js";
36
+ /** Reads `globalThis.XRRigidTransform`, or `undefined` where there is none (Node). */
37
+ function defaultRigidTransformFactory() {
38
+ const ctor = globalThis
39
+ .XRRigidTransform;
40
+ return ctor ? (position, orientation) => new ctor(position, orientation) : undefined;
41
+ }
19
42
  const SESSION_VISIBILITY_VALUES = new Set([
20
43
  "visible",
21
44
  "visible-blurred",
@@ -60,15 +83,25 @@ export class WebXRRuntimeAdapter {
60
83
  host;
61
84
  scheduler;
62
85
  sessionInit;
86
+ manager;
87
+ rigidTransform;
63
88
  derived = DEFAULT_CAPABILITIES;
64
89
  overrides = {};
65
90
  capabilities = DEFAULT_CAPABILITIES;
66
91
  sessionState = "none";
92
+ mode = null;
67
93
  boundSession = null;
68
94
  sessionListeners = [];
69
95
  animationLoopBound = false;
70
96
  frame = 0;
71
97
  lastTimestamp = 0;
98
+ /**
99
+ * Whether the current session is visible/focused; `undefined` while there is
100
+ * no session, meaning ticking is not gated at all. Reset to `undefined` on
101
+ * session end, so a desktop page resumes exactly as before a session ever
102
+ * existed.
103
+ */
104
+ focused;
72
105
  onManagerSessionStart = () => {
73
106
  this.handleSessionStart();
74
107
  };
@@ -78,8 +111,11 @@ export class WebXRRuntimeAdapter {
78
111
  /** Session lifecycle over `navigator.xr` and the renderer's XR manager. */
79
112
  session = {
80
113
  getState: () => this.sessionState,
114
+ getMode: () => (this.sessionState === "active" ? this.mode : null),
115
+ isSupported: (mode) => this.checkSupported(mode),
81
116
  request: (mode, options) => this.requestSession(mode, options),
82
117
  end: () => this.endSession(),
118
+ recentre: () => this.recentreViewer(),
83
119
  onStateChange: (listener) => {
84
120
  this.stateListeners.add(listener);
85
121
  return () => {
@@ -99,6 +135,8 @@ export class WebXRRuntimeAdapter {
99
135
  this.host = options.host;
100
136
  this.scheduler = options.scheduler;
101
137
  this.sessionInit = options.sessionInit;
138
+ this.manager = options.manager;
139
+ this.rigidTransform = options.rigidTransform ?? defaultRigidTransformFactory();
102
140
  this.xr.addEventListener("sessionstart", this.onManagerSessionStart);
103
141
  this.xr.addEventListener("sessionend", this.onManagerSessionEnd);
104
142
  const session = this.xr.getSession();
@@ -129,10 +167,10 @@ export class WebXRRuntimeAdapter {
129
167
  return this.boundSession;
130
168
  }
131
169
  /**
132
- * Bind the animation loop, if this adapter was given a host. Each callback
133
- * becomes one {@link FrameInfo} and, where a scheduler was supplied, one
134
- * `renderTick`. With no host this does nothing: the app owns the loop and
135
- * calls {@link WebXRRuntimeAdapter.emitFrame} itself.
170
+ * Bind the animation loop, if this adapter was given a host: each callback
171
+ * runs {@link WebXRRuntimeAdapter.tick}. With no host this does nothing -
172
+ * something else owns the loop, and drives {@link WebXRRuntimeAdapter.tick}
173
+ * or {@link WebXRRuntimeAdapter.emitFrame} itself.
136
174
  *
137
175
  * three.js routes `setAnimationLoop` through the session's own
138
176
  * `requestAnimationFrame` while presenting, so one call covers both the 2D
@@ -143,7 +181,7 @@ export class WebXRRuntimeAdapter {
143
181
  return;
144
182
  }
145
183
  this.animationLoopBound = true;
146
- this.host?.setAnimationLoop((timestamp) => this.handleAnimationFrame(timestamp));
184
+ this.host?.setAnimationLoop((timestamp) => this.tick(timestamp));
147
185
  }
148
186
  /** Release the animation loop. Safe to call when it was never bound. */
149
187
  stop() {
@@ -154,9 +192,9 @@ export class WebXRRuntimeAdapter {
154
192
  this.host?.setAnimationLoop(null);
155
193
  }
156
194
  /** Push one frame to every subscriber. Call this when you own the loop. */
157
- emitFrame(timestamp, delta) {
158
- const frame = { timestamp, delta };
159
- this.frameListeners.forEach((listener) => listener(frame));
195
+ emitFrame(timestamp, delta, frame) {
196
+ const info = frame === undefined ? { timestamp, delta } : { timestamp, delta, frame };
197
+ this.frameListeners.forEach((listener) => listener(info));
160
198
  }
161
199
  /**
162
200
  * Re-read the renderer's session and publish any capability change. The
@@ -210,21 +248,54 @@ export class WebXRRuntimeAdapter {
210
248
  this.stateListeners.clear();
211
249
  this.visibilityListeners.clear();
212
250
  }
213
- handleAnimationFrame(timestamp) {
214
- const deltaMs = this.lastTimestamp === 0 ? FIRST_FRAME_DELTA_MS : timestamp - this.lastTimestamp;
215
- this.lastTimestamp = timestamp;
251
+ /**
252
+ * Run one frame step: the visibility gate, the focus/pause signals, the one
253
+ * frame count, {@link WebXRRuntimeAdapter.emitFrame} and `renderTick`. This
254
+ * is what {@link WebXRRuntimeAdapter.start} binds to the owned animation
255
+ * loop, and it is public so a host that owns its OWN loop - one that will
256
+ * never call `start()` because it already calls `setAnimationLoop` itself,
257
+ * such as an XR Blocks app - can drive the exact same step by hand from
258
+ * whatever per-frame hook that host provides, rather than reimplementing the
259
+ * gate, the frame count or the `renderTick` shape.
260
+ *
261
+ * `timestampMs` is the loop's own clock reading, the same value `start()`
262
+ * passes from `setAnimationLoop`'s callback.
263
+ */
264
+ tick(timestampMs) {
265
+ const deltaMs = this.lastTimestamp === 0 ? FIRST_FRAME_DELTA_MS : timestampMs - this.lastTimestamp;
266
+ this.lastTimestamp = timestampMs;
267
+ const session = this.boundSession;
268
+ if (session) {
269
+ this.setFocused(toSessionVisibility(session.visibilityState) === "visible");
270
+ if (!this.focused) {
271
+ // Gated: a live session that is not visible. Skip this tick entirely -
272
+ // no frame, no renderTick, no advance of the frame count - as IWSDK's
273
+ // bridge skips an unfocused frame. A page with no session at all never
274
+ // reaches this branch, so it is never gated.
275
+ return;
276
+ }
277
+ }
216
278
  this.frame += 1;
217
279
  // `FrameInfo.delta` is seconds; the scheduler's `LifecycleContext` is in
218
280
  // milliseconds, which is the unit `ThreeRenderLoopBridge` already emits.
219
- this.emitFrame(timestamp, deltaMs / 1000);
281
+ this.emitFrame(timestampMs, deltaMs / 1000, this.frame);
220
282
  const context = {
221
- timestamp,
283
+ timestamp: timestampMs,
222
284
  deltaTime: deltaMs,
223
285
  frame: this.frame,
224
286
  source: "three",
225
287
  };
226
288
  this.scheduler?.emit("renderTick", context);
227
289
  }
290
+ /** Emit focus and pause on a change, as `ServiceBridgeSystem` does. */
291
+ setFocused(focused) {
292
+ if (focused === this.focused) {
293
+ return;
294
+ }
295
+ this.focused = focused;
296
+ this.manager?.emitFocusChange(focused);
297
+ this.manager?.emitPauseChange({ paused: !focused });
298
+ }
228
299
  /**
229
300
  * Subscribe to one session's own events. The listeners close over the session
230
301
  * they belong to, so nothing here has to re-check which session is live.
@@ -282,6 +353,14 @@ export class WebXRRuntimeAdapter {
282
353
  this.updateDerived(null);
283
354
  this.notifyVisibility("non-immersive");
284
355
  this.settleEndWaiters();
356
+ // The session that was gating ticks is gone. A page with no session is
357
+ // never gated, so if it had been paused, restore focus now rather than
358
+ // leaving the manager believing it still is.
359
+ if (this.focused === false) {
360
+ this.manager?.emitFocusChange(true);
361
+ this.manager?.emitPauseChange({ paused: false });
362
+ }
363
+ this.focused = undefined;
285
364
  }
286
365
  updateDerived(session) {
287
366
  this.derived = deriveCapabilities(session);
@@ -300,9 +379,61 @@ export class WebXRRuntimeAdapter {
300
379
  this.capabilities = next;
301
380
  this.capabilitiesListeners.forEach((listener) => listener(next));
302
381
  }
382
+ /** Answers `isSupported()` off `navigator.xr`; never rejects, unlike `openSession`'s own check. */
383
+ async checkSupported(mode) {
384
+ const system = this.xrSystem;
385
+ if (!system) {
386
+ return false;
387
+ }
388
+ try {
389
+ return await system.isSessionSupported(mode);
390
+ }
391
+ catch {
392
+ return false;
393
+ }
394
+ }
395
+ /**
396
+ * Make the viewer's current floor position and yaw the new origin, by
397
+ * offsetting the manager's reference space. Does nothing when the manager
398
+ * carries none of the optional members this needs, when there is no live
399
+ * frame or reference space, or when no `XRRigidTransform` constructor is
400
+ * available - see {@link WebXRRuntimeAdapterOptions.rigidTransform}.
401
+ */
402
+ recentreViewer() {
403
+ const { getReferenceSpace, setReferenceSpace, getFrame } = this.xr;
404
+ const factory = this.rigidTransform;
405
+ if (!getReferenceSpace || !setReferenceSpace || !getFrame || !factory) {
406
+ return;
407
+ }
408
+ const space = getReferenceSpace.call(this.xr);
409
+ const frame = getFrame.call(this.xr);
410
+ if (!space || !frame) {
411
+ return;
412
+ }
413
+ const pose = frame.getViewerPose(space);
414
+ if (!pose) {
415
+ return;
416
+ }
417
+ const offset = recentreOffset({
418
+ position: [pose.transform.position.x, pose.transform.position.y, pose.transform.position.z],
419
+ orientation: [
420
+ pose.transform.orientation.x,
421
+ pose.transform.orientation.y,
422
+ pose.transform.orientation.z,
423
+ pose.transform.orientation.w,
424
+ ],
425
+ });
426
+ const transform = factory({ x: offset.position[0], y: offset.position[1], z: offset.position[2] }, { x: offset.orientation[0], y: offset.orientation[1], z: offset.orientation[2], w: offset.orientation[3] });
427
+ setReferenceSpace.call(this.xr, space.getOffsetReferenceSpace(transform));
428
+ }
303
429
  async requestSession(mode, options) {
304
430
  if (this.sessionState === "active") {
305
- return { ok: true };
431
+ if (this.mode === mode) {
432
+ return { ok: true };
433
+ }
434
+ // End-and-request: three.js always has an end() to call while a session
435
+ // is live, so the switch is always possible.
436
+ await this.endSession();
306
437
  }
307
438
  const system = this.xrSystem;
308
439
  if (!system) {
@@ -320,6 +451,7 @@ export class WebXRRuntimeAdapter {
320
451
  this.setSessionState("none");
321
452
  return result;
322
453
  }
454
+ this.mode = mode;
323
455
  this.setSessionState("active");
324
456
  return result;
325
457
  }
@@ -341,6 +473,7 @@ export class WebXRRuntimeAdapter {
341
473
  }
342
474
  const init = mergeSessionInit(this.sessionInit?.(mode), options);
343
475
  const session = await system.requestSession(mode, init);
476
+ this.xr.setReferenceSpaceType?.(SESSION_REFERENCE_SPACE);
344
477
  await this.xr.setSession(session);
345
478
  // A manager that raises `sessionstart` has already attached this session;
346
479
  // one that does not is attached here, so both host styles behave alike.
@@ -387,6 +520,9 @@ export class WebXRRuntimeAdapter {
387
520
  return;
388
521
  }
389
522
  this.sessionState = state;
523
+ if (state === "none") {
524
+ this.mode = null;
525
+ }
390
526
  this.stateListeners.forEach((listener) => listener(state));
391
527
  }
392
528
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webxr-runtime-adapter.js","sourceRoot":"","sources":["../src/webxr-runtime-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebXR implementation of {@link RuntimeAdapter}, for a three.js app or any\n * other page that owns its own renderer.\n *\n * The IWSDK binding gets its adapter from IWSDK. Everything else - a plain\n * three.js app, a desktop build with no headset - had nothing, so a service\n * written against `RuntimeAdapter` could not be hosted there. This adapter\n * closes that gap: it orchestrates the entry points the platform already\n * provides (`navigator.xr` for session negotiation, `renderer.xr` for the\n * session the renderer presents, `setAnimationLoop` for frames) and publishes\n * them through the same seam. It renders nothing, plays nothing and owns no\n * scene state.\n *\n * Every host type here is structural, so this package still imports neither\n * `three` nor any WebXR type, and the adapter unit-tests headless.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n mergeSessionInit,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type CapabilitySessionLike,\n type FrameInfo,\n type FrameListener,\n type IScheduler,\n type LifecycleContext,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionFailureReason,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { FIRST_FRAME_DELTA_MS, type AnimationLoopHostLike } from \"./three-render-loop-bridge.js\";\n\n/** The events an `XRSession` raises that this adapter listens for. */\nexport type WebXRSessionEventType = \"end\" | \"visibilitychange\" | \"inputsourceschange\";\n\n/** The events three.js's `WebXRManager` raises that this adapter listens for. */\nexport type WebXRManagerEventType = \"sessionstart\" | \"sessionend\";\n\n/** Host event callback. The adapter reads the host, not the event object. */\nexport type WebXREventListener = (event?: unknown) => void;\n\n/**\n * The slice of an `XRSession` the adapter reads. It extends the core's\n * {@link CapabilitySessionLike}, so a live session goes straight to\n * `deriveCapabilities` with no mapping.\n */\nexport interface WebXRSessionLike extends CapabilitySessionLike {\n /** Ends the session. The `end` event is what the adapter acts on. */\n end(): Promise<void>;\n /** `\"visible\"`, `\"visible-blurred\"` or `\"hidden\"` on a live session. */\n readonly visibilityState?: string;\n addEventListener(type: WebXRSessionEventType, listener: WebXREventListener): void;\n removeEventListener(type: WebXRSessionEventType, listener: WebXREventListener): void;\n}\n\n/** The slice of `navigator.xr` the session facet negotiates through. */\nexport interface WebXRSystemLike {\n isSessionSupported(mode: string): Promise<boolean>;\n requestSession(mode: string, init?: unknown): Promise<WebXRSessionLike>;\n}\n\n/** The slice of a three.js `renderer.xr` (`WebXRManager`) the adapter drives. */\nexport interface WebXRManagerLike {\n /** Hands the renderer the session it should present. */\n setSession(session: WebXRSessionLike): Promise<void> | void;\n /** The session the renderer is presenting, or null in 2D. */\n getSession(): WebXRSessionLike | null;\n addEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;\n removeEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;\n}\n\nexport interface WebXRRuntimeAdapterOptions {\n /** `renderer.xr` - the renderer's WebXR manager. */\n readonly xr: WebXRManagerLike;\n /**\n * `navigator.xr`. Defaults to the global when omitted, and to `null` where\n * there is no global, which is what a Node test sees. A null system reports\n * every session request as `\"unsupported\"`.\n */\n readonly xrSystem?: WebXRSystemLike | null;\n /**\n * The renderer, or anything else with `setAnimationLoop`. Given one, the\n * adapter owns the loop: {@link WebXRRuntimeAdapter.start} binds it and each\n * callback becomes a frame. Omit it to drive frames yourself with\n * {@link WebXRRuntimeAdapter.emitFrame}.\n */\n readonly host?: AnimationLoopHostLike;\n /**\n * Given a scheduler, each owned frame also emits the `renderTick` channel\n * with `source: \"three\"`, exactly as `ThreeRenderLoopBridge` does, so an app\n * needs one loop owner rather than two.\n */\n readonly scheduler?: IScheduler;\n /**\n * Supplies the `XRSessionInit` for a mode - required and optional features.\n * Called once per request; the default sends no init at all.\n */\n readonly sessionInit?: (mode: SessionMode) => unknown;\n}\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n]);\n\n/**\n * Map `XRSession.visibilityState` onto the facet's vocabulary. An unrecognised\n * value is reported as `\"hidden\"`, because treating an unknown state as visible\n * 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/**\n * WebXR reports a blocked request through the error name: `NotAllowedError`\n * when the user or the permission prompt refused, `SecurityError` when the\n * permissions policy did. Both are a refusal rather than a fault.\n */\nfunction toFailureReason(error: unknown): SessionFailureReason {\n const name = (error as { readonly name?: unknown } | null | undefined)?.name;\n\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n return \"denied\";\n }\n\n return \"error\";\n}\n\n/** `navigator.xr`, read defensively: there is no navigator in a Node test. */\nfunction defaultXRSystem(): WebXRSystemLike | null {\n const globalNavigator = (globalThis as { navigator?: { xr?: WebXRSystemLike } }).navigator;\n\n return globalNavigator?.xr ?? null;\n}\n\nexport class WebXRRuntimeAdapter 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 endWaiters = new Set<() => void>();\n\n private readonly xr: WebXRManagerLike;\n private readonly xrSystem: WebXRSystemLike | null;\n private readonly host: AnimationLoopHostLike | undefined;\n private readonly scheduler: IScheduler | undefined;\n private readonly sessionInit: ((mode: SessionMode) => unknown) | undefined;\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 boundSession: WebXRSessionLike | null = null;\n private sessionListeners: {\n readonly type: WebXRSessionEventType;\n readonly listener: WebXREventListener;\n }[] = [];\n private animationLoopBound = false;\n private frame = 0;\n private lastTimestamp = 0;\n\n private readonly onManagerSessionStart: WebXREventListener = () => {\n this.handleSessionStart();\n };\n\n private readonly onManagerSessionEnd: WebXREventListener = () => {\n this.handleSessionEnd();\n };\n\n /** Session lifecycle over `navigator.xr` and the renderer's XR manager. */\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(options: WebXRRuntimeAdapterOptions) {\n this.xr = options.xr;\n this.xrSystem = options.xrSystem === undefined ? defaultXRSystem() : options.xrSystem;\n this.host = options.host;\n this.scheduler = options.scheduler;\n this.sessionInit = options.sessionInit;\n\n this.xr.addEventListener(\"sessionstart\", this.onManagerSessionStart);\n this.xr.addEventListener(\"sessionend\", this.onManagerSessionEnd);\n\n const session = this.xr.getSession();\n\n if (session) {\n this.attachSession(session);\n this.sessionState = \"active\";\n }\n\n this.derived = deriveCapabilities(session);\n this.capabilities = this.derived;\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 session the renderer is presenting, or null in 2D. */\n public getSession(): WebXRSessionLike | null {\n return this.boundSession;\n }\n\n /**\n * Bind the animation loop, if this adapter was given a host. Each callback\n * becomes one {@link FrameInfo} and, where a scheduler was supplied, one\n * `renderTick`. With no host this does nothing: the app owns the loop and\n * calls {@link WebXRRuntimeAdapter.emitFrame} itself.\n *\n * three.js routes `setAnimationLoop` through the session's own\n * `requestAnimationFrame` while presenting, so one call covers both the 2D\n * page and the headset.\n */\n public start(): void {\n if (this.animationLoopBound) {\n return;\n }\n\n this.animationLoopBound = true;\n this.host?.setAnimationLoop((timestamp) => this.handleAnimationFrame(timestamp));\n }\n\n /** Release the animation loop. Safe to call when it was never bound. */\n public stop(): void {\n if (!this.animationLoopBound) {\n return;\n }\n\n this.animationLoopBound = false;\n this.host?.setAnimationLoop(null);\n }\n\n /** Push one frame to every subscriber. Call this when you own the loop. */\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 * Re-read the renderer's session and publish any capability change. The\n * adapter does this itself on every session and input-source event; call it\n * directly after the host enables a feature mid-session, or after a host that\n * raises no events changes what it presents.\n */\n public refreshCapabilities(): void {\n const session = this.xr.getSession();\n\n if (session) {\n this.attachSession(session);\n this.updateDerived(session);\n return;\n }\n\n if (this.boundSession) {\n this.handleSessionEnd();\n return;\n }\n\n this.updateDerived(null);\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 WebXRRuntimeAdapter.clearCapabilityOverrides} or\n * {@link WebXRRuntimeAdapter.dispose}.\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 /**\n * Release everything the adapter holds: the animation loop, both sets of host\n * event listeners, any in-flight `end()`, the manual overrides and every\n * listener. It does not end the session - the app owns that decision.\n */\n public dispose(): void {\n this.stop();\n this.xr.removeEventListener(\"sessionstart\", this.onManagerSessionStart);\n this.xr.removeEventListener(\"sessionend\", this.onManagerSessionEnd);\n this.detachSession();\n this.settleEndWaiters();\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private handleAnimationFrame(timestamp: number): void {\n const deltaMs =\n this.lastTimestamp === 0 ? FIRST_FRAME_DELTA_MS : timestamp - this.lastTimestamp;\n\n this.lastTimestamp = timestamp;\n this.frame += 1;\n\n // `FrameInfo.delta` is seconds; the scheduler's `LifecycleContext` is in\n // milliseconds, which is the unit `ThreeRenderLoopBridge` already emits.\n this.emitFrame(timestamp, deltaMs / 1000);\n\n const context: LifecycleContext = {\n timestamp,\n deltaTime: deltaMs,\n frame: this.frame,\n source: \"three\",\n };\n\n this.scheduler?.emit(\"renderTick\", context);\n }\n\n /**\n * Subscribe to one session's own events. The listeners close over the session\n * they belong to, so nothing here has to re-check which session is live.\n */\n private attachSession(session: WebXRSessionLike): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.detachSession();\n this.boundSession = session;\n this.sessionListeners = [\n { type: \"end\", listener: () => this.handleSessionEnd() },\n {\n type: \"visibilitychange\",\n listener: () => this.notifyVisibility(toSessionVisibility(session.visibilityState)),\n },\n { type: \"inputsourceschange\", listener: () => this.updateDerived(session) },\n ];\n\n for (const entry of this.sessionListeners) {\n session.addEventListener(entry.type, entry.listener);\n }\n }\n\n private detachSession(): void {\n const session = this.boundSession;\n\n if (!session) {\n return;\n }\n\n for (const entry of this.sessionListeners) {\n session.removeEventListener(entry.type, entry.listener);\n }\n\n this.sessionListeners = [];\n this.boundSession = null;\n }\n\n private handleSessionStart(): void {\n const session = this.xr.getSession();\n\n if (!session) {\n return;\n }\n\n this.attachSession(session);\n this.setSessionState(\"active\");\n this.updateDerived(session);\n this.notifyVisibility(toSessionVisibility(session.visibilityState));\n }\n\n /**\n * One session ending raises both the session's `end` event and the manager's\n * `sessionend`, so this runs twice per session and is written to be\n * idempotent.\n */\n private handleSessionEnd(): void {\n if (!this.boundSession && this.sessionState === \"none\") {\n return;\n }\n\n this.detachSession();\n this.setSessionState(\"none\");\n this.updateDerived(null);\n this.notifyVisibility(\"non-immersive\");\n this.settleEndWaiters();\n }\n\n private updateDerived(session: CapabilitySessionLike | null): void {\n this.derived = deriveCapabilities(session);\n this.publishCapabilities();\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 async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n return { ok: true };\n }\n\n const system = this.xrSystem;\n\n if (!system) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<SessionResult>((resolve) => {\n timer = setTimeout(() => resolve({ ok: false, reason: \"timeout\" }), timeoutMs);\n });\n\n const result = await Promise.race([this.openSession(system, mode, options), timeout]);\n clearTimeout(timer);\n\n if (!result.ok) {\n this.setSessionState(\"none\");\n return result;\n }\n\n this.setSessionState(\"active\");\n return result;\n }\n\n /**\n * Negotiate the session and hand it to the renderer. Nothing here throws at\n * the caller: a headset that is absent, refused or broken is a normal runtime\n * condition, so every outcome comes back as a {@link SessionResult}.\n *\n * The request's own features are merged over the `sessionInit` hook's result\n * by the core's `mergeSessionInit`, so they add to the app's defaults rather\n * than replacing them. A request that names none passes the hook's result\n * through untouched.\n */\n private async openSession(\n system: WebXRSystemLike,\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n try {\n const supported = await system.isSessionSupported(mode);\n\n if (!supported) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const init = mergeSessionInit(this.sessionInit?.(mode), options);\n const session = await system.requestSession(mode, init);\n await this.xr.setSession(session);\n\n // A manager that raises `sessionstart` has already attached this session;\n // one that does not is attached here, so both host styles behave alike.\n this.attachSession(session);\n this.updateDerived(session);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, reason: toFailureReason(error), error };\n }\n }\n\n private async endSession(): Promise<void> {\n const session = this.boundSession;\n\n if (!session) {\n return;\n }\n\n this.setSessionState(\"ending\");\n\n const ended = new Promise<void>((resolve) => {\n this.endWaiters.add(resolve);\n });\n\n try {\n await session.end();\n } catch {\n // Ending is best effort. The `end` event, not this promise, is what the\n // adapter acts on, and a host that refuses still has to be reported.\n }\n\n // The event has normally arrived by now and this is a no-op. A host that\n // raises none would otherwise leave the caller waiting forever, so the\n // settled `end()` call is the fallback signal.\n this.handleSessionEnd();\n\n await ended;\n }\n\n private settleEndWaiters(): void {\n const waiters = Array.from(this.endWaiters);\n this.endWaiters.clear();\n waiters.forEach((resolve) => resolve());\n }\n\n private notifyVisibility(visibility: SessionVisibility): void {\n this.visibilityListeners.forEach((listener) => listener(visibility));\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":"webxr-runtime-adapter.js","sourceRoot":"","sources":["../src/webxr-runtime-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebXR implementation of {@link RuntimeAdapter}, for a three.js app or any\n * other page that owns its own renderer.\n *\n * The IWSDK binding gets its adapter from IWSDK. Everything else - a plain\n * three.js app, a desktop build with no headset - had nothing, so a service\n * written against `RuntimeAdapter` could not be hosted there. This adapter\n * closes that gap: it orchestrates the entry points the platform already\n * provides (`navigator.xr` for session negotiation, `renderer.xr` for the\n * session the renderer presents, `setAnimationLoop` for frames) and publishes\n * them through the same seam. It renders nothing, plays nothing and owns no\n * scene state.\n *\n * Every host type here is structural, so this package still imports neither\n * `three` nor any WebXR type, and the adapter unit-tests headless.\n *\n * Given a `host` and a `manager`, the owned animation loop also gates on the\n * live session's visibility, as IWSDK's `ServiceBridgeSystem` and the native\n * adapter do: while a session is live, frames and `renderTick` flow only on\n * `\"visible\"`, `emitFocusChange`/`emitPauseChange` fire on every change, and\n * the frame count skips a gated tick. Unlike IWSDK and native, this adapter\n * also serves a desktop page with no session at all, and gating never applies\n * there - a plain three.js page keeps ticking exactly as it did before this\n * existed.\n *\n * That whole step - the gate, the focus/pause signals, the frame count,\n * `emitFrame` and `renderTick` - lives in one place, {@link\n * WebXRRuntimeAdapter.tick}, which `start()` binds to the owned loop. A host\n * that owns its OWN animation loop and will never call `start()` - an XR\n * Blocks app, whose `Core` already calls `setAnimationLoop` itself - calls\n * `tick(timestampMs)` by hand from its own per-frame hook instead, and gets\n * the identical gate rather than reimplementing it.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n SESSION_REFERENCE_SPACE,\n deriveCapabilities,\n mergeSessionInit,\n recentreOffset,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type CapabilitySessionLike,\n type FrameInfo,\n type FrameListener,\n type IScheduler,\n type LifecycleContext,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionFailureReason,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { FIRST_FRAME_DELTA_MS, type AnimationLoopHostLike } from \"./three-render-loop-bridge.js\";\n\n/** The events an `XRSession` raises that this adapter listens for. */\nexport type WebXRSessionEventType = \"end\" | \"visibilitychange\" | \"inputsourceschange\";\n\n/** The events three.js's `WebXRManager` raises that this adapter listens for. */\nexport type WebXRManagerEventType = \"sessionstart\" | \"sessionend\";\n\n/** Host event callback. The adapter reads the host, not the event object. */\nexport type WebXREventListener = (event?: unknown) => void;\n\n/**\n * The two focus signals the adapter drives while a session is live: a\n * `ServiceManager` is one. Declared locally, as `NativeFocusSink` is in the\n * native package, rather than shared, because the two packages do not depend\n * on each other.\n */\nexport interface WebXRFocusSink {\n emitFocusChange(focused: boolean): void;\n emitPauseChange(context: { readonly paused: boolean }): void;\n}\n\n/**\n * The slice of an `XRSession` the adapter reads. It extends the core's\n * {@link CapabilitySessionLike}, so a live session goes straight to\n * `deriveCapabilities` with no mapping.\n */\nexport interface WebXRSessionLike extends CapabilitySessionLike {\n /** Ends the session. The `end` event is what the adapter acts on. */\n end(): Promise<void>;\n /** `\"visible\"`, `\"visible-blurred\"` or `\"hidden\"` on a live session. */\n readonly visibilityState?: string;\n addEventListener(type: WebXRSessionEventType, listener: WebXREventListener): void;\n removeEventListener(type: WebXRSessionEventType, listener: WebXREventListener): void;\n}\n\n/** The slice of `navigator.xr` the session facet negotiates through. */\nexport interface WebXRSystemLike {\n isSessionSupported(mode: string): Promise<boolean>;\n requestSession(mode: string, init?: unknown): Promise<WebXRSessionLike>;\n}\n\n/** An `XRRigidTransform`-shaped position, as `recentre()` reads and builds one. */\nexport interface WebXRVectorLike {\n readonly x: number;\n readonly y: number;\n readonly z: number;\n}\n\n/** An `XRRigidTransform`-shaped orientation. */\nexport interface WebXRQuaternionLike {\n readonly x: number;\n readonly y: number;\n readonly z: number;\n readonly w: number;\n}\n\n/** The slice of an `XRRigidTransform` `recentre()` reads off a viewer pose. */\nexport interface WebXRRigidTransformLike {\n readonly position: WebXRVectorLike;\n readonly orientation: WebXRQuaternionLike;\n}\n\n/** The slice of an `XRView`/viewer pose `recentre()` reads. */\nexport interface WebXRViewerPoseLike {\n readonly transform: WebXRRigidTransformLike;\n}\n\n/** The slice of an `XRFrame` `recentre()` reads the viewer pose from. */\nexport interface WebXRFrameLike {\n getViewerPose(referenceSpace: WebXRReferenceSpaceLike): WebXRViewerPoseLike | null;\n}\n\n/** The slice of an `XRReferenceSpace` `recentre()` offsets. */\nexport interface WebXRReferenceSpaceLike {\n getOffsetReferenceSpace(originOffset: unknown): WebXRReferenceSpaceLike;\n}\n\n/** Builds the `XRRigidTransform` `recentre()` hands to `getOffsetReferenceSpace`. */\nexport type WebXRRigidTransformFactory = (\n position: WebXRVectorLike,\n orientation: WebXRQuaternionLike,\n) => unknown;\n\n/** The slice of a three.js `renderer.xr` (`WebXRManager`) the adapter drives. */\nexport interface WebXRManagerLike {\n /** Hands the renderer the session it should present. */\n setSession(session: WebXRSessionLike): Promise<void> | void;\n /** The session the renderer is presenting, or null in 2D. */\n getSession(): WebXRSessionLike | null;\n addEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;\n removeEventListener(type: WebXRManagerEventType, listener: WebXREventListener): void;\n /** Sets the reference space type the manager requests a session with. */\n setReferenceSpaceType?(type: string): void;\n /** The reference space the manager is presenting with, if any. */\n getReferenceSpace?(): WebXRReferenceSpaceLike | null;\n /** Replaces the reference space the manager presents with - `recentre()`'s write. */\n setReferenceSpace?(space: WebXRReferenceSpaceLike): void;\n /** The current `XRFrame`, if the manager is mid-frame. */\n getFrame?(): WebXRFrameLike | null;\n}\n\n/** Reads `globalThis.XRRigidTransform`, or `undefined` where there is none (Node). */\nfunction defaultRigidTransformFactory(): WebXRRigidTransformFactory | undefined {\n const ctor = (globalThis as { XRRigidTransform?: new (p: unknown, o: unknown) => unknown })\n .XRRigidTransform;\n\n return ctor ? (position, orientation) => new ctor(position, orientation) : undefined;\n}\n\nexport interface WebXRRuntimeAdapterOptions {\n /** `renderer.xr` - the renderer's WebXR manager. */\n readonly xr: WebXRManagerLike;\n /**\n * `navigator.xr`. Defaults to the global when omitted, and to `null` where\n * there is no global, which is what a Node test sees. A null system reports\n * every session request as `\"unsupported\"`.\n */\n readonly xrSystem?: WebXRSystemLike | null;\n /**\n * The renderer, or anything else with `setAnimationLoop`. Given one, the\n * adapter owns the loop: {@link WebXRRuntimeAdapter.start} binds it and each\n * callback runs {@link WebXRRuntimeAdapter.tick}. Omit it where something\n * else already owns the loop: call {@link WebXRRuntimeAdapter.tick} by hand\n * from whatever per-frame hook that host provides for the same gate, frame\n * count and `renderTick`, or {@link WebXRRuntimeAdapter.emitFrame} directly\n * for the frame fan-out alone.\n */\n readonly host?: AnimationLoopHostLike;\n /**\n * Given a scheduler, each owned frame also emits the `renderTick` channel\n * with `source: \"three\"`, exactly as `ThreeRenderLoopBridge` does, so an app\n * needs one loop owner rather than two.\n */\n readonly scheduler?: IScheduler;\n /**\n * Supplies the `XRSessionInit` for a mode - required and optional features.\n * Called once per request; the default sends no init at all.\n */\n readonly sessionInit?: (mode: SessionMode) => unknown;\n /**\n * The service manager whose focus and pause signals follow the session's\n * visibility, as `ServiceBridgeSystem`'s `manager` option does on IWSDK and\n * the native adapter's `manager` option does on a native host: both fire on\n * every change of focus, `paused` being `!focused`. Gated ONLY while a\n * session is live - `XRSession.visibilityState` via `visibilitychange` -\n * because unlike IWSDK and native, this adapter also serves a desktop page\n * with no session at all, and that page keeps ticking exactly as it did\n * before this option existed.\n */\n readonly manager?: WebXRFocusSink;\n /**\n * Builds the `XRRigidTransform` {@link WebXRRuntimeAdapter.recentre} hands to\n * `getOffsetReferenceSpace`. Defaults to `globalThis.XRRigidTransform` when\n * present; with neither, `recentre()` does nothing.\n */\n readonly rigidTransform?: WebXRRigidTransformFactory;\n}\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n]);\n\n/**\n * Map `XRSession.visibilityState` onto the facet's vocabulary. An unrecognised\n * value is reported as `\"hidden\"`, because treating an unknown state as visible\n * 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/**\n * WebXR reports a blocked request through the error name: `NotAllowedError`\n * when the user or the permission prompt refused, `SecurityError` when the\n * permissions policy did. Both are a refusal rather than a fault.\n */\nfunction toFailureReason(error: unknown): SessionFailureReason {\n const name = (error as { readonly name?: unknown } | null | undefined)?.name;\n\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n return \"denied\";\n }\n\n return \"error\";\n}\n\n/** `navigator.xr`, read defensively: there is no navigator in a Node test. */\nfunction defaultXRSystem(): WebXRSystemLike | null {\n const globalNavigator = (globalThis as { navigator?: { xr?: WebXRSystemLike } }).navigator;\n\n return globalNavigator?.xr ?? null;\n}\n\nexport class WebXRRuntimeAdapter 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 endWaiters = new Set<() => void>();\n\n private readonly xr: WebXRManagerLike;\n private readonly xrSystem: WebXRSystemLike | null;\n private readonly host: AnimationLoopHostLike | undefined;\n private readonly scheduler: IScheduler | undefined;\n private readonly sessionInit: ((mode: SessionMode) => unknown) | undefined;\n private readonly manager: WebXRFocusSink | undefined;\n private readonly rigidTransform: WebXRRigidTransformFactory | undefined;\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 mode: SessionMode | null = null;\n private boundSession: WebXRSessionLike | null = null;\n private sessionListeners: {\n readonly type: WebXRSessionEventType;\n readonly listener: WebXREventListener;\n }[] = [];\n private animationLoopBound = false;\n private frame = 0;\n private lastTimestamp = 0;\n /**\n * Whether the current session is visible/focused; `undefined` while there is\n * no session, meaning ticking is not gated at all. Reset to `undefined` on\n * session end, so a desktop page resumes exactly as before a session ever\n * existed.\n */\n private focused: boolean | undefined;\n\n private readonly onManagerSessionStart: WebXREventListener = () => {\n this.handleSessionStart();\n };\n\n private readonly onManagerSessionEnd: WebXREventListener = () => {\n this.handleSessionEnd();\n };\n\n /** Session lifecycle over `navigator.xr` and the renderer's XR manager. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n getMode: () => (this.sessionState === \"active\" ? this.mode : null),\n isSupported: (mode) => this.checkSupported(mode),\n request: (mode, options) => this.requestSession(mode, options),\n end: () => this.endSession(),\n recentre: () => this.recentreViewer(),\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(options: WebXRRuntimeAdapterOptions) {\n this.xr = options.xr;\n this.xrSystem = options.xrSystem === undefined ? defaultXRSystem() : options.xrSystem;\n this.host = options.host;\n this.scheduler = options.scheduler;\n this.sessionInit = options.sessionInit;\n this.manager = options.manager;\n this.rigidTransform = options.rigidTransform ?? defaultRigidTransformFactory();\n\n this.xr.addEventListener(\"sessionstart\", this.onManagerSessionStart);\n this.xr.addEventListener(\"sessionend\", this.onManagerSessionEnd);\n\n const session = this.xr.getSession();\n\n if (session) {\n this.attachSession(session);\n this.sessionState = \"active\";\n }\n\n this.derived = deriveCapabilities(session);\n this.capabilities = this.derived;\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 session the renderer is presenting, or null in 2D. */\n public getSession(): WebXRSessionLike | null {\n return this.boundSession;\n }\n\n /**\n * Bind the animation loop, if this adapter was given a host: each callback\n * runs {@link WebXRRuntimeAdapter.tick}. With no host this does nothing -\n * something else owns the loop, and drives {@link WebXRRuntimeAdapter.tick}\n * or {@link WebXRRuntimeAdapter.emitFrame} itself.\n *\n * three.js routes `setAnimationLoop` through the session's own\n * `requestAnimationFrame` while presenting, so one call covers both the 2D\n * page and the headset.\n */\n public start(): void {\n if (this.animationLoopBound) {\n return;\n }\n\n this.animationLoopBound = true;\n this.host?.setAnimationLoop((timestamp) => this.tick(timestamp));\n }\n\n /** Release the animation loop. Safe to call when it was never bound. */\n public stop(): void {\n if (!this.animationLoopBound) {\n return;\n }\n\n this.animationLoopBound = false;\n this.host?.setAnimationLoop(null);\n }\n\n /** Push one frame to every subscriber. Call this when you own the loop. */\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 * Re-read the renderer's session and publish any capability change. The\n * adapter does this itself on every session and input-source event; call it\n * directly after the host enables a feature mid-session, or after a host that\n * raises no events changes what it presents.\n */\n public refreshCapabilities(): void {\n const session = this.xr.getSession();\n\n if (session) {\n this.attachSession(session);\n this.updateDerived(session);\n return;\n }\n\n if (this.boundSession) {\n this.handleSessionEnd();\n return;\n }\n\n this.updateDerived(null);\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 WebXRRuntimeAdapter.clearCapabilityOverrides} or\n * {@link WebXRRuntimeAdapter.dispose}.\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 /**\n * Release everything the adapter holds: the animation loop, both sets of host\n * event listeners, any in-flight `end()`, the manual overrides and every\n * listener. It does not end the session - the app owns that decision.\n */\n public dispose(): void {\n this.stop();\n this.xr.removeEventListener(\"sessionstart\", this.onManagerSessionStart);\n this.xr.removeEventListener(\"sessionend\", this.onManagerSessionEnd);\n this.detachSession();\n this.settleEndWaiters();\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n /**\n * Run one frame step: the visibility gate, the focus/pause signals, the one\n * frame count, {@link WebXRRuntimeAdapter.emitFrame} and `renderTick`. This\n * is what {@link WebXRRuntimeAdapter.start} binds to the owned animation\n * loop, and it is public so a host that owns its OWN loop - one that will\n * never call `start()` because it already calls `setAnimationLoop` itself,\n * such as an XR Blocks app - can drive the exact same step by hand from\n * whatever per-frame hook that host provides, rather than reimplementing the\n * gate, the frame count or the `renderTick` shape.\n *\n * `timestampMs` is the loop's own clock reading, the same value `start()`\n * passes from `setAnimationLoop`'s callback.\n */\n public tick(timestampMs: number): void {\n const deltaMs =\n this.lastTimestamp === 0 ? FIRST_FRAME_DELTA_MS : timestampMs - this.lastTimestamp;\n\n this.lastTimestamp = timestampMs;\n\n const session = this.boundSession;\n\n if (session) {\n this.setFocused(toSessionVisibility(session.visibilityState) === \"visible\");\n\n if (!this.focused) {\n // Gated: a live session that is not visible. Skip this tick entirely -\n // no frame, no renderTick, no advance of the frame count - as IWSDK's\n // bridge skips an unfocused frame. A page with no session at all never\n // reaches this branch, so it is never gated.\n return;\n }\n }\n\n this.frame += 1;\n\n // `FrameInfo.delta` is seconds; the scheduler's `LifecycleContext` is in\n // milliseconds, which is the unit `ThreeRenderLoopBridge` already emits.\n this.emitFrame(timestampMs, deltaMs / 1000, this.frame);\n\n const context: LifecycleContext = {\n timestamp: timestampMs,\n deltaTime: deltaMs,\n frame: this.frame,\n source: \"three\",\n };\n\n this.scheduler?.emit(\"renderTick\", context);\n }\n\n /** Emit focus and pause on a change, as `ServiceBridgeSystem` does. */\n private setFocused(focused: boolean): void {\n if (focused === this.focused) {\n return;\n }\n\n this.focused = focused;\n this.manager?.emitFocusChange(focused);\n this.manager?.emitPauseChange({ paused: !focused });\n }\n\n /**\n * Subscribe to one session's own events. The listeners close over the session\n * they belong to, so nothing here has to re-check which session is live.\n */\n private attachSession(session: WebXRSessionLike): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.detachSession();\n this.boundSession = session;\n this.sessionListeners = [\n { type: \"end\", listener: () => this.handleSessionEnd() },\n {\n type: \"visibilitychange\",\n listener: () => this.notifyVisibility(toSessionVisibility(session.visibilityState)),\n },\n { type: \"inputsourceschange\", listener: () => this.updateDerived(session) },\n ];\n\n for (const entry of this.sessionListeners) {\n session.addEventListener(entry.type, entry.listener);\n }\n }\n\n private detachSession(): void {\n const session = this.boundSession;\n\n if (!session) {\n return;\n }\n\n for (const entry of this.sessionListeners) {\n session.removeEventListener(entry.type, entry.listener);\n }\n\n this.sessionListeners = [];\n this.boundSession = null;\n }\n\n private handleSessionStart(): void {\n const session = this.xr.getSession();\n\n if (!session) {\n return;\n }\n\n this.attachSession(session);\n this.setSessionState(\"active\");\n this.updateDerived(session);\n this.notifyVisibility(toSessionVisibility(session.visibilityState));\n }\n\n /**\n * One session ending raises both the session's `end` event and the manager's\n * `sessionend`, so this runs twice per session and is written to be\n * idempotent.\n */\n private handleSessionEnd(): void {\n if (!this.boundSession && this.sessionState === \"none\") {\n return;\n }\n\n this.detachSession();\n this.setSessionState(\"none\");\n this.updateDerived(null);\n this.notifyVisibility(\"non-immersive\");\n this.settleEndWaiters();\n\n // The session that was gating ticks is gone. A page with no session is\n // never gated, so if it had been paused, restore focus now rather than\n // leaving the manager believing it still is.\n if (this.focused === false) {\n this.manager?.emitFocusChange(true);\n this.manager?.emitPauseChange({ paused: false });\n }\n\n this.focused = undefined;\n }\n\n private updateDerived(session: CapabilitySessionLike | null): void {\n this.derived = deriveCapabilities(session);\n this.publishCapabilities();\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 /** Answers `isSupported()` off `navigator.xr`; never rejects, unlike `openSession`'s own check. */\n private async checkSupported(mode: SessionMode): Promise<boolean> {\n const system = this.xrSystem;\n\n if (!system) {\n return false;\n }\n\n try {\n return await system.isSessionSupported(mode);\n } catch {\n return false;\n }\n }\n\n /**\n * Make the viewer's current floor position and yaw the new origin, by\n * offsetting the manager's reference space. Does nothing when the manager\n * carries none of the optional members this needs, when there is no live\n * frame or reference space, or when no `XRRigidTransform` constructor is\n * available - see {@link WebXRRuntimeAdapterOptions.rigidTransform}.\n */\n private recentreViewer(): void {\n const { getReferenceSpace, setReferenceSpace, getFrame } = this.xr;\n const factory = this.rigidTransform;\n\n if (!getReferenceSpace || !setReferenceSpace || !getFrame || !factory) {\n return;\n }\n\n const space = getReferenceSpace.call(this.xr);\n const frame = getFrame.call(this.xr);\n\n if (!space || !frame) {\n return;\n }\n\n const pose = frame.getViewerPose(space);\n\n if (!pose) {\n return;\n }\n\n const offset = recentreOffset({\n position: [pose.transform.position.x, pose.transform.position.y, pose.transform.position.z],\n orientation: [\n pose.transform.orientation.x,\n pose.transform.orientation.y,\n pose.transform.orientation.z,\n pose.transform.orientation.w,\n ],\n });\n\n const transform = factory(\n { x: offset.position[0], y: offset.position[1], z: offset.position[2] },\n { x: offset.orientation[0], y: offset.orientation[1], z: offset.orientation[2], w: offset.orientation[3] },\n );\n\n setReferenceSpace.call(this.xr, space.getOffsetReferenceSpace(transform));\n }\n\n private async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n if (this.mode === mode) {\n return { ok: true };\n }\n\n // End-and-request: three.js always has an end() to call while a session\n // is live, so the switch is always possible.\n await this.endSession();\n }\n\n const system = this.xrSystem;\n\n if (!system) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<SessionResult>((resolve) => {\n timer = setTimeout(() => resolve({ ok: false, reason: \"timeout\" }), timeoutMs);\n });\n\n const result = await Promise.race([this.openSession(system, mode, options), timeout]);\n clearTimeout(timer);\n\n if (!result.ok) {\n this.setSessionState(\"none\");\n return result;\n }\n\n this.mode = mode;\n this.setSessionState(\"active\");\n return result;\n }\n\n /**\n * Negotiate the session and hand it to the renderer. Nothing here throws at\n * the caller: a headset that is absent, refused or broken is a normal runtime\n * condition, so every outcome comes back as a {@link SessionResult}.\n *\n * The request's own features are merged over the `sessionInit` hook's result\n * by the core's `mergeSessionInit`, so they add to the app's defaults rather\n * than replacing them. A request that names none passes the hook's result\n * through untouched.\n */\n private async openSession(\n system: WebXRSystemLike,\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n try {\n const supported = await system.isSessionSupported(mode);\n\n if (!supported) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const init = mergeSessionInit(this.sessionInit?.(mode), options);\n const session = await system.requestSession(mode, init);\n this.xr.setReferenceSpaceType?.(SESSION_REFERENCE_SPACE);\n await this.xr.setSession(session);\n\n // A manager that raises `sessionstart` has already attached this session;\n // one that does not is attached here, so both host styles behave alike.\n this.attachSession(session);\n this.updateDerived(session);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, reason: toFailureReason(error), error };\n }\n }\n\n private async endSession(): Promise<void> {\n const session = this.boundSession;\n\n if (!session) {\n return;\n }\n\n this.setSessionState(\"ending\");\n\n const ended = new Promise<void>((resolve) => {\n this.endWaiters.add(resolve);\n });\n\n try {\n await session.end();\n } catch {\n // Ending is best effort. The `end` event, not this promise, is what the\n // adapter acts on, and a host that refuses still has to be reported.\n }\n\n // The event has normally arrived by now and this is a no-op. A host that\n // raises none would otherwise leave the caller waiting forever, so the\n // settled `end()` call is the fallback signal.\n this.handleSessionEnd();\n\n await ended;\n }\n\n private settleEndWaiters(): void {\n const waiters = Array.from(this.endWaiters);\n this.endWaiters.clear();\n waiters.forEach((resolve) => resolve());\n }\n\n private notifyVisibility(visibility: SessionVisibility): void {\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n\n if (state === \"none\") {\n this.mode = null;\n }\n\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/service-framework-three",
3
- "version": "1.0.2-preview.0",
3
+ "version": "1.0.2-preview.2",
4
4
  "description": "three.js render-loop bindings for the Reality Collective TypeScript Service Framework.",
5
5
  "author": "Reality Collective",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
36
36
  },
37
37
  "dependencies": {
38
- "@realitycollective/service-framework": "^1.0.2-preview.0"
38
+ "@realitycollective/service-framework": "^1.0.2-preview.2"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -p tsconfig.build.json",