@realitycollective/service-framework-iwsdk 1.0.2-preview.1 → 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 +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/iwsdk-adapter.d.ts +21 -2
- package/dist/iwsdk-adapter.js +65 -3
- package/dist/iwsdk-adapter.js.map +1 -1
- package/dist/iwsdk-host.d.ts +42 -0
- package/dist/iwsdk-host.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,11 @@ A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that work
|
|
|
20
20
|
- `LifecycleContext` now documents its units: `timestamp` and `deltaTime` in milliseconds, `frame` counted from 1, and `source` naming the binding.
|
|
21
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
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.
|
|
23
28
|
|
|
24
29
|
### Changed
|
|
25
30
|
|
|
@@ -29,6 +34,7 @@ A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that work
|
|
|
29
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.
|
|
30
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.
|
|
31
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.
|
|
32
38
|
|
|
33
39
|
### Fixed
|
|
34
40
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, MockRuntimeAdapter, RUNTIME_ADAPTER_FACETS, SnapshotService, } from "@realitycollective/service-framework";
|
|
8
8
|
export type { AdapterCapabilities, CapabilitiesListener, FrameInfo, FrameListener, RuntimeAdapter, RuntimeAdapterFacet, ServiceContext, SessionFacet, SessionFailureReason, SessionMode, SessionRequestOptions, SessionResult, SessionState, SessionVisibility, SnapshotListener, Unsubscribe, } from "@realitycollective/service-framework";
|
|
9
|
-
export type { CreateSystemLike, IWSDKDepthSensingFlagLike, IWSDKFeatureFlagLike, IWSDKInputSourceLike, IWSDKSessionEventListener, IWSDKSessionEventType, IWSDKSessionLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKWorldLike, IWSDKXRFeatureOptionsLike, IWSDKXROptionsLike, } from "./iwsdk-host.js";
|
|
9
|
+
export type { CreateSystemLike, IWSDKDepthSensingFlagLike, IWSDKFeatureFlagLike, IWSDKInputSourceLike, IWSDKObject3DLike, IWSDKPlayerLike, IWSDKPlayerSpaceEntitiesLike, IWSDKQuaternionLike, IWSDKSessionEventListener, IWSDKSessionEventType, IWSDKSessionLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKVector3Like, IWSDKWorldLike, IWSDKXRFeatureOptionsLike, IWSDKXROptionsLike, IWSDKXRSystemLike, } from "./iwsdk-host.js";
|
|
10
10
|
export { IWSDK_FEATURE_KEYS, toIWSDKFeatures } from "./iwsdk-features.js";
|
|
11
11
|
export type { IWSDKFeatureMapping } from "./iwsdk-features.js";
|
|
12
12
|
export { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
13
|
+
export type { IWSDKAdapterOptions } from "./iwsdk-adapter.js";
|
|
13
14
|
export { makeServiceBridgeSystem } from "./service-bridge-system.js";
|
|
14
15
|
export type { ServiceBridgeSystemOptions } from "./service-bridge-system.js";
|
|
15
16
|
export { startServiceRuntime } from "./bootstrap.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,GAChB,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,GAChB,MAAM,sCAAsC,CAAC;AA0C9C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * The runtime-adapter contract, the snapshot service base and the headless mock\n * moved into `@realitycollective/service-framework` in 1.0.1: none of them ever\n * touched IWSDK, and every host binding needs them. They are re-exported here\n * unchanged so existing imports from this package keep working.\n */\nexport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n MockRuntimeAdapter,\n RUNTIME_ADAPTER_FACETS,\n SnapshotService,\n} from \"@realitycollective/service-framework\";\nexport type {\n AdapterCapabilities,\n CapabilitiesListener,\n FrameInfo,\n FrameListener,\n RuntimeAdapter,\n RuntimeAdapterFacet,\n ServiceContext,\n SessionFacet,\n SessionFailureReason,\n SessionMode,\n SessionRequestOptions,\n SessionResult,\n SessionState,\n SessionVisibility,\n SnapshotListener,\n Unsubscribe,\n} from \"@realitycollective/service-framework\";\n\nexport type {\n CreateSystemLike,\n IWSDKDepthSensingFlagLike,\n IWSDKFeatureFlagLike,\n IWSDKInputSourceLike,\n IWSDKObject3DLike,\n IWSDKPlayerLike,\n IWSDKPlayerSpaceEntitiesLike,\n IWSDKQuaternionLike,\n IWSDKSessionEventListener,\n IWSDKSessionEventType,\n IWSDKSessionLike,\n IWSDKSignalLike,\n IWSDKSystemConstructor,\n IWSDKSystemLike,\n IWSDKVector3Like,\n IWSDKWorldLike,\n IWSDKXRFeatureOptionsLike,\n IWSDKXROptionsLike,\n IWSDKXRSystemLike,\n} from \"./iwsdk-host.js\";\n\nexport { IWSDK_FEATURE_KEYS, toIWSDKFeatures } from \"./iwsdk-features.js\";\nexport type { IWSDKFeatureMapping } from \"./iwsdk-features.js\";\n\nexport { IWSDKAdapter } from \"./iwsdk-adapter.js\";\nexport type { IWSDKAdapterOptions } from \"./iwsdk-adapter.js\";\n\nexport { makeServiceBridgeSystem } from \"./service-bridge-system.js\";\nexport type { ServiceBridgeSystemOptions } from \"./service-bridge-system.js\";\n\nexport { startServiceRuntime } from \"./bootstrap.js\";\nexport type { ServiceRuntime } from \"./bootstrap.js\";\n"]}
|
package/dist/iwsdk-adapter.d.ts
CHANGED
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
* `exitXR` entry points.
|
|
12
12
|
*/
|
|
13
13
|
import { type AdapterCapabilities, type CapabilitiesListener, type FrameListener, type RuntimeAdapter, type SessionFacet, type Unsubscribe } from "@realitycollective/service-framework";
|
|
14
|
-
import type { IWSDKWorldLike } from "./iwsdk-host.js";
|
|
14
|
+
import type { IWSDKWorldLike, IWSDKXRSystemLike } from "./iwsdk-host.js";
|
|
15
|
+
/** Options for {@link IWSDKAdapter}, beyond the `World` it binds to. */
|
|
16
|
+
export interface IWSDKAdapterOptions {
|
|
17
|
+
/**
|
|
18
|
+
* `navigator.xr`, the slice `isSupported()` reads. Defaults to the global
|
|
19
|
+
* when omitted, and to `null` where there is no global, which is what a
|
|
20
|
+
* Node test sees - `isSupported` then resolves `false` for every mode.
|
|
21
|
+
*/
|
|
22
|
+
readonly xrSystem?: IWSDKXRSystemLike | null;
|
|
23
|
+
}
|
|
15
24
|
export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
16
25
|
private readonly world;
|
|
17
26
|
private readonly frameListeners;
|
|
@@ -19,10 +28,12 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
19
28
|
private readonly stateListeners;
|
|
20
29
|
private readonly visibilityListeners;
|
|
21
30
|
private readonly pendingWaits;
|
|
31
|
+
private readonly xrSystem;
|
|
22
32
|
private derived;
|
|
23
33
|
private overrides;
|
|
24
34
|
private capabilities;
|
|
25
35
|
private sessionState;
|
|
36
|
+
private mode;
|
|
26
37
|
private unsubscribeVisibility;
|
|
27
38
|
private boundSession;
|
|
28
39
|
/**
|
|
@@ -32,7 +43,7 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
32
43
|
private readonly onInputSourcesChange;
|
|
33
44
|
/** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */
|
|
34
45
|
readonly session: SessionFacet;
|
|
35
|
-
constructor(world: IWSDKWorldLike);
|
|
46
|
+
constructor(world: IWSDKWorldLike, options?: IWSDKAdapterOptions);
|
|
36
47
|
onFrame(listener: FrameListener): Unsubscribe;
|
|
37
48
|
getCapabilities(): AdapterCapabilities;
|
|
38
49
|
onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
|
|
@@ -81,6 +92,14 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
81
92
|
* `end()` own the state while they run, so the signal does not fight them.
|
|
82
93
|
*/
|
|
83
94
|
private syncSessionState;
|
|
95
|
+
/** Answers `isSupported()` off `navigator.xr`; never rejects. */
|
|
96
|
+
private checkSupported;
|
|
97
|
+
/**
|
|
98
|
+
* Move the player rig so the head's current floor position and yaw become
|
|
99
|
+
* the origin, leaving the head's pose local to the rig untouched. Does
|
|
100
|
+
* nothing when the world carries no rig, or no head entity, to move.
|
|
101
|
+
*/
|
|
102
|
+
private recentrePlayerRig;
|
|
84
103
|
private requestSession;
|
|
85
104
|
private endSession;
|
|
86
105
|
/**
|
package/dist/iwsdk-adapter.js
CHANGED
|
@@ -10,8 +10,13 @@
|
|
|
10
10
|
* exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and
|
|
11
11
|
* `exitXR` entry points.
|
|
12
12
|
*/
|
|
13
|
-
import { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, deriveCapabilities, } from "@realitycollective/service-framework";
|
|
13
|
+
import { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, deriveCapabilities, recentreRig, } from "@realitycollective/service-framework";
|
|
14
14
|
import { toIWSDKFeatures } from "./iwsdk-features.js";
|
|
15
|
+
/** `navigator.xr`, read defensively: there is no navigator in a Node test. */
|
|
16
|
+
function defaultXRSystem() {
|
|
17
|
+
const globalNavigator = globalThis.navigator;
|
|
18
|
+
return globalNavigator?.xr ?? null;
|
|
19
|
+
}
|
|
15
20
|
/** How often the session waits re-check the world when the signal is silent. */
|
|
16
21
|
const SESSION_POLL_INTERVAL_MS = 50;
|
|
17
22
|
const SESSION_VISIBILITY_VALUES = new Set([
|
|
@@ -38,10 +43,12 @@ export class IWSDKAdapter {
|
|
|
38
43
|
stateListeners = new Set();
|
|
39
44
|
visibilityListeners = new Set();
|
|
40
45
|
pendingWaits = new Set();
|
|
46
|
+
xrSystem;
|
|
41
47
|
derived = DEFAULT_CAPABILITIES;
|
|
42
48
|
overrides = {};
|
|
43
49
|
capabilities = DEFAULT_CAPABILITIES;
|
|
44
50
|
sessionState = "none";
|
|
51
|
+
mode = null;
|
|
45
52
|
unsubscribeVisibility;
|
|
46
53
|
boundSession = null;
|
|
47
54
|
/**
|
|
@@ -54,8 +61,11 @@ export class IWSDKAdapter {
|
|
|
54
61
|
/** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */
|
|
55
62
|
session = {
|
|
56
63
|
getState: () => this.sessionState,
|
|
64
|
+
getMode: () => (this.sessionState === "active" ? this.mode : null),
|
|
65
|
+
isSupported: (mode) => this.checkSupported(mode),
|
|
57
66
|
request: (mode, options) => this.requestSession(mode, options),
|
|
58
67
|
end: () => this.endSession(),
|
|
68
|
+
recentre: () => this.recentrePlayerRig(),
|
|
59
69
|
onStateChange: (listener) => {
|
|
60
70
|
this.stateListeners.add(listener);
|
|
61
71
|
return () => {
|
|
@@ -69,8 +79,9 @@ export class IWSDKAdapter {
|
|
|
69
79
|
};
|
|
70
80
|
},
|
|
71
81
|
};
|
|
72
|
-
constructor(world) {
|
|
82
|
+
constructor(world, options = {}) {
|
|
73
83
|
this.world = world;
|
|
84
|
+
this.xrSystem = options.xrSystem === undefined ? defaultXRSystem() : options.xrSystem;
|
|
74
85
|
this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {
|
|
75
86
|
this.handleVisibilityChange(value);
|
|
76
87
|
});
|
|
@@ -201,9 +212,56 @@ export class IWSDKAdapter {
|
|
|
201
212
|
}
|
|
202
213
|
this.setSessionState(this.hasSession() ? "active" : "none");
|
|
203
214
|
}
|
|
215
|
+
/** Answers `isSupported()` off `navigator.xr`; never rejects. */
|
|
216
|
+
async checkSupported(mode) {
|
|
217
|
+
const system = this.xrSystem;
|
|
218
|
+
if (!system) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
return await system.isSessionSupported(mode);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Move the player rig so the head's current floor position and yaw become
|
|
230
|
+
* the origin, leaving the head's pose local to the rig untouched. Does
|
|
231
|
+
* nothing when the world carries no rig, or no head entity, to move.
|
|
232
|
+
*/
|
|
233
|
+
recentrePlayerRig() {
|
|
234
|
+
const rig = this.world.player?.object3D;
|
|
235
|
+
const head = this.world.playerSpaceEntities?.head?.object3D;
|
|
236
|
+
if (!rig || !head) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const next = recentreRig({
|
|
240
|
+
position: [rig.position.x, rig.position.y, rig.position.z],
|
|
241
|
+
orientation: [rig.quaternion.x, rig.quaternion.y, rig.quaternion.z, rig.quaternion.w],
|
|
242
|
+
}, {
|
|
243
|
+
position: [head.position.x, head.position.y, head.position.z],
|
|
244
|
+
orientation: [head.quaternion.x, head.quaternion.y, head.quaternion.z, head.quaternion.w],
|
|
245
|
+
});
|
|
246
|
+
rig.position.x = next.position[0];
|
|
247
|
+
rig.position.y = next.position[1];
|
|
248
|
+
rig.position.z = next.position[2];
|
|
249
|
+
rig.quaternion.x = next.orientation[0];
|
|
250
|
+
rig.quaternion.y = next.orientation[1];
|
|
251
|
+
rig.quaternion.z = next.orientation[2];
|
|
252
|
+
rig.quaternion.w = next.orientation[3];
|
|
253
|
+
}
|
|
204
254
|
async requestSession(mode, options) {
|
|
205
255
|
if (this.sessionState === "active") {
|
|
206
|
-
|
|
256
|
+
if (this.mode === mode) {
|
|
257
|
+
return { ok: true };
|
|
258
|
+
}
|
|
259
|
+
// End-and-request: a world with no `exitXR` cannot end its own session,
|
|
260
|
+
// so the switch is refused outright and the live session is left alone.
|
|
261
|
+
if (!this.world.exitXR) {
|
|
262
|
+
return { ok: false, reason: "unsupported" };
|
|
263
|
+
}
|
|
264
|
+
await this.endSession();
|
|
207
265
|
}
|
|
208
266
|
const launch = this.world.launchXR;
|
|
209
267
|
if (!launch) {
|
|
@@ -229,6 +287,7 @@ export class IWSDKAdapter {
|
|
|
229
287
|
this.setSessionState("none");
|
|
230
288
|
return { ok: false, reason: "timeout" };
|
|
231
289
|
}
|
|
290
|
+
this.mode = mode;
|
|
232
291
|
this.setSessionState("active");
|
|
233
292
|
this.refreshCapabilities();
|
|
234
293
|
return { ok: true };
|
|
@@ -284,6 +343,9 @@ export class IWSDKAdapter {
|
|
|
284
343
|
return;
|
|
285
344
|
}
|
|
286
345
|
this.sessionState = state;
|
|
346
|
+
if (state === "none") {
|
|
347
|
+
this.mode = null;
|
|
348
|
+
}
|
|
287
349
|
this.stateListeners.forEach((listener) => listener(state));
|
|
288
350
|
}
|
|
289
351
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so\n * frames are pushed in rather than pulled: the `ServiceBridgeSystem` (a normal\n * IWSDK system) calls {@link IWSDKAdapter.emitFrame} once per frame and the\n * adapter notifies subscribed services.\n *\n * Capabilities are derived from the live session on construction and on every\n * visibility change, through the core's shared `deriveCapabilities`, with a\n * manual override layer on top. Session lifecycle is\n * exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and\n * `exitXR` entry points.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type FrameInfo,\n type FrameListener,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { toIWSDKFeatures } from \"./iwsdk-features.js\";\nimport type {\n IWSDKSessionEventListener,\n IWSDKSessionLike,\n IWSDKWorldLike,\n} from \"./iwsdk-host.js\";\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\n/** How often the session waits re-check the world when the signal is silent. */\nconst SESSION_POLL_INTERVAL_MS = 50;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n \"non-immersive\",\n]);\n\n/**\n * IWSDK's visibility signal carries the WebXR visibility strings. Anything this\n * adapter does not recognise is reported as `\"hidden\"`, because treating an\n * unknown state as visible would keep game logic running when it should not.\n */\nfunction toSessionVisibility(value: unknown): SessionVisibility {\n if (typeof value === \"string\" && SESSION_VISIBILITY_VALUES.has(value)) {\n return value as SessionVisibility;\n }\n\n return \"hidden\";\n}\n\n/** A `request()` or `end()` waiting for the world to reach some state. */\ninterface PendingWait {\n readonly predicate: () => boolean;\n readonly resolve: (settled: boolean) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n readonly interval: ReturnType<typeof setInterval>;\n}\n\nexport class IWSDKAdapter implements RuntimeAdapter {\n private readonly frameListeners = new Set<FrameListener>();\n private readonly capabilitiesListeners = new Set<CapabilitiesListener>();\n private readonly stateListeners = new Set<SessionStateListener>();\n private readonly visibilityListeners = new Set<SessionVisibilityListener>();\n private readonly pendingWaits = new Set<PendingWait>();\n\n private derived: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private overrides: Partial<AdapterCapabilities> = {};\n private capabilities: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private sessionState: SessionState = \"none\";\n private unsubscribeVisibility: Unsubscribe | undefined;\n private boundSession: IWSDKSessionLike | null = null;\n\n /**\n * Pre-bound, because `removeEventListener` has to be handed the same function\n * `addEventListener` got.\n */\n private readonly onInputSourcesChange: IWSDKSessionEventListener = () => {\n this.refreshCapabilities();\n };\n\n /** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n request: (mode, options) => this.requestSession(mode, options),\n end: () => this.endSession(),\n onStateChange: (listener) => {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n },\n onVisibilityChange: (listener) => {\n this.visibilityListeners.add(listener);\n return () => {\n this.visibilityListeners.delete(listener);\n };\n },\n };\n\n public constructor(private readonly world: IWSDKWorldLike) {\n this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {\n this.handleVisibilityChange(value);\n });\n\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.capabilities = this.derived;\n this.sessionState = this.hasSession() ? \"active\" : \"none\";\n }\n\n public onFrame(listener: FrameListener): Unsubscribe {\n this.frameListeners.add(listener);\n return () => {\n this.frameListeners.delete(listener);\n };\n }\n\n public getCapabilities(): AdapterCapabilities {\n return this.capabilities;\n }\n\n public onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe {\n this.capabilitiesListeners.add(listener);\n return () => {\n this.capabilitiesListeners.delete(listener);\n };\n }\n\n /** The IWSDK `World` this adapter is bound to. */\n public getWorld(): IWSDKWorldLike {\n return this.world;\n }\n\n /**\n * Re-read the live session and publish any capability change. The adapter\n * does this itself whenever the visibility signal fires and whenever the\n * session's input sources change; call it directly on a host whose signal has\n * no `subscribe`, or after the host enables a feature mid-session.\n */\n public refreshCapabilities(): void {\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number, frame?: number): void {\n const info: FrameInfo = frame === undefined ? { timestamp, delta } : { timestamp, delta, frame };\n this.frameListeners.forEach((listener) => listener(info));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, the\n * session's own listener, any in-flight session wait, the manual overrides\n * and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\n this.unbindSession();\n\n for (const wait of Array.from(this.pendingWaits)) {\n this.settleWait(wait, false);\n }\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private hasSession(): boolean {\n return Boolean(this.world.session);\n }\n\n /**\n * Follow one session's `inputsourceschange` event, so `handTracking` updates\n * when the player picks the controllers up or puts them down. IWSDK's world\n * carries a real `XRSession`, which raises the event; a host that reports a\n * session without listener methods is bound all the same and simply never\n * pushes, which is what `refreshCapabilities()` remains for.\n */\n private bindSession(session: IWSDKSessionLike | null): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.unbindSession();\n this.boundSession = session;\n session?.addEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n }\n\n private unbindSession(): void {\n this.boundSession?.removeEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n this.boundSession = null;\n }\n\n private publishCapabilities(): void {\n const next: AdapterCapabilities = { ...this.derived, ...this.overrides };\n const current = this.capabilities;\n\n if (\n next.immersive === current.immersive &&\n next.handTracking === current.handTracking &&\n next.planeDetection === current.planeDetection &&\n next.passthrough === current.passthrough &&\n next.environmentBlendMode === current.environmentBlendMode\n ) {\n return;\n }\n\n this.capabilities = next;\n this.capabilitiesListeners.forEach((listener) => listener(next));\n }\n\n private handleVisibilityChange(value: unknown): void {\n this.refreshCapabilities();\n this.syncSessionState();\n this.checkWaits();\n\n const visibility = toSessionVisibility(value);\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n /**\n * Follow the world when nothing local is mid-transition. `request()` and\n * `end()` own the state while they run, so the signal does not fight them.\n */\n private syncSessionState(): void {\n if (this.sessionState === \"requesting\" || this.sessionState === \"ending\") {\n return;\n }\n\n this.setSessionState(this.hasSession() ? \"active\" : \"none\");\n }\n\n private async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n return { ok: true };\n }\n\n const launch = this.world.launchXR;\n\n if (!launch) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n const { features } = toIWSDKFeatures(options);\n\n try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged. The\n // request's feature strings become IWSDK's structured flags; `launchXR`\n // merges what it is given over the world's `xrDefaults`, so a request\n // that names no features leaves the app's defaults alone.\n launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so\n * frames are pushed in rather than pulled: the `ServiceBridgeSystem` (a normal\n * IWSDK system) calls {@link IWSDKAdapter.emitFrame} once per frame and the\n * adapter notifies subscribed services.\n *\n * Capabilities are derived from the live session on construction and on every\n * visibility change, through the core's shared `deriveCapabilities`, with a\n * manual override layer on top. Session lifecycle is\n * exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and\n * `exitXR` entry points.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n recentreRig,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type FrameInfo,\n type FrameListener,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { toIWSDKFeatures } from \"./iwsdk-features.js\";\nimport type {\n IWSDKSessionEventListener,\n IWSDKSessionLike,\n IWSDKWorldLike,\n IWSDKXRSystemLike,\n} from \"./iwsdk-host.js\";\n\n/** Options for {@link IWSDKAdapter}, beyond the `World` it binds to. */\nexport interface IWSDKAdapterOptions {\n /**\n * `navigator.xr`, the slice `isSupported()` reads. Defaults to the global\n * when omitted, and to `null` where there is no global, which is what a\n * Node test sees - `isSupported` then resolves `false` for every mode.\n */\n readonly xrSystem?: IWSDKXRSystemLike | null;\n}\n\n/** `navigator.xr`, read defensively: there is no navigator in a Node test. */\nfunction defaultXRSystem(): IWSDKXRSystemLike | null {\n const globalNavigator = (globalThis as { navigator?: { xr?: IWSDKXRSystemLike } }).navigator;\n\n return globalNavigator?.xr ?? null;\n}\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\n/** How often the session waits re-check the world when the signal is silent. */\nconst SESSION_POLL_INTERVAL_MS = 50;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n \"non-immersive\",\n]);\n\n/**\n * IWSDK's visibility signal carries the WebXR visibility strings. Anything this\n * adapter does not recognise is reported as `\"hidden\"`, because treating an\n * unknown state as visible would keep game logic running when it should not.\n */\nfunction toSessionVisibility(value: unknown): SessionVisibility {\n if (typeof value === \"string\" && SESSION_VISIBILITY_VALUES.has(value)) {\n return value as SessionVisibility;\n }\n\n return \"hidden\";\n}\n\n/** A `request()` or `end()` waiting for the world to reach some state. */\ninterface PendingWait {\n readonly predicate: () => boolean;\n readonly resolve: (settled: boolean) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n readonly interval: ReturnType<typeof setInterval>;\n}\n\nexport class IWSDKAdapter implements RuntimeAdapter {\n private readonly frameListeners = new Set<FrameListener>();\n private readonly capabilitiesListeners = new Set<CapabilitiesListener>();\n private readonly stateListeners = new Set<SessionStateListener>();\n private readonly visibilityListeners = new Set<SessionVisibilityListener>();\n private readonly pendingWaits = new Set<PendingWait>();\n\n private readonly xrSystem: IWSDKXRSystemLike | null;\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 unsubscribeVisibility: Unsubscribe | undefined;\n private boundSession: IWSDKSessionLike | null = null;\n\n /**\n * Pre-bound, because `removeEventListener` has to be handed the same function\n * `addEventListener` got.\n */\n private readonly onInputSourcesChange: IWSDKSessionEventListener = () => {\n this.refreshCapabilities();\n };\n\n /** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n 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.recentrePlayerRig(),\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(\n private readonly world: IWSDKWorldLike,\n options: IWSDKAdapterOptions = {},\n ) {\n this.xrSystem = options.xrSystem === undefined ? defaultXRSystem() : options.xrSystem;\n this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {\n this.handleVisibilityChange(value);\n });\n\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.capabilities = this.derived;\n this.sessionState = this.hasSession() ? \"active\" : \"none\";\n }\n\n public onFrame(listener: FrameListener): Unsubscribe {\n this.frameListeners.add(listener);\n return () => {\n this.frameListeners.delete(listener);\n };\n }\n\n public getCapabilities(): AdapterCapabilities {\n return this.capabilities;\n }\n\n public onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe {\n this.capabilitiesListeners.add(listener);\n return () => {\n this.capabilitiesListeners.delete(listener);\n };\n }\n\n /** The IWSDK `World` this adapter is bound to. */\n public getWorld(): IWSDKWorldLike {\n return this.world;\n }\n\n /**\n * Re-read the live session and publish any capability change. The adapter\n * does this itself whenever the visibility signal fires and whenever the\n * session's input sources change; call it directly on a host whose signal has\n * no `subscribe`, or after the host enables a feature mid-session.\n */\n public refreshCapabilities(): void {\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number, frame?: number): void {\n const info: FrameInfo = frame === undefined ? { timestamp, delta } : { timestamp, delta, frame };\n this.frameListeners.forEach((listener) => listener(info));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, the\n * session's own listener, any in-flight session wait, the manual overrides\n * and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\n this.unbindSession();\n\n for (const wait of Array.from(this.pendingWaits)) {\n this.settleWait(wait, false);\n }\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private hasSession(): boolean {\n return Boolean(this.world.session);\n }\n\n /**\n * Follow one session's `inputsourceschange` event, so `handTracking` updates\n * when the player picks the controllers up or puts them down. IWSDK's world\n * carries a real `XRSession`, which raises the event; a host that reports a\n * session without listener methods is bound all the same and simply never\n * pushes, which is what `refreshCapabilities()` remains for.\n */\n private bindSession(session: IWSDKSessionLike | null): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.unbindSession();\n this.boundSession = session;\n session?.addEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n }\n\n private unbindSession(): void {\n this.boundSession?.removeEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n this.boundSession = null;\n }\n\n private publishCapabilities(): void {\n const next: AdapterCapabilities = { ...this.derived, ...this.overrides };\n const current = this.capabilities;\n\n if (\n next.immersive === current.immersive &&\n next.handTracking === current.handTracking &&\n next.planeDetection === current.planeDetection &&\n next.passthrough === current.passthrough &&\n next.environmentBlendMode === current.environmentBlendMode\n ) {\n return;\n }\n\n this.capabilities = next;\n this.capabilitiesListeners.forEach((listener) => listener(next));\n }\n\n private handleVisibilityChange(value: unknown): void {\n this.refreshCapabilities();\n this.syncSessionState();\n this.checkWaits();\n\n const visibility = toSessionVisibility(value);\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n /**\n * Follow the world when nothing local is mid-transition. `request()` and\n * `end()` own the state while they run, so the signal does not fight them.\n */\n private syncSessionState(): void {\n if (this.sessionState === \"requesting\" || this.sessionState === \"ending\") {\n return;\n }\n\n this.setSessionState(this.hasSession() ? \"active\" : \"none\");\n }\n\n /** Answers `isSupported()` off `navigator.xr`; never rejects. */\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 * Move the player rig so the head's current floor position and yaw become\n * the origin, leaving the head's pose local to the rig untouched. Does\n * nothing when the world carries no rig, or no head entity, to move.\n */\n private recentrePlayerRig(): void {\n const rig = this.world.player?.object3D;\n const head = this.world.playerSpaceEntities?.head?.object3D;\n\n if (!rig || !head) {\n return;\n }\n\n const next = recentreRig(\n {\n position: [rig.position.x, rig.position.y, rig.position.z],\n orientation: [rig.quaternion.x, rig.quaternion.y, rig.quaternion.z, rig.quaternion.w],\n },\n {\n position: [head.position.x, head.position.y, head.position.z],\n orientation: [head.quaternion.x, head.quaternion.y, head.quaternion.z, head.quaternion.w],\n },\n );\n\n rig.position.x = next.position[0];\n rig.position.y = next.position[1];\n rig.position.z = next.position[2];\n rig.quaternion.x = next.orientation[0];\n rig.quaternion.y = next.orientation[1];\n rig.quaternion.z = next.orientation[2];\n rig.quaternion.w = next.orientation[3];\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: a world with no `exitXR` cannot end its own session,\n // so the switch is refused outright and the live session is left alone.\n if (!this.world.exitXR) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n await this.endSession();\n }\n\n const launch = this.world.launchXR;\n\n if (!launch) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n const { features } = toIWSDKFeatures(options);\n\n try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged. The\n // request's feature strings become IWSDK's structured flags; `launchXR`\n // merges what it is given over the world's `xrDefaults`, so a request\n // that names no features leaves the app's defaults alone.\n launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.mode = mode;\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n\n if (state === \"none\") {\n this.mode = null;\n }\n\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
package/dist/iwsdk-host.d.ts
CHANGED
|
@@ -89,6 +89,44 @@ export interface IWSDKXROptionsLike {
|
|
|
89
89
|
readonly sessionMode?: string;
|
|
90
90
|
readonly features?: IWSDKXRFeatureOptionsLike;
|
|
91
91
|
}
|
|
92
|
+
/** A three.js `Vector3`-shaped position, in metres. */
|
|
93
|
+
export interface IWSDKVector3Like {
|
|
94
|
+
x: number;
|
|
95
|
+
y: number;
|
|
96
|
+
z: number;
|
|
97
|
+
}
|
|
98
|
+
/** A three.js `Quaternion`-shaped orientation. */
|
|
99
|
+
export interface IWSDKQuaternionLike {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
z: number;
|
|
103
|
+
w: number;
|
|
104
|
+
}
|
|
105
|
+
/** The slice of a three.js `Object3D` `recentre()` reads and writes. */
|
|
106
|
+
export interface IWSDKObject3DLike {
|
|
107
|
+
position: IWSDKVector3Like;
|
|
108
|
+
quaternion: IWSDKQuaternionLike;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* IWSDK's player rig entity. `object3D` is the rig's own world transform -
|
|
112
|
+
* what `recentre()` moves. Absent on a host that carries no rig at all.
|
|
113
|
+
*/
|
|
114
|
+
export interface IWSDKPlayerLike {
|
|
115
|
+
readonly object3D?: IWSDKObject3DLike;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* IWSDK's named player-space entities. `head.object3D` is the head's pose
|
|
119
|
+
* local to the player rig - read by `recentre()`, never written.
|
|
120
|
+
*/
|
|
121
|
+
export interface IWSDKPlayerSpaceEntitiesLike {
|
|
122
|
+
readonly head: {
|
|
123
|
+
readonly object3D?: IWSDKObject3DLike;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** The slice of `navigator.xr` the session facet's `isSupported` reads. */
|
|
127
|
+
export interface IWSDKXRSystemLike {
|
|
128
|
+
isSessionSupported(mode: string): Promise<boolean>;
|
|
129
|
+
}
|
|
92
130
|
export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
93
131
|
readonly visibilityState: IWSDKSignalLike<TVisibility>;
|
|
94
132
|
/** The live XR session, or null/absent when the app is running in 2D. */
|
|
@@ -97,6 +135,10 @@ export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
|
97
135
|
launchXR?(options?: IWSDKXROptionsLike): void;
|
|
98
136
|
/** IWSDK's session exit point. Absent on a host that cannot end one. */
|
|
99
137
|
exitXR?(): void;
|
|
138
|
+
/** The player rig - `recentre()`'s write. Absent on a host with no rig. */
|
|
139
|
+
readonly player?: IWSDKPlayerLike;
|
|
140
|
+
/** Named player-space entities, including the head - `recentre()`'s read. */
|
|
141
|
+
readonly playerSpaceEntities?: IWSDKPlayerSpaceEntitiesLike;
|
|
100
142
|
}
|
|
101
143
|
/** The per-frame entry point IWSDK invokes on a registered system. */
|
|
102
144
|
export interface IWSDKSystemLike {
|
package/dist/iwsdk-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iwsdk-host.js","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG","sourcesContent":["/**\n * Structural contracts for the slice of `@iwsdk/core` this shim touches.\n *\n * Mirrors the approach the three.js and Babylon.js bridges take with their\n * engine packages: the shim never imports `@iwsdk/core` directly, so it builds\n * and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK\n * works without bumping this package. Consumers pass the real `World`,\n * `createSystem` and `VisibilityState.Visible` value through\n * {@link makeServiceBridgeSystem}.\n *\n * Every member added here is optional, so a world that only carries the\n * visibility signal - the shape the 1.0.x bridge accepted - still type-checks.\n */\n\nimport type {\n CapabilityInputSourceLike,\n CapabilitySessionLike,\n} from \"@realitycollective/service-framework\";\n\n/**\n * IWSDK exposes reactive values as `{ value }` signals. The bridge only reads\n * `.value`; the adapter also subscribes when the signal supports it, which is\n * how it learns that a session came or went without polling every frame.\n */\nexport interface IWSDKSignalLike<TValue> {\n readonly value: TValue;\n /** Optional push notification. Returns an unsubscribe handle. */\n subscribe?(listener: (value: TValue) => void): () => void;\n}\n\n/** One entry of `XRSession.inputSources`; `hand` is set for a tracked hand. */\nexport type IWSDKInputSourceLike = CapabilityInputSourceLike;\n\n/** The event an `XRSession` raises that the adapter listens for. */\nexport type IWSDKSessionEventType = \"inputsourceschange\";\n\n/** Session event callback. The adapter reads the session, not the event. */\nexport type IWSDKSessionEventListener = (event?: unknown) => void;\n\n/**\n * The slice of the live `XRSession` the adapter reads to derive capabilities.\n * It is the core's {@link CapabilitySessionLike} with `inputSources` required,\n * because an IWSDK world always carries the collection, and the adapter hands\n * the session straight to the core's `deriveCapabilities`.\n *\n * The two listener members are what IWSDK's own session carries, because it is\n * a real `XRSession`. They are optional all the same, so a world faked in a\n * test still satisfies the type without them, and the adapter guards for their\n * absence rather than assuming a full session.\n */\nexport interface IWSDKSessionLike extends CapabilitySessionLike {\n readonly inputSources: Iterable<IWSDKInputSourceLike>;\n addEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;\n removeEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;\n}\n\n/**\n * The slice of an IWSDK `World` the bridge and adapter read: the visibility\n * signal, the live session, and the session entry points.\n */\n/**\n * IWSDK's `FeatureFlag`. `true` requests the feature as optional,\n * `{ required: true }` as required, and `false` or absent does not request it.\n */\nexport type IWSDKFeatureFlagLike = boolean | { readonly required?: boolean };\n\n/**\n * IWSDK's `DepthSensingFlag` - a {@link IWSDKFeatureFlagLike} that can also\n * carry the depth usage and format preferences.\n */\nexport type IWSDKDepthSensingFlagLike =\n | boolean\n | {\n readonly required?: boolean;\n readonly usage?: \"cpu-optimized\" | \"gpu-optimized\";\n readonly format?: \"luminance-alpha\" | \"float32\";\n };\n\n/**\n * IWSDK's `XRFeatureOptions`. IWSDK takes structured flags rather than the\n * WebXR feature strings, which is why the adapter maps between the two.\n */\nexport interface IWSDKXRFeatureOptionsLike {\n readonly handTracking?: IWSDKFeatureFlagLike;\n readonly anchors?: IWSDKFeatureFlagLike;\n readonly hitTest?: IWSDKFeatureFlagLike;\n readonly planeDetection?: IWSDKFeatureFlagLike;\n readonly meshDetection?: IWSDKFeatureFlagLike;\n readonly depthSensing?: IWSDKDepthSensingFlagLike;\n readonly layers?: IWSDKFeatureFlagLike;\n readonly unbounded?: IWSDKFeatureFlagLike;\n}\n\n/**\n * The slice of IWSDK `XROptions` the session facet passes to `launchXR`.\n * `sessionMode` carries the WebXR mode string (\"immersive-vr\", \"immersive-ar\",\n * \"inline\"), which is also the value of IWSDK's `SessionMode` enum members.\n * `launchXR` merges what it is given over the world's `xrDefaults`, so anything\n * left out here keeps the app's default.\n */\nexport interface IWSDKXROptionsLike {\n readonly sessionMode?: string;\n readonly features?: IWSDKXRFeatureOptionsLike;\n}\n\nexport interface IWSDKWorldLike<TVisibility = unknown> {\n readonly visibilityState: IWSDKSignalLike<TVisibility>;\n /** The live XR session, or null/absent when the app is running in 2D. */\n readonly session?: IWSDKSessionLike | null;\n /** IWSDK's session entry point. Absent on a host that cannot start one. */\n launchXR?(options?: IWSDKXROptionsLike): void;\n /** IWSDK's session exit point. Absent on a host that cannot end one. */\n exitXR?(): void;\n}\n\n/** The per-frame entry point IWSDK invokes on a registered system. */\nexport interface IWSDKSystemLike {\n update(delta: number, time: number): void;\n}\n\n/** Constructor shape of the base class IWSDK's `createSystem` returns. */\nexport type IWSDKSystemConstructor = new (...args: unknown[]) => IWSDKSystemLike;\n\n/**\n * Structural shape of IWSDK's `createSystem` factory. `createSystem(schema)`\n * returns a base system class that the bridge extends; the bridge passes an\n * empty schema because it queries no ECS components.\n */\nexport type CreateSystemLike = (schema?: Record<string, unknown>) => IWSDKSystemConstructor;\n"]}
|
|
1
|
+
{"version":3,"file":"iwsdk-host.js","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG","sourcesContent":["/**\n * Structural contracts for the slice of `@iwsdk/core` this shim touches.\n *\n * Mirrors the approach the three.js and Babylon.js bridges take with their\n * engine packages: the shim never imports `@iwsdk/core` directly, so it builds\n * and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK\n * works without bumping this package. Consumers pass the real `World`,\n * `createSystem` and `VisibilityState.Visible` value through\n * {@link makeServiceBridgeSystem}.\n *\n * Every member added here is optional, so a world that only carries the\n * visibility signal - the shape the 1.0.x bridge accepted - still type-checks.\n */\n\nimport type {\n CapabilityInputSourceLike,\n CapabilitySessionLike,\n} from \"@realitycollective/service-framework\";\n\n/**\n * IWSDK exposes reactive values as `{ value }` signals. The bridge only reads\n * `.value`; the adapter also subscribes when the signal supports it, which is\n * how it learns that a session came or went without polling every frame.\n */\nexport interface IWSDKSignalLike<TValue> {\n readonly value: TValue;\n /** Optional push notification. Returns an unsubscribe handle. */\n subscribe?(listener: (value: TValue) => void): () => void;\n}\n\n/** One entry of `XRSession.inputSources`; `hand` is set for a tracked hand. */\nexport type IWSDKInputSourceLike = CapabilityInputSourceLike;\n\n/** The event an `XRSession` raises that the adapter listens for. */\nexport type IWSDKSessionEventType = \"inputsourceschange\";\n\n/** Session event callback. The adapter reads the session, not the event. */\nexport type IWSDKSessionEventListener = (event?: unknown) => void;\n\n/**\n * The slice of the live `XRSession` the adapter reads to derive capabilities.\n * It is the core's {@link CapabilitySessionLike} with `inputSources` required,\n * because an IWSDK world always carries the collection, and the adapter hands\n * the session straight to the core's `deriveCapabilities`.\n *\n * The two listener members are what IWSDK's own session carries, because it is\n * a real `XRSession`. They are optional all the same, so a world faked in a\n * test still satisfies the type without them, and the adapter guards for their\n * absence rather than assuming a full session.\n */\nexport interface IWSDKSessionLike extends CapabilitySessionLike {\n readonly inputSources: Iterable<IWSDKInputSourceLike>;\n addEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;\n removeEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;\n}\n\n/**\n * The slice of an IWSDK `World` the bridge and adapter read: the visibility\n * signal, the live session, and the session entry points.\n */\n/**\n * IWSDK's `FeatureFlag`. `true` requests the feature as optional,\n * `{ required: true }` as required, and `false` or absent does not request it.\n */\nexport type IWSDKFeatureFlagLike = boolean | { readonly required?: boolean };\n\n/**\n * IWSDK's `DepthSensingFlag` - a {@link IWSDKFeatureFlagLike} that can also\n * carry the depth usage and format preferences.\n */\nexport type IWSDKDepthSensingFlagLike =\n | boolean\n | {\n readonly required?: boolean;\n readonly usage?: \"cpu-optimized\" | \"gpu-optimized\";\n readonly format?: \"luminance-alpha\" | \"float32\";\n };\n\n/**\n * IWSDK's `XRFeatureOptions`. IWSDK takes structured flags rather than the\n * WebXR feature strings, which is why the adapter maps between the two.\n */\nexport interface IWSDKXRFeatureOptionsLike {\n readonly handTracking?: IWSDKFeatureFlagLike;\n readonly anchors?: IWSDKFeatureFlagLike;\n readonly hitTest?: IWSDKFeatureFlagLike;\n readonly planeDetection?: IWSDKFeatureFlagLike;\n readonly meshDetection?: IWSDKFeatureFlagLike;\n readonly depthSensing?: IWSDKDepthSensingFlagLike;\n readonly layers?: IWSDKFeatureFlagLike;\n readonly unbounded?: IWSDKFeatureFlagLike;\n}\n\n/**\n * The slice of IWSDK `XROptions` the session facet passes to `launchXR`.\n * `sessionMode` carries the WebXR mode string (\"immersive-vr\", \"immersive-ar\",\n * \"inline\"), which is also the value of IWSDK's `SessionMode` enum members.\n * `launchXR` merges what it is given over the world's `xrDefaults`, so anything\n * left out here keeps the app's default.\n */\nexport interface IWSDKXROptionsLike {\n readonly sessionMode?: string;\n readonly features?: IWSDKXRFeatureOptionsLike;\n}\n\n/** A three.js `Vector3`-shaped position, in metres. */\nexport interface IWSDKVector3Like {\n x: number;\n y: number;\n z: number;\n}\n\n/** A three.js `Quaternion`-shaped orientation. */\nexport interface IWSDKQuaternionLike {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\n/** The slice of a three.js `Object3D` `recentre()` reads and writes. */\nexport interface IWSDKObject3DLike {\n position: IWSDKVector3Like;\n quaternion: IWSDKQuaternionLike;\n}\n\n/**\n * IWSDK's player rig entity. `object3D` is the rig's own world transform -\n * what `recentre()` moves. Absent on a host that carries no rig at all.\n */\nexport interface IWSDKPlayerLike {\n readonly object3D?: IWSDKObject3DLike;\n}\n\n/**\n * IWSDK's named player-space entities. `head.object3D` is the head's pose\n * local to the player rig - read by `recentre()`, never written.\n */\nexport interface IWSDKPlayerSpaceEntitiesLike {\n readonly head: { readonly object3D?: IWSDKObject3DLike };\n}\n\n/** The slice of `navigator.xr` the session facet's `isSupported` reads. */\nexport interface IWSDKXRSystemLike {\n isSessionSupported(mode: string): Promise<boolean>;\n}\n\nexport interface IWSDKWorldLike<TVisibility = unknown> {\n readonly visibilityState: IWSDKSignalLike<TVisibility>;\n /** The live XR session, or null/absent when the app is running in 2D. */\n readonly session?: IWSDKSessionLike | null;\n /** IWSDK's session entry point. Absent on a host that cannot start one. */\n launchXR?(options?: IWSDKXROptionsLike): void;\n /** IWSDK's session exit point. Absent on a host that cannot end one. */\n exitXR?(): void;\n /** The player rig - `recentre()`'s write. Absent on a host with no rig. */\n readonly player?: IWSDKPlayerLike;\n /** Named player-space entities, including the head - `recentre()`'s read. */\n readonly playerSpaceEntities?: IWSDKPlayerSpaceEntitiesLike;\n}\n\n/** The per-frame entry point IWSDK invokes on a registered system. */\nexport interface IWSDKSystemLike {\n update(delta: number, time: number): void;\n}\n\n/** Constructor shape of the base class IWSDK's `createSystem` returns. */\nexport type IWSDKSystemConstructor = new (...args: unknown[]) => IWSDKSystemLike;\n\n/**\n * Structural shape of IWSDK's `createSystem` factory. `createSystem(schema)`\n * returns a base system class that the bridge extends; the bridge passes an\n * empty schema because it queries no ECS components.\n */\nexport type CreateSystemLike = (schema?: Record<string, unknown>) => IWSDKSystemConstructor;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realitycollective/service-framework-iwsdk",
|
|
3
|
-
"version": "1.0.2-preview.
|
|
3
|
+
"version": "1.0.2-preview.2",
|
|
4
4
|
"description": "Meta IWSDK (WebXR) frame-source bindings for the Reality Collective TypeScript Service Framework.",
|
|
5
5
|
"author": "Reality Collective",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@realitycollective/service-framework": "^1.0.2-preview.
|
|
39
|
+
"@realitycollective/service-framework": "^1.0.2-preview.2"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.build.json",
|