@realitycollective/service-framework-iwsdk 1.0.2-preview.1 → 1.0.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 +12 -1
- package/README.md +14 -8
- 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 +66 -3
- package/dist/iwsdk-adapter.js.map +1 -1
- package/dist/iwsdk-host.d.ts +62 -0
- package/dist/iwsdk-host.js +19 -1
- package/dist/iwsdk-host.js.map +1 -1
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,15 @@ Change log for the Reality Collective Service Framework for TypeScript. All pack
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
|
|
6
6
|
|
|
7
|
-
## [1.0.2]
|
|
7
|
+
## [1.0.2] - 2026-10-01
|
|
8
8
|
|
|
9
9
|
A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that works the same on the web and on a native host.
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- A native test harness in `harness/native`, a small app for the conversion pipeline that runs on a Quest headset. In `kits` mode it runs `runtimeAdapterContractCases()`, `renderTickContractCases()` and `hostIOContractCases()` over `NativeRuntimeAdapter` and `createNativeHostIO`, plus a boot with no global `AbortController`, against the shell's test host, or this repository's reference fake under Node. In `play` mode it logs session, visibility, pause, focus, capability and telemetry changes from the live host, and reads a packed file through `HostIO`. `npm run harness:compile` typechecks and bundles it through the engine and browser-global gates and compiles it to Hermes bytecode; CI compiles it and never runs it.
|
|
14
|
+
- `@realitycollective/service-framework-iwsdk` - IWSDK 1.0.0's player rig: `world.player` is the `XROrigin` itself (an `Object3D` with the head as its `head` child), not an entity carrying `object3D`. `IWSDKPlayerLike` now accepts both shapes (`IWSDKPlayerEntityLike`, `IWSDKXROriginLike`), `playerRig` and `playerHead` read either, and `recentre()` moves a 1.0 rig; an IWSDK 1.0 `World` type-checks against `IWSDKWorldLike` again.
|
|
15
|
+
- `@realitycollective/service-framework` - `RuntimeAdapterDriver.sessionStart` states the mode-switch semantics the contract case relies on: called while a session is live, the host hands over the new session once the adapter has ended the old one itself, so a driver whose host starts sessions at once queues the start until the adapter's `endSession` completes or its next request arrives.
|
|
13
16
|
- `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
17
|
- `@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.
|
|
15
18
|
- `@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.
|
|
@@ -20,6 +23,11 @@ A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that work
|
|
|
20
23
|
- `LifecycleContext` now documents its units: `timestamp` and `deltaTime` in milliseconds, `frame` counted from 1, and `source` naming the binding.
|
|
21
24
|
- `@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
25
|
- `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.
|
|
26
|
+
- `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`.
|
|
27
|
+
- `@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.
|
|
28
|
+
- `@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.
|
|
29
|
+
- `@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.
|
|
30
|
+
- `@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
31
|
|
|
24
32
|
### Changed
|
|
25
33
|
|
|
@@ -29,6 +37,8 @@ A native platform, beside IWSDK, three.js and Babylon.js, and byte I/O that work
|
|
|
29
37
|
- `@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
38
|
- `@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
39
|
- `@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.
|
|
40
|
+
- **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.
|
|
41
|
+
- Dependencies on other Reality Collective repositories now follow one rule, checked by `scripts/rc-dependencies.mjs`. Going into `main`, each names the release npm tags `latest` and never a preview. Going into any other branch, each names the newest version on npm, release or preview. The rule covers every `package.json` in the repository (published packages, demos, harnesses and examples) and every copy `package-lock.json` records. A range passes only when its lowest version is the target, so `^0.1.8` passes for 0.1.8 and `^0.1.4` does not. CI runs it before the install, against the branch a pull request targets, and the publish workflow runs it before publishing. `release.mjs status` reports it. `release.mjs prepare` stops before cutting a release when a dependency names an unreleased preview of another repository, because that repository has to release first, and moves any range that is only behind. Other dependencies are out of scope. The script stays identical across the Reality Collective repositories.
|
|
32
42
|
|
|
33
43
|
### Fixed
|
|
34
44
|
|
|
@@ -104,5 +114,6 @@ First release. Six packages, published together.
|
|
|
104
114
|
- `@realitycollective/service-framework-iwsdk` - Meta IWSDK (WebXR) bindings. IWSDK owns its own render loop, so this package is a passive frame source: one system relays each frame to services and maps `visibilityState` onto focus and pause, so services pause when the headset comes off. Services depend only on `RuntimeAdapter`, never on `@iwsdk/core`.
|
|
105
115
|
- A worked example in every package's `Examples/` folder, and two runnable Vite apps in `runtime-examples/`.
|
|
106
116
|
|
|
117
|
+
[1.0.2]: https://github.com/realitycollective/com.realitycollective.service-framework.ts/releases/tag/v1.0.2
|
|
107
118
|
[1.0.1]: https://github.com/realitycollective/com.realitycollective.service-framework.ts/releases/tag/v1.0.1
|
|
108
119
|
[1.0.0]: https://github.com/realitycollective/com.realitycollective.service-framework.ts/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Instead it is a **passive frame source**: it receives frames rather than produci
|
|
|
16
16
|
|
|
17
17
|
IWSDK calls one system, `ServiceBridgeSystem`, once per frame. That system hands the frame to every subscribed service through `IWSDKAdapter`. It also maps IWSDK's `visibilityState` onto the manager's focus and pause signals, so services pause when the headset comes off.
|
|
18
18
|
|
|
19
|
-
```
|
|
19
|
+
```text
|
|
20
20
|
IWSDK World (render loop, XR session, input, ECS)
|
|
21
21
|
│ world.registerSystem(makeServiceBridgeSystem({ ... }))
|
|
22
22
|
▼
|
|
@@ -38,11 +38,15 @@ Each focused frame goes out twice, on the two channels a service can be written
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
+
## IWSDK 1.0
|
|
42
|
+
|
|
43
|
+
The adapter accepts IWSDK 1.0.0's player rig (`world.player` is the `XROrigin` itself, with the head as its `head` child) as well as the earlier entity form. `IWSDKPlayerLike` names both, and the adapter reads the rig and the head from either. `session.recentre()` moves the rig by the core's `recentreRig` rule. `IWSDKAdapterOptions.xrSystem` is the `navigator.xr` slice `session.isSupported` reads.
|
|
44
|
+
|
|
41
45
|
## No hard dependency on `@iwsdk/core`
|
|
42
46
|
|
|
43
|
-
Like the three.js and Babylon.js bridges keep their renderer packages at arm's length, this package **never imports `@iwsdk/core`**. The IWSDK primitives the bridge needs - `createSystem` and the `VisibilityState.Visible` value - are passed in by the consumer (who owns IWSDK). This keeps the package tree-shakeable, version-tolerant
|
|
47
|
+
Like the three.js and Babylon.js bridges keep their renderer packages at arm's length, this package **never imports `@iwsdk/core`**. The IWSDK primitives the bridge needs - `createSystem` and the `VisibilityState.Visible` value - are passed in by the consumer (who owns IWSDK). This keeps the package tree-shakeable, version-tolerant from IWSDK `0.4.x` to `1.0`, and trivially mockable in unit tests.
|
|
44
48
|
|
|
45
|
-
`@iwsdk/core` is **not declared as a dependency of any kind** - not even an optional peer. Everything this package reads off the world is structurally typed in `iwsdk-host.ts` and passed in: `createSystem`, the `VisibilityState.Visible` value, the visibility signal, and - all optional - `visibilityState.subscribe`, the live `session`, `launchXR` and
|
|
49
|
+
`@iwsdk/core` is **not declared as a dependency of any kind** - not even an optional peer. Everything this package reads off the world is structurally typed in `iwsdk-host.ts` and passed in: `createSystem`, the `VisibilityState.Visible` value, the visibility signal, and - all optional - `visibilityState.subscribe`, the live `session`, `launchXR`, `exitXR` and the player rig. Because the additions are optional, a world that carries nothing but the visibility signal still type-checks and still works; it simply reports no capabilities and no session. Those contracts are the same from 0.4.x to 1.0, apart from the 1.0 player rig described above, which the adapter reads in either form. A peer range here would constrain nothing while still being able to fail a consumer's clean install, so there is deliberately no entry.
|
|
46
50
|
|
|
47
51
|
---
|
|
48
52
|
|
|
@@ -135,7 +139,7 @@ No `@iwsdk/core` import appears anywhere in the test.
|
|
|
135
139
|
|
|
136
140
|
`AdapterCapabilities` (`immersive`, `handTracking`, `planeDetection`, `passthrough`, `environmentBlendMode`) is what gating services read (`adapter.getCapabilities()`) or subscribe to (`adapter.onCapabilitiesChange(cb)` - mirrors `onFrame`, so gates don't poll every frame).
|
|
137
141
|
|
|
138
|
-
`IWSDKAdapter` derives all
|
|
142
|
+
`IWSDKAdapter` derives all five from the live session through `deriveCapabilities(session)`, exported by `@realitycollective/service-framework`. The rules live in the core so that every host binding - this one, the three.js `WebXRRuntimeAdapter`, and whatever comes next - reports the same flags for the same session. It derives on construction, every time the world's visibility signal fires, which is when a session comes or goes, and every time the live session raises `inputsourceschange`:
|
|
139
143
|
|
|
140
144
|
| Flag | Derived from |
|
|
141
145
|
| --- | --- |
|
|
@@ -234,11 +238,13 @@ Owned by this package:
|
|
|
234
238
|
| Symbol | Kind | Purpose |
|
|
235
239
|
| --- | --- | --- |
|
|
236
240
|
| `IWSDKAdapter` | class | Production adapter; `emitFrame`, `refreshCapabilities`, `setCapabilities`, `clearCapabilityOverrides`, `session`, `getWorld`, `dispose`. |
|
|
241
|
+
| `IWSDKAdapterOptions` | interface | `{ xrSystem? }` - the `navigator.xr` slice `session.isSupported` reads. |
|
|
237
242
|
| `makeServiceBridgeSystem` | factory | Returns the IWSDK `ServiceBridgeSystem` class; pumps `onFrame` and `renderTick`. |
|
|
238
243
|
| `ServiceBridgeSystemOptions` | interface | `{ adapter, manager, world, createSystem, visibleState }`. |
|
|
239
244
|
| `startServiceRuntime` / `ServiceRuntime` | function / interface | Bootstraps `{ manager, adapter }` from a profile factory. |
|
|
240
245
|
| `toIWSDKFeatures` / `IWSDK_FEATURE_KEYS` / `IWSDKFeatureMapping` | function / const / interface | Maps WebXR feature strings onto IWSDK's structured flags and reports what it could not map. |
|
|
241
|
-
| `IWSDKWorldLike` / `IWSDKSignalLike` / `IWSDKSessionLike` / `IWSDKInputSourceLike` / `IWSDKSessionEventType` / `IWSDKSessionEventListener` / `IWSDKXROptionsLike` / `IWSDKXRFeatureOptionsLike` / `IWSDKFeatureFlagLike` / `IWSDKDepthSensingFlagLike` / `IWSDKSystemLike` / `IWSDKSystemConstructor` / `CreateSystemLike` | types | Structural `@iwsdk/core` contracts (no engine import). |
|
|
246
|
+
| `IWSDKWorldLike` / `IWSDKSignalLike` / `IWSDKSessionLike` / `IWSDKInputSourceLike` / `IWSDKSessionEventType` / `IWSDKSessionEventListener` / `IWSDKXROptionsLike` / `IWSDKXRFeatureOptionsLike` / `IWSDKFeatureFlagLike` / `IWSDKDepthSensingFlagLike` / `IWSDKXRSystemLike` / `IWSDKSystemLike` / `IWSDKSystemConstructor` / `CreateSystemLike` | types | Structural `@iwsdk/core` contracts (no engine import). |
|
|
247
|
+
| `IWSDKPlayerLike` / `IWSDKPlayerEntityLike` / `IWSDKXROriginLike` / `IWSDKPlayerSpaceEntitiesLike` / `IWSDKObject3DLike` / `IWSDKVector3Like` / `IWSDKQuaternionLike` | types | The player rig in both forms (the 1.0 `XROrigin` and the earlier entity), as `recentre()` reads it. |
|
|
242
248
|
|
|
243
249
|
Re-exported from `@realitycollective/service-framework`. None of these ever touched IWSDK, and every host binding needs them, so they moved into the core in 1.0.1. They are re-exported here unchanged, so importing them from this package keeps working; new code should import them from the core package.
|
|
244
250
|
|
|
@@ -248,7 +254,7 @@ Re-exported from `@realitycollective/service-framework`. None of these ever touc
|
|
|
248
254
|
| `FrameInfo` | interface | `{ timestamp, delta }`. |
|
|
249
255
|
| `AdapterCapabilities` / `DEFAULT_CAPABILITIES` | interface / const | XR capability flags; all-false default. |
|
|
250
256
|
| `Unsubscribe` / `FrameListener` / `CapabilitiesListener` | types | Callback / handle aliases. |
|
|
251
|
-
| `SessionFacet` | interface | `getState`, `request`, `end`, `onStateChange`, `onVisibilityChange`. |
|
|
257
|
+
| `SessionFacet` | interface | `getState`, `getMode`, `isSupported`, `request`, `end`, `recentre`, `onStateChange`, `onVisibilityChange`. |
|
|
252
258
|
| `SessionMode` / `SessionState` / `SessionResult` / `SessionFailureReason` / `SessionVisibility` / `SessionRequestOptions` | types | The session facet's vocabulary. |
|
|
253
259
|
| `DEFAULT_SESSION_TIMEOUT_MS` | const | 10000 - the default `request` timeout. |
|
|
254
260
|
| `RUNTIME_ADAPTER_FACETS` / `RuntimeAdapterFacet` | const / type | Every optional facet an adapter can carry; walked by the conformance suite. |
|
|
@@ -260,7 +266,7 @@ Re-exported from `@realitycollective/service-framework`. None of these ever touc
|
|
|
260
266
|
|
|
261
267
|
## Running tests
|
|
262
268
|
|
|
263
|
-
From the
|
|
269
|
+
From the repository root:
|
|
264
270
|
|
|
265
271
|
```bash
|
|
266
272
|
npm test
|
|
@@ -283,4 +289,4 @@ The example mocks the `@iwsdk/core` primitives so it runs in plain Node.js: it d
|
|
|
283
289
|
|
|
284
290
|
## License
|
|
285
291
|
|
|
286
|
-
MIT
|
|
292
|
+
MIT - see [LICENSE](./LICENSE).
|
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, IWSDKPlayerEntityLike, IWSDKPlayerLike, IWSDKXROriginLike, 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;AA4C9C,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 IWSDKPlayerEntityLike,\n IWSDKPlayerLike,\n IWSDKXROriginLike,\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
|
|
14
|
+
import { 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,14 @@
|
|
|
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
|
+
import { playerHead, playerRig, } from "./iwsdk-host.js";
|
|
16
|
+
/** `navigator.xr`, read defensively: there is no navigator in a Node test. */
|
|
17
|
+
function defaultXRSystem() {
|
|
18
|
+
const globalNavigator = globalThis.navigator;
|
|
19
|
+
return globalNavigator?.xr ?? null;
|
|
20
|
+
}
|
|
15
21
|
/** How often the session waits re-check the world when the signal is silent. */
|
|
16
22
|
const SESSION_POLL_INTERVAL_MS = 50;
|
|
17
23
|
const SESSION_VISIBILITY_VALUES = new Set([
|
|
@@ -38,10 +44,12 @@ export class IWSDKAdapter {
|
|
|
38
44
|
stateListeners = new Set();
|
|
39
45
|
visibilityListeners = new Set();
|
|
40
46
|
pendingWaits = new Set();
|
|
47
|
+
xrSystem;
|
|
41
48
|
derived = DEFAULT_CAPABILITIES;
|
|
42
49
|
overrides = {};
|
|
43
50
|
capabilities = DEFAULT_CAPABILITIES;
|
|
44
51
|
sessionState = "none";
|
|
52
|
+
mode = null;
|
|
45
53
|
unsubscribeVisibility;
|
|
46
54
|
boundSession = null;
|
|
47
55
|
/**
|
|
@@ -54,8 +62,11 @@ export class IWSDKAdapter {
|
|
|
54
62
|
/** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */
|
|
55
63
|
session = {
|
|
56
64
|
getState: () => this.sessionState,
|
|
65
|
+
getMode: () => (this.sessionState === "active" ? this.mode : null),
|
|
66
|
+
isSupported: (mode) => this.checkSupported(mode),
|
|
57
67
|
request: (mode, options) => this.requestSession(mode, options),
|
|
58
68
|
end: () => this.endSession(),
|
|
69
|
+
recentre: () => this.recentrePlayerRig(),
|
|
59
70
|
onStateChange: (listener) => {
|
|
60
71
|
this.stateListeners.add(listener);
|
|
61
72
|
return () => {
|
|
@@ -69,8 +80,9 @@ export class IWSDKAdapter {
|
|
|
69
80
|
};
|
|
70
81
|
},
|
|
71
82
|
};
|
|
72
|
-
constructor(world) {
|
|
83
|
+
constructor(world, options = {}) {
|
|
73
84
|
this.world = world;
|
|
85
|
+
this.xrSystem = options.xrSystem === undefined ? defaultXRSystem() : options.xrSystem;
|
|
74
86
|
this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {
|
|
75
87
|
this.handleVisibilityChange(value);
|
|
76
88
|
});
|
|
@@ -201,9 +213,56 @@ export class IWSDKAdapter {
|
|
|
201
213
|
}
|
|
202
214
|
this.setSessionState(this.hasSession() ? "active" : "none");
|
|
203
215
|
}
|
|
216
|
+
/** Answers `isSupported()` off `navigator.xr`; never rejects. */
|
|
217
|
+
async checkSupported(mode) {
|
|
218
|
+
const system = this.xrSystem;
|
|
219
|
+
if (!system) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
return await system.isSessionSupported(mode);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Move the player rig so the head's current floor position and yaw become
|
|
231
|
+
* the origin, leaving the head's pose local to the rig untouched. Does
|
|
232
|
+
* nothing when the world carries no rig, or no head entity, to move.
|
|
233
|
+
*/
|
|
234
|
+
recentrePlayerRig() {
|
|
235
|
+
const rig = playerRig(this.world.player);
|
|
236
|
+
const head = playerHead(this.world.player, this.world.playerSpaceEntities);
|
|
237
|
+
if (!rig || !head) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const next = recentreRig({
|
|
241
|
+
position: [rig.position.x, rig.position.y, rig.position.z],
|
|
242
|
+
orientation: [rig.quaternion.x, rig.quaternion.y, rig.quaternion.z, rig.quaternion.w],
|
|
243
|
+
}, {
|
|
244
|
+
position: [head.position.x, head.position.y, head.position.z],
|
|
245
|
+
orientation: [head.quaternion.x, head.quaternion.y, head.quaternion.z, head.quaternion.w],
|
|
246
|
+
});
|
|
247
|
+
rig.position.x = next.position[0];
|
|
248
|
+
rig.position.y = next.position[1];
|
|
249
|
+
rig.position.z = next.position[2];
|
|
250
|
+
rig.quaternion.x = next.orientation[0];
|
|
251
|
+
rig.quaternion.y = next.orientation[1];
|
|
252
|
+
rig.quaternion.z = next.orientation[2];
|
|
253
|
+
rig.quaternion.w = next.orientation[3];
|
|
254
|
+
}
|
|
204
255
|
async requestSession(mode, options) {
|
|
205
256
|
if (this.sessionState === "active") {
|
|
206
|
-
|
|
257
|
+
if (this.mode === mode) {
|
|
258
|
+
return { ok: true };
|
|
259
|
+
}
|
|
260
|
+
// End-and-request: a world with no `exitXR` cannot end its own session,
|
|
261
|
+
// so the switch is refused outright and the live session is left alone.
|
|
262
|
+
if (!this.world.exitXR) {
|
|
263
|
+
return { ok: false, reason: "unsupported" };
|
|
264
|
+
}
|
|
265
|
+
await this.endSession();
|
|
207
266
|
}
|
|
208
267
|
const launch = this.world.launchXR;
|
|
209
268
|
if (!launch) {
|
|
@@ -229,6 +288,7 @@ export class IWSDKAdapter {
|
|
|
229
288
|
this.setSessionState("none");
|
|
230
289
|
return { ok: false, reason: "timeout" };
|
|
231
290
|
}
|
|
291
|
+
this.mode = mode;
|
|
232
292
|
this.setSessionState("active");
|
|
233
293
|
this.refreshCapabilities();
|
|
234
294
|
return { ok: true };
|
|
@@ -284,6 +344,9 @@ export class IWSDKAdapter {
|
|
|
284
344
|
return;
|
|
285
345
|
}
|
|
286
346
|
this.sessionState = state;
|
|
347
|
+
if (state === "none") {
|
|
348
|
+
this.mode = null;
|
|
349
|
+
}
|
|
287
350
|
this.stateListeners.forEach((listener) => listener(state));
|
|
288
351
|
}
|
|
289
352
|
}
|
|
@@ -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 {\n playerHead,\n playerRig,\n 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 = playerRig(this.world.player);\n const head = playerHead(this.world.player, this.world.playerSpaceEntities);\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,64 @@ 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 0.5'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 IWSDKPlayerEntityLike {
|
|
115
|
+
readonly object3D?: IWSDKObject3DLike;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* IWSDK 1.0's player rig: `world.player` is the `XROrigin` itself, an
|
|
119
|
+
* `Object3D` (its own `position` and `quaternion` are what `recentre()`
|
|
120
|
+
* moves), with the head as its `head` child. The head's pose is local to the
|
|
121
|
+
* rig, read by `recentre()` and never written.
|
|
122
|
+
*/
|
|
123
|
+
export interface IWSDKXROriginLike extends IWSDKObject3DLike {
|
|
124
|
+
readonly head?: IWSDKObject3DLike;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Either shape IWSDK has given `world.player`: the 0.5 entity carrying an
|
|
128
|
+
* `object3D`, or the 1.0 `XROrigin` that IS the object. `recentre()` reads
|
|
129
|
+
* both (`playerRig`, `playerHead`).
|
|
130
|
+
*/
|
|
131
|
+
export type IWSDKPlayerLike = IWSDKPlayerEntityLike | IWSDKXROriginLike;
|
|
132
|
+
/**
|
|
133
|
+
* IWSDK's named player-space entities. `head.object3D` is the head's pose
|
|
134
|
+
* local to the player rig - read by `recentre()`, never written. IWSDK 1.0
|
|
135
|
+
* keeps them (`world.playerSpaceEntities.head`) beside `world.player.head`.
|
|
136
|
+
*/
|
|
137
|
+
export interface IWSDKPlayerSpaceEntitiesLike {
|
|
138
|
+
readonly head: {
|
|
139
|
+
readonly object3D?: IWSDKObject3DLike;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** The rig object `recentre()` moves, from either player shape, or undefined without a rig. */
|
|
143
|
+
export declare function playerRig(player: IWSDKPlayerLike | undefined): IWSDKObject3DLike | undefined;
|
|
144
|
+
/** The head pose `recentre()` reads: the named head entity, else the 1.0 rig's `head` child. */
|
|
145
|
+
export declare function playerHead(player: IWSDKPlayerLike | undefined, playerSpaceEntities: IWSDKPlayerSpaceEntitiesLike | undefined): IWSDKObject3DLike | undefined;
|
|
146
|
+
/** The slice of `navigator.xr` the session facet's `isSupported` reads. */
|
|
147
|
+
export interface IWSDKXRSystemLike {
|
|
148
|
+
isSessionSupported(mode: string): Promise<boolean>;
|
|
149
|
+
}
|
|
92
150
|
export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
93
151
|
readonly visibilityState: IWSDKSignalLike<TVisibility>;
|
|
94
152
|
/** The live XR session, or null/absent when the app is running in 2D. */
|
|
@@ -97,6 +155,10 @@ export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
|
97
155
|
launchXR?(options?: IWSDKXROptionsLike): void;
|
|
98
156
|
/** IWSDK's session exit point. Absent on a host that cannot end one. */
|
|
99
157
|
exitXR?(): void;
|
|
158
|
+
/** The player rig - `recentre()`'s write. Absent on a host with no rig. */
|
|
159
|
+
readonly player?: IWSDKPlayerLike;
|
|
160
|
+
/** Named player-space entities, including the head - `recentre()`'s read. */
|
|
161
|
+
readonly playerSpaceEntities?: IWSDKPlayerSpaceEntitiesLike;
|
|
100
162
|
}
|
|
101
163
|
/** The per-frame entry point IWSDK invokes on a registered system. */
|
|
102
164
|
export interface IWSDKSystemLike {
|
package/dist/iwsdk-host.js
CHANGED
|
@@ -11,5 +11,23 @@
|
|
|
11
11
|
* Every member added here is optional, so a world that only carries the
|
|
12
12
|
* visibility signal - the shape the 1.0.x bridge accepted - still type-checks.
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
/** The rig object `recentre()` moves, from either player shape, or undefined without a rig. */
|
|
15
|
+
export function playerRig(player) {
|
|
16
|
+
if (!player)
|
|
17
|
+
return undefined;
|
|
18
|
+
if ("object3D" in player && player.object3D)
|
|
19
|
+
return player.object3D;
|
|
20
|
+
if ("position" in player && "quaternion" in player)
|
|
21
|
+
return player;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/** The head pose `recentre()` reads: the named head entity, else the 1.0 rig's `head` child. */
|
|
25
|
+
export function playerHead(player, playerSpaceEntities) {
|
|
26
|
+
const named = playerSpaceEntities?.head?.object3D;
|
|
27
|
+
if (named)
|
|
28
|
+
return named;
|
|
29
|
+
if (player && "head" in player)
|
|
30
|
+
return player.head;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
15
33
|
//# sourceMappingURL=iwsdk-host.js.map
|
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;AAoJH,+FAA+F;AAC/F,MAAM,UAAU,SAAS,CAAC,MAAmC;IAC3D,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,UAAU,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IACpE,IAAI,UAAU,IAAI,MAAM,IAAI,YAAY,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAClE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,UAAU,CACxB,MAAmC,EACnC,mBAA6D;IAE7D,MAAM,KAAK,GAAG,mBAAmB,EAAE,IAAI,EAAE,QAAQ,CAAC;IAClD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IACnD,OAAO,SAAS,CAAC;AACnB,CAAC","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 0.5'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 IWSDKPlayerEntityLike {\n readonly object3D?: IWSDKObject3DLike;\n}\n\n/**\n * IWSDK 1.0's player rig: `world.player` is the `XROrigin` itself, an\n * `Object3D` (its own `position` and `quaternion` are what `recentre()`\n * moves), with the head as its `head` child. The head's pose is local to the\n * rig, read by `recentre()` and never written.\n */\nexport interface IWSDKXROriginLike extends IWSDKObject3DLike {\n readonly head?: IWSDKObject3DLike;\n}\n\n/**\n * Either shape IWSDK has given `world.player`: the 0.5 entity carrying an\n * `object3D`, or the 1.0 `XROrigin` that IS the object. `recentre()` reads\n * both (`playerRig`, `playerHead`).\n */\nexport type IWSDKPlayerLike = IWSDKPlayerEntityLike | IWSDKXROriginLike;\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. IWSDK 1.0\n * keeps them (`world.playerSpaceEntities.head`) beside `world.player.head`.\n */\nexport interface IWSDKPlayerSpaceEntitiesLike {\n readonly head: { readonly object3D?: IWSDKObject3DLike };\n}\n\n/** The rig object `recentre()` moves, from either player shape, or undefined without a rig. */\nexport function playerRig(player: IWSDKPlayerLike | undefined): IWSDKObject3DLike | undefined {\n if (!player) return undefined;\n if (\"object3D\" in player && player.object3D) return player.object3D;\n if (\"position\" in player && \"quaternion\" in player) return player;\n return undefined;\n}\n\n/** The head pose `recentre()` reads: the named head entity, else the 1.0 rig's `head` child. */\nexport function playerHead(\n player: IWSDKPlayerLike | undefined,\n playerSpaceEntities: IWSDKPlayerSpaceEntitiesLike | undefined,\n): IWSDKObject3DLike | undefined {\n const named = playerSpaceEntities?.head?.object3D;\n if (named) return named;\n if (player && \"head\" in player) return player.head;\n return undefined;\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
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"meta",
|
|
26
26
|
"xr",
|
|
27
27
|
"realitycollective",
|
|
28
|
-
"typescript"
|
|
28
|
+
"typescript",
|
|
29
|
+
"runtime-adapter",
|
|
30
|
+
"session"
|
|
29
31
|
],
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
"url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@realitycollective/service-framework": "^1.0.2
|
|
41
|
+
"@realitycollective/service-framework": "^1.0.2"
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
44
|
"build": "tsc -p tsconfig.build.json",
|