@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.
@@ -1,4 +1,12 @@
1
- import type { CanvasShapeCommand, Scene2DClipHooks, Renderer, RenderState } from '@flighthq/types/contract';
1
+ import { createSlotTable, getRegistryTableEntry } from '@flighthq/registry/contract';
2
+ import type {
3
+ ColorAdjustmentUnsupportedGuard,
4
+ RenderRootGuard,
5
+ Renderer,
6
+ RenderState,
7
+ Scene2DClipHooks,
8
+ } from '@flighthq/types/contract';
9
+ import { RegistryEntryState } from '@flighthq/types/contract';
2
10
 
3
11
  import {
4
12
  copyAllRenderersFromRenderState,
@@ -26,7 +34,7 @@ describe('copyAllRenderersFromRenderState', () => {
26
34
 
27
35
  copyAllRenderersFromRenderState(target, source);
28
36
 
29
- expect(getRenderStateRuntime(target).rendererMap.get(kind)).toBe(renderer);
37
+ expect(getRegistryTableEntry(getRenderStateRuntime(target).registries.renderers, kind)).toBe(renderer);
30
38
  expect(target.displayObjectClipHooks).toBe(hooks);
31
39
  });
32
40
 
@@ -34,7 +42,7 @@ describe('copyAllRenderersFromRenderState', () => {
34
42
  const source = createRenderState();
35
43
  const target = createRenderState();
36
44
  copyAllRenderersFromRenderState(target, source);
37
- expect(getRenderStateRuntime(target).rendererMap.size).toBe(0);
45
+ expect(getRenderStateRuntime(target).registries.renderers.entries.size).toBe(0);
38
46
  expect(target.displayObjectClipHooks).toBeNull();
39
47
  });
40
48
  });
@@ -47,14 +55,15 @@ describe('copyRenderersFromRenderState', () => {
47
55
  const renderer = { createData: vi.fn(), submit: vi.fn() } as unknown as Renderer;
48
56
  registerRenderer(source, kind, renderer);
49
57
  copyRenderersFromRenderState(target, source);
50
- expect(getRenderStateRuntime(target).rendererMap.get(kind)).toBe(renderer);
58
+ expect(getRenderStateRuntime(target).registries.renderers).toBe(getRenderStateRuntime(source).registries.renderers);
59
+ expect(getRegistryTableEntry(getRenderStateRuntime(target).registries.renderers, kind)).toBe(renderer);
51
60
  });
52
61
 
53
62
  it('is a no-op when source has no renderer registrations', () => {
54
63
  const source = createRenderState();
55
64
  const target = createRenderState();
56
65
  copyRenderersFromRenderState(target, source);
57
- expect(getRenderStateRuntime(target).rendererMap.size).toBe(0);
66
+ expect(getRenderStateRuntime(target).registries.renderers.entries.size).toBe(0);
58
67
  });
59
68
 
60
69
  it('does not affect source rendererMapId', () => {
@@ -67,38 +76,146 @@ describe('copyRenderersFromRenderState', () => {
67
76
  copyRenderersFromRenderState(target, source);
68
77
  expect(getRenderStateRuntime(source).rendererMapId).toBe(sourceIdBeforeCopy);
69
78
  });
79
+
80
+ it('preserves target-only registrations while source registrations win collisions', () => {
81
+ const source = createRenderState();
82
+ const target = createRenderState();
83
+ const sourceRenderer = { submit: vi.fn() } as unknown as Renderer;
84
+ const targetRenderer = { submit: vi.fn() } as unknown as Renderer;
85
+ const targetOnlyRenderer = { submit: vi.fn() } as unknown as Renderer;
86
+ registerRenderer(source, 'shared', sourceRenderer);
87
+ registerRenderer(target, 'shared', targetRenderer);
88
+ registerRenderer(target, 'target-only', targetOnlyRenderer);
89
+
90
+ copyRenderersFromRenderState(target, source);
91
+
92
+ const targetTable = getRenderStateRuntime(target).registries.renderers;
93
+ expect(getRegistryTableEntry(targetTable, 'shared')).toBe(sourceRenderer);
94
+ expect(getRegistryTableEntry(targetTable, 'target-only')).toBe(targetOnlyRenderer);
95
+ });
96
+
97
+ it('shares a fresh immutable snapshot and diverges on later registration', () => {
98
+ const source = createRenderState();
99
+ const target = createRenderState();
100
+ const renderer = { submit: vi.fn() } as unknown as Renderer;
101
+ const lateRenderer = { submit: vi.fn() } as unknown as Renderer;
102
+ registerRenderer(source, 'shared', renderer);
103
+
104
+ copyRenderersFromRenderState(target, source);
105
+
106
+ const sharedSnapshot = getRenderStateRuntime(target).registries.renderers;
107
+ expect(sharedSnapshot).toBe(getRenderStateRuntime(source).registries.renderers);
108
+ registerRenderer(source, 'late', lateRenderer);
109
+ expect(getRenderStateRuntime(target).registries.renderers).toBe(sharedSnapshot);
110
+ expect(getRenderStateRuntime(source).registries.renderers).not.toBe(sharedSnapshot);
111
+ expect(getRegistryTableEntry(sharedSnapshot, 'late')).toBeNull();
112
+ });
70
113
  });
