@nexart/ui-renderer 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +458 -0
- package/README.md +196 -269
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.js +2 -2
- package/dist/index.d.ts +36 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -21
- package/dist/preview/code-renderer.d.ts +2 -2
- package/dist/preview/code-renderer.d.ts.map +1 -1
- package/dist/preview/code-renderer.js +9 -17
- package/dist/preview/preview-engine.d.ts +13 -10
- package/dist/preview/preview-engine.d.ts.map +1 -1
- package/dist/preview/preview-engine.js +178 -39
- package/dist/preview/preview-types.d.ts +81 -2
- package/dist/preview/preview-types.d.ts.map +1 -1
- package/dist/preview/preview-types.js +13 -0
- package/examples/basic-preview.ts +92 -0
- package/examples/budget-demo.ts +161 -0
- package/package.json +14 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nexart/ui-renderer
|
|
3
|
-
* Version: 0.
|
|
3
|
+
* Version: 0.9.0
|
|
4
4
|
*
|
|
5
5
|
* Lightweight Preview Runtime for NexArt Protocol
|
|
6
6
|
*
|
|
@@ -11,21 +11,15 @@
|
|
|
11
11
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
12
12
|
* ║ ║
|
|
13
13
|
* ║ Performance Limits: ║
|
|
14
|
-
* ║ -
|
|
15
|
-
* ║ - Max total time: 500ms ║
|
|
14
|
+
* ║ - FPS: Native RAF (~60 FPS) ║
|
|
16
15
|
* ║ - Max canvas dimension: 900px ║
|
|
17
|
-
* ║ -
|
|
16
|
+
* ║ - Budget: 1800 frames / 5 minutes (configurable) ║
|
|
18
17
|
* ║ ║
|
|
19
|
-
* ║
|
|
20
|
-
* ║ -
|
|
21
|
-
* ║ -
|
|
22
|
-
* ║ -
|
|
23
|
-
* ║ -
|
|
24
|
-
* ║ ║
|
|
25
|
-
* ║ NOT For: ║
|
|
26
|
-
* ║ - Minting / export ║
|
|
27
|
-
* ║ - ByX ║
|
|
28
|
-
* ║ - Protocol validation ║
|
|
18
|
+
* ║ v0.9.0 Features: ║
|
|
19
|
+
* ║ - Budget exceed callbacks + overlay ║
|
|
20
|
+
* ║ - getPreviewStats() for observability ║
|
|
21
|
+
* ║ - createPreviewRuntime() as recommended entrypoint ║
|
|
22
|
+
* ║ - toCanonicalRequest() for handoff to @nexart/codemode-sdk ║
|
|
29
23
|
* ║ ║
|
|
30
24
|
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
31
25
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
@@ -39,16 +33,40 @@ export { compileBackgroundPreset, getPaletteColors } from './presets/backgrounds
|
|
|
39
33
|
export { compilePrimitive } from './presets/primitives';
|
|
40
34
|
export { wrapSketch, validateSketchSafety } from './presets/sketch-wrapper';
|
|
41
35
|
export { getCapabilities, getPrimitiveTypes, getPrimitivesByCategory, getPrimitiveInfo, isPrimitiveValid, getMotionSources, getBackgroundTextures, } from './capabilities';
|
|
42
|
-
export { createPreviewEngine, renderStaticPreview, stopActivePreview, } from './preview/preview-engine';
|
|
43
|
-
export { PREVIEW_FPS, CANVAS_LIMITS, type RuntimeProfile, type PreviewMode, type PreviewEngineConfig, type PreviewRenderResult, type PreviewRenderer, type FpsThrottleState, } from './preview/preview-types';
|
|
36
|
+
export { createPreviewEngine, renderStaticPreview, stopActivePreview, toCanonicalRequest, } from './preview/preview-engine';
|
|
37
|
+
export { PREVIEW_FPS, PREVIEW_BUDGET, CANVAS_LIMITS, type RuntimeProfile, type PreviewMode, type PreviewEngineConfig, type PreviewRenderResult, type PreviewRenderer, type PreviewStats, type BudgetExceededInfo, type BudgetExceedReason, type BudgetBehavior, type CanonicalRequest, type FpsThrottleState, } from './preview/preview-types';
|
|
44
38
|
export { type FrameBudgetState } from './preview/frame-budget';
|
|
45
39
|
export { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, createFrameBudget, canRenderFrame, resetBudget, shouldSkipFrame, getElapsedMs, } from './preview/frame-budget';
|
|
46
40
|
export { calculateScaledDimensions, applyScaledDimensions, type ScaledDimensions, } from './preview/canvas-scaler';
|
|
47
41
|
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';
|
|
48
42
|
export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
|
|
49
43
|
export type { Capabilities, PrimitiveCapability, ParameterSpec, } from './capabilities';
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
/**
|
|
45
|
+
* createPreviewRuntime - Recommended entrypoint for AI coding agents.
|
|
46
|
+
*
|
|
47
|
+
* This is an alias for createPreviewEngine with sensible defaults.
|
|
48
|
+
* Use this as the primary way to create previews.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { createPreviewRuntime } from '@nexart/ui-renderer';
|
|
53
|
+
*
|
|
54
|
+
* const runtime = createPreviewRuntime({
|
|
55
|
+
* canvas: document.getElementById('canvas'),
|
|
56
|
+
* source: 'function draw() { circle(width/2, height/2, 100); }',
|
|
57
|
+
* mode: 'loop',
|
|
58
|
+
* width: 1950,
|
|
59
|
+
* height: 2400,
|
|
60
|
+
* onBudgetExceeded: (info) => console.log('Budget exceeded:', info),
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* runtime.startLoop();
|
|
64
|
+
* console.log(runtime.getPreviewStats());
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export { createPreviewEngine as createPreviewRuntime } from './preview/preview-engine';
|
|
68
|
+
export declare const SDK_VERSION = "0.9.0";
|
|
69
|
+
export declare const PROTOCOL_VERSION = "0.9";
|
|
52
70
|
export declare const IS_CANONICAL = false;
|
|
53
71
|
export declare const IS_ARCHIVAL = false;
|
|
54
72
|
export declare const RENDERER = "@nexart/ui-renderer";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;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,EACjB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,WAAW,EACX,cAAc,EACd,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,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;AAMxB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,mBAAmB,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEvF,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.
|
|
3
|
+
* Version: 0.9.0
|
|
4
4
|
*
|
|
5
5
|
* Lightweight Preview Runtime for NexArt Protocol
|
|
6
6
|
*
|
|
@@ -11,21 +11,15 @@
|
|
|
11
11
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
12
12
|
* ║ ║
|
|
13
13
|
* ║ Performance Limits: ║
|
|
14
|
-
* ║ -
|
|
15
|
-
* ║ - Max total time: 500ms ║
|
|
14
|
+
* ║ - FPS: Native RAF (~60 FPS) ║
|
|
16
15
|
* ║ - Max canvas dimension: 900px ║
|
|
17
|
-
* ║ -
|
|
16
|
+
* ║ - Budget: 1800 frames / 5 minutes (configurable) ║
|
|
18
17
|
* ║ ║
|
|
19
|
-
* ║
|
|
20
|
-
* ║ -
|
|
21
|
-
* ║ -
|
|
22
|
-
* ║ -
|
|
23
|
-
* ║ -
|
|
24
|
-
* ║ ║
|
|
25
|
-
* ║ NOT For: ║
|
|
26
|
-
* ║ - Minting / export ║
|
|
27
|
-
* ║ - ByX ║
|
|
28
|
-
* ║ - Protocol validation ║
|
|
18
|
+
* ║ v0.9.0 Features: ║
|
|
19
|
+
* ║ - Budget exceed callbacks + overlay ║
|
|
20
|
+
* ║ - getPreviewStats() for observability ║
|
|
21
|
+
* ║ - createPreviewRuntime() as recommended entrypoint ║
|
|
22
|
+
* ║ - toCanonicalRequest() for handoff to @nexart/codemode-sdk ║
|
|
29
23
|
* ║ ║
|
|
30
24
|
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
31
25
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
@@ -39,15 +33,40 @@ export { compileBackgroundPreset, getPaletteColors } from './presets/backgrounds
|
|
|
39
33
|
export { compilePrimitive } from './presets/primitives';
|
|
40
34
|
export { wrapSketch, validateSketchSafety } from './presets/sketch-wrapper';
|
|
41
35
|
export { getCapabilities, getPrimitiveTypes, getPrimitivesByCategory, getPrimitiveInfo, isPrimitiveValid, getMotionSources, getBackgroundTextures, } from './capabilities';
|
|
42
|
-
export { createPreviewEngine, renderStaticPreview, stopActivePreview, } from './preview/preview-engine';
|
|
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';
|
|
36
|
+
export { createPreviewEngine, renderStaticPreview, stopActivePreview, toCanonicalRequest, } from './preview/preview-engine';
|
|
37
|
+
export { PREVIEW_FPS, PREVIEW_BUDGET, CANVAS_LIMITS, } from './preview/preview-types';
|
|
38
|
+
export { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, createFrameBudget, canRenderFrame, resetBudget, shouldSkipFrame, getElapsedMs, } from './preview/frame-budget';
|
|
47
39
|
export { calculateScaledDimensions, applyScaledDimensions, } from './preview/canvas-scaler';
|
|
48
40
|
export { AESTHETIC_DEFAULTS, SDK_VERSION as TYPE_SDK_VERSION } from './types';
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// v0.9.0 RECOMMENDED ENTRYPOINT
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* createPreviewRuntime - Recommended entrypoint for AI coding agents.
|
|
46
|
+
*
|
|
47
|
+
* This is an alias for createPreviewEngine with sensible defaults.
|
|
48
|
+
* Use this as the primary way to create previews.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { createPreviewRuntime } from '@nexart/ui-renderer';
|
|
53
|
+
*
|
|
54
|
+
* const runtime = createPreviewRuntime({
|
|
55
|
+
* canvas: document.getElementById('canvas'),
|
|
56
|
+
* source: 'function draw() { circle(width/2, height/2, 100); }',
|
|
57
|
+
* mode: 'loop',
|
|
58
|
+
* width: 1950,
|
|
59
|
+
* height: 2400,
|
|
60
|
+
* onBudgetExceeded: (info) => console.log('Budget exceeded:', info),
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* runtime.startLoop();
|
|
64
|
+
* console.log(runtime.getPreviewStats());
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export { createPreviewEngine as createPreviewRuntime } from './preview/preview-engine';
|
|
68
|
+
export const SDK_VERSION = '0.9.0';
|
|
69
|
+
export const PROTOCOL_VERSION = '0.9';
|
|
51
70
|
export const IS_CANONICAL = false;
|
|
52
71
|
export const IS_ARCHIVAL = false;
|
|
53
72
|
export const RENDERER = '@nexart/ui-renderer';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.8 - Code Mode Renderer
|
|
3
3
|
*
|
|
4
4
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
5
|
* ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* ║ This renderer is a preview-only runtime. ║
|
|
8
8
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
9
|
* ║ ║
|
|
10
|
-
* ║ Performance:
|
|
10
|
+
* ║ Performance: Native requestAnimationFrame (~60 FPS), canvas max 900px ║
|
|
11
11
|
* ║ Animation: Runs continuously until stop() is called ║
|
|
12
12
|
* ║ ║
|
|
13
13
|
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAiBjE,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,CA4Sd"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.8.
|
|
2
|
+
* @nexart/ui-renderer v0.8.8 - Code Mode Renderer
|
|
3
3
|
*
|
|
4
4
|
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
5
|
* ║ PREVIEW RENDERER — LIGHTWEIGHT, NON-AUTHORITATIVE ║
|
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
* ║ This renderer is a preview-only runtime. ║
|
|
8
8
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
9
|
* ║ ║
|
|
10
|
-
* ║ Performance:
|
|
10
|
+
* ║ Performance: Native requestAnimationFrame (~60 FPS), canvas max 900px ║
|
|
11
11
|
* ║ Animation: Runs continuously until stop() is called ║
|
|
12
12
|
* ║ ║
|
|
13
13
|
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
14
14
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import { createFpsThrottle, shouldRenderFrame, recordFrame, resetThrottle, } from './frame-budget';
|
|
16
|
+
import { CANVAS_LIMITS, } from './preview-types';
|
|
18
17
|
import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, clearCanvasIgnoringTransform, } from './canvas-scaler';
|
|
19
18
|
import { createPreviewRuntime } from './preview-runtime';
|
|
20
19
|
const PROTOCOL_VERSION = '1.2.0';
|
|
@@ -42,8 +41,8 @@ function normalizeVars(vars) {
|
|
|
42
41
|
return result;
|
|
43
42
|
}
|
|
44
43
|
export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
45
|
-
console.log('[UIRenderer] Preview mode →
|
|
46
|
-
console.log(`[UIRenderer]
|
|
44
|
+
console.log('[UIRenderer] Preview mode → native requestAnimationFrame (~60 FPS)');
|
|
45
|
+
console.log(`[UIRenderer] Canvas max ${CANVAS_LIMITS.MAX_DIMENSION}px`);
|
|
47
46
|
if (activeRendererInstance) {
|
|
48
47
|
activeRendererInstance.destroy();
|
|
49
48
|
activeRendererInstance = null;
|
|
@@ -61,7 +60,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
61
60
|
let animationId = null;
|
|
62
61
|
let isRunning = false;
|
|
63
62
|
let isDestroyed = false;
|
|
64
|
-
const throttle = createFpsThrottle();
|
|
65
63
|
const normalizedVars = normalizeVars(system.vars);
|
|
66
64
|
let runtime = null;
|
|
67
65
|
let setupFn = null;
|
|
@@ -217,15 +215,14 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
217
215
|
if (setupFn) {
|
|
218
216
|
setupFn();
|
|
219
217
|
}
|
|
220
|
-
resetThrottle(throttle);
|
|
221
218
|
isRunning = true;
|
|
222
219
|
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
223
|
-
// ║ ANIMATION LOOP —
|
|
220
|
+
// ║ ANIMATION LOOP — NATIVE requestAnimationFrame (~60 FPS) ║
|
|
224
221
|
// ║ ║
|
|
225
|
-
// ║
|
|
226
|
-
// ║
|
|
222
|
+
// ║ v0.8.8: Removed FPS throttle for smooth rendering matching NexArt. ║
|
|
223
|
+
// ║ Browser handles frame pacing naturally via requestAnimationFrame. ║
|
|
227
224
|
// ║ Looping uses modulo math: t = (frame % total) / total ║
|
|
228
|
-
// ║ Canvas cleared before each draw
|
|
225
|
+
// ║ Canvas cleared before each draw() call. ║
|
|
229
226
|
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
230
227
|
const loop = () => {
|
|
231
228
|
// Schedule next frame first — loop runs until stop()
|
|
@@ -233,10 +230,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
233
230
|
// Exit if stopped or destroyed
|
|
234
231
|
if (!isRunning || isDestroyed)
|
|
235
232
|
return;
|
|
236
|
-
// FPS throttle — skip if not enough time has passed
|
|
237
|
-
if (!shouldRenderFrame(throttle)) {
|
|
238
|
-
return; // Preserve current canvas
|
|
239
|
-
}
|
|
240
233
|
frameCount++;
|
|
241
234
|
// Update runtime timing using modulo for natural looping
|
|
242
235
|
if (runtime) {
|
|
@@ -251,7 +244,6 @@ export function renderCodeModeSystem(system, canvas, options = {}) {
|
|
|
251
244
|
if (drawFn)
|
|
252
245
|
drawFn();
|
|
253
246
|
drawBadge();
|
|
254
|
-
recordFrame(throttle);
|
|
255
247
|
}
|
|
256
248
|
catch (error) {
|
|
257
249
|
console.warn('[UIRenderer] Draw error:', error);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nexart/ui-renderer - Preview Engine
|
|
3
|
+
* Version: 0.9.0
|
|
3
4
|
*
|
|
4
5
|
* Lightweight, non-authoritative preview executor.
|
|
5
6
|
* Safe, interruptible, and performant.
|
|
@@ -10,21 +11,18 @@
|
|
|
10
11
|
* ║ This engine is a preview-only runtime. ║
|
|
11
12
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
12
13
|
* ║ ║
|
|
13
|
-
* ║
|
|
14
|
-
* ║ -
|
|
15
|
-
* ║ -
|
|
16
|
-
* ║ -
|
|
17
|
-
* ║ - Static or loop previews ║
|
|
14
|
+
* ║ v0.9.0 Features: ║
|
|
15
|
+
* ║ - Budget exceed callbacks + overlay ║
|
|
16
|
+
* ║ - getPreviewStats() for observability ║
|
|
17
|
+
* ║ - toCanonicalRequest() for handoff to @nexart/codemode-sdk ║
|
|
18
18
|
* ║ ║
|
|
19
|
-
* ║
|
|
20
|
-
* ║
|
|
21
|
-
* ║ - ByX ║
|
|
22
|
-
* ║ - Protocol validation ║
|
|
19
|
+
* ║ Animation runs at native RAF cadence (~60 FPS) per v0.8.8. ║
|
|
20
|
+
* ║ Budget system uses frame count + time limits (not FPS throttling). ║
|
|
23
21
|
* ║ ║
|
|
24
22
|
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
25
23
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
26
24
|
*/
|
|
27
|
-
import { type PreviewEngineConfig, type PreviewRenderResult, type PreviewRenderer } from './preview-types';
|
|
25
|
+
import { type PreviewEngineConfig, type PreviewRenderResult, type PreviewRenderer, type CanonicalRequest } from './preview-types';
|
|
28
26
|
/**
|
|
29
27
|
* Create a preview renderer for the given configuration.
|
|
30
28
|
*
|
|
@@ -39,4 +37,9 @@ export declare function renderStaticPreview(config: PreviewEngineConfig): Previe
|
|
|
39
37
|
* Stop any active preview renderer.
|
|
40
38
|
*/
|
|
41
39
|
export declare function stopActivePreview(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Create a canonical request from preview config.
|
|
42
|
+
* For handoff to @nexart/codemode-sdk.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toCanonicalRequest(config: PreviewEngineConfig): CanonicalRequest;
|
|
42
45
|
//# sourceMappingURL=preview-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"preview-engine.d.ts","sourceRoot":"","sources":["../../src/preview/preview-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAIpB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAyVzB;;;;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;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,CAchF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @nexart/ui-renderer - Preview Engine
|
|
3
|
+
* Version: 0.9.0
|
|
3
4
|
*
|
|
4
5
|
* Lightweight, non-authoritative preview executor.
|
|
5
6
|
* Safe, interruptible, and performant.
|
|
@@ -10,33 +11,38 @@
|
|
|
10
11
|
* ║ This engine is a preview-only runtime. ║
|
|
11
12
|
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
12
13
|
* ║ ║
|
|
13
|
-
* ║
|
|
14
|
-
* ║ -
|
|
15
|
-
* ║ -
|
|
16
|
-
* ║ -
|
|
17
|
-
* ║ - Static or loop previews ║
|
|
14
|
+
* ║ v0.9.0 Features: ║
|
|
15
|
+
* ║ - Budget exceed callbacks + overlay ║
|
|
16
|
+
* ║ - getPreviewStats() for observability ║
|
|
17
|
+
* ║ - toCanonicalRequest() for handoff to @nexart/codemode-sdk ║
|
|
18
18
|
* ║ ║
|
|
19
|
-
* ║
|
|
20
|
-
* ║
|
|
21
|
-
* ║ - ByX ║
|
|
22
|
-
* ║ - Protocol validation ║
|
|
19
|
+
* ║ Animation runs at native RAF cadence (~60 FPS) per v0.8.8. ║
|
|
20
|
+
* ║ Budget system uses frame count + time limits (not FPS throttling). ║
|
|
23
21
|
* ║ ║
|
|
24
22
|
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
25
23
|
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
26
24
|
*/
|
|
27
|
-
import {
|
|
25
|
+
import { PREVIEW_BUDGET, } from './preview-types';
|
|
28
26
|
import { calculateScaledDimensions, applyScaledDimensions, reapplyContextScale, } from './canvas-scaler';
|
|
29
27
|
import { createPreviewRuntime } from './preview-runtime';
|
|
28
|
+
const SDK_VERSION = '0.9.0';
|
|
30
29
|
let activePreviewRenderer = null;
|
|
31
30
|
class PreviewEngine {
|
|
31
|
+
get previewScale() {
|
|
32
|
+
return this.scaled?.scaleFactor ?? 1;
|
|
33
|
+
}
|
|
32
34
|
constructor(config) {
|
|
33
35
|
this.runtime = null;
|
|
34
36
|
this.setupFn = null;
|
|
35
37
|
this.drawFn = null;
|
|
36
38
|
this.animationFrameId = null;
|
|
37
|
-
this.throttle = createFpsThrottle();
|
|
38
39
|
this.running = false;
|
|
39
40
|
this.internalFrameCount = 0;
|
|
41
|
+
this.startTimeMs = 0;
|
|
42
|
+
this.scaled = null;
|
|
43
|
+
this.currentStride = 1;
|
|
44
|
+
this.budgetExceededReason = null;
|
|
45
|
+
this.overlayElement = null;
|
|
40
46
|
this.isCanonical = false;
|
|
41
47
|
this.isArchival = false;
|
|
42
48
|
this.config = config;
|
|
@@ -44,20 +50,10 @@ class PreviewEngine {
|
|
|
44
50
|
this.initialize();
|
|
45
51
|
}
|
|
46
52
|
initialize() {
|
|
47
|
-
|
|
48
|
-
applyScaledDimensions(this.canvas, scaled);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
reapplyContextScale(this.canvas, scaled);
|
|
52
|
-
// ╔═══════════════════════════════════════════════════════════════════════╗
|
|
53
|
-
// ║ ARCHITECTURAL INVARIANT — DO NOT CHANGE ║
|
|
54
|
-
// ║ Runtime width/height MUST equal protocol dimensions. ║
|
|
55
|
-
// ║ DO NOT pass renderWidth/renderHeight — breaks loop animations. ║
|
|
56
|
-
// ║ Scaling is handled by ctx.scale(), not by changing width/height. ║
|
|
57
|
-
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
58
|
-
this.runtime = createPreviewRuntime(this.canvas, scaled.originalWidth, // ← Protocol dimension
|
|
59
|
-
scaled.originalHeight, // ← Protocol dimension
|
|
60
|
-
this.config.seed ?? 12345, this.config.vars ?? []);
|
|
53
|
+
this.scaled = calculateScaledDimensions(this.config.width, this.config.height);
|
|
54
|
+
applyScaledDimensions(this.canvas, this.scaled);
|
|
55
|
+
reapplyContextScale(this.canvas, this.scaled);
|
|
56
|
+
this.runtime = createPreviewRuntime(this.canvas, this.scaled.originalWidth, this.scaled.originalHeight, this.config.seed ?? 12345, this.config.vars ?? []);
|
|
61
57
|
const totalFrames = this.config.totalFrames ?? 120;
|
|
62
58
|
this.runtime.totalFrames = totalFrames;
|
|
63
59
|
try {
|
|
@@ -86,8 +82,123 @@ class PreviewEngine {
|
|
|
86
82
|
const fn = new Function(...globalVars, wrappedSource);
|
|
87
83
|
return () => fn(...globalValues);
|
|
88
84
|
}
|
|
85
|
+
getPreviewStats() {
|
|
86
|
+
const elapsed = this.startTimeMs > 0 ? performance.now() - this.startTimeMs : 0;
|
|
87
|
+
return {
|
|
88
|
+
mode: 'preview',
|
|
89
|
+
scale: this.previewScale,
|
|
90
|
+
semanticWidth: this.scaled?.originalWidth ?? this.config.width,
|
|
91
|
+
semanticHeight: this.scaled?.originalHeight ?? this.config.height,
|
|
92
|
+
bufferWidth: this.scaled?.renderWidth ?? this.config.width,
|
|
93
|
+
bufferHeight: this.scaled?.renderHeight ?? this.config.height,
|
|
94
|
+
frames: this.internalFrameCount,
|
|
95
|
+
stride: this.currentStride,
|
|
96
|
+
totalTimeMs: elapsed,
|
|
97
|
+
...(this.budgetExceededReason ? { budgetExceeded: { reason: this.budgetExceededReason } } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
toCanonicalRequest() {
|
|
101
|
+
return {
|
|
102
|
+
seed: this.config.seed ?? 12345,
|
|
103
|
+
vars: this.config.vars ?? [],
|
|
104
|
+
code: this.config.source,
|
|
105
|
+
settings: {
|
|
106
|
+
width: this.config.width,
|
|
107
|
+
height: this.config.height,
|
|
108
|
+
mode: this.config.mode,
|
|
109
|
+
totalFrames: this.config.totalFrames,
|
|
110
|
+
},
|
|
111
|
+
renderer: 'preview',
|
|
112
|
+
uiRendererVersion: SDK_VERSION,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
showBudgetOverlay(reason) {
|
|
116
|
+
if (this.overlayElement)
|
|
117
|
+
return;
|
|
118
|
+
const showOverlay = this.config.showOverlay ?? (this.config.budgetBehavior === 'stop');
|
|
119
|
+
if (!showOverlay)
|
|
120
|
+
return;
|
|
121
|
+
const overlay = document.createElement('div');
|
|
122
|
+
overlay.style.cssText = `
|
|
123
|
+
position: absolute;
|
|
124
|
+
top: 0;
|
|
125
|
+
left: 0;
|
|
126
|
+
right: 0;
|
|
127
|
+
bottom: 0;
|
|
128
|
+
background: rgba(0, 0, 0, 0.85);
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
color: white;
|
|
134
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
135
|
+
z-index: 1000;
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
`;
|
|
138
|
+
const reasonText = reason === 'frame_limit'
|
|
139
|
+
? `Frame limit reached (${this.internalFrameCount} frames)`
|
|
140
|
+
: `Time limit reached (${Math.round((performance.now() - this.startTimeMs) / 1000)}s)`;
|
|
141
|
+
overlay.innerHTML = `
|
|
142
|
+
<div style="font-size: 24px; margin-bottom: 8px;">⚠️ Preview Budget Exceeded</div>
|
|
143
|
+
<div style="font-size: 14px; opacity: 0.8; margin-bottom: 16px;">${reasonText}</div>
|
|
144
|
+
<div style="font-size: 12px; opacity: 0.6; max-width: 280px; text-align: center;">
|
|
145
|
+
Reduce sketch complexity or use @nexart/codemode-sdk for canonical execution.
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
const parent = this.canvas.parentElement;
|
|
149
|
+
if (parent) {
|
|
150
|
+
parent.style.position = 'relative';
|
|
151
|
+
parent.appendChild(overlay);
|
|
152
|
+
this.overlayElement = overlay;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
removeOverlay() {
|
|
156
|
+
if (this.overlayElement && this.overlayElement.parentElement) {
|
|
157
|
+
this.overlayElement.parentElement.removeChild(this.overlayElement);
|
|
158
|
+
this.overlayElement = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
handleBudgetExceeded(reason) {
|
|
162
|
+
if (this.budgetExceededReason)
|
|
163
|
+
return; // Already exceeded — fire callback only once
|
|
164
|
+
this.budgetExceededReason = reason;
|
|
165
|
+
const behavior = this.config.budgetBehavior ?? 'stop';
|
|
166
|
+
const info = {
|
|
167
|
+
reason,
|
|
168
|
+
framesRendered: this.internalFrameCount,
|
|
169
|
+
totalTimeMs: performance.now() - this.startTimeMs,
|
|
170
|
+
stride: this.currentStride,
|
|
171
|
+
scale: this.previewScale,
|
|
172
|
+
};
|
|
173
|
+
console.warn(`[PreviewEngine] Budget exceeded: ${reason}`, info);
|
|
174
|
+
if (this.config.onBudgetExceeded) {
|
|
175
|
+
this.config.onBudgetExceeded(info);
|
|
176
|
+
}
|
|
177
|
+
if (behavior === 'stop') {
|
|
178
|
+
this.showBudgetOverlay(reason);
|
|
179
|
+
this.stopLoop();
|
|
180
|
+
}
|
|
181
|
+
else if (behavior === 'degrade') {
|
|
182
|
+
this.currentStride = PREVIEW_BUDGET.DEGRADE_STRIDE;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
checkBudget() {
|
|
186
|
+
const maxFrames = this.config.maxFrames ?? PREVIEW_BUDGET.MAX_FRAMES;
|
|
187
|
+
const maxTimeMs = this.config.maxTimeMs ?? PREVIEW_BUDGET.MAX_TOTAL_TIME_MS;
|
|
188
|
+
const elapsed = performance.now() - this.startTimeMs;
|
|
189
|
+
if (this.internalFrameCount >= maxFrames) {
|
|
190
|
+
this.handleBudgetExceeded('frame_limit');
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
if (elapsed >= maxTimeMs) {
|
|
194
|
+
this.handleBudgetExceeded('time_limit');
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
89
199
|
renderStatic() {
|
|
90
200
|
const startTime = performance.now();
|
|
201
|
+
this.startTimeMs = startTime;
|
|
91
202
|
try {
|
|
92
203
|
if (this.setupFn) {
|
|
93
204
|
this.setupFn();
|
|
@@ -119,8 +230,11 @@ class PreviewEngine {
|
|
|
119
230
|
}
|
|
120
231
|
activePreviewRenderer = this;
|
|
121
232
|
this.running = true;
|
|
122
|
-
|
|
233
|
+
this.startTimeMs = performance.now();
|
|
123
234
|
this.internalFrameCount = 0;
|
|
235
|
+
this.currentStride = 1;
|
|
236
|
+
this.budgetExceededReason = null;
|
|
237
|
+
this.removeOverlay();
|
|
124
238
|
try {
|
|
125
239
|
if (this.setupFn) {
|
|
126
240
|
this.setupFn();
|
|
@@ -131,25 +245,31 @@ class PreviewEngine {
|
|
|
131
245
|
}
|
|
132
246
|
this.scheduleNextFrame();
|
|
133
247
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// ║ Looping uses modulo math: t = (frame % total) / total ║
|
|
140
|
-
// ╚═══════════════════════════════════════════════════════════════════════╝
|
|
248
|
+
/**
|
|
249
|
+
* Animation loop — runs at native RAF cadence (~60 FPS).
|
|
250
|
+
* No FPS throttle per v0.8.8 decision.
|
|
251
|
+
* Budget system uses frame count and time limits only.
|
|
252
|
+
*/
|
|
141
253
|
scheduleNextFrame() {
|
|
142
|
-
// Schedule next frame — loop runs until stopLoop()
|
|
143
254
|
this.animationFrameId = requestAnimationFrame(() => {
|
|
255
|
+
// Always schedule next frame first (RAF continues until stop)
|
|
144
256
|
this.scheduleNextFrame();
|
|
145
257
|
// Exit if stopped
|
|
146
258
|
if (!this.running)
|
|
147
259
|
return;
|
|
148
|
-
// FPS throttle — skip if not enough time has passed
|
|
149
|
-
if (!shouldRenderFrame(this.throttle)) {
|
|
150
|
-
return; // Preserve current canvas
|
|
151
|
-
}
|
|
152
260
|
this.internalFrameCount++;
|
|
261
|
+
// Check budget — fires callback once when exceeded
|
|
262
|
+
if (!this.checkBudget()) {
|
|
263
|
+
// If behavior is 'stop', loop already halted by handleBudgetExceeded
|
|
264
|
+
// If behavior is 'degrade', continue with stride skipping below
|
|
265
|
+
if (this.config.budgetBehavior !== 'degrade') {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Stride skip in degrade mode (render every Nth frame)
|
|
270
|
+
if (this.currentStride > 1 && this.internalFrameCount % this.currentStride !== 0) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
153
273
|
// Update runtime timing using modulo for natural looping
|
|
154
274
|
try {
|
|
155
275
|
if (this.runtime) {
|
|
@@ -162,7 +282,6 @@ class PreviewEngine {
|
|
|
162
282
|
if (this.drawFn) {
|
|
163
283
|
this.drawFn();
|
|
164
284
|
}
|
|
165
|
-
recordFrame(this.throttle);
|
|
166
285
|
}
|
|
167
286
|
catch (error) {
|
|
168
287
|
console.warn('[PreviewEngine] Draw error:', error);
|
|
@@ -184,6 +303,7 @@ class PreviewEngine {
|
|
|
184
303
|
}
|
|
185
304
|
destroy() {
|
|
186
305
|
this.stopLoop();
|
|
306
|
+
this.removeOverlay();
|
|
187
307
|
this.runtime = null;
|
|
188
308
|
this.setupFn = null;
|
|
189
309
|
this.drawFn = null;
|
|
@@ -215,3 +335,22 @@ export function stopActivePreview() {
|
|
|
215
335
|
activePreviewRenderer = null;
|
|
216
336
|
}
|
|
217
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Create a canonical request from preview config.
|
|
340
|
+
* For handoff to @nexart/codemode-sdk.
|
|
341
|
+
*/
|
|
342
|
+
export function toCanonicalRequest(config) {
|
|
343
|
+
return {
|
|
344
|
+
seed: config.seed ?? 12345,
|
|
345
|
+
vars: config.vars ?? [],
|
|
346
|
+
code: config.source,
|
|
347
|
+
settings: {
|
|
348
|
+
width: config.width,
|
|
349
|
+
height: config.height,
|
|
350
|
+
mode: config.mode,
|
|
351
|
+
totalFrames: config.totalFrames,
|
|
352
|
+
},
|
|
353
|
+
renderer: 'preview',
|
|
354
|
+
uiRendererVersion: SDK_VERSION,
|
|
355
|
+
};
|
|
356
|
+
}
|