@realitycollective/service-framework-iwsdk 1.0.0
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/Examples/main.ts +127 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/bootstrap.d.ts +20 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +20 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/iwsdk-adapter.d.ts +30 -0
- package/dist/iwsdk-adapter.d.ts.map +1 -0
- package/dist/iwsdk-adapter.js +51 -0
- package/dist/iwsdk-adapter.js.map +1 -0
- package/dist/iwsdk-host.d.ts +31 -0
- package/dist/iwsdk-host.d.ts.map +1 -0
- package/dist/iwsdk-host.js +12 -0
- package/dist/iwsdk-host.js.map +1 -0
- package/dist/mock-runtime-adapter.d.ts +21 -0
- package/dist/mock-runtime-adapter.d.ts.map +1 -0
- package/dist/mock-runtime-adapter.js +40 -0
- package/dist/mock-runtime-adapter.js.map +1 -0
- package/dist/runtime-adapter.d.ts +39 -0
- package/dist/runtime-adapter.d.ts.map +1 -0
- package/dist/runtime-adapter.js +15 -0
- package/dist/runtime-adapter.js.map +1 -0
- package/dist/service-bridge-system.d.ts +46 -0
- package/dist/service-bridge-system.d.ts.map +1 -0
- package/dist/service-bridge-system.js +25 -0
- package/dist/service-bridge-system.js.map +1 -0
- package/dist/snapshot-service.d.ts +28 -0
- package/dist/snapshot-service.d.ts.map +1 -0
- package/dist/snapshot-service.js +37 -0
- package/dist/snapshot-service.js.map +1 -0
- package/package.json +53 -0
package/Examples/main.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* service-framework-iwsdk — minimal usage example
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the three.js / Babylon.js examples, but for the Meta IWSDK frame
|
|
5
|
+
* source. IWSDK owns the render loop, so instead of a bridge that owns
|
|
6
|
+
* `setAnimationLoop` / `runRenderLoop`, the IWSDK shim is a *passive* frame
|
|
7
|
+
* source pumped from inside an IWSDK ECS system, and visibility is mapped to
|
|
8
|
+
* the manager's focus/pause signals (auto-pause when the headset comes off).
|
|
9
|
+
*
|
|
10
|
+
* The mocks below stand in for `@iwsdk/core` so this snippet runs in any
|
|
11
|
+
* Node.js environment. In a real IWSDK app you pass the real primitives:
|
|
12
|
+
*
|
|
13
|
+
* import { World, createSystem, VisibilityState } from "@iwsdk/core";
|
|
14
|
+
* const { manager, adapter } = startServiceRuntime(world, createEnergyProfile);
|
|
15
|
+
* world.registerSystem(
|
|
16
|
+
* makeServiceBridgeSystem({
|
|
17
|
+
* adapter, manager, world, createSystem,
|
|
18
|
+
* visibleState: VisibilityState.Visible,
|
|
19
|
+
* }),
|
|
20
|
+
* );
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
createServiceProfile,
|
|
25
|
+
createServiceToken,
|
|
26
|
+
type ServiceProfile,
|
|
27
|
+
} from "@realitycollective/service-framework";
|
|
28
|
+
import {
|
|
29
|
+
IWSDKAdapter,
|
|
30
|
+
SnapshotService,
|
|
31
|
+
makeServiceBridgeSystem,
|
|
32
|
+
startServiceRuntime,
|
|
33
|
+
type CreateSystemLike,
|
|
34
|
+
type IWSDKWorldLike,
|
|
35
|
+
type RuntimeAdapter,
|
|
36
|
+
type ServiceContext,
|
|
37
|
+
type Unsubscribe,
|
|
38
|
+
} from "@realitycollective/service-framework-iwsdk";
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// A leaf service that owns its state and decays energy once per frame.
|
|
42
|
+
// It depends only on RuntimeAdapter — never on @iwsdk/core — so it is portable
|
|
43
|
+
// and unit-testable headless against MockRuntimeAdapter.
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
interface EnergySnapshot {
|
|
47
|
+
readonly energy: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ENERGY_SERVICE_TOKEN = createServiceToken<EnergyService>("EnergyService");
|
|
51
|
+
|
|
52
|
+
class EnergyService extends SnapshotService<unknown, EnergySnapshot> {
|
|
53
|
+
private unsubscribe?: Unsubscribe;
|
|
54
|
+
|
|
55
|
+
public constructor(
|
|
56
|
+
context: ServiceContext,
|
|
57
|
+
private readonly adapter: RuntimeAdapter,
|
|
58
|
+
) {
|
|
59
|
+
super(context, { energy: 1 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public override initialize(): void {
|
|
63
|
+
this.unsubscribe = this.adapter.onFrame(({ delta }) => {
|
|
64
|
+
const energy = Math.max(0, this.getSnapshot().energy - delta * 0.1);
|
|
65
|
+
this.updateSnapshot({ energy });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public override destroy(): void {
|
|
70
|
+
this.unsubscribe?.();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createEnergyProfile(adapter: IWSDKAdapter): ServiceProfile {
|
|
75
|
+
return createServiceProfile("iwsdk-example", [
|
|
76
|
+
{
|
|
77
|
+
token: ENERGY_SERVICE_TOKEN,
|
|
78
|
+
useFactory: (context) => new EnergyService(context, adapter),
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Mock @iwsdk/core primitives — swap for the real ones in an IWSDK app.
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
const VISIBLE: string = "visible";
|
|
88
|
+
const HIDDEN: string = "hidden";
|
|
89
|
+
|
|
90
|
+
const world: IWSDKWorldLike<string> = { visibilityState: { value: VISIBLE } };
|
|
91
|
+
|
|
92
|
+
const createSystem: CreateSystemLike = () =>
|
|
93
|
+
class {
|
|
94
|
+
public update(_delta: number, _time: number): void {}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Bootstrap: stand up the manager + adapter, then wire the bridge system.
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
const { manager, adapter } = startServiceRuntime(world, createEnergyProfile);
|
|
102
|
+
|
|
103
|
+
const ServiceBridgeSystem = makeServiceBridgeSystem({
|
|
104
|
+
adapter,
|
|
105
|
+
manager,
|
|
106
|
+
world,
|
|
107
|
+
createSystem,
|
|
108
|
+
visibleState: VISIBLE,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// In a real app: world.registerSystem(ServiceBridgeSystem). Here we construct
|
|
112
|
+
// it directly and pump a few frames to simulate IWSDK's loop.
|
|
113
|
+
const bridge = new ServiceBridgeSystem();
|
|
114
|
+
|
|
115
|
+
const energyService = manager.resolve(ENERGY_SERVICE_TOKEN);
|
|
116
|
+
energyService.subscribe((snapshot) => console.log(`energy: ${snapshot.energy.toFixed(3)}`));
|
|
117
|
+
|
|
118
|
+
for (let frame = 1; frame <= 3; frame++) {
|
|
119
|
+
bridge.update(1 / 60, (frame * 1000) / 60);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Headset removed → the bridge stops pumping frames and the manager auto-pauses,
|
|
123
|
+
// so energy stops decaying.
|
|
124
|
+
(world as IWSDKWorldLike<string> & { visibilityState: { value: string } }).visibilityState.value = HIDDEN;
|
|
125
|
+
bridge.update(1 / 60, (4 * 1000) / 60);
|
|
126
|
+
|
|
127
|
+
console.log("Energy decays only while the session is visible.");
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reality Collective
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# `@realitycollective/service-framework-iwsdk`
|
|
2
|
+
|
|
3
|
+
Meta **IWSDK** (WebXR) frame-source bindings for the [Reality Collective TypeScript Service Framework](https://github.com/realitycollective/com.realitycollective.service-framework.ts).
|
|
4
|
+
|
|
5
|
+
Lets `@realitycollective/service-framework` services run inside the [IWSDK](https://github.com/meta-quest/immersive-web-sdk) engine loop without each project re-implementing the bridge layer. Services written against `BaseService<TConfig>` run unchanged here, on three.js, or on Babylon.js.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it differs from the render-loop bridges
|
|
10
|
+
|
|
11
|
+
`service-framework-three` and `service-framework-babylon` **own** the loop — the bridge calls `renderer.setAnimationLoop(...)` / `engine.runRenderLoop(...)`. **IWSDK already owns the loop**, the XR session, input and the ECS, so this shim must *not* own one.
|
|
12
|
+
|
|
13
|
+
Instead it is a **passive frame source**: a single IWSDK ECS system (`ServiceBridgeSystem`) is pumped one frame at a time by the engine, fans those frames out to subscribed services via `IWSDKAdapter`, and maps IWSDK's `visibilityState` to the manager's focus/pause signals (auto-pause when the headset comes off).
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
IWSDK World (render loop, XR session, input, ECS)
|
|
17
|
+
│ world.registerSystem(makeServiceBridgeSystem({ ... }))
|
|
18
|
+
▼
|
|
19
|
+
ServiceBridgeSystem ← the only place the engine touches services
|
|
20
|
+
every update(delta, time):
|
|
21
|
+
• visibilityState → manager.emitFocusChange / emitPauseChange
|
|
22
|
+
• if focused: adapter.emitFrame(time, delta)
|
|
23
|
+
▼
|
|
24
|
+
IWSDKAdapter (RuntimeAdapter) → onFrame fan-out → services
|
|
25
|
+
▼
|
|
26
|
+
ServiceManager → your SnapshotService graph
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Invariant:** services depend only on `RuntimeAdapter`, never on `@iwsdk/core`. That is what keeps them unit-testable headless — swap `IWSDKAdapter` for `MockRuntimeAdapter`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## No hard dependency on `@iwsdk/core`
|
|
34
|
+
|
|
35
|
+
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 across IWSDK `0.4.x`, and trivially mockable in unit tests.
|
|
36
|
+
|
|
37
|
+
`@iwsdk/core` is declared as an **optional peer dependency**.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createServiceProfile } from "@realitycollective/service-framework";
|
|
45
|
+
import {
|
|
46
|
+
startServiceRuntime,
|
|
47
|
+
makeServiceBridgeSystem,
|
|
48
|
+
} from "@realitycollective/service-framework-iwsdk";
|
|
49
|
+
import { World, createSystem, VisibilityState } from "@iwsdk/core";
|
|
50
|
+
|
|
51
|
+
// 1. Build the service graph from a profile factory: (adapter) => ServiceProfile
|
|
52
|
+
const createProfile = (adapter) =>
|
|
53
|
+
createServiceProfile("my-app", [/* your registrations, wired to `adapter` */]);
|
|
54
|
+
|
|
55
|
+
// 2. Stand up the manager + adapter and start it.
|
|
56
|
+
const { manager, adapter } = startServiceRuntime(world, createProfile);
|
|
57
|
+
|
|
58
|
+
// 3. Register the one ECS system that pumps frames and maps visibility.
|
|
59
|
+
world.registerSystem(
|
|
60
|
+
makeServiceBridgeSystem({
|
|
61
|
+
adapter,
|
|
62
|
+
manager,
|
|
63
|
+
world,
|
|
64
|
+
createSystem,
|
|
65
|
+
visibleState: VisibilityState.Visible,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Game logic ticks **only while the session is visible/focused** — in the browser / 2D preview the host app shows its own "Enter VR" gate and services stay idle until the player enters VR.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Services own their state: `SnapshotService`
|
|
75
|
+
|
|
76
|
+
`SnapshotService<TConfig, TSnapshot>` is the "services own state" base — one immutable snapshot plus pub/sub. Subscribers receive the current value immediately, then every publish. It has **no IWSDK dependency**.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { SnapshotService, type ServiceContext, type RuntimeAdapter } from "@realitycollective/service-framework-iwsdk";
|
|
80
|
+
|
|
81
|
+
interface EnergySnapshot { readonly energy: number; }
|
|
82
|
+
|
|
83
|
+
class EnergyService extends SnapshotService<unknown, EnergySnapshot> {
|
|
84
|
+
constructor(context: ServiceContext, private readonly adapter: RuntimeAdapter) {
|
|
85
|
+
super(context, { energy: 1 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override initialize(): void {
|
|
89
|
+
this.adapter.onFrame(({ delta }) => {
|
|
90
|
+
this.updateSnapshot({ energy: Math.max(0, this.getSnapshot().energy - delta * 0.1) });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The presentation layer (or another service) subscribes:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const energy = manager.resolve(ENERGY_SERVICE_TOKEN);
|
|
100
|
+
const unsubscribe = energy.subscribe(({ energy }) => hud.setEnergy(energy));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Headless testing with `MockRuntimeAdapter`
|
|
106
|
+
|
|
107
|
+
Services run against `MockRuntimeAdapter` with no IWSDK / renderer / headset. Tests drive the loop by calling `emitFrame`:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { MockRuntimeAdapter } from "@realitycollective/service-framework-iwsdk";
|
|
111
|
+
|
|
112
|
+
const adapter = new MockRuntimeAdapter({ immersive: true });
|
|
113
|
+
const service = new EnergyService(makeContext(), adapter);
|
|
114
|
+
service.initialize();
|
|
115
|
+
|
|
116
|
+
adapter.emitFrame(0, 1 / 72); // one frame
|
|
117
|
+
expect(service.getSnapshot().energy).toBeLessThan(1);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
No `@iwsdk/core` import appears anywhere in the test.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Capabilities
|
|
125
|
+
|
|
126
|
+
`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). Refine them with `adapter.setCapabilities({ ... })`.
|
|
127
|
+
|
|
128
|
+
> **One gap still to close:** deriving capabilities from the live `XRSession` (enabled features, blend mode, hand input sources) is intentionally left to the host wiring because it needs IWSDK session internals. Until that is wired, the adapter reports `DEFAULT_CAPABILITIES` (all-false) and gating services behave conservatively. `IWSDKAdapter.getWorld()` exposes the bound world as the source for that derivation, and `onCapabilitiesChange` is the notification channel for when it lands.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## API surface
|
|
133
|
+
|
|
134
|
+
| Symbol | Kind | Purpose |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `RuntimeAdapter` | interface | The seam services depend on (`onFrame`, `getCapabilities`, `onCapabilitiesChange`). |
|
|
137
|
+
| `FrameInfo` | interface | `{ timestamp, delta }`. |
|
|
138
|
+
| `AdapterCapabilities` / `DEFAULT_CAPABILITIES` | interface / const | XR capability flags; all-false default. |
|
|
139
|
+
| `Unsubscribe` / `FrameListener` / `CapabilitiesListener` | types | Callback / handle aliases. |
|
|
140
|
+
| `IWSDKAdapter` | class | Production adapter; `emitFrame`, `setCapabilities`, `getWorld`. |
|
|
141
|
+
| `MockRuntimeAdapter` | class | Headless adapter; `emitFrame(ts?, delta?)`, `setCapabilities`. |
|
|
142
|
+
| `SnapshotService<C, S>` | abstract class | State-owning base (`subscribe` / `getSnapshot` / `publishSnapshot` / `updateSnapshot`). |
|
|
143
|
+
| `ServiceContext<C>` / `SnapshotListener<S>` | types | Activation-context alias; snapshot callback. |
|
|
144
|
+
| `makeServiceBridgeSystem` | factory | Returns the IWSDK `ServiceBridgeSystem` class. |
|
|
145
|
+
| `ServiceBridgeSystemOptions` | interface | `{ adapter, manager, world, createSystem, visibleState }`. |
|
|
146
|
+
| `startServiceRuntime` / `ServiceRuntime` | function / interface | Bootstraps `{ manager, adapter }` from a profile factory. |
|
|
147
|
+
| `IWSDKWorldLike` / `CreateSystemLike` / … | types | Structural `@iwsdk/core` contracts (no engine import). |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Running tests
|
|
152
|
+
|
|
153
|
+
From the workspace root:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm test
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Running the example
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx tsx packages/service-framework-iwsdk/Examples/main.ts
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The example mocks the `@iwsdk/core` primitives so it runs in plain Node.js: it decays an `EnergyService` while the session is "visible", then shows it idling once visibility is lost.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-React bootstrap for an IWSDK client: build the adapter, stand up the
|
|
3
|
+
* ServiceManager from the app's profile, and start it. The caller registers
|
|
4
|
+
* {@link makeServiceBridgeSystem} with the IWSDK world to pump per-frame ticks.
|
|
5
|
+
*/
|
|
6
|
+
import { ServiceManager } from "@realitycollective/service-framework";
|
|
7
|
+
import type { ServiceProfile } from "@realitycollective/service-framework";
|
|
8
|
+
import { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
9
|
+
import type { IWSDKWorldLike } from "./iwsdk-host.js";
|
|
10
|
+
export interface ServiceRuntime {
|
|
11
|
+
readonly manager: ServiceManager;
|
|
12
|
+
readonly adapter: IWSDKAdapter;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param world the IWSDK world (owns the loop / XR session).
|
|
16
|
+
* @param profileFactory builds the app service graph from the adapter, e.g.
|
|
17
|
+
* `(adapter) => createServiceProfile("my-app", [...])`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function startServiceRuntime(world: IWSDKWorldLike, profileFactory: (adapter: IWSDKAdapter) => ServiceProfile): ServiceRuntime;
|
|
20
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EACrB,cAAc,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,cAAc,GACxD,cAAc,CAMhB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-React bootstrap for an IWSDK client: build the adapter, stand up the
|
|
3
|
+
* ServiceManager from the app's profile, and start it. The caller registers
|
|
4
|
+
* {@link makeServiceBridgeSystem} with the IWSDK world to pump per-frame ticks.
|
|
5
|
+
*/
|
|
6
|
+
import { ServiceManager } from "@realitycollective/service-framework";
|
|
7
|
+
import { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
8
|
+
/**
|
|
9
|
+
* @param world the IWSDK world (owns the loop / XR session).
|
|
10
|
+
* @param profileFactory builds the app service graph from the adapter, e.g.
|
|
11
|
+
* `(adapter) => createServiceProfile("my-app", [...])`.
|
|
12
|
+
*/
|
|
13
|
+
export function startServiceRuntime(world, profileFactory) {
|
|
14
|
+
const adapter = new IWSDKAdapter(world);
|
|
15
|
+
const manager = new ServiceManager();
|
|
16
|
+
manager.initializeProfile(profileFactory(adapter));
|
|
17
|
+
manager.start();
|
|
18
|
+
return { manager, adapter };
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAqB,EACrB,cAAyD;IAEzD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;IACrC,OAAO,CAAC,iBAAiB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { DEFAULT_CAPABILITIES } from "./runtime-adapter.js";
|
|
2
|
+
export type { AdapterCapabilities, CapabilitiesListener, FrameInfo, FrameListener, RuntimeAdapter, Unsubscribe, } from "./runtime-adapter.js";
|
|
3
|
+
export type { CreateSystemLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKWorldLike, } from "./iwsdk-host.js";
|
|
4
|
+
export { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
5
|
+
export { MockRuntimeAdapter } from "./mock-runtime-adapter.js";
|
|
6
|
+
export { SnapshotService } from "./snapshot-service.js";
|
|
7
|
+
export type { ServiceContext, SnapshotListener } from "./snapshot-service.js";
|
|
8
|
+
export { makeServiceBridgeSystem } from "./service-bridge-system.js";
|
|
9
|
+
export type { ServiceBridgeSystemOptions } from "./service-bridge-system.js";
|
|
10
|
+
export { startServiceRuntime } from "./bootstrap.js";
|
|
11
|
+
export type { ServiceRuntime } from "./bootstrap.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,SAAS,EACT,aAAa,EACb,cAAc,EACd,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { DEFAULT_CAPABILITIES } from "./runtime-adapter.js";
|
|
2
|
+
export { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
3
|
+
export { MockRuntimeAdapter } from "./mock-runtime-adapter.js";
|
|
4
|
+
export { SnapshotService } from "./snapshot-service.js";
|
|
5
|
+
export { makeServiceBridgeSystem } from "./service-bridge-system.js";
|
|
6
|
+
export { startServiceRuntime } from "./bootstrap.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAkB5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so
|
|
3
|
+
* the adapter is a passive fan-out: the `ServiceBridgeSystem` (a normal IWSDK
|
|
4
|
+
* system) calls {@link IWSDKAdapter.emitFrame} once per frame and the adapter
|
|
5
|
+
* notifies subscribed services. Capabilities are refined from the XR session via
|
|
6
|
+
* {@link IWSDKAdapter.setCapabilities}.
|
|
7
|
+
*/
|
|
8
|
+
import { type AdapterCapabilities, type CapabilitiesListener, type FrameListener, type RuntimeAdapter, type Unsubscribe } from "./runtime-adapter.js";
|
|
9
|
+
import type { IWSDKWorldLike } from "./iwsdk-host.js";
|
|
10
|
+
export declare class IWSDKAdapter implements RuntimeAdapter {
|
|
11
|
+
private readonly world;
|
|
12
|
+
private readonly frameListeners;
|
|
13
|
+
private readonly capabilitiesListeners;
|
|
14
|
+
private capabilities;
|
|
15
|
+
constructor(world: IWSDKWorldLike);
|
|
16
|
+
onFrame(listener: FrameListener): Unsubscribe;
|
|
17
|
+
getCapabilities(): AdapterCapabilities;
|
|
18
|
+
onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
|
|
19
|
+
/**
|
|
20
|
+
* The IWSDK `World` this adapter is bound to. Reserved for capability
|
|
21
|
+
* derivation from the live XR session (see the package README "Capabilities"
|
|
22
|
+
* section) — the one piece still to be wired to the real IWSDK session API.
|
|
23
|
+
*/
|
|
24
|
+
getWorld(): IWSDKWorldLike;
|
|
25
|
+
/** Refine capabilities once the XR session reports them; notifies subscribers. */
|
|
26
|
+
setCapabilities(capabilities: Partial<AdapterCapabilities>): void;
|
|
27
|
+
/** Called once per frame by the ECS bridge system. */
|
|
28
|
+
emitFrame(timestamp: number, delta: number): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=iwsdk-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iwsdk-adapter.d.ts","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,qBAAa,YAAa,YAAW,cAAc;IAK9B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAmC;IACzE,OAAO,CAAC,YAAY,CAA6C;gBAE7B,KAAK,EAAE,cAAc;IAElD,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;IAO7C,eAAe,IAAI,mBAAmB;IAItC,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW;IAOxE;;;;OAIG;IACI,QAAQ,IAAI,cAAc;IAIjC,kFAAkF;IAC3E,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAKxE,sDAAsD;IAC/C,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAIzD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so
|
|
3
|
+
* the adapter is a passive fan-out: the `ServiceBridgeSystem` (a normal IWSDK
|
|
4
|
+
* system) calls {@link IWSDKAdapter.emitFrame} once per frame and the adapter
|
|
5
|
+
* notifies subscribed services. Capabilities are refined from the XR session via
|
|
6
|
+
* {@link IWSDKAdapter.setCapabilities}.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_CAPABILITIES, } from "./runtime-adapter.js";
|
|
9
|
+
export class IWSDKAdapter {
|
|
10
|
+
world;
|
|
11
|
+
frameListeners = new Set();
|
|
12
|
+
capabilitiesListeners = new Set();
|
|
13
|
+
capabilities = DEFAULT_CAPABILITIES;
|
|
14
|
+
constructor(world) {
|
|
15
|
+
this.world = world;
|
|
16
|
+
}
|
|
17
|
+
onFrame(listener) {
|
|
18
|
+
this.frameListeners.add(listener);
|
|
19
|
+
return () => {
|
|
20
|
+
this.frameListeners.delete(listener);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
getCapabilities() {
|
|
24
|
+
return this.capabilities;
|
|
25
|
+
}
|
|
26
|
+
onCapabilitiesChange(listener) {
|
|
27
|
+
this.capabilitiesListeners.add(listener);
|
|
28
|
+
return () => {
|
|
29
|
+
this.capabilitiesListeners.delete(listener);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The IWSDK `World` this adapter is bound to. Reserved for capability
|
|
34
|
+
* derivation from the live XR session (see the package README "Capabilities"
|
|
35
|
+
* section) — the one piece still to be wired to the real IWSDK session API.
|
|
36
|
+
*/
|
|
37
|
+
getWorld() {
|
|
38
|
+
return this.world;
|
|
39
|
+
}
|
|
40
|
+
/** Refine capabilities once the XR session reports them; notifies subscribers. */
|
|
41
|
+
setCapabilities(capabilities) {
|
|
42
|
+
this.capabilities = { ...this.capabilities, ...capabilities };
|
|
43
|
+
this.capabilitiesListeners.forEach((listener) => listener(this.capabilities));
|
|
44
|
+
}
|
|
45
|
+
/** Called once per frame by the ECS bridge system. */
|
|
46
|
+
emitFrame(timestamp, delta) {
|
|
47
|
+
const frame = { timestamp, delta };
|
|
48
|
+
this.frameListeners.forEach((listener) => listener(frame));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=iwsdk-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,oBAAoB,GAOrB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,OAAO,YAAY;IAKa;IAJnB,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;IACjE,YAAY,GAAwB,oBAAoB,CAAC;IAEjE,YAAoC,KAAqB;QAArB,UAAK,GAAL,KAAK,CAAgB;IAAG,CAAC;IAEtD,OAAO,CAAC,QAAuB;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;IACJ,CAAC;IAEM,eAAe;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEM,oBAAoB,CAAC,QAA8B;QACxD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,QAAQ;QACb,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,kFAAkF;IAC3E,eAAe,CAAC,YAA0C;QAC/D,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,YAAY,EAAE,CAAC;QAC9D,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,sDAAsD;IAC/C,SAAS,CAAC,SAAiB,EAAE,KAAa;QAC/C,MAAM,KAAK,GAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural contracts for the slice of `@iwsdk/core` this shim touches.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the approach the three.js and Babylon.js bridges take with their
|
|
5
|
+
* engine packages: the shim never imports `@iwsdk/core` directly, so it builds
|
|
6
|
+
* and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK
|
|
7
|
+
* works without bumping this package. Consumers pass the real `World`,
|
|
8
|
+
* `createSystem` and `VisibilityState.Visible` value through
|
|
9
|
+
* {@link makeServiceBridgeSystem}.
|
|
10
|
+
*/
|
|
11
|
+
/** IWSDK exposes reactive values as `{ value }` signals; the bridge only reads `.value`. */
|
|
12
|
+
export interface IWSDKSignalLike<TValue> {
|
|
13
|
+
readonly value: TValue;
|
|
14
|
+
}
|
|
15
|
+
/** The slice of an IWSDK `World` the bridge reads: the visibility signal. */
|
|
16
|
+
export interface IWSDKWorldLike<TVisibility = unknown> {
|
|
17
|
+
readonly visibilityState: IWSDKSignalLike<TVisibility>;
|
|
18
|
+
}
|
|
19
|
+
/** The per-frame entry point IWSDK invokes on a registered system. */
|
|
20
|
+
export interface IWSDKSystemLike {
|
|
21
|
+
update(delta: number, time: number): void;
|
|
22
|
+
}
|
|
23
|
+
/** Constructor shape of the base class IWSDK's `createSystem` returns. */
|
|
24
|
+
export type IWSDKSystemConstructor = new (...args: unknown[]) => IWSDKSystemLike;
|
|
25
|
+
/**
|
|
26
|
+
* Structural shape of IWSDK's `createSystem` factory. `createSystem(schema)`
|
|
27
|
+
* returns a base system class that the bridge extends; the bridge passes an
|
|
28
|
+
* empty schema because it queries no ECS components.
|
|
29
|
+
*/
|
|
30
|
+
export type CreateSystemLike = (schema?: Record<string, unknown>) => IWSDKSystemConstructor;
|
|
31
|
+
//# sourceMappingURL=iwsdk-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iwsdk-host.d.ts","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4FAA4F;AAC5F,MAAM,WAAW,eAAe,CAAC,MAAM;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc,CAAC,WAAW,GAAG,OAAO;IACnD,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;CACxD;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,0EAA0E;AAC1E,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,eAAe,CAAC;AAEjF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural contracts for the slice of `@iwsdk/core` this shim touches.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the approach the three.js and Babylon.js bridges take with their
|
|
5
|
+
* engine packages: the shim never imports `@iwsdk/core` directly, so it builds
|
|
6
|
+
* and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK
|
|
7
|
+
* works without bumping this package. Consumers pass the real `World`,
|
|
8
|
+
* `createSystem` and `VisibilityState.Visible` value through
|
|
9
|
+
* {@link makeServiceBridgeSystem}.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=iwsdk-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iwsdk-host.js","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless {@link RuntimeAdapter} for tests. Services run against this with no
|
|
3
|
+
* IWSDK / renderer / headset; tests drive the loop by calling
|
|
4
|
+
* {@link MockRuntimeAdapter.emitFrame} and refine gating with
|
|
5
|
+
* {@link MockRuntimeAdapter.setCapabilities}.
|
|
6
|
+
*/
|
|
7
|
+
import { type AdapterCapabilities, type CapabilitiesListener, type FrameListener, type RuntimeAdapter, type Unsubscribe } from "./runtime-adapter.js";
|
|
8
|
+
export declare class MockRuntimeAdapter implements RuntimeAdapter {
|
|
9
|
+
private readonly frameListeners;
|
|
10
|
+
private readonly capabilitiesListeners;
|
|
11
|
+
private capabilities;
|
|
12
|
+
constructor(capabilities?: Partial<AdapterCapabilities>);
|
|
13
|
+
onFrame(listener: FrameListener): Unsubscribe;
|
|
14
|
+
getCapabilities(): AdapterCapabilities;
|
|
15
|
+
onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
|
|
16
|
+
/** Refine capabilities in tests; notifies subscribers. */
|
|
17
|
+
setCapabilities(capabilities: Partial<AdapterCapabilities>): void;
|
|
18
|
+
/** Drive a frame in tests. */
|
|
19
|
+
emitFrame(timestamp?: number, delta?: number): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=mock-runtime-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/mock-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,kBAAmB,YAAW,cAAc;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAmC;IACzE,OAAO,CAAC,YAAY,CAAsB;gBAEvB,YAAY,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAI3D,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;IAO7C,eAAe,IAAI,mBAAmB;IAItC,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW;IAOxE,0DAA0D;IACnD,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAKxE,8BAA8B;IACvB,SAAS,CAAC,SAAS,SAAI,EAAE,KAAK,SAAS,GAAG,IAAI;CAGtD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless {@link RuntimeAdapter} for tests. Services run against this with no
|
|
3
|
+
* IWSDK / renderer / headset; tests drive the loop by calling
|
|
4
|
+
* {@link MockRuntimeAdapter.emitFrame} and refine gating with
|
|
5
|
+
* {@link MockRuntimeAdapter.setCapabilities}.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_CAPABILITIES, } from "./runtime-adapter.js";
|
|
8
|
+
export class MockRuntimeAdapter {
|
|
9
|
+
frameListeners = new Set();
|
|
10
|
+
capabilitiesListeners = new Set();
|
|
11
|
+
capabilities;
|
|
12
|
+
constructor(capabilities = {}) {
|
|
13
|
+
this.capabilities = { ...DEFAULT_CAPABILITIES, ...capabilities };
|
|
14
|
+
}
|
|
15
|
+
onFrame(listener) {
|
|
16
|
+
this.frameListeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
this.frameListeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
getCapabilities() {
|
|
22
|
+
return this.capabilities;
|
|
23
|
+
}
|
|
24
|
+
onCapabilitiesChange(listener) {
|
|
25
|
+
this.capabilitiesListeners.add(listener);
|
|
26
|
+
return () => {
|
|
27
|
+
this.capabilitiesListeners.delete(listener);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Refine capabilities in tests; notifies subscribers. */
|
|
31
|
+
setCapabilities(capabilities) {
|
|
32
|
+
this.capabilities = { ...this.capabilities, ...capabilities };
|
|
33
|
+
this.capabilitiesListeners.forEach((listener) => listener(this.capabilities));
|
|
34
|
+
}
|
|
35
|
+
/** Drive a frame in tests. */
|
|
36
|
+
emitFrame(timestamp = 0, delta = 1 / 72) {
|
|
37
|
+
this.frameListeners.forEach((listener) => listener({ timestamp, delta }));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=mock-runtime-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-runtime-adapter.js","sourceRoot":"","sources":["../src/mock-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,oBAAoB,GAMrB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,OAAO,kBAAkB;IACZ,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;IACjE,YAAY,CAAsB;IAE1C,YAAmB,eAA6C,EAAE;QAChE,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,oBAAoB,EAAE,GAAG,YAAY,EAAE,CAAC;IACnE,CAAC;IAEM,OAAO,CAAC,QAAuB;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;IACJ,CAAC;IAEM,eAAe;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEM,oBAAoB,CAAC,QAA8B;QACxD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC;IAED,0DAA0D;IACnD,eAAe,CAAC,YAA0C;QAC/D,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,YAAY,EAAE,CAAC;QAC9D,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,8BAA8B;IACvB,SAAS,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,EAAE;QAC5C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter contract (IWSDK-only, minimal frame-source).
|
|
3
|
+
*
|
|
4
|
+
* Trimmed to what an IWSDK-only client needs: a per-frame fan-out and
|
|
5
|
+
* capability flags. IWSDK already owns sessions, input and rendering, so the
|
|
6
|
+
* adapter deliberately does NOT re-abstract those. {@link MockRuntimeAdapter}
|
|
7
|
+
* implements this same interface so services can be unit-tested headless.
|
|
8
|
+
*/
|
|
9
|
+
export type Unsubscribe = () => void;
|
|
10
|
+
export interface FrameInfo {
|
|
11
|
+
/** Frame timestamp in milliseconds (IWSDK system `time`). */
|
|
12
|
+
readonly timestamp: number;
|
|
13
|
+
/** Seconds elapsed since the previous frame (IWSDK system `delta`). */
|
|
14
|
+
readonly delta: number;
|
|
15
|
+
}
|
|
16
|
+
/** XR capabilities services gate on (e.g. passthrough requires `immersive`). */
|
|
17
|
+
export interface AdapterCapabilities {
|
|
18
|
+
readonly immersive: boolean;
|
|
19
|
+
readonly handTracking: boolean;
|
|
20
|
+
readonly planeDetection: boolean;
|
|
21
|
+
readonly passthrough: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_CAPABILITIES: AdapterCapabilities;
|
|
24
|
+
export type FrameListener = (frame: FrameInfo) => void;
|
|
25
|
+
export type CapabilitiesListener = (capabilities: AdapterCapabilities) => void;
|
|
26
|
+
export interface RuntimeAdapter {
|
|
27
|
+
/** Subscribe to per-frame updates; returns an unsubscribe handle. */
|
|
28
|
+
onFrame(listener: FrameListener): Unsubscribe;
|
|
29
|
+
/** Current XR capabilities (may change once a session is established). */
|
|
30
|
+
getCapabilities(): AdapterCapabilities;
|
|
31
|
+
/**
|
|
32
|
+
* Subscribe to capability changes; returns an unsubscribe handle. Mirrors
|
|
33
|
+
* {@link RuntimeAdapter.onFrame} so gating services can react to a session
|
|
34
|
+
* coming online instead of polling {@link RuntimeAdapter.getCapabilities}
|
|
35
|
+
* every frame.
|
|
36
|
+
*/
|
|
37
|
+
onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=runtime-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-adapter.d.ts","sourceRoot":"","sources":["../src/runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,oBAAoB,EAAE,mBAKlC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,YAAY,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAAC;IAC9C,0EAA0E;IAC1E,eAAe,IAAI,mBAAmB,CAAC;IACvC;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW,CAAC;CACnE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter contract (IWSDK-only, minimal frame-source).
|
|
3
|
+
*
|
|
4
|
+
* Trimmed to what an IWSDK-only client needs: a per-frame fan-out and
|
|
5
|
+
* capability flags. IWSDK already owns sessions, input and rendering, so the
|
|
6
|
+
* adapter deliberately does NOT re-abstract those. {@link MockRuntimeAdapter}
|
|
7
|
+
* implements this same interface so services can be unit-tested headless.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_CAPABILITIES = {
|
|
10
|
+
immersive: false,
|
|
11
|
+
handTracking: false,
|
|
12
|
+
planeDetection: false,
|
|
13
|
+
passthrough: false,
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=runtime-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-adapter.js","sourceRoot":"","sources":["../src/runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,MAAM,CAAC,MAAM,oBAAoB,GAAwB;IACvD,SAAS,EAAE,KAAK;IAChB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,KAAK;IACrB,WAAW,EAAE,KAAK;CACnB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single ECS-services bridge. A normal IWSDK system that, each frame:
|
|
3
|
+
* - maps IWSDK `visibilityState` to manager focus/pause (auto-pause when the
|
|
4
|
+
* headset is removed), and
|
|
5
|
+
* - drives the adapter's per-frame fan-out via {@link IWSDKAdapter.emitFrame}.
|
|
6
|
+
*
|
|
7
|
+
* Game logic is only ticked while the session is visible/focused: in the
|
|
8
|
+
* browser / 2D preview the host app shows its own gate (e.g. an "Enter VR"
|
|
9
|
+
* overlay) and the services stay idle until the player enters VR.
|
|
10
|
+
*
|
|
11
|
+
* This is the only place the engine loop touches the service layer; services
|
|
12
|
+
* themselves never see IWSDK. The IWSDK primitives (`createSystem` and the
|
|
13
|
+
* `VisibilityState.Visible` value) are injected so this package never imports
|
|
14
|
+
* `@iwsdk/core` — mirroring how the three.js / Babylon.js bridges keep their
|
|
15
|
+
* engine packages at arm's length.
|
|
16
|
+
*/
|
|
17
|
+
import type { ServiceManager } from "@realitycollective/service-framework";
|
|
18
|
+
import type { IWSDKAdapter } from "./iwsdk-adapter.js";
|
|
19
|
+
import type { CreateSystemLike, IWSDKWorldLike } from "./iwsdk-host.js";
|
|
20
|
+
export interface ServiceBridgeSystemOptions<TVisibility = unknown> {
|
|
21
|
+
/** The passive frame source fanned out to services. */
|
|
22
|
+
readonly adapter: IWSDKAdapter;
|
|
23
|
+
/** The service manager whose focus/pause signals are driven by visibility. */
|
|
24
|
+
readonly manager: ServiceManager;
|
|
25
|
+
/** The IWSDK world whose `visibilityState` is read each frame. */
|
|
26
|
+
readonly world: IWSDKWorldLike<TVisibility>;
|
|
27
|
+
/** IWSDK's `createSystem` factory (from `@iwsdk/core`). */
|
|
28
|
+
readonly createSystem: CreateSystemLike;
|
|
29
|
+
/**
|
|
30
|
+
* The `VisibilityState` value that means the session is visible/focused
|
|
31
|
+
* (IWSDK `VisibilityState.Visible`). The bridge ticks services only while
|
|
32
|
+
* `world.visibilityState.value === visibleState`.
|
|
33
|
+
*/
|
|
34
|
+
readonly visibleState: TVisibility;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Builds the IWSDK `ServiceBridgeSystem` class. Register the returned class with
|
|
38
|
+
* the world (`world.registerSystem(makeServiceBridgeSystem({ ... }))`); IWSDK
|
|
39
|
+
* then calls its `update(delta, time)` once per frame.
|
|
40
|
+
*/
|
|
41
|
+
export declare function makeServiceBridgeSystem<TVisibility>(options: ServiceBridgeSystemOptions<TVisibility>): {
|
|
42
|
+
new (...args: unknown[]): {
|
|
43
|
+
update(delta: number, time: number): void;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=service-bridge-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bridge-system.d.ts","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAExE,MAAM,WAAW,0BAA0B,CAAC,WAAW,GAAG,OAAO;IAC/D,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IAC5C,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EACjD,OAAO,EAAE,0BAA0B,CAAC,WAAW,CAAC;;sBAMhB,MAAM,QAAQ,MAAM,GAAG,IAAI;;EAgB5D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the IWSDK `ServiceBridgeSystem` class. Register the returned class with
|
|
3
|
+
* the world (`world.registerSystem(makeServiceBridgeSystem({ ... }))`); IWSDK
|
|
4
|
+
* then calls its `update(delta, time)` once per frame.
|
|
5
|
+
*/
|
|
6
|
+
export function makeServiceBridgeSystem(options) {
|
|
7
|
+
const { adapter, manager, world, createSystem, visibleState } = options;
|
|
8
|
+
let lastFocused;
|
|
9
|
+
return class ServiceBridgeSystem extends createSystem({}) {
|
|
10
|
+
update(delta, time) {
|
|
11
|
+
const focused = world.visibilityState.value === visibleState;
|
|
12
|
+
if (focused !== lastFocused) {
|
|
13
|
+
lastFocused = focused;
|
|
14
|
+
manager.emitFocusChange(focused);
|
|
15
|
+
manager.emitPauseChange({ paused: !focused });
|
|
16
|
+
}
|
|
17
|
+
// Only run game logic while visible/focused; idle in the 2D/browser
|
|
18
|
+
// preview and while the headset is removed (visible-blurred).
|
|
19
|
+
if (focused) {
|
|
20
|
+
adapter.emitFrame(time, delta);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=service-bridge-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bridge-system.js","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgD;IAEhD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACxE,IAAI,WAAgC,CAAC;IAErC,OAAO,MAAM,mBAAoB,SAAQ,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,KAAa,EAAE,IAAY;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,KAAK,YAAY,CAAC;YAE7D,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC5B,WAAW,GAAG,OAAO,CAAC;gBACtB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAChD,CAAC;YAED,oEAAoE;YACpE,8DAA8D;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SnapshotService` base — the "services own state" pattern. A snapshot service
|
|
3
|
+
* owns one immutable state object and a pub/sub list: consumers (other services,
|
|
4
|
+
* or the ECS presentation layer) subscribe and receive the current value
|
|
5
|
+
* immediately, then every subsequent publish.
|
|
6
|
+
*
|
|
7
|
+
* It has no IWSDK dependency — only `@realitycollective/service-framework` — so
|
|
8
|
+
* services that extend it stay unit-testable headless against
|
|
9
|
+
* {@link MockRuntimeAdapter}.
|
|
10
|
+
*/
|
|
11
|
+
import { BaseService } from "@realitycollective/service-framework";
|
|
12
|
+
/**
|
|
13
|
+
* The activation-context type a service constructor receives. Aliased here so
|
|
14
|
+
* every service uses one consistent, framework-correct shape (matches the
|
|
15
|
+
* `useFactory(context)` parameter the ServiceManager passes).
|
|
16
|
+
*/
|
|
17
|
+
export type ServiceContext<TConfig = unknown> = ConstructorParameters<typeof BaseService<TConfig>>[0];
|
|
18
|
+
export type SnapshotListener<TSnapshot> = (snapshot: TSnapshot) => void;
|
|
19
|
+
export declare abstract class SnapshotService<TConfig, TSnapshot> extends BaseService<TConfig> {
|
|
20
|
+
protected snapshot: TSnapshot;
|
|
21
|
+
private readonly listeners;
|
|
22
|
+
protected constructor(context: ServiceContext<TConfig>, initialSnapshot: TSnapshot);
|
|
23
|
+
getSnapshot(): TSnapshot;
|
|
24
|
+
subscribe(listener: SnapshotListener<TSnapshot>): () => void;
|
|
25
|
+
protected publishSnapshot(snapshot: TSnapshot): void;
|
|
26
|
+
protected updateSnapshot(partial: Partial<TSnapshot>): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=snapshot-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-service.d.ts","sourceRoot":"","sources":["../src/snapshot-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAEnE;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,OAAO,GAAG,OAAO,IAC1C,qBAAqB,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,MAAM,MAAM,gBAAgB,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,SAAS,KAAK,IAAI,CAAC;AAExE,8BAAsB,eAAe,CAAC,OAAO,EAAE,SAAS,CAAE,SAAQ,WAAW,CAAC,OAAO,CAAC;IACpF,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IAEpE,SAAS,aAAa,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,SAAS;IAK3E,WAAW,IAAI,SAAS;IAIxB,SAAS,CAAC,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI;IAQnE,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,GAAG,IAAI;IAKpD,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;CAG5D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SnapshotService` base — the "services own state" pattern. A snapshot service
|
|
3
|
+
* owns one immutable state object and a pub/sub list: consumers (other services,
|
|
4
|
+
* or the ECS presentation layer) subscribe and receive the current value
|
|
5
|
+
* immediately, then every subsequent publish.
|
|
6
|
+
*
|
|
7
|
+
* It has no IWSDK dependency — only `@realitycollective/service-framework` — so
|
|
8
|
+
* services that extend it stay unit-testable headless against
|
|
9
|
+
* {@link MockRuntimeAdapter}.
|
|
10
|
+
*/
|
|
11
|
+
import { BaseService } from "@realitycollective/service-framework";
|
|
12
|
+
export class SnapshotService extends BaseService {
|
|
13
|
+
snapshot;
|
|
14
|
+
listeners = new Set();
|
|
15
|
+
constructor(context, initialSnapshot) {
|
|
16
|
+
super(context);
|
|
17
|
+
this.snapshot = initialSnapshot;
|
|
18
|
+
}
|
|
19
|
+
getSnapshot() {
|
|
20
|
+
return this.snapshot;
|
|
21
|
+
}
|
|
22
|
+
subscribe(listener) {
|
|
23
|
+
this.listeners.add(listener);
|
|
24
|
+
listener(this.snapshot);
|
|
25
|
+
return () => {
|
|
26
|
+
this.listeners.delete(listener);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
publishSnapshot(snapshot) {
|
|
30
|
+
this.snapshot = snapshot;
|
|
31
|
+
this.listeners.forEach((listener) => listener(this.snapshot));
|
|
32
|
+
}
|
|
33
|
+
updateSnapshot(partial) {
|
|
34
|
+
this.publishSnapshot({ ...this.snapshot, ...partial });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=snapshot-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-service.js","sourceRoot":"","sources":["../src/snapshot-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAYnE,MAAM,OAAgB,eAAoC,SAAQ,WAAoB;IAC1E,QAAQ,CAAY;IACb,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEpE,YAAsB,OAAgC,EAAE,eAA0B;QAChF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC;IAClC,CAAC;IAEM,WAAW;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAEM,SAAS,CAAC,QAAqC;QACpD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAES,eAAe,CAAC,QAAmB;QAC3C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,CAAC;IAES,cAAc,CAAC,OAA2B;QAClD,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@realitycollective/service-framework-iwsdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Meta IWSDK (WebXR) frame-source bindings for the Reality Collective TypeScript Service Framework.",
|
|
5
|
+
"author": "Reality Collective",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"Examples",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"service-framework",
|
|
23
|
+
"iwsdk",
|
|
24
|
+
"webxr",
|
|
25
|
+
"meta",
|
|
26
|
+
"xr",
|
|
27
|
+
"realitycollective",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/realitycollective/com.realitycollective.service-framework.ts#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@iwsdk/core": ">=0.4.0 <0.5.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@iwsdk/core": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@realitycollective/service-framework": "^1.0.0"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.build.json",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|