@weasel-js/core 1.0.0 → 1.0.1
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/CHANGELOG.md +335 -0
- package/dist/{chunk-D2C6K6FO.js → chunk-FVNWK7U3.js} +613 -219
- package/dist/chunk-FVNWK7U3.js.map +1 -0
- package/dist/clipboard.d.ts +1 -1
- package/dist/{index-ChM3ZJ2v.d.ts → index-DPI8DV0A.d.ts} +1 -1
- package/dist/index.d.ts +41 -13
- package/dist/index.js +1 -1
- package/dist/renderer.d.ts +52 -8
- package/dist/renderer.js +1 -1
- package/dist/routing.d.ts +2 -2
- package/dist/{types-DUpI7Apc.d.ts → types-Kh_osAq9.d.ts} +8 -1
- package/package.json +6 -6
- package/dist/chunk-D2C6K6FO.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,340 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d6c2eff: Take the id→node lookup off the render and hit-test paths.
|
|
8
|
+
|
|
9
|
+
Six call sites walked `scene.renderOrder()` and immediately resolved each id
|
|
10
|
+
back through `scene.get` — a map lookup per node, per call, for nodes the
|
|
11
|
+
traversal had already held. They now read `scene.renderOrderNodes()` directly:
|
|
12
|
+
`sceneToAdapter.getNodes`, the move gesture adapter and the default commit
|
|
13
|
+
adapter, `useSceneSelectTool`'s `hitTestArea` and default `pickEvery`, and the
|
|
14
|
+
text-edit hit test. Adapter `getNodes` runs on the render path once a frame.
|
|
15
|
+
|
|
16
|
+
Building the node list is about twice as fast (`npm run bench`, `min`, Apple M2
|
|
17
|
+
Max / Node v26.1.0):
|
|
18
|
+
|
|
19
|
+
| nodes | via `renderOrder` + `get` | via `renderOrderNodes` |
|
|
20
|
+
| ------ | ------------------------- | ---------------------- |
|
|
21
|
+
| 1,000 | 0.031 ms | 0.011 ms |
|
|
22
|
+
| 10,000 | 0.40 ms | 0.19 ms |
|
|
23
|
+
|
|
24
|
+
`tests/bench/scene-ops.bench.ts` gains that comparison, and the committed
|
|
25
|
+
baseline is re-recorded.
|
|
26
|
+
|
|
27
|
+
- d62dc17: Bake the rect batch's transform and alpha into its vertices, so a rotated or
|
|
28
|
+
partly-transparent node no longer breaks a run.
|
|
29
|
+
|
|
30
|
+
`RectBatch.push` now maps the four corners through the model matrix itself and
|
|
31
|
+
the flush draws at `u_model` identity — ~12 flops against the ~66 us a draw call
|
|
32
|
+
costs. An affine maps a rect to a parallelogram, so the two-triangle index
|
|
33
|
+
pattern still covers it. Group alpha multiplies into the vertex alpha the same
|
|
34
|
+
way fill opacity already did. Neither is batch state any more.
|
|
35
|
+
|
|
36
|
+
Frame cost for rects each wrapped in their own transform group — what
|
|
37
|
+
`wrapNodeOutput` emits for a rotated node — M2 Max via ANGLE at 800x600
|
|
38
|
+
(`npm run test:perf`, new `rotated` variant):
|
|
39
|
+
|
|
40
|
+
| rects | before | after |
|
|
41
|
+
| ----- | --------- | ------- |
|
|
42
|
+
| 400 | 24.94 ms | 0.08 ms |
|
|
43
|
+
| 1,600 | 102.78 ms | 0.42 ms |
|
|
44
|
+
| 3,200 | 216.90 ms | 0.52 ms |
|
|
45
|
+
|
|
46
|
+
The color matrix stays a uniform and stays a barrier. The shader applies it and
|
|
47
|
+
its clamp to the straight-alpha source _before_ multiplying by `u_alpha`, so a
|
|
48
|
+
pre-multiplied vertex alpha is the same number only under an identity matrix —
|
|
49
|
+
which is every scene that does not use one. Alpha therefore folds only there,
|
|
50
|
+
and rides `u_alpha` otherwise.
|
|
51
|
+
|
|
52
|
+
- 2604ce2: Merge consecutive solid-fill rects into one draw call.
|
|
53
|
+
|
|
54
|
+
The draw loop cost a flat ~66 us per draw call at every scene size, so a frame
|
|
55
|
+
of 3,200 rects took 212 ms. Solid-fill rects now append into a growable vertex
|
|
56
|
+
buffer and go out as a single `drawElements` at flush. Fill color rides the
|
|
57
|
+
vertices — the batch draws through the existing `pathFillVColor` program with
|
|
58
|
+
`u_color` held at white, which is bit-identical to the flat program's math.
|
|
59
|
+
|
|
60
|
+
Frame cost for a **flat** command stream, M2 Max via ANGLE at 800x600
|
|
61
|
+
(`npm run test:perf`):
|
|
62
|
+
|
|
63
|
+
| rects | before | after |
|
|
64
|
+
| ----- | --------- | ------- |
|
|
65
|
+
| 400 | 25.62 ms | 0.03 ms |
|
|
66
|
+
| 1,600 | 105.63 ms | 0.11 ms |
|
|
67
|
+
| 3,200 | 211.78 ms | 0.15 ms |
|
|
68
|
+
|
|
69
|
+
Painter's order is unchanged. A run absorbs only consecutive commands and
|
|
70
|
+
flushes before anything it cannot express: another fill kind, a stroke, a clip
|
|
71
|
+
push or pop, or a group changing the transform, alpha, or color matrix.
|
|
72
|
+
|
|
73
|
+
That last barrier means `SceneCanvas` does not benefit yet — it emits one
|
|
74
|
+
wrapper group per node, which breaks every run. Consumers building flat command
|
|
75
|
+
streams get the numbers above today. See
|
|
76
|
+
`docs/handoffs/2026-08-14-batched-dispatch.md` for the plan to reach the scene
|
|
77
|
+
path. Nothing else in the loop got faster either: a frame alternating solid and
|
|
78
|
+
gradient rects still costs ~34 us per command, now almost entirely the gradient
|
|
79
|
+
half.
|
|
80
|
+
|
|
81
|
+
- 24ae9f4: Batch solid-fill meshes and stroke ribbons alongside rects, so a stroked shape
|
|
82
|
+
costs one draw instead of two plus a fresh VAO every frame.
|
|
83
|
+
|
|
84
|
+
`RectBatch` becomes `SolidBatch`, with a `pushMesh` alongside `pushRect` that
|
|
85
|
+
appends transformed vertices and rebases the mesh's indices onto the staged run.
|
|
86
|
+
Solid path fills and solid stroke ribbons both take it, and land in the same
|
|
87
|
+
draw as each other: GL rasterizes a draw's primitives in index order, so staging
|
|
88
|
+
the ribbon after its own fill is what keeps the stroke on top.
|
|
89
|
+
|
|
90
|
+
Frame cost at 3,200 commands, M2 Max via ANGLE at 800x600 (`npm run test:perf`,
|
|
91
|
+
new `meshes` and `stroked` variants):
|
|
92
|
+
|
|
93
|
+
| variant | before | after |
|
|
94
|
+
| ----------------------------- | --------- | ------- |
|
|
95
|
+
| solid-fill octagons | 5.63 ms | 0.65 ms |
|
|
96
|
+
| stroked rects (fill + ribbon) | 243.80 ms | 9.41 ms |
|
|
97
|
+
|
|
98
|
+
The stroke figure is the interesting one, and not for the reason the plan
|
|
99
|
+
assumed. A draw call is ~1.8 us when nothing is touched between draws; what
|
|
100
|
+
costs is issuing a draw against a buffer minted that same frame, which is
|
|
101
|
+
exactly what a per-frame stroke ribbon did. Of the 9.41 ms left, 7.9 ms is
|
|
102
|
+
stroke tessellation, which batching does not address.
|
|
103
|
+
|
|
104
|
+
Excluded from a run: stencil fills, inner/outer-aligned polygon strokes, and
|
|
105
|
+
anything carrying per-vertex colors, all as before — plus meshes past a vertex
|
|
106
|
+
cap, since batching re-copies a mesh every frame where the persistent mesh cache
|
|
107
|
+
would not. Rects pay ~0.1 ms per frame at 3,200 for the index buffer becoming a
|
|
108
|
+
per-flush upload rather than a static pattern.
|
|
109
|
+
|
|
110
|
+
- c2ebfdf: Break rect batches on group state rather than on tree shape, so `SceneCanvas`
|
|
111
|
+
gets the batching.
|
|
112
|
+
|
|
113
|
+
A group was a batch barrier because it _might_ move a uniform. `buildSceneTree`
|
|
114
|
+
gives every node its own group with no transform, alpha, colorMatrix or clip, so
|
|
115
|
+
in the scene shape every run broke after one rect and the previous release's
|
|
116
|
+
batching reached nothing the app renders. A run now carries the state it was
|
|
117
|
+
staged under and breaks only when the live state differs by value — which a
|
|
118
|
+
no-op wrapper never does.
|
|
119
|
+
|
|
120
|
+
Scene-shaped frame cost, M2 Max via ANGLE at 800x600 (`npm run test:perf`, new
|
|
121
|
+
`scene` variant — one wrapper group per command):
|
|
122
|
+
|
|
123
|
+
| rects | before | after |
|
|
124
|
+
| ----- | --------- | ------- |
|
|
125
|
+
| 400 | 26.32 ms | 0.03 ms |
|
|
126
|
+
| 1,600 | 105.18 ms | 0.11 ms |
|
|
127
|
+
| 3,200 | 208.72 ms | 0.36 ms |
|
|
128
|
+
|
|
129
|
+
Clips stay hard flush points in both directions: the stencil is GL state that a
|
|
130
|
+
staged run cannot reconstruct, so the flush happens before `pushClip` and before
|
|
131
|
+
`popClip` rather than at group boundaries. Text, images, shaders, strokes and
|
|
132
|
+
non-solid fills flush as before.
|
|
133
|
+
|
|
134
|
+
- dce3306: Cache `renderOrder()` and `renderOrderNodes()` between structural edits.
|
|
135
|
+
|
|
136
|
+
Both walk the whole tree, and both run per frame and per hit-test query, on a
|
|
137
|
+
sequence that only changes when something structural moves. They now build once
|
|
138
|
+
and are served from a cache until it does. On a 10,000-node, four-layer scene a
|
|
139
|
+
repeat call drains in 0.0033 ms against a 0.33 ms rebuild (`npm run bench`,
|
|
140
|
+
`min`).
|
|
141
|
+
|
|
142
|
+
Invalidation hangs off the four writers that can reorder the scene — `attach`,
|
|
143
|
+
`detach`, `kit:setLayer`, `rebuildLayerIndex` — plus `loadState`. Pose and data
|
|
144
|
+
edits do **not** invalidate: they change no order and fire every frame during a
|
|
145
|
+
drag, which is exactly when the cache earns its keep.
|
|
146
|
+
|
|
147
|
+
Repeat calls now return the same array instance rather than a fresh one. It is
|
|
148
|
+
still a snapshot — a structural edit builds a new array, so a reference taken
|
|
149
|
+
earlier keeps the order it was taken with — but callers must not mutate what
|
|
150
|
+
they get back. Both return types have always been `readonly`.
|
|
151
|
+
|
|
152
|
+
- 69395b0: Detached scene renders honor pose rotation and per-node alpha.
|
|
153
|
+
|
|
154
|
+
Rotation and the per-id alpha multiplier were applied by `buildSceneLayer`,
|
|
155
|
+
the main canvas's scene walk. Every other way of painting a scene —
|
|
156
|
+
`<SceneViewCanvas>`, `<MinimapCanvas>`, and `renderSceneToPixels` — goes
|
|
157
|
+
through `buildSceneViewCommands` instead, which applied neither. A rotated
|
|
158
|
+
node came out upright in a minimap, a thumbnail, or a print export, and a
|
|
159
|
+
scene dimmed on screen exported at full strength.
|
|
160
|
+
|
|
161
|
+
Both wraps now live in one helper that both scene walks call, so the detached
|
|
162
|
+
renders match the canvas. Rotation needs nothing from the caller — it comes
|
|
163
|
+
off the pose. Dimming does: `alphaFor` is a new optional prop on
|
|
164
|
+
`<SceneViewCanvas>` and `<MinimapCanvas>`, and a new argument to
|
|
165
|
+
`renderSceneToCanvas`, `renderSceneToPixels`, `planPixelRender`, and
|
|
166
|
+
`buildSceneViewCommands`. Pass the same function `<SceneCanvas>` gets.
|
|
167
|
+
|
|
168
|
+
If you supply a `drawOne` to one of these that rotates its own output, it will
|
|
169
|
+
now rotate twice — emit unrotated geometry and let the pose drive it, which is
|
|
170
|
+
what the main canvas has always required.
|
|
171
|
+
|
|
172
|
+
- 3d93f2e: Detached scene renders now honor layer visibility and container clips.
|
|
173
|
+
|
|
174
|
+
`buildSceneViewCommands` walked `scene.renderOrder()` and painted every node it
|
|
175
|
+
found. That walk knew nothing about scene layers or parentage, so
|
|
176
|
+
`<SceneViewCanvas>`, `<MinimapCanvas>` and `renderSceneToPixels` all painted
|
|
177
|
+
nodes on hidden layers and let a container's children spill past the container.
|
|
178
|
+
The main canvas got both right, because `buildSceneLayer` goes through
|
|
179
|
+
`buildSceneTree`.
|
|
180
|
+
|
|
181
|
+
The detached path now goes through `buildSceneTree` too, which is the dedupe the
|
|
182
|
+
detached-minimap spec called for. One walk, so the two surfaces cannot disagree
|
|
183
|
+
again.
|
|
184
|
+
|
|
185
|
+
Output nesting follows `buildSceneTree`: the view group holds one group per
|
|
186
|
+
**visible** scene layer, each holding one group per node. Code that indexed the
|
|
187
|
+
view group's children as one-per-node — `commands[0].children[i]` — now finds a
|
|
188
|
+
layer group there and needs a further hop. `extraCommands` still come last,
|
|
189
|
+
beside the layer groups.
|
|
190
|
+
|
|
191
|
+
A hand-written `Scene` stand-in must now supply `layers`, `roots`, and
|
|
192
|
+
`children` on containers; scenes from `createScene` and `sceneFromJSON` already
|
|
193
|
+
do.
|
|
194
|
+
|
|
195
|
+
- e367165: One enumeration of the `TargetSpec` forms. `@weasel-js/gestures` now exports
|
|
196
|
+
`parseTargetSpec`, which resolves a target spec to a discriminated
|
|
197
|
+
`TargetSpecForm` (`body` / `kind` / `affordance` / `predicate`), and the three
|
|
198
|
+
places that used to re-derive the string prefixes independently — `matchTarget`,
|
|
199
|
+
and `targetRank` / `targetConsultsAffordance` in core's dispatcher matcher —
|
|
200
|
+
switch on it exhaustively. Adding a form to `TargetSpec` is now a compile error
|
|
201
|
+
at every site that has to handle it.
|
|
202
|
+
|
|
203
|
+
For consumers: `matchTarget`'s `specTarget` parameter and core's
|
|
204
|
+
`targetConsultsAffordance` take `TargetSpec | undefined` instead of `unknown`,
|
|
205
|
+
so a target string that is no known form is a type error rather than a silent
|
|
206
|
+
no-match. The predicate form has a name, `TargetPredicate`, carrying the
|
|
207
|
+
`readsAffordance` flag the exclusive-claim filter reads. Runtime behavior is
|
|
208
|
+
unchanged.
|
|
209
|
+
|
|
210
|
+
- 52e9c57: Walk the scene once in `renderOrder()` instead of once per layer.
|
|
211
|
+
|
|
212
|
+
The generator behind `renderOrder()` and `toJSON()` was layer-major in the
|
|
213
|
+
literal sense: it ran a full DFS of the tree for every layer and yielded only
|
|
214
|
+
the nodes belonging to that pass, so producing N ids cost L×N work. A single
|
|
215
|
+
DFS now buckets each node by its layer and concatenates the buckets, which is
|
|
216
|
+
O(N + L). The emitted sequence is unchanged — same layer-major order, same
|
|
217
|
+
DFS-preorder within each layer, same skip for dangling child ids — and a
|
|
218
|
+
differential test holds the new implementation to a transcription of the old
|
|
219
|
+
one across 200 generated scenes plus mutation, layer-edit and undo sequences.
|
|
220
|
+
|
|
221
|
+
Over 10k nodes the layer sweep goes from 0.37 ms / 1.03 ms / 3.54 ms / 12.93 ms
|
|
222
|
+
at 1 / 4 / 16 / 64 layers to 0.30 / 0.35 / 0.40 / 0.42. The flat single-layer
|
|
223
|
+
case improves too, 0.37 ms → 0.33 ms at 10k nodes and 2.1x at 100 nodes, since
|
|
224
|
+
one pass replaces the per-yield generator overhead.
|
|
225
|
+
|
|
226
|
+
`renderOrder()` now returns an array rather than a generator, so a caller that
|
|
227
|
+
stops early no longer avoids the rest of the walk. Every caller in the repo
|
|
228
|
+
drains it fully except four test helpers reading the first id from a handful of
|
|
229
|
+
nodes. Its declared type stays `Iterable<NodeId>`.
|
|
230
|
+
|
|
231
|
+
- d68e734: Memoize the per-node AABB in the area hit-test, for silhouette poses.
|
|
232
|
+
|
|
233
|
+
`hitTestArea` — the marquee and lasso dep source — recomputed every node's
|
|
234
|
+
bounding box on every query. For a polygon pose that means walking the whole
|
|
235
|
+
command stream and allocating a rect, per node, before the fast-reject could
|
|
236
|
+
discard it. The box is now cached through `nodeMemo`, keyed on the node's
|
|
237
|
+
`pose` and `data` references, so a repeat query over an unedited scene reuses
|
|
238
|
+
it and an edit through any scene op invalidates it.
|
|
239
|
+
|
|
240
|
+
Measured on 24-gon scenes (`npm run bench`, `min` column, same machine
|
|
241
|
+
back-to-back): 10,000 nodes 11.85 ms → 1.16 ms per query, 1,000 nodes
|
|
242
|
+
1.14 ms → 0.15 ms. Query-rect size now moves the number (0.117 ms at 17 hits
|
|
243
|
+
against 0.141 ms at 1,000 hits on a 1,000-node scene, previously flat at
|
|
244
|
+
~1.15 ms either way) because the silhouette kernel, not the bounds
|
|
245
|
+
computation, is what survives the reject.
|
|
246
|
+
|
|
247
|
+
Rect-pose scenes pay 5–17% for it: `aabbOfPose` returns a rect pose
|
|
248
|
+
unchanged, so there is nothing to cache, and deciding that per node costs
|
|
249
|
+
more than the call it skips. 10,000 rect nodes go 0.76 ms → 0.85 ms.
|
|
250
|
+
|
|
251
|
+
- ca9673a: Stop re-sending unchanged uniforms on every draw command.
|
|
252
|
+
|
|
253
|
+
`u_proj` is constant for a whole frame and `u_colorMatrix` is the identity in
|
|
254
|
+
every scene that does not use a color matrix, yet both were uploaded for every
|
|
255
|
+
command — along with a fresh `Float32Array(16)` and a transpose per draw to
|
|
256
|
+
build the color matrix, and a fresh `screenToClip` matrix per draw to build the
|
|
257
|
+
projection. GL holds uniform state per program object, so all of that was
|
|
258
|
+
buying nothing.
|
|
259
|
+
|
|
260
|
+
`draw.ts` now remembers what it last sent each program and skips the upload
|
|
261
|
+
when the value has not changed. On a frame of 1,000 solid rects that takes
|
|
262
|
+
`uniformMatrix4fv` from 1,000 calls to 1 and `uniformMatrix3fv` from 2,000 to
|
|
263
|
+
2, and removes two per-draw allocations.
|
|
264
|
+
|
|
265
|
+
The cache hangs off the `DrawContext`, which is rebuilt per frame, so it cannot
|
|
266
|
+
outlive a frame or go stale against GL state changed between frames. It covers
|
|
267
|
+
only the four uniforms this module is the sole writer of — `u_color` and
|
|
268
|
+
`u_alpha` have several writers and are still sent every draw.
|
|
269
|
+
|
|
270
|
+
This does not measurably change frame time on an M2 Max: the draw loop is bound
|
|
271
|
+
by per-draw-call cost (~68 us per command), not by uniform uploads. It removes
|
|
272
|
+
the calls and the allocations; `docs/TODO.md` tracks what the remaining cost
|
|
273
|
+
actually is.
|
|
274
|
+
|
|
275
|
+
- fa1ed05: Dragging a text node no longer re-lays it out.
|
|
276
|
+
|
|
277
|
+
`layoutRuns` baked the text's position into every quad, decoration rule and
|
|
278
|
+
line box, so `layoutCache` had to carry that position in its key. Panning and
|
|
279
|
+
zooming still hit the cache, but _moving_ a text node missed on every frame —
|
|
280
|
+
at 500 wrapped glyphs a move cost 0.130 ms against a 1.7e-4 ms hit, which is
|
|
281
|
+
the same 0.134 ms a full miss cost. Moving text paid as if there were no cache
|
|
282
|
+
at all.
|
|
283
|
+
|
|
284
|
+
Layout now emits geometry relative to the text's own top-left and `drawText`
|
|
285
|
+
translates while packing vertices, alongside the `verticalAlign` offset it
|
|
286
|
+
already applied there. Position is out of the cache key, so a move is a hit:
|
|
287
|
+
median 0.130 ms → 0.000125 ms, min 0.123 ms → 0.000041 ms. The cold path is
|
|
288
|
+
unchanged (min 0.123 ms → 0.110 ms for a full miss).
|
|
289
|
+
|
|
290
|
+
This is not a rendering change. Alignment, wrapping, tracking, kerning and
|
|
291
|
+
decoration placement read widths and pen deltas, never an absolute coordinate,
|
|
292
|
+
and nothing in the walk rounds or snaps — checked over 247,572 coordinates
|
|
293
|
+
spanning three alignments, four wrap widths, mixed sizes, positive and negative
|
|
294
|
+
tracking, and fractional positions. The only differences were float64 rounding
|
|
295
|
+
from folding the position into the accumulator early, none of them survived the
|
|
296
|
+
conversion to the float32 vertex buffers, and they favor the new code: at a
|
|
297
|
+
position of 1e6 the old path computed 28.800000000046566 where this one gives
|
|
298
|
+
28.8. The 37-test Playwright visual suite is unchanged.
|
|
299
|
+
|
|
300
|
+
`layoutRuns` and `cachedLayoutRuns` lose their `origin` parameter, and the
|
|
301
|
+
`LayoutRunsOrigin` type is gone. Neither is exported from the package. Callers
|
|
302
|
+
of the public `textLineBoxes` and `measureTextBounds` see no change.
|
|
303
|
+
|
|
304
|
+
- 0a40c29: Add `scene.renderOrderNodes()`, and scan it in the area hit-test.
|
|
305
|
+
|
|
306
|
+
`renderOrder()` hands back ids, and almost every caller immediately resolves
|
|
307
|
+
each one back to a node — a map lookup per node, per query, for a node the
|
|
308
|
+
traversal had in hand and dropped. `renderOrderNodes()` is the same
|
|
309
|
+
layer-major sequence as the nodes themselves. It is a snapshot, freshly built
|
|
310
|
+
per call, exactly like `renderOrder()`.
|
|
311
|
+
|
|
312
|
+
`hitTestArea` (marquee and lasso) now scans it, and reads `pose.kind` inline
|
|
313
|
+
instead of through the `isPathLike` predicate. Together those recover the
|
|
314
|
+
5–17% the AABB memo cost rect scenes and take a good deal more besides
|
|
315
|
+
(`npm run bench`, `min` column, three alternating runs per build on one
|
|
316
|
+
machine; run-to-run scatter on these was under 3%):
|
|
317
|
+
|
|
318
|
+
| 10,000 nodes, 25% query rect | before | after |
|
|
319
|
+
| ---------------------------- | ------- | ------- |
|
|
320
|
+
| rect poses | 0.94 ms | 0.53 ms |
|
|
321
|
+
| 24-gon silhouettes | 1.21 ms | 0.78 ms |
|
|
322
|
+
|
|
323
|
+
`renderOrder()` itself gets a separate walk for the single-layer case, which
|
|
324
|
+
needs no per-layer buckets and can compare the layer id rather than index it:
|
|
325
|
+
10,000 nodes over one layer 0.27 ms → 0.19 ms, with multi-layer scenes
|
|
326
|
+
unchanged. `toJSON()` rides the nodes walk and skips its lookups too.
|
|
327
|
+
|
|
328
|
+
`Scene` gains a method, so a hand-written stand-in for a scene needs to
|
|
329
|
+
implement it; scenes from `createScene` and `sceneFromJSON` already do.
|
|
330
|
+
|
|
331
|
+
- Updated dependencies [e367165]
|
|
332
|
+
- @weasel-js/gestures@1.0.1
|
|
333
|
+
- @weasel-js/font@1.0.1
|
|
334
|
+
- @weasel-js/geom@1.0.1
|
|
335
|
+
- @weasel-js/history@1.0.1
|
|
336
|
+
- @weasel-js/modes@1.0.1
|
|
337
|
+
|
|
3
338
|
## 1.0.0
|
|
4
339
|
|
|
5
340
|
### Minor Changes
|