@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/src/mount.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  removal,
28
28
  sceneErrorDispatch,
29
29
  sceneEventDispatch,
30
+ dispatchAssetNotice,
30
31
  } from './dom/canvas.ts';
31
32
  import { applying, createLatch, type Latch, routedWhileOpen } from './dom/latch.ts';
32
33
  import { containOverlays, createOverlayWriter, type OverlayWriter } from './dom/overlays.ts';
@@ -140,6 +141,27 @@ const connectObserver =
140
141
  const observed = <A>(observe: (emit: (value: A) => void) => () => void) =>
141
142
  Stream.callback<A>(connectObserver(observe));
142
143
 
144
+ /** What the scene asks for now, given to the environment's asset registry. */
145
+ const asking = (mounted: Mounted, scene: SceneDescription) => () =>
146
+ mounted.environment.assetRegistry.want(mounted.element, scene.assets, (notice) =>
147
+ dispatchAssetNotice(mounted.element, notice),
148
+ );
149
+
150
+ const lettingGo = (mounted: Mounted) => () =>
151
+ mounted.environment.assetRegistry.release(mounted.element);
152
+
153
+ /** The scene's assets, asked for while it is mounted: going, it asks for none. */
154
+ const acquireAssets = (mounted: Mounted, initial: SceneDescription) => {
155
+ const ask = Effect.sync(asking(mounted, initial));
156
+ return Effect.acquireRelease(ask, () => Effect.sync(lettingGo(mounted)));
157
+ };
158
+
159
+ /** An update applied, then what it asks for given to the registry (a refused one asks nothing). */
160
+ const updated = (mounted: Mounted) => (scene: SceneDescription) => {
161
+ const ask = Effect.sync(asking(mounted, scene));
162
+ return Effect.tap(mounted.handle.update(scene), () => ask);
163
+ };
164
+
143
165
  const applyUpdate = (mounted: Mounted) => (value: unknown) => {
144
166
  const reportError = sceneErrorDispatch(mounted.element);
145
167
  const reportReconciled = afterUpdate(
@@ -148,7 +170,7 @@ const applyUpdate = (mounted: Mounted) => (value: unknown) => {
148
170
  );
149
171
  const decoded = decodeScene(value);
150
172
  const applied = decoded.pipe(
151
- Effect.flatMap(mounted.handle.update),
173
+ Effect.flatMap(updated(mounted)),
152
174
  Effect.flatMap((ops) => Effect.sync(reportReconciled(ops))),
153
175
  );
154
176
  return applied.pipe(
@@ -258,6 +280,7 @@ function* runScene(
258
280
  gate: createFrameGate(frames),
259
281
  live: createLatch(true),
260
282
  };
283
+ yield* acquireAssets(mounted, initial);
261
284
  yield* acquirePointer(canvas, mounted);
262
285
  yield* acquireXrTarget(mounted);
263
286
  yield* Effect.sync(touchActionSync(canvas, handle.graph));
package/src/view.ts CHANGED
@@ -52,6 +52,9 @@ export type CollisionDetail = typeof SceneSchema.CollisionDetail.Type;
52
52
 
53
53
  /** Detail of contact force events: the two bodies, and how hard they pushed. */
54
54
  export type ContactForceDetail = typeof SceneSchema.ContactForceDetail.Type;
55
+ export type PhysicsReachedDetail = typeof SceneSchema.PhysicsReachedDetail.Type;
56
+ export type AssetLoadedDetail = typeof SceneSchema.AssetLoadedDetail.Type;
57
+ export type AssetFailedDetail = typeof SceneSchema.AssetFailedDetail.Type;
55
58
 
56
59
  /** Detail of measured events: a node's world bounding box. */
57
60
  export type MeasuredDetail = typeof SceneSchema.MeasuredDetail.Type;
@@ -144,6 +147,12 @@ export type ViewConfig<Message> = {
144
147
  * set the threshold high.
145
148
  */
146
149
  readonly onContactForce?: (detail: ContactForceDetail) => Message;
150
+ /**
151
+ * A world on `physics({ until })` reached that step and waits there, with its run and a
152
+ * checksum of its bodies' poses (ADR-014): the moment to apply the inputs due at that
153
+ * step and move `until` on. It follows the collisions of the steps that led there.
154
+ */
155
+ readonly onPhysicsReached?: (detail: PhysicsReachedDetail) => Message;
147
156
  /**
148
157
  * A node listed in the scene's `measure` has a new world bounding box (Drei's `Bounds`
149
158
  * measuring, as a Message): fit the camera to it with `fitCamera`.
@@ -197,6 +206,13 @@ export type ViewConfig<Message> = {
197
206
  readonly onPerformanceChange?: (detail: PerformanceDetail) => Message;
198
207
  /** A scene update was rejected; the scene keeps its last good state. */
199
208
  readonly onError?: (detail: { readonly error: SceneError }) => Message;
209
+ /**
210
+ * An asset the scene asks for (`scene({ assets })`) has loaded and is lent under its id:
211
+ * at once, for one another scene loaded already. A model's parts and clips come with it.
212
+ */
213
+ readonly onAssetLoaded?: (detail: AssetLoadedDetail) => Message;
214
+ /** An asset the scene asks for could not be loaded, and why. */
215
+ readonly onAssetFailed?: (detail: AssetFailedDetail) => Message;
200
216
  /**
201
217
  * What each scene update did (operations by kind) and the GPU resources held afterwards.
202
218
  * Fires at startup and after every update that changed something, never for no-ops.
@@ -234,6 +250,7 @@ export const view = <Message>(
234
250
  Option.map(given(config.onDragEnd), element.OnSceneDragEnd),
235
251
  Option.map(given(config.onCollision), element.OnSceneCollision),
236
252
  Option.map(given(config.onContactForce), element.OnSceneContactForce),
253
+ Option.map(given(config.onPhysicsReached), element.OnScenePhysicsReached),
237
254
  Option.map(given(config.onMeasured), element.OnSceneMeasured),
238
255
  Option.map(given(config.onLabelMeasured), element.OnSceneLabelMeasured),
239
256
  Option.map(given(config.onXRSession), element.OnSceneXr),
@@ -250,6 +267,8 @@ export const view = <Message>(
250
267
  Option.map(given(config.onGestureEnd), element.OnSceneGestureEnd),
251
268
  Option.map(given(config.onPerformanceChange), element.OnScenePerformance),
252
269
  Option.map(given(config.onError), element.OnSceneError),
270
+ Option.map(given(config.onAssetLoaded), element.OnSceneAssetLoaded),
271
+ Option.map(given(config.onAssetFailed), element.OnSceneAssetFailed),
253
272
  Option.map(given(config.onReconciled), element.OnSceneReconciled),
254
273
  Option.map(given(config.className), h.Class),
255
274
  ]);