@nakednous/host 0.0.10 → 0.0.12

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/README.md CHANGED
@@ -70,7 +70,7 @@ dependency first and an options object last, and registers with the context so
70
70
 
71
71
  | member | what it is |
72
72
  |---|---|
73
- | `createHost(canvas, opts)` | the context: `ndcZMin` (WEBGL default, or WEBGPU), `onFrame(dt, host)` for the host's own loop, `onSize(w, h, dpr)` for the bridge's buffer, `raf` / `caf` overrides |
73
+ | `createHost(canvas, opts)` | the context: `ndcZMin` (WEBGL default, or WEBGPU), `onFrame(dt, host)` for the host's own loop, `afterFrame(dt, host)` after the pointer flush — the frame's last draw, a pointer handler's included, where a WebGPU bridge submits its command encoder — `onSize(w, h, dpr)` for the bridge's buffer, `raf` / `caf` overrides |
74
74
  | `host.view` | the view bag — `mat4Proj` · `mat4View` · `mat4PV` · `mat4PVInv` · `mat4Eye`, `vp`, `ndcZMin`, `stale`; `set(P, V)`, `setCamera(cam)`, `resize(w, h)` |
75
75
  | `host.pointer` | the pointer source — presses queued per frame, per-pointer entries with a move counter, claims that capture on the canvas, Esc cancel, `flush()` |
76
76
  | `host.players` | the registry ticked each frame — `{ tick(dt) → boolean }`, removed on `false` |
@@ -116,6 +116,10 @@ one wrote:
116
116
  5. **draw** — the bridge reads the camera state, the handles' values, the tracks' paths.
117
117
  6. **labels tick** — world anchors projected through the current bag, transforms written.
118
118
  7. **pointer flush** — the press queue empties, released pointers leave.
119
+ 8. **the post-frame hook** — `afterFrame(dt, host)`, when it was given: the frame's last draw,
120
+ a pointer handler's included, so a WebGPU bridge submits its command encoder there. It is
121
+ the host's loop's step and nothing in external tick — there the caller writes the frame's
122
+ end — and `host.dispose()` clears it.
119
123
 
120
124
  `dt` is seconds, clamped to 50 ms so a stalled tab never integrates a leap.
121
125
 
@@ -126,6 +130,7 @@ one wrote:
126
130
  | seam | direction | contract |
127
131
  |---|---|---|
128
132
  | view bag | bridge → host | the bridge's `setCamera(cam)` also calls `host.view.setCamera(cam)`; without a host the bridge keeps its own matrices |
133
+ | frame end | host → bridge | `afterFrame(dt, host)` runs after `pointer.flush()`; a WebGPU bridge submits its command encoder there, and without the hook the application calls the bridge's `endFrame` itself |
129
134
  | external tick | adapter → host | `host.tick(dt)` from the adapter's predraw; `host.labels.tick()` after the adapter set `host.view` from renderer state |
130
135
  | proxies · locus | tree → host → bridge | constraint objects carry `proxy` and `locus`; the host tests, the bridge draws |
131
136
  | label anchors | tree → bridge → host | a generator writes `out.labels`; the bridge forwards each to `labels.set` |
package/dist/index.js CHANGED
@@ -760,7 +760,7 @@ class Handle {
760
760
  c.s = this._savedS;
761
761
  c._dialPoint();
762
762
  } else {
763
- c.seed(this._saved[0], this._saved[1], this._saved[2]);
763
+ c.seed(this._saved[0], this._saved[1], this._saved[2], POINT);
764
764
  }
765
765
  if (this._binder) this._binder.set(this.value(this._bindVal));
766
766
  this._grabbed = false;
@@ -854,7 +854,7 @@ class Handle {
854
854
  _v3[1] = gy > 0 ? Math.round(c.pt[1] / gy) * gy : c.pt[1];
855
855
  _v3[2] = gz > 0 ? Math.round(c.pt[2] / gz) * gz : c.pt[2];
856
856
  if (this._isView) { c.pt[0] = _v3[0]; c.pt[1] = _v3[1]; c.pt[2] = _v3[2]; }
857
- else c.seed(_v3[0], _v3[1], _v3[2]);
857
+ else c.seed(_v3[0], _v3[1], _v3[2], POINT);
858
858
  } else if (c.kind === AXIS || c.kind === DIAL) {
859
859
  const step = Array.isArray(sn) ? sn[0] : sn;
860
860
  if (!(step > 0)) return;
@@ -901,6 +901,11 @@ class Handle {
901
901
  * accessor, or a vec3 array mutated in place (a camera state's eye or
902
902
  * center is one). get() seeds the constraint now; each solve calls
903
903
  * set(value) with the reused value array, then fires onChange. Chainable.
904
+ *
905
+ * The array lives in the handle's `report` space, both ways: a SPHERE handle
906
+ * (DIRECTION by default) binds and receives a **unit heading**, not a point on
907
+ * its sphere; a PLANE / AXIS handle binds and receives a world point. Construct
908
+ * the handle with `report: POINT` to bind a point to a sphere instead.
904
909
  * @param {number[]|{ get:Function, set:Function }} target
905
910
  * @returns {Handle} this
906
911
  */
@@ -920,7 +925,9 @@ class Handle {
920
925
  return this;
921
926
  }
922
927
 
923
- /** Re-seed the constraint from the bound target after it changed externally. Chainable. */
928
+ /** Re-seed the constraint from the bound target after it changed externally —
929
+ * the target holds the handle's own `report` space, as the write-back does, so
930
+ * seed → solve → write never changes what the array means. Chainable. */
924
931
  sync() {
925
932
  if (this._binder) this._seedFromBinding();
926
933
  return this;
@@ -931,7 +938,7 @@ class Handle {
931
938
  if (g == null) return;
932
939
  const x = _vx$1(g, 0, 0), y = _vx$1(g, 1, 0), z = _vx$1(g, 2, 0);
933
940
  if (this._isView) { const pt = this._constraint.pt; pt[0] = x; pt[1] = y; pt[2] = z; }
934
- else this._constraint.seed(x, y, z);
941
+ else this._constraint.seed(x, y, z, this._constraint.report);
935
942
  }
936
943
 
937
944
  // Push the freshly solved value to the binding and fire onChange, once per solve.
@@ -982,7 +989,7 @@ class Handle {
982
989
  c.pt[0] = c.anchor[0] + c.s * c.u[0];
983
990
  c.pt[1] = c.anchor[1] + c.s * c.u[1];
984
991
  c.pt[2] = c.anchor[2] + c.s * c.u[2];
985
- } else if (c.kind === PLANE) c.seed(c.pt[0], c.pt[1], c.pt[2]);
992
+ } else if (c.kind === PLANE) c.seed(c.pt[0], c.pt[1], c.pt[2], POINT);
986
993
  }
987
994
  return this;
988
995
  }
