@opsydyn/effect-three-foldkit 0.4.0 → 0.6.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAyBhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAyPpD;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAiB,WAAW;;;;;EAInD,CAAC;AAEL,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AA0BhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+QpD;;;GAGG;AACH,eAAO,MAAM,cAAc,gBAAiB,WAAW;;;;;EAInD,CAAC;AAEL,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/view.d.ts CHANGED
@@ -33,6 +33,9 @@ export type GestureEndDetail = typeof SceneSchema.GestureEndDetail.Type;
33
33
  export type CollisionDetail = typeof SceneSchema.CollisionDetail.Type;
34
34
  /** Detail of contact force events: the two bodies, and how hard they pushed. */
35
35
  export type ContactForceDetail = typeof SceneSchema.ContactForceDetail.Type;
36
+ export type PhysicsReachedDetail = typeof SceneSchema.PhysicsReachedDetail.Type;
37
+ export type AssetLoadedDetail = typeof SceneSchema.AssetLoadedDetail.Type;
38
+ export type AssetFailedDetail = typeof SceneSchema.AssetFailedDetail.Type;
36
39
  /** Detail of measured events: a node's world bounding box. */
37
40
  export type MeasuredDetail = typeof SceneSchema.MeasuredDetail.Type;
38
41
  /** Detail of auto-rotated events: where a camera turning by itself got to. */
