@realitycollective/service-framework-iwsdk 1.0.1-preview.2 → 1.0.1-preview.3
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 +7 -2
- package/README.md +51 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/iwsdk-adapter.d.ts +21 -5
- package/dist/iwsdk-adapter.js +45 -8
- package/dist/iwsdk-adapter.js.map +1 -1
- package/dist/iwsdk-features.d.ts +48 -0
- package/dist/iwsdk-features.js +43 -0
- package/dist/iwsdk-features.js.map +1 -0
- package/dist/iwsdk-host.d.ts +44 -0
- package/dist/iwsdk-host.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
6
6
|
|
|
7
7
|
## [1.0.1]
|
|
8
8
|
|
|
9
|
-
Packaging, tooling and documentation, plus additive runtime API: the engine-free adapter contracts move into the core, the IWSDK adapter derives capabilities from the live session and gains a session facet, and the three.js and Babylon.js packages gain runtime adapters so a three.js, Babylon or desktop app reaches the same seam. Nothing that existed was removed
|
|
9
|
+
Packaging, tooling and documentation, plus additive runtime API: the engine-free adapter contracts move into the core, the IWSDK adapter derives capabilities from the live session and gains a session facet, and the three.js and Babylon.js packages gain runtime adapters so a three.js, Babylon or desktop app reaches the same seam. Nothing that existed was removed, and one interface changed shape: `AdapterCapabilities` gains a required `environmentBlendMode` key, so code that builds the whole object by hand - an adapter of your own, or a test fake - has one more field to supply. Code that only reads capabilities is unaffected.
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
@@ -17,11 +17,16 @@ Packaging, tooling and documentation, plus additive runtime API: the engine-free
|
|
|
17
17
|
- `BabylonRuntimeAdapter` in `@realitycollective/service-framework-babylon`, with the structural host types it is written against: `BabylonXRExperienceLike` (`WebXRDefaultExperience.baseExperience`), `BabylonSessionManagerLike`, `BabylonXRSessionLike`, `BabylonObservableLike`, `BabylonObserverLike`, `BabylonRuntimeAdapterOptions`, `BabylonXRSessionEventType`, `BabylonXREventListener` and `BabylonWebXRState`, plus the `BABYLON_WEBXR_STATE` and `DEFAULT_REFERENCE_SPACE_TYPE` constants. The package shipped a render-loop bridge and nothing else, so a Babylon app could emit `renderTick` but could not host a service written against `RuntimeAdapter`. The adapter orchestrates the entry points Babylon already provides - the experience helper to negotiate a session, the session manager for the live `XRSession`, `runRenderLoop` for frames - and publishes them through the same interface, with the same members and the same semantics as `WebXRRuntimeAdapter`, so a consumer moving between renderers sees no difference at this seam. Given a `host` it owns the render loop, and given a `scheduler` as well it emits `renderTick` with `source: "babylon"` exactly as `BabylonRenderLoopBridge` does, so an app needs one loop owner rather than two; given no host, the app drives `emitFrame` itself. `referenceSpaceType` defaults to `"local-floor"`, and `sessionInit` supplies the session creation options per mode. Capabilities come from the core's `deriveCapabilities` and are re-derived on `onXRSessionInit`, `onXRSessionEnded` and the session's `inputsourceschange`, with the same override layer, `refreshCapabilities()` and `clearCapabilityOverrides()` the other adapters have. A session started outside the adapter - by Babylon's own enter-XR UI, for instance - is followed through `onStateChangedObservable`, so the facet is correct either way. The same adapter serves a desktop build with no headset: `session.request` returns `{ ok: false, reason: "unsupported" }` where there is no experience or the mode is not supported, and capabilities stay at the all-false defaults. `@babylonjs/core` is still neither imported nor installed: every Babylon shape is structural, and anything a version might move or drop - the session manager, the observables, `isSessionSupportedAsync` - is optional and read through a guard.
|
|
18
18
|
- `FIRST_FRAME_DELTA_MS` from the Babylon.js package - the same 16 ms, now shared by `BabylonRenderLoopBridge` and `BabylonRuntimeAdapter` instead of sitting inline in the bridge.
|
|
19
19
|
- Capability derivation in `IWSDKAdapter`. It reads the live session on construction and on every visibility change: `immersive` when a session exists, `handTracking` from the `hand-tracking` enabled feature or any input source carrying a hand, `planeDetection` from the `plane-detection` enabled feature, and `passthrough` when `environmentBlendMode` is present and is not `opaque`. Subscribers are notified only when a flag actually changes. `setCapabilities` becomes a manual override layer on top, dropped by the new `clearCapabilityOverrides()` or by the new `dispose()`. `refreshCapabilities()` re-derives on demand, for a host whose visibility signal cannot push. Before this, the adapter reported all-false until the app called `setCapabilities` by hand.
|
|
20
|
+
- `inputsourceschange` handling in `IWSDKAdapter`, closing a parity gap with the three.js and Babylon.js adapters, which both bound the event already. The IWSDK adapter re-derived only on the world's visibility signal and at session start and end, so an app that wanted `handTracking` to update when the player put the controllers down had to attach the listener itself. It now attaches one listener to the live session, moves it when a session is replaced, and drops it when the session goes or the adapter is disposed. `IWSDKSessionLike` gains optional `addEventListener` and `removeEventListener` for it, with the two new types `IWSDKSessionEventType` and `IWSDKSessionEventListener`. Both members are optional, so a world faked in a test still satisfies the type, and the adapter guards for their absence: such a host pushes nothing and `refreshCapabilities()` remains the way to nudge it. IWSDK's own world carries a real `XRSession`, so the event arrives there.
|
|
21
|
+
- `requiredFeatures` and `optionalFeatures` on `SessionRequestOptions`, both optional arrays of WebXR feature strings, plus the `mergeSessionInit(init, options)` helper and its `SessionInitLike` type in the core. `SessionRequestOptions` carried only `timeoutMs`, so an app that swapped from `"immersive-vr"` to `"immersive-ar"` mid-session got whatever defaults the host binding was built with, which were chosen for the mode it was leaving. Each binding now folds the request's features over its own: the host's entries come first, the request's are appended, and a feature named twice appears once. A request that names none passes the host's init through untouched, identity included. The three.js and Babylon.js bindings share `mergeSessionInit` rather than writing the same merge twice.
|
|
22
|
+
- `toIWSDKFeatures(options)`, `IWSDK_FEATURE_KEYS` and `IWSDKFeatureMapping` in `@realitycollective/service-framework-iwsdk`, with the structural `IWSDKXRFeatureOptionsLike`, `IWSDKFeatureFlagLike` and `IWSDKDepthSensingFlagLike` types and a `features` member on `IWSDKXROptionsLike`. IWSDK does not take WebXR feature strings: `launchXR` takes an `XROptions` whose `features` is a structured object, one key per feature. The adapter therefore maps `hand-tracking`, `anchors`, `hit-test`, `plane-detection`, `mesh-detection`, `depth-sensing`, `layers` and `unbounded` onto `handTracking`, `anchors`, `hitTest`, `planeDetection`, `meshDetection`, `depthSensing`, `layers` and `unbounded`, setting a required feature as `{ required: true }` and an optional one as `true`; a feature named in both lists comes out required. A string IWSDK has no key for - `local-floor` and `bounded-floor`, which it configures through `referenceSpace`, or `dom-overlay`, which it does not model - is dropped from the request rather than thrown, because the session is still one the host can serve. `toIWSDKFeatures` is exported so an app can read the `unmapped` list and decide for itself before asking. `launchXR` merges what it is given over the world's `xrDefaults`, so a request naming no features leaves the app's defaults alone.
|
|
23
|
+
- `environmentBlendMode` on `AdapterCapabilities`, typed `EnvironmentBlendMode | null` where `EnvironmentBlendMode` is the new `"opaque" | "alpha-blend" | "additive"` union, plus `environmentBlendMode: null` in `DEFAULT_CAPABILITIES`. `deriveCapabilities` reduced the session's blend mode to the boolean `passthrough` and threw the string away, which says whether the world shows through but not how. The two passthrough modes behave oppositely: `"alpha-blend"` is video passthrough, as on a Quest, and composites normally, so black stays black; `"additive"` is a see-through optical display and adds the rendered image to the light already reaching the eye, so black is fully transparent. A service that dims the world must draw brighter on an additive display, not darker, and could not tell the two apart. The key is required rather than optional, mirroring the `presence` decision in `@realitycollective/webxr-input`, so the conformance suite's capability-key check stays meaningful. A value WebXR does not define derives as `null` rather than passing through, so a consumer switching on the mode never meets a string it has no rule for. `passthrough` keeps its exact meaning, still `true` for any blend mode other than `"opaque"`, including one that derives as `null`. Every adapter reports the new key and every adapter's change detection compares it.
|
|
24
|
+
- `IWSDKXROptionsLike` is now exported from `@realitycollective/service-framework-iwsdk`. It was already the declared parameter type of `IWSDKWorldLike.launchXR`, so a consumer typing a world by hand could not name it.
|
|
20
25
|
- An optional session facet on `RuntimeAdapter`: `session?: SessionFacet`, with `getState`, `request(mode, options)`, `end()`, `onStateChange` and `onVisibilityChange`, plus the `SessionMode`, `SessionState`, `SessionResult`, `SessionFailureReason`, `SessionVisibility` and `SessionRequestOptions` types and the `DEFAULT_SESSION_TIMEOUT_MS` constant. A request resolves with a result rather than throwing, because a host that cannot start a session is a normal runtime condition. `IWSDKAdapter` implements it over the world's `launchXR` and `exitXR`; `MockRuntimeAdapter` implements it in memory, driven by `simulateSessionStart()`, `simulateSessionEnd()` and `simulateVisibility()`.
|
|
21
26
|
- `RUNTIME_ADAPTER_FACETS`, the list of optional facets an adapter can carry, and the `RuntimeAdapterFacet` type. A conformance test walks the list, so a facet added without a mock implementation fails the suite instead of being found by a consumer.
|
|
22
27
|
- `renderTick` under IWSDK. `makeServiceBridgeSystem` now emits the scheduler's `renderTick` channel on every focused frame, with `source: "iwsdk"`, alongside the adapter's `onFrame` fan-out it already drove. A service written against the scheduler now runs under IWSDK exactly as it does under the three.js and Babylon.js bridges. IWSDK reports its frame delta in seconds and the scheduler's `LifecycleContext` is in milliseconds, so the bridge converts.
|
|
23
28
|
- Structural contracts for the parts of an IWSDK world the adapter now reads: `IWSDKSessionLike`, `IWSDKInputSourceLike`, an optional `session` and `launchXR` / `exitXR` on `IWSDKWorldLike`, and an optional `subscribe` on `IWSDKSignalLike`. Every addition is optional, so a world carrying nothing but the visibility signal still type-checks and still works. `@iwsdk/core` remains undeclared as a dependency of any kind.
|
|
24
|
-
- A shared runtime-adapter conformance suite, run against `MockRuntimeAdapter`, `IWSDKAdapter`, `WebXRRuntimeAdapter` and `BabylonRuntimeAdapter
|
|
29
|
+
- A shared runtime-adapter conformance suite, run against `MockRuntimeAdapter`, `IWSDKAdapter`, `WebXRRuntimeAdapter` and `BabylonRuntimeAdapter`, and published as `runtimeAdapterContractCases()` from `@realitycollective/service-framework` with the `RuntimeAdapterContractCase`, `RuntimeAdapterSubject` and `RuntimeAdapterDriver` types. It began as an in-repo vitest helper, which meant an adapter written outside this repository could not prove it conformed: the checks existed but were not shipped. Following `inputProviderContractCases()` in `@realitycollective/webxr-input`, the suite is now data rather than a runner - each case is a `{ name, run }` pair that returns silently on success and throws a plain `Error` on failure - so an adapter repository hosts the whole thing in three lines of its own runner. Some cases are asynchronous, so a runner must await what `run` returns, and each case needs a fresh subject because the session cases drive a session through its whole lifecycle. A subject whose adapter has no session facet passes the session cases without running them. Test coverage now measures `@realitycollective/service-framework-iwsdk`, `@realitycollective/service-framework-three` and `@realitycollective/service-framework-babylon` as well, at the same 100% thresholds as the core and client packages.
|
|
25
30
|
- Updated documentation packs for all projects.
|
|
26
31
|
- Improved release scripting and validation to improve delivery coherance.
|
|
27
32
|
- `scripts/release.config.json` - names the core package and the publish order, so the release tooling is identical across every Reality Collective TypeScript repository.
|
package/README.md
CHANGED
|
@@ -127,15 +127,15 @@ expect(service.getSnapshot().energy).toBeLessThan(1);
|
|
|
127
127
|
|
|
128
128
|
No `@iwsdk/core` import appears anywhere in the test.
|
|
129
129
|
|
|
130
|
-
`MockRuntimeAdapter` implements the session facet in memory too: `simulateSessionStart()`, `simulateSessionEnd()` and `simulateVisibility(v)` drive it, and `request()` resolves `{ ok: true }` when a start is simulated before the timeout, `{ ok: false, reason: "timeout" }` otherwise. Both adapters run the same
|
|
130
|
+
`MockRuntimeAdapter` implements the session facet in memory too: `simulateSessionStart()`, `simulateSessionEnd()` and `simulateVisibility(v)` drive it, and `request()` resolves `{ ok: true }` when a start is simulated before the timeout, `{ ok: false, reason: "timeout" }` otherwise. Both adapters run the same conformance suite, published from the core as `runtimeAdapterContractCases()`, so a service that behaves one way headless behaves the same way on a headset.
|
|
131
131
|
|
|
132
132
|
---
|
|
133
133
|
|
|
134
134
|
## Capabilities
|
|
135
135
|
|
|
136
|
-
`AdapterCapabilities` (`immersive`, `handTracking`, `planeDetection`, `passthrough`) is what gating services read (`adapter.getCapabilities()`) or subscribe to (`adapter.onCapabilitiesChange(cb)` - mirrors `onFrame`, so gates don't poll every frame).
|
|
136
|
+
`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
137
|
|
|
138
|
-
`IWSDKAdapter` derives all four 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,
|
|
138
|
+
`IWSDKAdapter` derives all four 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
139
|
|
|
140
140
|
| Flag | Derived from |
|
|
141
141
|
| --- | --- |
|
|
@@ -143,10 +143,15 @@ No `@iwsdk/core` import appears anywhere in the test.
|
|
|
143
143
|
| `handTracking` | `"hand-tracking"` in `session.enabledFeatures`, or any `session.inputSources[i].hand` |
|
|
144
144
|
| `planeDetection` | `"plane-detection"` in `session.enabledFeatures` |
|
|
145
145
|
| `passthrough` | `session.environmentBlendMode` is present and is not `"opaque"` |
|
|
146
|
+
| `environmentBlendMode` | `session.environmentBlendMode` where WebXR defines the value, otherwise `null` |
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
`passthrough` says whether the world shows through; `environmentBlendMode` says how, which is what a service needs to decide what to draw. The two passthrough modes behave oppositely. `"alpha-blend"` is video passthrough, as on a Quest, and composites normally, so black stays black. `"additive"` is a see-through optical display and adds the rendered image to the light already reaching the eye, so black is fully transparent. A service that dims the world has to draw brighter on an additive display, not darker. A value WebXR does not define reports as `null` rather than passing a string through, so a consumer switching on the mode never meets one it has no rule for; `passthrough` still reports `true` for such a value.
|
|
148
149
|
|
|
149
|
-
|
|
150
|
+
With no session the adapter reports `DEFAULT_CAPABILITIES`: every flag false and the blend mode `null`, so gating services behave conservatively before the player enters XR. Subscribers are notified only when a flag actually changes, so a signal that fires every visibility blip does not wake every gate.
|
|
151
|
+
|
|
152
|
+
Input sources are followed without a nudge. The adapter attaches an `inputsourceschange` listener to the live session and detaches it when the session goes, so `handTracking` updates when the player picks the controllers up or puts them down. IWSDK's world carries a real `XRSession`, which raises the event; both listener methods are optional on `IWSDKSessionLike` and the adapter guards for their absence, so a world faked in a test still works and simply never pushes.
|
|
153
|
+
|
|
154
|
+
One host still needs a nudge. If the world's `visibilityState` has no `subscribe`, nothing tells the adapter that a session came or went: call `adapter.refreshCapabilities()` after the host changes something. Call it too if the host enables a feature mid-session without a visibility transition.
|
|
150
155
|
|
|
151
156
|
### Overrides
|
|
152
157
|
|
|
@@ -176,12 +181,50 @@ await adapter.session.end();
|
|
|
176
181
|
```
|
|
177
182
|
|
|
178
183
|
- `getState()` walks `"none"` → `"requesting"` → `"active"` → `"ending"` → `"none"`.
|
|
179
|
-
- `request(mode, options)` calls `launchXR` with `{ sessionMode: mode }` (the `XROptions` shape IWSDK accepts; its `SessionMode` enum values are the WebXR mode strings) and resolves once a session appears, whether the adapter learns that from the visibility signal or from its polling fallback. A synchronous throw from `launchXR` comes back as `{ ok: false, reason: "unsupported", error }`. Nothing appearing within `timeoutMs` (default 10000) comes back as `{ ok: false, reason: "timeout" }`.
|
|
184
|
+
- `request(mode, options)` calls `launchXR` with `{ sessionMode: mode }` (the `XROptions` shape IWSDK accepts; its `SessionMode` enum values are the WebXR mode strings), plus a `features` object when the request named any, and resolves once a session appears, whether the adapter learns that from the visibility signal or from its polling fallback. A synchronous throw from `launchXR` comes back as `{ ok: false, reason: "unsupported", error }`. Nothing appearing within `timeoutMs` (default 10000) comes back as `{ ok: false, reason: "timeout" }`.
|
|
180
185
|
- `end()` calls `exitXR` and resolves once the session is gone.
|
|
181
186
|
- `onVisibilityChange` maps IWSDK's signal onto `"visible"`, `"visible-blurred"`, `"hidden"` and `"non-immersive"`. A value the adapter does not recognise is reported as `"hidden"`, because treating an unknown state as visible would keep game logic running when it should not.
|
|
182
187
|
|
|
183
188
|
A world with no `launchXR` reports `{ ok: false, reason: "unsupported" }` rather than throwing, so a 2D preview build needs no special case.
|
|
184
189
|
|
|
190
|
+
### Per-request features
|
|
191
|
+
|
|
192
|
+
`SessionRequestOptions` carries `requiredFeatures` and `optionalFeatures` as WebXR feature strings, the same on every host binding. An app that swaps from `"immersive-vr"` to `"immersive-ar"` mid-session needs them: without them the second session gets whatever defaults the host was built with, which were chosen for the first.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
await adapter.session.request("immersive-ar", {
|
|
196
|
+
requiredFeatures: ["hand-tracking"],
|
|
197
|
+
optionalFeatures: ["plane-detection"],
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
IWSDK does not take feature strings. Its `launchXR` takes an `XROptions` whose `features` is a structured object, so the adapter translates. A required feature becomes `{ required: true }`, an optional one becomes `true`, and a feature named in both lists comes out required.
|
|
202
|
+
|
|
203
|
+
| WebXR feature string | IWSDK `XRFeatureOptions` key |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `hand-tracking` | `handTracking` |
|
|
206
|
+
| `anchors` | `anchors` |
|
|
207
|
+
| `hit-test` | `hitTest` |
|
|
208
|
+
| `plane-detection` | `planeDetection` |
|
|
209
|
+
| `mesh-detection` | `meshDetection` |
|
|
210
|
+
| `depth-sensing` | `depthSensing` |
|
|
211
|
+
| `layers` | `layers` |
|
|
212
|
+
| `unbounded` | `unbounded` |
|
|
213
|
+
|
|
214
|
+
Anything else has nowhere to go. `local-floor` and `bounded-floor` are reference spaces, which IWSDK configures through `XROptions.referenceSpace` rather than as features; `dom-overlay` it does not model at all. Such a string is dropped from the request rather than thrown, because the session is still one the host can serve and failing it over a feature the app may not need would be worse. Nothing is logged.
|
|
215
|
+
|
|
216
|
+
To see what would be dropped before you ask, call the mapping yourself:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { toIWSDKFeatures } from "@realitycollective/service-framework-iwsdk";
|
|
220
|
+
|
|
221
|
+
const { features, unmapped } = toIWSDKFeatures({ requiredFeatures: ["hand-tracking", "dom-overlay"] });
|
|
222
|
+
// features -> { handTracking: { required: true } }
|
|
223
|
+
// unmapped -> ["dom-overlay"]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`launchXR` merges what it is given over the world's `xrDefaults`, so a request that names no features leaves the app's defaults exactly as they were, and one that names some adds to them.
|
|
227
|
+
|
|
185
228
|
---
|
|
186
229
|
|
|
187
230
|
## API surface
|
|
@@ -194,7 +237,8 @@ Owned by this package:
|
|
|
194
237
|
| `makeServiceBridgeSystem` | factory | Returns the IWSDK `ServiceBridgeSystem` class; pumps `onFrame` and `renderTick`. |
|
|
195
238
|
| `ServiceBridgeSystemOptions` | interface | `{ adapter, manager, world, createSystem, visibleState }`. |
|
|
196
239
|
| `startServiceRuntime` / `ServiceRuntime` | function / interface | Bootstraps `{ manager, adapter }` from a profile factory. |
|
|
197
|
-
| `
|
|
240
|
+
| `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). |
|
|
198
242
|
|
|
199
243
|
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.
|
|
200
244
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
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, IWSDKInputSourceLike, IWSDKSessionLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKWorldLike, } from "./iwsdk-host.js";
|
|
9
|
+
export type { CreateSystemLike, IWSDKDepthSensingFlagLike, IWSDKFeatureFlagLike, IWSDKInputSourceLike, IWSDKSessionEventListener, IWSDKSessionEventType, IWSDKSessionLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKWorldLike, IWSDKXRFeatureOptionsLike, IWSDKXROptionsLike, } from "./iwsdk-host.js";
|
|
10
|
+
export { IWSDK_FEATURE_KEYS, toIWSDKFeatures } from "./iwsdk-features.js";
|
|
11
|
+
export type { IWSDKFeatureMapping } from "./iwsdk-features.js";
|
|
10
12
|
export { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
11
13
|
export { makeServiceBridgeSystem } from "./service-bridge-system.js";
|
|
12
14
|
export type { ServiceBridgeSystemOptions } from "./service-bridge-system.js";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* unchanged so existing imports from this package keep working.
|
|
6
6
|
*/
|
|
7
7
|
export { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, MockRuntimeAdapter, RUNTIME_ADAPTER_FACETS, SnapshotService, } from "@realitycollective/service-framework";
|
|
8
|
+
export { IWSDK_FEATURE_KEYS, toIWSDKFeatures } from "./iwsdk-features.js";
|
|
8
9
|
export { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
9
10
|
export { makeServiceBridgeSystem } from "./service-bridge-system.js";
|
|
10
11
|
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;AAoC9C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,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 IWSDKSessionEventListener,\n IWSDKSessionEventType,\n IWSDKSessionLike,\n IWSDKSignalLike,\n IWSDKSystemConstructor,\n IWSDKSystemLike,\n IWSDKWorldLike,\n IWSDKXRFeatureOptionsLike,\n IWSDKXROptionsLike,\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\";\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
|
@@ -24,6 +24,12 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
24
24
|
private capabilities;
|
|
25
25
|
private sessionState;
|
|
26
26
|
private unsubscribeVisibility;
|
|
27
|
+
private boundSession;
|
|
28
|
+
/**
|
|
29
|
+
* Pre-bound, because `removeEventListener` has to be handed the same function
|
|
30
|
+
* `addEventListener` got.
|
|
31
|
+
*/
|
|
32
|
+
private readonly onInputSourcesChange;
|
|
27
33
|
/** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */
|
|
28
34
|
readonly session: SessionFacet;
|
|
29
35
|
constructor(world: IWSDKWorldLike);
|
|
@@ -34,9 +40,9 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
34
40
|
getWorld(): IWSDKWorldLike;
|
|
35
41
|
/**
|
|
36
42
|
* Re-read the live session and publish any capability change. The adapter
|
|
37
|
-
* does this itself whenever the visibility signal fires
|
|
38
|
-
*
|
|
39
|
-
* mid-session.
|
|
43
|
+
* does this itself whenever the visibility signal fires and whenever the
|
|
44
|
+
* session's input sources change; call it directly on a host whose signal has
|
|
45
|
+
* no `subscribe`, or after the host enables a feature mid-session.
|
|
40
46
|
*/
|
|
41
47
|
refreshCapabilities(): void;
|
|
42
48
|
/**
|
|
@@ -53,11 +59,21 @@ export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
|
53
59
|
/** Called once per frame by the ECS bridge system. */
|
|
54
60
|
emitFrame(timestamp: number, delta: number): void;
|
|
55
61
|
/**
|
|
56
|
-
* Release everything the adapter holds: the visibility subscription,
|
|
57
|
-
* in-flight session wait, the manual overrides
|
|
62
|
+
* Release everything the adapter holds: the visibility subscription, the
|
|
63
|
+
* session's own listener, any in-flight session wait, the manual overrides
|
|
64
|
+
* and every listener.
|
|
58
65
|
*/
|
|
59
66
|
dispose(): void;
|
|
60
67
|
private hasSession;
|
|
68
|
+
/**
|
|
69
|
+
* Follow one session's `inputsourceschange` event, so `handTracking` updates
|
|
70
|
+
* when the player picks the controllers up or puts them down. IWSDK's world
|
|
71
|
+
* carries a real `XRSession`, which raises the event; a host that reports a
|
|
72
|
+
* session without listener methods is bound all the same and simply never
|
|
73
|
+
* pushes, which is what `refreshCapabilities()` remains for.
|
|
74
|
+
*/
|
|
75
|
+
private bindSession;
|
|
76
|
+
private unbindSession;
|
|
61
77
|
private publishCapabilities;
|
|
62
78
|
private handleVisibilityChange;
|
|
63
79
|
/**
|
package/dist/iwsdk-adapter.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* `exitXR` entry points.
|
|
12
12
|
*/
|
|
13
13
|
import { DEFAULT_CAPABILITIES, DEFAULT_SESSION_TIMEOUT_MS, deriveCapabilities, } from "@realitycollective/service-framework";
|
|
14
|
+
import { toIWSDKFeatures } from "./iwsdk-features.js";
|
|
14
15
|
/** How often the session waits re-check the world when the signal is silent. */
|
|
15
16
|
const SESSION_POLL_INTERVAL_MS = 50;
|
|
16
17
|
const SESSION_VISIBILITY_VALUES = new Set([
|
|
@@ -42,6 +43,14 @@ export class IWSDKAdapter {
|
|
|
42
43
|
capabilities = DEFAULT_CAPABILITIES;
|
|
43
44
|
sessionState = "none";
|
|
44
45
|
unsubscribeVisibility;
|
|
46
|
+
boundSession = null;
|
|
47
|
+
/**
|
|
48
|
+
* Pre-bound, because `removeEventListener` has to be handed the same function
|
|
49
|
+
* `addEventListener` got.
|
|
50
|
+
*/
|
|
51
|
+
onInputSourcesChange = () => {
|
|
52
|
+
this.refreshCapabilities();
|
|
53
|
+
};
|
|
45
54
|
/** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */
|
|
46
55
|
session = {
|
|
47
56
|
getState: () => this.sessionState,
|
|
@@ -65,6 +74,7 @@ export class IWSDKAdapter {
|
|
|
65
74
|
this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {
|
|
66
75
|
this.handleVisibilityChange(value);
|
|
67
76
|
});
|
|
77
|
+
this.bindSession(this.world.session ?? null);
|
|
68
78
|
this.derived = deriveCapabilities(this.world.session);
|
|
69
79
|
this.capabilities = this.derived;
|
|
70
80
|
this.sessionState = this.hasSession() ? "active" : "none";
|
|
@@ -90,11 +100,12 @@ export class IWSDKAdapter {
|
|
|
90
100
|
}
|
|
91
101
|
/**
|
|
92
102
|
* Re-read the live session and publish any capability change. The adapter
|
|
93
|
-
* does this itself whenever the visibility signal fires
|
|
94
|
-
*
|
|
95
|
-
* mid-session.
|
|
103
|
+
* does this itself whenever the visibility signal fires and whenever the
|
|
104
|
+
* session's input sources change; call it directly on a host whose signal has
|
|
105
|
+
* no `subscribe`, or after the host enables a feature mid-session.
|
|
96
106
|
*/
|
|
97
107
|
refreshCapabilities() {
|
|
108
|
+
this.bindSession(this.world.session ?? null);
|
|
98
109
|
this.derived = deriveCapabilities(this.world.session);
|
|
99
110
|
this.publishCapabilities();
|
|
100
111
|
}
|
|
@@ -121,12 +132,14 @@ export class IWSDKAdapter {
|
|
|
121
132
|
this.frameListeners.forEach((listener) => listener(frame));
|
|
122
133
|
}
|
|
123
134
|
/**
|
|
124
|
-
* Release everything the adapter holds: the visibility subscription,
|
|
125
|
-
* in-flight session wait, the manual overrides
|
|
135
|
+
* Release everything the adapter holds: the visibility subscription, the
|
|
136
|
+
* session's own listener, any in-flight session wait, the manual overrides
|
|
137
|
+
* and every listener.
|
|
126
138
|
*/
|
|
127
139
|
dispose() {
|
|
128
140
|
this.unsubscribeVisibility?.();
|
|
129
141
|
this.unsubscribeVisibility = undefined;
|
|
142
|
+
this.unbindSession();
|
|
130
143
|
for (const wait of Array.from(this.pendingWaits)) {
|
|
131
144
|
this.settleWait(wait, false);
|
|
132
145
|
}
|
|
@@ -139,13 +152,33 @@ export class IWSDKAdapter {
|
|
|
139
152
|
hasSession() {
|
|
140
153
|
return Boolean(this.world.session);
|
|
141
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Follow one session's `inputsourceschange` event, so `handTracking` updates
|
|
157
|
+
* when the player picks the controllers up or puts them down. IWSDK's world
|
|
158
|
+
* carries a real `XRSession`, which raises the event; a host that reports a
|
|
159
|
+
* session without listener methods is bound all the same and simply never
|
|
160
|
+
* pushes, which is what `refreshCapabilities()` remains for.
|
|
161
|
+
*/
|
|
162
|
+
bindSession(session) {
|
|
163
|
+
if (session === this.boundSession) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.unbindSession();
|
|
167
|
+
this.boundSession = session;
|
|
168
|
+
session?.addEventListener?.("inputsourceschange", this.onInputSourcesChange);
|
|
169
|
+
}
|
|
170
|
+
unbindSession() {
|
|
171
|
+
this.boundSession?.removeEventListener?.("inputsourceschange", this.onInputSourcesChange);
|
|
172
|
+
this.boundSession = null;
|
|
173
|
+
}
|
|
142
174
|
publishCapabilities() {
|
|
143
175
|
const next = { ...this.derived, ...this.overrides };
|
|
144
176
|
const current = this.capabilities;
|
|
145
177
|
if (next.immersive === current.immersive &&
|
|
146
178
|
next.handTracking === current.handTracking &&
|
|
147
179
|
next.planeDetection === current.planeDetection &&
|
|
148
|
-
next.passthrough === current.passthrough
|
|
180
|
+
next.passthrough === current.passthrough &&
|
|
181
|
+
next.environmentBlendMode === current.environmentBlendMode) {
|
|
149
182
|
return;
|
|
150
183
|
}
|
|
151
184
|
this.capabilities = next;
|
|
@@ -178,10 +211,14 @@ export class IWSDKAdapter {
|
|
|
178
211
|
}
|
|
179
212
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;
|
|
180
213
|
this.setSessionState("requesting");
|
|
214
|
+
const { features } = toIWSDKFeatures(options);
|
|
181
215
|
try {
|
|
182
216
|
// IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the
|
|
183
|
-
// WebXR mode strings, so the facet mode passes through unchanged.
|
|
184
|
-
|
|
217
|
+
// WebXR mode strings, so the facet mode passes through unchanged. The
|
|
218
|
+
// request's feature strings become IWSDK's structured flags; `launchXR`
|
|
219
|
+
// merges what it is given over the world's `xrDefaults`, so a request
|
|
220
|
+
// that names no features leaves the app's defaults alone.
|
|
221
|
+
launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });
|
|
185
222
|
}
|
|
186
223
|
catch (error) {
|
|
187
224
|
this.setSessionState("none");
|
|
@@ -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 type { IWSDKWorldLike } 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\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.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; call it directly on\n * a host whose signal has no `subscribe`, or after the host enables a feature\n * mid-session.\n */\n public refreshCapabilities(): void {\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number): void {\n const frame: FrameInfo = { timestamp, delta };\n this.frameListeners.forEach((listener) => listener(frame));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, any\n * in-flight session wait, the manual overrides and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\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 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 ) {\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 try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged.\n launch.call(this.world, { sessionMode: mode });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so\n * frames are pushed in rather than pulled: the `ServiceBridgeSystem` (a normal\n * IWSDK system) calls {@link IWSDKAdapter.emitFrame} once per frame and the\n * adapter notifies subscribed services.\n *\n * Capabilities are derived from the live session on construction and on every\n * visibility change, through the core's shared `deriveCapabilities`, with a\n * manual override layer on top. Session lifecycle is\n * exposed through {@link IWSDKAdapter.session}, over the world's `launchXR` and\n * `exitXR` entry points.\n */\nimport {\n DEFAULT_CAPABILITIES,\n DEFAULT_SESSION_TIMEOUT_MS,\n deriveCapabilities,\n type AdapterCapabilities,\n type CapabilitiesListener,\n type FrameInfo,\n type FrameListener,\n type RuntimeAdapter,\n type SessionFacet,\n type SessionMode,\n type SessionRequestOptions,\n type SessionResult,\n type SessionState,\n type SessionVisibility,\n type Unsubscribe,\n} from \"@realitycollective/service-framework\";\nimport { toIWSDKFeatures } from \"./iwsdk-features.js\";\nimport type {\n IWSDKSessionEventListener,\n IWSDKSessionLike,\n IWSDKWorldLike,\n} from \"./iwsdk-host.js\";\n\ntype SessionStateListener = (state: SessionState) => void;\ntype SessionVisibilityListener = (visibility: SessionVisibility) => void;\n\n/** How often the session waits re-check the world when the signal is silent. */\nconst SESSION_POLL_INTERVAL_MS = 50;\n\nconst SESSION_VISIBILITY_VALUES: ReadonlySet<string> = new Set([\n \"visible\",\n \"visible-blurred\",\n \"hidden\",\n \"non-immersive\",\n]);\n\n/**\n * IWSDK's visibility signal carries the WebXR visibility strings. Anything this\n * adapter does not recognise is reported as `\"hidden\"`, because treating an\n * unknown state as visible would keep game logic running when it should not.\n */\nfunction toSessionVisibility(value: unknown): SessionVisibility {\n if (typeof value === \"string\" && SESSION_VISIBILITY_VALUES.has(value)) {\n return value as SessionVisibility;\n }\n\n return \"hidden\";\n}\n\n/** A `request()` or `end()` waiting for the world to reach some state. */\ninterface PendingWait {\n readonly predicate: () => boolean;\n readonly resolve: (settled: boolean) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n readonly interval: ReturnType<typeof setInterval>;\n}\n\nexport class IWSDKAdapter implements RuntimeAdapter {\n private readonly frameListeners = new Set<FrameListener>();\n private readonly capabilitiesListeners = new Set<CapabilitiesListener>();\n private readonly stateListeners = new Set<SessionStateListener>();\n private readonly visibilityListeners = new Set<SessionVisibilityListener>();\n private readonly pendingWaits = new Set<PendingWait>();\n\n private derived: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private overrides: Partial<AdapterCapabilities> = {};\n private capabilities: AdapterCapabilities = DEFAULT_CAPABILITIES;\n private sessionState: SessionState = \"none\";\n private unsubscribeVisibility: Unsubscribe | undefined;\n private boundSession: IWSDKSessionLike | null = null;\n\n /**\n * Pre-bound, because `removeEventListener` has to be handed the same function\n * `addEventListener` got.\n */\n private readonly onInputSourcesChange: IWSDKSessionEventListener = () => {\n this.refreshCapabilities();\n };\n\n /** Session lifecycle over the world's `launchXR` / `exitXR` entry points. */\n public readonly session: SessionFacet = {\n getState: () => this.sessionState,\n request: (mode, options) => this.requestSession(mode, options),\n end: () => this.endSession(),\n onStateChange: (listener) => {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n },\n onVisibilityChange: (listener) => {\n this.visibilityListeners.add(listener);\n return () => {\n this.visibilityListeners.delete(listener);\n };\n },\n };\n\n public constructor(private readonly world: IWSDKWorldLike) {\n this.unsubscribeVisibility = world.visibilityState.subscribe?.((value) => {\n this.handleVisibilityChange(value);\n });\n\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.capabilities = this.derived;\n this.sessionState = this.hasSession() ? \"active\" : \"none\";\n }\n\n public onFrame(listener: FrameListener): Unsubscribe {\n this.frameListeners.add(listener);\n return () => {\n this.frameListeners.delete(listener);\n };\n }\n\n public getCapabilities(): AdapterCapabilities {\n return this.capabilities;\n }\n\n public onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe {\n this.capabilitiesListeners.add(listener);\n return () => {\n this.capabilitiesListeners.delete(listener);\n };\n }\n\n /** The IWSDK `World` this adapter is bound to. */\n public getWorld(): IWSDKWorldLike {\n return this.world;\n }\n\n /**\n * Re-read the live session and publish any capability change. The adapter\n * does this itself whenever the visibility signal fires and whenever the\n * session's input sources change; call it directly on a host whose signal has\n * no `subscribe`, or after the host enables a feature mid-session.\n */\n public refreshCapabilities(): void {\n this.bindSession(this.world.session ?? null);\n this.derived = deriveCapabilities(this.world.session);\n this.publishCapabilities();\n }\n\n /**\n * Force capability flags regardless of what the session reports. Overrides\n * are a layer on top of the derived values: they win for as long as they are\n * set, survive every later derivation, and are dropped only by\n * {@link IWSDKAdapter.clearCapabilityOverrides} or\n * {@link IWSDKAdapter.dispose}. Subscribers are notified if the effective\n * capabilities changed.\n */\n public setCapabilities(capabilities: Partial<AdapterCapabilities>): void {\n this.overrides = { ...this.overrides, ...capabilities };\n this.publishCapabilities();\n }\n\n /** Drop every manual override and fall back to the derived capabilities. */\n public clearCapabilityOverrides(): void {\n this.overrides = {};\n this.publishCapabilities();\n }\n\n /** Called once per frame by the ECS bridge system. */\n public emitFrame(timestamp: number, delta: number): void {\n const frame: FrameInfo = { timestamp, delta };\n this.frameListeners.forEach((listener) => listener(frame));\n }\n\n /**\n * Release everything the adapter holds: the visibility subscription, the\n * session's own listener, any in-flight session wait, the manual overrides\n * and every listener.\n */\n public dispose(): void {\n this.unsubscribeVisibility?.();\n this.unsubscribeVisibility = undefined;\n this.unbindSession();\n\n for (const wait of Array.from(this.pendingWaits)) {\n this.settleWait(wait, false);\n }\n\n this.overrides = {};\n this.frameListeners.clear();\n this.capabilitiesListeners.clear();\n this.stateListeners.clear();\n this.visibilityListeners.clear();\n }\n\n private hasSession(): boolean {\n return Boolean(this.world.session);\n }\n\n /**\n * Follow one session's `inputsourceschange` event, so `handTracking` updates\n * when the player picks the controllers up or puts them down. IWSDK's world\n * carries a real `XRSession`, which raises the event; a host that reports a\n * session without listener methods is bound all the same and simply never\n * pushes, which is what `refreshCapabilities()` remains for.\n */\n private bindSession(session: IWSDKSessionLike | null): void {\n if (session === this.boundSession) {\n return;\n }\n\n this.unbindSession();\n this.boundSession = session;\n session?.addEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n }\n\n private unbindSession(): void {\n this.boundSession?.removeEventListener?.(\"inputsourceschange\", this.onInputSourcesChange);\n this.boundSession = null;\n }\n\n private publishCapabilities(): void {\n const next: AdapterCapabilities = { ...this.derived, ...this.overrides };\n const current = this.capabilities;\n\n if (\n next.immersive === current.immersive &&\n next.handTracking === current.handTracking &&\n next.planeDetection === current.planeDetection &&\n next.passthrough === current.passthrough &&\n next.environmentBlendMode === current.environmentBlendMode\n ) {\n return;\n }\n\n this.capabilities = next;\n this.capabilitiesListeners.forEach((listener) => listener(next));\n }\n\n private handleVisibilityChange(value: unknown): void {\n this.refreshCapabilities();\n this.syncSessionState();\n this.checkWaits();\n\n const visibility = toSessionVisibility(value);\n this.visibilityListeners.forEach((listener) => listener(visibility));\n }\n\n /**\n * Follow the world when nothing local is mid-transition. `request()` and\n * `end()` own the state while they run, so the signal does not fight them.\n */\n private syncSessionState(): void {\n if (this.sessionState === \"requesting\" || this.sessionState === \"ending\") {\n return;\n }\n\n this.setSessionState(this.hasSession() ? \"active\" : \"none\");\n }\n\n private async requestSession(\n mode: SessionMode,\n options?: SessionRequestOptions,\n ): Promise<SessionResult> {\n if (this.sessionState === \"active\") {\n return { ok: true };\n }\n\n const launch = this.world.launchXR;\n\n if (!launch) {\n return { ok: false, reason: \"unsupported\" };\n }\n\n const timeoutMs = options?.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;\n this.setSessionState(\"requesting\");\n\n const { features } = toIWSDKFeatures(options);\n\n try {\n // IWSDK reads `XROptions.sessionMode`; its `SessionMode` enum values are the\n // WebXR mode strings, so the facet mode passes through unchanged. The\n // request's feature strings become IWSDK's structured flags; `launchXR`\n // merges what it is given over the world's `xrDefaults`, so a request\n // that names no features leaves the app's defaults alone.\n launch.call(this.world, { sessionMode: mode, ...(features ? { features } : {}) });\n } catch (error) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"unsupported\", error };\n }\n\n const started = await this.waitFor(() => this.hasSession(), timeoutMs);\n\n if (!started) {\n this.setSessionState(\"none\");\n return { ok: false, reason: \"timeout\" };\n }\n\n this.setSessionState(\"active\");\n this.refreshCapabilities();\n return { ok: true };\n }\n\n private async endSession(): Promise<void> {\n if (this.sessionState === \"none\") {\n return;\n }\n\n const exit = this.world.exitXR;\n this.setSessionState(\"ending\");\n\n if (exit) {\n exit.call(this.world);\n }\n\n await this.waitFor(() => !this.hasSession(), DEFAULT_SESSION_TIMEOUT_MS);\n this.setSessionState(\"none\");\n this.refreshCapabilities();\n }\n\n /**\n * Resolve `true` once `predicate` holds, `false` on timeout. The visibility\n * signal drives this where the host has one; the interval is the fallback for\n * a host whose signal does not push, and for a session that appears without\n * a visibility transition.\n */\n private waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {\n if (predicate()) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve) => {\n const wait: PendingWait = {\n predicate,\n resolve,\n timer: setTimeout(() => this.settleWait(wait, false), timeoutMs),\n interval: setInterval(() => this.checkWaits(), SESSION_POLL_INTERVAL_MS),\n };\n\n this.pendingWaits.add(wait);\n });\n }\n\n private checkWaits(): void {\n for (const wait of Array.from(this.pendingWaits)) {\n if (wait.predicate()) {\n this.settleWait(wait, true);\n }\n }\n }\n\n private settleWait(wait: PendingWait, settled: boolean): void {\n this.pendingWaits.delete(wait);\n clearTimeout(wait.timer);\n clearInterval(wait.interval);\n wait.resolve(settled);\n }\n\n private setSessionState(state: SessionState): void {\n if (this.sessionState === state) {\n return;\n }\n\n this.sessionState = state;\n this.stateListeners.forEach((listener) => listener(state));\n }\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping WebXR feature strings onto IWSDK's structured feature flags.
|
|
3
|
+
*
|
|
4
|
+
* `SessionRequestOptions` carries `requiredFeatures` and `optionalFeatures` as
|
|
5
|
+
* WebXR strings, which is what the three.js and Babylon bindings hand straight
|
|
6
|
+
* to `XRSessionInit`. IWSDK does not take strings: `launchXR` takes an
|
|
7
|
+
* `XROptions` whose `features` is a structured object, one key per feature,
|
|
8
|
+
* each set to `true` for optional or `{ required: true }` for required. This
|
|
9
|
+
* translates between the two so an app can name features the same way whatever
|
|
10
|
+
* host it is on.
|
|
11
|
+
*
|
|
12
|
+
* IWSDK names eight features. A WebXR string outside that set - `"local-floor"`
|
|
13
|
+
* and `"bounded-floor"`, which IWSDK configures through `referenceSpace`
|
|
14
|
+
* instead, or `"dom-overlay"`, which it does not model - has nowhere to go. It
|
|
15
|
+
* is collected and reported rather than thrown, because a request that names
|
|
16
|
+
* one is still a request the host can serve: dropping the string leaves the
|
|
17
|
+
* rest of the session intact, while throwing would fail a session over a
|
|
18
|
+
* feature the app may not need.
|
|
19
|
+
*/
|
|
20
|
+
import type { SessionRequestOptions } from "@realitycollective/service-framework";
|
|
21
|
+
import type { IWSDKXRFeatureOptionsLike } from "./iwsdk-host.js";
|
|
22
|
+
/**
|
|
23
|
+
* The eight WebXR feature strings IWSDK has a key for. Anything else is
|
|
24
|
+
* unmappable; see {@link IWSDKFeatureMapping.unmapped}.
|
|
25
|
+
*/
|
|
26
|
+
export declare const IWSDK_FEATURE_KEYS: Readonly<Record<string, keyof IWSDKXRFeatureOptionsLike>>;
|
|
27
|
+
/** What {@link toIWSDKFeatures} made of a request's feature strings. */
|
|
28
|
+
export interface IWSDKFeatureMapping {
|
|
29
|
+
/**
|
|
30
|
+
* The structured flags to pass as `XROptions.features`, absent when nothing
|
|
31
|
+
* mapped. `launchXR` merges what it is given over the world's `xrDefaults`,
|
|
32
|
+
* so sending nothing leaves the app's defaults alone.
|
|
33
|
+
*/
|
|
34
|
+
readonly features?: IWSDKXRFeatureOptionsLike;
|
|
35
|
+
/**
|
|
36
|
+
* Feature strings IWSDK has no key for, in the order they were first seen.
|
|
37
|
+
* They are dropped from the request. Read this to log or refuse ahead of a
|
|
38
|
+
* call, since the adapter itself says nothing about them.
|
|
39
|
+
*/
|
|
40
|
+
readonly unmapped: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Translate a request's feature strings into IWSDK's `XRFeatureOptions`.
|
|
44
|
+
*
|
|
45
|
+
* A feature named in both lists comes out required, because the stricter of
|
|
46
|
+
* the two is the one the caller cannot do without.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toIWSDKFeatures(options?: SessionRequestOptions): IWSDKFeatureMapping;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eight WebXR feature strings IWSDK has a key for. Anything else is
|
|
3
|
+
* unmappable; see {@link IWSDKFeatureMapping.unmapped}.
|
|
4
|
+
*/
|
|
5
|
+
export const IWSDK_FEATURE_KEYS = {
|
|
6
|
+
"hand-tracking": "handTracking",
|
|
7
|
+
anchors: "anchors",
|
|
8
|
+
"hit-test": "hitTest",
|
|
9
|
+
"plane-detection": "planeDetection",
|
|
10
|
+
"mesh-detection": "meshDetection",
|
|
11
|
+
"depth-sensing": "depthSensing",
|
|
12
|
+
layers: "layers",
|
|
13
|
+
unbounded: "unbounded",
|
|
14
|
+
};
|
|
15
|
+
function collect(names, flag, flags, unmapped) {
|
|
16
|
+
for (const name of names ?? []) {
|
|
17
|
+
const key = IWSDK_FEATURE_KEYS[name];
|
|
18
|
+
if (key === undefined) {
|
|
19
|
+
unmapped.add(name);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
flags[key] = flag;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Translate a request's feature strings into IWSDK's `XRFeatureOptions`.
|
|
27
|
+
*
|
|
28
|
+
* A feature named in both lists comes out required, because the stricter of
|
|
29
|
+
* the two is the one the caller cannot do without.
|
|
30
|
+
*/
|
|
31
|
+
export function toIWSDKFeatures(options) {
|
|
32
|
+
const flags = {};
|
|
33
|
+
const unmapped = new Set();
|
|
34
|
+
// Optional first, so a name in both lists is overwritten by the required flag.
|
|
35
|
+
collect(options?.optionalFeatures, true, flags, unmapped);
|
|
36
|
+
collect(options?.requiredFeatures, { required: true }, flags, unmapped);
|
|
37
|
+
const mapped = Object.keys(flags).length > 0;
|
|
38
|
+
return {
|
|
39
|
+
...(mapped ? { features: flags } : {}),
|
|
40
|
+
unmapped: Array.from(unmapped),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=iwsdk-features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iwsdk-features.js","sourceRoot":"","sources":["../src/iwsdk-features.ts"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA8D;IAC3F,eAAe,EAAE,cAAc;IAC/B,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,SAAS;IACrB,iBAAiB,EAAE,gBAAgB;IACnC,gBAAgB,EAAE,eAAe;IACjC,eAAe,EAAE,cAAc;IAC/B,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;CACvB,CAAC;AAkBF,SAAS,OAAO,CACd,KAAoC,EACpC,IAA0B,EAC1B,KAA2C,EAC3C,QAAqB;IAErB,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAErC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,MAAM,KAAK,GAAyC,EAAE,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,+EAA+E;IAC/E,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC1D,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAExE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAE7C,OAAO;QACL,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAkC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Mapping WebXR feature strings onto IWSDK's structured feature flags.\n *\n * `SessionRequestOptions` carries `requiredFeatures` and `optionalFeatures` as\n * WebXR strings, which is what the three.js and Babylon bindings hand straight\n * to `XRSessionInit`. IWSDK does not take strings: `launchXR` takes an\n * `XROptions` whose `features` is a structured object, one key per feature,\n * each set to `true` for optional or `{ required: true }` for required. This\n * translates between the two so an app can name features the same way whatever\n * host it is on.\n *\n * IWSDK names eight features. A WebXR string outside that set - `\"local-floor\"`\n * and `\"bounded-floor\"`, which IWSDK configures through `referenceSpace`\n * instead, or `\"dom-overlay\"`, which it does not model - has nowhere to go. It\n * is collected and reported rather than thrown, because a request that names\n * one is still a request the host can serve: dropping the string leaves the\n * rest of the session intact, while throwing would fail a session over a\n * feature the app may not need.\n */\nimport type { SessionRequestOptions } from \"@realitycollective/service-framework\";\nimport type { IWSDKFeatureFlagLike, IWSDKXRFeatureOptionsLike } from \"./iwsdk-host.js\";\n\n/**\n * The eight WebXR feature strings IWSDK has a key for. Anything else is\n * unmappable; see {@link IWSDKFeatureMapping.unmapped}.\n */\nexport const IWSDK_FEATURE_KEYS: Readonly<Record<string, keyof IWSDKXRFeatureOptionsLike>> = {\n \"hand-tracking\": \"handTracking\",\n anchors: \"anchors\",\n \"hit-test\": \"hitTest\",\n \"plane-detection\": \"planeDetection\",\n \"mesh-detection\": \"meshDetection\",\n \"depth-sensing\": \"depthSensing\",\n layers: \"layers\",\n unbounded: \"unbounded\",\n};\n\n/** What {@link toIWSDKFeatures} made of a request's feature strings. */\nexport interface IWSDKFeatureMapping {\n /**\n * The structured flags to pass as `XROptions.features`, absent when nothing\n * mapped. `launchXR` merges what it is given over the world's `xrDefaults`,\n * so sending nothing leaves the app's defaults alone.\n */\n readonly features?: IWSDKXRFeatureOptionsLike;\n /**\n * Feature strings IWSDK has no key for, in the order they were first seen.\n * They are dropped from the request. Read this to log or refuse ahead of a\n * call, since the adapter itself says nothing about them.\n */\n readonly unmapped: readonly string[];\n}\n\nfunction collect(\n names: readonly string[] | undefined,\n flag: IWSDKFeatureFlagLike,\n flags: Record<string, IWSDKFeatureFlagLike>,\n unmapped: Set<string>,\n): void {\n for (const name of names ?? []) {\n const key = IWSDK_FEATURE_KEYS[name];\n\n if (key === undefined) {\n unmapped.add(name);\n continue;\n }\n\n flags[key] = flag;\n }\n}\n\n/**\n * Translate a request's feature strings into IWSDK's `XRFeatureOptions`.\n *\n * A feature named in both lists comes out required, because the stricter of\n * the two is the one the caller cannot do without.\n */\nexport function toIWSDKFeatures(options?: SessionRequestOptions): IWSDKFeatureMapping {\n const flags: Record<string, IWSDKFeatureFlagLike> = {};\n const unmapped = new Set<string>();\n\n // Optional first, so a name in both lists is overwritten by the required flag.\n collect(options?.optionalFeatures, true, flags, unmapped);\n collect(options?.requiredFeatures, { required: true }, flags, unmapped);\n\n const mapped = Object.keys(flags).length > 0;\n\n return {\n ...(mapped ? { features: flags as IWSDKXRFeatureOptionsLike } : {}),\n unmapped: Array.from(unmapped),\n };\n}\n"]}
|
package/dist/iwsdk-host.d.ts
CHANGED
|
@@ -24,26 +24,70 @@ export interface IWSDKSignalLike<TValue> {
|
|
|
24
24
|
}
|
|
25
25
|
/** One entry of `XRSession.inputSources`; `hand` is set for a tracked hand. */
|
|
26
26
|
export type IWSDKInputSourceLike = CapabilityInputSourceLike;
|
|
27
|
+
/** The event an `XRSession` raises that the adapter listens for. */
|
|
28
|
+
export type IWSDKSessionEventType = "inputsourceschange";
|
|
29
|
+
/** Session event callback. The adapter reads the session, not the event. */
|
|
30
|
+
export type IWSDKSessionEventListener = (event?: unknown) => void;
|
|
27
31
|
/**
|
|
28
32
|
* The slice of the live `XRSession` the adapter reads to derive capabilities.
|
|
29
33
|
* It is the core's {@link CapabilitySessionLike} with `inputSources` required,
|
|
30
34
|
* because an IWSDK world always carries the collection, and the adapter hands
|
|
31
35
|
* the session straight to the core's `deriveCapabilities`.
|
|
36
|
+
*
|
|
37
|
+
* The two listener members are what IWSDK's own session carries, because it is
|
|
38
|
+
* a real `XRSession`. They are optional all the same, so a world faked in a
|
|
39
|
+
* test still satisfies the type without them, and the adapter guards for their
|
|
40
|
+
* absence rather than assuming a full session.
|
|
32
41
|
*/
|
|
33
42
|
export interface IWSDKSessionLike extends CapabilitySessionLike {
|
|
34
43
|
readonly inputSources: Iterable<IWSDKInputSourceLike>;
|
|
44
|
+
addEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;
|
|
45
|
+
removeEventListener?(type: IWSDKSessionEventType, listener: IWSDKSessionEventListener): void;
|
|
35
46
|
}
|
|
36
47
|
/**
|
|
37
48
|
* The slice of an IWSDK `World` the bridge and adapter read: the visibility
|
|
38
49
|
* signal, the live session, and the session entry points.
|
|
39
50
|
*/
|
|
51
|
+
/**
|
|
52
|
+
* IWSDK's `FeatureFlag`. `true` requests the feature as optional,
|
|
53
|
+
* `{ required: true }` as required, and `false` or absent does not request it.
|
|
54
|
+
*/
|
|
55
|
+
export type IWSDKFeatureFlagLike = boolean | {
|
|
56
|
+
readonly required?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* IWSDK's `DepthSensingFlag` - a {@link IWSDKFeatureFlagLike} that can also
|
|
60
|
+
* carry the depth usage and format preferences.
|
|
61
|
+
*/
|
|
62
|
+
export type IWSDKDepthSensingFlagLike = boolean | {
|
|
63
|
+
readonly required?: boolean;
|
|
64
|
+
readonly usage?: "cpu-optimized" | "gpu-optimized";
|
|
65
|
+
readonly format?: "luminance-alpha" | "float32";
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* IWSDK's `XRFeatureOptions`. IWSDK takes structured flags rather than the
|
|
69
|
+
* WebXR feature strings, which is why the adapter maps between the two.
|
|
70
|
+
*/
|
|
71
|
+
export interface IWSDKXRFeatureOptionsLike {
|
|
72
|
+
readonly handTracking?: IWSDKFeatureFlagLike;
|
|
73
|
+
readonly anchors?: IWSDKFeatureFlagLike;
|
|
74
|
+
readonly hitTest?: IWSDKFeatureFlagLike;
|
|
75
|
+
readonly planeDetection?: IWSDKFeatureFlagLike;
|
|
76
|
+
readonly meshDetection?: IWSDKFeatureFlagLike;
|
|
77
|
+
readonly depthSensing?: IWSDKDepthSensingFlagLike;
|
|
78
|
+
readonly layers?: IWSDKFeatureFlagLike;
|
|
79
|
+
readonly unbounded?: IWSDKFeatureFlagLike;
|
|
80
|
+
}
|
|
40
81
|
/**
|
|
41
82
|
* The slice of IWSDK `XROptions` the session facet passes to `launchXR`.
|
|
42
83
|
* `sessionMode` carries the WebXR mode string ("immersive-vr", "immersive-ar",
|
|
43
84
|
* "inline"), which is also the value of IWSDK's `SessionMode` enum members.
|
|
85
|
+
* `launchXR` merges what it is given over the world's `xrDefaults`, so anything
|
|
86
|
+
* left out here keeps the app's default.
|
|
44
87
|
*/
|
|
45
88
|
export interface IWSDKXROptionsLike {
|
|
46
89
|
readonly sessionMode?: string;
|
|
90
|
+
readonly features?: IWSDKXRFeatureOptionsLike;
|
|
47
91
|
}
|
|
48
92
|
export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
49
93
|
readonly visibilityState: IWSDKSignalLike<TVisibility>;
|
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/**\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 */\nexport interface IWSDKSessionLike extends CapabilitySessionLike {\n readonly inputSources: Iterable<IWSDKInputSourceLike>;\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 * 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 */\nexport interface IWSDKXROptionsLike {\n readonly sessionMode?: string;\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\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realitycollective/service-framework-iwsdk",
|
|
3
|
-
"version": "1.0.1-preview.
|
|
3
|
+
"version": "1.0.1-preview.3",
|
|
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.1-preview.
|
|
39
|
+
"@realitycollective/service-framework": "^1.0.1-preview.3"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.build.json",
|