@@ -3091,10 +3098,14 @@ function decodeRGBE(bytes, opts) {
3091
3098
  *
3092
3099
  * Two loop modes. With { onFrame } the host runs requestAnimationFrame
3093
3100
  * (overridable through { raf, caf }): each frame computes dt (clamped to
3094
- * 50 ms), ticks the players, calls onFrame(dt, host), then flushes the
3095
- * pointer source. Without it the application, or the p5 adapter from
3096
- * predraw, calls host.tick(dt) = players.tick(dt) and host.pointer.flush()
3097
- * once its consumers ran. Nothing else in the host cares who owns the loop.
3101
+ * 50 ms), ticks the players, calls onFrame(dt, host), flushes the pointer
3102
+ * source, then calls afterFrame(dt, host) when one was given — the frame's
3103
+ * last draw, a pointer handler's included, which is where a WebGPU bridge
3104
+ * submits its command encoder. Without it the application, or the p5
3105
+ * adapter from predraw, calls host.tick(dt) = players.tick(dt) and
3106
+ * host.pointer.flush() once its consumers ran: external tick gains no
3107
+ * frame end, so afterFrame belongs to the host's own loop. Nothing else in
3108
+ * the host cares who owns the loop.
3098
3109
  *
3099
3110
  * The frame order inside onFrame: install the camera (which fills
3100
3111
  * host.view), update handles or routers, fall through to the orbit when
@@ -3108,12 +3119,18 @@ const _now = () => (typeof performance !== 'undefined' && performance.now) ? per
3108
3119
  * Create the host context of a canvas.
3109
3120
  *
3110
3121
  * @param {HTMLCanvasElement} canvas
3111
- * @param {{ ndcZMin?:number, onFrame?:function(number, object):void, raf?:function,
3122
+ * @param {{ ndcZMin?:number, onFrame?:function(number, object):void,
3123
+ * afterFrame?:function(number, object):void, raf?:function,
3112
3124
  * caf?:function, onSize?:function(number, number, number):void }} [opts]
3113
3125
  * ndcZMin: the GPU API's NDC-z convention, WEBGL (default) or WEBGPU —
3114
3126
  * written once, never detected. onFrame: the host's own loop; absent,
3115
- * external-tick mode. onSize(width, height, dpr): the bridge's chance
3116
- * to resize its drawing buffer.
3127
+ * external-tick mode. afterFrame: called after the pointer source
3128
+ * flushes, so the frame's last draw — a pointer handler's included —
3129
+ * is behind it; the submit seam of a WebGPU bridge, which ends its
3130
+ * command encoder there. The host's loop only: an external tick has
3131
+ * no frame end but the one its caller writes. dispose() clears it.
3132
+ * onSize(width, height, dpr): the bridge's chance to resize its
3133
+ * drawing buffer.
3117
3134
  * @returns {object} The host.
3118
3135
  */
3119
3136
  function createHost(canvas, opts) {
@@ -3121,6 +3138,7 @@ function createHost(canvas, opts) {
3121
3138
  const t0 = _now();
3122
3139
  const constructs = new Set();
3123
3140
  let loop = null, observer = null, labels = null;
3141
+ let afterFrame = typeof o.afterFrame === 'function' ? o.afterFrame : null;
3124
3142
  const host = {
3125
3143
  canvas,
3126
3144
  width: 0, height: 0, dpr: 1,
@@ -3202,6 +3220,7 @@ function createHost(canvas, opts) {
3202
3220
 
3203
3221
  /** Stop the loop, disconnect the observer, remove every listener, dispose every construct. */
3204
3222
  dispose() {
3223
+ afterFrame = null;
3205
3224
  if (loop) loop.stop();
3206
3225
  for (const c of [...constructs]) { if (typeof c.dispose === 'function') c.dispose(); }
3207
3226
  constructs.clear();
@@ -3230,6 +3249,7 @@ function createHost(canvas, opts) {
3230
3249
  host.players.tick(dt);
3231
3250
  o.onFrame(dt, host);
3232
3251
  host.pointer.flush();
3252
+ if (afterFrame !== null) afterFrame(dt, host);
3233
3253
  },
3234
3254
  });
3235
3255
  loop.start();