@@ -113,6 +116,12 @@ export type ViewConfig<Message> = {
113
116
  * set the threshold high.
114
117
  */
115
118
  readonly onContactForce?: (detail: ContactForceDetail) => Message;
119
+ /**
120
+ * A world on `physics({ until })` reached that step and waits there, with its run and a
121
+ * checksum of its bodies' poses (ADR-014): the moment to apply the inputs due at that
122
+ * step and move `until` on. It follows the collisions of the steps that led there.
123
+ */
124
+ readonly onPhysicsReached?: (detail: PhysicsReachedDetail) => Message;
116
125
  /**
117
126
  * A node listed in the scene's `measure` has a new world bounding box (Drei's `Bounds`
118
127
  * measuring, as a Message): fit the camera to it with `fitCamera`.
@@ -168,6 +177,13 @@ export type ViewConfig<Message> = {
168
177
  readonly onError?: (detail: {
169
178
  readonly error: SceneError;
170
179
  }) => Message;
180
+ /**
181
+ * An asset the scene asks for (`scene({ assets })`) has loaded and is lent under its id:
182
+ * at once, for one another scene loaded already. A model's parts and clips come with it.
183
+ */
184
+ readonly onAssetLoaded?: (detail: AssetLoadedDetail) => Message;
185
+ /** An asset the scene asks for could not be loaded, and why. */
186
+ readonly onAssetFailed?: (detail: AssetFailedDetail) => Message;
171
187
  /**
172
188
  * What each scene update did (operations by kind) and the GPU resources held afterwards.
173
189
  * Fires at startup and after every update that changed something, never for no-ops.
@@ -1 +1 @@
1
- {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opsydyn/effect-three-foldkit",
3
- "version": "0.4.0",
3
+ "version": "0.6.4",
4
4
  "description": "Thin Foldkit adapter for @opsydyn/effect-three.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,17 +44,17 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@arethetypeswrong/cli": "0.18.5",
47
- "@effect/platform-browser": "4.0.0",
47
+ "@effect/platform-browser": "4.1.0",
48
48
  "@fontsource/inter": "5.3.0",
49
49
  "@microsoft/api-extractor": "7.59.4",
50
- "@opsydyn/effect-three": "0.1.0",
51
- "@opsydyn/effect-three-troika": "0.1.0",
50
+ "@opsydyn/effect-three": "0.6.3",
51
+ "@opsydyn/effect-three-troika": "0.6.3",
52
52
  "@opsydyn/oxlint-effect": "2.0.1",
53
53
  "@types/bun": "1.4.3",
54
54
  "@types/pngjs": "6.0.5",
55
55
  "@types/three": "0.186.0",
56
- "effect": "4.0.0",
57
- "foldkit": "0.167.0",
56
+ "effect": "4.1.0",
57
+ "foldkit": "0.170.0",
58
58
  "iwer": "2.5.0",
59
59
  "oxlint": "1.88.0",
60
60
  "oxlint-tsgolint": "7.0.2004",
@@ -66,9 +66,9 @@
66
66
  "typescript": "7.0.2"
67
67
  },
68
68
  "peerDependencies": {
69
- "@opsydyn/effect-three": "0.4.0",
69
+ "@opsydyn/effect-three": "0.6.4",
70
70
  "effect": "^4.0.0",
71
- "foldkit": "0.167.0",
71
+ "foldkit": ">=0.167.0 <0.171.0",
72
72
  "three": ">=0.186.0 <0.187.0"
73
73
  }
74
74
  }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * The assets scenes ask for with `scene({ assets })` (ADR-015), held for the environment:
3
+ * each loaded once however many scenes ask, lent to the store under its id, its load
4
+ * cancelled when no scene asks for it any more, and withdrawn and disposed when the last
5
+ * scene stops asking. Each scene hears whether what it asked for loaded or failed.
6
+ *
7
+ * An asset with coarser `levels` streams in: every level loads at once, each is lent as it
8
+ * arrives if it is finer than what is shown (replacing it), and once the finest is shown
9
+ * the coarser loads still under way are cancelled. Only the finest failing fails it.
10
+ *
11
+ * An id names one asset at a time: a scene asking for an id another scene holds as
12
+ * something else (another kind or URL) is told it failed, and gets it once the other lets
13
+ * go.
14
+ *
15
+ * @module
16
+ */
17
+ import { type Asset, assetsEqual } from '@opsydyn/effect-three';
18
+
19
+ /** An asset as loaded and lent: what a model offers, and how to withdraw and free it. */
20
+ export type Lent = {
21
+ readonly parts: ReadonlyArray<string>;
22
+ readonly clips: ReadonlyArray<string>;
23
+ readonly release: () => void;
24
+ };
25
+
26
+ /** Loads one asset and lends it under its id; gives up (rejecting) when `signal` aborts. */
27
+ export type AssetLoader = (asset: Asset, signal: AbortSignal) => Promise<Lent>;
28
+
29
+ /**
30
+ * What a scene hears about an asset it asked for: a level of detail shown (0 for the
31
+ * coarsest of its levels, `levels.length` for its URL, the finest), or its finest failed.
32
+ */
33
+ export type AssetNotice =
34
+ | ({
35
+ readonly _tag: 'Loaded';
36
+ readonly level: number;
37
+ readonly final: boolean;
38
+ } & Asset &
39
+ Pick<Lent, 'parts' | 'clips'>)
40
+ | ({ readonly _tag: 'Failed'; readonly reason: string } & Asset);
41
+
42
+ /** Who asks: a mounted scene, by any object that stands for it. */
43
+ type Holder = object;
44
+
45
+ type Notify = (notice: AssetNotice) => void;
46
+
47
+ type Entry = {
48
+ readonly asset: Asset;
49
+ readonly holders: Set<Holder>;
50
+ /** One a level, coarsest first: aborted once a finer level is shown, or the entry ends. */
51
+ readonly controllers: ReadonlyArray<AbortController>;
52
+ /** The finest level lent so far. */
53
+ shown: { readonly level: number; readonly lent: Lent } | undefined;
54
+ /** Why the finest level could not be loaded, once it could not. */
55
+ failure: string | undefined;
56
+ };
57
+
58
+ const sameAsset = (a: Asset, b: Asset): boolean =>
59
+ a.kind === b.kind &&
60
+ a.url === b.url &&
61
+ a.levels.length === b.levels.length &&
62
+ a.levels.every((level, i) => level === b.levels[i]);
63
+
64
+ /** Every level's URL, coarsest first: the asset's own URL is the last, and finest. */
65
+ const urlsOf = (asset: Asset): ReadonlyArray<string> => [...asset.levels, asset.url];
66
+
67
+ const reasonOf = (asset: Asset, cause: unknown): string =>
68
+ cause instanceof Error && cause.message !== ''
69
+ ? `Could not load ${asset.url}: ${cause.message}`
70
+ : `Could not load ${asset.url}`;
71
+
72
+ const taken = (asset: Asset, holding: Asset): AssetNotice => ({
73
+ _tag: 'Failed',
74
+ ...asset,
75
+ reason: `"${asset.id}" is already ${holding.url} for another scene`,
76
+ });
77
+
78
+ /** Creates the registry for one environment, loading with `load`. */
79
+ export const makeAssetRegistry = (load: AssetLoader) => {
80
+ const entries = new Map<string, Entry>();
81
+ /** What each scene asks for now, and how to tell it. */
82
+ const asking = new Map<
83
+ Holder,
84
+ { readonly assets: ReadonlyArray<Asset>; readonly notify: Notify }
85
+ >();
86
+
87
+ const tellAll = (entry: Entry, notice: AssetNotice): void => {
88
+ for (const holder of entry.holders) asking.get(holder)?.notify(notice);
89
+ };
90
+
91
+ const loadedNotice = (entry: Entry, level: number, lent: Lent): AssetNotice => ({
92
+ _tag: 'Loaded',
93
+ ...entry.asset,
94
+ level,
95
+ final: level === entry.asset.levels.length,
96
+ parts: lent.parts,
97
+ clips: lent.clips,
98
+ });
99
+
100
+ /** What a scene that starts holding an entry hears at once: what is shown, and a failure. */
101
+ const noticesOf = (entry: Entry): ReadonlyArray<AssetNotice> => [
102
+ ...(entry.shown === undefined
103
+ ? []
104
+ : [loadedNotice(entry, entry.shown.level, entry.shown.lent)]),
105
+ ...(entry.failure === undefined
106
+ ? []
107
+ : [{ _tag: 'Failed' as const, ...entry.asset, reason: entry.failure }]),
108
+ ];
109
+
110
+ /** Starts loading every level of `asset`; what comes back too late is freed unseen. */
111
+ const start = (asset: Asset): Entry => {
112
+ const urls = urlsOf(asset);
113
+ const finest = urls.length - 1;
114
+ const entry: Entry = {
115
+ asset,
116
+ holders: new Set(),
117
+ controllers: urls.map(() => new AbortController()),
118
+ shown: undefined,
119
+ failure: undefined,
120
+ };
121
+ entries.set(asset.id, entry);
122
+ function current(level: number): boolean {
123
+ return entries.get(asset.id) === entry && entry.controllers[level]?.signal.aborted === false;
124
+ }
125
+ function loaded(level: number) {
126
+ return (lent: Lent): void => {
127
+ if (!current(level) || (entry.shown !== undefined && entry.shown.level >= level)) {
128
+ lent.release();
129
+ return;
130
+ }
131
+ // Lent first, then the coarser one freed: its withdrawal leaves the new loan.
132
+ const before = entry.shown;
133
+ entry.shown = { level, lent };
134
+ before?.lent.release();
135
+ if (level === finest)
136
+ for (const coarser of entry.controllers.slice(0, level)) coarser.abort();
137
+ tellAll(entry, loadedNotice(entry, level, lent));
138
+ };
139
+ }
140
+ function failed(level: number) {
141
+ return (cause: unknown): void => {
142
+ // A coarser level that fails is skipped; only the finest failing fails the asset.
143
+ if (!current(level) || level !== finest) return;
144
+ const reason = reasonOf({ ...asset, url: urls[level] ?? asset.url }, cause);
145
+ entry.failure = reason;
146
+ tellAll(entry, { _tag: 'Failed', ...asset, reason });
147
+ };
148
+ }
149
+ urls.forEach((url, level) => {
150
+ const signal = entry.controllers[level]?.signal ?? AbortSignal.abort();
151
+ load({ ...asset, url }, signal).then(loaded(level), failed(level));
152
+ });
153
+ return entry;
154
+ };
155
+
156
+ /** Ends an entry: its loads cancelled, and what it lent withdrawn and freed. */
157
+ const end = (entry: Entry): void => {
158
+ entries.delete(entry.asset.id);
159
+ for (const controller of entry.controllers) controller.abort();
160
+ entry.shown?.lent.release();
161
+ };
162
+
163
+ /** `holder` lets go of `id`; the last to let go ends it, and whoever waited on it asks again. */
164
+ const letGo = (holder: Holder, id: string): void => {
165
+ const entry = entries.get(id);
166
+ if (entry === undefined || !entry.holders.delete(holder) || entry.holders.size > 0) return;
167
+ end(entry);
168
+ for (const [waiting, { assets }] of asking) {
169
+ if (assets.some((asset) => asset.id === id)) hold(waiting, assets);
170
+ }
171
+ };
172
+
173
+ /** `holder` takes each of `assets` it does not hold yet. */
174
+ const hold = (holder: Holder, assets: ReadonlyArray<Asset>): void => {
175
+ for (const asset of assets) {
176
+ const entry = entries.get(asset.id) ?? start(asset);
177
+ if (!sameAsset(entry.asset, asset)) {
178
+ asking.get(holder)?.notify(taken(asset, entry.asset));
179
+ continue;
180
+ }
181
+ if (entry.holders.has(holder)) continue;
182
+ entry.holders.add(holder);
183
+ for (const notice of noticesOf(entry)) asking.get(holder)?.notify(notice);
184
+ }
185
+ };
186
+
187
+ /**
188
+ * What `holder` asks for now: loads what is new, lets go of what it no longer asks for
189
+ * (or asks for as something else), and tells it, through `notify`, when each asset it
190
+ * holds has loaded or failed (at once, for one loaded already).
191
+ */
192
+ const want = (holder: Holder, assets: ReadonlyArray<Asset>, notify: Notify): void => {
193
+ const before = asking.get(holder);
194
+ asking.set(holder, { assets, notify });
195
+ if (before !== undefined && assetsEqual(before.assets, assets)) return;
196
+ for (const old of before?.assets ?? []) {
197
+ const still = assets.find((asset) => asset.id === old.id);
198
+ if (still === undefined || !sameAsset(still, old)) letGo(holder, old.id);
199
+ }
200
+ hold(holder, assets);
201
+ };
202
+
203
+ /** `holder` (a scene going away) asks for nothing any more. */
204
+ const release = (holder: Holder): void => {
205
+ want(holder, [], () => undefined);
206
+ asking.delete(holder);
207
+ };
208
+
209
+ /** Ends every load and frees every asset (the environment going away). */
210
+ const dispose = (): void => {
211
+ asking.clear();
212
+ for (const entry of [...entries.values()]) end(entry);
213
+ };
214
+
215
+ return { want, release, dispose };
216
+ };
217
+
218
+ export type AssetRegistry = ReturnType<typeof makeAssetRegistry>;
package/src/assets.ts CHANGED
@@ -1,16 +1,23 @@
1
1
  /**
2
- * Loading textures, glTF models and typeface fonts by URL, as Effects an application runs
3
- * from a Foldkit Command and settles into `AsyncData`. Each asset is lent to the shared
4
- * resource store under an id: materials use a texture with `map: id`, a `borrowed({ id })`
5
- * node shows a model, and `text({ font: id })` geometry draws with a font. The environment owns what it loads: loading an id again replaces and disposes
6
- * the earlier asset, and `unload` or the environment's `dispose` frees it.
2
+ * Loading textures, glTF models, HDR panoramas and typeface fonts by URL. Each asset is
3
+ * lent to the shared resource store under an id: materials use a texture with `map: id`,
4
+ * a `borrowed({ id })` node shows a model, `environment({ map: id })` a panorama, and
5
+ * `text({ font: id })` geometry draws with a font.
6
+ *
7
+ * Two ways in. A scene can ask for assets in its description (`scene({ assets })`), and
8
+ * the environment's registry loads, shares, cancels and frees them as scenes ask
9
+ * (`assetLoader` is what it loads with; ADR-015). Or an application loads one with an
10
+ * Effect from a Command and settles it into `AsyncData`; the environment owns what it
11
+ * loads that way, loading an id again replaces and disposes the earlier asset, and
12
+ * `unload` or the environment's `dispose` frees it. Use one way for any one id.
7
13
  *
8
14
  * @module
9
15
  */
10
- import type { ResourceStore } from '@opsydyn/effect-three';
16
+ import type { Asset, AssetKind, ResourceStore } from '@opsydyn/effect-three';
11
17
  import { Data, Effect } from 'effect';
12
18
  import {
13
19
  EquirectangularReflectionMapping,
20
+ LoadingManager,
14
21
  type Object3D,
15
22
  SRGBColorSpace,
16
23
  type Texture,
@@ -20,6 +27,7 @@ import { type Font, FontLoader } from 'three/addons/loaders/FontLoader.js';
20
27
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
21
28
  import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
22
29
 
30
+ import type { AssetLoader, Lent } from './assetRegistry.ts';
23
31
  import { disposeModel, namesIn } from './dom/models.ts';
24
32
 
25
33
  /** An asset could not be fetched or decoded. */
@@ -37,6 +45,78 @@ export type ModelInfo = {
37
45
  readonly clips: ReadonlyArray<string>;
38
46
  };
39
47
 
48
+ type Gltf = { readonly scene: Object3D; readonly animations: Object3D['animations'] };
49
+
50
+ const nothing: ReadonlyArray<string> = [];
51
+
52
+ /** What lending an asset gives back: no parts or clips, and how to withdraw and free it. */
53
+ const plain = (release: () => void): Lent => ({ parts: nothing, clips: nothing, release });
54
+
55
+ /** Lends a colour texture (sRGB). */
56
+ const lentTexture = (store: ResourceStore, id: string, texture: Texture): Lent => {
57
+ texture.colorSpace = SRGBColorSpace;
58
+ const withdraw = store.lendTexture(id, texture);
59
+ return plain(() => {
60
+ withdraw();
61
+ texture.dispose();
62
+ });
63
+ };
64
+
65
+ /** Lends a model's scene, keeping its clips with it so a borrowed node can play them. */
66
+ const lentModel = (store: ResourceStore, id: string, gltf: Gltf): Lent => {
67
+ gltf.scene.animations = gltf.animations;
68
+ const withdraw = store.lend(id, gltf.scene);
69
+ return {
70
+ parts: namesIn(gltf.scene),
71
+ clips: gltf.animations.map((clip) => clip.name),
72
+ release: () => {
73
+ withdraw();
74
+ disposeModel(gltf.scene);
75
+ },
76
+ };
77
+ };
78
+
79
+ /** Lends an HDR panorama for `environment({ map })`: linear, mapped for reflection. */
80
+ const lentEnvironment = (store: ResourceStore, id: string, texture: Texture): Lent => {
81
+ texture.mapping = EquirectangularReflectionMapping;
82
+ const withdraw = store.lendTexture(id, texture);
83
+ return plain(() => {
84
+ withdraw();
85
+ texture.dispose();
86
+ });
87
+ };
88
+
89
+ /** Lends a font; fonts hold no GPU resources, so withdrawing the loan frees them. */
90
+ const lentFont = (store: ResourceStore, id: string, font: Font): Lent =>
91
+ plain(store.lendFont(id, font));
92
+
93
+ type LoadAndLend = (store: ResourceStore, asset: Asset, manager: LoadingManager) => Promise<Lent>;
94
+
95
+ /** How each kind of asset loads, with a loading manager of its own, and is lent. */
96
+ const loadAndLend = {
97
+ model: async (store, { id, url }, manager) =>
98
+ lentModel(store, id, await new GLTFLoader(manager).loadAsync(url)),
99
+ texture: async (store, { id, url }, manager) =>
100
+ lentTexture(store, id, await new TextureLoader(manager).loadAsync(url)),
101
+ environment: async (store, { id, url }, manager) =>
102
+ lentEnvironment(store, id, await new HDRLoader(manager).loadAsync(url)),
103
+ font: async (store, { id, url }, manager) =>
104
+ lentFont(store, id, await new FontLoader(manager).loadAsync(url)),
105
+ } satisfies Record<AssetKind, LoadAndLend>;
106
+
107
+ /**
108
+ * What the environment's registry loads scenes' assets with: three's loaders, each load
109
+ * with a loading manager of its own, so aborting it cancels its requests (three's file
110
+ * and image-bitmap loaders honour the manager's abort).
111
+ */
112
+ export const assetLoader =
113
+ (store: ResourceStore): AssetLoader =>
114
+ (asset, signal) => {
115
+ const manager = new LoadingManager();
116
+ signal.addEventListener('abort', () => manager.abort(), { once: true });
117
+ return loadAndLend[asset.kind](store, asset, manager);
118
+ };
119
+
40
120
  /** Creates the asset loader for a store. One per environment. */
41
121
  export const makeAssets = (store: ResourceStore) => {
42
122
  const releases = new Map<string, () => void>();
@@ -48,47 +128,24 @@ export const makeAssets = (store: ResourceStore) => {
48
128
  };
49
129
 
50
130
  const lendTexture = (id: string) => (texture: Texture) => {
51
- texture.colorSpace = SRGBColorSpace;
52
- const withdraw = store.lendTexture(id, texture);
53
- own(id, () => {
54
- withdraw();
55
- texture.dispose();
56
- });
131
+ own(id, lentTexture(store, id, texture).release);
57
132
  return id;
58
133
  };
59
134
 
60
- const lendModel =
61
- (id: string) =>
62
- (gltf: { readonly scene: Object3D; readonly animations: Object3D['animations'] }) => {
63
- // Keep the clips with the object, so a borrowed node can play them by name.
64
- gltf.scene.animations = gltf.animations;
65
- const withdraw = store.lend(id, gltf.scene);
66
- own(id, () => {
67
- withdraw();
68
- disposeModel(gltf.scene);
69
- });
70
- const info: ModelInfo = {
71
- id,
72
- parts: namesIn(gltf.scene),
73
- clips: gltf.animations.map((clip) => clip.name),
74
- };
75
- return info;
76
- };
77
-
78
- /** Lends an HDR panorama for `environment({ map })`: linear, mapped for reflection. */
135
+ const lendModel = (id: string) => (gltf: Gltf) => {
136
+ const lent = lentModel(store, id, gltf);
137
+ own(id, lent.release);
138
+ const info: ModelInfo = { id, parts: lent.parts, clips: lent.clips };
139
+ return info;
140
+ };
141
+
79
142
  const lendEnvironment = (id: string) => (texture: Texture) => {
80
- texture.mapping = EquirectangularReflectionMapping;
81
- const withdraw = store.lendTexture(id, texture);
82
- own(id, () => {
83
- withdraw();
84
- texture.dispose();
85
- });
143
+ own(id, lentEnvironment(store, id, texture).release);
86
144
  return id;
87
145
  };
88
146
 
89
147
  const lendFont = (id: string) => (font: Font) => {
90
- // Fonts hold no GPU resources: withdrawing the loan frees them.
91
- own(id, store.lendFont(id, font));
148
+ own(id, lentFont(store, id, font).release);
92
149
  return id;
93
150
  };
94
151
 
package/src/dom/canvas.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { Op, ResourceStore, SceneEvent } from '@opsydyn/effect-three';
7
7
 
8
+ import type { AssetNotice } from '../assetRegistry.ts';
8
9
  import { domEventName, type SceneError } from '../element.ts';
9
10
  import type { Latch } from './latch.ts';
10
11
  import { summarise } from './reconciled.ts';
@@ -31,6 +32,17 @@ export const dispatchSceneError = (element: Element, error: SceneError): void =>
31
32
  element.dispatchEvent(new CustomEvent('scene-error', { detail: { error } }));
32
33
  };
33
34
 
35
+ /**
36
+ * Dispatches what the environment says about an asset the scene asked for, as a
37
+ * `scene-asset-loaded` or `scene-asset-failed` event. Not held back while time travel is
38
+ * paused: whether an asset loaded is a fact about the page, not a frame.
39
+ */
40
+ export const dispatchAssetNotice = (element: Element, notice: AssetNotice): void => {
41
+ const { _tag, ...detail } = notice;
42
+ const name = _tag === 'Loaded' ? 'scene-asset-loaded' : 'scene-asset-failed';
43
+ element.dispatchEvent(new CustomEvent(name, { detail }));
44
+ };
45
+
34
46
  /** Thunk builders, so Effect code can run DOM work with `Effect.sync(thunk)`. */
35
47
  export const canvasIn = (host: Element) => () => appendCanvas(host);
36
48
  export const removal = (node: ChildNode) => () => node.remove();
@@ -3,7 +3,13 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { Gestures, Modifiers, PointerInput, SceneEvent } from '@opsydyn/effect-three';
6
+ import type {
7
+ Gestures,
8
+ Modifiers,
9
+ PointerDevice,
10
+ PointerInput,
11
+ SceneEvent,
12
+ } from '@opsydyn/effect-three';
7
13
 
8
14
  /** The part of `DOMRect` the conversion needs. */
9
15
  export type Rect = {
@@ -33,6 +39,12 @@ const modifiersOf = (event: MouseEvent): Modifiers => ({
33
39
  alt: event.altKey,
34
40
  });
35
41
 
42
+ /** What pressed: a finger, a pen, or (for anything else, and plain mouse events) a mouse. */
43
+ const deviceOf = (event: MouseEvent): PointerDevice =>
44
+ event instanceof PointerEvent && (event.pointerType === 'touch' || event.pointerType === 'pen')
45
+ ? event.pointerType
46
+ : 'mouse';
47
+
36
48
  /** The pointer's id, or 0 for mouse events that are not pointer events. */
37
49
  const pointerIdOf = (event: MouseEvent): number =>
38
50
  event instanceof PointerEvent ? event.pointerId : 0;
@@ -64,6 +76,7 @@ export const toPointerInput = (event: MouseEvent, rect: Rect): PointerInput | un
64
76
  button: event.button,
65
77
  pointer,
66
78
  modifiers: modifiersOf(event),
79
+ device: deviceOf(event),
67
80
  };
68
81
  case 'pointerup':
69
82
  return { _tag: 'PointerUp', x, y, button: event.button, pointer };
package/src/element.ts CHANGED
@@ -63,6 +63,7 @@ export const sceneElement = CustomElement.define({
63
63
  'scene-auto-rotated': SceneSchema.AutoRotatedDetail,
64
64
  'scene-collision': SceneSchema.CollisionDetail,
65
65
  'scene-contact-force': SceneSchema.ContactForceDetail,
66
+ 'scene-physics-reached': SceneSchema.PhysicsReachedDetail,
66
67
  'scene-measured': SceneSchema.MeasuredDetail,
67
68
  'scene-label-measured': SceneSchema.LabelMeasuredDetail,
68
69
  'scene-xr': XrSessionDetail,
@@ -74,6 +75,8 @@ export const sceneElement = CustomElement.define({
74
75
  'scene-xr-meshes': SceneSchema.XrMeshesDetail,
75
76
  'scene-performance': SceneSchema.PerformanceDetail,
76
77
  'scene-error': Schema.Struct({ error: SceneError }),
78
+ 'scene-asset-loaded': SceneSchema.AssetLoadedDetail,
79
+ 'scene-asset-failed': SceneSchema.AssetFailedDetail,
77
80
  'scene-reconciled': ReconciledDetail,
78
81
  },
79
82
  });
@@ -97,6 +100,7 @@ export const domEventName = {
97
100
  AutoRotated: 'scene-auto-rotated',
98
101
  Collision: 'scene-collision',
99
102
  ContactForce: 'scene-contact-force',
103
+ PhysicsReached: 'scene-physics-reached',
100
104
  Measured: 'scene-measured',
101
105
  LabelMeasured: 'scene-label-measured',
102
106
  XrStick: 'scene-xr-stick',
@@ -13,7 +13,8 @@ import {
13
13
  type ResourceStore,
14
14
  } from '@opsydyn/effect-three';
15
15
 
16
- import { makeAssets } from './assets.ts';
16
+ import { makeAssetRegistry } from './assetRegistry.ts';
17
+ import { assetLoader, makeAssets } from './assets.ts';
17
18
  import { animationFrameScheduler, type AnimationFrameHost } from './dom/animationFrames.ts';
18
19
  import { defaultPixelRatio, type PixelRatio } from './dom/viewport.ts';
19
20
  import { webglRendererFactory } from './dom/webgl.ts';
@@ -49,6 +50,7 @@ export type EnvironmentOptions = {
49
50
  export const makeEnvironment = (host: EnvironmentHost, options: EnvironmentOptions = {}) => {
50
51
  const store: ResourceStore = createResourceStore();
51
52
  const assets = makeAssets(store);
53
+ const assetRegistry = makeAssetRegistry(assetLoader(store));
52
54
  const xrHub = createXrHub(() => host.navigator?.xr);
53
55
  return {
54
56
  host,
@@ -63,12 +65,15 @@ export const makeEnvironment = (host: EnvironmentHost, options: EnvironmentOptio
63
65
  labels: options.labels,
64
66
  /** Loads textures and glTF models by URL and lends them to the store. */
65
67
  assets,
68
+ /** The assets scenes ask for in their descriptions: loaded, shared, cancelled, freed. */
69
+ assetRegistry,
66
70
  /** Enters and leaves immersive (WebXR) sessions, from Commands. */
67
71
  xr: makeXr(xrHub),
68
72
  /** The page's presentable scenes and its session; scene mounts register here. */
69
73
  xrHub,
70
74
  /** Disposes every loaded asset and every GPU resource still held. */
71
75
  dispose: () => {
76
+ assetRegistry.dispose();
72
77
  assets.dispose();
73
78
  store.dispose();
74
79
  },
package/src/index.ts CHANGED
@@ -53,6 +53,8 @@ export * as TransformGizmo from './transformGizmo.ts';
53
53
  export * as ViewportGizmo from './viewportGizmo.ts';
54
54
  export { type OverlayView } from './overlayView.ts';
55
55
  export {
56
+ type AssetFailedDetail,
57
+ type AssetLoadedDetail,
56
58
  type AutoRotatedDetail,
57
59
  type ButtonDetail,
58
60
  type CollisionDetail,
@@ -66,6 +68,7 @@ export {
66
68
  type MeasuredDetail,
67
69
  type MissDetail,
68
70
  type PerformanceDetail,
71
+ type PhysicsReachedDetail,
69
72
  view,
70
73
  type ViewConfig,
71
74
  type WheelDetail,