@solidrt/core 0.0.50 → 0.0.51

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/AGENTS.md CHANGED
@@ -7,6 +7,15 @@ trust this file and the types in src/types.d.ts and jsx-runtime.d.ts.
7
7
  SolidRT is a custom SolidJS renderer: it paints through a Rust runtime, not the
8
8
  DOM. There is no HTML, no CSS cascade, no `className`.
9
9
 
10
+ Two companion files carry the depth this one leaves out; read the one that
11
+ matches the work before starting it:
12
+ - agents/painting.md - what you paint with, and what replaces each CSS
13
+ reflex. Read before styling a screen: a background, a gradient, a shadow,
14
+ an effect, vector art, a chart.
15
+ - agents/performance.md - the performance model, in order of leverage. Read
16
+ before writing any per-frame code, any animation, or anything that writes
17
+ properties in a loop.
18
+
10
19
  ## The window is device-sized - design fluid
11
20
 
12
21
  A SolidRT window is host-sized and resizable, and the SAME app runs on phones,
@@ -135,6 +144,8 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
135
144
  box center; a d-view has no box). To scale a detached group around its
136
145
  content's center, set the origin explicitly in pixels, e.g.
137
146
  `originX={100} originY={50}` for content drawn in a 200x100 local space.
147
+ Avoid pct()/keyword origins on a d-view - they resolve against the box
148
+ inherited from the nearest laid-out ancestor.
138
149
 
139
150
  - Layout-affecting vs not (this matters for per-frame work). Props fall in three
140
151
  buckets, split by where they take effect:
@@ -160,7 +171,8 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
160
171
  `\n` inside one produces a hard line break.
161
172
 
162
173
  - Rich text: `<span>` inside `<text>` restyles a run (`color`, `fontFamily`,
163
- `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`); spans nest and
174
+ `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`,
175
+ `textDecoration="underline"`); spans nest and
164
176
  inherit inward from the `<text>`. Never lay a paragraph out word by word in
165
177
  a wrapping row to mix styles - one `<text>` with spans wraps as a whole.
