@solidrt/core 0.0.6 → 0.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "colord": "^2.9.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@solidrt/flux-types": "0.0.6"
23
+ "@solidrt/flux-types": "0.0.7"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@solidjs/signals": "2.0.0-beta.14",
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from "./renderer"
2
2
  export { setFocus, getFocusedNodeId, measureText, getBoundingBox } from "./core"
3
3
  export type { BoundingBox } from "./core"
4
- export { onFrame, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
4
+ export { onFrame, createPacedClock, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
5
5
  export { createTexture, decodeImage } from "./gpu"
6
6
  export type { DecodedImage } from "./gpu"
7
7
  export type {
package/src/types.d.ts CHANGED
@@ -207,7 +207,7 @@ export interface PathProps extends Position, PaintProps, PointerProps {
207
207
  fillRule?: "nonZero" | "evenOdd"
208
208
  }
209
209
 
210
- export interface TextProps extends PaintProps {
210
+ export interface TextProps extends PaintProps, PointerProps {
211
211
  children?: Children
212
212
  fontFamily?: "sans" | "mono" | (string & {})
213
213
  fontSize?: number
package/src/window.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { onCleanup, onSettled, flush } from "@solidjs/signals"
1
+ import { onCleanup, onSettled, flush, createSignal } from "@solidjs/signals"
2
2
  import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
3
3
 
4
4
  // ------ Animation frames ----------------
@@ -6,16 +6,22 @@ import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
6
6
  let nextFrameId = 1
7
7
  let animationFrames = new Map<number, Function>()
8
8
 
9
+ // Latest display refresh rate (Hz) reported by the runtime, passed to onFrame
10
+ // callbacks. Defaults to 60 until the first displayRefreshRate event arrives.
11
+ let refreshRate = 60
12
+
9
13
  /**
10
- * Calls `fn` before every frame is painted. The first call receives tick=0 (game time).
11
- * Returns a cleanup function to stop updates. When called within a reactive scope
12
- * (e.g. a component or createEffect), cleanup is also automatic.
14
+ * Calls `fn` before every frame is painted, with the raw runtime signals: `tick`
15
+ * is the unsmoothed wall-clock time in ms sampled at present, `frame` is the
16
+ * present count, and `rate` is the current refresh rate in Hz. No pacing is
17
+ * applied; see createPacedClock for an opt-in smooth clock.
18
+ * Returns a cleanup function; also auto-cleans within a reactive scope.
13
19
  */
14
- export function onFrame(fn: (tick: number, frame: number) => void) {
20
+ export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
15
21
  let frameId: number = null!
16
22
 
17
- let extendedFn = (tick: number, frame: number) => {
18
- fn(tick, frame)
23
+ let extendedFn = (tick: number, frame: number, rate: number) => {
24
+ fn(tick, frame, rate)
19
25
  frameId = nextFrameId++
20
26
  animationFrames.set(frameId, extendedFn)
21
27
  }
@@ -28,6 +34,26 @@ export function onFrame(fn: (tick: number, frame: number) => void) {
28
34
  return cleanup
29
35
  }
30
36
 
37
+ /**
38
+ * Opt-in smooth clock built on the raw onFrame signals. Paces by present count
39
+ * (one refresh period per frame) and slowly corrects toward the raw wall-clock
40
+ * tick, so it stays smooth while keeping up and tracks real time when the
41
+ * framerate drops. `gain` (0..1) trades convergence speed for jitter. Returns an
42
+ * accessor for the paced time in milliseconds.
43
+ */
44
+ export function createPacedClock(opts?: { gain?: number }) {
45
+ let gain = opts?.gain ?? 0.05
46
+ let [time, setTime] = createSignal(0)
47
+ let clock = 0
48
+ onFrame((tick, _frame, rate) => {
49
+ let period = 1000 / rate
50
+ clock += period
51
+ clock += (tick - clock) * gain
52
+ setTime(clock)
53
+ })
54
+ return time
55
+ }
56
+
31
57
  // ------ Resize ----------------
32
58
 
33
59
  interface SafeArea {
@@ -86,21 +112,27 @@ export function attachWindow(_nodeId: number) {
86
112
  let unsubKeyUp: () => void = null!
87
113
  let unsubTextInput: () => void = null!
88
114
  let unsubKeyboardVisibility: () => void = null!
115
+ let unsubRefreshRate: () => void = null!
89
116
  let unsubFirstResize: (() => void) | null = null
90
117
 
91
118
  function runFrame(t: number, frame: number) {
92
119
  if (animationFrames.size > 0) {
93
120
  let frames = animationFrames
94
121
  animationFrames = new Map()
95
- for (let fn of frames.values()) fn(t, frame)
122
+ for (let fn of frames.values()) fn(t, frame, refreshRate)
96
123
  }
97
124
  flush()
98
125
  draw()
99
126
  }
100
127
 
101
128
  onSettled(() => {
129
+ // Sticky event: a late subscriber still receives the current rate.
130
+ unsubRefreshRate = Flux.on("displayRefreshRate", ({ hz }: { hz: number }) => {
131
+ if (hz > 0) refreshRate = hz
132
+ })
133
+
102
134
  unsubscribe = Flux.on("render", ({ time, frame }: { time: number; frame: number }) => {
103
- runFrame((time * 1000) | 0, frame)
135
+ runFrame(time * 1000, frame)
104
136
  })
105
137
 
106
138
  unsubDown = Flux.on(
@@ -208,6 +240,7 @@ export function attachWindow(_nodeId: number) {
208
240
  if (unsubKeyUp) unsubKeyUp()
209
241
  if (unsubTextInput) unsubTextInput()
210
242
  if (unsubKeyboardVisibility) unsubKeyboardVisibility()
243
+ if (unsubRefreshRate) unsubRefreshRate()
211
244
  if (unsubFirstResize) unsubFirstResize()
212
245
  })
213
246
  }