@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.
package/dist/index.js CHANGED
@@ -85,6 +85,7 @@ __export(exports_viewportGizmo, {
85
85
  import { Data, Effect } from "effect";
86
86
  import {
87
87
  EquirectangularReflectionMapping,
88
+ LoadingManager,
88
89
  SRGBColorSpace,
89
90
  TextureLoader
90
91
  } from "three";
@@ -124,6 +125,48 @@ var namesIn = (root) => {
124
125
  // src/assets.ts
125
126
  class AssetLoadError extends Data.TaggedError("AssetLoadError") {
126
127
  }
128
+ var nothing = [];
129
+ var plain = (release) => ({ parts: nothing, clips: nothing, release });
130
+ var lentTexture = (store, id, texture) => {
131
+ texture.colorSpace = SRGBColorSpace;
132
+ const withdraw = store.lendTexture(id, texture);
133
+ return plain(() => {
134
+ withdraw();
135
+ texture.dispose();
136
+ });
137
+ };
138
+ var lentModel = (store, id, gltf) => {
139
+ gltf.scene.animations = gltf.animations;
140
+ const withdraw = store.lend(id, gltf.scene);
141
+ return {
142
+ parts: namesIn(gltf.scene),
143
+ clips: gltf.animations.map((clip) => clip.name),
144
+ release: () => {
145
+ withdraw();
146
+ disposeModel(gltf.scene);
147
+ }
148
+ };
149
+ };
150
+ var lentEnvironment = (store, id, texture) => {
151
+ texture.mapping = EquirectangularReflectionMapping;
152
+ const withdraw = store.lendTexture(id, texture);
153
+ return plain(() => {
154
+ withdraw();
155
+ texture.dispose();
156
+ });
157
+ };
158
+ var lentFont = (store, id, font) => plain(store.lendFont(id, font));
159
+ var loadAndLend = {
160
+ model: async (store, { id, url }, manager) => lentModel(store, id, await new GLTFLoader(manager).loadAsync(url)),
161
+ texture: async (store, { id, url }, manager) => lentTexture(store, id, await new TextureLoader(manager).loadAsync(url)),
162
+ environment: async (store, { id, url }, manager) => lentEnvironment(store, id, await new HDRLoader(manager).loadAsync(url)),
163
+ font: async (store, { id, url }, manager) => lentFont(store, id, await new FontLoader(manager).loadAsync(url))
164
+ };
165
+ var assetLoader = (store) => (asset, signal) => {
166
+ const manager = new LoadingManager;
167
+ signal.addEventListener("abort", () => manager.abort(), { once: true });
168
+ return loadAndLend[asset.kind](store, asset, manager);
169
+ };
127
170
  var makeAssets = (store) => {
128
171
  const releases = new Map;
129
172
  const own = (id, release) => {
@@ -131,39 +174,21 @@ var makeAssets = (store) => {
131
174
  releases.set(id, release);
132
175
  };
133
176
  const lendTexture = (id) => (texture) => {
134
- texture.colorSpace = SRGBColorSpace;
135
- const withdraw = store.lendTexture(id, texture);
136
- own(id, () => {
137
- withdraw();
138
- texture.dispose();
139
- });
177
+ own(id, lentTexture(store, id, texture).release);
140
178
  return id;
141
179
  };
142
180
  const lendModel = (id) => (gltf) => {
143
- gltf.scene.animations = gltf.animations;
144
- const withdraw = store.lend(id, gltf.scene);
145
- own(id, () => {
146
- withdraw();
147
- disposeModel(gltf.scene);
148
- });
149
- const info = {
150
- id,
151
- parts: namesIn(gltf.scene),
152
- clips: gltf.animations.map((clip) => clip.name)
153
- };
181
+ const lent = lentModel(store, id, gltf);
182
+ own(id, lent.release);
183
+ const info = { id, parts: lent.parts, clips: lent.clips };
154
184
  return info;
155
185
  };
156
186
  const lendEnvironment = (id) => (texture) => {
157
- texture.mapping = EquirectangularReflectionMapping;
158
- const withdraw = store.lendTexture(id, texture);
159
- own(id, () => {
160
- withdraw();
161
- texture.dispose();
162
- });
187
+ own(id, lentEnvironment(store, id, texture).release);
163
188
  return id;
164
189
  };
165
190
  const lendFont = (id) => (font) => {
166
- own(id, store.lendFont(id, font));
191
+ own(id, lentFont(store, id, font).release);
167
192
  return id;
168
193
  };
169
194
  const releaseAll = () => {
@@ -245,6 +270,7 @@ var modifiersOf = (event) => ({
245
270
  meta: event.metaKey,
246
271
  alt: event.altKey
247
272
  });
273
+ var deviceOf = (event) => event instanceof PointerEvent && (event.pointerType === "touch" || event.pointerType === "pen") ? event.pointerType : "mouse";
248
274
  var pointerIdOf = (event) => event instanceof PointerEvent ? event.pointerId : 0;
249
275
  var wheelScale = [1, 16, 100];
250
276
  var toPointerInput = (event, rect) => {
@@ -263,7 +289,8 @@ var toPointerInput = (event, rect) => {
263
289
  y,
264
290
  button: event.button,
265
291
  pointer,
266
- modifiers: modifiersOf(event)
292
+ modifiers: modifiersOf(event),
293
+ device: deviceOf(event)
267
294
  };
268
295
  case "pointerup":
269
296
  return { _tag: "PointerUp", x, y, button: event.button, pointer };
@@ -969,6 +996,7 @@ var sceneElement = CustomElement.define({
969
996
  "scene-auto-rotated": SceneSchema.AutoRotatedDetail,
970
997
  "scene-collision": SceneSchema.CollisionDetail,
971
998
  "scene-contact-force": SceneSchema.ContactForceDetail,
999
+ "scene-physics-reached": SceneSchema.PhysicsReachedDetail,
972
1000
  "scene-measured": SceneSchema.MeasuredDetail,
973
1001
  "scene-label-measured": SceneSchema.LabelMeasuredDetail,
974
1002
  "scene-xr": XrSessionDetail,
@@ -980,6 +1008,8 @@ var sceneElement = CustomElement.define({
980
1008
  "scene-xr-meshes": SceneSchema.XrMeshesDetail,
981
1009
  "scene-performance": SceneSchema.PerformanceDetail,
982
1010
  "scene-error": Schema.Struct({ error: SceneError }),
1011
+ "scene-asset-loaded": SceneSchema.AssetLoadedDetail,
1012
+ "scene-asset-failed": SceneSchema.AssetFailedDetail,
983
1013
  "scene-reconciled": ReconciledDetail
984
1014
  }
985
1015
  });
@@ -1001,6 +1031,7 @@ var domEventName = {
1001
1031
  AutoRotated: "scene-auto-rotated",
1002
1032
  Collision: "scene-collision",
1003
1033
  ContactForce: "scene-contact-force",
1034
+ PhysicsReached: "scene-physics-reached",
1004
1035
  Measured: "scene-measured",
1005
1036
  LabelMeasured: "scene-label-measured",
1006
1037
  XrStick: "scene-xr-stick",
@@ -1075,6 +1106,135 @@ import {
1075
1106
  createResourceStore
1076
1107
  } from "@opsydyn/effect-three";
1077
1108
 
1109
+ // src/assetRegistry.ts
1110
+ import { assetsEqual } from "@opsydyn/effect-three";
1111
+ var sameAsset = (a, b) => a.kind === b.kind && a.url === b.url && a.levels.length === b.levels.length && a.levels.every((level, i) => level === b.levels[i]);
1112
+ var urlsOf = (asset) => [...asset.levels, asset.url];
1113
+ var reasonOf = (asset, cause) => cause instanceof Error && cause.message !== "" ? `Could not load ${asset.url}: ${cause.message}` : `Could not load ${asset.url}`;
1114
+ var taken = (asset, holding) => ({
1115
+ _tag: "Failed",
1116
+ ...asset,
1117
+ reason: `"${asset.id}" is already ${holding.url} for another scene`
1118
+ });
1119
+ var makeAssetRegistry = (load) => {
1120
+ const entries = new Map;
1121
+ const asking = new Map;
1122
+ const tellAll = (entry, notice) => {
1123
+ for (const holder of entry.holders)
1124
+ asking.get(holder)?.notify(notice);
1125
+ };
1126
+ const loadedNotice = (entry, level, lent) => ({
1127
+ _tag: "Loaded",
1128
+ ...entry.asset,
1129
+ level,
1130
+ final: level === entry.asset.levels.length,
1131
+ parts: lent.parts,
1132
+ clips: lent.clips
1133
+ });
1134
+ const noticesOf = (entry) => [
1135
+ ...entry.shown === undefined ? [] : [loadedNotice(entry, entry.shown.level, entry.shown.lent)],
1136
+ ...entry.failure === undefined ? [] : [{ _tag: "Failed", ...entry.asset, reason: entry.failure }]
1137
+ ];
1138
+ const start = (asset) => {
1139
+ const urls = urlsOf(asset);
1140
+ const finest = urls.length - 1;
1141
+ const entry = {
1142
+ asset,
1143
+ holders: new Set,
1144
+ controllers: urls.map(() => new AbortController),
1145
+ shown: undefined,
1146
+ failure: undefined
1147
+ };
1148
+ entries.set(asset.id, entry);
1149
+ function current(level) {
1150
+ return entries.get(asset.id) === entry && entry.controllers[level]?.signal.aborted === false;
1151
+ }
1152
+ function loaded(level) {
1153
+ return (lent) => {
1154
+ if (!current(level) || entry.shown !== undefined && entry.shown.level >= level) {
1155
+ lent.release();
1156
+ return;
1157
+ }
1158
+ const before = entry.shown;
1159
+ entry.shown = { level, lent };
1160
+ before?.lent.release();
1161
+ if (level === finest)
1162
+ for (const coarser of entry.controllers.slice(0, level))
1163
+ coarser.abort();
1164
+ tellAll(entry, loadedNotice(entry, level, lent));
1165
+ };
1166
+ }
1167
+ function failed(level) {
1168
+ return (cause) => {
1169
+ if (!current(level) || level !== finest)
1170
+ return;
1171
+ const reason = reasonOf({ ...asset, url: urls[level] ?? asset.url }, cause);
1172
+ entry.failure = reason;
1173
+ tellAll(entry, { _tag: "Failed", ...asset, reason });
1174
+ };
1175
+ }
1176
+ urls.forEach((url, level) => {
1177
+ const signal = entry.controllers[level]?.signal ?? AbortSignal.abort();
1178
+ load({ ...asset, url }, signal).then(loaded(level), failed(level));
1179
+ });
1180
+ return entry;
1181
+ };
1182
+ const end = (entry) => {
1183
+ entries.delete(entry.asset.id);
1184
+ for (const controller of entry.controllers)
1185
+ controller.abort();
1186
+ entry.shown?.lent.release();
1187
+ };
1188
+ const letGo = (holder, id) => {
1189
+ const entry = entries.get(id);
1190
+ if (entry === undefined || !entry.holders.delete(holder) || entry.holders.size > 0)
1191
+ return;
1192
+ end(entry);
1193
+ for (const [waiting, { assets }] of asking) {
1194
+ if (assets.some((asset) => asset.id === id))
1195
+ hold(waiting, assets);
1196
+ }
1197
+ };
1198
+ const hold = (holder, assets) => {
1199
+ for (const asset of assets) {
1200
+ const entry = entries.get(asset.id) ?? start(asset);
1201
+ if (!sameAsset(entry.asset, asset)) {
1202
+ asking.get(holder)?.notify(taken(asset, entry.asset));
1203
+ continue;
1204
+ }
1205
+ if (entry.holders.has(holder))
1206
+ continue;
1207
+ entry.holders.add(holder);
1208
+ for (const notice of noticesOf(entry))
1209
+ asking.get(holder)?.notify(notice);
1210
+ }
1211
+ };
1212
+ const want = (holder, assets, notify) => {
1213
+ const before = asking.get(holder);
1214
+ asking.set(holder, { assets, notify });
1215
+ if (before !== undefined && assetsEqual(before.assets, assets))
1216
+ return;
1217
+ for (const old of before?.assets ?? []) {
1218
+ const still = assets.find((asset) => asset.id === old.id);
1219
+ if (still === undefined || !sameAsset(still, old))
1220
+ letGo(holder, old.id);
1221
+ }
1222
+ hold(holder, assets);
1223
+ };
1224
+ const release = (holder) => {
1225
+ want(holder, [], () => {
1226
+ return;
1227
+ });
1228
+ asking.delete(holder);
1229
+ };
1230
+ const dispose = () => {
1231
+ asking.clear();
1232
+ for (const entry of [...entries.values()])
1233
+ end(entry);
1234
+ };
1235
+ return { want, release, dispose };
1236
+ };
1237
+
1078
1238
  // src/dom/xr.ts
1079
1239
  import { Group as Group2 } from "three";
1080
1240
 
@@ -1729,6 +1889,7 @@ var createXrHub = (system) => {
1729
1889
  var makeEnvironment = (host, options = {}) => {
1730
1890
  const store = createResourceStore();
1731
1891
  const assets = makeAssets(store);
1892
+ const assetRegistry = makeAssetRegistry(assetLoader(store));
1732
1893
  const xrHub = createXrHub(() => host.navigator?.xr);
1733
1894
  return {
1734
1895
  host,
@@ -1740,9 +1901,11 @@ var makeEnvironment = (host, options = {}) => {
1740
1901
  physics: Promise.resolve(options.physics),
1741
1902
  labels: options.labels,
1742
1903
  assets,
1904
+ assetRegistry,
1743
1905
  xr: makeXr(xrHub),
1744
1906
  xrHub,
1745
1907
  dispose: () => {
1908
+ assetRegistry.dispose();
1746
1909
  assets.dispose();
1747
1910
  store.dispose();
1748
1911
  }
@@ -1821,6 +1984,11 @@ var dispatchSceneEvent = (element, event) => {
1821
1984
  var dispatchSceneError = (element, error) => {
1822
1985
  element.dispatchEvent(new CustomEvent("scene-error", { detail: { error } }));
1823
1986
  };
1987
+ var dispatchAssetNotice = (element, notice) => {
1988
+ const { _tag, ...detail } = notice;
1989
+ const name = _tag === "Loaded" ? "scene-asset-loaded" : "scene-asset-failed";
1990
+ element.dispatchEvent(new CustomEvent(name, { detail }));
1991
+ };
1824
1992
  var canvasIn = (host) => () => appendCanvas(host);
1825
1993
  var removal = (node) => () => node.remove();
1826
1994
  var sceneEventDispatch = (element, live) => (event) => () => {
@@ -1844,10 +2012,10 @@ var createLatch = (open) => {
1844
2012
  }
1845
2013
  };
1846
2014
  };
1847
- var nothing = [];
2015
+ var nothing2 = [];
1848
2016
  var routedWhileOpen = (latch, route) => (value) => {
1849
2017
  if (!latch.isOpen())
1850
- return nothing;
2018
+ return nothing2;
1851
2019
  return route(value);
1852
2020
  };
1853
2021
  var applying = (f, value) => () => f(value);
@@ -2018,11 +2186,21 @@ var connectObserver = (observe) => (queue) => {
2018
2186
  return Effect3.acquireRelease(connect, Effect3.sync);
2019
2187
  };
2020
2188
  var observed = (observe) => Stream.callback(connectObserver(observe));
2189
+ var asking = (mounted, scene) => () => mounted.environment.assetRegistry.want(mounted.element, scene.assets, (notice) => dispatchAssetNotice(mounted.element, notice));
2190
+ var lettingGo = (mounted) => () => mounted.environment.assetRegistry.release(mounted.element);
2191
+ var acquireAssets = (mounted, initial) => {
2192
+ const ask = Effect3.sync(asking(mounted, initial));
2193
+ return Effect3.acquireRelease(ask, () => Effect3.sync(lettingGo(mounted)));
2194
+ };
2195
+ var updated = (mounted) => (scene) => {
2196
+ const ask = Effect3.sync(asking(mounted, scene));
2197
+ return Effect3.tap(mounted.handle.update(scene), () => ask);
2198
+ };
2021
2199
  var applyUpdate = (mounted) => (value) => {
2022
2200
  const reportError = sceneErrorDispatch(mounted.element);
2023
2201
  const reportReconciled = afterUpdate(reconciledDispatch(mounted.element, mounted.environment.store), [touchActionSync(mounted.canvas, mounted.handle.graph), mounted.overlays.rewrite]);
2024
2202
  const decoded = decodeScene(value);
2025
- const applied = decoded.pipe(Effect3.flatMap(mounted.handle.update), Effect3.flatMap((ops) => Effect3.sync(reportReconciled(ops))));
2203
+ const applied = decoded.pipe(Effect3.flatMap(updated(mounted)), Effect3.flatMap((ops) => Effect3.sync(reportReconciled(ops))));
2026
2204
  return applied.pipe(Effect3.catchTags({
2027
2205
  SchemaError: (error) => Effect3.sync(reportError({ _tag: "MalformedScene", issue: error.message })),
2028
2206
  InvalidScene: ({ error }) => Effect3.sync(reportError(error))
@@ -2083,6 +2261,7 @@ function* runScene(environment, element, viewStateChanges) {
2083
2261
  gate: createFrameGate(frames),
2084
2262
  live: createLatch(true)
2085
2263
  };
2264
+ yield* acquireAssets(mounted, initial);
2086
2265
  yield* acquirePointer(canvas, mounted);
2087
2266
  yield* acquireXrTarget(mounted);
2088
2267
  yield* Effect3.sync(touchActionSync(canvas, handle.graph));
@@ -2487,6 +2666,7 @@ var view = (config, h) => {
2487
2666
  Option7.map(given(config.onDragEnd), element.OnSceneDragEnd),
2488
2667
  Option7.map(given(config.onCollision), element.OnSceneCollision),
2489
2668
  Option7.map(given(config.onContactForce), element.OnSceneContactForce),
2669
+ Option7.map(given(config.onPhysicsReached), element.OnScenePhysicsReached),
2490
2670
  Option7.map(given(config.onMeasured), element.OnSceneMeasured),
2491
2671
  Option7.map(given(config.onLabelMeasured), element.OnSceneLabelMeasured),
2492
2672
  Option7.map(given(config.onXRSession), element.OnSceneXr),
@@ -2501,6 +2681,8 @@ var view = (config, h) => {
2501
2681
  Option7.map(given(config.onGestureEnd), element.OnSceneGestureEnd),
2502
2682
  Option7.map(given(config.onPerformanceChange), element.OnScenePerformance),
2503
2683
  Option7.map(given(config.onError), element.OnSceneError),
2684
+ Option7.map(given(config.onAssetLoaded), element.OnSceneAssetLoaded),
2685
+ Option7.map(given(config.onAssetFailed), element.OnSceneAssetFailed),
2504
2686
  Option7.map(given(config.onReconciled), element.OnSceneReconciled),
2505
2687
  Option7.map(given(config.className), h.Class)
2506
2688
  ]);
@@ -2540,5 +2722,5 @@ export {
2540
2722
  webgpuRendererFactory
2541
2723
  };
2542
2724
 
2543
- //# debugId=B27477F988FB686A64756E2164756E21
2725
+ //# debugId=91E3923C3006D06864756E2164756E21
2544
2726
  //# sourceMappingURL=index.js.map