@solidrt/core 0.0.67 → 0.0.68
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 +14 -4
- package/package.json +3 -3
- package/src/core.ts +12 -0
- package/src/gpu.ts +9 -18
- package/src/index.ts +3 -3
- package/src/runtime-modules.d.ts +8 -0
- package/src/shaders.ts +82 -0
- package/src/types.d.ts +20 -2
- package/src/window.ts +92 -1
package/AGENTS.md
CHANGED
|
@@ -138,9 +138,9 @@ that reads exactly like Solid fallout.
|
|
|
138
138
|
window (message, stack, a Reset button that retries the failed
|
|
139
139
|
computations) and logs `Uncaught error`. `<Errored>` gives a subtree its
|
|
140
140
|
own in-place fallback.
|
|
141
|
-
An `onFrame` callback is its own boundary,
|
|
142
|
-
is caught and logged (`Error in onFrame
|
|
143
|
-
frame), the subscription stays, and THE REST OF THAT CALLBACK IS
|
|
141
|
+
An `onFrame` (or `onBeforeRender`) callback is its own boundary,
|
|
142
|
+
event-listener style: a throw is caught and logged (`Error in onFrame
|
|
143
|
+
callback`, repeating every frame), the subscription stays, and THE REST OF THAT CALLBACK IS
|
|
144
144
|
ABANDONED - what it wrote before the throw lands, what came after never
|
|
145
145
|
does, frame after frame while the app keeps presenting. A partial frame
|
|
146
146
|
is therefore a live possibility: every sprite drawn after the throwing
|
|
@@ -613,7 +613,17 @@ that reads exactly like Solid fallout.
|
|
|
613
613
|
`onFrame(fn, { demand: false })` is the same hook without the standing
|
|
614
614
|
frame request: it runs on every frame the runtime delivers and the app
|
|
615
615
|
presents only when the body writes, for a loop that follows time but
|
|
616
|
-
seldom changes the picture. `
|
|
616
|
+
seldom changes the picture. `onBeforeRender(fn)` is the frame's late
|
|
617
|
+
update: it runs after every onFrame callback and the reactive flush,
|
|
618
|
+
when the frame's state is final, for what must see everything the frame
|
|
619
|
+
wrote and write once more (a camera follow, a fit to a measured box;
|
|
620
|
+
Unity's LateUpdate); its signal writes are flushed before the paint
|
|
621
|
+
too, the way onLayout's are. `onBeforeRender(fn, {
|
|
622
|
+
publish: true })` runs after those, and again after a frame's onLayout
|
|
623
|
+
handlers: the pass where an extension hands the engine what the frame
|
|
624
|
+
wrote into its mirrors (a scene its record buffers and lights, a sprite
|
|
625
|
+
layer its style and count), so a write made anywhere in the frame's JS
|
|
626
|
+
is in that frame's picture, never the next. `frameTime()` is the current frame's `tick`
|
|
617
627
|
read from anywhere (not reactive, requests nothing): the app's own
|
|
618
628
|
clock for code with no tick in hand, where `performance.now()` would
|
|
619
629
|
ignore the dev clock, `sol render` and tests. A JS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.68",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"AGENTS.md"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@solidrt/flux-types": "0.0.
|
|
37
|
-
"@solidrt/test": "0.0.
|
|
36
|
+
"@solidrt/flux-types": "0.0.68",
|
|
37
|
+
"@solidrt/test": "0.0.68"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@solidjs/signals": "2.0.0-rc.13",
|
package/src/core.ts
CHANGED
|
@@ -390,6 +390,18 @@ export function prepareText(text: string, options?: tree.MeasureTextOptions): tr
|
|
|
390
390
|
return tree.prepareText(text, options)
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Makes the glyph cells of printable ASCII for these font styles ahead of
|
|
395
|
+
* their first use, off the frame: call it at startup with the type scale
|
|
396
|
+
* the app draws in (the components' Window does it for the theme's), so a
|
|
397
|
+
* screen's first paint in any of them never waits for cells. Glyphs outside
|
|
398
|
+
* ASCII are made at first use. Each entry names a font as a <text> would;
|
|
399
|
+
* line height and maxLines are ignored.
|
|
400
|
+
*/
|
|
401
|
+
export function warmText(styles: tree.MeasureTextOptions[]): void {
|
|
402
|
+
tree.warmText(styles)
|
|
403
|
+
}
|
|
404
|
+
|
|
393
405
|
/** One laid-out line from layoutNextLine. */
|
|
394
406
|
export type TextLine = {
|
|
395
407
|
/** Unit range [from, to) into prepared.units. */
|
package/src/gpu.ts
CHANGED
|
@@ -97,7 +97,7 @@ export type { TextureFormat } from "flux:gpu"
|
|
|
97
97
|
// runtime, distinct types to the checker, so a cross-space slip like
|
|
98
98
|
// destroyBuffer(textureId) fails to compile. Exported so apps can annotate
|
|
99
99
|
// storage (`let ids: TextureId[]`).
|
|
100
|
-
export type { BufferId, DrawId, ProgramId, RenderPipelineId, ShaderStageId, TextureId } from "flux:gpu"
|
|
100
|
+
export type { BufferId, DrawId, ProgramId, RenderPipelineId, ShaderStageId, TextureId, TextureRect } from "flux:gpu"
|
|
101
101
|
|
|
102
102
|
// Re-exported so callers that depend on @solidrt/core -- like @solidrt/components
|
|
103
103
|
// -- need not import flux directly: destroyTexture for the manual-cleanup path
|
|
@@ -196,22 +196,11 @@ export {
|
|
|
196
196
|
programAttributes,
|
|
197
197
|
} from "flux:gpu"
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* grammars look for).
|
|
205
|
-
*
|
|
206
|
-
* Interpolated values are stringified verbatim, with no GLSL-aware formatting:
|
|
207
|
-
* `${2}` splices in the int literal `2`, which will not assign to a float. Pass
|
|
208
|
-
* anything that varies as a uniform instead of building it into the source.
|
|
209
|
-
*
|
|
210
|
-
* Raw semantics, so backslashes reach the compiler as written: the GLSL
|
|
211
|
-
* preprocessor continues a line with a trailing `\`, which a cooked template
|
|
212
|
-
* would reject as an invalid escape and silently pass through as `undefined`.
|
|
213
|
-
*/
|
|
214
|
-
export let glsl = String.raw
|
|
199
|
+
// The tag, and the screen-size clamp both sprite renderers share: the
|
|
200
|
+
// GLSL function a vertex stage pastes in, its JS twin for the picker, and
|
|
201
|
+
// the validation of a [minScreenPx, maxScreenPx] pair. Both live in the
|
|
202
|
+
// pure shaders.ts so headless tests and pure modules can reach them.
|
|
203
|
+
export { checkScreenSize, glsl, SCREEN_SIZE_GLSL, screenSizeScale } from "./shaders.ts"
|
|
215
204
|
|
|
216
205
|
// captureSnapshot renders a node to pixels and readTexture reads any
|
|
217
206
|
// texture's bytes back; both resolve the same { width, height, data } shape.
|
|
@@ -293,7 +282,9 @@ export function createCubeTexture(
|
|
|
293
282
|
|
|
294
283
|
/**
|
|
295
284
|
* Creates a GPU texture you intend to update over time: seed it with `data`,
|
|
296
|
-
* then call `uploadTexture(id, data)` (from flux:gpu) to push new pixels
|
|
285
|
+
* then call `uploadTexture(id, data)` (from flux:gpu) to push new pixels, or
|
|
286
|
+
* `uploadTexture(id, cell, { x, y, width, height })` to write one rect of it
|
|
287
|
+
* (a runtime atlas placing a cell without re-sending the sheet).
|
|
297
288
|
* `data` must hold at least `width * height` pixels at the declared format's
|
|
298
289
|
* size, in the view type matching the format (Uint8Array for "rgba8"/
|
|
299
290
|
* "rgba8-srgb"/"r8", Float32Array for "r32f"/"rgba32f"/"rgba16f"); it may
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export * from "./renderer"
|
|
2
|
-
export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, layoutNextLine, unitInk, getBoundingBox, getBoundingBoxViewport, getLayoutBox, snapshotTexture, onPointerMove } from "./core"
|
|
2
|
+
export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, warmText, layoutNextLine, unitInk, getBoundingBox, getBoundingBoxViewport, getLayoutBox, snapshotTexture, onPointerMove } from "./core"
|
|
3
3
|
export type { BoundingBox, GlobalPointerEvent, TextLine } from "./core"
|
|
4
4
|
export { parseColor, mixColors, withAlpha, brightness, createLinearGradient, createRadialGradient } from "./color"
|
|
5
5
|
export type { Gradient, GradientStop } from "./color"
|
|
6
|
-
export { onFrame, frameTime, onLayout, onResize, onWindowFocus, onWindowBlur, onBack, onLink, onSuspend, onQuit, exit, background, registerProtocolHandler } from "./window"
|
|
7
|
-
export type { BackEvent, FrameOptions, LifecycleHandler } from "./window"
|
|
6
|
+
export { onFrame, frameTime, onBeforeRender, onLayout, onResize, onWindowFocus, onWindowBlur, onBack, onLink, onSuspend, onQuit, exit, background, registerProtocolHandler } from "./window"
|
|
7
|
+
export type { BackEvent, BeforeRenderOptions, FrameOptions, LifecycleHandler } from "./window"
|
|
8
8
|
export { windowSize, safeArea, displayScale, windowFocused, keyboardHeight, lockPointer, pointerLocked } from "./window"
|
|
9
9
|
export { env } from "./environment"
|
|
10
10
|
export type { InputDevices, SystemTheme, Orientation, Visibility, Launch } from "./environment"
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -306,4 +306,12 @@ declare module "sol:test" {
|
|
|
306
306
|
* frame runs and no app time passes.
|
|
307
307
|
*/
|
|
308
308
|
export function capture(node: number): { width: number; height: number; data: Uint8Array }
|
|
309
|
+
/**
|
|
310
|
+
* Run one frame with a capture of `node` queued ahead of it, so the
|
|
311
|
+
* frame's own paint services it: fulfills, once the frame has run, with
|
|
312
|
+
* what that frame drew of the node (the shape of `capture`). Written
|
|
313
|
+
* and painted in one frame, where `capture` paints the tree afresh
|
|
314
|
+
* after the fact. One frame at a time, as `frame`.
|
|
315
|
+
*/
|
|
316
|
+
export function painted(node: number): Promise<{ width: number; height: number; data: Uint8Array }>
|
|
309
317
|
}
|
package/src/shaders.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The GPU-free shader helpers: the `glsl` tag every shader source in the
|
|
2
|
+
// repo is written with, and the GLSL shared between renderers with its JS
|
|
3
|
+
// twins. Pure - no GPU or GUI imports - so the tests run headless on flux
|
|
4
|
+
// and the extensions' pure modules can import from here; gpu.ts re-exports
|
|
5
|
+
// all of it for apps.
|
|
6
|
+
//
|
|
7
|
+
// The screen-size clamp, shared by every renderer that draws a world-sized
|
|
8
|
+
// quad and lets it hold a size on screen: @solidrt/2d's sprites (the
|
|
9
|
+
// camera zoom is the pixels per world unit) and @solidrt/3d's sprite
|
|
10
|
+
// material (the projection at the quad's depth is). A quad `w x h` world
|
|
11
|
+
// units draws at its world size times the pixels per unit, clamped to
|
|
12
|
+
// [minPx, maxPx] on its smaller axis as ONE uniform scale, so its aspect
|
|
13
|
+
// holds. No bounds is world size; a floor alone is the marker that stays
|
|
14
|
+
// findable at the overview; a ceiling alone is the label that must not
|
|
15
|
+
// balloon; equal bounds is a constant screen size (Three's
|
|
16
|
+
// `sizeAttenuation: false`), where w and h carry the aspect alone. A zero
|
|
17
|
+
// bound is off. The GLSL and the JS below are the same function twice,
|
|
18
|
+
// and every picker applies the JS one to the rect it tests, so a hit is
|
|
19
|
+
// where the pixel is (tests/shaders.test.ts pins the pair).
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tags an inline GLSL source, returning it unchanged. Shaders small enough to
|
|
23
|
+
* belong beside the code that uses them stay in the file; the tag is what makes
|
|
24
|
+
* them legible there, because editors highlight GLSL inside a template literal
|
|
25
|
+
* only when a known tag marks it (the name matters - `glsl` is the one the
|
|
26
|
+
* grammars look for).
|
|
27
|
+
*
|
|
28
|
+
* Interpolated values are stringified verbatim, with no GLSL-aware formatting:
|
|
29
|
+
* `${2}` splices in the int literal `2`, which will not assign to a float. Pass
|
|
30
|
+
* anything that varies as a uniform instead of building it into the source.
|
|
31
|
+
*
|
|
32
|
+
* Raw semantics, so backslashes reach the compiler as written: the GLSL
|
|
33
|
+
* preprocessor continues a line with a trailing `\`, which a cooked template
|
|
34
|
+
* would reject as an invalid escape and silently pass through as `undefined`.
|
|
35
|
+
*/
|
|
36
|
+
export let glsl = String.raw
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The GLSL half: `screenSizeScale(size, pxPerUnit, screenPx)` returns the
|
|
40
|
+
* uniform scale a `size` (world units) quad takes so its smaller axis
|
|
41
|
+
* covers at least `screenPx.x` and at most `screenPx.y` pixels when one
|
|
42
|
+
* world unit is `pxPerUnit` pixels; a zero bound is off, a collapsed quad
|
|
43
|
+
* (zero axis) stays collapsed. Paste it before `main`, multiply the quad's
|
|
44
|
+
* size by it.
|
|
45
|
+
*/
|
|
46
|
+
export const SCREEN_SIZE_GLSL = glsl`
|
|
47
|
+
float screenSizeScale(vec2 size, float pxPerUnit, vec2 screenPx) {
|
|
48
|
+
float smallest = min(abs(size.x), abs(size.y)) * pxPerUnit;
|
|
49
|
+
if (smallest <= 0.0) return 1.0;
|
|
50
|
+
float target = smallest;
|
|
51
|
+
if (screenPx.x > 0.0) target = max(target, screenPx.x);
|
|
52
|
+
if (screenPx.y > 0.0) target = min(target, screenPx.y);
|
|
53
|
+
return target / smallest;
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The JS half of SCREEN_SIZE_GLSL, for the picker: the uniform scale a
|
|
59
|
+
* `w x h` quad draws at when one world unit is `pxPerUnit` pixels and
|
|
60
|
+
* its smaller axis is clamped to [minPx, maxPx] on screen. 1 with both
|
|
61
|
+
* bounds off, when the quad already lies within them, or when it is
|
|
62
|
+
* collapsed.
|
|
63
|
+
*/
|
|
64
|
+
export function screenSizeScale(w: number, h: number, pxPerUnit: number, minPx: number, maxPx: number): number {
|
|
65
|
+
let smallest = Math.min(Math.abs(w), Math.abs(h)) * pxPerUnit
|
|
66
|
+
if (!(smallest > 0)) return 1
|
|
67
|
+
let target = smallest
|
|
68
|
+
if (minPx > 0) target = Math.max(target, minPx)
|
|
69
|
+
if (maxPx > 0) target = Math.min(target, maxPx)
|
|
70
|
+
return target / smallest
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate a screen-size pair (throws - the dev validation policy): each
|
|
75
|
+
* bound finite and >= 0 (0 = off), and a ceiling never below a floor that
|
|
76
|
+
* is on. `site` names the caller in the message.
|
|
77
|
+
*/
|
|
78
|
+
export function checkScreenSize(site: string, minPx: number, maxPx: number): void {
|
|
79
|
+
if (!(Number.isFinite(minPx) && minPx >= 0)) throw new Error(`${site}: minScreenPx must be a finite number >= 0, got ${minPx}`)
|
|
80
|
+
if (!(Number.isFinite(maxPx) && maxPx >= 0)) throw new Error(`${site}: maxScreenPx must be a finite number >= 0, got ${maxPx}`)
|
|
81
|
+
if (minPx > 0 && maxPx > 0 && maxPx < minPx) throw new Error(`${site}: maxScreenPx ${maxPx} is below minScreenPx ${minPx}`)
|
|
82
|
+
}
|
package/src/types.d.ts
CHANGED
|
@@ -1053,6 +1053,14 @@ export interface PathProps extends PaintProps, PointerProps, DashProps {
|
|
|
1053
1053
|
shadow?: ShadowProps
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* A font role ("sans", "serif", "mono": the font the app packages for it,
|
|
1058
|
+
* or the runtime's own Noto) or a registered family name; an unknown
|
|
1059
|
+
* family falls back to sans. A character the family's font lacks is drawn
|
|
1060
|
+
* from the next registered font that has it, roles first; one no
|
|
1061
|
+
* registered font has draws as the font's missing-glyph box. Whitespace
|
|
1062
|
+
* and control characters never draw a box: a tab advances like a space.
|
|
1063
|
+
*/
|
|
1056
1064
|
export type FontFamily = "sans" | "serif" | "mono" | (string & {})
|
|
1057
1065
|
export type FontStyle = "normal" | "italic"
|
|
1058
1066
|
export type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
|
@@ -1076,11 +1084,21 @@ export interface TextRunProps {
|
|
|
1076
1084
|
lineHeight?: number
|
|
1077
1085
|
fontStyle?: FontStyle
|
|
1078
1086
|
fontWeight?: FontWeight
|
|
1087
|
+
/**
|
|
1088
|
+
* Width as a percentage of normal (CSS font-stretch: 100 normal, 75
|
|
1089
|
+
* condensed, 125 expanded), set on the font's width axis and clamped to
|
|
1090
|
+
* its range; a font without one draws at its one width. Default 100.
|
|
1091
|
+
*/
|
|
1092
|
+
fontStretch?: number
|
|
1093
|
+
/**
|
|
1094
|
+
* Extra advance after every character (grapheme cluster) in pixels, the
|
|
1095
|
+
* last one included, as CSS letter-spacing; negative tightens. Default 0.
|
|
1096
|
+
*/
|
|
1097
|
+
letterSpacing?: number
|
|
1079
1098
|
/**
|
|
1080
1099
|
* Underline in the run's own color, drawn straight through descenders
|
|
1081
1100
|
* (no skip-ink). Position and thickness come from the font's own metrics
|
|
1082
|
-
* unless overridden
|
|
1083
|
-
* gets the shipped Noto values.
|
|
1101
|
+
* unless overridden.
|
|
1084
1102
|
*/
|
|
1085
1103
|
textDecoration?: TextDecoration
|
|
1086
1104
|
/** Pixels from the baseline to the top of the underline. */
|
package/src/window.ts
CHANGED
|
@@ -422,7 +422,9 @@ export function keyboardHeight(): number {
|
|
|
422
422
|
// write made by a handler would otherwise reach its node a frame late. The
|
|
423
423
|
// drain lives here, the way runFrame drains before renderFrame, so that no
|
|
424
424
|
// handler has to flush for itself. A throwing handler skips neither the
|
|
425
|
-
// handlers after it nor the flush.
|
|
425
|
+
// handlers after it nor the flush. The publish pass (onBeforeRender) runs
|
|
426
|
+
// once more after the flush, for the same reason: what a handler hands an
|
|
427
|
+
// extension lands in this frame's paint too.
|
|
426
428
|
let layoutHandlers: (() => void)[] = []
|
|
427
429
|
let layoutSubscribed = false
|
|
428
430
|
|
|
@@ -439,6 +441,7 @@ function runLayoutHandlers() {
|
|
|
439
441
|
} catch (err) {
|
|
440
442
|
console.error("Error in reactive flush:", err)
|
|
441
443
|
}
|
|
444
|
+
runPublish()
|
|
442
445
|
}
|
|
443
446
|
|
|
444
447
|
/**
|
|
@@ -462,6 +465,93 @@ export function onLayout(fn: () => void) {
|
|
|
462
465
|
return unsubscribe
|
|
463
466
|
}
|
|
464
467
|
|
|
468
|
+
// ------ Before render ----------------
|
|
469
|
+
|
|
470
|
+
// The last JS of a frame, in two passes. The plain handlers are the app's
|
|
471
|
+
// late update: they run once per frame after every onFrame callback and
|
|
472
|
+
// the reactive flush, so a follow or a fit reads the finished state of the
|
|
473
|
+
// frame (Unity's LateUpdate). The publish handlers run after them, and
|
|
474
|
+
// once more after the post-layout handlers' flush (the one other JS entry
|
|
475
|
+
// ahead of the paint): an extension hands its pending writes to the engine
|
|
476
|
+
// there - a scene's record bytes and lights, a layer's style and pose
|
|
477
|
+
// publish - so they are in the frame they were made in, the way the
|
|
478
|
+
// engine's own transform flush ahead of the paint is. Without this pass
|
|
479
|
+
// those writes land at the microtask after the whole frame closure, one
|
|
480
|
+
// frame after the paint that should have shown them. Each handler is its
|
|
481
|
+
// own boundary, listener style: a throw is logged and the rest still run.
|
|
482
|
+
let lateHandlers: (() => void)[] = []
|
|
483
|
+
let publishHandlers: (() => void)[] = []
|
|
484
|
+
|
|
485
|
+
function runHandlers(handlers: (() => void)[]) {
|
|
486
|
+
for (let fn of [...handlers]) {
|
|
487
|
+
try {
|
|
488
|
+
fn()
|
|
489
|
+
} catch (err) {
|
|
490
|
+
console.error("Error in onBeforeRender callback:", err)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Before renderFrame(): the late pass (skipped on the bootstrap frame, which
|
|
496
|
+
// runs no frame callbacks either), its signal writes flushed, then the
|
|
497
|
+
// publish pass - the same handlers-then-flush shape as the post-layout
|
|
498
|
+
// entry, so a late handler's reactive write reaches its node in this frame
|
|
499
|
+
// and the publish pass sees it. An empty flush is a flag check.
|
|
500
|
+
function runBeforeRender(bootstrap: boolean) {
|
|
501
|
+
if (!bootstrap) {
|
|
502
|
+
runHandlers(lateHandlers)
|
|
503
|
+
try {
|
|
504
|
+
flush()
|
|
505
|
+
} catch (err) {
|
|
506
|
+
console.error("Error in reactive flush:", err)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
runHandlers(publishHandlers)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// After the post-layout handlers' flush: the publish pass alone.
|
|
513
|
+
function runPublish() {
|
|
514
|
+
runHandlers(publishHandlers)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export interface BeforeRenderOptions {
|
|
518
|
+
/**
|
|
519
|
+
* Run in the publish pass: after every plain handler, and again after
|
|
520
|
+
* the `onLayout` handlers of a frame that has any. For handing pending
|
|
521
|
+
* state to the engine, never for deciding it.
|
|
522
|
+
*/
|
|
523
|
+
publish?: boolean
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Calls `fn` right before every frame renders: after the frame's `onFrame`
|
|
528
|
+
* callbacks and the reactive flush, when the frame's state is final. The
|
|
529
|
+
* frame's late update: a camera follow, a fit to a measured box, anything
|
|
530
|
+
* that must see what every frame callback wrote and write once more.
|
|
531
|
+
* Writes made here, to the engine or through signals, are in this frame:
|
|
532
|
+
* the pending reactive writes are flushed once every handler has run.
|
|
533
|
+
*
|
|
534
|
+
* `{ publish: true }` registers for the publish pass instead: after every
|
|
535
|
+
* plain handler, and once more after the `onLayout` handlers of a frame
|
|
536
|
+
* that has any. It is where an extension hands the engine what the frame
|
|
537
|
+
* wrote into its mirrors (a scene syncs its record buffers and lights, a
|
|
538
|
+
* sprite layer its style and pose publish), so a write made anywhere in
|
|
539
|
+
* the frame's JS is in that frame's paint. A publish handler must be
|
|
540
|
+
* idempotent (it may run twice per frame) and must not write app state.
|
|
541
|
+
*
|
|
542
|
+
* Returns a cleanup function; also auto-cleans within an owned scope.
|
|
543
|
+
*/
|
|
544
|
+
export function onBeforeRender(fn: () => void, options?: BeforeRenderOptions) {
|
|
545
|
+
let handlers = options?.publish ? publishHandlers : lateHandlers
|
|
546
|
+
handlers.push(fn)
|
|
547
|
+
let unsubscribe = () => {
|
|
548
|
+
let i = handlers.indexOf(fn)
|
|
549
|
+
if (i >= 0) handlers.splice(i, 1)
|
|
550
|
+
}
|
|
551
|
+
if (getOwner()) onCleanup(unsubscribe)
|
|
552
|
+
return unsubscribe
|
|
553
|
+
}
|
|
554
|
+
|
|
465
555
|
export function onWindowFocus(fn: () => void) {
|
|
466
556
|
let unsubscribe = on("windowFocus", fn)
|
|
467
557
|
if (getOwner()) onCleanup(unsubscribe)
|
|
@@ -589,6 +679,7 @@ export function attachWindow(nodeId: number) {
|
|
|
589
679
|
} catch (err) {
|
|
590
680
|
console.error("Error in reactive flush:", err)
|
|
591
681
|
}
|
|
682
|
+
runBeforeRender(bootstrap)
|
|
592
683
|
scanForOrphans(t)
|
|
593
684
|
renderFrame()
|
|
594
685
|
}
|