@flighthq/render 0.3.0 → 0.3.1-next.1041.35b950c
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/dist/enableColorAdjustmentGuards.d.ts.map +1 -1
- package/dist/enableColorAdjustmentGuards.js +14 -5
- package/dist/enableColorAdjustmentGuards.js.map +1 -1
- package/dist/enableColorAdjustments.d.ts.map +1 -1
- package/dist/enableColorAdjustments.js +16 -7
- package/dist/enableColorAdjustments.js.map +1 -1
- package/dist/explainScene2DCoverage.d.ts +2 -2
- package/dist/explainScene2DCoverage.d.ts.map +1 -1
- package/dist/explainScene2DCoverage.js +51 -13
- package/dist/explainScene2DCoverage.js.map +1 -1
- package/dist/explainScene2DRender.d.ts.map +1 -1
- package/dist/explainScene2DRender.js +2 -1
- package/dist/explainScene2DRender.js.map +1 -1
- package/dist/renderProxy.d.ts.map +1 -1
- package/dist/renderProxy.js +16 -8
- package/dist/renderProxy.js.map +1 -1
- package/dist/renderRegistryGuards.js +4 -2
- package/dist/renderRegistryGuards.js.map +1 -1
- package/dist/renderState.d.ts +2 -1
- package/dist/renderState.d.ts.map +1 -1
- package/dist/renderState.js +11 -8
- package/dist/renderState.js.map +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +39 -17
- package/dist/renderer.js.map +1 -1
- package/package.json +12 -11
- package/src/enableColorAdjustmentGuards.test.ts +19 -3
- package/src/enableColorAdjustments.test.ts +18 -1
- package/src/explainScene2DCoverage.test.ts +71 -15
- package/src/renderCache.test.ts +2 -1
- package/src/renderProxy.test.ts +15 -2
- package/src/renderState.test.ts +48 -9
- package/src/renderer.test.ts +144 -25
package/dist/renderer.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getRegistryTableEntry, withRegistryTableEntry } from '@flighthq/registry/contract';
|
|
2
|
+
import { RegistryEntryState } from '@flighthq/types/contract';
|
|
1
3
|
import { getRenderStateRuntime } from './renderState';
|
|
2
4
|
// Mask renderers were retired (a mask is now a path ClipRegion realized by the backend clip hooks), so
|
|
3
5
|
// there is no mask-renderer registry to copy — only the kind→renderer map and the clip hooks.
|
|
@@ -7,35 +9,55 @@ export function copyAllRenderersFromRenderState(target, source) {
|
|
|
7
9
|
target.displayObjectClipHooks = source.displayObjectClipHooks;
|
|
8
10
|
}
|
|
9
11
|
export function copyRenderersFromRenderState(target, source) {
|
|
10
|
-
getRenderStateRuntime(
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const targetRuntime = getRenderStateRuntime(target);
|
|
13
|
+
const sourceTable = getRenderStateRuntime(source).registries.renderers;
|
|
14
|
+
const targetTable = targetRuntime.registries.renderers;
|
|
15
|
+
// A fresh derived pipeline can share the immutable source snapshot directly. A target that already
|
|
16
|
+
// has policy keeps its target-only registrations, matching the additive copy contract, while source
|
|
17
|
+
// bindings remain last-write-wins through registerRenderer.
|
|
18
|
+
if (targetTable.entries.size === 0) {
|
|
19
|
+
let registrationCount = 0;
|
|
20
|
+
for (const entry of sourceTable.entries.values()) {
|
|
21
|
+
if (entry.state === RegistryEntryState.Bound)
|
|
22
|
+
registrationCount++;
|
|
23
|
+
}
|
|
24
|
+
if (registrationCount === 0)
|
|
25
|
+
return;
|
|
26
|
+
targetRuntime.registries.renderers = sourceTable;
|
|
27
|
+
targetRuntime.rendererMapId = (targetRuntime.rendererMapId + registrationCount) >>> 0;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const [kind, entry] of sourceTable.entries) {
|
|
31
|
+
if (entry.state === RegistryEntryState.Bound)
|
|
32
|
+
registerRenderer(target, kind, entry.value);
|
|
33
|
+
}
|
|
13
34
|
}
|
|
14
|
-
// Copies the backend-agnostic policy registries that participate in pipeline derivation.
|
|
15
|
-
//
|
|
35
|
+
// Copies the backend-agnostic policy registries that participate in pipeline derivation. Persistent
|
|
36
|
+
// tables share an immutable snapshot through distinct aggregates, so later replacements diverge until
|
|
37
|
+
// an explicit re-copy.
|
|
16
38
|
export function copyRenderStateRegistrations(target, source) {
|
|
17
39
|
const targetRuntime = getRenderStateRuntime(target);
|
|
18
40
|
const sourceRuntime = getRenderStateRuntime(source);
|
|
19
|
-
targetRuntime.
|
|
20
|
-
|
|
21
|
-
targetRuntime.
|
|
22
|
-
|
|
23
|
-
// The shape-command set is
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
targetRuntime.
|
|
28
|
-
sourceShapeCommands === null || sourceShapeCommands === undefined ? null : new Map(sourceShapeCommands);
|
|
41
|
+
targetRuntime.registries.colorAdjustments = sourceRuntime.registries.colorAdjustments;
|
|
42
|
+
targetRuntime.registries.colorAdjustmentUnsupportedGuard = sourceRuntime.registries.colorAdjustmentUnsupportedGuard;
|
|
43
|
+
targetRuntime.registries.effectPaddingResolvers = sourceRuntime.registries.effectPaddingResolvers;
|
|
44
|
+
targetRuntime.registries.renderRootGuard = sourceRuntime.registries.renderRootGuard;
|
|
45
|
+
// The shape-command set is base policy every backend replays through, so a pipeline that inherits
|
|
46
|
+
// the renderers must inherit the commands too. Without it an offscreen state resolves no handler for
|
|
47
|
+
// any command in a shape's stream and bakes an empty target.
|
|
48
|
+
targetRuntime.registries.canvasShapeCommands = sourceRuntime.registries.canvasShapeCommands;
|
|
49
|
+
targetRuntime.registries.strokeTessellator = sourceRuntime.registries.strokeTessellator;
|
|
29
50
|
}
|
|
30
51
|
export function noopRendererData(_state, _source) {
|
|
31
52
|
return null;
|
|
32
53
|
}
|
|
33
54
|
export function registerRenderer(state, kind, renderer) {
|
|
34
55
|
const runtime = getRenderStateRuntime(state);
|
|
35
|
-
|
|
56
|
+
const table = runtime.registries.renderers;
|
|
57
|
+
if (getRegistryTableEntry(table, kind) === renderer)
|
|
36
58
|
return;
|
|
59
|
+
runtime.registries.renderers = withRegistryTableEntry(table, kind, renderer);
|
|
37
60
|
runtime.rendererMapId = (runtime.rendererMapId + 1) >>> 0;
|
|
38
|
-
runtime.rendererMap.set(kind, renderer);
|
|
39
61
|
}
|
|
40
62
|
// Batch form of registerRenderer over a caller-supplied set of [kind, renderer] pairs. The registry
|
|
41
63
|
// stays open and tree-shakable: only the renderers the caller references are pulled in — there is no
|
package/dist/renderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/render",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-next.1041.35b950c",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,16 +38,17 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/adjustments": "0.3.
|
|
42
|
-
"@flighthq/color": "0.3.
|
|
43
|
-
"@flighthq/entity": "0.3.
|
|
44
|
-
"@flighthq/geometry": "0.3.
|
|
45
|
-
"@flighthq/log": "0.3.
|
|
46
|
-
"@flighthq/materials": "0.3.
|
|
47
|
-
"@flighthq/mesh": "0.3.
|
|
48
|
-
"@flighthq/node": "0.3.
|
|
49
|
-
"@flighthq/
|
|
50
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/adjustments": "0.3.1-next.1041.35b950c",
|
|
42
|
+
"@flighthq/color": "0.3.1-next.1041.35b950c",
|
|
43
|
+
"@flighthq/entity": "0.3.1-next.1041.35b950c",
|
|
44
|
+
"@flighthq/geometry": "0.3.1-next.1041.35b950c",
|
|
45
|
+
"@flighthq/log": "0.3.1-next.1041.35b950c",
|
|
46
|
+
"@flighthq/materials": "0.3.1-next.1041.35b950c",
|
|
47
|
+
"@flighthq/mesh": "0.3.1-next.1041.35b950c",
|
|
48
|
+
"@flighthq/node": "0.3.1-next.1041.35b950c",
|
|
49
|
+
"@flighthq/registry": "0.3.1-next.1041.35b950c",
|
|
50
|
+
"@flighthq/signals": "0.3.1-next.1041.35b950c",
|
|
51
|
+
"@flighthq/types": "0.3.1-next.1041.35b950c"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@flighthq/camera": "*",
|
|
@@ -2,7 +2,8 @@ import { createTintAdjustment } from '@flighthq/adjustments/contract';
|
|
|
2
2
|
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
3
3
|
import { setNodeColorAdjustments } from '@flighthq/node/contract';
|
|
4
4
|
import { createDisplayObject } from '@flighthq/scene2d/contract';
|
|
5
|
-
import type { Adjustment, Renderable } from '@flighthq/types/contract';
|
|
5
|
+
import type { Adjustment, Renderable, RenderProxy, RenderState } from '@flighthq/types/contract';
|
|
6
|
+
import { RegistryEntryState } from '@flighthq/types/contract';
|
|
6
7
|
|
|
7
8
|
import { areColorAdjustmentGuardsEnabled, enableColorAdjustmentGuards } from './enableColorAdjustmentGuards';
|
|
8
9
|
import { enableColorAdjustments } from './enableColorAdjustments';
|
|
@@ -15,6 +16,15 @@ describe('areColorAdjustmentGuardsEnabled', () => {
|
|
|
15
16
|
expect(areColorAdjustmentGuardsEnabled(state)).toBe(false);
|
|
16
17
|
enableColorAdjustmentGuards(state);
|
|
17
18
|
expect(areColorAdjustmentGuardsEnabled(state)).toBe(true);
|
|
19
|
+
const table = getRenderStateRuntime(state).registries.colorAdjustmentUnsupportedGuard;
|
|
20
|
+
expect(table).toMatchObject({
|
|
21
|
+
entry: { state: RegistryEntryState.Bound },
|
|
22
|
+
onMiss: 'Disabled',
|
|
23
|
+
registry: 'ColorAdjustmentUnsupportedGuard',
|
|
24
|
+
shape: 'slot',
|
|
25
|
+
});
|
|
26
|
+
enableColorAdjustmentGuards(state);
|
|
27
|
+
expect(getRenderStateRuntime(state).registries.colorAdjustmentUnsupportedGuard).toBe(table);
|
|
18
28
|
});
|
|
19
29
|
});
|
|
20
30
|
|
|
@@ -30,7 +40,7 @@ describe('enableColorAdjustmentGuards', () => {
|
|
|
30
40
|
const sink = createMemoryLogSink(8);
|
|
31
41
|
addLogSink(sink.sink);
|
|
32
42
|
try {
|
|
33
|
-
|
|
43
|
+
resolveColorAdjustments(state, data);
|
|
34
44
|
const entries = getMemoryLogSinkEntries(sink);
|
|
35
45
|
expect(entries.length).toBe(1);
|
|
36
46
|
expect(String((entries[0].data as Record<string, unknown>).message)).toContain('not inline-able');
|
|
@@ -49,10 +59,16 @@ describe('enableColorAdjustmentGuards', () => {
|
|
|
49
59
|
const sink = createMemoryLogSink(8);
|
|
50
60
|
addLogSink(sink.sink);
|
|
51
61
|
try {
|
|
52
|
-
|
|
62
|
+
resolveColorAdjustments(state, data);
|
|
53
63
|
expect(getMemoryLogSinkEntries(sink).length).toBe(0);
|
|
54
64
|
} finally {
|
|
55
65
|
removeLogSink(sink.sink);
|
|
56
66
|
}
|
|
57
67
|
});
|
|
58
68
|
});
|
|
69
|
+
|
|
70
|
+
function resolveColorAdjustments(state: RenderState, data: RenderProxy): void {
|
|
71
|
+
const entry = getRenderStateRuntime(state).registries.colorAdjustments?.entry;
|
|
72
|
+
if (entry?.state !== RegistryEntryState.Bound) throw new Error('Color-adjustment resolver is not enabled');
|
|
73
|
+
entry.value(state, data);
|
|
74
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { addNodeChild, setNodeColorAdjustments } from '@flighthq/node/contract';
|
|
8
8
|
import { createDisplayObject, getNode2DRuntime } from '@flighthq/scene2d/contract';
|
|
9
9
|
import type { Renderable, RenderProxy, RenderState } from '@flighthq/types/contract';
|
|
10
|
+
import { RegistryEntryState } from '@flighthq/types/contract';
|
|
10
11
|
|
|
11
12
|
import { areColorAdjustmentsEnabled, enableColorAdjustments } from './enableColorAdjustments';
|
|
12
13
|
import { createRenderProxy, getRenderProxy2D, prepareScene2DRender } from './renderProxy';
|
|
@@ -22,6 +23,20 @@ describe('areColorAdjustmentsEnabled', () => {
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
describe('enableColorAdjustments', () => {
|
|
26
|
+
it('replaces the persistent resolver slot once and remains idempotent', () => {
|
|
27
|
+
const state = createRenderState();
|
|
28
|
+
const runtime = getRenderStateRuntime(state);
|
|
29
|
+
const empty = runtime.registries.colorAdjustments;
|
|
30
|
+
|
|
31
|
+
enableColorAdjustments(state);
|
|
32
|
+
|
|
33
|
+
const bound = runtime.registries.colorAdjustments!;
|
|
34
|
+
expect(bound).not.toBe(empty);
|
|
35
|
+
expect(bound.entry?.state).toBe(RegistryEntryState.Bound);
|
|
36
|
+
enableColorAdjustments(state);
|
|
37
|
+
expect(runtime.registries.colorAdjustments).toBe(bound);
|
|
38
|
+
});
|
|
39
|
+
|
|
25
40
|
it('leaves color adjustments unresolved until the state opts in', () => {
|
|
26
41
|
const state = createRenderState();
|
|
27
42
|
const node = createDisplayObject();
|
|
@@ -229,5 +244,7 @@ function createEnabledRenderState(): RenderState {
|
|
|
229
244
|
}
|
|
230
245
|
|
|
231
246
|
function resolveColorAdjustments(state: RenderState, data: RenderProxy, parentData?: RenderProxy): void {
|
|
232
|
-
getRenderStateRuntime(state).
|
|
247
|
+
const entry = getRenderStateRuntime(state).registries.colorAdjustments?.entry;
|
|
248
|
+
if (entry?.state !== RegistryEntryState.Bound) throw new Error('Color-adjustment resolver is not enabled');
|
|
249
|
+
entry.value(state, data, parentData);
|
|
233
250
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Renderer, Scene2DKindUsage, SceneCoverageEntry } from '@flighthq/types/contract';
|
|
2
|
-
import { RenderRegistry, SceneCoverage } from '@flighthq/types/contract';
|
|
1
|
+
import type { Renderer, Scene2DKindUsage, SceneCoverageCatalog, SceneCoverageEntry } from '@flighthq/types/contract';
|
|
2
|
+
import { RegistryEntryState, RenderRegistry, RequirementFacet, SceneCoverage } from '@flighthq/types/contract';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { explainScene2DCoverage, hasScene2DCoverage } from './explainScene2DCoverage';
|
|
@@ -8,28 +8,75 @@ import { createRenderState } from './renderState';
|
|
|
8
8
|
import { getRenderStateRuntime } from './renderState';
|
|
9
9
|
|
|
10
10
|
const renderer: Renderer = { createData: () => null, submit: () => {} } as unknown as Renderer;
|
|
11
|
+
const coverageCatalog: SceneCoverageCatalog = [
|
|
12
|
+
{
|
|
13
|
+
kind: 'Shape',
|
|
14
|
+
registrations: [
|
|
15
|
+
{ module: '@flighthq/scene2d', registrar: 'registerShapeRenderer' },
|
|
16
|
+
{ module: '@flighthq/scene2d-canvas', registrar: 'registerCanvasShapeCommands' },
|
|
17
|
+
],
|
|
18
|
+
registry: RenderRegistry.NodeRenderer,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
kind: 'Sprite',
|
|
22
|
+
registrations: [{ module: '@flighthq/scene2d', registrar: 'registerSpriteRenderer' }],
|
|
23
|
+
registry: RenderRegistry.NodeRenderer,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
kind: 'beginFill',
|
|
27
|
+
registrations: [{ module: '@flighthq/scene2d-canvas', registrar: 'registerCanvasShapeCommands' }],
|
|
28
|
+
registry: RenderRegistry.ShapeCommandHandler,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
11
31
|
|
|
12
32
|
function usage(overrides: Partial<Scene2DKindUsage> = {}): Scene2DKindUsage {
|
|
13
33
|
return { blendModes: [], materialKinds: [], nodeKinds: [], shapeCommandKeys: [], ...overrides };
|
|
14
34
|
}
|
|
15
35
|
|
|
16
|
-
function entries(
|
|
36
|
+
function entries(
|
|
37
|
+
state: ReturnType<typeof createRenderState>,
|
|
38
|
+
u: Scene2DKindUsage,
|
|
39
|
+
catalog: SceneCoverageCatalog = coverageCatalog,
|
|
40
|
+
): SceneCoverageEntry[] {
|
|
17
41
|
const out: SceneCoverageEntry[] = [];
|
|
18
|
-
explainScene2DCoverage(out, state, u);
|
|
42
|
+
explainScene2DCoverage(out, state, u, catalog);
|
|
19
43
|
return out;
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
// The registry
|
|
46
|
+
// The registry lives on the base runtime; registering a real command means reaching through
|
|
23
47
|
// scene2d-canvas, which render cannot depend on, so the test installs the binding directly.
|
|
24
48
|
function wireShapeCommand(state: ReturnType<typeof createRenderState>, key: string): void {
|
|
25
49
|
const runtime = getRenderStateRuntime(state);
|
|
26
|
-
|
|
50
|
+
runtime.registries.canvasShapeCommands = {
|
|
51
|
+
entries: new Map([[key, { state: RegistryEntryState.Bound, value: { key, draw: () => {} } as never }]]),
|
|
52
|
+
onMiss: 'Unregistered',
|
|
53
|
+
registry: 'CanvasShapeCommand',
|
|
54
|
+
shape: 'keyed',
|
|
55
|
+
};
|
|
27
56
|
}
|
|
28
57
|
|
|
29
58
|
describe('explainScene2DCoverage', () => {
|
|
30
|
-
it('reports an unregistered node kind
|
|
59
|
+
it('reports an unregistered node kind with the primary catalog remedy', () => {
|
|
31
60
|
expect(entries(createRenderState(), usage({ nodeKinds: ['Shape'] }))).toEqual([
|
|
32
|
-
{
|
|
61
|
+
{
|
|
62
|
+
coverage: SceneCoverage.Unregistered,
|
|
63
|
+
facet: RequirementFacet.SceneNodeKind,
|
|
64
|
+
kind: 'Shape',
|
|
65
|
+
module: '@flighthq/scene2d',
|
|
66
|
+
registrar: 'registerShapeRenderer',
|
|
67
|
+
registry: RenderRegistry.NodeRenderer,
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('reports an uncatalogued node kind as Unavailable, with no remedy fields', () => {
|
|
73
|
+
expect(entries(createRenderState(), usage({ nodeKinds: ['Shape'] }), [])).toEqual([
|
|
74
|
+
{
|
|
75
|
+
coverage: SceneCoverage.Unavailable,
|
|
76
|
+
facet: RequirementFacet.SceneNodeKind,
|
|
77
|
+
kind: 'Shape',
|
|
78
|
+
registry: RenderRegistry.NodeRenderer,
|
|
79
|
+
},
|
|
33
80
|
]);
|
|
34
81
|
});
|
|
35
82
|
|
|
@@ -37,14 +84,22 @@ describe('explainScene2DCoverage', () => {
|
|
|
37
84
|
const state = createRenderState();
|
|
38
85
|
registerRenderer(state, 'Shape', renderer);
|
|
39
86
|
expect(entries(state, usage({ nodeKinds: ['Shape'] }))).toEqual([
|
|
40
|
-
{
|
|
87
|
+
{
|
|
88
|
+
coverage: SceneCoverage.Satisfied,
|
|
89
|
+
facet: RequirementFacet.SceneNodeKind,
|
|
90
|
+
kind: 'Shape',
|
|
91
|
+
registry: RenderRegistry.NodeRenderer,
|
|
92
|
+
},
|
|
41
93
|
]);
|
|
42
94
|
});
|
|
43
95
|
|
|
44
96
|
it('reports an unhandled shape command key against the state that would replay it', () => {
|
|
45
97
|
expect(entries(createRenderState(), usage({ shapeCommandKeys: ['beginFill'] }))).toContainEqual({
|
|
46
|
-
coverage: SceneCoverage.
|
|
98
|
+
coverage: SceneCoverage.Unregistered,
|
|
99
|
+
facet: RequirementFacet.SceneShapeCommand,
|
|
47
100
|
kind: 'beginFill',
|
|
101
|
+
module: '@flighthq/scene2d-canvas',
|
|
102
|
+
registrar: 'registerCanvasShapeCommands',
|
|
48
103
|
registry: RenderRegistry.ShapeCommandHandler,
|
|
49
104
|
});
|
|
50
105
|
});
|
|
@@ -54,6 +109,7 @@ describe('explainScene2DCoverage', () => {
|
|
|
54
109
|
wireShapeCommand(state, 'beginFill');
|
|
55
110
|
expect(entries(state, usage({ shapeCommandKeys: ['beginFill'] }))).toContainEqual({
|
|
56
111
|
coverage: SceneCoverage.Satisfied,
|
|
112
|
+
facet: RequirementFacet.SceneShapeCommand,
|
|
57
113
|
kind: 'beginFill',
|
|
58
114
|
registry: RenderRegistry.ShapeCommandHandler,
|
|
59
115
|
});
|
|
@@ -64,7 +120,7 @@ describe('explainScene2DCoverage', () => {
|
|
|
64
120
|
registerRenderer(state, 'Shape', renderer);
|
|
65
121
|
const found = entries(state, usage({ nodeKinds: ['Shape', 'Sprite'], shapeCommandKeys: ['beginFill'] }));
|
|
66
122
|
expect(found).toHaveLength(3);
|
|
67
|
-
expect(found.filter((e) => e.coverage
|
|
123
|
+
expect(found.filter((e) => e.coverage !== SceneCoverage.Satisfied).map((e) => e.kind)).toEqual([
|
|
68
124
|
'Sprite',
|
|
69
125
|
'beginFill',
|
|
70
126
|
]);
|
|
@@ -74,9 +130,9 @@ describe('explainScene2DCoverage', () => {
|
|
|
74
130
|
const state = createRenderState();
|
|
75
131
|
const u = usage({ nodeKinds: ['Shape'] });
|
|
76
132
|
const out: SceneCoverageEntry[] = [];
|
|
77
|
-
explainScene2DCoverage(out, state, u);
|
|
133
|
+
explainScene2DCoverage(out, state, u, coverageCatalog);
|
|
78
134
|
const first = out.length;
|
|
79
|
-
explainScene2DCoverage(out, state, u);
|
|
135
|
+
explainScene2DCoverage(out, state, u, coverageCatalog);
|
|
80
136
|
expect(out).toHaveLength(first);
|
|
81
137
|
});
|
|
82
138
|
});
|
|
@@ -92,7 +148,7 @@ describe('hasScene2DCoverage', () => {
|
|
|
92
148
|
wireShapeCommand(state, 'beginFill');
|
|
93
149
|
const u = usage({ nodeKinds: ['Shape'], shapeCommandKeys: ['beginFill'] });
|
|
94
150
|
const out: SceneCoverageEntry[] = [];
|
|
95
|
-
explainScene2DCoverage(out, state, u);
|
|
151
|
+
explainScene2DCoverage(out, state, u, coverageCatalog);
|
|
96
152
|
expect(out.every((e) => e.coverage === SceneCoverage.Satisfied)).toBe(true);
|
|
97
153
|
expect(hasScene2DCoverage(state, u)).toBe(true);
|
|
98
154
|
});
|
|
@@ -102,7 +158,7 @@ describe('hasScene2DCoverage', () => {
|
|
|
102
158
|
registerRenderer(state, 'Shape', renderer);
|
|
103
159
|
const u = usage({ nodeKinds: ['Shape', 'Sprite'] });
|
|
104
160
|
const out: SceneCoverageEntry[] = [];
|
|
105
|
-
explainScene2DCoverage(out, state, u);
|
|
161
|
+
explainScene2DCoverage(out, state, u, coverageCatalog);
|
|
106
162
|
const gaps = out.filter((e) => e.coverage !== SceneCoverage.Satisfied);
|
|
107
163
|
expect(hasScene2DCoverage(state, u)).toBe(gaps.length === 0);
|
|
108
164
|
});
|
package/src/renderCache.test.ts
CHANGED
|
@@ -117,7 +117,7 @@ describe('registerRenderCacheRenderer', () => {
|
|
|
117
117
|
const state = createRenderState();
|
|
118
118
|
const renderer = { createData: () => null, submit: vi.fn() };
|
|
119
119
|
registerRenderCacheRenderer(state, renderer as any);
|
|
120
|
-
expect(getRenderStateRuntime(state).
|
|
120
|
+
expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, RenderCacheKind)).toBe(renderer);
|
|
121
121
|
});
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -149,3 +149,4 @@ describe('useRenderCache', () => {
|
|
|
149
149
|
expect(getRenderProxyAdapter(stateB, obj as any)).toBeNull();
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
|
+
import { getRegistryTableEntry } from '@flighthq/registry/contract';
|
package/src/renderProxy.test.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { createDisplayObject, setNode2DClip } from '@flighthq/scene2d/contract';
|
|
15
15
|
import { createSprite } from '@flighthq/scene2d/contract';
|
|
16
16
|
import type { ClipRegion, Node, RenderProxy, RenderProxy2D, RenderState } from '@flighthq/types/contract';
|
|
17
|
+
import { RegistryEntryState } from '@flighthq/types/contract';
|
|
17
18
|
|
|
18
19
|
import { registerRenderer } from './renderer';
|
|
19
20
|
import {
|
|
@@ -224,17 +225,23 @@ describe('getOrCreateRenderProxy2D', () => {
|
|
|
224
225
|
expect(a).toBe(b);
|
|
225
226
|
});
|
|
226
227
|
|
|
227
|
-
it('syncs
|
|
228
|
+
it('invalidates and syncs an existing proxy when the renderer table is replaced', () => {
|
|
228
229
|
const state = createRenderState();
|
|
229
230
|
const source = createSprite();
|
|
230
231
|
const node = getOrCreateRenderProxy2D(state, source);
|
|
231
232
|
expect(node.renderer).toBeNull();
|
|
232
233
|
|
|
233
234
|
const renderer = makeRenderer();
|
|
235
|
+
const runtime = getRenderStateRuntime(state);
|
|
236
|
+
const tableBeforeRegistration = runtime.registries.renderers;
|
|
237
|
+
const idBeforeRegistration = runtime.rendererMapId;
|
|
234
238
|
registerRenderer(state, source.kind, renderer as any);
|
|
239
|
+
expect(runtime.registries.renderers).not.toBe(tableBeforeRegistration);
|
|
240
|
+
expect(runtime.rendererMapId).toBe(idBeforeRegistration + 1);
|
|
235
241
|
getOrCreateRenderProxy2D(state, source);
|
|
236
242
|
|
|
237
243
|
expect(node.renderer).toBe(renderer);
|
|
244
|
+
expect(node.rendererMapId).toBe(runtime.rendererMapId);
|
|
238
245
|
});
|
|
239
246
|
});
|
|
240
247
|
|
|
@@ -583,7 +590,13 @@ describe('updateRenderProxy2D', () => {
|
|
|
583
590
|
expect((resolvedData as RenderProxy2D).clipDepth).toBe(0);
|
|
584
591
|
expect(resolvedData.lastChildrenId).toBe(-1);
|
|
585
592
|
});
|
|
586
|
-
getRenderStateRuntime(state)
|
|
593
|
+
const runtime = getRenderStateRuntime(state);
|
|
594
|
+
runtime.registries.colorAdjustments = {
|
|
595
|
+
entry: { state: RegistryEntryState.Bound, value: resolver },
|
|
596
|
+
onMiss: 'Disabled',
|
|
597
|
+
registry: 'ColorAdjustments',
|
|
598
|
+
shape: 'slot',
|
|
599
|
+
};
|
|
587
600
|
|
|
588
601
|
updateRenderProxy2D(state, root, data, undefined);
|
|
589
602
|
|
package/src/renderState.test.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { createMatrix } from '@flighthq/geometry/contract';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { createKeyedTable, createSlotTable } from '@flighthq/registry/contract';
|
|
3
|
+
import type { ColorAdjustmentUnsupportedGuard, RenderState } from '@flighthq/types/contract';
|
|
4
|
+
import { BlendMode, EntityRuntimeKey, RegistryEntryState } from '@flighthq/types/contract';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createRenderState,
|
|
8
|
+
createRenderStateRuntime,
|
|
9
|
+
destroyRenderState,
|
|
10
|
+
getColorAdjustmentUnsupportedGuard,
|
|
11
|
+
getRenderStateRuntime,
|
|
12
|
+
} from './renderState';
|
|
6
13
|
|
|
7
14
|
describe('createRenderState', () => {
|
|
8
15
|
let state: RenderState;
|
|
@@ -30,7 +37,10 @@ describe('createRenderState', () => {
|
|
|
30
37
|
expect(runtime.currentFrameId).toStrictEqual(0);
|
|
31
38
|
expect(runtime.renderProxyMap).toStrictEqual(new WeakMap());
|
|
32
39
|
expect(runtime.renderProxyAdapterMap).toStrictEqual(new WeakMap());
|
|
33
|
-
expect(runtime.
|
|
40
|
+
expect(runtime.registries).toStrictEqual({
|
|
41
|
+
renderers: createKeyedTable('NodeRenderer', 'Unregistered'),
|
|
42
|
+
strokeTessellator: createSlotTable('StrokeTessellator', 'Rasterize'),
|
|
43
|
+
});
|
|
34
44
|
expect(runtime.rendererMapId).toStrictEqual(0);
|
|
35
45
|
expect(runtime.tempStack).toStrictEqual([]);
|
|
36
46
|
});
|
|
@@ -69,11 +79,12 @@ describe('createRenderState', () => {
|
|
|
69
79
|
describe('createRenderStateRuntime', () => {
|
|
70
80
|
it('initializes the machinery fields', () => {
|
|
71
81
|
const runtime = createRenderStateRuntime();
|
|
72
|
-
expect(runtime.colorAdjustmentResolver).toBeNull();
|
|
73
82
|
expect(runtime.currentFrameId).toStrictEqual(0);
|
|
74
83
|
expect(runtime.renderProxyMap).toStrictEqual(new WeakMap());
|
|
75
84
|
expect(runtime.renderProxyAdapterMap).toStrictEqual(new WeakMap());
|
|
76
|
-
expect(runtime.
|
|
85
|
+
expect(runtime.registries.colorAdjustments).toBeUndefined();
|
|
86
|
+
expect(runtime.registries.renderers).toStrictEqual(createKeyedTable('NodeRenderer', 'Unregistered'));
|
|
87
|
+
expect(runtime.registries.strokeTessellator).toStrictEqual(createSlotTable('StrokeTessellator', 'Rasterize'));
|
|
77
88
|
expect(runtime.rendererMapId).toStrictEqual(0);
|
|
78
89
|
expect(runtime.tempStack).toStrictEqual([]);
|
|
79
90
|
});
|
|
@@ -82,7 +93,8 @@ describe('createRenderStateRuntime', () => {
|
|
|
82
93
|
const a = createRenderStateRuntime();
|
|
83
94
|
const b = createRenderStateRuntime();
|
|
84
95
|
expect(a).not.toBe(b);
|
|
85
|
-
expect(a.
|
|
96
|
+
expect(a.registries.renderers).not.toBe(b.registries.renderers);
|
|
97
|
+
expect(a.registries.strokeTessellator).not.toBe(b.registries.strokeTessellator);
|
|
86
98
|
});
|
|
87
99
|
});
|
|
88
100
|
|
|
@@ -91,12 +103,39 @@ describe('destroyRenderState', () => {
|
|
|
91
103
|
const state = createRenderState();
|
|
92
104
|
const runtime = getRenderStateRuntime(state);
|
|
93
105
|
runtime.tempStack.push({} as never);
|
|
94
|
-
runtime.
|
|
106
|
+
runtime.registries.effectPaddingResolvers = {
|
|
107
|
+
entries: new Map(),
|
|
108
|
+
onMiss: 'Zero',
|
|
109
|
+
registry: 'RenderEffectPaddingResolver',
|
|
110
|
+
shape: 'keyed',
|
|
111
|
+
};
|
|
95
112
|
|
|
96
113
|
destroyRenderState(state);
|
|
97
114
|
|
|
98
115
|
expect(runtime.tempStack).toHaveLength(0);
|
|
99
|
-
expect(runtime.
|
|
116
|
+
expect(runtime.registries.effectPaddingResolvers).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getColorAdjustmentUnsupportedGuard', () => {
|
|
121
|
+
it('resolves only a bound guard entry', () => {
|
|
122
|
+
const state = createRenderState();
|
|
123
|
+
const guard: ColorAdjustmentUnsupportedGuard = vi.fn();
|
|
124
|
+
const runtime = getRenderStateRuntime(state);
|
|
125
|
+
|
|
126
|
+
expect(getColorAdjustmentUnsupportedGuard(state)).toBeNull();
|
|
127
|
+
runtime.registries.colorAdjustmentUnsupportedGuard = {
|
|
128
|
+
entry: { state: RegistryEntryState.Bound, value: guard },
|
|
129
|
+
onMiss: 'Disabled',
|
|
130
|
+
registry: 'ColorAdjustmentUnsupportedGuard',
|
|
131
|
+
shape: 'slot',
|
|
132
|
+
};
|
|
133
|
+
expect(getColorAdjustmentUnsupportedGuard(state)).toBe(guard);
|
|
134
|
+
runtime.registries.colorAdjustmentUnsupportedGuard = {
|
|
135
|
+
...runtime.registries.colorAdjustmentUnsupportedGuard,
|
|
136
|
+
entry: { state: RegistryEntryState.Tombstoned },
|
|
137
|
+
};
|
|
138
|
+
expect(getColorAdjustmentUnsupportedGuard(state)).toBeNull();
|
|
100
139
|
});
|
|
101
140
|
});
|
|
102
141
|
|