@nexart/ui-renderer 0.8.5 → 0.8.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/README.md +25 -21
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/preview/code-renderer.d.ts +3 -6
- package/dist/preview/code-renderer.d.ts.map +1 -1
- package/dist/preview/code-renderer.js +81 -40
- package/dist/preview/frame-budget.d.ts +37 -23
- package/dist/preview/frame-budget.d.ts.map +1 -1
- package/dist/preview/frame-budget.js +48 -53
- package/dist/preview/preview-engine.d.ts.map +1 -1
- package/dist/preview/preview-engine.js +16 -23
- package/dist/preview/preview-types.d.ts +11 -21
- package/dist/preview/preview-types.d.ts.map +1 -1
- package/dist/preview/preview-types.js +7 -11
- package/dist/preview/unified-renderer.d.ts.map +1 -1
- package/dist/preview/unified-renderer.js +16 -22
- package/dist/types.d.ts +2 -2
- package/dist/types.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nexart/ui-renderer
|
|
2
2
|
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.7
|
|
4
4
|
|
|
5
5
|
**Lightweight Preview Runtime for NexArt Protocol**
|
|
6
6
|
|
|
@@ -19,31 +19,35 @@ Version: 0.8.5
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## v0.8.
|
|
22
|
+
## v0.8.7 — Live Runtime Binding Fix
|
|
23
23
|
|
|
24
|
-
Fixed
|
|
24
|
+
Fixed critical bug where time-varying properties were frozen in loop mode.
|
|
25
25
|
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **Canvas preservation**: Skipped frames keep current pixels, no black flashes
|
|
26
|
+
- **Live binding**: `frameCount`, `t`, `time`, `tGlobal`, `totalFrames` now update every frame
|
|
27
|
+
- **Proxy + with pattern**: Uses a Proxy scope with `with()` for live property access
|
|
28
|
+
- **Animations work**: Sketches see correct values on every frame
|
|
30
29
|
|
|
31
|
-
**
|
|
30
|
+
**Animation Semantics:**
|
|
31
|
+
In loop mode, all time variables update every frame:
|
|
32
|
+
- `frameCount`: Increments each frame
|
|
33
|
+
- `t`: Normalized time `(frameCount % totalFrames) / totalFrames` (range [0,1))
|
|
34
|
+
- `time`: Alias for `t`
|
|
35
|
+
- `tGlobal`: Alias for `t`
|
|
36
|
+
- `totalFrames`: Total frames in the loop (default 120)
|
|
37
|
+
|
|
38
|
+
**Test Sketch:**
|
|
32
39
|
```javascript
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
function draw() {
|
|
41
|
+
background(0);
|
|
42
|
+
fill(255);
|
|
43
|
+
circle(
|
|
44
|
+
width / 2 + sin(frameCount * 0.1) * 300,
|
|
45
|
+
height / 2,
|
|
46
|
+
120
|
|
47
|
+
);
|
|
40
48
|
}
|
|
41
49
|
```
|
|
42
|
-
|
|
43
|
-
**Behavior:**
|
|
44
|
-
- Renders up to 30 frames (budget limit)
|
|
45
|
-
- After budget exhausted: RAF continues, draw() skipped, last frame stays visible
|
|
46
|
-
- Restart via `stop()` → `start()` to reset and continue
|
|
50
|
+
Expected: Circle moves horizontally, motion is continuous.
|
|
47
51
|
|
|
48
52
|
---
|
|
49
53
|
|
|
@@ -269,7 +273,7 @@ import { getCapabilities } from '@nexart/ui-renderer';
|
|
|
269
273
|
|
|
270
274
|
const caps = getCapabilities();
|
|
271
275
|
// {
|
|
272
|
-
// version: '0.8.
|
|
276
|
+
// version: '0.8.7',
|
|
273
277
|
// isCanonical: false,
|
|
274
278
|
// isArchival: false,
|
|
275
279
|
// previewBudget: { MAX_FRAMES: 30, MAX_TOTAL_TIME_MS: 500, FRAME_STRIDE: 3 },
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nexart/ui-renderer
|
|
3
|
-
* Version: 0.8.
|
|
3
|
+
* Version: 0.8.6
|
|
4
4
|
*
|
|
5
5
|
* Lightweight Preview Runtime for NexArt Protocol
|
|
6
6
|
*
|
|
@@ -40,13 +40,14 @@ export { compilePrimitive } from './presets/primitives';
|
|
|
40
40
|
export { wrapSketch, validateSketchSafety } from './presets/sketch-wrapper';
|
|
41
41
|
export { getCapabilities, getPrimitiveTypes, getPrimitivesByCategory, getPrimitiveInfo, isPrimitiveValid, getMotionSources, getBackgroundTextures, } from './capabilities';
|
|
42
42
|
export { createPreviewEngine, renderStaticPreview, stopActivePreview, } from './preview/preview-engine';
|
|
43
|
-
export {
|
|
44
|
-
export {
|
|
43
|
+
export { PREVIEW_FPS, CANVAS_LIMITS, type RuntimeProfile, type PreviewMode, type PreviewEngineConfig, type PreviewRenderResult, type PreviewRenderer, type FpsThrottleState, } from './preview/preview-types';
|
|
44
|
+
export { type FrameBudgetState } from './preview/frame-budget';
|
|
45
|
+
export { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, createFrameBudget, canRenderFrame, resetBudget, shouldSkipFrame, getElapsedMs, } from './preview/frame-budget';
|
|
45
46
|
export { calculateScaledDimensions, applyScaledDimensions, type ScaledDimensions, } from './preview/canvas-scaler';
|
|
46
47
|
export type { NexArtSystemInput, NexArtSystem, DeclarativeSystemInput, DeclarativeSystem, CodeSystem, NexArtCodeSystem, UnifiedSystemInput, UnifiedSystem, UnifiedElement, BackgroundElement, PrimitiveElement, SketchElement, BackgroundPreset, PrimitiveName, ColorPalette, MotionSpeed, StrokeWeightAuto, LoopConfig, DeclarativeElement, SystemElement, DotsElement, LinesElement, WavesElement, GridElement, FlowFieldElement, OrbitsElement, BackgroundConfig, MotionConfig, PreviewOptions, ValidationResult, } from './types';
|
|
47
48
|
export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
|
|
48
49
|
export type { Capabilities, PrimitiveCapability, ParameterSpec, } from './capabilities';
|
|
49
|
-
export declare const SDK_VERSION = "0.8.
|
|
50
|
+
export declare const SDK_VERSION = "0.8.6";
|
|
50
51
|
export declare const PROTOCOL_VERSION = "0.8";
|
|
51
52
|
export declare const IS_CANONICAL = false;
|
|
52
53
|
export declare const IS_ARCHIVAL = false;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,WAAW,EACX,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EAEb,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,eAAe,EACf,YAAY,GACb,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,kBAAkB,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE9E,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,eAAO,MAAM,WAAW,UAAU,CAAC;AACnC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AACtC,eAAO,MAAM,YAAY,QAAQ,CAAC;AAClC,eAAO,MAAM,WAAW,QAAQ,CAAC;AACjC,eAAO,MAAM,QAAQ,wBAAwB,CAAC;AAC9C,eAAO,MAAM,wBAAwB,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nexart/ui-renderer
|
|
3
|
-
* Version: 0.8.
|
|
3
|
+
* Version: 0.8.6
|
|
4
4
|
*
|
|
5
5
|
* Lightweight Preview Runtime for NexArt Protocol
|
|
6
6
|
*
|
|
@@ -40,11 +40,13 @@ export { compilePrimitive } from './presets/primitives';
|
|
|
40
40
|
export { wrapSketch, validateSketchSafety } from './presets/sketch-wrapper';
|
|
41
41
|
export { getCapabilities, getPrimitiveTypes, getPrimitivesByCategory, getPrimitiveInfo, isPrimitiveValid, getMotionSources, getBackgroundTextures, } from './capabilities';
|
|
42
42
|
export { createPreviewEngine, renderStaticPreview, stopActivePreview, } from './preview/preview-engine';
|
|
43
|
-
export {
|
|
44
|
-
export {
|
|
43
|
+
export { PREVIEW_FPS, CANVAS_LIMITS, } from './preview/preview-types';
|
|
44
|
+
export { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle,
|
|
45
|
+
// Deprecated exports for backward compatibility (will be removed in v0.9.0)
|
|
46
|
+
createFrameBudget, canRenderFrame, resetBudget, shouldSkipFrame, getElapsedMs, } from './preview/frame-budget';
|
|
45
47
|
export { calculateScaledDimensions, applyScaledDimensions, } from './preview/canvas-scaler';
|
|
46
48
|
export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
|
|
47
|
-
export const SDK_VERSION = '0.8.
|
|
49
|
+
export const SDK_VERSION = '0.8.6';
|
|
48
50
|
export const PROTOCOL_VERSION = '0.8';
|
|
49
51
|
export const IS_CANONICAL = false;
|
|
50
52
|
export const IS_ARCHIVAL = false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.7 - Code Mode Renderer
|
|
3
3
|
*
|
|
4
4
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
5
|
* ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
|
|
@@ -7,11 +7,8 @@
|
|
|
7
7
|
* ║ This renderer is a preview-only runtime. ║
|
|
8
8
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
9
|
* ║ ║
|
|
10
|
-
* ║ Performance
|
|
11
|
-
* ║
|
|
12
|
-
* ║ - Max total time: 500ms ║
|
|
13
|
-
* ║ - Max canvas dimension: 900px ║
|
|
14
|
-
* ║ - Frame stride: render every 3rd frame ║
|
|
10
|
+
* ║ Performance: FPS-capped at 8 FPS, canvas max 900px ║
|
|
11
|
+
* ║ Animation: Runs continuously until stop() is called ║
|
|
15
12
|
* ║ ║
|
|
16
13
|
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
17
14
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"code-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/code-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAwBjE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AAwBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,YAAY,CAoTd"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.7 - Code Mode Renderer
|
|
3
3
|
*
|
|
4
4
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
5
|
* ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
|
|
@@ -7,17 +7,14 @@
|
|
|
7
7
|
* ║ This renderer is a preview-only runtime. ║
|
|
8
8
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
9
|
* ║ ║
|
|
10
|
-
* ║ Performance
|
|
11
|
-
* ║
|
|
12
|
-
* ║ - Max total time: 500ms ║
|
|
13
|
-
* ║ - Max canvas dimension: 900px ║
|
|
14
|
-
* ║ - Frame stride: render every 3rd frame ║
|
|
10
|
+
* ║ Performance: FPS-capped at 8 FPS, canvas max 900px ║
|
|
11
|
+
* ║ Animation: Runs continuously until stop() is called ║
|
|
15
12
|
* ║ ║
|
|
16
13
|
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
17
14
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
18
15
|
*/
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
16
|
+
import { PREVIEW_FPS, CANVAS_LIMITS, } from './preview-types';
|
|
17
|
+
import { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, } from './frame-budget';
|
|
21
18
|
import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, clearCanvasIgnoringTransform, } from './canvas-scaler';
|
|
22
19
|
import { createPreviewRuntime } from './preview-runtime';
|
|
23
20
|
const PROTOCOL_VERSION = '1.2.0';
|
|
@@ -45,8 +42,8 @@ function normalizeVars(vars) {
|
|
|
45
42
|
return result;
|
|
46
43
|
}
|
|
47
44
|
export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
48
|
-
console.log('[UIRenderer] Preview mode →
|
|
49
|
-
console.log(`[UIRenderer]
|
|
45
|
+
console.log('[UIRenderer] Preview mode → FPS-capped continuous animation');
|
|
46
|
+
console.log(`[UIRenderer] Target: ${PREVIEW_FPS.TARGET_FPS} FPS, canvas max ${CANVAS_LIMITS.MAX_DIMENSION}px`);
|
|
50
47
|
if (activeRendererInstance) {
|
|
51
48
|
activeRendererInstance.destroy();
|
|
52
49
|
activeRendererInstance = null;
|
|
@@ -64,7 +61,7 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
64
61
|
let animationId = null;
|
|
65
62
|
let isRunning = false;
|
|
66
63
|
let isDestroyed = false;
|
|
67
|
-
const
|
|
64
|
+
const throttle = createFpsThrottle();
|
|
68
65
|
const normalizedVars = normalizeVars(system.vars);
|
|
69
66
|
let runtime = null;
|
|
70
67
|
let setupFn = null;
|
|
@@ -88,19 +85,68 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
88
85
|
const totalFrames = system.totalFrames ?? 120;
|
|
89
86
|
runtime.totalFrames = totalFrames;
|
|
90
87
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
89
|
+
// ║ LIVE RUNTIME BINDING — v0.8.7 FIX ║
|
|
90
|
+
// ║ ║
|
|
91
|
+
// ║ Time-varying properties (frameCount, t, time, tGlobal, totalFrames) ║
|
|
92
|
+
// ║ must be accessed via getters that read from the live runtime object. ║
|
|
93
|
+
// ║ ║
|
|
94
|
+
// ║ We use a Proxy + with() pattern to enable bare-name access to live ║
|
|
95
|
+
// ║ runtime properties. The proxy's get trap reads from runtime for ║
|
|
96
|
+
// ║ time-varying props, and from a static cache for everything else. ║
|
|
97
|
+
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
98
|
+
// Time-varying properties that need live access
|
|
99
|
+
const liveProps = new Set(['frameCount', 't', 'time', 'tGlobal', 'totalFrames']);
|
|
100
|
+
// Cache static properties (functions, constants)
|
|
101
|
+
const staticCache = {};
|
|
102
|
+
for (const key of Object.keys(runtime)) {
|
|
103
|
+
if (!liveProps.has(key)) {
|
|
104
|
+
staticCache[key] = runtime[key];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
98
107
|
const registerSetup = (fn) => { setupFn = fn; };
|
|
99
108
|
const registerDraw = (fn) => { drawFn = fn; };
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
// Build the set of all known keys (for has trap)
|
|
110
|
+
const knownKeys = new Set([
|
|
111
|
+
...liveProps,
|
|
112
|
+
...Object.keys(staticCache),
|
|
113
|
+
'__registerSetup',
|
|
114
|
+
'__registerDraw'
|
|
115
|
+
]);
|
|
116
|
+
// Create a proxy that provides live access to time-varying properties
|
|
117
|
+
// IMPORTANT: has() must return true ONLY for known keys
|
|
118
|
+
// Otherwise globals (Math, window, etc.) are masked and become undefined
|
|
119
|
+
const runtimeRef = runtime;
|
|
120
|
+
const scope = new Proxy({}, {
|
|
121
|
+
has: (_, prop) => knownKeys.has(prop),
|
|
122
|
+
get: (_, prop) => {
|
|
123
|
+
// Time-varying properties - read live from runtime
|
|
124
|
+
if (liveProps.has(prop)) {
|
|
125
|
+
return runtimeRef[prop];
|
|
126
|
+
}
|
|
127
|
+
// Special functions
|
|
128
|
+
if (prop === '__registerSetup')
|
|
129
|
+
return registerSetup;
|
|
130
|
+
if (prop === '__registerDraw')
|
|
131
|
+
return registerDraw;
|
|
132
|
+
// Static properties from cache
|
|
133
|
+
if (prop in staticCache)
|
|
134
|
+
return staticCache[prop];
|
|
135
|
+
// Fall through to undefined (should not hit this if has() is correct)
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// Use with() to make the proxy scope available to bare variable names
|
|
140
|
+
// Note: with() is safe here since we control the scope completely
|
|
141
|
+
const wrappedSource = `
|
|
142
|
+
with (__scope) {
|
|
143
|
+
${system.source}
|
|
144
|
+
if (typeof setup === 'function') __registerSetup(setup);
|
|
145
|
+
if (typeof draw === 'function') __registerDraw(draw);
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
const fn = new Function('__scope', wrappedSource);
|
|
149
|
+
fn(scope);
|
|
104
150
|
}
|
|
105
151
|
catch (error) {
|
|
106
152
|
console.warn('[UIRenderer] Compile error:', error);
|
|
@@ -171,32 +217,27 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
171
217
|
if (setupFn) {
|
|
172
218
|
setupFn();
|
|
173
219
|
}
|
|
174
|
-
|
|
220
|
+
resetThrottle(throttle);
|
|
175
221
|
isRunning = true;
|
|
176
222
|
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
177
|
-
// ║ ANIMATION LOOP
|
|
223
|
+
// ║ ANIMATION LOOP — CONTINUOUS FPS-CAPPED RENDERING ║
|
|
178
224
|
// ║ ║
|
|
179
|
-
// ║ requestAnimationFrame
|
|
180
|
-
// ║
|
|
181
|
-
// ║
|
|
182
|
-
// ║ Canvas
|
|
183
|
-
// ║ Looping uses modulo math (t = frame % total), not counter resets. ║
|
|
225
|
+
// ║ requestAnimationFrame runs continuously until stop() is called. ║
|
|
226
|
+
// ║ FPS throttle (8 FPS) prevents excessive CPU usage. ║
|
|
227
|
+
// ║ Looping uses modulo math: t = (frame % total) / total ║
|
|
228
|
+
// ║ Canvas cleared before each draw; skipped frames preserve pixels. ║
|
|
184
229
|
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
185
230
|
const loop = () => {
|
|
186
|
-
//
|
|
231
|
+
// Schedule next frame first — loop runs until stop()
|
|
187
232
|
animationId = requestAnimationFrame(loop);
|
|
188
|
-
//
|
|
233
|
+
// Exit if stopped or destroyed
|
|
189
234
|
if (!isRunning || isDestroyed)
|
|
190
235
|
return;
|
|
191
|
-
//
|
|
192
|
-
if (!
|
|
193
|
-
return; // Preserve current canvas
|
|
236
|
+
// FPS throttle — skip if not enough time has passed
|
|
237
|
+
if (!shouldRenderFrame(throttle)) {
|
|
238
|
+
return; // Preserve current canvas
|
|
194
239
|
}
|
|
195
240
|
frameCount++;
|
|
196
|
-
// Frame stride check — skip some frames for performance
|
|
197
|
-
if (shouldSkipFrame(frameCount)) {
|
|
198
|
-
return; // Skip but don't clear — preserve last drawn frame
|
|
199
|
-
}
|
|
200
241
|
// Update runtime timing using modulo for natural looping
|
|
201
242
|
if (runtime) {
|
|
202
243
|
runtime.frameCount = frameCount;
|
|
@@ -205,12 +246,12 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
205
246
|
runtime.tGlobal = runtime.t;
|
|
206
247
|
}
|
|
207
248
|
try {
|
|
208
|
-
// Clear
|
|
249
|
+
// Clear and draw
|
|
209
250
|
clearCanvasIgnoringTransform(ctx, canvas);
|
|
210
251
|
if (drawFn)
|
|
211
252
|
drawFn();
|
|
212
253
|
drawBadge();
|
|
213
|
-
recordFrame(
|
|
254
|
+
recordFrame(throttle);
|
|
214
255
|
}
|
|
215
256
|
catch (error) {
|
|
216
257
|
console.warn('[UIRenderer] Draw error:', error);
|
|
@@ -1,43 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer -
|
|
2
|
+
* @nexart/ui-renderer - FPS Throttle
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Simple FPS cap for continuous preview animation.
|
|
5
|
+
* No frame limits, no time limits — just throttled rendering.
|
|
5
6
|
*
|
|
6
7
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
7
|
-
* ║
|
|
8
|
+
* ║ FPS THROTTLE — CONTINUOUS ANIMATION ║
|
|
8
9
|
* ║ ║
|
|
9
|
-
* ║
|
|
10
|
-
* ║
|
|
11
|
-
* ║
|
|
12
|
-
* ║ ║
|
|
13
|
-
* ║ If limits exceeded: stop immediately, do NOT throw. ║
|
|
10
|
+
* ║ Target FPS: 8 (125ms per frame) ║
|
|
11
|
+
* ║ No frame limit — runs until stop() is called ║
|
|
12
|
+
* ║ No time limit — runs indefinitely ║
|
|
14
13
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
15
14
|
*/
|
|
16
|
-
import { type
|
|
15
|
+
import { type FpsThrottleState } from './preview-types';
|
|
17
16
|
/**
|
|
18
|
-
* Create a new
|
|
17
|
+
* Create a new FPS throttle state
|
|
19
18
|
*/
|
|
20
|
-
export declare function
|
|
19
|
+
export declare function createFpsThrottle(): FpsThrottleState;
|
|
21
20
|
/**
|
|
22
|
-
* Check if
|
|
23
|
-
* Returns true if we
|
|
21
|
+
* Check if enough time has passed to render the next frame.
|
|
22
|
+
* Returns true if we should render, false if we should skip this RAF tick.
|
|
24
23
|
*/
|
|
25
|
-
export declare function
|
|
24
|
+
export declare function shouldRenderFrame(throttle: FpsThrottleState): boolean;
|
|
26
25
|
/**
|
|
27
|
-
* Record that a frame was rendered
|
|
26
|
+
* Record that a frame was rendered (updates last frame time)
|
|
28
27
|
*/
|
|
29
|
-
export declare function recordFrame(
|
|
28
|
+
export declare function recordFrame(throttle: FpsThrottleState): void;
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
30
|
+
* Reset the throttle for a new render session
|
|
32
31
|
*/
|
|
33
|
-
export declare function
|
|
32
|
+
export declare function resetThrottle(throttle: FpsThrottleState): void;
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
34
|
+
* @deprecated Legacy type alias for backward compatibility.
|
|
35
|
+
* Use FpsThrottleState instead.
|
|
36
36
|
*/
|
|
37
|
-
export
|
|
37
|
+
export type FrameBudgetState = FpsThrottleState;
|
|
38
|
+
/** @deprecated Use createFpsThrottle instead */
|
|
39
|
+
export declare function createFrameBudget(): FpsThrottleState;
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
+
* @deprecated Use shouldRenderFrame instead.
|
|
42
|
+
*
|
|
43
|
+
* COMPATIBILITY NOTE: This now delegates to shouldRenderFrame.
|
|
44
|
+
* For legacy code that interprets `false` as "budget exhausted forever",
|
|
45
|
+
* this will work correctly because:
|
|
46
|
+
* - Returns true when enough time has passed (ready to render)
|
|
47
|
+
* - Returns false when throttled (wait for next tick)
|
|
48
|
+
* - Never permanently exhausts like the old budget system
|
|
41
49
|
*/
|
|
42
|
-
export declare function
|
|
50
|
+
export declare function canRenderFrame(throttle: FpsThrottleState): boolean;
|
|
51
|
+
/** @deprecated Use resetThrottle instead */
|
|
52
|
+
export declare function resetBudget(throttle: FpsThrottleState): void;
|
|
53
|
+
/** @deprecated No longer used — removed frame stride. Always returns false. */
|
|
54
|
+
export declare function shouldSkipFrame(_frameCount: number): boolean;
|
|
55
|
+
/** @deprecated No longer tracked. Returns 0. */
|
|
56
|
+
export declare function getElapsedMs(_throttle: FpsThrottleState): number;
|
|
43
57
|
//# sourceMappingURL=frame-budget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frame-budget.d.ts","sourceRoot":"","sources":["../../src/preview/frame-budget.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frame-budget.d.ts","sourceRoot":"","sources":["../../src/preview/frame-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAErE;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,CAIpD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAIrE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAE9D;AAOD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAEhD,gDAAgD;AAChD,wBAAgB,iBAAiB,IAAI,gBAAgB,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAElE;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAE5D;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM,CAEhE"}
|
|
@@ -1,78 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer -
|
|
2
|
+
* @nexart/ui-renderer - FPS Throttle
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Simple FPS cap for continuous preview animation.
|
|
5
|
+
* No frame limits, no time limits — just throttled rendering.
|
|
5
6
|
*
|
|
6
7
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
7
|
-
* ║
|
|
8
|
+
* ║ FPS THROTTLE — CONTINUOUS ANIMATION ║
|
|
8
9
|
* ║ ║
|
|
9
|
-
* ║
|
|
10
|
-
* ║
|
|
11
|
-
* ║
|
|
12
|
-
* ║ ║
|
|
13
|
-
* ║ If limits exceeded: stop immediately, do NOT throw. ║
|
|
10
|
+
* ║ Target FPS: 8 (125ms per frame) ║
|
|
11
|
+
* ║ No frame limit — runs until stop() is called ║
|
|
12
|
+
* ║ No time limit — runs indefinitely ║
|
|
14
13
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
15
14
|
*/
|
|
16
|
-
import {
|
|
15
|
+
import { PREVIEW_FPS } from './preview-types';
|
|
17
16
|
/**
|
|
18
|
-
* Create a new
|
|
17
|
+
* Create a new FPS throttle state
|
|
19
18
|
*/
|
|
20
|
-
export function
|
|
19
|
+
export function createFpsThrottle() {
|
|
21
20
|
return {
|
|
22
|
-
|
|
23
|
-
startTimeMs: performance.now(),
|
|
24
|
-
exhausted: false,
|
|
25
|
-
exhaustionReason: undefined,
|
|
21
|
+
lastFrameTimeMs: 0,
|
|
26
22
|
};
|
|
27
23
|
}
|
|
28
24
|
/**
|
|
29
|
-
* Check if
|
|
30
|
-
* Returns true if we
|
|
25
|
+
* Check if enough time has passed to render the next frame.
|
|
26
|
+
* Returns true if we should render, false if we should skip this RAF tick.
|
|
31
27
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Check frame limit
|
|
37
|
-
if (budget.framesRendered >= PREVIEW_BUDGET.MAX_FRAMES) {
|
|
38
|
-
budget.exhausted = true;
|
|
39
|
-
budget.exhaustionReason = 'frame_limit';
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
// Check time limit
|
|
43
|
-
const elapsedMs = performance.now() - budget.startTimeMs;
|
|
44
|
-
if (elapsedMs >= PREVIEW_BUDGET.MAX_TOTAL_TIME_MS) {
|
|
45
|
-
budget.exhausted = true;
|
|
46
|
-
budget.exhaustionReason = 'time_limit';
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
28
|
+
export function shouldRenderFrame(throttle) {
|
|
29
|
+
const now = performance.now();
|
|
30
|
+
const elapsed = now - throttle.lastFrameTimeMs;
|
|
31
|
+
return elapsed >= PREVIEW_FPS.FRAME_INTERVAL_MS;
|
|
50
32
|
}
|
|
51
33
|
/**
|
|
52
|
-
* Record that a frame was rendered
|
|
34
|
+
* Record that a frame was rendered (updates last frame time)
|
|
53
35
|
*/
|
|
54
|
-
export function recordFrame(
|
|
55
|
-
|
|
36
|
+
export function recordFrame(throttle) {
|
|
37
|
+
throttle.lastFrameTimeMs = performance.now();
|
|
56
38
|
}
|
|
57
39
|
/**
|
|
58
|
-
*
|
|
40
|
+
* Reset the throttle for a new render session
|
|
59
41
|
*/
|
|
60
|
-
export function
|
|
61
|
-
|
|
42
|
+
export function resetThrottle(throttle) {
|
|
43
|
+
throttle.lastFrameTimeMs = 0;
|
|
62
44
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export function resetBudget(budget) {
|
|
67
|
-
budget.framesRendered = 0;
|
|
68
|
-
budget.startTimeMs = performance.now();
|
|
69
|
-
budget.exhausted = false;
|
|
70
|
-
budget.exhaustionReason = undefined;
|
|
45
|
+
/** @deprecated Use createFpsThrottle instead */
|
|
46
|
+
export function createFrameBudget() {
|
|
47
|
+
return createFpsThrottle();
|
|
71
48
|
}
|
|
72
49
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
50
|
+
* @deprecated Use shouldRenderFrame instead.
|
|
51
|
+
*
|
|
52
|
+
* COMPATIBILITY NOTE: This now delegates to shouldRenderFrame.
|
|
53
|
+
* For legacy code that interprets `false` as "budget exhausted forever",
|
|
54
|
+
* this will work correctly because:
|
|
55
|
+
* - Returns true when enough time has passed (ready to render)
|
|
56
|
+
* - Returns false when throttled (wait for next tick)
|
|
57
|
+
* - Never permanently exhausts like the old budget system
|
|
75
58
|
*/
|
|
76
|
-
export function
|
|
77
|
-
return
|
|
59
|
+
export function canRenderFrame(throttle) {
|
|
60
|
+
return shouldRenderFrame(throttle);
|
|
61
|
+
}
|
|
62
|
+
/** @deprecated Use resetThrottle instead */
|
|
63
|
+
export function resetBudget(throttle) {
|
|
64
|
+
resetThrottle(throttle);
|
|
65
|
+
}
|
|
66
|
+
/** @deprecated No longer used — removed frame stride. Always returns false. */
|
|
67
|
+
export function shouldSkipFrame(_frameCount) {
|
|
68
|
+
return false; // Never skip based on frame count
|
|
69
|
+
}
|
|
70
|
+
/** @deprecated No longer tracked. Returns 0. */
|
|
71
|
+
export function getElapsedMs(_throttle) {
|
|
72
|
+
return 0;
|
|
78
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAErB,MAAM,iBAAiB,CAAC;AAiNzB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAEhF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAKpF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
25
25
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
26
26
|
*/
|
|
27
|
-
import {
|
|
27
|
+
import { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, } from './frame-budget';
|
|
28
28
|
import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, } from './canvas-scaler';
|
|
29
29
|
import { createPreviewRuntime } from './preview-runtime';
|
|
30
30
|
let activePreviewRenderer = null;
|
|
@@ -34,7 +34,7 @@ class PreviewEngine {
|
|
|
34
34
|
this.setupFn = null;
|
|
35
35
|
this.drawFn = null;
|
|
36
36
|
this.animationFrameId = null;
|
|
37
|
-
this.
|
|
37
|
+
this.throttle = createFpsThrottle();
|
|
38
38
|
this.running = false;
|
|
39
39
|
this.internalFrameCount = 0;
|
|
40
40
|
this.isCanonical = false;
|
|
@@ -87,16 +87,15 @@ class PreviewEngine {
|
|
|
87
87
|
return () => fn(...globalValues);
|
|
88
88
|
}
|
|
89
89
|
renderStatic() {
|
|
90
|
-
|
|
90
|
+
const startTime = performance.now();
|
|
91
91
|
try {
|
|
92
92
|
if (this.setupFn) {
|
|
93
93
|
this.setupFn();
|
|
94
94
|
}
|
|
95
|
-
recordFrame(this.budget);
|
|
96
95
|
return {
|
|
97
96
|
success: true,
|
|
98
97
|
framesRendered: 1,
|
|
99
|
-
executionTimeMs:
|
|
98
|
+
executionTimeMs: performance.now() - startTime,
|
|
100
99
|
terminatedEarly: false,
|
|
101
100
|
};
|
|
102
101
|
}
|
|
@@ -105,7 +104,7 @@ class PreviewEngine {
|
|
|
105
104
|
return {
|
|
106
105
|
success: false,
|
|
107
106
|
framesRendered: 0,
|
|
108
|
-
executionTimeMs:
|
|
107
|
+
executionTimeMs: performance.now() - startTime,
|
|
109
108
|
terminatedEarly: true,
|
|
110
109
|
terminationReason: 'error',
|
|
111
110
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
@@ -120,7 +119,7 @@ class PreviewEngine {
|
|
|
120
119
|
}
|
|
121
120
|
activePreviewRenderer = this;
|
|
122
121
|
this.running = true;
|
|
123
|
-
|
|
122
|
+
resetThrottle(this.throttle);
|
|
124
123
|
this.internalFrameCount = 0;
|
|
125
124
|
try {
|
|
126
125
|
if (this.setupFn) {
|
|
@@ -133,30 +132,24 @@ class PreviewEngine {
|
|
|
133
132
|
this.scheduleNextFrame();
|
|
134
133
|
}
|
|
135
134
|
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
136
|
-
// ║ ANIMATION LOOP
|
|
135
|
+
// ║ ANIMATION LOOP — CONTINUOUS FPS-CAPPED RENDERING ║
|
|
137
136
|
// ║ ║
|
|
138
|
-
// ║ requestAnimationFrame
|
|
139
|
-
// ║
|
|
140
|
-
// ║
|
|
141
|
-
// ║ Canvas only changes when draw() runs — skipped frames keep pixels. ║
|
|
142
|
-
// ║ Looping uses modulo math (t = frame % total), not counter resets. ║
|
|
137
|
+
// ║ requestAnimationFrame runs continuously until stop() is called. ║
|
|
138
|
+
// ║ FPS throttle (8 FPS) prevents excessive CPU usage. ║
|
|
139
|
+
// ║ Looping uses modulo math: t = (frame % total) / total ║
|
|
143
140
|
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
144
141
|
scheduleNextFrame() {
|
|
145
|
-
//
|
|
142
|
+
// Schedule next frame — loop runs until stopLoop()
|
|
146
143
|
this.animationFrameId = requestAnimationFrame(() => {
|
|
147
144
|
this.scheduleNextFrame();
|
|
148
|
-
//
|
|
145
|
+
// Exit if stopped
|
|
149
146
|
if (!this.running)
|
|
150
147
|
return;
|
|
151
|
-
//
|
|
152
|
-
if (!
|
|
153
|
-
return; // Preserve current canvas
|
|
148
|
+
// FPS throttle — skip if not enough time has passed
|
|
149
|
+
if (!shouldRenderFrame(this.throttle)) {
|
|
150
|
+
return; // Preserve current canvas
|
|
154
151
|
}
|
|
155
152
|
this.internalFrameCount++;
|
|
156
|
-
// Frame stride check — skip some frames for performance
|
|
157
|
-
if (shouldSkipFrame(this.internalFrameCount)) {
|
|
158
|
-
return; // Skip but don't clear — preserve last drawn frame
|
|
159
|
-
}
|
|
160
153
|
// Update runtime timing using modulo for natural looping
|
|
161
154
|
try {
|
|
162
155
|
if (this.runtime) {
|
|
@@ -169,7 +162,7 @@ class PreviewEngine {
|
|
|
169
162
|
if (this.drawFn) {
|
|
170
163
|
this.drawFn();
|
|
171
164
|
}
|
|
172
|
-
recordFrame(this.
|
|
165
|
+
recordFrame(this.throttle);
|
|
173
166
|
}
|
|
174
167
|
catch (error) {
|
|
175
168
|
console.warn('[PreviewEngine] Draw error:', error);
|
|
@@ -16,18 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export type RuntimeProfile = 'preview';
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* FPS cap for preview rendering.
|
|
20
|
+
* Replaced time/frame budget with simple FPS throttling for continuous animation.
|
|
21
21
|
*/
|
|
22
|
-
export declare const
|
|
23
|
-
/**
|
|
24
|
-
readonly
|
|
25
|
-
/**
|
|
26
|
-
readonly
|
|
27
|
-
/** Target frame time (best effort, ~60fps) */
|
|
28
|
-
readonly TARGET_FRAME_TIME_MS: 16;
|
|
29
|
-
/** Frame stride for performance (render every Nth frame) */
|
|
30
|
-
readonly FRAME_STRIDE: 3;
|
|
22
|
+
export declare const PREVIEW_FPS: {
|
|
23
|
+
/** Target FPS for loop animations (lower = better performance) */
|
|
24
|
+
readonly TARGET_FPS: 8;
|
|
25
|
+
/** Minimum milliseconds between frames */
|
|
26
|
+
readonly FRAME_INTERVAL_MS: 125;
|
|
31
27
|
};
|
|
32
28
|
/**
|
|
33
29
|
* Canvas resolution limits.
|
|
@@ -101,16 +97,10 @@ export interface PreviewRenderer {
|
|
|
101
97
|
readonly isArchival: false;
|
|
102
98
|
}
|
|
103
99
|
/**
|
|
104
|
-
*
|
|
100
|
+
* FPS throttle state for continuous animation
|
|
105
101
|
*/
|
|
106
|
-
export interface
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
/** Start time of execution */
|
|
110
|
-
startTimeMs: number;
|
|
111
|
-
/** Whether budget is exhausted */
|
|
112
|
-
exhausted: boolean;
|
|
113
|
-
/** Reason for exhaustion */
|
|
114
|
-
exhaustionReason?: 'frame_limit' | 'time_limit';
|
|
102
|
+
export interface FpsThrottleState {
|
|
103
|
+
/** Timestamp of last rendered frame */
|
|
104
|
+
lastFrameTimeMs: number;
|
|
115
105
|
}
|
|
116
106
|
//# sourceMappingURL=preview-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-types.d.ts","sourceRoot":"","sources":["../../src/preview/preview-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"preview-types.d.ts","sourceRoot":"","sources":["../../src/preview/preview-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,WAAW;IACtB,kEAAkE;;IAElE,0CAA0C;;CAElC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB,2CAA2C;;IAE3C,wBAAwB;;CAEhB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,eAAe,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,iBAAiB,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IACzE,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,YAAY,EAAE,MAAM,mBAAmB,CAAC;IACxC,2BAA2B;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,oCAAoC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,mCAAmC;IACnC,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,qCAAqC;IACrC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,eAAe,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -11,18 +11,14 @@
|
|
|
11
11
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* FPS cap for preview rendering.
|
|
15
|
+
* Replaced time/frame budget with simple FPS throttling for continuous animation.
|
|
16
16
|
*/
|
|
17
|
-
export const
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
/** Target frame time (best effort, ~60fps) */
|
|
23
|
-
TARGET_FRAME_TIME_MS: 16,
|
|
24
|
-
/** Frame stride for performance (render every Nth frame) */
|
|
25
|
-
FRAME_STRIDE: 3,
|
|
17
|
+
export const PREVIEW_FPS = {
|
|
18
|
+
/** Target FPS for loop animations (lower = better performance) */
|
|
19
|
+
TARGET_FPS: 8,
|
|
20
|
+
/** Minimum milliseconds between frames */
|
|
21
|
+
FRAME_INTERVAL_MS: 125, // 1000 / 8 = 125ms per frame
|
|
26
22
|
};
|
|
27
23
|
/**
|
|
28
24
|
* Canvas resolution limits.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unified-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/unified-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,cAAc,EAAoE,MAAM,UAAU,CAAC;AAmBhJ,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AA6ED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,
|
|
1
|
+
{"version":3,"file":"unified-renderer.d.ts","sourceRoot":"","sources":["../../src/preview/unified-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,cAAc,EAAoE,MAAM,UAAU,CAAC;AAmBhJ,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC;IACnB,UAAU,EAAE,KAAK,CAAC;CACnB;AA6ED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CA4QjB"}
|
|
@@ -8,8 +8,8 @@ import { compilePrimitive } from '../presets/primitives';
|
|
|
8
8
|
import { wrapSketch, validateSketchSafety } from '../presets/sketch-wrapper';
|
|
9
9
|
import { createPreviewRuntime } from './preview-runtime';
|
|
10
10
|
import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, clearCanvasIgnoringTransform } from './canvas-scaler';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle } from './frame-budget';
|
|
12
|
+
import { PREVIEW_FPS, CANVAS_LIMITS } from './preview-types';
|
|
13
13
|
function validateSketchElement(element) {
|
|
14
14
|
const { safe, warnings } = validateSketchSafety(element.code);
|
|
15
15
|
if (!safe) {
|
|
@@ -81,8 +81,8 @@ function compileSketchElement(element) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
84
|
-
console.log('[UIRenderer] Unified preview →
|
|
85
|
-
console.log(`[UIRenderer]
|
|
84
|
+
console.log('[UIRenderer] Unified preview → FPS-capped continuous animation');
|
|
85
|
+
console.log(`[UIRenderer] Target: ${PREVIEW_FPS.TARGET_FPS} FPS, canvas max ${CANVAS_LIMITS.MAX_DIMENSION}px`);
|
|
86
86
|
const { showBadge = true, onPreview, onComplete, onError } = options;
|
|
87
87
|
const scaled = calculateScaledDimensions(system.width, system.height);
|
|
88
88
|
if (scaled.wasScaled) {
|
|
@@ -96,7 +96,7 @@ export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
|
96
96
|
let animationId = null;
|
|
97
97
|
let isRunning = false;
|
|
98
98
|
let isDestroyed = false;
|
|
99
|
-
const
|
|
99
|
+
const throttle = createFpsThrottle();
|
|
100
100
|
const palette = inferPalette(system.elements);
|
|
101
101
|
const colors = getPaletteColors(palette);
|
|
102
102
|
const totalFrames = system.loop?.duration ?? 120;
|
|
@@ -235,32 +235,26 @@ export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
|
235
235
|
scaled.originalHeight, // ← Protocol dimension
|
|
236
236
|
system.seed);
|
|
237
237
|
runSetup(p);
|
|
238
|
-
|
|
238
|
+
resetThrottle(throttle);
|
|
239
239
|
isRunning = true;
|
|
240
240
|
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
241
|
-
// ║ ANIMATION LOOP
|
|
241
|
+
// ║ ANIMATION LOOP — CONTINUOUS FPS-CAPPED RENDERING ║
|
|
242
242
|
// ║ ║
|
|
243
|
-
// ║ requestAnimationFrame
|
|
244
|
-
// ║
|
|
245
|
-
// ║
|
|
246
|
-
// ║ Canvas only changes when draw() runs — skipped frames keep pixels. ║
|
|
247
|
-
// ║ Looping uses modulo math (t = frame % total), not counter resets. ║
|
|
243
|
+
// ║ requestAnimationFrame runs continuously until stop() is called. ║
|
|
244
|
+
// ║ FPS throttle (8 FPS) prevents excessive CPU usage. ║
|
|
245
|
+
// ║ Looping uses modulo math: t = (frame % total) / total ║
|
|
248
246
|
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
249
247
|
const loop = () => {
|
|
250
|
-
//
|
|
248
|
+
// Schedule next frame — loop runs until stop()
|
|
251
249
|
animationId = requestAnimationFrame(loop);
|
|
252
|
-
//
|
|
250
|
+
// Exit if stopped or destroyed
|
|
253
251
|
if (!isRunning || isDestroyed)
|
|
254
252
|
return;
|
|
255
|
-
//
|
|
256
|
-
if (!
|
|
257
|
-
return; // Preserve current canvas
|
|
253
|
+
// FPS throttle — skip if not enough time has passed
|
|
254
|
+
if (!shouldRenderFrame(throttle)) {
|
|
255
|
+
return; // Preserve current canvas
|
|
258
256
|
}
|
|
259
257
|
frameCount++;
|
|
260
|
-
// Frame stride check — skip some frames for performance
|
|
261
|
-
if (shouldSkipFrame(frameCount)) {
|
|
262
|
-
return; // Skip but don't clear — preserve last drawn frame
|
|
263
|
-
}
|
|
264
258
|
// Calculate t using modulo for natural looping
|
|
265
259
|
const t = (frameCount % totalFrames) / totalFrames;
|
|
266
260
|
p.randomSeed(system.seed);
|
|
@@ -268,7 +262,7 @@ export function renderUnifiedSystem(system, canvas, options = {}) {
|
|
|
268
262
|
try {
|
|
269
263
|
runDraw(p, frameCount, t);
|
|
270
264
|
drawBadge();
|
|
271
|
-
recordFrame(
|
|
265
|
+
recordFrame(throttle);
|
|
272
266
|
}
|
|
273
267
|
catch (error) {
|
|
274
268
|
console.warn('[UIRenderer] Draw error:', error);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.6 - Type Definitions
|
|
3
3
|
*
|
|
4
4
|
* Lightweight Preview Runtime for NexArt Protocol.
|
|
5
5
|
* This SDK is non-canonical and for preview only.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Max total time: 500ms
|
|
10
10
|
* - Max canvas dimension: 900px
|
|
11
11
|
*/
|
|
12
|
-
export declare const SDK_VERSION = "0.8.
|
|
12
|
+
export declare const SDK_VERSION = "0.8.6";
|
|
13
13
|
export declare const AESTHETIC_DEFAULTS: {
|
|
14
14
|
readonly background: {
|
|
15
15
|
readonly r: 246;
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.6 - Type Definitions
|
|
3
3
|
*
|
|
4
4
|
* Lightweight Preview Runtime for NexArt Protocol.
|
|
5
5
|
* This SDK is non-canonical and for preview only.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Max total time: 500ms
|
|
10
10
|
* - Max canvas dimension: 900px
|
|
11
11
|
*/
|
|
12
|
-
export const SDK_VERSION = '0.8.
|
|
12
|
+
export const SDK_VERSION = '0.8.6';
|
|
13
13
|
export const AESTHETIC_DEFAULTS = {
|
|
14
14
|
background: { r: 246, g: 245, b: 242 },
|
|
15
15
|
foreground: { r: 45, g: 45, b: 45 },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexart/ui-renderer",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Lightweight Preview Runtime for NexArt Protocol. Non-canonical,
|
|
3
|
+
"version": "0.8.7",
|
|
4
|
+
"description": "Lightweight Preview Runtime for NexArt Protocol. Non-canonical, FPS-capped continuous animation (8 FPS, max 900px canvas).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|