71
114
 
72
115
  describe('copyRenderStateRegistrations', () => {
73
- it('snapshot-copies effect-padding registrations without aliasing the maps', () => {
116
+ it('shares the effect-padding snapshot through distinct aggregates', () => {
74
117
  const source = createRenderState();
75
118
  const target = createRenderState();
76
119
  const resolver = vi.fn();
77
120
  const colorAdjustmentResolver = vi.fn();
78
- getRenderStateRuntime(source).colorAdjustmentResolver = colorAdjustmentResolver;
79
- getRenderStateRuntime(source).renderEffectPaddingResolverRegistry = new Map([['acme.Effect', resolver]]);
121
+ const colorAdjustmentUnsupportedGuard: ColorAdjustmentUnsupportedGuard = vi.fn();
122
+ const renderRootGuard: RenderRootGuard = vi.fn();
123
+ const strokeTessellator = vi.fn(() => null);
124
+ getRenderStateRuntime(source).registries.colorAdjustments = {
125
+ ...createSlotTable('ColorAdjustments', 'Disabled'),
126
+ entry: { state: RegistryEntryState.Bound, value: colorAdjustmentResolver },
127
+ };
128
+ getRenderStateRuntime(source).registries.colorAdjustmentUnsupportedGuard = {
129
+ ...createSlotTable('ColorAdjustmentUnsupportedGuard', 'Disabled'),
130
+ entry: { state: RegistryEntryState.Bound, value: colorAdjustmentUnsupportedGuard },
131
+ };
132
+ getRenderStateRuntime(source).registries.strokeTessellator = {
133
+ ...getRenderStateRuntime(source).registries.strokeTessellator,
134
+ entry: { state: RegistryEntryState.Bound, value: strokeTessellator },
135
+ };
136
+ getRenderStateRuntime(source).registries.renderRootGuard = {
137
+ ...createSlotTable('RenderRootGuard', 'Disabled'),
138
+ entry: { state: RegistryEntryState.Bound, value: renderRootGuard },
139
+ };
140
+ getRenderStateRuntime(source).registries.effectPaddingResolvers = {
141
+ entries: new Map([['acme.Effect', { state: RegistryEntryState.Bound, value: resolver }]]),
142
+ onMiss: 'Zero',
143
+ registry: 'RenderEffectPaddingResolver',
144
+ shape: 'keyed',
145
+ };
80
146
 
81
147
  copyRenderStateRegistrations(target, source);
82
148
 
83
- expect(getRenderStateRuntime(target).colorAdjustmentResolver).toBe(colorAdjustmentResolver);
84
- expect(getRenderStateRuntime(target).renderEffectPaddingResolverRegistry).not.toBe(
85
- getRenderStateRuntime(source).renderEffectPaddingResolverRegistry,
149
+ const sourceRuntime = getRenderStateRuntime(source);
150
+ const targetRuntime = getRenderStateRuntime(target);
151
+ expect(targetRuntime.registries).not.toBe(sourceRuntime.registries);
152
+ expect(targetRuntime.registries.colorAdjustments).toBe(sourceRuntime.registries.colorAdjustments);
153
+ expect(targetRuntime.registries.colorAdjustments?.entry).toEqual({
154
+ state: RegistryEntryState.Bound,
155
+ value: colorAdjustmentResolver,
156
+ });
157
+ const sharedColorSnapshot = targetRuntime.registries.colorAdjustments;
158
+ sourceRuntime.registries.colorAdjustments = undefined;
159
+ expect(sourceRuntime.registries.colorAdjustments).not.toBe(sharedColorSnapshot);
160
+ expect(targetRuntime.registries.colorAdjustments).toBe(sharedColorSnapshot);
161
+ expect(targetRuntime.registries.colorAdjustments?.entry?.state).toBe(RegistryEntryState.Bound);
162
+ expect(targetRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(
163
+ sourceRuntime.registries.colorAdjustmentUnsupportedGuard,
86
164
  );
87
- expect(getRenderStateRuntime(target).renderEffectPaddingResolverRegistry?.get('acme.Effect')).toBe(resolver);
165
+ expect(targetRuntime.registries.colorAdjustmentUnsupportedGuard?.entry).toEqual({
166
+ state: RegistryEntryState.Bound,
167
+ value: colorAdjustmentUnsupportedGuard,
168
+ });
169
+ const sharedGuardSnapshot = targetRuntime.registries.colorAdjustmentUnsupportedGuard;
170
+ sourceRuntime.registries.colorAdjustmentUnsupportedGuard = undefined;
171
+ expect(sourceRuntime.registries.colorAdjustmentUnsupportedGuard).not.toBe(sharedGuardSnapshot);
172
+ expect(targetRuntime.registries.colorAdjustmentUnsupportedGuard).toBe(sharedGuardSnapshot);
173
+ expect(targetRuntime.registries.strokeTessellator).toBe(sourceRuntime.registries.strokeTessellator);
174
+ expect(targetRuntime.registries.strokeTessellator.entry).toEqual({
175
+ state: RegistryEntryState.Bound,
176
+ value: strokeTessellator,
177
+ });
178
+ const sharedStrokeSnapshot = targetRuntime.registries.strokeTessellator;
179
+ sourceRuntime.registries.strokeTessellator = { ...sourceRuntime.registries.strokeTessellator, entry: null };
180
+ expect(sourceRuntime.registries.strokeTessellator).not.toBe(sharedStrokeSnapshot);
181
+ expect(targetRuntime.registries.strokeTessellator).toBe(sharedStrokeSnapshot);
182
+ expect(targetRuntime.registries.strokeTessellator.entry?.state).toBe(RegistryEntryState.Bound);
183
+ expect(targetRuntime.registries.effectPaddingResolvers).toBe(sourceRuntime.registries.effectPaddingResolvers);
184
+ expect(targetRuntime.registries.effectPaddingResolvers?.entries.get('acme.Effect')).toEqual({
185
+ state: RegistryEntryState.Bound,
186
+ value: resolver,
187
+ });
188
+ expect(targetRuntime.registries.renderRootGuard).toBe(sourceRuntime.registries.renderRootGuard);
189
+ expect(targetRuntime.registries.renderRootGuard?.entry).toEqual({
190
+ state: RegistryEntryState.Bound,
191
+ value: renderRootGuard,
192
+ });
193
+ const sharedRootGuardSnapshot = targetRuntime.registries.renderRootGuard;
194
+ sourceRuntime.registries.renderRootGuard = undefined;
195
+ expect(sourceRuntime.registries.renderRootGuard).not.toBe(sharedRootGuardSnapshot);
196
+ expect(targetRuntime.registries.renderRootGuard).toBe(sharedRootGuardSnapshot);
88
197
  });
89
198
 
90
199
  it('snapshot-copies the shape-command registry without aliasing the map', () => {
91
200
  const source = createRenderState();
92
201
  const target = createRenderState();
93
- const command = { key: 'beginFill', draw: vi.fn() } as unknown as CanvasShapeCommand;
94
- getRenderStateRuntime(source).canvasShapeCommandRegistry = new Map([['beginFill', command]]);
202
+ const command = { key: 'beginFill', draw: vi.fn() } as never;
203
+ getRenderStateRuntime(source).registries.canvasShapeCommands = {
204
+ entries: new Map([['beginFill', { state: RegistryEntryState.Bound, value: command }]]),
205
+ onMiss: 'Unregistered',
206
+ registry: 'CanvasShapeCommand',
207
+ shape: 'keyed',
208
+ };
95
209
 
96
210
  copyRenderStateRegistrations(target, source);
97
211
 
98
- expect(getRenderStateRuntime(target).canvasShapeCommandRegistry).not.toBe(
99
- getRenderStateRuntime(source).canvasShapeCommandRegistry,
212
+ expect(getRenderStateRuntime(target).registries.canvasShapeCommands).toBe(
213
+ getRenderStateRuntime(source).registries.canvasShapeCommands,
100
214
  );
101
- expect(getRenderStateRuntime(target).canvasShapeCommandRegistry?.get('beginFill')).toBe(command);
215
+ expect(getRenderStateRuntime(target).registries.canvasShapeCommands?.entries.get('beginFill')).toEqual({
216
+ state: RegistryEntryState.Bound,
217
+ value: command,
218
+ });
102
219
  });
103
220
 
104
221
  it('leaves the shape-command registry null when the source never registered one', () => {
@@ -107,7 +224,7 @@ describe('copyRenderStateRegistrations', () => {
107
224
 
108
225
  copyRenderStateRegistrations(target, source);
109
226
 
110
- expect(getRenderStateRuntime(target).canvasShapeCommandRegistry).toBeNull();
227
+ expect(getRenderStateRuntime(target).registries.canvasShapeCommands).toBeUndefined();
111
228
  });
112
229
  });
113
230
 
@@ -134,9 +251,11 @@ describe('registerRenderer', () => {
134
251
  });
135
252
 
136
253
  it('should register a new renderer', () => {
137
- expect(getRenderStateRuntime(state).rendererMap.has(kindA)).toBe(false);
254
+ const before = getRenderStateRuntime(state).registries.renderers;
255
+ expect(getRegistryTableEntry(before, kindA)).toBeNull();
138
256
  registerRenderer(state, kindA, renderer1);
139
- expect(getRenderStateRuntime(state).rendererMap.get(kindA)).toBe(renderer1);
257
+ expect(getRenderStateRuntime(state).registries.renderers).not.toBe(before);
258
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindA)).toBe(renderer1);
140
259
  expect(getRenderStateRuntime(state).rendererMapId).toBe(1);
141
260
  });
142
261
 
@@ -144,7 +263,7 @@ describe('registerRenderer', () => {
144
263
  registerRenderer(state, kindA, renderer1);
145
264
  const idAfterFirst = getRenderStateRuntime(state).rendererMapId;
146
265
  registerRenderer(state, kindB, renderer2);
147
- expect(getRenderStateRuntime(state).rendererMap.get(kindB)).toBe(renderer2);
266
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindB)).toBe(renderer2);
148
267
  expect(getRenderStateRuntime(state).rendererMapId).toBe(idAfterFirst + 1);
149
268
  });
150
269
 
@@ -152,7 +271,7 @@ describe('registerRenderer', () => {
152
271
  registerRenderer(state, kindA, renderer1);
153
272
  const idBefore = getRenderStateRuntime(state).rendererMapId;
154
273
  registerRenderer(state, kindA, renderer1);
155
- expect(getRenderStateRuntime(state).rendererMap.get(kindA)).toBe(renderer1);
274
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindA)).toBe(renderer1);
156
275
  expect(getRenderStateRuntime(state).rendererMapId).toBe(idBefore);
157
276
  });
158
277
 
@@ -160,7 +279,7 @@ describe('registerRenderer', () => {
160
279
  registerRenderer(state, kindA, renderer1);
161
280
  const idBefore = getRenderStateRuntime(state).rendererMapId;
162
281
  registerRenderer(state, kindA, renderer2);
163
- expect(getRenderStateRuntime(state).rendererMap.get(kindA)).toBe(renderer2);
282
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindA)).toBe(renderer2);
164
283
  expect(getRenderStateRuntime(state).rendererMapId).toBe(idBefore + 1);
165
284
  });
166
285
 
@@ -182,8 +301,8 @@ describe('registerRenderers', () => {
182
301
  [kindA, rendererA],
183
302
  [kindB, rendererB],
184
303
  ]);
185
- expect(getRenderStateRuntime(state).rendererMap.get(kindA)).toBe(rendererA);
186
- expect(getRenderStateRuntime(state).rendererMap.get(kindB)).toBe(rendererB);
304
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindA)).toBe(rendererA);
305
+ expect(getRegistryTableEntry(getRenderStateRuntime(state).registries.renderers, kindB)).toBe(rendererB);
187
306
  });
188
307
 
189
308
  it('should register nothing for an empty set', () => {