166
178
  A span is content, not a box (no layout or `d-` form, no size, no
package/README.md CHANGED
@@ -53,7 +53,7 @@ Optionally, create a `tsconfig.json` to enable type recognition for SolidRT elem
53
53
 
54
54
  ## API
55
55
 
56
- See [docs/core.md](https://github.com/wellawaretech/solidrt/blob/main/docs/core.md) for the full API reference.
56
+ See [docs/20-core](https://github.com/wellawaretech/solidrt/blob/main/docs/20-core/index.md) for the full API reference.
57
57
 
58
58
  ## License
59
59
 
@@ -0,0 +1,61 @@
1
+ # What you paint with (there is no CSS layer)
2
+
3
+ Read this before building any screen whose look matters: a background, a
4
+ decoration, an effect, a chart, anything you would have reached for CSS for.
5
+
6
+ Layout and props are half the model. There is no stylesheet: no filters, no
7
+ box-shadow, no keyframes, no canvas. The visual range a web app gets from CSS
8
+ comes from the tiers below instead, and reaching past tier 1 is ordinary
9
+ app-building here, not optimization - a screen built only from view
10
+ backgrounds and text is using a fraction of the runtime. Pick the tier the
11
+ CONTENT calls for, not the one that looks safest.
12
+
13
+ 1. Laid-out elements - `<view>`/`<text>`, with `<rect>` (or a filling
14
+ `<d-rect>` child) for background, border, radius. The structure of a
15
+ screen, not its finish.
16
+ 2. Vector art, detached from layout - `d-path`/`d-rect`/`d-oval`/`d-line`,
17
+ whose `color` takes a gradient (createLinearGradient /
18
+ createRadialGradient) and which honour `blendMode`, plus `parseSvg` to
19
+ draw a whole SVG document as one subtree. Free-form shapes, decoration,
20
+ diagrams, charts, anything positioned rather than flowed. Examples:
21
+ parse-svg, detached-positioning, text-paint-styling.
22
+ 3. GPU textures - `createShaderTexture` puts a fragment shader in a
23
+ `<texture>` (moving gradients, noise, glow, dissolves, a background that
24
+ is alive), `createPipelineTexture` draws geometry you generate yourself
25
+ (particles, point clouds, splats), and the `shader` prop post-processes
26
+ content that already exists: on a `<view>` it grades, warps or dissolves
27
+ that subtree, on `<window>` the whole frame. Stack `<texture>` elements
28
+ with `blendMode` to combine passes. Examples: gpu-shader, gpu-particles,
29
+ gpu-pipeline, gpu-instancing, gpu-texture-blend, view-shader,
30
+ window-shader.
31
+ 4. 3D scenes - add `@solidrt/3d` (not a scaffold dependency): meshes,
32
+ materials and a camera declared as Solid components, rendered into a
33
+ texture that sits in the UI tree like any other element.
34
+
35
+ Tier 3 is cheaper than it looks. A shader costs one property write per frame
36
+ no matter how complex the effect, which is why the performance model
37
+ (agents/performance.md) reaches for it first rather than as a last resort.
38
+
39
+ ## Web reflexes and what replaces them
40
+
41
+ - gradient background -> a gradient `color` on a `d-rect` (gradients are
42
+ paint values, usable anywhere a color is)
43
+ - `filter: blur/grayscale/hue-rotate`, and any "make this look processed" ->
44
+ a `shader` on the view (requires repaintBoundary="snapshot"), or on
45
+ `<window>` for the whole frame
46
+ - `box-shadow` / `text-shadow` / glow -> no shadow prop exists: draw an
47
+ offset `d-*` shape under the content, or a view shader with `outset` (the
48
+ transparent margin an effect bleeds into)
49
+ - `backdrop-filter` -> no equivalent. A view shader sees only its own
50
+ subtree's pixels, never what is behind it. Frost the whole frame with a
51
+ window shader, or fake the layer with your own content
52
+ - CSS `transition` -> the `transition` prop: declare it on the element and
53
+ keep writing targets; the runtime animates natively (performance rule 1)
54
+ - `@keyframes` -> a `transition` prop when the motion is target-shaped;
55
+ `onFrame` writing a signal for genuinely procedural sequences; a `uTime`
56
+ uniform when the animation is continuous and visual
57
+ - `<canvas>` 2D -> `d-*` primitives (rebuild one `d-path` string per frame
58
+ rather than animating N elements)
59
+ - `<canvas>` WebGL, three.js -> `createPipelineTexture`, or `@solidrt/3d`
60
+ - video background, animated hero, particle field -> a shader texture; this
61
+ is the case the runtime is built for
@@ -0,0 +1,216 @@
1
+ # Performance model (JS is the slow lane)
2
+
3
+ Read this before writing any per-frame code, any animation, or anything that
4
+ writes properties in a loop.
5
+
6
+ The JS engine is interpreted and every property write crosses an FFI boundary
7
+ into the runtime, so per-frame JS work is the expensive path while GPU work is
8
+ nearly free. That holds on desktop and on current mobile hardware; "Where GPU
9
+ work stops being free" below is where it does not. The design answer is not
10
+ "write less JS" but "keep JS off the per-frame path": the platform animates
11
+ (transitions), caches (repaint boundaries), shades (GPU) and computes
12
+ (isolates, wasm) natively, and JS stays the coordinator that sets targets.
13
+ Rules, in order of leverage:
14
+
15
+ 1. Motion between states (position, size, opacity, transform components,
16
+ solid colors, enter/exit) belongs in a native transition, never in
17
+ per-frame JS. Declare `transition` on the element and keep writing
18
+ targets the ordinary way; the runtime interpolates every frame on the
19
+ Rust side, so JS runs only when a target changes and the running
20
+ animation costs no JS and no property writes per frame, however many
21
+ elements move. Flat spec, ms durations, kind inferred:
22
+ `{ duration }` / `{ duration, bounce }` is a spring (the default kind;
23
+ springs carry velocity, so a retarget mid-flight stays continuous -
24
+ use them for anything interactive), `{ duration, curve }` is a tween
25
+ (`linear | ease | ease-in | ease-out | ease-in-out` or a cubic-bezier
26
+ array; tweens restart from the current value on retarget, CSS
27
+ semantics). Keys are property names plus `all` as catch-all; a string
28
+ is shorthand (`transition="300ms ease-out"`); `delay` holds each
29
+ write, `from` animates the first attach in (enter), `exit` animates
30
+ removal out before the node frees, `stagger` on a parent cascades its
31
+ children's enters/exits, and `onTransitionEnd` fires per settled
32
+ property. The initial value never animates without `from`; a write to
33
+ a property without a transition snaps, as always. A JS tween loop or
34
+ animation library pushing interpolated values through signals pays the
35
+ whole write path per element per frame - port it to this.
36
+ 2. Continuous effects (snow, particles, animated backgrounds) belong in a
37
+ fragment shader: createShaderTexture (from @solidrt/core/gpu) + `<texture
38
+ params={{ uTime }}>` (the shader declares `uniform float uTime;` itself -
39
+ the preamble declares only what the runtime fills). The whole effect then
40
+ costs one setProperty per frame - the uTime write - regardless of visual
41
+ complexity. Shader output
42
+ must be premultiplied alpha (white flakes are `vec4(vec3(a), a)`);
43
+ straight alpha (`vec4(1,1,1,a)`) composites as opaque white. A source that
44
+ starts with `#version 300 es` is compiled exactly as written - no preamble
45
+ is injected, though the built-in vertex stage still supplies `vUV` - so a
46
+ shader ported from elsewhere keeps its own uniform names without dropping
47
+ to compileShader/linkProgram. Params drive any uniform type: a number
48
+ fills a `float`/`int` scalar, a flat number array fills `vec2`/`vec3`/
49
+ `vec4` (2/3/4 numbers) or `mat4` (16, column-major), dispatched by the
50
+ shader's own declaration - a ported shader's `vec2 uCenter` or
51
+ `vec3 iResolution` needs no splitting into scalars. To combine several
52
+ GPU passes, stack `<texture>` elements and set `blendMode` (e.g. a base
53
+ pass plus an additive `blendMode="plus"` pass) rather than writing a
54
+ compositing shader. Within one pipeline draw, createPipelineTexture's
55
+ `blend: "add"` accumulates overlapping geometry additively (soft point
56
+ splats, glow) - pair it with `depthWrite: false` when depth-tested;
57
+ neither option implies the other. A pipeline's own vertex stage writes
58
+ into a y-down clip space: `gl_Position` y = -1 is the top row of the
59
+ target and +1 the bottom, so camera-up geometry must negate y (or fold
60
+ the flip into its projection) or it draws upside down. Sampling is a
61
+ create-time option on every texture: `{ filter: "nearest" }` for
62
+ hard-pixel upscaling (render a small target, display it big - the
63
+ retro/pixel-art path) and `{ wrap: "repeat" }` to tile outside 0..1 in
64
+ shaders; the defaults are linear and clamp, and the choice applies both
65
+ on screen and to shaders sampling the texture.
66
+ 3. Reduce setProperty calls wherever possible: one path string rebuilt per
67
+ frame beats N elements with N animated positions; a shader beats the path
68
+ string. get_stats' setPropsPerFrame is the counter to watch. Compiled JSX
69
+ attribute expressions diff before writing, so a per-frame expression that
70
+ returns an unchanged value costs no property write - setPropsPerFrame
71
+ counts values that actually changed, not expressions re-run.
72
+ 4. Never leave onFrame registered while nothing animates: a pending onFrame
73
+ is a standing frame request, so the runtime renders and presents every
74
+ vsync even when the callback body does nothing - an invisible 60fps GPU
75
+ burn that also drags the OS compositor along with it. Tweens and
76
+ springs need no pump at all - that is rule 1, and the runtime requests
77
+ frames only while tracks run. For genuinely procedural per-frame motion,
78
+ use a self-rechaining one-shot requestAnimationFrame that stops
79
+ re-requesting when its work list empties. (Registering onFrame outside a
80
+ component body also warns NO_OWNER_CLEANUP - it assumes a reactive owner.)
81
+ 5. repaintBoundary works like Flutter's: transforms and opacity on the
82
+ boundary node itself (or any ancestor) are hoisted out of the cache and
83
+ applied at composite time, so animating x/y/scale/rotate/opacity of a
84
+ boundary does NOT re-raster it (verified by A/B measurement - the damage
85
+ system classifies these as Transform and keeps the node's own cache).
86
+ What DOES invalidate the cache is any paint or content change inside the
87
+ subtree - colors, path data, text, a Show toggling - so drive animation
88
+ with transforms and keep the cached content itself static. Off a boundary,
89
+ `opacity` on a view is NOT cheap: it wraps the subtree in a compositing
90
+ layer (save_layer) for as long as it is below 1. To fade a single
91
+ primitive, put the alpha in its `color` (`rgba(...)`) - paint alpha is
92
+ free; reserve view `opacity` for fading a genuine group as a whole.
93
+ Placement rule for animation-heavy screens: a boundary around a node
94
+ that animates its own paint (a moving d-*, a changing color) is useless
95
+ - its interior is damaged every frame, so the cache never survives. The
96
+ win is a boundary around the static bulk NEXT TO the animators: the
97
+ frame then re-records only the moving nodes and replays the fenced
98
+ content as one cached draw, an order-of-magnitude cut when static
99
+ content dominates the node count. get_stats' nodesPainted shows exactly
100
+ what the paint walk still enters. The exception where a boundary on the
101
+ animator itself pays is transform/opacity animation of the boundary
102
+ node - the hoisting described above.
103
+ 6. "snapshot" boundaries pay first-frame texture allocation + raster:
104
+ creating many at once (dealing a board of 64 sprites) is a visible
105
+ one-frame hiccup - pool or pre-warm if that moment matters.
106
+ 7. Shading pixels the app already drew is a different mechanism from rule 2's
107
+ generated textures, and both forms are a `shader` prop taking a linked
108
+ program from compileShader/linkProgram (@solidrt/core/gpu), not a
109
+ createShaderTexture source. On `<window>`, `shader={{ program, params }}`
110
+ runs the finished frame through the program as the last step before it
111
+ reaches the screen: the frame binds as `uniform sampler2D uSource`,
112
+ `iResolution` fills by name, and `previous: true` retains the last frame as
113
+ `uPrevious` for motion echo or frame differencing. On a `<view>` the same
114
+ prop shades that subtree in place and REQUIRES repaintBoundary="snapshot"
115
+ (without it the shader is ignored with a warning); the pass sees only the
116
+ subtree's own pixels - grading, warping or dissolving the panel works,
117
+ anything needing what is behind it does not - and is split from content
118
+ invalidation, so a params-only change re-runs the pass against the cached
119
+ snapshot instead of re-rasterizing. A window shader's output is invisible
120
+ to get_snapshot and every other MCP tool; `bunx srt render` is the only
121
+ way to see it (see @solidrt/cli AGENTS.md).
122
+ 8. `flux:wasm` runs a pure interpreter (wasmi, no JIT), so temper browser
123
+ expectations - but do not write it off for compute. A genuinely numeric
124
+ kernel (typed-array math, tight inner loops, no host calls inside the
125
+ loop) compiled from a systems language can come out a real multiple
126
+ faster than the same loop in interpreted JavaScript, and when profiling
127
+ shows such a kernel is what the app is spending its time on, that
128
+ multiple is worth having: measure the JS loop, port the kernel, measure
129
+ again, keep whichever wins. What wasm does not do is speed up
130
+ render-path work (rules 1-3 are that leverage), and every host call
131
+ costs marshalling, so batch at the boundary - one call over a byte
132
+ buffer, not a call per element. It is also the way to ship one compiled
133
+ module across every target with no native toolchain, and it pairs with
134
+ an isolate when a call runs long enough to block.
135
+ 9. `flux:ffi` (dlopen of a native library) is a binding tool, not a
136
+ performance tool. It needs a shared library compiled per platform and
137
+ architecture and shipped under each target's packing rules (Android
138
+ loads only what arrives inside the APK as a lib*.so), so reaching for
139
+ it "to make something fast" buys a build-and-packaging problem on every
140
+ platform the app targets. Use it when the app must call a native
141
+ library that already exists and already ships for those targets; for
142
+ speed, everything above comes first.
143
+
144
+ ## Isolates: heavy work off the JS thread
145
+
146
+ A long synchronous computation (a big parse, a simulation step, a blocking
147
+ `flux:ffi`/`flux:wasm` call) freezes rendering and input for its duration.
148
+ Move it into an isolate module: a file whose first statement is the
149
+ `"use isolate"` directive runs in a second runtime on its own thread, and
150
+ main calls its exports as async functions.
151
+
152
+ ```ts
153
+ // src/worker.ts
154
+ "use isolate"
155
+ export function crunch(data: Uint8Array): number { /* ... */ }
156
+ ```
157
+
158
+ ```ts
159
+ // src/index.tsx
160
+ import { isolate } from "flux:isolate"
161
+ import type * as Worker from "./worker"
162
+ let worker = isolate<typeof Worker>("worker") // id = path from src/, no extension
163
+ let n = await worker.crunch(bytes) // main keeps rendering meanwhile
164
+ ```
165
+
166
+ The bundler builds each such module as its own bundle and ships it with the
167
+ app (dev pushes and `srt pack` alike). Rules: main may only `import type`
168
+ from an isolate module (a value import is a build error); arguments and
169
+ results are copies (numbers, strings, byte buffers, arrays, plain objects -
170
+ no functions, no class instances); the child has the non-gui `flux:*`
171
+ modules only, so it never touches the render tree; module state persists
172
+ between calls and each `isolate()` call is its own instance. An
173
+ `async function*` export is a stream: `for await (let p of worker.progress())`
174
+ pulls one item per step (progress, ticks, a subscription), `break` ends it in
175
+ the isolate, and streams never block plain calls. Full contract:
176
+ node_modules/@solidrt/flux-types/modules/isolate.d.ts.
177
+
178
+ ## Where GPU work stops being free
179
+
180
+ "GPU work is nearly free" is a property of the hardware, not of the engine, and
181
+ the spread is wide enough to design against rather than discover late. The same
182
+ app - two point-cloud pipelines, 233,600 vertices, one params write each per
183
+ onFrame, i.e. exactly what rule 2 recommends - measured 16.7 ms/frame (60 fps,
184
+ vsync-locked) on both desktop and a mid-range 2020 tablet, and 120 ms/frame
185
+ (8.3 fps) on a 2017 Android TV. Roughly 8x for identical work, with the tablet
186
+ indistinguishable from desktop. Measure on a target device if it matters; do
187
+ not infer it from the desktop number.
188
+
189
+ - **On a tiled GPU the budget is primitive count, not pixels.** Every point or
190
+ triangle costs the tiler regardless of how few pixels it covers. On that TV,
191
+ frame time against total vertices with a trivial vertex shader: 20k -> 80 ms,
192
+ 35k -> 100 ms, 100k -> 380 ms. Meanwhile `gl_PointSize = 3.0` - nine times
193
+ the fill - measured within one vsync of 1.0, and rendering into a
194
+ quarter-size target measured identical to full size. So for a heavy pass the
195
+ lever is fewer primitives; shrinking the target or the splat usually is not,
196
+ and coverage is far cheaper bought with point size than with more points.
197
+ - **A device's compositor can set the frame budget outright**, in which case
198
+ none of the above moves. That TV never presents faster than every 80 ms -
199
+ four refresh periods blocked inside `eglSwapBuffers` - even for a near-empty
200
+ scene, so its ceiling is ~12 fps whatever you draw. Recognise it by a
201
+ content-independent floor: if a trivial scene and a heavy one present at
202
+ nearly the same rate, you are compositor-bound and tuning the scene is
203
+ wasted effort.
204
+ - **Per-frame writes are gated on the raster thread, not on vsync**, so a pass
205
+ that costs more than a refresh period does not silently pile up. If
206
+ `rasterQueue` climbs across queries while fps drops the raster thread is
207
+ behind; if `fenceTimeoutsPerSec` (in get_stats' window block) is nonzero,
208
+ the GPU is over its pacing budget right now.
209
+
210
+ Finding your own numbers: `get_stats` gives fps, frameMs, setPropsPerFrame,
211
+ the window summary (worst frame, percentiles, GPU rates), rasterQueue and
212
+ fenceTimeouts. When those disagree with what the screen is
213
+ visibly doing, ground truth on Android is
214
+ `adb shell dumpsys SurfaceFlinger --latency <layer>` for real present
215
+ timestamps - engine-reported phase timings can each be honest and still not add
216
+ up to the frame period, because work outside the frame call is not in them.
@@ -47,8 +47,8 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
47
47
  - `window-shader.tsx` - the `shader` prop on `<window>`: the finished frame drawn through a raw-linked warp program before present, click to toggle between warp and identity.
48
48
  - `window-shader-history.tsx` - the window shader's frame history: `previous` binds last frame as uPrevious, drawn as a one-frame motion echo behind an orbiting square; click toggles the echo term.
49
49
 
50
- ## Sound
51
- - `sound.tsx` - `createSound`: decode a clip once from bytes (here a binary import), replay cheaply; `overlap` stacking vs single-voice, `playing()` signal, release on unmount. `createPcmSound` for a synthesised clip from raw samples (a generated sine sweep). Points to `createSoundStream` for long tracks streamed from a path.
50
+ ## Audio
51
+ - `audio.tsx` - `createSound`: decode a clip once from bytes (here a binary import), replay cheaply; `overlap` stacking vs single-voice, `playing()` signal, release on unmount. `createPcmSound` for a synthesised clip from raw samples (a generated sine sweep). Points to `createSoundStream` for long tracks streamed from a path.
52
52
 
53
53
  ## Vector graphics
54
54
  - `parse-svg.tsx` - `parseSvg` turns a whole SVG *document string* (not HTML/JSX children) into plain draw data mapped to `<d-path>` inside a `viewBox`-fitted view; per-shape hover highlighting shows the payoff (exact-outline hit testing, recolor without re-parse), plus a `currentColor` icon recolored via the `color` option. This is how to use existing icon libraries (Lucide, Heroicons, etc.) - hand their SVG source to `parseSvg`.
@@ -23,7 +23,7 @@
23
23
  // demand and stays off the heap. Same play()/stop()/playing() surface,
24
24
  // always single-voice.
25
25
  import { render } from "@solidrt/core"
26
- import { createPcmSound, createSound } from "@solidrt/core/sound"
26
+ import { createPcmSound, createSound } from "@solidrt/core/audio"
27
27
  import blipBytes from "./blip.wav" with { type: "binary" }
28
28
 
29
29
  const RATE = 44100
@@ -0,0 +1,102 @@
1
+ // The zero-copy per-frame geometry path: instanced quads whose records are
2
+ // rewritten every frame through a buffer write lease. beginBufferWrite hands
3
+ // back a Float32Array over runtime-owned memory (contents unspecified - fill
4
+ // everything you publish), the frame callback writes every live record into
5
+ // it, and endBufferWrite publishes by MOVING the block to the raster thread:
6
+ // no data copy anywhere on the CPU path, and no per-sprite property writes.
7
+ // Compare gpu-instancing.tsx, where the records are static and only the draw
8
+ // range changes; here the records themselves are the animation.
9
+ //
10
+ // The buffer is created from a byte LENGTH (zeroed storage) - the natural
11
+ // create when every byte arrives through the lease. Publishing a prefix and
12
+ // setDraw({ instanceCount }) keep buffer capacity and live population
13
+ // independent: reserve the max up front, publish what exists this frame.
14
+ import { render, onFrame } from "@solidrt/core"
15
+ import { beginBufferWrite, createBuffer, createPipelineTexture, endBufferWrite, glsl, setDraw } from "@solidrt/core/gpu"
16
+
17
+ const MAX_SPRITES = 2000
18
+ // floats per record: center vec2, half-size f32, tint vec3.
19
+ const RECORD = 6
20
+
21
+ let VERTEX = glsl`
22
+ in vec2 aPos;
23
+ in vec2 iCenter;
24
+ in float iSize;
25
+ in vec3 iTint;
26
+ out vec3 vTint;
27
+
28
+ void main() {
29
+ gl_Position = vec4(iCenter + aPos * iSize, 0.0, 1.0);
30
+ vTint = iTint;
31
+ }
32
+ `
33
+
34
+ let FRAGMENT = glsl`
35
+ in vec3 vTint;
36
+
37
+ void main() {
38
+ fragColor = vec4(vTint, 1.0);
39
+ }
40
+ `
41
+
42
+ // Simulation state lives in plain arrays; the instance buffer holds only
43
+ // this frame's published snapshot of it.
44
+ let x = new Float32Array(MAX_SPRITES)
45
+ let y = new Float32Array(MAX_SPRITES)
46
+ let vx = new Float32Array(MAX_SPRITES)
47
+ let vy = new Float32Array(MAX_SPRITES)
48
+ for (let i = 0; i < MAX_SPRITES; i++) {
49
+ x[i] = Math.random() * 1.9 - 0.95
50
+ y[i] = Math.random() * 1.9 - 0.95
51
+ vx[i] = (Math.random() * 2 - 1) * 0.01
52
+ vy[i] = (Math.random() * 2 - 1) * 0.01
53
+ }
54
+
55
+ function App() {
56
+ // One unit quad (triangle strip), reused by every instance.
57
+ let quad = createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]), { label: "sprite-quad" })
58
+ let records = createBuffer(MAX_SPRITES * RECORD * 4, { label: "sprite-records" })
59
+ let id = createPipelineTexture(VERTEX, FRAGMENT, 720, 720, null, {
60
+ label: "sprites",
61
+ topology: "triangle-strip",
62
+ vertexCount: 4,
63
+ attributes: [{ name: "aPos", format: "vec2" }],
64
+ buffer: quad,
65
+ instanceAttributes: [
66
+ { name: "iCenter", format: "vec2" },
67
+ { name: "iSize", format: "f32" },
68
+ { name: "iTint", format: "vec3" },
69
+ ],
70
+ instanceBuffer: records,
71
+ instanceCount: MAX_SPRITES,
72
+ clearColor: [0.03, 0.03, 0.06, 1],
73
+ })
74
+
75
+ onFrame(() => {
76
+ let out = beginBufferWrite(records)
77
+ for (let i = 0; i < MAX_SPRITES; i++) {
78
+ let nx = x[i]! + vx[i]!
79
+ let ny = y[i]! + vy[i]!
80
+ if (nx < -0.95 || nx > 0.95) vx[i] = -vx[i]!
81
+ else x[i] = nx
82
+ if (ny < -0.95 || ny > 0.95) vy[i] = -vy[i]!
83
+ else y[i] = ny
84
+ let at = i * RECORD
85
+ out[at] = x[i]!
86
+ out[at + 1] = y[i]!
87
+ out[at + 2] = 0.01 + 0.008 * (i % 5)
88
+ out[at + 3] = 0.5 + 0.5 * Math.cos(i * 0.11)
89
+ out[at + 4] = 0.5 + 0.5 * Math.cos(i * 0.13 + 2.1)
90
+ out[at + 5] = 0.5 + 0.5 * Math.cos(i * 0.17 + 4.2)
91
+ }
92
+ endBufferWrite(records)
93
+ })
94
+
95
+ return (
96
+ <window alignItems="center" justifyContent="center">
97
+ <texture src={id} width={480} height={480} />
98
+ </window>
99
+ )
100
+ }
101
+
102
+ render(() => <App />)
package/jsx-runtime.d.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  OvalGeometryProps,
16
16
  TextGeometryProps,
17
17
  LineGeometryProps,
18
+ TransitionProps,
18
19
  Element as CoreElement,
19
20
  ElementChildrenAttribute as CoreElementChildrenAttribute
20
21
  } from "./src/types"
