diff --git a/packages/vrender-animate/__tests__/unit/graphic-state-extension.test.ts b/packages/vrender-animate/__tests__/unit/graphic-state-extension.test.ts index ca9efde42..806a7a725 100644 --- a/packages/vrender-animate/__tests__/unit/graphic-state-extension.test.ts +++ b/packages/vrender-animate/__tests__/unit/graphic-state-extension.test.ts @@ -1,5 +1,8 @@ +import { createGraphic } from '@visactor/vrender-core/graphic/creator'; +import { registerGroupGraphic } from '@visactor/vrender-core/register/register-group'; import { GraphicStateExtension } from '../../src/state/graphic-extension'; import { AnimationStateManager } from '../../src/state/animation-state'; +import { registerAnimate } from '../../src/register'; describe('GraphicStateExtension', () => { afterEach(() => { @@ -71,4 +74,14 @@ describe('GraphicStateExtension', () => { expect(graphic.currentStates).toEqual(['hover']); expect(graphic.normalAttrs).toEqual({ fill: 'blue' }); }); + + test('registerAnimate should mix animation APIs into graphics created through core creator subpath', () => { + registerAnimate(); + registerGroupGraphic(); + + const group = createGraphic('group', {}); + + expect(typeof (group as any).applyAnimationState).toBe('function'); + expect(typeof (group as any).clearAnimationStates).toBe('function'); + }); }); diff --git a/packages/vrender-core/__tests__/unit/common/contribution-provider.test.ts b/packages/vrender-core/__tests__/unit/common/contribution-provider.test.ts index 437314582..e662ead07 100644 --- a/packages/vrender-core/__tests__/unit/common/contribution-provider.test.ts +++ b/packages/vrender-core/__tests__/unit/common/contribution-provider.test.ts @@ -104,6 +104,16 @@ describe('contribution-provider', () => { expect(spy2).toHaveBeenCalledTimes(1); }); + test('ContributionStore should use realm-level shared state for duplicated ESM entry evaluation', () => { + const id = Symbol('shared-svc'); + const cache = createProviderCache({ isBound: jest.fn(() => false), getAll: jest.fn(() => []) }, id); + const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/contribution-store-state')]; + + expect(state).toBeDefined(); + expect(state.store).toBe(ContributionStore.store); + expect(state.store.get(id)?.has(cache)).toBe(true); + }); + test('bindContributionProviderNoSingletonScope does not call inSingletonScope', () => { let factory: DynamicValueFactory | undefined; diff --git a/packages/vrender-core/__tests__/unit/entries/runtime-installer.test.ts b/packages/vrender-core/__tests__/unit/entries/runtime-installer.test.ts index c8dc537b0..949b70c66 100644 --- a/packages/vrender-core/__tests__/unit/entries/runtime-installer.test.ts +++ b/packages/vrender-core/__tests__/unit/entries/runtime-installer.test.ts @@ -1,8 +1,15 @@ import { application } from '../../../src/application'; -import { AppContext, configureRuntimeApplicationForApp } from '../../../src/entries'; +import { AppContext, configureRuntimeApplicationForApp, getRuntimeInstallerBindingContext } from '../../../src/entries'; import { DefaultIncrementalDrawContribution } from '../../../src/render/contributions/render/incremental-draw-contribution'; describe('runtime installer', () => { + test('runtime installer should use realm-level shared state for duplicated ESM entry evaluation', () => { + const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/runtime-installer-state')]; + + expect(state).toBeDefined(); + expect(state.runtimeInstallerContext).toBe(getRuntimeInstallerBindingContext()); + }); + test('configureRuntimeApplicationForApp should configure an app-scoped incremental draw contribution factory', () => { const context = new AppContext(); const app = { diff --git a/packages/vrender-core/__tests__/unit/factory/factory.test.ts b/packages/vrender-core/__tests__/unit/factory/factory.test.ts index bc80845f5..871162899 100644 --- a/packages/vrender-core/__tests__/unit/factory/factory.test.ts +++ b/packages/vrender-core/__tests__/unit/factory/factory.test.ts @@ -8,7 +8,7 @@ import type { IWindow } from '../../../src/interface'; import type { IGraphic, IGraphicAttribute } from '../../../src/interface/graphic'; -import { GraphicFactory, LayerFactory, StageFactory } from '../../../src/factory'; +import { Factory, GraphicFactory, LayerFactory, StageFactory } from '../../../src/factory'; class StageStub { readonly params: Partial; @@ -41,6 +41,17 @@ class GraphicStub { } describe('factory module', () => { + test('Factory plugin registry should use realm-level shared state for duplicated ESM entry evaluation', () => { + class PluginStub {} + + Factory.registerPlugin('unit-plugin', PluginStub); + const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/factory-state')]; + + expect(state).toBeDefined(); + expect(state.pluginClasses['unit-plugin']).toBe(PluginStub); + expect(Factory.getPlugin('unit-plugin')).toBe(PluginStub); + }); + test('StageFactory should create stage instances with forwarded params', () => { const factory = new StageFactory(StageStub as any); const params = { width: 320, height: 180 }; diff --git a/packages/vrender-core/__tests__/unit/graphic/graphic-factory-migration.test.ts b/packages/vrender-core/__tests__/unit/graphic/graphic-factory-migration.test.ts index 0207d37cf..6ecd94d2f 100644 --- a/packages/vrender-core/__tests__/unit/graphic/graphic-factory-migration.test.ts +++ b/packages/vrender-core/__tests__/unit/graphic/graphic-factory-migration.test.ts @@ -1,7 +1,12 @@ import type { IRectGraphicAttribute } from '../../../src/interface'; import { createGraphic, graphicCreator, registerGraphic } from '../../../src/graphic'; +import { Arc3d } from '../../../src/graphic/arc3d'; +import { Graphic } from '../../../src/graphic/base'; import { DefaultGraphicService } from '../../../src/graphic/graphic-service/graphic-service'; +import { Group } from '../../../src/graphic/group'; import { Rect } from '../../../src/graphic/rect'; +import { registerArc3dGraphic } from '../../../src/register/register-arc3d'; +import { registerGroupGraphic } from '../../../src/register/register-group'; import { registerRectGraphic } from '../../../src/register/register-rect'; class GraphicStub { @@ -9,6 +14,31 @@ class GraphicStub { } describe('graphic factory migration', () => { + test('graphic registry should use realm-level shared state for duplicated ESM entry evaluation', () => { + registerGraphic('realm-shared-stub', GraphicStub as any); + + const registryState = (globalThis as any)[Symbol.for('@visactor/vrender-core/graphic-registry')]; + + expect(registryState).toBeDefined(); + expect(registryState.graphicCreator).toBe(graphicCreator); + expect(registryState.graphicFactory.create('realm-shared-stub', { x: 1 })).toBeInstanceOf(GraphicStub); + expect(createGraphic('realm-shared-stub', { x: 2 })).toBeInstanceOf(GraphicStub); + }); + + test('Graphic class should use realm-level shared state for duplicated ESM entry evaluation', () => { + registerGroupGraphic(); + registerArc3dGraphic(); + + const classState = (globalThis as any)[Symbol.for('@visactor/vrender-core/graphic-class')]; + const group = createGraphic('group', {}); + const arc3d = createGraphic('arc3d', {}); + + expect(classState).toBeDefined(); + expect(classState.Graphic).toBe(Graphic); + expect(group).toBeInstanceOf(classState.Graphic); + expect(arc3d).toBeInstanceOf(classState.Graphic); + }); + test('registerGraphic should register creators for createGraphic', () => { registerGraphic('unit-stub', GraphicStub as any); @@ -37,6 +67,22 @@ describe('graphic factory migration', () => { expect((rect as Rect).attribute.width).toBe(100); }); + test('registerGroupGraphic should enable createGraphic for group from a separate register entry', () => { + registerGroupGraphic(); + + const group = createGraphic('group', {}); + + expect(group).toBeInstanceOf(Group); + }); + + test('registerArc3dGraphic should enable createGraphic for arc3d from a separate register entry', () => { + registerArc3dGraphic(); + + const arc3d = createGraphic('arc3d', {}); + + expect(arc3d).toBeInstanceOf(Arc3d); + }); + test('DefaultGraphicService should default to the shared graphic creator adapter', () => { const service = new DefaultGraphicService(); diff --git a/packages/vrender-core/__tests__/unit/legacy/legacy-binding-context.test.ts b/packages/vrender-core/__tests__/unit/legacy/legacy-binding-context.test.ts index 3eb3e4984..ab3dd1b7a 100644 --- a/packages/vrender-core/__tests__/unit/legacy/legacy-binding-context.test.ts +++ b/packages/vrender-core/__tests__/unit/legacy/legacy-binding-context.test.ts @@ -113,4 +113,14 @@ describe('legacy binding context', () => { expect(loadRenderContributions).toHaveBeenCalledTimes(1); }); }); + + test('legacy bootstrap should use realm-level shared state for duplicated ESM entry evaluation', () => { + jest.isolateModules(() => { + const bootstrap = require('../../../src/legacy/bootstrap'); + const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/legacy-bootstrap-state')]; + + expect(state).toBeDefined(); + expect(state.legacyBindingContext).toBe(bootstrap.getLegacyBindingContext()); + }); + }); }); diff --git a/packages/vrender-core/__tests__/unit/modules/container-compatibility.test.ts b/packages/vrender-core/__tests__/unit/modules/container-compatibility.test.ts index 907861887..84364003c 100644 --- a/packages/vrender-core/__tests__/unit/modules/container-compatibility.test.ts +++ b/packages/vrender-core/__tests__/unit/modules/container-compatibility.test.ts @@ -12,6 +12,14 @@ function readArtifact(relativePath: string) { } describe('vrender-core container compatibility', () => { + test('application should use realm-level shared state for duplicated ESM entry evaluation', () => { + const { application } = require(path.join(packageRoot, 'src/application')); + const state = (globalThis as any)[Symbol.for('@visactor/vrender-core/application-state')]; + + expect(state).toBeDefined(); + expect(state.application).toBe(application); + }); + test('es artifacts should expose legacy container compatibility surface', () => { expect(readArtifact('es/modules.js')).toContain('export const container'); expect(readArtifact('es/modules.d.ts')).toContain('container'); diff --git a/packages/vrender-core/__tests__/unit/public-subpath-exports.test.ts b/packages/vrender-core/__tests__/unit/public-subpath-exports.test.ts index 2748986c5..49db836f2 100644 --- a/packages/vrender-core/__tests__/unit/public-subpath-exports.test.ts +++ b/packages/vrender-core/__tests__/unit/public-subpath-exports.test.ts @@ -19,6 +19,7 @@ type ExpectedSubpath = { const expectedExports: ExpectedSubpath[] = [ { subpath: './graphic/creator', source: 'src/graphic/creator.ts' }, + { subpath: './graphic/graphic-registry', source: 'src/graphic/graphic-registry.ts' }, { subpath: './graphic/base', source: 'src/graphic/base.ts' }, { subpath: './graphic/modules', source: 'src/graphic/modules.ts' }, { subpath: './graphic/symbol', source: 'src/graphic/symbol.ts' }, diff --git a/packages/vrender-core/package.json b/packages/vrender-core/package.json index 95393fb53..7f18176ba 100644 --- a/packages/vrender-core/package.json +++ b/packages/vrender-core/package.json @@ -27,6 +27,7 @@ "graphic/base": ["es/graphic/base.d.ts"], "graphic/builtin-symbol": ["es/graphic/builtin-symbol.d.ts"], "graphic/creator": ["es/graphic/creator.d.ts"], + "graphic/graphic-registry": ["es/graphic/graphic-registry.d.ts"], "graphic/group": ["es/graphic/group.d.ts"], "graphic/modules": ["es/graphic/modules.d.ts"], "graphic/symbol": ["es/graphic/symbol.d.ts"], @@ -185,6 +186,11 @@ "import": "./es/graphic/creator.js", "require": "./cjs/graphic/creator.js" }, + "./graphic/graphic-registry": { + "types": "./es/graphic/graphic-registry.d.ts", + "import": "./es/graphic/graphic-registry.js", + "require": "./cjs/graphic/graphic-registry.js" + }, "./graphic/group": { "types": "./es/graphic/group.d.ts", "import": "./es/graphic/group.js", diff --git a/packages/vrender-core/src/application-state.ts b/packages/vrender-core/src/application-state.ts new file mode 100644 index 000000000..60671ced3 --- /dev/null +++ b/packages/vrender-core/src/application-state.ts @@ -0,0 +1,58 @@ +import type { IGraphicUtil, ILayerService, ITransformUtil } from './interface/core'; +import type { + ICanvasFactory, + IContext2dFactory, + IGlobal, + IGraphicService, + ILayerHandlerContribution, + IPickerService, + IPluginService, + IRenderService, + IWindow, + IWindowHandlerContribution, + LayerMode +} from './interface'; +import type { IDrawContribution } from './interface/render'; + +export class Application { + global: IGlobal; + graphicUtil: IGraphicUtil; + graphicService: IGraphicService; + renderService: IRenderService; + renderServiceFactory?: () => IRenderService; + pluginService?: IPluginService; + pluginServiceFactory?: () => IPluginService; + pickerServiceFactory?: () => IPickerService; + windowFactory?: () => IWindow; + windowHandlerFactory?: (env: string) => IWindowHandlerContribution; + layerHandlerFactory?: (layerMode: LayerMode) => ILayerHandlerContribution; + incrementalDrawContributionFactory?: () => IDrawContribution; + canvasFactory?: (env: string) => ICanvasFactory | undefined; + context2dFactory?: (env: string) => IContext2dFactory | undefined; + transformUtil: ITransformUtil; + layerService: ILayerService; +} + +export const APPLICATION_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/application-state'); + +export interface IApplicationState { + application: Application; +} + +function createApplicationState(): IApplicationState { + return { + application: new Application() + }; +} + +export function getApplicationState(): IApplicationState { + const scope = globalThis as typeof globalThis & { [APPLICATION_STATE_SYMBOL]?: IApplicationState }; + + if (!scope[APPLICATION_STATE_SYMBOL]) { + scope[APPLICATION_STATE_SYMBOL] = createApplicationState(); + } + + return scope[APPLICATION_STATE_SYMBOL] as IApplicationState; +} + +export const application = getApplicationState().application; diff --git a/packages/vrender-core/src/application.ts b/packages/vrender-core/src/application.ts index 494eafb79..c58bac766 100644 --- a/packages/vrender-core/src/application.ts +++ b/packages/vrender-core/src/application.ts @@ -1,36 +1,2 @@ -import type { IGraphicUtil, ILayerService, ITransformUtil } from './interface/core'; -import type { - ICanvasFactory, - IContext2dFactory, - IGlobal, - IGraphicService, - ILayerHandlerContribution, - IPickerService, - IPluginService, - IRenderService, - IWindow, - IWindowHandlerContribution, - LayerMode -} from './interface'; -import type { IDrawContribution } from './interface/render'; - -export class Application { - global: IGlobal; - graphicUtil: IGraphicUtil; - graphicService: IGraphicService; - renderService: IRenderService; - renderServiceFactory?: () => IRenderService; - pluginService?: IPluginService; - pluginServiceFactory?: () => IPluginService; - pickerServiceFactory?: () => IPickerService; - windowFactory?: () => IWindow; - windowHandlerFactory?: (env: string) => IWindowHandlerContribution; - layerHandlerFactory?: (layerMode: LayerMode) => ILayerHandlerContribution; - incrementalDrawContributionFactory?: () => IDrawContribution; - canvasFactory?: (env: string) => ICanvasFactory | undefined; - context2dFactory?: (env: string) => IContext2dFactory | undefined; - transformUtil: ITransformUtil; - layerService: ILayerService; -} - -export const application = new Application(); +export { application, Application, APPLICATION_STATE_SYMBOL, getApplicationState } from './application-state'; +export type { IApplicationState } from './application-state'; diff --git a/packages/vrender-core/src/common/contribution-provider.ts b/packages/vrender-core/src/common/contribution-provider.ts index 7dd70d42e..b12675c56 100644 --- a/packages/vrender-core/src/common/contribution-provider.ts +++ b/packages/vrender-core/src/common/contribution-provider.ts @@ -1,5 +1,6 @@ import type { IBindingResolver, ServiceIdentifier } from './explicit-binding'; import type { IContributionProvider } from '../interface'; +import { getContributionStoreState } from './contribution-store-state'; export const ContributionProvider = Symbol('ContributionProvider'); @@ -63,7 +64,7 @@ export function bindContributionProviderNoSingletonScope(bind: any, id: ServiceI } export class ContributionStore { - static store: Map, Set>> = new Map(); + static store = getContributionStoreState().store as Map, Set>>; static getStore(id: ServiceIdentifier): ContributionProviderCache | undefined { return this.store.get(id)?.values().next().value; @@ -86,3 +87,6 @@ export class ContributionStore { }); } } + +export { CONTRIBUTION_STORE_STATE_SYMBOL, getContributionStoreState } from './contribution-store-state'; +export type { IContributionStoreState } from './contribution-store-state'; diff --git a/packages/vrender-core/src/common/contribution-store-state.ts b/packages/vrender-core/src/common/contribution-store-state.ts new file mode 100644 index 000000000..42d5c4abc --- /dev/null +++ b/packages/vrender-core/src/common/contribution-store-state.ts @@ -0,0 +1,23 @@ +import type { ServiceIdentifier } from './explicit-binding'; + +export const CONTRIBUTION_STORE_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/contribution-store-state'); + +export interface IContributionStoreState { + store: Map, Set>; +} + +function createContributionStoreState(): IContributionStoreState { + return { + store: new Map() + }; +} + +export function getContributionStoreState(): IContributionStoreState { + const scope = globalThis as typeof globalThis & { [CONTRIBUTION_STORE_STATE_SYMBOL]?: IContributionStoreState }; + + if (!scope[CONTRIBUTION_STORE_STATE_SYMBOL]) { + scope[CONTRIBUTION_STORE_STATE_SYMBOL] = createContributionStoreState(); + } + + return scope[CONTRIBUTION_STORE_STATE_SYMBOL] as IContributionStoreState; +} diff --git a/packages/vrender-core/src/entries/runtime-installer-state.ts b/packages/vrender-core/src/entries/runtime-installer-state.ts new file mode 100644 index 000000000..d8b776994 --- /dev/null +++ b/packages/vrender-core/src/entries/runtime-installer-state.ts @@ -0,0 +1,33 @@ +import type { IGlobal, IDrawItemInterceptorContribution } from '../interface'; +import { createLegacyBindingContext, type ILegacyBindingContext } from '../legacy/binding-context'; + +export const RUNTIME_INSTALLER_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/runtime-installer-state'); + +export interface IRuntimeInstallerState { + runtimeInstallerContext: ILegacyBindingContext; + preloaded: boolean; + runtimeGlobal?: IGlobal; + runtimeEntryKeys: WeakMap>>; + runtimeDrawContributions: WeakMap>; + loadedRuntimeContributionModules: WeakMap>; +} + +function createRuntimeInstallerState(): IRuntimeInstallerState { + return { + runtimeInstallerContext: createLegacyBindingContext(), + preloaded: false, + runtimeEntryKeys: new WeakMap>>(), + runtimeDrawContributions: new WeakMap>(), + loadedRuntimeContributionModules: new WeakMap>() + }; +} + +export function getRuntimeInstallerState(): IRuntimeInstallerState { + const scope = globalThis as typeof globalThis & { [RUNTIME_INSTALLER_STATE_SYMBOL]?: IRuntimeInstallerState }; + + if (!scope[RUNTIME_INSTALLER_STATE_SYMBOL]) { + scope[RUNTIME_INSTALLER_STATE_SYMBOL] = createRuntimeInstallerState(); + } + + return scope[RUNTIME_INSTALLER_STATE_SYMBOL] as IRuntimeInstallerState; +} diff --git a/packages/vrender-core/src/entries/runtime-installer.ts b/packages/vrender-core/src/entries/runtime-installer.ts index 6300fb00f..f6da4f946 100644 --- a/packages/vrender-core/src/entries/runtime-installer.ts +++ b/packages/vrender-core/src/entries/runtime-installer.ts @@ -18,7 +18,7 @@ import { DefaultGlobal } from '../core/global'; import { WindowHandlerContribution, DefaultWindow } from '../core/window'; import coreModule from '../core/core-modules'; import graphicModule from '../graphic/graphic-service/graphic-module'; -import { createLegacyBindingContext, type ILegacyBindingContext } from '../legacy/binding-context'; +import type { ILegacyBindingContext } from '../legacy/binding-context'; import { getLegacyBindingContext, preLoadAllModule } from '../legacy/bootstrap'; import pickModule from '../picker/pick-modules'; import { AreaRenderContribution } from '../render/contributions/render/contributions/constants'; @@ -31,6 +31,7 @@ import { GraphicRender } from '../render/contributions/render/symbol'; import loadRenderContributions from '../render/contributions/modules'; import { AutoEnablePlugins } from '../plugins/constants'; import { DefaultPluginService } from '../plugins/plugin-service'; +import { getRuntimeInstallerState } from './runtime-installer-state'; export type TRuntimeContributionModuleRegistry = ( bind: ILegacyBindingContext['bind'], @@ -58,23 +59,22 @@ export interface IRuntimeContributionModuleInstallOptions { targets?: TRuntimeContributionInstallTarget[]; } -const runtimeInstallerContext = createLegacyBindingContext(); -let runtimeInstallerPreloaded = false; -let runtimeGlobal: IGlobal | undefined; +const runtimeInstallerState = getRuntimeInstallerState(); +const runtimeInstallerContext = runtimeInstallerState.runtimeInstallerContext; const RUNTIME_RENDERER_NAMESPACE = 'vrender:runtime-renderer'; const RUNTIME_PICKER_NAMESPACE = 'vrender:runtime-picker'; -const runtimeEntryKeys = new WeakMap>>(); -const runtimeDrawContributions = new WeakMap>(); -const loadedRuntimeContributionModules = new WeakMap>(); +const runtimeEntryKeys = runtimeInstallerState.runtimeEntryKeys; +const runtimeDrawContributions = runtimeInstallerState.runtimeDrawContributions; +const loadedRuntimeContributionModules = runtimeInstallerState.loadedRuntimeContributionModules; const DEFAULT_RUNTIME_CONTRIBUTION_TARGETS: TRuntimeContributionInstallTarget[] = ['graphic-renderer']; const noopUnbindRuntimeContributionService = (): void => undefined; function ensureRuntimeInstallerPreloaded(): void { - if (runtimeInstallerPreloaded) { + if (runtimeInstallerState.preloaded) { return; } - runtimeInstallerPreloaded = true; + runtimeInstallerState.preloaded = true; coreModule({ bind: runtimeInstallerContext.bind }); graphicModule({ bind: runtimeInstallerContext.bind }); renderModule({ bind: runtimeInstallerContext.bind }); @@ -146,10 +146,10 @@ export function refreshRuntimeInstallerContributions(): void { export function getRuntimeInstallerGlobal(): IGlobal { ensureRuntimeInstallerPreloaded(); - runtimeGlobal ??= new DefaultGlobal( + runtimeInstallerState.runtimeGlobal ??= new DefaultGlobal( createContributionProvider(EnvContribution, runtimeInstallerContext) ); - return runtimeGlobal; + return runtimeInstallerState.runtimeGlobal; } export function configureRuntimeApplicationForApp(app: IApp): void { @@ -177,6 +177,9 @@ export function configureRuntimeApplicationForApp(app: IApp): void { ); } +export { RUNTIME_INSTALLER_STATE_SYMBOL, getRuntimeInstallerState } from './runtime-installer-state'; +export type { IRuntimeInstallerState } from './runtime-installer-state'; + export function installRuntimeGraphicRenderersToApp(app: IApp): void { const bindingContext = getRuntimeInstallerBindingContext(); refreshRuntimeInstallerContributions(); diff --git a/packages/vrender-core/src/factory-state.ts b/packages/vrender-core/src/factory-state.ts new file mode 100644 index 000000000..beb8b555c --- /dev/null +++ b/packages/vrender-core/src/factory-state.ts @@ -0,0 +1,21 @@ +export const FACTORY_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/factory-state'); + +export interface IFactoryState { + pluginClasses: Record; +} + +function createFactoryState(): IFactoryState { + return { + pluginClasses: {} + }; +} + +export function getFactoryState(): IFactoryState { + const scope = globalThis as typeof globalThis & { [FACTORY_STATE_SYMBOL]?: IFactoryState }; + + if (!scope[FACTORY_STATE_SYMBOL]) { + scope[FACTORY_STATE_SYMBOL] = createFactoryState(); + } + + return scope[FACTORY_STATE_SYMBOL] as IFactoryState; +} diff --git a/packages/vrender-core/src/factory.ts b/packages/vrender-core/src/factory.ts index 618e44c06..2b872b439 100644 --- a/packages/vrender-core/src/factory.ts +++ b/packages/vrender-core/src/factory.ts @@ -1,5 +1,7 @@ +import { getFactoryState } from './factory-state'; + export class Factory { - private static _pluginClasses: Record = {}; + private static _pluginClasses = getFactoryState().pluginClasses; static registerPlugin(pluginKey: string, pluginClass: any) { Factory._pluginClasses[pluginKey] = pluginClass; @@ -10,4 +12,6 @@ export class Factory { } } +export { FACTORY_STATE_SYMBOL, getFactoryState } from './factory-state'; +export type { IFactoryState } from './factory-state'; export * from './factory/index'; diff --git a/packages/vrender-core/src/graphic/graphic-creator.ts b/packages/vrender-core/src/graphic/graphic-creator.ts index 1c23f11a8..750af2df8 100644 --- a/packages/vrender-core/src/graphic/graphic-creator.ts +++ b/packages/vrender-core/src/graphic/graphic-creator.ts @@ -1,116 +1,8 @@ -import type { - IArc, - IArcGraphicAttribute, - IArea, - IAreaGraphicAttribute, - ICircle, - ICircleGraphicAttribute, - IGroup, - IGroupGraphicAttribute, - IImage, - IImageGraphicAttribute, - ILine, - ILineGraphicAttribute, - IPath, - IPathGraphicAttribute, - IPolygon, - IPolygonGraphicAttribute, - IRect, - IRect3d, - IRect3dGraphicAttribute, - IRectGraphicAttribute, - IRichText, - IRichTextGraphicAttribute, - IShadowRoot, - ISymbol, - ISymbolGraphicAttribute, - IText, - ITextGraphicAttribute, - IWrapTextGraphicAttribute -} from '../interface'; -import { GraphicFactory } from '../factory/graphic-factory'; -import type { IGraphic } from '../interface/graphic'; - -export type IGraphicCreateCallback = ( - attributes: TAttributes -) => TGraphic; - -function createGraphicCtor( - creator: IGraphicCreateCallback -) { - return class RegisteredGraphicCtor { - constructor(attributes: TAttributes) { - try { - return new (creator as any)(attributes); - } catch (error) { - return creator(attributes); - } - } - } as any; -} - -const sharedGraphicFactory = new GraphicFactory(); - -class GraphicCreator { - declare store: Map; - declare arc?: (attribute: IArcGraphicAttribute) => IArc; - declare area?: (attribute: IAreaGraphicAttribute) => IArea; - declare circle?: (attribute: ICircleGraphicAttribute) => ICircle; - declare group?: (attribute: IGroupGraphicAttribute) => IGroup; - declare image?: (attribute: IImageGraphicAttribute) => IImage; - declare line?: (attribute: ILineGraphicAttribute) => ILine; - declare path?: (attribute: IPathGraphicAttribute) => IPath; - declare rect?: (attribute: IRectGraphicAttribute) => IRect; - declare rect3d?: (attribute: IRect3dGraphicAttribute) => IRect3d; - declare symbol?: (attribute: ISymbolGraphicAttribute) => ISymbol; - declare text?: (attribute: ITextGraphicAttribute) => IText; - declare richtext?: (attribute: IRichTextGraphicAttribute) => IRichText; - declare polygon?: (attribute: IPolygonGraphicAttribute) => IPolygon; - declare shadowRoot?: (attribute: IGroupGraphicAttribute) => IShadowRoot; - declare wrapText?: (attribute: IWrapTextGraphicAttribute) => IText; - - constructor() { - this.store = new Map(); - } - - registerStore(name: string, creator: IGraphicCreateCallback) { - this.store.set(name, creator); - (this as any)[name] = creator; - } - - RegisterGraphicCreator(name: string, creator: IGraphicCreateCallback) { - registerGraphic(name, creator); - } - - CreateGraphic( - name: string, - attributes: TAttributes - ): TGraphic | null { - if (!this.store.has(name)) { - return null; - } - - return createGraphic(name, attributes); - } -} - -export const graphicCreator = new GraphicCreator(); - -export function registerGraphic( - name: string, - creator: IGraphicCreateCallback -) { - if (!name) { - throw new Error('Graphic registration requires a non-empty graphic type'); - } - - graphicCreator.registerStore(name, creator as IGraphicCreateCallback); - sharedGraphicFactory.register(name, createGraphicCtor(creator)); -} - -export function createGraphic( - name: string, - attributes: TAttributes -): TGraphic { - return sharedGraphicFactory.create(name, attributes); -} +export { + createGraphic, + GRAPHIC_REGISTRY_SYMBOL, + graphicCreator, + getGraphicRegistryState, + registerGraphic +} from './graphic-registry'; +export type { IGraphicCreateCallback, IGraphicRegistryState } from './graphic-registry'; diff --git a/packages/vrender-core/src/graphic/graphic-registry.ts b/packages/vrender-core/src/graphic/graphic-registry.ts new file mode 100644 index 000000000..b974c0b4f --- /dev/null +++ b/packages/vrender-core/src/graphic/graphic-registry.ts @@ -0,0 +1,140 @@ +import { GraphicFactory } from '../factory/graphic-factory'; +import type { + IArc, + IArcGraphicAttribute, + IArea, + IAreaGraphicAttribute, + ICircle, + ICircleGraphicAttribute, + IGroup, + IGroupGraphicAttribute, + IImage, + IImageGraphicAttribute, + ILine, + ILineGraphicAttribute, + IPath, + IPathGraphicAttribute, + IPolygon, + IPolygonGraphicAttribute, + IRect, + IRect3d, + IRect3dGraphicAttribute, + IRectGraphicAttribute, + IRichText, + IRichTextGraphicAttribute, + IShadowRoot, + ISymbol, + ISymbolGraphicAttribute, + IText, + ITextGraphicAttribute, + IWrapTextGraphicAttribute +} from '../interface'; +import type { IGraphic } from '../interface/graphic'; + +export type IGraphicCreateCallback = ( + attributes: TAttributes +) => TGraphic; + +function createGraphicCtor( + creator: IGraphicCreateCallback +) { + return class RegisteredGraphicCtor { + constructor(attributes: TAttributes) { + try { + return new (creator as any)(attributes); + } catch (error) { + return creator(attributes); + } + } + } as any; +} + +class GraphicCreator { + declare store: Map; + declare arc?: (attribute: IArcGraphicAttribute) => IArc; + declare area?: (attribute: IAreaGraphicAttribute) => IArea; + declare circle?: (attribute: ICircleGraphicAttribute) => ICircle; + declare group?: (attribute: IGroupGraphicAttribute) => IGroup; + declare image?: (attribute: IImageGraphicAttribute) => IImage; + declare line?: (attribute: ILineGraphicAttribute) => ILine; + declare path?: (attribute: IPathGraphicAttribute) => IPath; + declare rect?: (attribute: IRectGraphicAttribute) => IRect; + declare rect3d?: (attribute: IRect3dGraphicAttribute) => IRect3d; + declare symbol?: (attribute: ISymbolGraphicAttribute) => ISymbol; + declare text?: (attribute: ITextGraphicAttribute) => IText; + declare richtext?: (attribute: IRichTextGraphicAttribute) => IRichText; + declare polygon?: (attribute: IPolygonGraphicAttribute) => IPolygon; + declare shadowRoot?: (attribute: IGroupGraphicAttribute) => IShadowRoot; + declare wrapText?: (attribute: IWrapTextGraphicAttribute) => IText; + + constructor() { + this.store = new Map(); + } + + registerStore(name: string, creator: IGraphicCreateCallback) { + this.store.set(name, creator); + (this as any)[name] = creator; + } + + RegisterGraphicCreator(name: string, creator: IGraphicCreateCallback) { + registerGraphic(name, creator); + } + + CreateGraphic( + name: string, + attributes: TAttributes + ): TGraphic | null { + if (!this.store.has(name)) { + return null; + } + + return createGraphic(name, attributes); + } +} + +export const GRAPHIC_REGISTRY_SYMBOL = Symbol.for('@visactor/vrender-core/graphic-registry'); + +export interface IGraphicRegistryState { + graphicCreator: GraphicCreator; + graphicFactory: GraphicFactory; +} + +function createGraphicRegistryState(): IGraphicRegistryState { + return { + graphicCreator: new GraphicCreator(), + graphicFactory: new GraphicFactory() + }; +} + +export function getGraphicRegistryState(): IGraphicRegistryState { + const scope = globalThis as typeof globalThis & { [GRAPHIC_REGISTRY_SYMBOL]?: IGraphicRegistryState }; + + if (!scope[GRAPHIC_REGISTRY_SYMBOL]) { + scope[GRAPHIC_REGISTRY_SYMBOL] = createGraphicRegistryState(); + } + + return scope[GRAPHIC_REGISTRY_SYMBOL] as IGraphicRegistryState; +} + +const sharedGraphicRegistry = getGraphicRegistryState(); + +export const graphicCreator = sharedGraphicRegistry.graphicCreator; + +export function registerGraphic( + name: string, + creator: IGraphicCreateCallback +) { + if (!name) { + throw new Error('Graphic registration requires a non-empty graphic type'); + } + + graphicCreator.registerStore(name, creator as IGraphicCreateCallback); + sharedGraphicRegistry.graphicFactory.register(name, createGraphicCtor(creator)); +} + +export function createGraphic( + name: string, + attributes: TAttributes +): TGraphic { + return sharedGraphicRegistry.graphicFactory.create(name, attributes); +} diff --git a/packages/vrender-core/src/graphic/graphic.ts b/packages/vrender-core/src/graphic/graphic.ts index 4493b86a1..f7456939c 100644 --- a/packages/vrender-core/src/graphic/graphic.ts +++ b/packages/vrender-core/src/graphic/graphic.ts @@ -306,7 +306,7 @@ export const NOWORK_ANIMATE_ATTR = { * 3. 所有节点的transform修改,或者globalTransform修改,都会下发到自己的shadowRoot上 */ -export abstract class Graphic = Partial> +abstract class GraphicImpl = Partial> extends Node implements IGraphic, IAnimateTarget { @@ -327,7 +327,7 @@ export abstract class Graphic = Partial = Partial = Partial> = GraphicImpl; + +export const GRAPHIC_CLASS_SYMBOL = Symbol.for('@visactor/vrender-core/graphic-class'); + +export interface IGraphicClassState { + Graphic: typeof GraphicImpl; +} + +function createGraphicClassState(): IGraphicClassState { + return { + Graphic: GraphicImpl + }; +} + +export function getGraphicClassState(): IGraphicClassState { + const globalScope = globalThis as typeof globalThis & { + [GRAPHIC_CLASS_SYMBOL]?: IGraphicClassState; + }; + + globalScope[GRAPHIC_CLASS_SYMBOL] ??= createGraphicClassState(); + return globalScope[GRAPHIC_CLASS_SYMBOL]; +} + +export const Graphic = getGraphicClassState().Graphic; + Graphic.mixin(EventTarget); function backgroundNotImage(image: any) { diff --git a/packages/vrender-core/src/graphic/index.ts b/packages/vrender-core/src/graphic/index.ts index 8c5cf1e34..f2d2f1405 100644 --- a/packages/vrender-core/src/graphic/index.ts +++ b/packages/vrender-core/src/graphic/index.ts @@ -22,6 +22,8 @@ export * from './shadow-root'; export * from './config'; export * from './graphic-service/graphic-service'; export * from './graphic-creator'; +export { GRAPHIC_REGISTRY_SYMBOL, getGraphicRegistryState } from './graphic-registry'; +export type { IGraphicRegistryState } from './graphic-registry'; export * from './builtin-symbol'; export * from './graphic'; export * from './bounds'; diff --git a/packages/vrender-core/src/legacy/bootstrap-state.ts b/packages/vrender-core/src/legacy/bootstrap-state.ts new file mode 100644 index 000000000..1ab693c9e --- /dev/null +++ b/packages/vrender-core/src/legacy/bootstrap-state.ts @@ -0,0 +1,25 @@ +import { createLegacyBindingContext, type ILegacyBindingContext } from './binding-context'; + +export const LEGACY_BOOTSTRAP_STATE_SYMBOL = Symbol.for('@visactor/vrender-core/legacy-bootstrap-state'); + +export interface ILegacyBootstrapState { + legacyBindingContext: ILegacyBindingContext; + preloaded: boolean; +} + +function createLegacyBootstrapState(): ILegacyBootstrapState { + return { + legacyBindingContext: createLegacyBindingContext(), + preloaded: false + }; +} + +export function getLegacyBootstrapState(): ILegacyBootstrapState { + const scope = globalThis as typeof globalThis & { [LEGACY_BOOTSTRAP_STATE_SYMBOL]?: ILegacyBootstrapState }; + + if (!scope[LEGACY_BOOTSTRAP_STATE_SYMBOL]) { + scope[LEGACY_BOOTSTRAP_STATE_SYMBOL] = createLegacyBootstrapState(); + } + + return scope[LEGACY_BOOTSTRAP_STATE_SYMBOL] as ILegacyBootstrapState; +} diff --git a/packages/vrender-core/src/legacy/bootstrap.ts b/packages/vrender-core/src/legacy/bootstrap.ts index f0255692d..7c13b8f67 100644 --- a/packages/vrender-core/src/legacy/bootstrap.ts +++ b/packages/vrender-core/src/legacy/bootstrap.ts @@ -5,11 +5,13 @@ import graphicModule from '../graphic/graphic-service/graphic-module'; import pluginModule from '../plugins/plugin-modules'; import loadBuiltinContributions from '../core/contributions/modules'; import loadRenderContributions from '../render/contributions/modules'; -import { createLegacyBindingContext, type ILegacyBindContext, type ILegacyBindingContext } from './binding-context'; +import type { ILegacyBindContext, ILegacyBindingContext } from './binding-context'; +import { getLegacyBootstrapState } from './bootstrap-state'; export type { ILegacyBindContext, ILegacyBindingContext } from './binding-context'; -const legacyBindingContext = createLegacyBindingContext(); +const legacyBootstrapState = getLegacyBootstrapState(); +const legacyBindingContext = legacyBootstrapState.legacyBindingContext; function getLegacyTarget(resolver: () => T): () => T { let target: T | undefined; @@ -24,10 +26,10 @@ function getLegacyTarget(resolver: () => T): () => T { } export function preLoadAllModule() { - if (preLoadAllModule.__loaded) { + if (legacyBootstrapState.preloaded) { return; } - preLoadAllModule.__loaded = true; + legacyBootstrapState.preloaded = true; coreModule({ bind: legacyBindingContext.bind }); graphicModule({ bind: legacyBindingContext.bind }); renderModule({ bind: legacyBindingContext.bind }); @@ -37,8 +39,6 @@ export function preLoadAllModule() { loadRenderContributions(legacyBindingContext); } -preLoadAllModule.__loaded = false; - export function getLegacyBindingContext(): ILegacyBindingContext { return legacyBindingContext; } @@ -49,6 +49,9 @@ export function resolveLegacySingleton(serviceIdentifier: any): T { return instance; } +export { LEGACY_BOOTSTRAP_STATE_SYMBOL, getLegacyBootstrapState } from './bootstrap-state'; +export type { ILegacyBootstrapState } from './bootstrap-state'; + export function resolveLegacyNamed(serviceIdentifier: any, name: string): T | undefined { preLoadAllModule(); return legacyBindingContext.getNamed(serviceIdentifier, name);