Skip to content

Commit ce9f3ec

Browse files
committed
fix(core): share registry singleton state
1 parent 9ea256b commit ce9f3ec

21 files changed

Lines changed: 431 additions & 173 deletions

packages/vrender-core/__tests__/unit/common/contribution-provider.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ describe('contribution-provider', () => {
104104
expect(spy2).toHaveBeenCalledTimes(1);
105105
});
106106

107+
test('ContributionStore should use realm-level shared state for duplicated ESM entry evaluation', () => {
108+
const id = Symbol('shared-svc');
109+
const cache = createProviderCache({ isBound: jest.fn(() => false), getAll: jest.fn(() => []) }, id);
110+
const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/contribution-store-state')];
111+
112+
expect(state).toBeDefined();
113+
expect(state.store).toBe(ContributionStore.store);
114+
expect(state.store.get(id)?.has(cache)).toBe(true);
115+
});
116+
107117
test('bindContributionProviderNoSingletonScope does not call inSingletonScope', () => {
108118
let factory: DynamicValueFactory | undefined;
109119

packages/vrender-core/__tests__/unit/entries/runtime-installer.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { application } from '../../../src/application';
2-
import { AppContext, configureRuntimeApplicationForApp } from '../../../src/entries';
2+
import { AppContext, configureRuntimeApplicationForApp, getRuntimeInstallerBindingContext } from '../../../src/entries';
33
import { DefaultIncrementalDrawContribution } from '../../../src/render/contributions/render/incremental-draw-contribution';
44

55
describe('runtime installer', () => {
6+
test('runtime installer should use realm-level shared state for duplicated ESM entry evaluation', () => {
7+
const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/runtime-installer-state')];
8+
9+
expect(state).toBeDefined();
10+
expect(state.runtimeInstallerContext).toBe(getRuntimeInstallerBindingContext());
11+
});
12+
613
test('configureRuntimeApplicationForApp should configure an app-scoped incremental draw contribution factory', () => {
714
const context = new AppContext();
815
const app = {

packages/vrender-core/__tests__/unit/factory/factory.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
IWindow
99
} from '../../../src/interface';
1010
import type { IGraphic, IGraphicAttribute } from '../../../src/interface/graphic';
11-
import { GraphicFactory, LayerFactory, StageFactory } from '../../../src/factory';
11+
import { Factory, GraphicFactory, LayerFactory, StageFactory } from '../../../src/factory';
1212

1313
class StageStub {
1414
readonly params: Partial<IStageParams>;
@@ -41,6 +41,17 @@ class GraphicStub {
4141
}
4242

4343
describe('factory module', () => {
44+
test('Factory plugin registry should use realm-level shared state for duplicated ESM entry evaluation', () => {
45+
class PluginStub {}
46+
47+
Factory.registerPlugin('unit-plugin', PluginStub);
48+
const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/factory-state')];
49+
50+
expect(state).toBeDefined();
51+
expect(state.pluginClasses['unit-plugin']).toBe(PluginStub);
52+
expect(Factory.getPlugin('unit-plugin')).toBe(PluginStub);
53+
});
54+
4455
test('StageFactory should create stage instances with forwarded params', () => {
4556
const factory = new StageFactory(StageStub as any);
4657
const params = { width: 320, height: 180 };

packages/vrender-core/__tests__/unit/graphic/graphic-factory-migration.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
import type { IRectGraphicAttribute } from '../../../src/interface';
22
import { createGraphic, graphicCreator, registerGraphic } from '../../../src/graphic';
3+
import { Arc3d } from '../../../src/graphic/arc3d';
34
import { DefaultGraphicService } from '../../../src/graphic/graphic-service/graphic-service';
5+
import { Group } from '../../../src/graphic/group';
46
import { Rect } from '../../../src/graphic/rect';
7+
import { registerArc3dGraphic } from '../../../src/register/register-arc3d';
8+
import { registerGroupGraphic } from '../../../src/register/register-group';
59
import { registerRectGraphic } from '../../../src/register/register-rect';
610

711
class GraphicStub {
812
constructor(public readonly attribute: Record<string, unknown>) {}
913
}
1014

1115
describe('graphic factory migration', () => {
16+
test('graphic registry should use realm-level shared state for duplicated ESM entry evaluation', () => {
17+
registerGraphic('realm-shared-stub', GraphicStub as any);
18+
19+
const registryState = (globalThis as any)[Symbol.for('@visactor/vrender-core/graphic-registry')];
20+
21+
expect(registryState).toBeDefined();
22+
expect(registryState.graphicCreator).toBe(graphicCreator);
23+
expect(registryState.graphicFactory.create('realm-shared-stub', { x: 1 })).toBeInstanceOf(GraphicStub);
24+
expect(createGraphic('realm-shared-stub', { x: 2 })).toBeInstanceOf(GraphicStub);
25+
});
26+
1227
test('registerGraphic should register creators for createGraphic', () => {
1328
registerGraphic('unit-stub', GraphicStub as any);
1429

@@ -37,6 +52,22 @@ describe('graphic factory migration', () => {
3752
expect((rect as Rect).attribute.width).toBe(100);
3853
});
3954

55+
test('registerGroupGraphic should enable createGraphic for group from a separate register entry', () => {
56+
registerGroupGraphic();
57+
58+
const group = createGraphic('group', {});
59+
60+
expect(group).toBeInstanceOf(Group);
61+
});
62+
63+
test('registerArc3dGraphic should enable createGraphic for arc3d from a separate register entry', () => {
64+
registerArc3dGraphic();
65+
66+
const arc3d = createGraphic('arc3d', {});
67+
68+
expect(arc3d).toBeInstanceOf(Arc3d);
69+
});
70+
4071
test('DefaultGraphicService should default to the shared graphic creator adapter', () => {
4172
const service = new DefaultGraphicService();
4273

packages/vrender-core/__tests__/unit/legacy/legacy-binding-context.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,14 @@ describe('legacy binding context', () => {
113113
expect(loadRenderContributions).toHaveBeenCalledTimes(1);
114114
});
115115
});
116+
117+
test('legacy bootstrap should use realm-level shared state for duplicated ESM entry evaluation', () => {
118+
jest.isolateModules(() => {
119+
const bootstrap = require('../../../src/legacy/bootstrap');
120+
const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/legacy-bootstrap-state')];
121+
122+
expect(state).toBeDefined();
123+
expect(state.legacyBindingContext).toBe(bootstrap.getLegacyBindingContext());
124+
});
125+
});
116126
});

packages/vrender-core/__tests__/unit/modules/container-compatibility.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ function readArtifact(relativePath: string) {
1212
}
1313

1414
describe('vrender-core container compatibility', () => {
15+
test('application should use realm-level shared state for duplicated ESM entry evaluation', () => {
16+
const { application } = require(path.join(packageRoot, 'src/application'));
17+
const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/application-state')];
18+
19+
expect(state).toBeDefined();
20+
expect(state.application).toBe(application);
21+
});
22+
1523
test('es artifacts should expose legacy container compatibility surface', () => {
1624
expect(readArtifact('es/modules.js')).toContain('export const container');
1725
expect(readArtifact('es/modules.d.ts')).toContain('container');

packages/vrender-core/__tests__/unit/public-subpath-exports.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ExpectedSubpath = {
1919

2020
const expectedExports: ExpectedSubpath[] = [
2121
{ subpath: './graphic/creator', source: 'src/graphic/creator.ts' },
22+
{ subpath: './graphic/graphic-registry', source: 'src/graphic/graphic-registry.ts' },
2223
{ subpath: './graphic/base', source: 'src/graphic/base.ts' },
2324
{ subpath: './graphic/modules', source: 'src/graphic/modules.ts' },
2425
{ subpath: './graphic/symbol', source: 'src/graphic/symbol.ts' },

packages/vrender-core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"graphic/base": ["es/graphic/base.d.ts"],
2828
"graphic/builtin-symbol": ["es/graphic/builtin-symbol.d.ts"],
2929
"graphic/creator": ["es/graphic/creator.d.ts"],
30+
"graphic/graphic-registry": ["es/graphic/graphic-registry.d.ts"],
3031
"graphic/group": ["es/graphic/group.d.ts"],
3132
"graphic/modules": ["es/graphic/modules.d.ts"],
3233
"graphic/symbol": ["es/graphic/symbol.d.ts"],
@@ -185,6 +186,11 @@
185186
"import": "./es/graphic/creator.js",
186187
"require": "./cjs/graphic/creator.js"
187188
},
189+
"./graphic/graphic-registry": {
190+
"types": "./es/graphic/graphic-registry.d.ts",
191+
"import": "./es/graphic/graphic-registry.js",
192+
"require": "./cjs/graphic/graphic-registry.js"
193+
},
188194
"./graphic/group": {
189195
"types": "./es/graphic/group.d.ts",
190196
"import": "./es/graphic/group.js",
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { IGraphicUtil, ILayerService, ITransformUtil } from './interface/core';
2+
import type {
3+
ICanvasFactory,
4+
IContext2dFactory,
5+
IGlobal,
6+
IGraphicService,
7+
ILayerHandlerContribution,
8+
IPickerService,
9+
IPluginService,
10+
IRenderService,
11+
IWindow,
12+
IWindowHandlerContribution,
13+
LayerMode
14+
} from './interface';
15+
import type { IDrawContribution } from './interface/render';
16+
17+
export class Application {
18+
global: IGlobal;
19+
graphicUtil: IGraphicUtil;
20+
graphicService: IGraphicService;
21+
renderService: IRenderService;
22+
renderServiceFactory?: () => IRenderService;
23+
pluginService?: IPluginService;
24+
pluginServiceFactory?: () => IPluginService;
25+
pickerServiceFactory?: () => IPickerService;
26+
windowFactory?: () => IWindow;
27+
windowHandlerFactory?: (env: string) => IWindowHandlerContribution;
28+
layerHandlerFactory?: (layerMode: LayerMode) => ILayerHandlerContribution;
29+
incrementalDrawContributionFactory?: () => IDrawContribution;
30+
canvasFactory?: (env: string) => ICanvasFactory | undefined;
31+
context2dFactory?: (env: string) => IContext2dFactory | undefined;
32+
transformUtil: ITransformUtil;
33+
layerService: ILayerService;
34+
}
35+
36+
export const APPLICATION_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/application-state');
37+
38+
export interface IApplicationState {
39+
application: Application;
40+
}
41+
42+
function createApplicationState(): IApplicationState {
43+
return {
44+
application: new Application()
45+
};
46+
}
47+
48+
export function getApplicationState(): IApplicationState {
49+
const scope = globalThis as typeof globalThis & { [APPLICATION_STATE_SYMBOL]?: IApplicationState };
50+
51+
if (!scope[APPLICATION_STATE_SYMBOL]) {
52+
scope[APPLICATION_STATE_SYMBOL] = createApplicationState();
53+
}
54+
55+
return scope[APPLICATION_STATE_SYMBOL] as IApplicationState;
56+
}
57+
58+
export const application = getApplicationState().application;
Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,2 @@
1-
import type { IGraphicUtil, ILayerService, ITransformUtil } from './interface/core';
2-
import type {
3-
ICanvasFactory,
4-
IContext2dFactory,
5-
IGlobal,
6-
IGraphicService,
7-
ILayerHandlerContribution,
8-
IPickerService,
9-
IPluginService,
10-
IRenderService,
11-
IWindow,
12-
IWindowHandlerContribution,
13-
LayerMode
14-
} from './interface';
15-
import type { IDrawContribution } from './interface/render';
16-
17-
export class Application {
18-
global: IGlobal;
19-
graphicUtil: IGraphicUtil;
20-
graphicService: IGraphicService;
21-
renderService: IRenderService;
22-
renderServiceFactory?: () => IRenderService;
23-
pluginService?: IPluginService;
24-
pluginServiceFactory?: () => IPluginService;
25-
pickerServiceFactory?: () => IPickerService;
26-
windowFactory?: () => IWindow;
27-
windowHandlerFactory?: (env: string) => IWindowHandlerContribution;
28-
layerHandlerFactory?: (layerMode: LayerMode) => ILayerHandlerContribution;
29-
incrementalDrawContributionFactory?: () => IDrawContribution;
30-
canvasFactory?: (env: string) => ICanvasFactory | undefined;
31-
context2dFactory?: (env: string) => IContext2dFactory | undefined;
32-
transformUtil: ITransformUtil;
33-
layerService: ILayerService;
34-
}
35-
36-
export const application = new Application();
1+
export { application, Application, APPLICATION_STATE_SYMBOL, getApplicationState } from './application-state';
2+
export type { IApplicationState } from './application-state';

0 commit comments

Comments
 (0)