@@ -41,20 +42,20 @@ export namespace JSX {
41
42
  // layout box; d-* forms compose the paint-space geometry props instead.
42
43
  interface IntrinsicElements {
43
44
  window: WindowProps & ElementRef
44
- view: ViewProps & ElementRef
45
- text: TextProps & LayoutProps & ElementRef
46
- rect: RectProps & LayoutProps & ElementRef
47
- oval: OvalProps & LayoutProps & ElementRef
48
- line: LineProps & LayoutProps & ElementRef
49
- path: PathProps & LayoutProps & ElementRef
50
- texture: TextureProps & LayoutProps & ElementRef
51
- "d-view": ViewOwnProps & ElementRef
52
- "d-rect": RectProps & GeometryProps & ElementRef
53
- "d-oval": OvalProps & OvalGeometryProps & ElementRef
54
- "d-line": LineProps & LineGeometryProps & ElementRef
55
- "d-path": PathProps & PositionProps & ElementRef
56
- "d-texture": TextureProps & GeometryProps & ElementRef
57
- "d-text": TextProps & TextGeometryProps & ElementRef
45
+ view: ViewProps & TransitionProps & ElementRef
46
+ text: TextProps & LayoutProps & TransitionProps & ElementRef
47
+ rect: RectProps & LayoutProps & TransitionProps & ElementRef
48
+ oval: OvalProps & LayoutProps & TransitionProps & ElementRef
49
+ line: LineProps & LayoutProps & TransitionProps & ElementRef
50
+ path: PathProps & LayoutProps & TransitionProps & ElementRef
51
+ texture: TextureProps & LayoutProps & TransitionProps & ElementRef
52
+ "d-view": ViewOwnProps & TransitionProps & ElementRef
53
+ "d-rect": RectProps & GeometryProps & TransitionProps & ElementRef
54
+ "d-oval": OvalProps & OvalGeometryProps & TransitionProps & ElementRef
55
+ "d-line": LineProps & LineGeometryProps & TransitionProps & ElementRef
56
+ "d-path": PathProps & PositionProps & TransitionProps & ElementRef
57
+ "d-texture": TextureProps & GeometryProps & TransitionProps & ElementRef
58
+ "d-text": TextProps & TextGeometryProps & TransitionProps & ElementRef
58
59
  // A styled run inside <text>/<d-text>; never has a layout box, so there is
59
60
  // no d- form.
60
61
  span: SpanProps & ElementRef
package/package.json CHANGED
@@ -1,17 +1,20 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "license": "MIT",
5
+ "funding": "https://github.com/sponsors/wellawaretech",
5
6
  "author": "Antoine van Wel",
6
7
  "type": "module",
7
8
  "main": "src/index.ts",
8
9
  "exports": {
9
10
  ".": "./src/index.ts",
10
11
  "./camera": "./src/camera.ts",
12
+ "./color": "./src/color.ts",
13
+ "./data": "./src/data.ts",
11
14
  "./gpu": "./src/gpu.ts",
12
15
  "./image": "./src/image.ts",
16
+ "./audio": "./src/audio.ts",
13
17
  "./microphone": "./src/microphone.ts",
14
- "./sound": "./src/sound.ts",
15
18
  "./speech-recognition": "./src/speech-recognition.ts",
16
19
  "./text-input": "./src/text-input.ts",
17
20
  "./jsx-runtime": "./jsx-runtime.d.ts",
@@ -21,13 +24,11 @@
21
24
  "src/",
22
25
  "examples/",
23
26
  "jsx-runtime.d.ts",
27
+ "agents/",
24
28
  "AGENTS.md"
25
29
  ],
26
- "dependencies": {
27
- "colord": "^2.9.3"
28
- },
29
30
  "devDependencies": {
30
- "@solidrt/flux-types": "0.0.50"
31
+ "@solidrt/flux-types": "0.0.51"
31
32
  },
32
33
  "peerDependencies": {
33
34
  "@solidjs/signals": "2.0.0-rc.0",