ecspresso 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -17
- package/dist/asset-manager.d.ts +15 -15
- package/dist/asset-types.d.ts +16 -14
- package/dist/bundle.d.ts +66 -16
- package/dist/bundles/audio.d.ts +293 -0
- package/dist/bundles/{utils/bounds.d.ts → bounds.d.ts} +9 -7
- package/dist/bundles/camera.d.ts +89 -0
- package/dist/bundles/collision.d.ts +289 -0
- package/dist/bundles/diagnostics.d.ts +48 -0
- package/dist/bundles/{utils/input.d.ts → input.d.ts} +16 -17
- package/dist/bundles/physics2D.d.ts +159 -0
- package/dist/bundles/renderers/renderer2D.d.ts +65 -24
- package/dist/bundles/spatial-index.d.ts +57 -0
- package/dist/bundles/state-machine.d.ts +298 -0
- package/dist/bundles/{utils/timers.d.ts → timers.d.ts} +9 -8
- package/dist/bundles/{utils/transform.d.ts → transform.d.ts} +10 -10
- package/dist/bundles/tween.d.ts +197 -0
- package/dist/command-buffer.d.ts +20 -20
- package/dist/ecspresso-builder.d.ts +165 -0
- package/dist/ecspresso.d.ts +157 -178
- package/dist/entity-manager.d.ts +76 -40
- package/dist/event-bus.d.ts +6 -1
- package/dist/index.d.ts +1 -9
- package/dist/reactive-query-manager.d.ts +14 -3
- package/dist/resource-manager.d.ts +35 -19
- package/dist/screen-manager.d.ts +4 -4
- package/dist/screen-types.d.ts +12 -11
- package/dist/src/bundles/audio.js +4 -0
- package/dist/src/bundles/audio.js.map +10 -0
- package/dist/src/bundles/bounds.js +4 -0
- package/dist/src/bundles/bounds.js.map +10 -0
- package/dist/src/bundles/camera.js +4 -0
- package/dist/src/bundles/camera.js.map +10 -0
- package/dist/src/bundles/collision.js +4 -0
- package/dist/src/bundles/collision.js.map +11 -0
- package/dist/src/bundles/diagnostics.js +5 -0
- package/dist/src/bundles/diagnostics.js.map +10 -0
- package/dist/src/bundles/input.js +4 -0
- package/dist/src/bundles/input.js.map +10 -0
- package/dist/src/bundles/physics2D.js +4 -0
- package/dist/src/bundles/physics2D.js.map +11 -0
- package/dist/src/bundles/renderers/renderer2D.js +4 -0
- package/dist/src/bundles/renderers/renderer2D.js.map +10 -0
- package/dist/src/bundles/spatial-index.js +4 -0
- package/dist/src/bundles/spatial-index.js.map +11 -0
- package/dist/src/bundles/state-machine.js +4 -0
- package/dist/src/bundles/state-machine.js.map +10 -0
- package/dist/src/bundles/timers.js +4 -0
- package/dist/src/bundles/timers.js.map +10 -0
- package/dist/src/bundles/transform.js +4 -0
- package/dist/src/bundles/transform.js.map +10 -0
- package/dist/src/bundles/tween.js +4 -0
- package/dist/src/bundles/tween.js.map +11 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +25 -0
- package/dist/system-builder.d.ts +36 -42
- package/dist/type-utils.d.ts +52 -3
- package/dist/types.d.ts +10 -19
- package/dist/utils/check-required-cycle.d.ts +12 -0
- package/dist/utils/easing.d.ts +71 -0
- package/dist/utils/math.d.ts +67 -0
- package/dist/utils/narrowphase.d.ts +63 -0
- package/dist/utils/spatial-hash.d.ts +53 -0
- package/package.json +50 -20
- package/dist/bundles/renderers/renderer2D.js +0 -4
- package/dist/bundles/renderers/renderer2D.js.map +0 -10
- package/dist/bundles/utils/bounds.js +0 -4
- package/dist/bundles/utils/bounds.js.map +0 -10
- package/dist/bundles/utils/collision.d.ts +0 -204
- package/dist/bundles/utils/collision.js +0 -4
- package/dist/bundles/utils/collision.js.map +0 -10
- package/dist/bundles/utils/input.js +0 -4
- package/dist/bundles/utils/input.js.map +0 -10
- package/dist/bundles/utils/movement.d.ts +0 -86
- package/dist/bundles/utils/movement.js +0 -4
- package/dist/bundles/utils/movement.js.map +0 -10
- package/dist/bundles/utils/timers.js +0 -4
- package/dist/bundles/utils/timers.js.map +0 -10
- package/dist/bundles/utils/transform.js +0 -4
- package/dist/bundles/utils/transform.js.map +0 -10
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -22
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/renderers/renderer2D.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * 2D Renderer Bundle for ECSpresso\n *\n * An opt-in PixiJS-based 2D rendering bundle that automates scene graph wiring.\n * Import from 'ecspresso/bundles/renderers/renderer2D'\n *\n * This bundle includes transform propagation automatically.\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport { Bundle, mergeBundles } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransformBundle,\n\ttype LocalTransform,\n\ttype WorldTransform,\n\ttype TransformComponentTypes,\n\ttype TransformBundleOptions,\n} from 'ecspresso/bundles/transform';\nimport { createBounds, type BoundsRect } from 'ecspresso/bundles/bounds';\nimport type { CameraResourceTypes, CameraState } from 'ecspresso/bundles/camera';\n\n// Re-export transform and bounds types for convenience\nexport type { LocalTransform, WorldTransform, TransformComponentTypes };\nexport type { BoundsRect };\nexport { createTransform, createLocalTransform, createWorldTransform, DEFAULT_LOCAL_TRANSFORM, DEFAULT_WORLD_TRANSFORM } from 'ecspresso/bundles/transform';\n\n// Dynamic import for Application to avoid requiring pixi.js at bundle creation time\n// when using managed mode (init options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init(options);\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Visibility and alpha component\n */\nexport interface Visible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for the 2D renderer bundle.\n * Included automatically via `.withBundle(createRenderer2DBundle({ ... }))`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createRenderer2DBundle({ ... }))\n * .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()\n * .build();\n * ```\n */\nexport interface Renderer2DComponentTypes extends TransformComponentTypes {\n\tsprite: Sprite;\n\tgraphics: Graphics;\n\tcontainer: Container;\n\tvisible: Visible;\n\t/** Assigns the entity to a named render layer for z-ordering */\n\trenderLayer: string;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 2D renderer bundle\n */\nexport interface Renderer2DEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the 2D renderer bundle\n */\nexport interface Renderer2DResourceTypes {\n\tpixiApp: Application;\n\trootContainer: Container;\n\t/** Screen bounds derived from PixiJS screen dimensions, updated on resize */\n\tbounds: BoundsRect;\n}\n\n// ==================== Scale Mode Types ====================\n\nexport type ScaleMode = 'fit' | 'cover' | 'stretch';\n\nexport interface ScreenScaleOptions {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly mode?: ScaleMode;\n}\n\nexport interface ViewportScale {\n\tscaleX: number;\n\tscaleY: number;\n\toffsetX: number;\n\toffsetY: number;\n\tphysicalWidth: number;\n\tphysicalHeight: number;\n\treadonly designWidth: number;\n\treadonly designHeight: number;\n}\n\nexport interface ViewportScaleResourceTypes {\n\tviewportScale: ViewportScale;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface Renderer2DBundleCommonOptions<G extends string = 'renderer2d'> {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'renderer2d') */\n\tsystemGroup?: G;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included transform bundle */\n\ttransform?: TransformBundleOptions;\n\t/** When true, wires up pixiApp.ticker to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n\t/** Ordered render layer names (back-to-front). Entities with a renderLayer component are placed in the corresponding container. */\n\trenderLayers?: string[];\n\t/** Automatically apply cameraState resource to rootContainer each frame.\n\t * Requires the camera bundle to be installed. (default: false) */\n\tcamera?: boolean;\n\t/** Enforce a logical design resolution with automatic aspect-ratio-aware scaling.\n\t * When set, systems work in design-resolution coordinate space. */\n\tscreenScale?: ScreenScaleOptions;\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface Renderer2DBundleAppOptions<G extends string = 'renderer2d'> extends Renderer2DBundleCommonOptions<G> {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tinit?: never;\n\tcontainer?: never;\n}\n\n/**\n * Options when letting the bundle create and manage the PixiJS Application\n */\nexport interface Renderer2DBundleManagedOptions<G extends string = 'renderer2d'> extends Renderer2DBundleCommonOptions<G> {\n\tapp?: never;\n\t/** PixiJS ApplicationOptions - bundle will create and initialize the Application */\n\tinit: Partial<ApplicationOptions>;\n\t/** Container element to append the canvas to, or CSS selector string */\n\tcontainer?: HTMLElement | string;\n}\n\n/**\n * Configuration options for the 2D renderer bundle.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`\n *\n * This bundle includes transform propagation automatically - no need to add createTransformBundle() separately.\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create()\n * .withBundle(createRenderer2DBundle({ app }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type Renderer2DBundleOptions<G extends string = 'renderer2d'> = Renderer2DBundleAppOptions<G> | Renderer2DBundleManagedOptions<G>;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createLocalTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createWorldTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): Visible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<Renderer2DComponentTypes, 'sprite' | 'localTransform' | 'worldTransform' | 'visible'> {\n\tif (options?.anchor) {\n\t\tsprite.anchor.set(options.anchor.x, options.anchor.y);\n\t}\n\treturn {\n\t\tsprite,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'graphics' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tgraphics,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'container' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tcontainer,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Viewport Scale Utilities ====================\n\nconst scaleModeStrategy: Record<ScaleMode, (ratioX: number, ratioY: number) => { scaleX: number; scaleY: number }> = {\n\tfit: (ratioX, ratioY) => {\n\t\tconst s = Math.min(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tcover: (ratioX, ratioY) => {\n\t\tconst s = Math.max(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tstretch: (ratioX, ratioY) => ({ scaleX: ratioX, scaleY: ratioY }),\n};\n\nexport function computeViewportScale(\n\tphysicalW: number,\n\tphysicalH: number,\n\tdesignW: number,\n\tdesignH: number,\n\tmode: ScaleMode,\n): ViewportScale {\n\tconst ratioX = physicalW / designW;\n\tconst ratioY = physicalH / designH;\n\tconst { scaleX, scaleY } = scaleModeStrategy[mode](ratioX, ratioY);\n\n\treturn {\n\t\tscaleX,\n\t\tscaleY,\n\t\toffsetX: (physicalW - designW * scaleX) / 2,\n\t\toffsetY: (physicalH - designH * scaleY) / 2,\n\t\tphysicalWidth: physicalW,\n\t\tphysicalHeight: physicalH,\n\t\tdesignWidth: designW,\n\t\tdesignHeight: designH,\n\t};\n}\n\n/**\n * Convert physical canvas pixel coordinates to design-resolution (logical) coordinates.\n * Compose with camera `screenToWorld()` for full physical→world conversion.\n */\nexport function physicalToLogical(\n\tphysicalX: number,\n\tphysicalY: number,\n\tviewport: ViewportScale,\n): { x: number; y: number } {\n\treturn {\n\t\tx: (physicalX - viewport.offsetX) / viewport.scaleX,\n\t\ty: (physicalY - viewport.offsetY) / viewport.scaleY,\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a 2D rendering bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation (localTransform -> worldTransform)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createRenderer2DBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\ntype Renderer2DLabels = 'renderer2d-sync' | 'renderer2d-scene-graph' | 'renderer2d-camera-sync' | 'transform-propagation';\ntype Renderer2DReactiveQueryNames = 'renderer2d-sprites' | 'renderer2d-graphics' | 'renderer2d-containers';\n\nexport function createRenderer2DBundle<G extends string = 'renderer2d'>(\n\toptions: Renderer2DBundleOptions<G> & { screenScale: ScreenScaleOptions; camera: true }\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DBundle<G extends string = 'renderer2d'>(\n\toptions: Renderer2DBundleOptions<G> & { screenScale: ScreenScaleOptions }\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DBundle<G extends string = 'renderer2d'>(\n\toptions: Renderer2DBundleOptions<G> & { camera: true }\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DBundle<G extends string = 'renderer2d'>(\n\toptions: Renderer2DBundleOptions<G>\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DBundle<G extends string = 'renderer2d'>(\n\toptions: Renderer2DBundleOptions<G> & { camera?: boolean; screenScale?: ScreenScaleOptions }\n): Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'renderer2d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t\trenderLayers = [],\n\t\tcamera = false,\n\t\tscreenScale,\n\t} = options;\n\n\tconst hasScreenScale = screenScale !== undefined;\n\tconst designWidth = screenScale?.width ?? 0;\n\tconst designHeight = screenScale?.height ?? 0;\n\tconst screenScaleMode: ScaleMode = screenScale?.mode ?? 'fit';\n\n\tconst rendererBundle = new Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>('renderer2d-internal');\n\n\t// Determine mode and set up resources accordingly\n\tconst isManaged = 'init' in options && options.init !== undefined;\n\n\tif (isManaged) {\n\t\t// Managed mode: create Application during initialization\n\t\tconst initOptions = options.init;\n\t\tconst containerOption = options.container;\n\n\t\t// Resource factory that creates the Application\n\t\trendererBundle.addResource('pixiApp', async () => {\n\t\t\tconst app = await createPixiApplication(initOptions);\n\n\t\t\t// Auto-append canvas if container specified\n\t\t\tif (containerOption) {\n\t\t\t\tconst containerEl = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\tif (containerEl) {\n\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\tconsole.warn(`Renderer2D bundle: container selector \"${containerOption}\" not found`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn app;\n\t\t});\n\n\t\t// rootContainer depends on pixiApp - declarative dependency\n\t\trendererBundle.addResource('rootContainer', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t});\n\n\t\t// Bounds resource: design resolution when scaleMode active, physical otherwise\n\t\trendererBundle.addResource('bounds', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => {\n\t\t\t\tif (hasScreenScale) return createBounds(designWidth, designHeight);\n\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\treturn createBounds(pixiApp.screen.width, pixiApp.screen.height);\n\t\t\t},\n\t\t});\n\n\t\t// viewportScale resource (only when scaleMode active)\n\t\tif (hasScreenScale) {\n\t\t\trendererBundle.addResource('viewportScale' as keyof Renderer2DResourceTypes, {\n\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\tfactory: (ecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>) => {\n\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\treturn computeViewportScale(pixiApp.screen.width, pixiApp.screen.height, designWidth, designHeight, screenScaleMode);\n\t\t\t\t},\n\t\t\t} as any);\n\t\t}\n\t} else {\n\t\t// Pre-initialized mode: use provided Application\n\t\tconst app = options.app;\n\t\trendererBundle.addResource('pixiApp', app);\n\t\trendererBundle.addResource('rootContainer', customRootContainer ?? app.stage);\n\t\trendererBundle.addResource('bounds', hasScreenScale\n\t\t\t? createBounds(designWidth, designHeight)\n\t\t\t: createBounds(app.screen.width, app.screen.height));\n\n\t\tif (hasScreenScale) {\n\t\t\trendererBundle.addResource('viewportScale' as keyof Renderer2DResourceTypes,\n\t\t\t\tcomputeViewportScale(app.screen.width, app.screen.height, designWidth, designHeight, screenScaleMode) as any);\n\t\t}\n\t}\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Render layer name -> PixiJS Container mapping\n\tconst layerContainers = new Map<string, Container>();\n\n\t// Container constructor captured during initialization via dynamic import\n\t// Used by getOrCreateLayerContainer for lazy layer creation\n\tlet createLayerContainer: (label: string) => Container = () => {\n\t\tthrow new Error('renderer2D: createLayerContainer called before initialization');\n\t};\n\n\t// Helper to get the PixiJS display object for an entity\n\tfunction getPixiObject(entityId: number, ecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>): Container | null {\n\t\t// Check cache first\n\t\tconst cached = entityToPixiObject.get(entityId);\n\t\tif (cached) return cached;\n\n\t\t// Try to get from components\n\t\tconst spriteComp = ecs.entityManager.getComponent(entityId, 'sprite');\n\t\tif (spriteComp) {\n\t\t\tentityToPixiObject.set(entityId, spriteComp);\n\t\t\treturn spriteComp;\n\t\t}\n\n\t\tconst graphicsComp = ecs.entityManager.getComponent(entityId, 'graphics');\n\t\tif (graphicsComp) {\n\t\t\tentityToPixiObject.set(entityId, graphicsComp);\n\t\t\treturn graphicsComp;\n\t\t}\n\n\t\tconst containerComp = ecs.entityManager.getComponent(entityId, 'container');\n\t\tif (containerComp) {\n\t\t\tentityToPixiObject.set(entityId, containerComp);\n\t\t\treturn containerComp;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Helper to get or create a render layer container\n\tfunction getOrCreateLayerContainer(\n\t\tlayerName: string,\n\t\trootCont: Container\n\t): Container {\n\t\tconst existing = layerContainers.get(layerName);\n\t\tif (existing) return existing;\n\n\t\t// Lazy-create for undeclared layers, appended to end\n\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\tlayerContainers.set(layerName, cont);\n\t\trootCont.addChild(cont);\n\t\treturn cont;\n\t}\n\n\t// Helper to resolve the target container for an entity\n\tfunction resolveTargetContainer(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): Container {\n\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t// 1. Check ECS parent hierarchy\n\t\tconst parentId = ecs.getParent(entityId);\n\t\tconst parentPixiObject = parentId !== null ? getPixiObject(parentId, ecs) : null;\n\t\tif (parentPixiObject) return parentPixiObject;\n\n\t\t// 2. Check render layer component\n\t\tconst layerName = ecs.entityManager.getComponent(entityId, 'renderLayer');\n\t\tif (layerName) return getOrCreateLayerContainer(layerName, rootCont);\n\n\t\t// 3. Fall back to root container\n\t\treturn rootCont;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Register dispose callbacks for display object components.\n\t// When a sprite/graphics/container component is removed (explicit removal,\n\t// entity destruction, or component replacement), the PixiJS object is\n\t// automatically detached from the scene graph.\n\trendererBundle.registerDispose('sprite', (sprite) => {\n\t\tsprite.removeFromParent();\n\t});\n\trendererBundle.registerDispose('graphics', (graphics) => {\n\t\tgraphics.removeFromParent();\n\t});\n\trendererBundle.registerDispose('container', (container) => {\n\t\tcontainer.removeFromParent();\n\t});\n\n\t// Display objects require localTransform (which transitively requires worldTransform\n\t// via the transform bundle) and visible. Explicit values on spawn always win.\n\trendererBundle.registerRequired('sprite', 'localTransform', () => createLocalTransformInternal());\n\trendererBundle.registerRequired('sprite', 'visible', () => createVisibleComponent());\n\trendererBundle.registerRequired('graphics', 'localTransform', () => createLocalTransformInternal());\n\trendererBundle.registerRequired('graphics', 'visible', () => createVisibleComponent());\n\trendererBundle.registerRequired('container', 'localTransform', () => createLocalTransformInternal());\n\trendererBundle.registerRequired('container', 'visible', () => createVisibleComponent());\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: ECSpresso<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// ==================== Render Sync System ====================\n\t// Updates PixiJS objects from world transforms and visibility\n\trendererBundle\n\t\t.addSystem('renderer2d-sync')\n\t\t.setPriority(renderSyncPriority)\n\t\t.inPhase('render')\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('sprites', {\n\t\t\twith: ['sprite', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('graphics', {\n\t\t\twith: ['graphics', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.addQuery('containers', {\n\t\t\twith: ['container', 'worldTransform'],\n\t\t\tchanged: ['worldTransform'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t// Process sprites\n\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\tconst { sprite, worldTransform } = entity.components;\n\n\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tsprite.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tsprite.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process graphics\n\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\tconst { graphics, worldTransform } = entity.components;\n\n\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tgraphics.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tgraphics.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process containers\n\t\t\tfor (const entity of queries.containers) {\n\t\t\t\tconst { container, worldTransform } = entity.components;\n\n\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visibleComp = ecs.entityManager.getComponent(entity.id, 'visible');\n\t\t\t\tif (visibleComp) {\n\t\t\t\t\tcontainer.visible = visibleComp.visible;\n\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\tcontainer.alpha = visibleComp.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Scene Graph Manager System ====================\n\t// Sets up reactive queries to manage scene graph on entity create/destroy\n\t// High priority ensures this runs before user systems' onInitialize\n\trendererBundle\n\t\t.addSystem('renderer2d-scene-graph')\n\t\t.setPriority(9999)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize(async (ecs) => {\n\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\tlet rootCont = ecs.getResource('rootContainer');\n\n\t\t\t// Capture Container constructor via dynamic import (same module instance as pixi.js internals)\n\t\t\tconst { Container: ContainerClass } = await import('pixi.js');\n\t\t\tcreateLayerContainer = (label: string) => {\n\t\t\t\tconst cont = new ContainerClass();\n\t\t\t\tcont.label = label;\n\t\t\t\treturn cont;\n\t\t\t};\n\n\t\t\t// Set up viewportContainer when scaleMode is active\n\t\t\tlet viewportContainer: Container | undefined;\n\t\t\tif (hasScreenScale) {\n\t\t\t\tviewportContainer = new ContainerClass();\n\t\t\t\tviewportContainer.label = 'viewportContainer';\n\n\t\t\t\t// Apply initial scale/offset\n\t\t\t\tconst vs = ecs.tryGetResource<ViewportScale>('viewportScale');\n\t\t\t\tif (!vs) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\n\t\t\t\t// Create a new rootContainer since app.stage can't be reparented\n\t\t\t\tconst newRoot = new ContainerClass();\n\t\t\t\tnewRoot.label = 'rootContainer';\n\n\t\t\t\t// Wire: stage → viewportContainer → newRoot\n\t\t\t\tpixiApp.stage.addChild(viewportContainer);\n\t\t\t\tviewportContainer.addChild(newRoot);\n\n\t\t\t\t// Swap the resource so all other systems see the new root\n\t\t\t\tecs.updateResource('rootContainer', () => newRoot);\n\t\t\t\trootCont = newRoot;\n\t\t\t}\n\n\t\t\t// Create declared render layer containers in order (back-to-front)\n\t\t\tfor (const layerName of renderLayers) {\n\t\t\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\t\t\tlayerContainers.set(layerName, cont);\n\t\t\t\trootCont.addChild(cont);\n\t\t\t}\n\n\t\t\t// Reactive query for sprites\n\t\t\tecs.addReactiveQuery('renderer2d-sprites', {\n\t\t\t\twith: ['sprite'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.sprite;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for graphics\n\t\t\tecs.addReactiveQuery('renderer2d-graphics', {\n\t\t\t\twith: ['graphics'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.graphics;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for containers\n\t\t\tecs.addReactiveQuery('renderer2d-containers', {\n\t\t\t\twith: ['container'],\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.container;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Subscribe to hierarchy changes to mirror reparenting in scene graph\n\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is added or changed\n\t\t\tecs.onComponentAdded('renderLayer', (_layerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Re-parent entity when render layer is removed\n\t\t\tecs.onComponentRemoved('renderLayer', (_oldLayerName, entity) => {\n\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t});\n\n\t\t\t// Set initial camera viewport dimensions\n\t\t\tif (camera) {\n\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\tcameraState.viewportWidth = hasScreenScale ? designWidth : pixiApp.screen.width;\n\t\t\t\tcameraState.viewportHeight = hasScreenScale ? designHeight : pixiApp.screen.height;\n\t\t\t}\n\n\t\t\t// Track screen dimensions on resize\n\t\t\tpixiApp.renderer.on('resize', (width: number, height: number) => {\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\t// Recompute viewport scale and apply to viewportContainer\n\t\t\t\t\tconst vs = computeViewportScale(width, height, designWidth, designHeight, screenScaleMode);\n\t\t\t\t\tconst vpResource = ecs.tryGetResource<ViewportScale>('viewportScale');\n\t\t\t\t\tif (!vpResource) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\t\tvpResource.scaleX = vs.scaleX;\n\t\t\t\t\tvpResource.scaleY = vs.scaleY;\n\t\t\t\t\tvpResource.offsetX = vs.offsetX;\n\t\t\t\t\tvpResource.offsetY = vs.offsetY;\n\t\t\t\t\tvpResource.physicalWidth = width;\n\t\t\t\t\tvpResource.physicalHeight = height;\n\n\t\t\t\t\tif (viewportContainer) {\n\t\t\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Camera viewport stays at design dimensions (no change needed)\n\t\t\t\t} else {\n\t\t\t\t\t// No scaleMode: update bounds to physical dimensions\n\t\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\t\tbounds.width = width;\n\t\t\t\t\tbounds.height = height;\n\n\t\t\t\t\tif (camera) {\n\t\t\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\tcameraState.viewportWidth = width;\n\t\t\t\t\t\tcameraState.viewportHeight = height;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Wire up the game loop if requested\n\t\t\tif (startLoop) {\n\t\t\t\tpixiApp.ticker.add((ticker) => {\n\t\t\t\t\tecs.update(ticker.deltaMS / 1_000);\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Camera Sync System (opt-in) ====================\n\tif (camera) {\n\t\trendererBundle\n\t\t\t.addSystem('renderer2d-camera-sync')\n\t\t\t.setPriority(900)\n\t\t\t.inPhase('render')\n\t\t\t.inGroup(systemGroup)\n\t\t\t.setProcess((_queries, _dt, ecs) => {\n\t\t\t\tconst state = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\tif (!state) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\tconst root = ecs.getResource('rootContainer');\n\t\t\t\tlet centerW: number, centerH: number;\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\tcenterW = designWidth;\n\t\t\t\t\tcenterH = designHeight;\n\t\t\t\t} else {\n\t\t\t\t\tconst screen = ecs.getResource('pixiApp').screen;\n\t\t\t\t\tcenterW = screen.width;\n\t\t\t\t\tcenterH = screen.height;\n\t\t\t\t}\n\n\t\t\t\troot.position.set(\n\t\t\t\t\tcenterW / 2 - (state.x + state.shakeOffsetX) * state.zoom,\n\t\t\t\t\tcenterH / 2 - (state.y + state.shakeOffsetY) * state.zoom,\n\t\t\t\t);\n\t\t\t\troot.scale.set(state.zoom);\n\t\t\t\troot.rotation = -(state.rotation + state.shakeRotation);\n\t\t\t})\n\t\t\t.and();\n\t}\n\n\t// Declare reactive query names registered by this bundle\n\tconst typedRendererBundle = rendererBundle.withReactiveQueryNames<'renderer2d-sprites' | 'renderer2d-graphics' | 'renderer2d-containers'>();\n\n\t// Merge transform bundle (runs first) with renderer bundle\n\tconst transformBundle = createTransformBundle(transformOptions);\n\treturn mergeBundles('renderer2d', transformBundle, typedRendererBundle) as unknown as Bundle<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes, {}, {}, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "E396407048E28B7064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:R,getOwnPropertyNames:S,getOwnPropertyDescriptor:C}=Object,I=Object.prototype.hasOwnProperty;var v=new WeakMap,f=(j)=>{var w=v.get(j),D;if(w)return w;if(w=R({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")S(j).map((F)=>!I.call(w,F)&&R(w,F,{get:()=>j[F],enumerable:!(D=C(j,F))||D.enumerable}));return v.set(j,w),w};var b=(j,w)=>{for(var D in w)R(j,D,{get:w[D],enumerable:!0,configurable:!0,set:(F)=>w[D]=()=>F})};var h=(j,w)=>()=>(j&&(w=j(j=0)),w);var g=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(w,D)=>(typeof require<"u"?require:w)[D]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as Y}from"ecspresso";function H(j,w){return j*73856093^w*19349663}function W(j){return{cellSize:j,invCellSize:1/j,cells:new Map,entries:new Map}}function Q(j){j.cells.clear(),j.entries.clear()}function p(j,w,D,F,K,L){j.entries.set(w,{entityId:w,x:D,y:F,halfW:K,halfH:L});let M=j.invCellSize,U=Math.floor((D-K)*M),J=Math.floor((D+K)*M),Z=Math.floor((F-L)*M),$=Math.floor((F+L)*M);for(let O=U;O<=J;O++)for(let N=Z;N<=$;N++){let T=H(O,N),V=j.cells.get(T);if(V)V.push(w);else j.cells.set(T,[w])}}function G(j,w,D,F,K,L){let M=j.invCellSize,U=Math.floor(w*M),J=Math.floor(F*M),Z=Math.floor(D*M),$=Math.floor(K*M);for(let O=U;O<=J;O++)for(let N=Z;N<=$;N++){let T=j.cells.get(H(O,N));if(!T)continue;for(let V=0;V<T.length;V++)L.add(T[V])}}var X=new Set;function _(j,w,D,F,K){let L=X;L.clear(),G(j,w-F,D-F,w+F,D+F,L);let M=F*F;for(let U of L){let J=j.entries.get(U);if(!J)continue;let Z=Math.max(J.x-J.halfW,Math.min(w,J.x+J.halfW)),$=Math.max(J.y-J.halfH,Math.min(D,J.y+J.halfH)),O=w-Z,N=D-$;if(O*O+N*N<=M)K.add(U)}}var E=new Set;function o(j){return{grid:j,queryRect(w,D,F,K){return E.clear(),G(j,w,D,F,K,E),Array.from(E)},queryRectInto(w,D,F,K,L){G(j,w,D,F,K,L)},queryRadius(w,D,F){return E.clear(),_(j,w,D,F,E),Array.from(E)},queryRadiusInto(w,D,F,K){_(j,w,D,F,K)},getEntry(w){return j.entries.get(w)}}}function y(j){let{cellSize:w=64,systemGroup:D="spatialIndex",priority:F=2000,phases:K=["fixedUpdate","postUpdate"]}=j??{},L=W(w),M=o(L),U=new Y("spatialIndex");U.addResource("spatialIndex",M);for(let J of K){let Z=J==="fixedUpdate"?"localTransform":"worldTransform";U.addSystem(`spatial-index-rebuild-${J}`).setPriority(F).inPhase(J).inGroup(D).addQuery("transforms",{with:[Z]}).setProcess(($,O,N)=>{Q(L);for(let T of $.transforms){let V=T.components[Z],A=N.entityManager.getComponent(T.id,"aabbCollider"),B=N.entityManager.getComponent(T.id,"circleCollider");if(!A&&!B)continue;let{x:z,y:k}=V,P=0,q=0;if(A)z+=A.offsetX??0,k+=A.offsetY??0,P=A.width/2,q=A.height/2;if(B)z+=B.offsetX??0,k+=B.offsetY??0,P=Math.max(P,B.radius),q=Math.max(q,B.radius);p(L,T.id,z,k,P,q)}}).and()}return U}export{y as createSpatialIndexBundle};
|
|
2
|
+
|
|
3
|
+
//# debugId=21AF37DA8CD0DF8064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=spatial-index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/spatial-index.ts", "../src/utils/spatial-hash.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Spatial Index Bundle for ECSpresso\n *\n * Provides a uniform-grid spatial hash for broadphase collision detection\n * and proximity queries. Replaces O(n²) brute-force with O(n·d) where\n * d = local density.\n *\n * Standalone usage: queryRect / queryRadius for proximity queries.\n * Automatic acceleration: collision and physics2D bundles detect the\n * spatialIndex resource at runtime and use it for broadphase when present.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\nimport type { CollisionComponentTypes } from './collision';\nimport {\n\ttype SpatialEntry,\n\ttype SpatialHashGrid,\n\ttype SpatialIndex,\n\tcreateGrid,\n\tclearGrid,\n\tinsertEntity,\n\tgridQueryRect,\n\tgridQueryRadius,\n} from '../utils/spatial-hash';\n\n// Module-scoped reusable set to reduce GC pressure\nconst _reusableQuerySet = new Set<number>();\n\n// ==================== Resource API ====================\n\nexport interface SpatialIndexResourceTypes {\n\tspatialIndex: SpatialIndex;\n}\n\nfunction createSpatialIndexResource(grid: SpatialHashGrid): SpatialIndex {\n\treturn {\n\t\tgrid,\n\t\tqueryRect(minX: number, minY: number, maxX: number, maxY: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryRect(grid, minX, minY, maxX, maxY, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void {\n\t\t\tgridQueryRect(grid, minX, minY, maxX, maxY, result);\n\t\t},\n\t\tqueryRadius(cx: number, cy: number, radius: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryRadius(grid, cx, cy, radius, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void {\n\t\t\tgridQueryRadius(grid, cx, cy, radius, result);\n\t\t},\n\t\tgetEntry(entityId: number): SpatialEntry | undefined {\n\t\t\treturn grid.entries.get(entityId);\n\t\t},\n\t};\n}\n\n// ==================== Component Types ====================\n\ntype SpatialIndexComponentTypes =\n\tTransformComponentTypes & Pick<CollisionComponentTypes<string>, 'aabbCollider' | 'circleCollider'>;\n\n// ==================== Bundle Options ====================\n\nexport type SpatialIndexPhase = 'fixedUpdate' | 'postUpdate';\ntype SpatialIndexLabel = `spatial-index-rebuild-${SpatialIndexPhase}`;\n\nexport interface SpatialIndexBundleOptions<G extends string = 'spatialIndex'> {\n\t/** Cell size for the spatial hash grid (default: 64) */\n\tcellSize?: number;\n\t/** System group name (default: 'spatialIndex') */\n\tsystemGroup?: G;\n\t/** Priority for rebuild systems (default: 2000, before collision) */\n\tpriority?: number;\n\t/** Phases to register rebuild systems in (default: ['fixedUpdate', 'postUpdate']) */\n\tphases?: ReadonlyArray<SpatialIndexPhase>;\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a spatial index bundle for ECSpresso.\n *\n * Provides a uniform-grid spatial hash that accelerates collision detection.\n * When installed alongside the collision or physics2D bundles, they\n * automatically use the spatial index for broadphase instead of O(n²)\n * brute-force.\n *\n * Also provides proximity query methods for game logic (e.g. \"find all\n * enemies within 200 units\").\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTransformBundle())\n * .withBundle(createCollisionBundle({ layers }))\n * .withBundle(createSpatialIndexBundle({ cellSize: 128 }))\n * .build();\n *\n * // Proximity query in a system:\n * const si = ecs.getResource('spatialIndex');\n * const nearby = si.queryRadius(playerX, playerY, 200);\n * ```\n */\nexport function createSpatialIndexBundle<G extends string = 'spatialIndex'>(\n\toptions?: SpatialIndexBundleOptions<G>,\n): Bundle<SpatialIndexComponentTypes, {}, SpatialIndexResourceTypes, {}, {}, SpatialIndexLabel, G> {\n\tconst {\n\t\tcellSize = 64,\n\t\tsystemGroup = 'spatialIndex',\n\t\tpriority = 2000,\n\t\tphases = ['fixedUpdate', 'postUpdate'] as const,\n\t} = options ?? {};\n\n\tconst grid = createGrid(cellSize);\n\tconst resource = createSpatialIndexResource(grid);\n\n\tconst bundle = new Bundle<SpatialIndexComponentTypes, {}, SpatialIndexResourceTypes>('spatialIndex');\n\n\tbundle.addResource('spatialIndex', resource);\n\n\t// Register a rebuild system for each requested phase\n\tfor (const phase of phases) {\n\t\tconst transformComponent = phase === 'fixedUpdate' ? 'localTransform' : 'worldTransform';\n\n\t\tbundle\n\t\t\t.addSystem(`spatial-index-rebuild-${phase}`)\n\t\t\t.setPriority(priority)\n\t\t\t.inPhase(phase as SystemPhase)\n\t\t\t.inGroup(systemGroup)\n\t\t\t.addQuery('transforms', {\n\t\t\t\twith: [transformComponent],\n\t\t\t})\n\t\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t\tclearGrid(grid);\n\n\t\t\t\tfor (const entity of queries.transforms) {\n\t\t\t\t\tconst transform = entity.components[transformComponent];\n\t\t\t\t\tconst aabb = ecs.entityManager.getComponent(entity.id, 'aabbCollider');\n\t\t\t\t\tconst circle = ecs.entityManager.getComponent(entity.id, 'circleCollider');\n\n\t\t\t\t\t// Only insert entities that have a collider\n\t\t\t\t\tif (!aabb && !circle) continue;\n\n\t\t\t\t\tlet x = transform.x;\n\t\t\t\t\tlet y = transform.y;\n\t\t\t\t\tlet halfW = 0;\n\t\t\t\t\tlet halfH = 0;\n\n\t\t\t\t\tif (aabb) {\n\t\t\t\t\t\tx += aabb.offsetX ?? 0;\n\t\t\t\t\t\ty += aabb.offsetY ?? 0;\n\t\t\t\t\t\thalfW = aabb.width / 2;\n\t\t\t\t\t\thalfH = aabb.height / 2;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (circle) {\n\t\t\t\t\t\tx += circle.offsetX ?? 0;\n\t\t\t\t\t\ty += circle.offsetY ?? 0;\n\t\t\t\t\t\t// Circle: use radius as half-extent in both dimensions\n\t\t\t\t\t\thalfW = Math.max(halfW, circle.radius);\n\t\t\t\t\t\thalfH = Math.max(halfH, circle.radius);\n\t\t\t\t\t}\n\n\t\t\t\t\tinsertEntity(grid, entity.id, x, y, halfW, halfH);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.and();\n\t}\n\n\treturn bundle as Bundle<SpatialIndexComponentTypes, {}, SpatialIndexResourceTypes, {}, {}, SpatialIndexLabel, G>;\n}\n",
|
|
6
|
+
"/**\n * Spatial Hash Grid\n *\n * Uniform-grid spatial hash for broadphase collision detection and\n * proximity queries. Pure data structure, no ECS dependencies.\n */\n\n// ==================== Data Structure ====================\n\nexport interface SpatialEntry {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\thalfW: number;\n\thalfH: number;\n}\n\nexport interface SpatialHashGrid {\n\tcellSize: number;\n\tinvCellSize: number;\n\tcells: Map<number, number[]>;\n\tentries: Map<number, SpatialEntry>;\n}\n\n// ==================== Pure Functions ====================\n\n/**\n * Hash a cell coordinate pair to a single integer key.\n * Uses large-prime XOR to distribute values.\n */\nexport function hashCell(cx: number, cy: number): number {\n\t// Large primes for spatial hashing distribution\n\treturn (cx * 73856093) ^ (cy * 19349663);\n}\n\n/**\n * Create a new empty spatial hash grid.\n */\nexport function createGrid(cellSize: number): SpatialHashGrid {\n\treturn {\n\t\tcellSize,\n\t\tinvCellSize: 1 / cellSize,\n\t\tcells: new Map(),\n\t\tentries: new Map(),\n\t};\n}\n\n/**\n * Clear all data from the grid without reallocating the Maps.\n */\nexport function clearGrid(grid: SpatialHashGrid): void {\n\tgrid.cells.clear();\n\tgrid.entries.clear();\n}\n\n/**\n * Insert an entity into all overlapping cells of the grid.\n */\nexport function insertEntity(\n\tgrid: SpatialHashGrid,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\thalfW: number,\n\thalfH: number,\n): void {\n\tgrid.entries.set(entityId, { entityId, x, y, halfW, halfH });\n\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor((x - halfW) * inv);\n\tconst maxCX = Math.floor((x + halfW) * inv);\n\tconst minCY = Math.floor((y - halfH) * inv);\n\tconst maxCY = Math.floor((y + halfH) * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst key = hashCell(cx, cy);\n\t\t\tconst bucket = grid.cells.get(key);\n\t\t\tif (bucket) {\n\t\t\t\tbucket.push(entityId);\n\t\t\t} else {\n\t\t\t\tgrid.cells.set(key, [entityId]);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Collect entity IDs from all cells overlapping the given rectangle.\n */\nexport function gridQueryRect(\n\tgrid: SpatialHashGrid,\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tresult: Set<number>,\n): void {\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor(minX * inv);\n\tconst maxCX = Math.floor(maxX * inv);\n\tconst minCY = Math.floor(minY * inv);\n\tconst maxCY = Math.floor(maxY * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst bucket = grid.cells.get(hashCell(cx, cy));\n\t\t\tif (!bucket) continue;\n\t\t\tfor (let i = 0; i < bucket.length; i++) {\n\t\t\t\tresult.add(bucket[i]!);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Module-scoped reusable set to reduce GC pressure\nconst _radiusCandidates = new Set<number>();\n\n/**\n * Collect entity IDs within a circle. Uses rect broadphase then\n * AABB-to-point distance filter.\n */\nexport function gridQueryRadius(\n\tgrid: SpatialHashGrid,\n\tcx: number,\n\tcy: number,\n\tradius: number,\n\tresult: Set<number>,\n): void {\n\t// Broadphase: rect query for bounding box of circle\n\tconst candidates = _radiusCandidates;\n\tcandidates.clear();\n\tgridQueryRect(grid, cx - radius, cy - radius, cx + radius, cy + radius, candidates);\n\n\tconst rSq = radius * radius;\n\n\tfor (const entityId of candidates) {\n\t\tconst entry = grid.entries.get(entityId);\n\t\tif (!entry) continue;\n\n\t\t// Closest point on entity AABB to query center\n\t\tconst closestX = Math.max(entry.x - entry.halfW, Math.min(cx, entry.x + entry.halfW));\n\t\tconst closestY = Math.max(entry.y - entry.halfH, Math.min(cy, entry.y + entry.halfH));\n\t\tconst dx = cx - closestX;\n\t\tconst dy = cy - closestY;\n\n\t\tif (dx * dx + dy * dy <= rSq) {\n\t\t\tresult.add(entityId);\n\t\t}\n\t}\n}\n\n// ==================== Resource API ====================\n\nexport interface SpatialIndex {\n\treadonly grid: SpatialHashGrid;\n\tqueryRect(minX: number, minY: number, maxX: number, maxY: number): number[];\n\tqueryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void;\n\tqueryRadius(cx: number, cy: number, radius: number): number[];\n\tqueryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void;\n\tgetEntry(entityId: number): SpatialEntry | undefined;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "21AF37DA8CD0DF8064756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:U,getOwnPropertyNames:Z,getOwnPropertyDescriptor:_}=Object,$=Object.prototype.hasOwnProperty;var Y=new WeakMap,q=(j)=>{var k=Y.get(j),x;if(k)return k;if(k=U({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")Z(j).map((A)=>!$.call(k,A)&&U(k,A,{get:()=>j[A],enumerable:!(x=_(j,A))||x.enumerable}));return Y.set(j,k),k};var B=(j,k)=>{for(var x in k)U(j,x,{get:k[x],enumerable:!0,configurable:!0,set:(A)=>k[x]=()=>A})};var N=(j,k)=>()=>(j&&(k=j(j=0)),k);var v=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,x)=>(typeof require<"u"?require:k)[x]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as w}from"ecspresso";function E(j,k){return Object.freeze({id:j,initial:k.initial,states:Object.freeze(k.states)})}function G(j,k){let x=k?.initial??j.initial;return{stateMachine:{definition:j,current:x,previous:null,stateTime:0}}}function O(j,k,x,A){let J=x.definition.states,F=J[x.current],D=J[A];if(!D)return!1;return F?.onExit?.(j,k),x.previous=x.current,x.current=A,x.stateTime=0,D.onEnter?.(j,k),j.markChanged(k,"stateMachine"),j.eventBus.publish("stateTransition",{entityId:k,from:x.previous,to:x.current,definitionId:x.definition.id}),!0}function W(j,k,x){let A=j.entityManager.getComponent(k,"stateMachine");if(!A)return!1;return O(j,k,A,x)}function C(j,k,x){let A=j.entityManager.getComponent(k,"stateMachine");if(!A)return!1;let F=A.definition.states[A.current];if(!F?.on)return!1;let D=F.on[x];if(D===void 0)return!1;if(typeof D==="string")return O(j,k,A,D);if(!D.guard(j,k))return!1;return O(j,k,A,D.target)}function T(j,k){return j.entityManager.getComponent(k,"stateMachine")?.current}function h(j){return{bundle:b(j),defineStateMachine:E,createStateMachine:G}}function b(j){let{systemGroup:k="stateMachine",priority:x=0,phase:A="update"}=j??{},J=new w("stateMachine"),F=new Set;return J.addSystem("state-machine-update").setPriority(x).inPhase(A).inGroup(k).addQuery("machines",{with:["stateMachine"]}).setOnInitialize((D)=>{D.onComponentRemoved("stateMachine",(P,Q)=>{F.delete(Q.id)})}).setProcess((D,P,Q)=>{for(let H of D.machines){let K=H.components.stateMachine,R=K.definition.states,L=Q;if(!F.has(H.id))F.add(H.id),R[K.current]?.onEnter?.(L,H.id);K.stateTime+=P,R[K.current]?.onUpdate?.(L,H.id,P);let V=R[K.current];if(V?.transitions){for(let X of V.transitions)if(X.guard(L,H.id)){O(L,H.id,K,X.target);break}}}}).and(),J}export{W as transitionTo,C as sendEvent,T as getStateMachineState,E as defineStateMachine,h as createStateMachineKit,b as createStateMachineBundle,G as createStateMachine};
|
|
2
|
+
|
|
3
|
+
//# debugId=3E99D19B151FCC8064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=state-machine.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/state-machine.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * State Machine Bundle for ECSpresso\n *\n * Provides ECS-native finite state machines with guard-based transitions,\n * event-driven transitions, and lifecycle hooks (onEnter, onExit, onUpdate).\n *\n * Each entity gets a `stateMachine` component referencing a shared definition.\n * One system processes all state machine entities each tick.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\n\n// ==================== World Interface ====================\n\n/**\n * Structural interface for ECS methods available inside state machine hooks.\n * Uses method syntax for bivariant parameter checking under strictFunctionTypes,\n * allowing users to annotate hooks with their concrete ECSpresso type.\n */\nexport interface StateMachineWorld {\n\tentityManager: {\n\t\tgetComponent(entityId: number, componentName: string): unknown | undefined;\n\t};\n\teventBus: {\n\t\tpublish(eventType: string, data: unknown): void;\n\t};\n\tspawn(components: Record<string, unknown>): { id: number };\n\tremoveEntity(entityOrId: number): boolean;\n\thasComponent(entityId: number, componentName: string): boolean;\n\tgetResource(key: string): unknown;\n\thasResource(key: string): boolean;\n\tmarkChanged(entityId: number, componentName: string): void;\n\tcommands: {\n\t\tspawn(components: Record<string, unknown>): void;\n\t\tremoveEntity(entityId: number): void;\n\t};\n}\n\n// ==================== State Config ====================\n\n/**\n * Configuration for a single state in a state machine definition.\n *\n * @template S - Union of state name strings\n * @template W - World interface type for hooks/guards (default: StateMachineWorld)\n */\nexport interface StateConfig<S extends string, W extends StateMachineWorld = StateMachineWorld> {\n\t/** Called when entering this state */\n\tonEnter?(ecs: W, entityId: number): void;\n\t/** Called when exiting this state */\n\tonExit?(ecs: W, entityId: number): void;\n\t/** Called each tick while in this state */\n\tonUpdate?(ecs: W, entityId: number, deltaTime: number): void;\n\t/** Guard-based transitions evaluated each tick. First passing guard wins. */\n\ttransitions?: ReadonlyArray<{\n\t\ttarget: S;\n\t\tguard(ecs: W, entityId: number): boolean;\n\t}>;\n\t/** Event-based transition map: eventName → target state or guarded transition */\n\ton?: Record<string, S | { target: S; guard(ecs: W, entityId: number): boolean }>;\n}\n\n// ==================== State Machine Definition ====================\n\n/**\n * Immutable definition of a state machine. Shared across entities.\n *\n * @template S - Union of state name strings\n */\nexport interface StateMachineDefinition<S extends string> {\n\treadonly id: string;\n\treadonly initial: S;\n\treadonly states: { readonly [K in S]: StateConfig<S> };\n}\n\n// ==================== Component ====================\n\n/**\n * Runtime state machine component stored on each entity.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachine<S extends string = string> {\n\treadonly definition: StateMachineDefinition<string>;\n\tcurrent: S;\n\tprevious: S | null;\n\tstateTime: number;\n}\n\n/**\n * Component types provided by the state machine bundle.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachineComponentTypes<S extends string = string> {\n\tstateMachine: StateMachine<S>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event published on every state transition.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateTransitionEvent<S extends string = string> {\n\tentityId: number;\n\tfrom: S;\n\tto: S;\n\tdefinitionId: string;\n}\n\n/**\n * Event types provided by the state machine bundle.\n *\n * @template S - Union of state name strings (default: string)\n */\nexport interface StateMachineEventTypes<S extends string = string> {\n\tstateTransition: StateTransitionEvent<S>;\n}\n\n/**\n * Extract the state name union from a StateMachineDefinition.\n *\n * @example\n * ```typescript\n * const enemyFSM = defineStateMachine('enemy', { initial: 'idle', states: { idle: {}, chase: {} } });\n * type EnemyStates = StatesOf<typeof enemyFSM>; // 'idle' | 'chase'\n * type AllStates = StatesOf<typeof enemyFSM> | StatesOf<typeof playerFSM>;\n * ```\n */\nexport type StatesOf<D> = D extends StateMachineDefinition<infer S> ? S : never;\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the state machine bundle.\n */\nexport interface StateMachineBundleOptions<G extends string = 'stateMachine'> {\n\t/** System group name (default: 'stateMachine') */\n\tsystemGroup?: G;\n\t/** Priority for state machine system (default: 0) */\n\tpriority?: number;\n\t/** Execution phase (default: 'update') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Define a state machine with type-safe state names.\n *\n * @template S - Union of state name strings, inferred from `states` keys\n * @param id - Unique identifier for this definition\n * @param config - Initial state and state configurations\n * @returns A frozen StateMachineDefinition\n *\n * @example\n * ```typescript\n * const enemyFSM = defineStateMachine('enemy', {\n * initial: 'idle',\n * states: {\n * idle: {\n * onEnter: (ecs, id) => { ... },\n * transitions: [{ target: 'chase', guard: (ecs, id) => playerNearby(ecs, id) }],\n * },\n * chase: {\n * onUpdate: (ecs, id, dt) => { ... },\n * on: { playerLost: 'idle' },\n * },\n * },\n * });\n * ```\n */\nexport function defineStateMachine<S extends string>(\n\tid: string,\n\tconfig: { initial: NoInfer<S>; states: Record<S, StateConfig<NoInfer<S>>> },\n): StateMachineDefinition<S> {\n\treturn Object.freeze({\n\t\tid,\n\t\tinitial: config.initial,\n\t\tstates: Object.freeze(config.states),\n\t}) as StateMachineDefinition<S>;\n}\n\n/**\n * Create a stateMachine component from a definition.\n *\n * @param definition - The state machine definition to use\n * @param options - Optional overrides (e.g., initial state)\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createStateMachine(enemyFSM),\n * position: { x: 100, y: 200 },\n * });\n * ```\n */\nexport function createStateMachine<S extends string>(\n\tdefinition: StateMachineDefinition<S>,\n\toptions?: { initial?: S },\n): Pick<StateMachineComponentTypes<S>, 'stateMachine'> {\n\tconst initial = options?.initial ?? definition.initial;\n\treturn {\n\t\tstateMachine: {\n\t\t\tdefinition,\n\t\t\tcurrent: initial,\n\t\t\tprevious: null,\n\t\t\tstateTime: 0,\n\t\t},\n\t};\n}\n\n// ==================== Internal: Shared Transition Logic ====================\n\n/**\n * Perform a state transition: onExit → update fields → onEnter → markChanged → publish event.\n * Returns true if the target state exists, false otherwise.\n */\nfunction performTransition(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\tsm: StateMachine,\n\ttargetState: string,\n): boolean {\n\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\tconst currentConfig = states[sm.current];\n\tconst targetConfig = states[targetState];\n\n\tif (!targetConfig) return false;\n\n\tcurrentConfig?.onExit?.(ecs, entityId);\n\n\tsm.previous = sm.current;\n\tsm.current = targetState;\n\tsm.stateTime = 0;\n\n\ttargetConfig.onEnter?.(ecs, entityId);\n\n\tecs.markChanged(entityId, 'stateMachine');\n\tecs.eventBus.publish('stateTransition', {\n\t\tentityId,\n\t\tfrom: sm.previous,\n\t\tto: sm.current,\n\t\tdefinitionId: sm.definition.id,\n\t} satisfies StateTransitionEvent);\n\n\treturn true;\n}\n\n// ==================== Utility Functions ====================\n\n/**\n * Directly transition an entity's state machine to a target state.\n * Fires onExit, onEnter hooks and publishes stateTransition event.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to transition\n * @param targetState - State to transition to\n * @returns true if transition succeeded, false if entity has no stateMachine or target state doesn't exist\n */\nexport function transitionTo(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\ttargetState: string,\n): boolean {\n\tconst sm = ecs.entityManager.getComponent(entityId, 'stateMachine') as StateMachine | undefined;\n\tif (!sm) return false;\n\treturn performTransition(ecs, entityId, sm, targetState);\n}\n\n/**\n * Send a named event to an entity's state machine.\n * Checks the current state's `on` handlers for a matching event.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to send event to\n * @param eventName - Event name to match against `on` handlers\n * @returns true if a transition occurred, false otherwise\n */\nexport function sendEvent(\n\tecs: StateMachineWorld,\n\tentityId: number,\n\teventName: string,\n): boolean {\n\tconst sm = ecs.entityManager.getComponent(entityId, 'stateMachine') as StateMachine | undefined;\n\tif (!sm) return false;\n\n\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\tconst currentConfig = states[sm.current];\n\tif (!currentConfig?.on) return false;\n\n\tconst handler = currentConfig.on[eventName];\n\tif (handler === undefined) return false;\n\n\tif (typeof handler === 'string') {\n\t\treturn performTransition(ecs, entityId, sm, handler);\n\t}\n\n\tif (!handler.guard(ecs, entityId)) return false;\n\treturn performTransition(ecs, entityId, sm, handler.target);\n}\n\n/**\n * Get the current state of an entity's state machine.\n *\n * @param ecs - ECS instance (structural typing)\n * @param entityId - Entity to query\n * @returns The current state string, or undefined if entity has no stateMachine\n */\nexport function getStateMachineState(\n\tecs: StateMachineWorld,\n\tentityId: number,\n): string | undefined {\n\tconst sm = ecs.entityManager.getComponent(entityId, 'stateMachine') as StateMachine | undefined;\n\treturn sm?.current;\n}\n\n// ==================== State Machine Kit ====================\n\n/**\n * A typed kit that captures the world type W once, providing helpers\n * where hooks/guards contextually receive W instead of StateMachineWorld.\n *\n * @template W - Concrete ECS world type\n */\nexport interface StateMachineKit<W extends StateMachineWorld, S extends string = string> {\n\tbundle: Bundle<StateMachineComponentTypes<S>, StateMachineEventTypes<S>, {}, {}, {}, 'state-machine-update', 'stateMachine'>;\n\tdefineStateMachine: <DS extends S>(\n\t\tid: string,\n\t\tconfig: { initial: NoInfer<DS>; states: Record<DS, StateConfig<NoInfer<DS>, W>> },\n\t) => StateMachineDefinition<DS>;\n\tcreateStateMachine: <DS extends S>(\n\t\tdefinition: StateMachineDefinition<DS>,\n\t\toptions?: { initial?: DS },\n\t) => Pick<StateMachineComponentTypes<S>, 'stateMachine'>;\n}\n\n/**\n * Create a typed state machine kit that captures the world type W.\n *\n * Hooks and guards in definitions created via the kit's `defineStateMachine`\n * contextually receive W as their `ecs` parameter — no manual annotations needed.\n *\n * @template W - Concrete ECS world type\n * @param options - Optional bundle configuration (same as createStateMachineBundle)\n * @returns A kit object with bundle, defineStateMachine, createStateMachine, transitionTo, sendEvent, getStateMachineState\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createStateMachineBundle())\n * .withComponentTypes<{ enemy: true }>()\n * .build();\n *\n * type ECS = typeof ecs;\n * const { bundle, defineStateMachine, createStateMachine } =\n * createStateMachineKit<ECS>();\n *\n * const enemyFSM = defineStateMachine('enemy', {\n * initial: 'patrol',\n * states: {\n * patrol: {\n * onEnter(ecs, entityId) {\n * ecs.getResource('bounds'); // fully typed\n * },\n * transitions: [{\n * target: 'chase',\n * guard: (ecs, entityId) => distanceToPlayer(ecs, entityId) < 180,\n * }],\n * },\n * chase: {},\n * },\n * });\n * ```\n */\nexport function createStateMachineKit<W extends StateMachineWorld = StateMachineWorld, S extends string = string>(\n\toptions?: StateMachineBundleOptions,\n): StateMachineKit<W, S> {\n\treturn {\n\t\tbundle: createStateMachineBundle<S>(options),\n\t\tdefineStateMachine: defineStateMachine as StateMachineKit<W, S>['defineStateMachine'],\n\t\tcreateStateMachine: createStateMachine as StateMachineKit<W, S>['createStateMachine'],\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a state machine bundle for ECSpresso.\n *\n * Provides:\n * - Lifecycle hooks (onEnter, onExit, onUpdate) per state\n * - Guard-based automatic transitions evaluated each tick\n * - Event-based transitions via `sendEvent()`\n * - Direct transitions via `transitionTo()`\n * - stateTransition events published on every transition\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createStateMachineBundle())\n * .build();\n *\n * const fsm = defineStateMachine('enemy', {\n * initial: 'idle',\n * states: {\n * idle: {\n * transitions: [{ target: 'chase', guard: (ecs, id) => playerNearby(ecs, id) }],\n * },\n * chase: {\n * onUpdate: (ecs, id, dt) => moveTowardPlayer(ecs, id, dt),\n * on: { playerLost: 'idle' },\n * },\n * },\n * });\n *\n * ecs.spawn({\n * ...createStateMachine(fsm),\n * position: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createStateMachineBundle<S extends string = string, G extends string = 'stateMachine'>(\n\toptions?: StateMachineBundleOptions<G>,\n): Bundle<StateMachineComponentTypes<S>, StateMachineEventTypes<S>, {}, {}, {}, 'state-machine-update', G> {\n\tconst {\n\t\tsystemGroup = 'stateMachine',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<StateMachineComponentTypes<S>, StateMachineEventTypes<S>, {}>('stateMachine');\n\n\tconst initialized = new Set<number>();\n\n\tbundle\n\t\t.addSystem('state-machine-update')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('machines', {\n\t\t\twith: ['stateMachine'],\n\t\t})\n\t\t.setOnInitialize((ecs) => {\n\t\t\tecs.onComponentRemoved('stateMachine', (_value, entity) => {\n\t\t\t\tinitialized.delete(entity.id);\n\t\t\t});\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tfor (const entity of queries.machines) {\n\t\t\t\tconst sm = entity.components.stateMachine;\n\t\t\t\tconst states = sm.definition.states as Record<string, StateConfig<string>>;\n\t\t\t\tconst ecsWorld = ecs as unknown as StateMachineWorld;\n\n\t\t\t\t// Initialize: fire onEnter for initial state on first tick\n\t\t\t\tif (!initialized.has(entity.id)) {\n\t\t\t\t\tinitialized.add(entity.id);\n\t\t\t\t\tstates[sm.current]?.onEnter?.(ecsWorld, entity.id);\n\t\t\t\t}\n\n\t\t\t\t// Accumulate state time\n\t\t\t\tsm.stateTime += deltaTime;\n\n\t\t\t\t// onUpdate hook\n\t\t\t\tstates[sm.current]?.onUpdate?.(ecsWorld, entity.id, deltaTime);\n\n\t\t\t\t// Evaluate guard transitions (first passing guard wins)\n\t\t\t\tconst currentConfig = states[sm.current];\n\t\t\t\tif (currentConfig?.transitions) {\n\t\t\t\t\tfor (const transition of currentConfig.transitions) {\n\t\t\t\t\t\tif (transition.guard(ecsWorld, entity.id)) {\n\t\t\t\t\t\t\tperformTransition(ecsWorld, entity.id, sm, transition.target);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<StateMachineComponentTypes<S>, StateMachineEventTypes<S>, {}, {}, {}, 'state-machine-update', G>;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "3E99D19B151FCC8064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:L,getOwnPropertyNames:R,getOwnPropertyDescriptor:U}=Object,V=Object.prototype.hasOwnProperty;var Q=new WeakMap,X=(j)=>{var k=Q.get(j),z;if(k)return k;if(k=L({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")R(j).map((A)=>!V.call(k,A)&&L(k,A,{get:()=>j[A],enumerable:!(z=U(j,A))||z.enumerable}));return Q.set(j,k),k};var Y=(j,k)=>{for(var z in k)L(j,z,{get:k[z],enumerable:!0,configurable:!0,set:(A)=>k[z]=()=>A})};var Z=(j,k)=>()=>(j&&(k=j(j=0)),k);var _=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,z)=>(typeof require<"u"?require:k)[z]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as W}from"ecspresso";function B(j,k){return{timer:{elapsed:0,duration:j,repeat:!1,active:!0,justFinished:!1,onComplete:k?.onComplete}}}function C(j,k){return{timer:{elapsed:0,duration:j,repeat:!0,active:!0,justFinished:!1,onComplete:k?.onComplete}}}function G(j){let{systemGroup:k="timers",priority:z=0,phase:A="preUpdate"}=j??{},M=new W("timers");M.addSystem("timer-update").setPriority(z).inPhase(A).inGroup(k).addQuery("timers",{with:["timer"]}).setProcess((J,K,F)=>{for(let H of J.timers){let{timer:x}=H.components;if(x.justFinished=!1,!x.active)continue;if(x.elapsed+=K,x.elapsed<x.duration)continue;if(x.repeat)while(x.elapsed>=x.duration)x.justFinished=!0,O(F,H.id,x),x.elapsed-=x.duration;else x.justFinished=!0,O(F,H.id,x),x.active=!1,F.commands.removeEntity(H.id)}}).and();function O(J,K,F){if(!F.onComplete)return;let H={entityId:K,duration:F.duration,elapsed:F.elapsed};J.eventBus.publish(F.onComplete,H)}return M}export{G as createTimerBundle,B as createTimer,C as createRepeatingTimer};
|
|
2
|
+
|
|
3
|
+
//# debugId=24F664FC7BF6417364756E2164756E21
|
|
4
|
+
//# sourceMappingURL=timers.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/timers.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Timer Bundle for ECSpresso\n *\n * Provides ECS-native timers following the \"data, not callbacks\" philosophy.\n * Timers are components processed each frame, automatically cleaned up when entities are removed.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\n\n// ==================== Event Types ====================\n\n/**\n * Data structure published when a timer completes.\n * Use this type when defining timer completion events in your EventTypes interface.\n *\n * @example\n * ```typescript\n * interface Events {\n * hideMessage: TimerEventData;\n * spawnWave: TimerEventData;\n * }\n * ```\n */\nexport interface TimerEventData {\n\t/** The entity ID that the timer belongs to */\n\tentityId: number;\n\t/** The timer's configured duration in seconds */\n\tduration: number;\n\t/** The actual elapsed time (may exceed duration slightly) */\n\telapsed: number;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Extracts event names from EventTypes that have TimerEventData as their payload.\n * This ensures only compatible events can be used with timer.onComplete.\n */\nexport type TimerEventName<EventTypes extends Record<string, any>> = {\n\t[K in keyof EventTypes]: EventTypes[K] extends TimerEventData ? K : never\n}[keyof EventTypes];\n\n/**\n * Timer component data structure.\n * Use `justFinished` to detect timer completion in your systems.\n *\n * @template EventTypes The event types from your ECS\n */\nexport interface Timer<EventTypes extends Record<string, any>> {\n\t/** Time accumulated so far (seconds) */\n\telapsed: number;\n\t/** Target duration (seconds) */\n\tduration: number;\n\t/** Whether timer repeats after completion */\n\trepeat: boolean;\n\t/** Whether timer is currently running */\n\tactive: boolean;\n\t/** True for one frame after timer completes */\n\tjustFinished: boolean;\n\t/** Optional event name to publish when timer completes. Must be an event with TimerEventData payload. */\n\tonComplete?: TimerEventName<EventTypes>;\n}\n\n/**\n * Component types provided by the timer bundle.\n * Included automatically via `.withBundle(createTimerBundle<Events>())`.\n * The EventTypes generic constrains which events can be used with `onComplete`.\n *\n * @template EventTypes The event types from your ECS\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTimerBundle<{ respawn: TimerEventData }>())\n * .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()\n * .build();\n * ```\n */\nexport interface TimerComponentTypes<EventTypes extends Record<string, any>> {\n\ttimer: Timer<EventTypes>;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the timer bundle.\n */\nexport interface TimerBundleOptions<G extends string = 'timers'> {\n\t/** System group name (default: 'timers') */\n\tsystemGroup?: G;\n\t/** Priority for timer update system (default: 0) */\n\tpriority?: number;\n\t/** Execution phase (default: 'preUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Options for timer creation\n *\n * @template EventTypes The event types from your ECS\n */\nexport interface TimerOptions<EventTypes extends Record<string, any>> {\n\t/** Event name to publish when timer completes. Must be an event with TimerEventData payload. */\n\tonComplete?: TimerEventName<EventTypes>;\n}\n\n/**\n * Create a one-shot timer that fires once after the specified duration.\n *\n * @template EventTypes The event types from your ECS (must be explicitly provided)\n * @param duration Duration in seconds until the timer completes\n * @param options Optional configuration including event name\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * // Timer without event\n * ecs.spawn({\n * ...createTimer<GameEvents>(2),\n * explosion: true,\n * });\n *\n * // Timer that publishes an event on completion\n * ecs.spawn({\n * ...createTimer<GameEvents>(1.5, { onComplete: 'hideMessage' }),\n * });\n * ```\n */\nexport function createTimer<EventTypes extends Record<string, any>>(\n\tduration: number,\n\toptions?: TimerOptions<EventTypes>\n): Pick<TimerComponentTypes<EventTypes>, 'timer'> {\n\treturn {\n\t\ttimer: {\n\t\t\telapsed: 0,\n\t\t\tduration,\n\t\t\trepeat: false,\n\t\t\tactive: true,\n\t\t\tjustFinished: false,\n\t\t\tonComplete: options?.onComplete,\n\t\t},\n\t};\n}\n\n/**\n * Create a repeating timer that fires every `duration` seconds.\n *\n * @template EventTypes The event types from your ECS (must be explicitly provided)\n * @param duration Duration in seconds between each timer completion\n * @param options Optional configuration including event name\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * // Timer without event\n * ecs.spawn({\n * ...createRepeatingTimer<GameEvents>(5),\n * spawner: true,\n * });\n *\n * // Repeating timer that publishes an event each cycle\n * ecs.spawn({\n * ...createRepeatingTimer<GameEvents>(3, { onComplete: 'spawnWave' }),\n * });\n * ```\n */\nexport function createRepeatingTimer<EventTypes extends Record<string, any>>(\n\tduration: number,\n\toptions?: TimerOptions<EventTypes>\n): Pick<TimerComponentTypes<EventTypes>, 'timer'> {\n\treturn {\n\t\ttimer: {\n\t\t\telapsed: 0,\n\t\t\tduration,\n\t\t\trepeat: true,\n\t\t\tactive: true,\n\t\t\tjustFinished: false,\n\t\t\tonComplete: options?.onComplete,\n\t\t},\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a timer bundle for ECSpresso.\n *\n * This bundle provides:\n * - Timer update system that processes all timer components each frame\n * - `justFinished` flag pattern for one-frame completion detection\n * - Automatic cleanup when entities are removed\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withBundle(createTimerBundle())\n * .build();\n *\n * // Spawn entity with timer\n * ecs.spawn({\n * ...createRepeatingTimer(5),\n * spawner: true,\n * });\n *\n * // React to timer completion in a system\n * ecs.addSystem('spawn-on-timer')\n * .addQuery('spawners', { with: ['timer', 'spawner'] })\n * .setProcess((queries, _dt, ecs) => {\n * for (const { components } of queries.spawners) {\n * if (components.timer.justFinished) {\n * ecs.spawn({ enemy: true });\n * }\n * }\n * });\n * ```\n */\nexport function createTimerBundle<EventTypes extends Record<string, any>, G extends string = 'timers'>(\n\toptions?: TimerBundleOptions<G>\n): Bundle<TimerComponentTypes<EventTypes>, EventTypes, {}, {}, {}, 'timer-update', G> {\n\tconst {\n\t\tsystemGroup = 'timers',\n\t\tpriority = 0,\n\t\tphase = 'preUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<TimerComponentTypes<EventTypes>, EventTypes, {}>('timers');\n\n\tbundle\n\t\t.addSystem('timer-update')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('timers', {\n\t\t\twith: ['timer'],\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tfor (const entity of queries.timers) {\n\t\t\t\tconst { timer } = entity.components;\n\n\t\t\t\t// Reset justFinished flag from previous frame\n\t\t\t\ttimer.justFinished = false;\n\n\t\t\t\t// Skip inactive timers\n\t\t\t\tif (!timer.active) continue;\n\n\t\t\t\t// Accumulate time\n\t\t\t\ttimer.elapsed += deltaTime;\n\n\t\t\t\t// Check if timer completed\n\t\t\t\tif (timer.elapsed < timer.duration) continue;\n\n\t\t\t\t// Timer completed - handle based on repeat mode\n\t\t\t\tif (timer.repeat) {\n\t\t\t\t\t// Handle multiple cycles in one frame\n\t\t\t\t\twhile (timer.elapsed >= timer.duration) {\n\t\t\t\t\t\ttimer.justFinished = true;\n\t\t\t\t\t\tpublishTimerEvent(ecs, entity.id, timer);\n\t\t\t\t\t\ttimer.elapsed -= timer.duration;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// One-shot timer\n\t\t\t\t\ttimer.justFinished = true;\n\t\t\t\t\tpublishTimerEvent(ecs, entity.id, timer);\n\t\t\t\t\ttimer.active = false;\n\t\t\t\t\t// Auto-remove one-shot timer entities after completion.\n\t\t\t\t\t// If configurability is needed in the future, add an autoRemove option to TimerOptions.\n\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t/**\n\t * Publishes timer completion event if onComplete is specified.\n\t * Type assertion needed: TypeScript can't infer that TimerEventName<EventTypes>\n\t * maps to events with TimerEventData payloads, even though that's what the type enforces.\n\t */\n\tfunction publishTimerEvent(\n\t\tecs: { eventBus: { publish: (event: any, data: any) => void } },\n\t\tentityId: number,\n\t\ttimer: Timer<EventTypes>\n\t): void {\n\t\tif (!timer.onComplete) return;\n\t\tconst eventData: TimerEventData = {\n\t\t\tentityId,\n\t\t\tduration: timer.duration,\n\t\t\telapsed: timer.elapsed,\n\t\t};\n\t\tecs.eventBus.publish(timer.onComplete, eventData);\n\t}\n\n\treturn bundle as Bundle<TimerComponentTypes<EventTypes>, EventTypes, {}, {}, {}, 'timer-update', G>;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "uuBAOA,iBAAS,kBA4HF,SAAS,CAAmD,CAClE,EACA,EACiD,CACjD,MAAO,CACN,MAAO,CACN,QAAS,EACT,WACA,OAAQ,GACR,OAAQ,GACR,aAAc,GACd,WAAY,GAAS,UACtB,CACD,EAyBM,SAAS,CAA4D,CAC3E,EACA,EACiD,CACjD,MAAO,CACN,MAAO,CACN,QAAS,EACT,WACA,OAAQ,GACR,OAAQ,GACR,aAAc,GACd,WAAY,GAAS,UACtB,CACD,EAsCM,SAAS,CAAsF,CACrG,EACqF,CACrF,IACC,cAAc,SACd,WAAW,EACX,QAAQ,aACL,GAAW,CAAC,EAEV,EAAS,IAAI,EAAwD,QAAQ,EAEnF,EACE,UAAU,cAAc,EACxB,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,SAAU,CACnB,KAAM,CAAC,OAAO,CACf,CAAC,EACA,WAAW,CAAC,EAAS,EAAW,IAAQ,CACxC,QAAW,KAAU,EAAQ,OAAQ,CACpC,IAAQ,SAAU,EAAO,WAMzB,GAHA,EAAM,aAAe,GAGjB,CAAC,EAAM,OAAQ,SAMnB,GAHA,EAAM,SAAW,EAGb,EAAM,QAAU,EAAM,SAAU,SAGpC,GAAI,EAAM,OAET,MAAO,EAAM,SAAW,EAAM,SAC7B,EAAM,aAAe,GACrB,EAAkB,EAAK,EAAO,GAAI,CAAK,EACvC,EAAM,SAAW,EAAM,SAIxB,OAAM,aAAe,GACrB,EAAkB,EAAK,EAAO,GAAI,CAAK,EACvC,EAAM,OAAS,GAGf,EAAI,SAAS,aAAa,EAAO,EAAE,GAGrC,EACA,IAAI,EAON,SAAS,CAAiB,CACzB,EACA,EACA,EACO,CACP,GAAI,CAAC,EAAM,WAAY,OACvB,IAAM,EAA4B,CACjC,WACA,SAAU,EAAM,SAChB,QAAS,EAAM,OAChB,EACA,EAAI,SAAS,QAAQ,EAAM,WAAY,CAAS,EAGjD,OAAO",
|
|
8
|
+
"debugId": "24F664FC7BF6417364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:H,getOwnPropertyNames:N,getOwnPropertyDescriptor:O}=Object,P=Object.prototype.hasOwnProperty;var K=new WeakMap,V=(j)=>{var k=K.get(j),q;if(k)return k;if(k=H({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")N(j).map((v)=>!P.call(k,v)&&H(k,v,{get:()=>j[v],enumerable:!(q=O(j,v))||q.enumerable}));return K.set(j,k),k};var Z=(j,k)=>{for(var q in k)H(j,q,{get:k[q],enumerable:!0,configurable:!0,set:(v)=>k[q]=()=>v})};var _=(j,k)=>()=>(j&&(k=j(j=0)),k);var $=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as Q}from"ecspresso";var B={x:0,y:0,rotation:0,scaleX:1,scaleY:1},E={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function G(j,k){return{localTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function R(j,k){return{worldTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function h(j,k,q){let v=q?.scale??q?.scaleX??1,D=q?.scale??q?.scaleY??1,z=q?.rotation??0,C={x:j,y:k,rotation:z,scaleX:v,scaleY:D};return{localTransform:{...C},worldTransform:{...C}}}function W(j){let{systemGroup:k="transform",priority:q=500,phase:v="postUpdate"}=j??{},D=new Q("transform");return D.registerRequired("localTransform","worldTransform",(z)=>({x:z.x,y:z.y,rotation:z.rotation,scaleX:z.scaleX,scaleY:z.scaleY})),D.addSystem("transform-propagation").setPriority(q).inPhase(v).inGroup(k).setProcess((z,C,F)=>{S(F)}).and(),D}function S(j){let k=j.entityManager;j.forEachInHierarchy((v,D)=>{let z=k.getComponent(v,"localTransform"),C=k.getComponent(v,"worldTransform");if(!z||!C)return;if(D===null)J(z,C);else{let F=k.getComponent(D,"worldTransform");if(F)U(F,z,C);else J(z,C)}j.markChanged(v,"worldTransform")});let q=j.getEntitiesWithQuery(["localTransform","worldTransform"]);for(let v of q)if(j.getParent(v.id)===null&&j.getChildren(v.id).length===0){let{localTransform:z,worldTransform:C}=v.components;J(z,C),j.markChanged(v.id,"worldTransform")}}function J(j,k){k.x=j.x,k.y=j.y,k.rotation=j.rotation,k.scaleX=j.scaleX,k.scaleY=j.scaleY}function U(j,k,q){let v=k.x*j.scaleX,D=k.y*j.scaleY,z=Math.cos(j.rotation),C=Math.sin(j.rotation),F=v*z-D*C,M=v*C+D*z;q.x=j.x+F,q.y=j.y+M,q.rotation=j.rotation+k.rotation,q.scaleX=j.scaleX*k.scaleX,q.scaleY=j.scaleY*k.scaleY}export{R as createWorldTransform,W as createTransformBundle,h as createTransform,G as createLocalTransform,E as DEFAULT_WORLD_TRANSFORM,B as DEFAULT_LOCAL_TRANSFORM};
|
|
2
|
+
|
|
3
|
+
//# debugId=B32D83741C8A3A2964756E2164756E21
|
|
4
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/transform.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Transform Bundle for ECSpresso\n *\n * Provides hierarchical transform propagation following Bevy's Transform/GlobalTransform pattern.\n * LocalTransform is modified by user code; WorldTransform is computed automatically.\n *\n * @see https://docs.rs/bevy/latest/bevy/transform/components/struct.GlobalTransform.html\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\n\n// ==================== Component Types ====================\n\n/**\n * Local transform relative to parent (or world if no parent).\n * This is the transform you modify directly.\n */\nexport interface LocalTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Computed world transform (accumulated from parent chain).\n * Read-only - managed by the transform propagation system.\n */\nexport interface WorldTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Component types provided by the transform bundle.\n * Included automatically via `.withBundle(createTransformBundle())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTransformBundle())\n * .withComponentTypes<{ sprite: Sprite; velocity: { x: number; y: number } }>()\n * .build();\n * ```\n */\nexport interface TransformComponentTypes {\n\tlocalTransform: LocalTransform;\n\tworldTransform: WorldTransform;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the transform bundle.\n */\nexport interface TransformBundleOptions<G extends string = 'transform'> {\n\t/** System group name (default: 'transform') */\n\tsystemGroup?: G;\n\t/** Priority for transform propagation (default: 500, runs after physics) */\n\tpriority?: number;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values.\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n/**\n * Default world transform values.\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a local transform component with position only.\n * Uses default rotation (0) and scale (1, 1).\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createLocalTransform(100, 200),\n * sprite,\n * });\n * ```\n */\nexport function createLocalTransform(x: number, y: number): Pick<TransformComponentTypes, 'localTransform'> {\n\treturn {\n\t\tlocalTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Create a world transform component with position only.\n * Typically used alongside createLocalTransform for initial state.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createWorldTransform(x: number, y: number): Pick<TransformComponentTypes, 'worldTransform'> {\n\treturn {\n\t\tworldTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Options for creating a full transform.\n */\nexport interface TransformOptions {\n\trotation?: number;\n\tscaleX?: number;\n\tscaleY?: number;\n\t/** Uniform scale (overrides scaleX/scaleY if provided) */\n\tscale?: number;\n}\n\n/**\n * Create both local and world transform components.\n * World transform is initialized to match local transform.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @param options Optional rotation and scale\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * sprite,\n * });\n *\n * // With rotation and scale\n * ecs.spawn({\n * ...createTransform(100, 200, { rotation: Math.PI / 4, scale: 2 }),\n * sprite,\n * });\n * ```\n */\nexport function createTransform(\n\tx: number,\n\ty: number,\n\toptions?: TransformOptions\n): TransformComponentTypes {\n\tconst scaleX = options?.scale ?? options?.scaleX ?? 1;\n\tconst scaleY = options?.scale ?? options?.scaleY ?? 1;\n\tconst rotation = options?.rotation ?? 0;\n\n\tconst transform = {\n\t\tx,\n\t\ty,\n\t\trotation,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n\n\treturn {\n\t\tlocalTransform: { ...transform },\n\t\tworldTransform: { ...transform },\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a transform bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation system that computes world transforms from local transforms\n * - Parent-first traversal ensures parents are processed before children\n * - Supports full transform hierarchy (position, rotation, scale)\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withBundle(createTransformBundle())\n * .withBundle(createPhysics2DBundle())\n * .build();\n *\n * // Spawn entity with transform\n * ecs.spawn({\n * ...createTransform(100, 200),\n * velocity: { x: 50, y: 0 },\n * });\n * ```\n */\nexport function createTransformBundle<G extends string = 'transform'>(\n\toptions?: TransformBundleOptions<G>\n): Bundle<TransformComponentTypes, {}, {}, {}, {}, 'transform-propagation', G> {\n\tconst {\n\t\tsystemGroup = 'transform',\n\t\tpriority = 500,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<TransformComponentTypes, {}, {}>('transform');\n\n\t// localTransform requires worldTransform — initialize from localTransform values\n\tbundle.registerRequired('localTransform', 'worldTransform', (lt) => ({\n\t\tx: lt.x, y: lt.y, rotation: lt.rotation, scaleX: lt.scaleX, scaleY: lt.scaleY,\n\t}));\n\n\tbundle\n\t\t.addSystem('transform-propagation')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\tpropagateTransforms(ecs);\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<TransformComponentTypes, {}, {}, {}, {}, 'transform-propagation', G>;\n}\n\n/**\n * Propagate transforms through the hierarchy.\n * Parent-first traversal ensures parents are computed before children.\n *\n * Runs unconditionally for all entities with transforms — user code can\n * freely mutate localTransform without needing to call markChanged.\n * Marks worldTransform as changed so downstream systems (e.g. renderer\n * sync) pick up the updated values.\n */\nfunction propagateTransforms(ecs: ECSpresso<TransformComponentTypes>): void {\n\tconst em = ecs.entityManager;\n\n\t// Use parent-first traversal for entities in hierarchy\n\tecs.forEachInHierarchy((entityId, parentId) => {\n\t\tconst localTransform = em.getComponent(entityId, 'localTransform');\n\t\tconst worldTransform = em.getComponent(entityId, 'worldTransform');\n\n\t\tif (!localTransform || !worldTransform) return;\n\n\t\tif (parentId === null) {\n\t\t\t// Root entity: world transform equals local transform\n\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t} else {\n\t\t\t// Child entity: combine with parent's world transform\n\t\t\tconst parentWorld = em.getComponent(parentId, 'worldTransform');\n\t\t\tif (parentWorld) {\n\t\t\t\tcombineTransforms(parentWorld, localTransform, worldTransform);\n\t\t\t} else {\n\t\t\t\t// Parent has no world transform, treat as root\n\t\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(entityId, 'worldTransform');\n\t});\n\n\t// Process orphaned entities (not in hierarchy but have transforms)\n\tconst orphanedEntities = ecs.getEntitiesWithQuery(['localTransform', 'worldTransform']);\n\tfor (const entity of orphanedEntities) {\n\t\tconst parentId = ecs.getParent(entity.id);\n\t\t// Only process if truly orphaned (no parent and not a root with children)\n\t\tif (parentId === null && ecs.getChildren(entity.id).length === 0) {\n\t\t\tconst { localTransform, worldTransform } = entity.components;\n\t\t\tcopyTransform(localTransform, worldTransform);\n\t\t\tecs.markChanged(entity.id, 'worldTransform');\n\t\t}\n\t}\n}\n\n/**\n * Copy transform values from source to destination.\n */\nfunction copyTransform(src: LocalTransform, dest: WorldTransform): void {\n\tdest.x = src.x;\n\tdest.y = src.y;\n\tdest.rotation = src.rotation;\n\tdest.scaleX = src.scaleX;\n\tdest.scaleY = src.scaleY;\n}\n\n/**\n * Combine parent world transform with child local transform into child world transform.\n */\nfunction combineTransforms(\n\tparent: WorldTransform,\n\tlocal: LocalTransform,\n\tworld: WorldTransform\n): void {\n\t// Apply parent's scale to local position\n\tconst scaledLocalX = local.x * parent.scaleX;\n\tconst scaledLocalY = local.y * parent.scaleY;\n\n\t// Rotate local position by parent's rotation\n\tconst cos = Math.cos(parent.rotation);\n\tconst sin = Math.sin(parent.rotation);\n\tconst rotatedX = scaledLocalX * cos - scaledLocalY * sin;\n\tconst rotatedY = scaledLocalX * sin + scaledLocalY * cos;\n\n\t// Add to parent's position\n\tworld.x = parent.x + rotatedX;\n\tworld.y = parent.y + rotatedY;\n\tworld.rotation = parent.rotation + local.rotation;\n\tworld.scaleX = parent.scaleX * local.scaleX;\n\tworld.scaleY = parent.scaleY * local.scaleY;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "B32D83741C8A3A2964756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:R,getOwnPropertyNames:_,getOwnPropertyDescriptor:T}=Object,B=Object.prototype.hasOwnProperty;var Q=new WeakMap,m=(z)=>{var H=Q.get(z),M;if(H)return H;if(H=R({},"__esModule",{value:!0}),z&&typeof z==="object"||typeof z==="function")_(z).map((N)=>!B.call(H,N)&&R(H,N,{get:()=>z[N],enumerable:!(M=T(z,N))||M.enumerable}));return Q.set(z,H),H};var d=(z,H)=>{for(var M in H)R(z,M,{get:H[M],enumerable:!0,configurable:!0,set:(N)=>H[M]=()=>N})};var l=(z,H)=>()=>(z&&(H=z(z=0)),H);var u=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(H,M)=>(typeof require<"u"?require:H)[M]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{Bundle as O}from"ecspresso";function j(z){return z}var b=1.70158,w=b*1.525,r=b+1;var i=2*Math.PI/3,p=2*Math.PI/4.5;function f(z,H,M,N,W){let{from:X,easing:Y=j,loop:G="once",loops:P=1,onComplete:k}=W??{};return{tween:{steps:[{targets:[{component:z,path:H.split("."),from:X??null,to:M}],duration:N,easing:Y}],currentStep:0,elapsed:0,loop:G,totalLoops:P,completedLoops:0,direction:1,state:"pending",onComplete:k,justFinished:!1}}}function v(z,H){let{loop:M="once",loops:N=1,onComplete:W}=H??{};return{tween:{steps:z.map((X)=>({targets:X.targets.map((Y)=>({component:Y.component,path:Y.field.split("."),from:Y.from??null,to:Y.to})),duration:X.duration,easing:X.easing??j})),currentStep:0,elapsed:0,loop:M,totalLoops:N,completedLoops:0,direction:1,state:"pending",onComplete:W,justFinished:!1}}}function n(z){return{bundle:y(z),createTween:f,createTweenSequence:v}}var F={parent:{},key:""};function K(z,H){let M=H.length-1,N=z;for(let X=0;X<M;X++){let Y=H[X];if(Y===void 0)return null;let G=N[Y];if(G===null||G===void 0||typeof G!=="object")return null;N=G}let W=H[M];if(W===void 0)return null;if(!(W in N))return null;return F.parent=N,F.key=W,F}function E(z,H){let M=K(z,H);if(!M)return null;let N=M.parent[M.key];return typeof N==="number"?N:null}function x(z,H,M){let N=K(z,H);if(!N)return!1;return N.parent[N.key]=M,!0}function S(z,H,M){return z<H?H:z>M?M:z}function g(z,H){for(let M of z.steps)for(let N of M.targets){if(N.from!==null)continue;let W=H[N.component];if(!W||typeof W!=="object")continue;let X=E(W,N.path);if(X!==null)N.from=X;else N.from=0}}function I(z,H,M,N,W){let X=z.easing(H);for(let Y of z.targets){let G=M[Y.component];if(!G||typeof G!=="object")continue;let P=Y.from??0,k=P+(Y.to-P)*X;if(x(G,Y.path,k))W.markChanged(N,Y.component)}}function h(z,H,M,N){for(let W of z.targets){let X=H[W.component];if(!X||typeof X!=="object")continue;if(x(X,W.path,W.to))N.markChanged(M,W.component)}}function C(z){for(let H of z.steps)for(let M of H.targets){let N=M.from??0;M.from=M.to,M.to=N}}function y(z){let{systemGroup:H="tweens",priority:M=0,phase:N="update"}=z??{},W=new O("tweens");W.addSystem("tween-update").setPriority(M).inPhase(N).inGroup(H).addQuery("tweens",{with:["tween"]}).setProcess((J,$,Z)=>{for(let D of J.tweens){let U=D.components.tween,V=D.components;if(U.justFinished){U.justFinished=!1;continue}if(U.state==="complete")continue;if(U.state==="pending")g(U,V),U.state="active";if(!U.steps[U.currentStep])continue;U.elapsed+=$,X(U,V,D.id,Z)}}).and();function X(J,$,Z,D){while(!0){let U=J.steps[J.currentStep];if(!U)return;if(U.duration<=0){if(h(U,$,Z,D),J.elapsed=0,!Y(J,$,Z,D))return;continue}if(J.elapsed>=U.duration){h(U,$,Z,D);let q=J.elapsed-U.duration;if(J.elapsed=q,!Y(J,$,Z,D))return;continue}let V=S(J.elapsed/U.duration,0,1);I(U,V,$,Z,D);return}}function Y(J,$,Z,D){let U=J.currentStep+1;if(U<J.steps.length){J.currentStep=U;let V=J.steps[U];if(V)for(let q of V.targets){if(q.from!==null)continue;let L=$[q.component];if(!L||typeof L!=="object")continue;let A=E(L,q.path);q.from=A??0}return!0}return G(J,Z,D)}function G(J,$,Z){if(J.completedLoops++,J.loop==="once")return P(J,$,Z),!1;if(J.totalLoops>0&&J.completedLoops>=J.totalLoops)return P(J,$,Z),!1;if(J.loop==="yoyo")J.direction=J.direction===1?-1:1,C(J);return J.currentStep=0,J.elapsed>0}function P(J,$,Z){J.state="complete",J.justFinished=!0,k(Z,$,J),Z.commands.removeComponent($,"tween")}function k(J,$,Z){if(!Z.onComplete)return;let D={entityId:$,stepCount:Z.steps.length};J.eventBus.publish(Z.onComplete,D)}return W}export{v as createTweenSequence,n as createTweenKit,y as createTweenBundle,f as createTween};
|
|
2
|
+
|
|
3
|
+
//# debugId=2CF1E4A2F7E8D74B64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=tween.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/tween.ts", "../src/utils/easing.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Tween Bundle for ECSpresso\n *\n * Declarative property animation within the ECS. Tween any numeric component\n * field over time with standard easing functions, sequences, and completion events.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase, ComponentsOfWorld, EventsOfWorld } from 'ecspresso';\nimport { linear, type EasingFn } from '../utils/easing';\n\n// ==================== Event Types ====================\n\n/**\n * Data structure published when a tween completes.\n * Use this type when defining tween completion events in your EventTypes interface.\n */\nexport interface TweenEventData {\n\t/** The entity ID the tween belongs to */\n\tentityId: number;\n\t/** Number of steps in the tween */\n\tstepCount: number;\n}\n\n/**\n * Extracts event names from EventTypes that have TweenEventData as their payload.\n * This ensures only compatible events can be used with tween.onComplete.\n * Uses `keyof EventTypes & string` to exclude number/symbol keys, ensuring the\n * result is always a string subtype and Tween<E> is assignable to Tween<Record<string, any>>.\n */\nexport type TweenEventName<EventTypes extends Record<string, any>> = {\n\t[K in keyof EventTypes & string]: EventTypes[K] extends TweenEventData ? K : never\n}[keyof EventTypes & string];\n\n// ==================== Component Types ====================\n\nexport interface TweenTarget {\n\t/** Component name on the entity */\n\tcomponent: string;\n\t/** Pre-split field path (e.g., ['position', 'x']) */\n\tpath: readonly string[];\n\t/** Starting value. null = resolve from current value on first tick */\n\tfrom: number | null;\n\t/** Target value */\n\tto: number;\n}\n\nexport interface TweenStep {\n\ttargets: TweenTarget[];\n\tduration: number;\n\teasing: EasingFn;\n}\n\nexport interface Tween<EventTypes extends Record<string, any> = Record<string, any>> {\n\tsteps: TweenStep[];\n\tcurrentStep: number;\n\telapsed: number;\n\tloop: LoopMode;\n\ttotalLoops: number;\n\tcompletedLoops: number;\n\tdirection: 1 | -1;\n\tstate: 'pending' | 'active' | 'complete';\n\tonComplete?: TweenEventName<EventTypes>;\n\tjustFinished: boolean;\n}\n\nexport type LoopMode = 'once' | 'loop' | 'yoyo';\n\n/**\n * Component types provided by the tween bundle.\n */\nexport interface TweenComponentTypes<EventTypes extends Record<string, any> = Record<string, any>> {\n\ttween: Tween<EventTypes>;\n}\n\n// ==================== Bundle Options ====================\n\nexport interface TweenBundleOptions<G extends string = 'tweens'> {\n\t/** System group name (default: 'tweens') */\n\tsystemGroup?: G;\n\t/** Priority for tween update system (default: 0) */\n\tpriority?: number;\n\t/** Execution phase (default: 'update') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\nexport interface TweenOptions<EventTypes extends Record<string, any> = Record<string, any>> {\n\t/** Explicit starting value (default: captures current value on first tick) */\n\tfrom?: number;\n\t/** Easing function (default: linear) */\n\teasing?: EasingFn;\n\t/** Loop mode (default: 'once') */\n\tloop?: LoopMode;\n\t/** Number of loops. -1 = infinite (default: 1) */\n\tloops?: number;\n\t/** Event name to publish when tween completes */\n\tonComplete?: TweenEventName<EventTypes>;\n}\n\n/**\n * Create a single-target tween component.\n *\n * @param component Component name on the entity\n * @param field Field path (dot-separated for nested, e.g. 'position.x')\n * @param to Target value\n * @param duration Duration in seconds\n * @param options Optional configuration\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createTween<EventTypes extends Record<string, any> = Record<string, any>>(\n\tcomponent: string,\n\tfield: string,\n\tto: number,\n\tduration: number,\n\toptions?: TweenOptions<EventTypes>,\n): Pick<TweenComponentTypes<EventTypes>, 'tween'> {\n\tconst {\n\t\tfrom,\n\t\teasing = linear,\n\t\tloop = 'once',\n\t\tloops = 1,\n\t\tonComplete,\n\t} = options ?? {};\n\n\treturn {\n\t\ttween: {\n\t\t\tsteps: [{\n\t\t\t\ttargets: [{\n\t\t\t\t\tcomponent,\n\t\t\t\t\tpath: field.split('.'),\n\t\t\t\t\tfrom: from ?? null,\n\t\t\t\t\tto,\n\t\t\t\t}],\n\t\t\t\tduration,\n\t\t\t\teasing,\n\t\t\t}],\n\t\t\tcurrentStep: 0,\n\t\t\telapsed: 0,\n\t\t\tloop,\n\t\t\ttotalLoops: loops,\n\t\t\tcompletedLoops: 0,\n\t\t\tdirection: 1,\n\t\t\tstate: 'pending',\n\t\t\tonComplete,\n\t\t\tjustFinished: false,\n\t\t},\n\t};\n}\n\nexport interface TweenSequenceStepInput {\n\ttargets: ReadonlyArray<{\n\t\tcomponent: string;\n\t\tfield: string;\n\t\tto: number;\n\t\tfrom?: number;\n\t}>;\n\tduration: number;\n\teasing?: EasingFn;\n}\n\nexport interface TweenSequenceOptions<EventTypes extends Record<string, any> = Record<string, any>> {\n\t/** Loop mode (default: 'once') */\n\tloop?: LoopMode;\n\t/** Number of loops. -1 = infinite (default: 1) */\n\tloops?: number;\n\t/** Event name to publish when tween completes */\n\tonComplete?: TweenEventName<EventTypes>;\n}\n\n/**\n * Create a multi-step tween sequence. Each step can have parallel targets.\n *\n * @param steps Array of step definitions\n * @param options Optional configuration\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createTweenSequence<EventTypes extends Record<string, any> = Record<string, any>>(\n\tsteps: ReadonlyArray<TweenSequenceStepInput>,\n\toptions?: TweenSequenceOptions<EventTypes>,\n): Pick<TweenComponentTypes<EventTypes>, 'tween'> {\n\tconst {\n\t\tloop = 'once',\n\t\tloops = 1,\n\t\tonComplete,\n\t} = options ?? {};\n\n\treturn {\n\t\ttween: {\n\t\t\tsteps: steps.map((step) => ({\n\t\t\t\ttargets: step.targets.map((target) => ({\n\t\t\t\t\tcomponent: target.component,\n\t\t\t\t\tpath: target.field.split('.'),\n\t\t\t\t\tfrom: target.from ?? null,\n\t\t\t\t\tto: target.to,\n\t\t\t\t})),\n\t\t\t\tduration: step.duration,\n\t\t\t\teasing: step.easing ?? linear,\n\t\t\t})),\n\t\t\tcurrentStep: 0,\n\t\t\telapsed: 0,\n\t\t\tloop,\n\t\t\ttotalLoops: loops,\n\t\t\tcompletedLoops: 0,\n\t\t\tdirection: 1,\n\t\t\tstate: 'pending',\n\t\t\tonComplete,\n\t\t\tjustFinished: false,\n\t\t},\n\t};\n}\n\n// ==================== Kit Types ====================\n\ntype AnyECSpresso = import('ecspresso').default<any, any, any, any, any, any, any>;\n\n/**\n * Recursively produce a union of dot-separated paths that resolve to `number`\n * within type T. Depth-limited to 4 levels to prevent TS recursion errors.\n *\n * @example\n * NumericPaths<{ x: number; y: number }> // 'x' | 'y'\n * NumericPaths<{ position: { x: number }; rotation: number }> // 'position.x' | 'rotation'\n */\nexport type NumericPaths<T, Depth extends readonly unknown[] = []> =\n\tDepth['length'] extends 4 ? never :\n\tT extends readonly unknown[] ? never :\n\tT extends Record<string, unknown>\n\t\t? { [K in keyof T & string]:\n\t\t\tNonNullable<T[K]> extends number\n\t\t\t\t? K\n\t\t\t\t: NonNullable<T[K]> extends readonly unknown[]\n\t\t\t\t\t? never\n\t\t\t\t\t: NonNullable<T[K]> extends Record<string, unknown>\n\t\t\t\t\t\t? `${K}.${NumericPaths<NonNullable<T[K]>, [...Depth, unknown]>}`\n\t\t\t\t\t\t: never\n\t\t}[keyof T & string]\n\t\t: never;\n\n/**\n * Discriminated union over component names: each variant constrains `field`\n * to the numeric paths of that component. TS narrows inline object literals\n * by `component` discriminant — zero runtime overhead.\n */\nexport type TypedTweenTargetInput<C extends Record<string, any>> = {\n\t[K in keyof C & string]: {\n\t\tcomponent: K;\n\t\tfield: NumericPaths<C[K]>;\n\t\tto: number;\n\t\tfrom?: number;\n\t}\n}[keyof C & string];\n\nexport interface TypedTweenSequenceStepInput<C extends Record<string, any>> {\n\ttargets: ReadonlyArray<TypedTweenTargetInput<C>>;\n\tduration: number;\n\teasing?: EasingFn;\n}\n\nexport interface TweenKit<W extends AnyECSpresso, G extends string = 'tweens'> {\n\tbundle: Bundle<TweenComponentTypes<EventsOfWorld<W>>, EventsOfWorld<W>, {}, {}, {}, 'tween-update', G>;\n\tcreateTween: <K extends keyof ComponentsOfWorld<W> & string>(\n\t\tcomponent: K,\n\t\tfield: NumericPaths<ComponentsOfWorld<W>[K]>,\n\t\tto: number,\n\t\tduration: number,\n\t\toptions?: TweenOptions<EventsOfWorld<W>>,\n\t) => Pick<TweenComponentTypes<EventsOfWorld<W>>, 'tween'>;\n\tcreateTweenSequence: (\n\t\tsteps: ReadonlyArray<TypedTweenSequenceStepInput<ComponentsOfWorld<W>>>,\n\t\toptions?: TweenSequenceOptions<EventsOfWorld<W>>,\n\t) => Pick<TweenComponentTypes<EventsOfWorld<W>>, 'tween'>;\n}\n\n/**\n * Create a typed tween kit that captures the world type W.\n *\n * The returned `createTween` and `createTweenSequence` validate component names\n * and field paths at compile time. Runtime behavior is identical to the standalone\n * functions — all validation is type-level only.\n *\n * @template W - Concrete ECS world type (e.g. `typeof ecs`)\n * @template G - System group name (default: 'tweens')\n * @param options - Optional bundle configuration (same as createTweenBundle)\n * @returns A kit object with bundle, createTween, createTweenSequence\n *\n * @example\n * ```typescript\n * const kit = createTweenKit<typeof ecs>();\n * // or: const kit = createTweenKit<ECS>();\n *\n * const ecs = ECSpresso.create()\n * .withBundle(kit.bundle)\n * .build();\n *\n * // Type-safe: 'position' must be a component, 'x' must be a numeric field\n * kit.createTween('position', 'x', 100, 1);\n *\n * // Type error: 'z' is not a field of position\n * kit.createTween('position', 'z', 100, 1);\n * ```\n */\nexport function createTweenKit<W extends AnyECSpresso, G extends string = 'tweens'>(\n\toptions?: TweenBundleOptions<G>,\n): TweenKit<W, G> {\n\treturn {\n\t\tbundle: createTweenBundle<EventsOfWorld<W>, G>(options),\n\t\tcreateTween: createTween as TweenKit<W, G>['createTween'],\n\t\tcreateTweenSequence: createTweenSequence as TweenKit<W, G>['createTweenSequence'],\n\t};\n}\n\n// ==================== Field Path Resolution ====================\n\n/**\n * Module-scoped mutable result to avoid per-call allocation in hot path.\n */\nconst _fieldRef: { parent: Record<string, unknown>; key: string } = { parent: {} as Record<string, unknown>, key: '' };\n\n/**\n * Traverse an object by path segments. Returns the parent object and final key\n * for read/write, or null if any segment is missing.\n */\nfunction resolveField(obj: Record<string, unknown>, path: readonly string[]): typeof _fieldRef | null {\n\tconst lastIdx = path.length - 1;\n\tlet current: Record<string, unknown> = obj;\n\n\tfor (let i = 0; i < lastIdx; i++) {\n\t\tconst segment = path[i];\n\t\tif (segment === undefined) return null;\n\t\tconst next = current[segment];\n\t\tif (next === null || next === undefined || typeof next !== 'object') return null;\n\t\tcurrent = next as Record<string, unknown>;\n\t}\n\n\tconst finalKey = path[lastIdx];\n\tif (finalKey === undefined) return null;\n\tif (!(finalKey in current)) return null;\n\n\t_fieldRef.parent = current;\n\t_fieldRef.key = finalKey;\n\treturn _fieldRef;\n}\n\nfunction readField(obj: Record<string, unknown>, path: readonly string[]): number | null {\n\tconst ref = resolveField(obj, path);\n\tif (!ref) return null;\n\tconst val = ref.parent[ref.key];\n\treturn typeof val === 'number' ? val : null;\n}\n\nfunction writeField(obj: Record<string, unknown>, path: readonly string[], value: number): boolean {\n\tconst ref = resolveField(obj, path);\n\tif (!ref) return false;\n\tref.parent[ref.key] = value;\n\treturn true;\n}\n\n// ==================== System Logic ====================\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn value < min ? min : value > max ? max : value;\n}\n\n/**\n * Resolve all null `from` values by reading current component field values.\n */\nfunction resolveFromValues<E extends Record<string, any>>(\n\ttween: Tween<E>,\n\tentityComponents: Record<string, unknown>,\n): void {\n\tfor (const step of tween.steps) {\n\t\tfor (const target of step.targets) {\n\t\t\tif (target.from !== null) continue;\n\t\t\tconst comp = entityComponents[target.component];\n\t\t\tif (!comp || typeof comp !== 'object') continue;\n\t\t\tconst val = readField(comp as Record<string, unknown>, target.path);\n\t\t\tif (val !== null) {\n\t\t\t\ttarget.from = val;\n\t\t\t} else {\n\t\t\t\ttarget.from = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Apply interpolation for a step's targets at a given progress.\n */\nfunction applyStep(\n\tstep: TweenStep,\n\tprogress: number,\n\tentityComponents: Record<string, unknown>,\n\tentityId: number,\n\tecs: { markChanged: (entityId: number, componentName: any) => void },\n): void {\n\tconst easedT = step.easing(progress);\n\n\tfor (const target of step.targets) {\n\t\tconst comp = entityComponents[target.component];\n\t\tif (!comp || typeof comp !== 'object') continue;\n\t\tconst from = target.from ?? 0;\n\t\tconst value = from + (target.to - from) * easedT;\n\t\tconst written = writeField(comp as Record<string, unknown>, target.path, value);\n\t\tif (written) {\n\t\t\tecs.markChanged(entityId, target.component);\n\t\t}\n\t}\n}\n\n/**\n * Snap all targets in a step to their final values (from or to depending on direction).\n */\nfunction snapStepToEnd(\n\tstep: TweenStep,\n\tentityComponents: Record<string, unknown>,\n\tentityId: number,\n\tecs: { markChanged: (entityId: number, componentName: any) => void },\n): void {\n\tfor (const target of step.targets) {\n\t\tconst comp = entityComponents[target.component];\n\t\tif (!comp || typeof comp !== 'object') continue;\n\t\tconst written = writeField(comp as Record<string, unknown>, target.path, target.to);\n\t\tif (written) {\n\t\t\tecs.markChanged(entityId, target.component);\n\t\t}\n\t}\n}\n\n/**\n * Reverse all from/to values in every step (for yoyo).\n */\nfunction reverseAllTargets<E extends Record<string, any>>(tween: Tween<E>): void {\n\tfor (const step of tween.steps) {\n\t\tfor (const target of step.targets) {\n\t\t\tconst tmp = target.from ?? 0;\n\t\t\ttarget.from = target.to;\n\t\t\ttarget.to = tmp;\n\t\t}\n\t}\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a tween bundle for ECSpresso.\n *\n * This bundle provides:\n * - Tween system that processes all tween components each frame\n * - Support for single-field, multi-target, and multi-step sequences\n * - 31 standard easing functions\n * - Loop modes: once, loop, yoyo\n * - `justFinished` flag for one-frame completion detection\n * - `onComplete` event publishing\n * - Change detection via markChanged\n */\nexport function createTweenBundle<EventTypes extends Record<string, any> = Record<string, any>, G extends string = 'tweens'>(\n\toptions?: TweenBundleOptions<G>\n): Bundle<TweenComponentTypes<EventTypes>, EventTypes, {}, {}, {}, 'tween-update', G> {\n\tconst {\n\t\tsystemGroup = 'tweens',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<TweenComponentTypes<EventTypes>, EventTypes, {}>('tweens');\n\n\tbundle\n\t\t.addSystem('tween-update')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('tweens', {\n\t\t\twith: ['tween'],\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tfor (const entity of queries.tweens) {\n\t\t\t\tconst tween = entity.components.tween as Tween<EventTypes>;\n\t\t\t\tconst entityComponents = entity.components as Record<string, unknown>;\n\n\t\t\t\t// Reset justFinished flag from previous frame\n\t\t\t\tif (tween.justFinished) {\n\t\t\t\t\ttween.justFinished = false;\n\t\t\t\t\t// Component removal was queued, skip processing\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip completed tweens\n\t\t\t\tif (tween.state === 'complete') continue;\n\n\t\t\t\t// Resolve pending state: capture null from values\n\t\t\t\tif (tween.state === 'pending') {\n\t\t\t\t\tresolveFromValues(tween, entityComponents);\n\t\t\t\t\ttween.state = 'active';\n\t\t\t\t}\n\n\t\t\t\t// Process active tween\n\t\t\t\tconst currentStep = tween.steps[tween.currentStep];\n\t\t\t\tif (!currentStep) continue;\n\n\t\t\t\ttween.elapsed += deltaTime;\n\n\t\t\t\t// Process steps, handling overflow across multiple steps\n\t\t\t\tprocessTweenProgress(tween, entityComponents, entity.id, ecs);\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\tfunction processTweenProgress(\n\t\ttween: Tween<EventTypes>,\n\t\tentityComponents: Record<string, unknown>,\n\t\tentityId: number,\n\t\tecs: { markChanged: (entityId: number, componentName: any) => void; eventBus: { publish: (event: any, data: any) => void }; commands: { removeComponent: (entityId: number, componentName: any) => void } },\n\t): void {\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile (true) {\n\t\t\tconst currentStep = tween.steps[tween.currentStep];\n\t\t\tif (!currentStep) return;\n\n\t\t\t// Zero-duration steps complete immediately\n\t\t\tif (currentStep.duration <= 0) {\n\t\t\t\tsnapStepToEnd(currentStep, entityComponents, entityId, ecs);\n\t\t\t\ttween.elapsed = 0;\n\n\t\t\t\tif (!advanceStep(tween, entityComponents, entityId, ecs)) return;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (tween.elapsed >= currentStep.duration) {\n\t\t\t\t// Step complete — snap to end and carry overflow\n\t\t\t\tsnapStepToEnd(currentStep, entityComponents, entityId, ecs);\n\t\t\t\tconst overflow = tween.elapsed - currentStep.duration;\n\t\t\t\ttween.elapsed = overflow;\n\n\t\t\t\tif (!advanceStep(tween, entityComponents, entityId, ecs)) return;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Step in progress — interpolate\n\t\t\tconst progress = clamp(tween.elapsed / currentStep.duration, 0, 1);\n\t\t\tapplyStep(currentStep, progress, entityComponents, entityId, ecs);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Advance to next step. Returns true if there's more work to process,\n\t * false if the tween has completed or looped.\n\t */\n\tfunction advanceStep(\n\t\ttween: Tween<EventTypes>,\n\t\tentityComponents: Record<string, unknown>,\n\t\tentityId: number,\n\t\tecs: { markChanged: (entityId: number, componentName: any) => void; eventBus: { publish: (event: any, data: any) => void }; commands: { removeComponent: (entityId: number, componentName: any) => void } },\n\t): boolean {\n\t\tconst nextStep = tween.currentStep + 1;\n\n\t\tif (nextStep < tween.steps.length) {\n\t\t\t// More steps — resolve from values for next step and continue\n\t\t\ttween.currentStep = nextStep;\n\t\t\tconst step = tween.steps[nextStep];\n\t\t\tif (step) {\n\t\t\t\tfor (const target of step.targets) {\n\t\t\t\t\tif (target.from !== null) continue;\n\t\t\t\t\tconst comp = entityComponents[target.component];\n\t\t\t\t\tif (!comp || typeof comp !== 'object') continue;\n\t\t\t\t\tconst val = readField(comp as Record<string, unknown>, target.path);\n\t\t\t\t\ttarget.from = val ?? 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\t// All steps done — handle loop/complete\n\t\treturn handleTweenEnd(tween, entityId, ecs);\n\t}\n\n\tfunction handleTweenEnd(\n\t\ttween: Tween<EventTypes>,\n\t\tentityId: number,\n\t\tecs: { markChanged: (entityId: number, componentName: any) => void; eventBus: { publish: (event: any, data: any) => void }; commands: { removeComponent: (entityId: number, componentName: any) => void } },\n\t): boolean {\n\t\ttween.completedLoops++;\n\n\t\tif (tween.loop === 'once') {\n\t\t\tcompleteTween(tween, entityId, ecs);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check if finite loops exhausted\n\t\tif (tween.totalLoops > 0 && tween.completedLoops >= tween.totalLoops) {\n\t\t\tcompleteTween(tween, entityId, ecs);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Loop continues\n\t\tif (tween.loop === 'yoyo') {\n\t\t\ttween.direction = tween.direction === 1 ? -1 : 1;\n\t\t\treverseAllTargets(tween);\n\t\t}\n\n\t\ttween.currentStep = 0;\n\n\t\t// For 'loop' mode, from values stay as-is so the animation replays identically.\n\t\t// For 'yoyo' mode, reverseAllTargets already swapped from/to.\n\n\t\treturn tween.elapsed > 0;\n\t}\n\n\tfunction completeTween(\n\t\ttween: Tween<EventTypes>,\n\t\tentityId: number,\n\t\tecs: { eventBus: { publish: (event: any, data: any) => void }; commands: { removeComponent: (entityId: number, componentName: any) => void } },\n\t): void {\n\t\ttween.state = 'complete';\n\t\ttween.justFinished = true;\n\n\t\tpublishTweenEvent(ecs, entityId, tween);\n\t\tecs.commands.removeComponent(entityId, 'tween');\n\t}\n\n\tfunction publishTweenEvent(\n\t\tecs: { eventBus: { publish: (event: any, data: any) => void } },\n\t\tentityId: number,\n\t\ttween: Tween<EventTypes>,\n\t): void {\n\t\tif (!tween.onComplete) return;\n\t\tconst eventData: TweenEventData = {\n\t\t\tentityId,\n\t\t\tstepCount: tween.steps.length,\n\t\t};\n\t\tecs.eventBus.publish(tween.onComplete, eventData);\n\t}\n\n\treturn bundle as Bundle<TweenComponentTypes<EventTypes>, EventTypes, {}, {}, {}, 'tween-update', G>;\n}\n",
|
|
6
|
+
"/**\n * Easing Functions\n *\n * 31 standard easing functions for animation. Pure math, no dependencies.\n */\n\nexport type EasingFn = (t: number) => number;\n\nexport function linear(t: number): number {\n\treturn t;\n}\n\n// Quad\nexport function easeInQuad(t: number): number {\n\treturn t * t;\n}\n\nexport function easeOutQuad(t: number): number {\n\treturn t * (2 - t);\n}\n\nexport function easeInOutQuad(t: number): number {\n\treturn t < 0.5\n\t\t? 2 * t * t\n\t\t: -1 + (4 - 2 * t) * t;\n}\n\n// Cubic\nexport function easeInCubic(t: number): number {\n\treturn t * t * t;\n}\n\nexport function easeOutCubic(t: number): number {\n\tconst t1 = t - 1;\n\treturn t1 * t1 * t1 + 1;\n}\n\nexport function easeInOutCubic(t: number): number {\n\treturn t < 0.5\n\t\t? 4 * t * t * t\n\t\t: 1 + (t - 1) * (2 * t - 2) * (2 * t - 2);\n}\n\n// Quart\nexport function easeInQuart(t: number): number {\n\treturn t * t * t * t;\n}\n\nexport function easeOutQuart(t: number): number {\n\tconst t1 = t - 1;\n\treturn 1 - t1 * t1 * t1 * t1;\n}\n\nexport function easeInOutQuart(t: number): number {\n\treturn t < 0.5\n\t\t? 8 * t * t * t * t\n\t\t: 1 - 8 * (t - 1) * (t - 1) * (t - 1) * (t - 1);\n}\n\n// Quint\nexport function easeInQuint(t: number): number {\n\treturn t * t * t * t * t;\n}\n\nexport function easeOutQuint(t: number): number {\n\tconst t1 = t - 1;\n\treturn 1 + t1 * t1 * t1 * t1 * t1;\n}\n\nexport function easeInOutQuint(t: number): number {\n\treturn t < 0.5\n\t\t? 16 * t * t * t * t * t\n\t\t: 1 + 16 * (t - 1) * (t - 1) * (t - 1) * (t - 1) * (t - 1);\n}\n\n// Sine\nexport function easeInSine(t: number): number {\n\treturn 1 - Math.cos((t * Math.PI) / 2);\n}\n\nexport function easeOutSine(t: number): number {\n\treturn Math.sin((t * Math.PI) / 2);\n}\n\nexport function easeInOutSine(t: number): number {\n\treturn -(Math.cos(Math.PI * t) - 1) / 2;\n}\n\n// Expo\nexport function easeInExpo(t: number): number {\n\treturn t === 0 ? 0 : Math.pow(2, 10 * (t - 1));\n}\n\nexport function easeOutExpo(t: number): number {\n\treturn t === 1 ? 1 : 1 - Math.pow(2, -10 * t);\n}\n\nexport function easeInOutExpo(t: number): number {\n\tif (t === 0) return 0;\n\tif (t === 1) return 1;\n\treturn t < 0.5\n\t\t? Math.pow(2, 20 * t - 10) / 2\n\t\t: (2 - Math.pow(2, -20 * t + 10)) / 2;\n}\n\n// Circ\nexport function easeInCirc(t: number): number {\n\treturn 1 - Math.sqrt(1 - t * t);\n}\n\nexport function easeOutCirc(t: number): number {\n\tconst t1 = t - 1;\n\treturn Math.sqrt(1 - t1 * t1);\n}\n\nexport function easeInOutCirc(t: number): number {\n\treturn t < 0.5\n\t\t? (1 - Math.sqrt(1 - 4 * t * t)) / 2\n\t\t: (Math.sqrt(1 - (-2 * t + 2) * (-2 * t + 2)) + 1) / 2;\n}\n\n// Back\nconst BACK_C1 = 1.70158;\nconst BACK_C2 = BACK_C1 * 1.525;\nconst BACK_C3 = BACK_C1 + 1;\n\nexport function easeInBack(t: number): number {\n\treturn BACK_C3 * t * t * t - BACK_C1 * t * t;\n}\n\nexport function easeOutBack(t: number): number {\n\tconst t1 = t - 1;\n\treturn 1 + BACK_C3 * t1 * t1 * t1 + BACK_C1 * t1 * t1;\n}\n\nexport function easeInOutBack(t: number): number {\n\treturn t < 0.5\n\t\t? ((2 * t) * (2 * t) * ((BACK_C2 + 1) * 2 * t - BACK_C2)) / 2\n\t\t: ((2 * t - 2) * (2 * t - 2) * ((BACK_C2 + 1) * (t * 2 - 2) + BACK_C2) + 2) / 2;\n}\n\n// Elastic\nconst ELASTIC_C4 = (2 * Math.PI) / 3;\nconst ELASTIC_C5 = (2 * Math.PI) / 4.5;\n\nexport function easeInElastic(t: number): number {\n\tif (t === 0) return 0;\n\tif (t === 1) return 1;\n\treturn -Math.pow(2, 10 * t - 10) * Math.sin((10 * t - 10.75) * ELASTIC_C4);\n}\n\nexport function easeOutElastic(t: number): number {\n\tif (t === 0) return 0;\n\tif (t === 1) return 1;\n\treturn Math.pow(2, -10 * t) * Math.sin((10 * t - 0.75) * ELASTIC_C4) + 1;\n}\n\nexport function easeInOutElastic(t: number): number {\n\tif (t === 0) return 0;\n\tif (t === 1) return 1;\n\treturn t < 0.5\n\t\t? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * ELASTIC_C5)) / 2\n\t\t: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * ELASTIC_C5)) / 2 + 1;\n}\n\n// Bounce\nexport function easeOutBounce(t: number): number {\n\tconst n1 = 7.5625;\n\tconst d1 = 2.75;\n\n\tif (t < 1 / d1) {\n\t\treturn n1 * t * t;\n\t} else if (t < 2 / d1) {\n\t\tconst t1 = t - 1.5 / d1;\n\t\treturn n1 * t1 * t1 + 0.75;\n\t} else if (t < 2.5 / d1) {\n\t\tconst t1 = t - 2.25 / d1;\n\t\treturn n1 * t1 * t1 + 0.9375;\n\t} else {\n\t\tconst t1 = t - 2.625 / d1;\n\t\treturn n1 * t1 * t1 + 0.984375;\n\t}\n}\n\nexport function easeInBounce(t: number): number {\n\treturn 1 - easeOutBounce(1 - t);\n}\n\nexport function easeInOutBounce(t: number): number {\n\treturn t < 0.5\n\t\t? (1 - easeOutBounce(1 - 2 * t)) / 2\n\t\t: (1 + easeOutBounce(2 * t - 1)) / 2;\n}\n\n/** Runtime lookup of all easing functions by name */\nexport const easings = {\n\tlinear,\n\teaseInQuad,\n\teaseOutQuad,\n\teaseInOutQuad,\n\teaseInCubic,\n\teaseOutCubic,\n\teaseInOutCubic,\n\teaseInQuart,\n\teaseOutQuart,\n\teaseInOutQuart,\n\teaseInQuint,\n\teaseOutQuint,\n\teaseInOutQuint,\n\teaseInSine,\n\teaseOutSine,\n\teaseInOutSine,\n\teaseInExpo,\n\teaseOutExpo,\n\teaseInOutExpo,\n\teaseInCirc,\n\teaseOutCirc,\n\teaseInOutCirc,\n\teaseInBack,\n\teaseOutBack,\n\teaseInOutBack,\n\teaseInElastic,\n\teaseOutElastic,\n\teaseInOutElastic,\n\teaseInBounce,\n\teaseOutBounce,\n\teaseInOutBounce,\n} as const;\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "2CF1E4A2F7E8D74B64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:q,getOwnPropertyNames:i,getOwnPropertyDescriptor:a}=Object,r=Object.prototype.hasOwnProperty;var k=new WeakMap,n=(j)=>{var $=k.get(j),J;if($)return $;if($=q({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")i(j).map((U)=>!r.call($,U)&&q($,U,{get:()=>j[U],enumerable:!(J=a(j,U))||J.enumerable}));return k.set(j,$),$};var t=(j,$)=>{for(var J in $)q(j,J,{get:$[J],enumerable:!0,configurable:!0,set:(U)=>$[J]=()=>U})};var E=(j,$)=>()=>(j&&($=j(j=0)),$);var Uj=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:($,J)=>(typeof require<"u"?require:$)[J]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});class H{parentMap=new Map;childrenMap=new Map;setParent(j,$){if(j===$)throw Error(`Cannot set entity ${j} as its own parent`);if(this.wouldCreateCycle(j,$))throw Error("Cannot set parent: would create circular reference");let J=this.parentMap.get(j);if(J!==void 0){let X=this.childrenMap.get(J);if(X){let Y=X.indexOf(j);if(Y!==-1)X.splice(Y,1)}}this.parentMap.set(j,$);let U=this.childrenMap.get($);if(U)U.push(j);else this.childrenMap.set($,[j]);return this}removeParent(j){let $=this.parentMap.get(j);if($===void 0)return!1;let J=this.childrenMap.get($);if(J){let U=J.indexOf(j);if(U!==-1)J.splice(U,1)}return this.parentMap.delete(j),!0}getParent(j){return this.parentMap.get(j)??null}getChildren(j){let $=this.childrenMap.get(j);return $?[...$]:[]}getChildAt(j,$){if($<0)return null;let J=this.childrenMap.get(j);if(!J||$>=J.length)return null;return J[$]??null}getChildIndex(j,$){let J=this.childrenMap.get(j);if(!J)return-1;return J.indexOf($)}removeEntity(j){let $=this.parentMap.get(j)??null;if($!==null){let X=this.childrenMap.get($);if(X){let Y=X.indexOf(j);if(Y!==-1)X.splice(Y,1)}}this.parentMap.delete(j);let J=this.childrenMap.get(j)??[],U=[...J];for(let X of J)this.parentMap.delete(X);return this.childrenMap.delete(j),{oldParent:$,orphanedChildren:U}}getAncestors(j){let $=[],J=this.parentMap.get(j);while(J!==void 0)$.push(J),J=this.parentMap.get(J);return $}getDescendants(j){let $=[],J=this.childrenMap.get(j);if(!J)return $;let U=J.slice().reverse();while(U.length>0){let X=U.pop();$.push(X);let Y=this.childrenMap.get(X);if(Y)for(let Z=Y.length-1;Z>=0;Z--)U.push(Y[Z])}return $}getRoot(j){let $=j,J=this.parentMap.get($);while(J!==void 0)$=J,J=this.parentMap.get($);return $}getSiblings(j){let $=this.parentMap.get(j);if($===void 0)return[];let J=this.childrenMap.get($);if(!J)return[];return J.filter((U)=>U!==j)}isDescendantOf(j,$){if(j===$)return!1;let J=this.parentMap.get(j);while(J!==void 0){if(J===$)return!0;J=this.parentMap.get(J)}return!1}isAncestorOf(j,$){return this.isDescendantOf($,j)}getRootEntities(){let j=[];for(let $ of this.childrenMap.keys())if(!this.parentMap.has($))j.push($);return j}wouldCreateCycle(j,$){let J=$;while(J!==void 0){if(J===j)return!0;J=this.parentMap.get(J)}return!1}forEachInHierarchy(j,$){let J=$?.roots??this.getRootEntities(),U=[];for(let X of J)U.push({entityId:X,parentId:null,depth:0});while(U.length>0){let X=U.shift();if(!X)break;j(X.entityId,X.parentId,X.depth);let Y=this.childrenMap.get(X.entityId);if(Y)for(let Z of Y)U.push({entityId:Z,parentId:X.entityId,depth:X.depth+1})}}*hierarchyIterator(j){let $=j?.roots??this.getRootEntities(),J=[];for(let U of $)J.push({entityId:U,parentId:null,depth:0});while(J.length>0){let U=J.shift();if(!U)break;yield U;let X=this.childrenMap.get(U.entityId);if(X)for(let Y of X)J.push({entityId:Y,parentId:U.entityId,depth:U.depth+1})}}}class P{nextId=1;entities=new Map;componentIndices=new Map;addedCallbacks=new Map;removedCallbacks=new Map;hierarchyManager=new H;disposeCallbacks=new Map;changeSeqs=new Map;_changeSeq=0;_afterComponentAddedHooks=[];_afterEntityMutatedHooks=[];_afterComponentRemovedHooks=[];_beforeEntityRemovedHooks=[];_afterParentChangedHooks=[];_batchingDepth=0;_batchedEntityIds=new Set;_pendingBatchKeys=null;get entityCount(){return this.entities.size}createEntity(){let j=this.nextId++,$={id:j,components:{}};return this.entities.set(j,$),$}registerDispose(j,$){this.disposeCallbacks.set(j,$)}getDisposeCallbacks(){return this.disposeCallbacks}invokeDispose(j,$){let J=this.disposeCallbacks.get(j);if(!J)return;try{J($)}catch(U){console.warn(`Component dispose callback for '${String(j)}' threw:`,U)}}resolveEntity(j){return typeof j==="number"?this.entities.get(j):j}resolveEntityId(j){return typeof j==="number"?j:j.id}addComponent(j,$,J){let U=this.resolveEntity(j);if(!U)throw Error(`Cannot add component '${String($)}': Entity with ID ${this.resolveEntityId(j)} does not exist`);let X=U.components[$];if(X!==void 0)this.invokeDispose($,X);if(U.components[$]=J,!this.componentIndices.has($))this.componentIndices.set($,new Set);this.componentIndices.get($)?.add(U.id);let Y=this.addedCallbacks.get($);if(Y)for(let Z of[...Y])Z(J,U);this._batchingDepth++;for(let Z of this._afterComponentAddedHooks)Z(U.id,$);if(this._batchedEntityIds.add(U.id),this._batchingDepth--,this._batchingDepth===0){for(let Z of this._batchedEntityIds)for(let D of this._afterEntityMutatedHooks)D(Z);this._batchedEntityIds.clear()}return this}addComponents(j,$){let J=this.resolveEntity(j);if(!J)throw Error(`Cannot add components: Entity with ID ${this.resolveEntityId(j)} does not exist`);let U=this._pendingBatchKeys;this._pendingBatchKeys=new Set(Object.keys($)),this._batchingDepth++;for(let X in $)this.addComponent(J,X,$[X]);if(this._batchingDepth--,this._pendingBatchKeys=U,this._batchingDepth===0){for(let X of this._batchedEntityIds)for(let Y of this._afterEntityMutatedHooks)Y(X);this._batchedEntityIds.clear()}return this}removeComponent(j,$){let J=this.resolveEntity(j);if(!J)throw Error(`Cannot remove component '${String($)}': Entity with ID ${this.resolveEntityId(j)} does not exist`);let U=J.components[$];if(U!==void 0)this.invokeDispose($,U);delete J.components[$];let X=this.removedCallbacks.get($);if(X&&U!==void 0)for(let Y of[...X])Y(U,J);if(this.componentIndices.get($)?.delete(J.id),U!==void 0)for(let Y of this._afterComponentRemovedHooks)Y(J.id,$);return this}getComponent(j,$){let J=this.entities.get(j);if(!J)throw Error(`Cannot get component '${String($)}': Entity with ID ${j} does not exist`);return J.components[$]}getEntitiesWithQuery(j=[],$=[],J,U,X){let Y=J!==void 0&&J.length>0&&U!==void 0,Z=X!==void 0&&X.length>0;if(j.length===0){if($.length===0&&!Y&&!Z)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((L)=>{if($.length>0&&!$.every((F)=>!(F in L.components)))return!1;if(Y){let F=this.changeSeqs.get(L.id);if(!F)return!1;if(!J.some((Q)=>(F.get(Q)??-1)>U))return!1}if(Z&&!this.parentHasComponents(L.id,X))return!1;return!0})}let D=j.reduce((L,F)=>{let Q=this.componentIndices.get(F)?.size??0,C=this.componentIndices.get(L)?.size??1/0;return Q<C?F:L},j[0]),G=this.componentIndices.get(D);if(!G||G.size===0)return[];let _=[],z=$.length>0;for(let L of G){let F=this.entities.get(L);if(F&&j.every((Q)=>(Q in F.components))&&(!z||$.every((Q)=>!(Q in F.components)))){if(Y){let Q=this.changeSeqs.get(L);if(!Q||!J.some((C)=>(Q.get(C)??-1)>U))continue}if(Z&&!this.parentHasComponents(L,X))continue;_.push(F)}}return _}parentHasComponents(j,$){let J=this.hierarchyManager.getParent(j);if(J===null)return!1;let U=this.entities.get(J);if(!U)return!1;for(let X of $)if(!(X in U.components))return!1;return!0}removeEntity(j,$){let J=this.resolveEntity(j);if(!J)return!1;if($?.cascade??!0){let X=this.hierarchyManager.getDescendants(J.id);for(let Y=X.length-1;Y>=0;Y--)for(let Z of this._beforeEntityRemovedHooks)Z(X[Y]);for(let Y of this._beforeEntityRemovedHooks)Y(J.id);for(let Y=X.length-1;Y>=0;Y--)this.removeEntityInternal(X[Y])}else for(let X of this._beforeEntityRemovedHooks)X(J.id);return this.removeEntityInternal(J.id)}removeEntityInternal(j){let $=this.entities.get(j);if(!$)return!1;this.hierarchyManager.removeEntity(j);for(let J of Object.keys($.components)){let U=$.components[J];if(U!==void 0){this.invokeDispose(J,U);let X=this.removedCallbacks.get(J);if(X)for(let Y of[...X])Y(U,$)}this.componentIndices.get(J)?.delete($.id)}return this.changeSeqs.delete($.id),this.entities.delete($.id)}getEntity(j){return this.entities.get(j)}onComponentAdded(j,$){if(!this.addedCallbacks.has(j))this.addedCallbacks.set(j,new Set);return this.addedCallbacks.get(j).add($),()=>{this.addedCallbacks.get(j)?.delete($)}}onComponentRemoved(j,$){if(!this.removedCallbacks.has(j))this.removedCallbacks.set(j,new Set);return this.removedCallbacks.get(j).add($),()=>{this.removedCallbacks.get(j)?.delete($)}}onAfterComponentAdded(j){return this._afterComponentAddedHooks.push(j),()=>{let $=this._afterComponentAddedHooks.indexOf(j);if($!==-1)this._afterComponentAddedHooks.splice($,1)}}onAfterEntityMutated(j){return this._afterEntityMutatedHooks.push(j),()=>{let $=this._afterEntityMutatedHooks.indexOf(j);if($!==-1)this._afterEntityMutatedHooks.splice($,1)}}onAfterComponentRemoved(j){return this._afterComponentRemovedHooks.push(j),()=>{let $=this._afterComponentRemovedHooks.indexOf(j);if($!==-1)this._afterComponentRemovedHooks.splice($,1)}}onBeforeEntityRemoved(j){return this._beforeEntityRemovedHooks.push(j),()=>{let $=this._beforeEntityRemovedHooks.indexOf(j);if($!==-1)this._beforeEntityRemovedHooks.splice($,1)}}onAfterParentChanged(j){return this._afterParentChangedHooks.push(j),()=>{let $=this._afterParentChangedHooks.indexOf(j);if($!==-1)this._afterParentChangedHooks.splice($,1)}}get changeSeq(){return this._changeSeq}markChanged(j,$){let J=this.resolveEntityId(j),U=++this._changeSeq,X=this.changeSeqs.get(J);if(!X)X=new Map,this.changeSeqs.set(J,X);X.set($,U)}getChangeSeq(j,$){return this.changeSeqs.get(j)?.get($)??-1}spawnChild(j,$){let J=this.createEntity();return this.addComponents(J,$),this.setParent(J.id,this.resolveEntityId(j)),J}setParent(j,$){let J=this.resolveEntityId(j);this.hierarchyManager.setParent(J,this.resolveEntityId($));for(let U of this._afterParentChangedHooks)U(J);return this}removeParent(j){let $=this.resolveEntityId(j),J=this.hierarchyManager.removeParent($);if(J)for(let U of this._afterParentChangedHooks)U($);return J}getParent(j){return this.hierarchyManager.getParent(this.resolveEntityId(j))}getChildren(j){return this.hierarchyManager.getChildren(this.resolveEntityId(j))}getChildAt(j,$){return this.hierarchyManager.getChildAt(this.resolveEntityId(j),$)}getChildIndex(j,$){return this.hierarchyManager.getChildIndex(this.resolveEntityId(j),this.resolveEntityId($))}getAncestors(j){return this.hierarchyManager.getAncestors(this.resolveEntityId(j))}getDescendants(j){return this.hierarchyManager.getDescendants(this.resolveEntityId(j))}getRoot(j){return this.hierarchyManager.getRoot(this.resolveEntityId(j))}getSiblings(j){return this.hierarchyManager.getSiblings(this.resolveEntityId(j))}isDescendantOf(j,$){return this.hierarchyManager.isDescendantOf(this.resolveEntityId(j),this.resolveEntityId($))}isAncestorOf(j,$){return this.hierarchyManager.isAncestorOf(this.resolveEntityId(j),this.resolveEntityId($))}getRootEntities(){return this.hierarchyManager.getRootEntities()}forEachInHierarchy(j,$){this.hierarchyManager.forEachInHierarchy(j,$)}hierarchyIterator(j){return this.hierarchyManager.hierarchyIterator(j)}}var h=()=>{};class w{handlers=new Map;subscribe(j,$){return this.addHandler(j,$,!1)}once(j,$){return this.addHandler(j,$,!0)}unsubscribe(j,$){let J=this.handlers.get(j);if(!J)return!1;let U=J.findIndex((X)=>X.callback===$);if(U===-1)return!1;return J.splice(U,1),!0}addHandler(j,$,J){if(!this.handlers.has(j))this.handlers.set(j,[]);let U={callback:$,once:J};return this.handlers.get(j).push(U),()=>{let X=this.handlers.get(j);if(X){let Y=X.indexOf(U);if(Y!==-1)X.splice(Y,1)}}}publish(...[j,$]){let J=this.handlers.get(j);if(!J||J.length===0)return;let U=!1,X=J.length;for(let Y=0;Y<X&&Y<J.length;Y++){let Z=J[Y];if(!Z)continue;if(Z.callback($),Z.once)U=!0}if(U){for(let Y=J.length-1;Y>=0;Y--)if(J[Y]?.once)J.splice(Y,1)}}clear(){this.handlers.clear()}clearEvent(j){this.handlers.delete(j)}}function e(j){return typeof j==="object"&&j!==null&&"factory"in j&&typeof j.factory==="function"}function g(j,$){let J=[],U=new Set,X=new Set;function Y(Z,D=[]){if(U.has(Z))return;if(X.has(Z))throw Error(`Circular resource dependency: ${[...D,Z].join(" -> ")}`);X.add(Z);for(let G of $(Z)){let _=j.find((z)=>z===G);if(_)Y(_,[...D,Z])}X.delete(Z),U.add(Z),J.push(Z)}for(let Z of j)Y(Z);return J}class R{resources=new Map;resourceFactories=new Map;resourceDependencies=new Map;resourceDisposers=new Map;initializedResourceKeys=new Set;add(j,$){if(e($)){if(this.resourceFactories.set(j,$.factory),this.resourceDependencies.set(j,$.dependsOn??[]),$.onDispose)this.resourceDisposers.set(j,$.onDispose)}else if(this._isFactoryFunction($))this.resourceFactories.set(j,$),this.resourceDependencies.set(j,[]);else this.resources.set(j,$),this.initializedResourceKeys.add(j),this.resourceDependencies.set(j,[]);return this}_isFactoryFunction(j){if(typeof j!=="function")return!1;let $=j.toString();if($.startsWith("class "))return!1;if($.includes("[native code]"))return!1;if(j.prototype){let U=Object.getOwnPropertyNames(j.prototype);if(U.length>1||U.length===1&&U[0]!=="constructor")return!1}let J=j.name.charAt(0);if(J&&J===J.toUpperCase()&&j.name.length>1){if($.includes("this.")||$.includes("new "))return!1}return!0}tryGet(j,...$){if(!this.has(j))return;return this.get(j,...$)}get(j,...$){let J=this.resources.get(j);if(J!==void 0)return J;let U=this.resourceFactories.get(j);if(U===void 0)throw Error(`Resource ${String(j)} not found`);let X=$[0],Y=U(X);if(!(Y instanceof Promise))this.resources.set(j,Y),this.initializedResourceKeys.add(j);return Y}has(j){return this.resources.has(j)||this.resourceFactories.has(j)}remove(j){let $=this.resources.delete(j),J=this.resourceFactories.delete(j);return this.resourceDependencies.delete(j),this.resourceDisposers.delete(j),this.initializedResourceKeys.delete(j),$||J}getKeys(){let j=new Set([...this.resources.keys(),...this.resourceFactories.keys()]);return Array.from(j)}needsInitialization(j){return this.resourceFactories.has(j)&&!this.initializedResourceKeys.has(j)}getPendingInitializationKeys(){return Array.from(this.resourceFactories.keys()).filter((j)=>!this.initializedResourceKeys.has(j))}async initializeResource(j,...$){if(!this.resourceFactories.has(j)||this.initializedResourceKeys.has(j))return;let J=this.resourceFactories.get(j),U=$[0],X=await J(U);this.resources.set(j,X),this.initializedResourceKeys.add(j),this.resourceFactories.delete(j)}async initializeResources(...j){let $=j.slice(1),J=$.length===0?this.getPendingInitializationKeys():$;if(J.length===0)return;let U=g(J,(X)=>[...this.resourceDependencies.get(X)??[]]);for(let X of U)await this.initializeResource(X,...j.slice(0,1))}getDependencies(j){return this.resourceDependencies.get(j)??[]}async disposeResource(j,...$){if(!this.resources.has(j)&&!this.resourceFactories.has(j))return!1;if(this.initializedResourceKeys.has(j)){let J=this.resourceDisposers.get(j),U=this.resources.get(j);if(J&&U!==void 0){let X=$[0];await J(U,X)}}return this.resources.delete(j),this.resourceFactories.delete(j),this.resourceDependencies.delete(j),this.resourceDisposers.delete(j),this.initializedResourceKeys.delete(j),!0}async disposeResources(...j){let $=Array.from(this.initializedResourceKeys);if($.length===0)return;let J=g($,(U)=>[...this.resourceDependencies.get(U)??[]]).reverse();for(let U of J)await this.disposeResource(U,...j)}}class K{queries=new Map;entityManager;_hasParentHasQueries=!1;constructor(j){this.entityManager=j}get hasParentHasQueries(){return this._hasParentHasQueries}addQuery(j,$){let J={definition:$,matchingEntities:new Set};if(this.queries.set(j,J),$.parentHas?.length)this._hasParentHasQueries=!0;let U=this.entityManager.getEntitiesWithQuery($.with,$.without??[]);for(let X of U)if(this.entityMatchesQuery(X,J.definition))J.matchingEntities.add(X.id),J.definition.onEnter?.(X)}removeQuery(j){let $=this.queries.delete(j);if($)this._recalcParentHasFlag();return $}entityMatchesQuery(j,$){for(let J of $.with)if(!(J in j.components))return!1;if($.without){for(let J of $.without)if(J in j.components)return!1}if($.parentHas?.length){let J=this.entityManager.getParent(j.id);if(J===null)return!1;let U=this.entityManager.getEntity(J);if(!U)return!1;for(let X of $.parentHas)if(!(X in U.components))return!1}return!0}_applyQueryTransition(j,$){let J=$.matchingEntities.has(j.id),U=this.entityMatchesQuery(j,$.definition);if(!J&&U)$.matchingEntities.add(j.id),$.definition.onEnter?.(j);else if(J&&!U)$.matchingEntities.delete(j.id),$.definition.onExit?.(j.id)}onComponentAdded(j,$){for(let[,J]of this.queries)this._applyQueryTransition(j,J);if(this._hasParentHasQueries)this._recheckChildren(j.id)}onComponentRemoved(j,$){for(let[,J]of this.queries)this._applyQueryTransition(j,J);if(this._hasParentHasQueries)this._recheckChildren(j.id)}onEntityRemoved(j){for(let[$,J]of this.queries)if(J.matchingEntities.has(j))J.matchingEntities.delete(j),J.definition.onExit?.(j)}recheckEntity(j){for(let[,$]of this.queries)this._applyQueryTransition(j,$)}recheckEntityAndChildren(j){if(this.recheckEntity(j),this._hasParentHasQueries)this._recheckChildren(j.id)}_recheckChildren(j){let $=this.entityManager.getChildren(j);for(let J of $){let U=this.entityManager.getEntity(J);if(U)this.recheckEntity(U)}}_recalcParentHasFlag(){this._hasParentHasQueries=!1;for(let[,j]of this.queries)if(j.definition.parentHas?.length){this._hasParentHasQueries=!0;return}}}class x{commands=[];removeEntity(j,$){this.commands.push((J)=>{J.removeEntity(j,$)})}addComponent(j,$,J){this.commands.push((U)=>{U.entityManager.addComponent(j,$,J)})}removeComponent(j,$){this.commands.push((J)=>{J.entityManager.removeComponent(j,$)})}spawn(j){this.commands.push(($)=>{$.spawn(j)})}spawnChild(j,$){this.commands.push((J)=>{J.spawnChild(j,$)})}addComponents(j,$){this.commands.push((J)=>{J.entityManager.addComponents(j,$)})}setParent(j,$){this.commands.push((J)=>{J.setParent(j,$)})}markChanged(j,$){this.commands.push((J)=>{J.markChanged(j,$)})}removeParent(j){this.commands.push(($)=>{$.removeParent(j)})}playback(j){for(let $ of this.commands)try{$(j)}catch(J){console.warn("CommandBuffer: Command failed during playback:",J)}this.commands=[]}clear(){this.commands=[]}get length(){return this.commands.length}}class S{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_phase="update";_isRegistered=!1;_groups=[];_inScreens;_excludeScreens;_requiredAssets;_runWhenEmpty=!1;constructor(j,$=null,J=null){this._label=j;this._ecspresso=$;this._bundle=J}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let j=this._createSystemObject();v(j,this._ecspresso),this._isRegistered=!0}_createSystemObject(){let j={label:this._label,entityQueries:this.queries,priority:this._priority,phase:this._phase};if(this.processFunction)j.process=this.processFunction;if(this.detachFunction)j.onDetach=this.detachFunction;if(this.initializeFunction)j.onInitialize=this.initializeFunction;if(this.eventHandlers)j.eventHandlers=this.eventHandlers;if(this._groups.length>0)j.groups=[...this._groups];if(this._inScreens)j.inScreens=this._inScreens;if(this._excludeScreens)j.excludeScreens=this._excludeScreens;if(this._requiredAssets)j.requiredAssets=this._requiredAssets;if(this._runWhenEmpty)j.runWhenEmpty=!0;return j}setPriority(j){return this._priority=j,this}inPhase(j){return this._phase=j,this}inGroup(j){if(!this._groups.includes(j))this._groups.push(j);return this}inScreens(j){return this._inScreens=[...j],this}excludeScreens(j){return this._excludeScreens=[...j],this}requiresAssets(j){return this._requiredAssets=[...j],this}runWhenEmpty(){return this._runWhenEmpty=!0,this}addQuery(j,$){let J=this;return J.queries={...this.queries,[j]:$},J}setProcess(j){return this.processFunction=j,this}registerAndContinue(){if(!this._ecspresso)throw Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);return this._autoRegister(),this._ecspresso}and(){if(this._ecspresso)return this._autoRegister(),this._ecspresso;if(this._bundle)return this._bundle;throw Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`)}setOnDetach(j){return this.detachFunction=j,this}setOnInitialize(j){return this.initializeFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let $=this._createSystemObject();if(this._ecspresso)v($,this._ecspresso);if(j)v($,j);return this}}function v(j,$){$._registerSystem(j)}function I(j,$){return new S(j,$)}function u(j,$){return new S(j,null,$)}function T(j,$,J){let U=new Set,X=[$];while(X.length>0){let Y=X.pop();if(Y===j)throw Error(`Circular required component dependency: '${String(j)}' -> '${String($)}' -> ... -> '${String(j)}'`);if(U.has(Y))continue;U.add(Y);let Z=J(Y);if(Z)for(let D of Z)X.push(D.component)}}var m="0.11.0";var p=()=>{};class V{assets=new Map;groups=new Map;eventBus=null;setEventBus(j){this.eventBus=j}register(j,$){if(this.assets.set(j,{definition:$,status:"pending"}),$.group){let J=this.groups.get($.group)??new Set;J.add(j),this.groups.set($.group,J)}}async loadEagerAssets(){let j=[];for(let[$,J]of this.assets)if(J.definition.eager&&J.status==="pending")j.push($);await Promise.all(j.map(($)=>this.loadAsset($)))}async loadAsset(j){let $=this.assets.get(j);if(!$)throw Error(`Asset '${String(j)}' not found`);if($.status==="loaded"&&$.value!==void 0)return $.value;if($.status==="loading"&&$.loadPromise)return $.loadPromise;if($.status==="failed")$.status="pending";$.status="loading",$.loadPromise=$.definition.loader();try{let J=await $.loadPromise;return $.value=J,$.status="loaded",$.loadPromise=void 0,this.eventBus?.publish("assetLoaded",{key:j}),this.checkGroupProgress($.definition.group),J}catch(J){let U=J instanceof Error?J:Error(String(J));throw $.status="failed",$.error=U,$.loadPromise=void 0,this.eventBus?.publish("assetFailed",{key:j,error:U}),U}}async loadAssetGroup(j){let $=this.groups.get(j);if(!$||$.size===0)throw Error(`Asset group '${j}' not found or empty`);await Promise.all(Array.from($).map((J)=>this.loadAsset(J)))}get(j){let $=this.assets.get(j);if(!$)throw Error(`Asset '${String(j)}' not found`);if($.status!=="loaded"||$.value===void 0)throw Error(`Asset '${String(j)}' is not loaded (status: ${$.status})`);return $.value}getOrUndefined(j){let $=this.assets.get(j);if(!$||$.status!=="loaded")return;return $.value}getHandle(j){let $=this.assets.get(j);if(!$)throw Error(`Asset '${String(j)}' not found`);let J=this;return{get status(){return $.status},get isLoaded(){return $.status==="loaded"},get(){return J.get(j)},getOrUndefined(){return J.getOrUndefined(j)}}}getStatus(j){let $=this.assets.get(j);if(!$)throw Error(`Asset '${String(j)}' not found`);return $.status}isLoaded(j){return this.assets.get(j)?.status==="loaded"}isGroupLoaded(j){let $=this.groups.get(j);if(!$||$.size===0)return!1;for(let J of $){let U=this.assets.get(J);if(!U||U.status!=="loaded")return!1}return!0}getGroupProgress(j){return this.getGroupProgressDetails(j).progress}getGroupProgressDetails(j){let $=this.groups.get(j);if(!$||$.size===0)return{loaded:0,total:0,progress:0};let J=0;for(let X of $)if(this.assets.get(X)?.status==="loaded")J++;let U=$.size;return{loaded:J,total:U,progress:J/U}}checkGroupProgress(j){if(!j||!this.eventBus)return;let $=j,J=this.getGroupProgressDetails($);if(this.eventBus.publish("assetGroupProgress",{group:$,...J}),J.loaded===J.total)this.eventBus.publish("assetGroupLoaded",{group:$})}createResource(){let j=this;return{getStatus($){return j.getStatus($)},isLoaded($){return j.isLoaded($)},isGroupLoaded($){return j.isGroupLoaded($)},getGroupProgress($){return j.getGroupProgress($)},get($){return j.get($)},getOrUndefined($){return j.getOrUndefined($)},getHandle($){return j.getHandle($)}}}getKeys(){return Array.from(this.assets.keys())}getGroupNames(){return Array.from(this.groups.keys())}getGroupKeys(j){let $=this.groups.get(j);return $?Array.from($):[]}}class s{manager;constructor(j){this.manager=j}add(j,$){return this.manager.register(j,{loader:$,eager:!0}),this}addWithConfig(j,$){return this.manager.register(j,$),this}addGroup(j,$){for(let[J,U]of Object.entries($))this.manager.register(J,{loader:U,eager:!1,group:j});return this}getManager(){return this.manager}}function f(j){return new s(j??new V)}class A{screens=new Map;currentScreen=null;screenStack=[];eventBus=null;assetManager=null;ecs=null;setDependencies(j,$,J){this.eventBus=j,this.assetManager=$,this.ecs=J}register(j,$){this.screens.set(j,{definition:$})}async setScreen(j,$){let J=this.screens.get(j);if(!J)throw Error(`Screen '${String(j)}' not found`);await this.verifyRequiredAssets(J.definition);while(this.screenStack.length>0){let X=this.screenStack.pop();if(X)await this.exitScreen(X.name)}if(this.currentScreen)await this.exitScreen(this.currentScreen.name);let U=J.definition.initialState($);this.currentScreen={name:j,config:$,state:U},await J.definition.onEnter?.($,this.ecs),this.eventBus?.publish("screenEnter",{screen:j,config:$})}async pushScreen(j,$){let J=this.screens.get(j);if(!J)throw Error(`Screen '${String(j)}' not found`);if(await this.verifyRequiredAssets(J.definition),this.currentScreen)this.screenStack.push(this.currentScreen);let U=J.definition.initialState($);this.currentScreen={name:j,config:$,state:U},await J.definition.onEnter?.($,this.ecs),this.eventBus?.publish("screenPush",{screen:j,config:$})}async popScreen(){if(this.screenStack.length===0)throw Error("Cannot pop screen: stack is empty");if(this.currentScreen)await this.exitScreen(this.currentScreen.name),this.eventBus?.publish("screenPop",{screen:this.currentScreen.name});this.currentScreen=this.screenStack.pop()??null}async exitScreen(j){let $=this.screens.get(j);if($?.definition.onExit)await $.definition.onExit(this.ecs);this.eventBus?.publish("screenExit",{screen:j})}async verifyRequiredAssets(j){if(!this.assetManager)return;if(j.requiredAssets){for(let $ of j.requiredAssets)if(!this.assetManager.isLoaded($))await this.assetManager.loadAsset($)}if(j.requiredAssetGroups){for(let $ of j.requiredAssetGroups)if(!this.assetManager.isGroupLoaded($))await this.assetManager.loadAssetGroup($)}}getCurrentScreen(){return this.currentScreen?.name??null}getConfig(){if(!this.currentScreen)throw Error("No current screen");return this.currentScreen.config}getConfigOrNull(){return this.currentScreen?.config??null}getState(){if(!this.currentScreen)throw Error("No current screen");return this.currentScreen.state}getStateOrNull(){return this.currentScreen?.state??null}updateState(j){if(!this.currentScreen)throw Error("No current screen");let $=typeof j==="function"?j(this.currentScreen.state):j;this.currentScreen.state={...this.currentScreen.state,...$}}getStackDepth(){return this.screenStack.length}isOverlay(){return this.screenStack.length>0}isActive(j){if(this.currentScreen?.name===j)return!0;return this.screenStack.some(($)=>$.name===j)}isCurrent(j){return this.currentScreen?.name===j}createResource(){let j=this;return{get current(){return j.getCurrentScreen()},get config(){return j.getConfigOrNull()},get state(){return j.getStateOrNull()},set state($){if(j.currentScreen)j.currentScreen.state=$},get stack(){return j.screenStack},get isOverlay(){return j.isOverlay()},get stackDepth(){return j.getStackDepth()},isActive($){return j.isActive($)},isCurrent($){return j.isCurrent($)}}}getScreenNames(){return Array.from(this.screens.keys())}hasScreen(j){return this.screens.has(j)}}class l{manager;constructor(j){this.manager=j}add(j,$){return this.manager.register(j,$),this}getManager(){return this.manager}}function O(j){return new l(j??new A)}class N{ecspresso;assetConfigurator=null;screenConfigurator=null;pendingResources=[];pendingDisposeCallbacks=[];pendingRequiredComponents=[];_fixedDt=null;constructor(){let{default:j}=(b(),n(y));this.ecspresso=new j}withBundle(j){return this.ecspresso._installBundle(j),this}withComponentTypes(){return this}withEventTypes(){return this}withResource(j,$){return this.pendingResources.push({key:j,value:$}),this}withDispose(j,$){return this.pendingDisposeCallbacks.push({key:j,callback:$}),this}withRequired(j,$,J){return this.pendingRequiredComponents.push({trigger:j,required:$,factory:J}),this}withAssets(j){let $=f();return j($),this.assetConfigurator=$,this}withScreens(j){let $=O();return j($),this.screenConfigurator=$,this}withFixedTimestep(j){return this._fixedDt=j,this}withReactiveQueryNames(){return this}build(){for(let{key:j,value:$}of this.pendingResources)this.ecspresso.addResource(j,$);for(let{key:j,callback:$}of this.pendingDisposeCallbacks)this.ecspresso.registerDispose(j,$);for(let{trigger:j,required:$,factory:J}of this.pendingRequiredComponents)this.ecspresso.registerRequired(j,$,J);if(this.assetConfigurator)this.ecspresso._setAssetManager(this.assetConfigurator.getManager());else if(this.ecspresso._hasPendingBundleAssets())this.ecspresso._setAssetManager(new V);if(this.screenConfigurator)this.ecspresso._setScreenManager(this.screenConfigurator.getManager());else if(this.ecspresso._hasPendingBundleScreens())this.ecspresso._setScreenManager(new A);if(this._fixedDt!==null)this.ecspresso._setFixedDt(this._fixedDt);return this.ecspresso}}var c=()=>{};var y={};t(y,{default:()=>M});var d,o,M;var b=E(()=>{h();p();c();d=["preUpdate","fixedUpdate","update","postUpdate","render"],o={};M=class M{static VERSION=m;_entityManager;_eventBus;_resourceManager;_commandBuffer;_systems=[];_phaseSystems={preUpdate:[],fixedUpdate:[],update:[],postUpdate:[],render:[]};_installedBundles=new Set;_disabledGroups=new Set;_assetManager=null;_screenManager=null;_reactiveQueryManager;_postUpdateHooks=[];_currentTick=0;_systemLastSeqs=new Map;_changeThreshold=0;_fixedDt=0.016666666666666666;_fixedAccumulator=0;_interpolationAlpha=0;_maxFixedSteps=8;_requiredComponents=new Map;_pendingBundleAssets=[];_pendingBundleScreens=[];_diagnosticsEnabled=!1;_systemTimings=new Map;_phaseTimings={preUpdate:0,fixedUpdate:0,update:0,postUpdate:0,render:0};constructor(){this._entityManager=new P,this._eventBus=new w,this._resourceManager=new R,this._reactiveQueryManager=new K(this._entityManager),this._commandBuffer=new x,this._subscribeLifecycleHooks()}_subscribeLifecycleHooks(){this._entityManager.onAfterComponentAdded((j,$)=>{this._entityManager.markChanged(j,$);let J=this._requiredComponents.get($);if(J){let U=this._entityManager.getEntity(j);if(U){let X=U.components[$];for(let{component:Y,factory:Z}of J){if(this._entityManager._pendingBatchKeys?.has(Y))continue;if(!(Y in U.components))this._entityManager.addComponent(j,Y,Z(X))}}}}),this._entityManager.onAfterEntityMutated((j)=>{let $=this._entityManager.getEntity(j);if($)this._reactiveQueryManager.recheckEntityAndChildren($)}),this._entityManager.onAfterComponentRemoved((j,$)=>{let J=this._entityManager.getEntity(j);if(J)this._reactiveQueryManager.onComponentRemoved(J,$)}),this._entityManager.onBeforeEntityRemoved((j)=>{this._reactiveQueryManager.onEntityRemoved(j)}),this._entityManager.onAfterParentChanged((j)=>{if(this._reactiveQueryManager.hasParentHasQueries){let $=this._entityManager.getEntity(j);if($)this._reactiveQueryManager.recheckEntity($)}})}static create(){return new N}addSystem(j){return I(j,this)}update(j){let $=this._screenManager?.getCurrentScreen()??null,J=this._diagnosticsEnabled;this._runPhase("preUpdate",j,$,J);let U=J?performance.now():0;this._fixedAccumulator+=j;let X=0;while(this._fixedAccumulator>=this._fixedDt&&X<this._maxFixedSteps)this._executePhase(this._phaseSystems.fixedUpdate,this._fixedDt,$),this._commandBuffer.playback(this),this._fixedAccumulator-=this._fixedDt,X++;if(this._fixedAccumulator>=this._fixedDt)this._fixedAccumulator=0;if(J)this._phaseTimings.fixedUpdate=performance.now()-U;this._interpolationAlpha=this._fixedAccumulator/this._fixedDt,this._runPhase("update",j,$,J),this._runPhase("postUpdate",j,$,J);for(let Y of this._postUpdateHooks)Y(this,j);this._runPhase("render",j,$,J),this._changeThreshold=this._entityManager.changeSeq,this._currentTick++}_executePhase(j,$,J){for(let U of j){if(!U.process)continue;if(U.groups?.length){let G=!1;for(let _ of U.groups)if(this._disabledGroups.has(_)){G=!0;break}if(G)continue}if(U.inScreens?.length){if(J===null||!U.inScreens.includes(J))continue}if(U.excludeScreens?.length){if(J!==null&&U.excludeScreens.includes(J))continue}if(U.requiredAssets?.length&&this._assetManager){let G=!0;for(let _ of U.requiredAssets)if(!this._assetManager.isLoaded(_)){G=!1;break}if(!G)continue}let X=this._systemLastSeqs.get(U)??0;this._changeThreshold=X;let Y={},Z=!1,D=!1;if(U.entityQueries)for(let G in U.entityQueries){D=!0;let _=U.entityQueries[G];if(_){if(Y[G]=this._entityManager.getEntitiesWithQuery(_.with,_.without||[],_.changed,_.changed?this._changeThreshold:void 0,_.parentHas),Y[G].length)Z=!0}}if(this._diagnosticsEnabled){let G=performance.now();if(Z||U.runWhenEmpty)U.process(Y,$,this);else if(!D)U.process(o,$,this);this._systemTimings.set(U.label,performance.now()-G)}else if(Z||U.runWhenEmpty)U.process(Y,$,this);else if(!D)U.process(o,$,this);this._systemLastSeqs.set(U,this._entityManager.changeSeq)}}_runPhase(j,$,J,U){if(U){let X=performance.now();this._executePhase(this._phaseSystems[j],$,J),this._phaseTimings[j]=performance.now()-X}else this._executePhase(this._phaseSystems[j],$,J);this._commandBuffer.playback(this)}async initialize(){if(await this.initializeResources(),this._assetManager)this._assetManager.setEventBus(this._eventBus),await this._assetManager.loadEagerAssets(),this._resourceManager.add("$assets",this._assetManager.createResource());if(this._screenManager)this._screenManager.setDependencies(this._eventBus,this._assetManager,this),this._resourceManager.add("$screen",this._screenManager.createResource());for(let j of this._systems)await j.onInitialize?.(this)}async initializeResources(...j){await this._resourceManager.initializeResources(this,...j)}_rebuildPhaseSystems(){for(let j of d)this._phaseSystems[j]=[];for(let j of this._systems){let $=j.phase??"update";this._phaseSystems[$].push(j)}for(let j of d)this._phaseSystems[j].sort(($,J)=>{let U=$.priority??0;return(J.priority??0)-U})}updateSystemPriority(j,$){let J=this._systems.find((U)=>U.label===j);if(!J)return!1;return J.priority=$,this._rebuildPhaseSystems(),!0}updateSystemPhase(j,$){let J=this._systems.find((U)=>U.label===j);if(!J)return!1;return J.phase=$,this._rebuildPhaseSystems(),!0}get interpolationAlpha(){return this._interpolationAlpha}get fixedDt(){return this._fixedDt}disableSystemGroup(j){this._disabledGroups.add(j)}enableSystemGroup(j){this._disabledGroups.delete(j)}isSystemGroupEnabled(j){return!this._disabledGroups.has(j)}getSystemsInGroup(j){return this._systems.filter(($)=>$.groups?.includes(j)).map(($)=>$.label)}removeSystem(j){let $=this._systems.findIndex((U)=>U.label===j);if($===-1)return!1;let J=this._systems[$];if(!J)return!1;if(J.onDetach)J.onDetach(this);return this._systems.splice($,1),this._systemLastSeqs.delete(J),this._rebuildPhaseSystems(),!0}_registerSystem(j){if(this._systems.push(j),this._systemLastSeqs.set(j,this._changeThreshold),this._rebuildPhaseSystems(),!j.eventHandlers)return;for(let $ in j.eventHandlers){let J=j.eventHandlers[$]?.handler;if(J)this._eventBus.subscribe($,(U)=>{J(U,this)})}}hasResource(j){return this._resourceManager.has(j)}getResource(j){if(!this._resourceManager.has(j))throw Error(`Resource '${String(j)}' not found. Available resources: [${this.getResourceKeys().map(($)=>String($)).join(", ")}]`);return this._resourceManager.get(j,this)}tryGetResource(j){let $=j;if(!this._resourceManager.has($))return;return this._resourceManager.get($,this)}addResource(j,$){return this._resourceManager.add(j,$),this}removeResource(j){return this._resourceManager.remove(j)}async disposeResource(j){return this._resourceManager.disposeResource(j,this)}async disposeResources(){return this._resourceManager.disposeResources(this)}updateResource(j,$){let J=this.getResource(j),U=$(J);return this._resourceManager.add(j,U),this}getResourceKeys(){return this._resourceManager.getKeys()}resourceNeedsInitialization(j){return this._resourceManager.needsInitialization(j)}hasComponent(j,$){return this._entityManager.getComponent(j,$)!==void 0}spawn(j){let $=this._entityManager.createEntity();return this._entityManager.addComponents($,j),$}getEntitiesWithQuery(j,$=[],J,U){return this._entityManager.getEntitiesWithQuery(j,$,J,J?this._changeThreshold:void 0,U)}getSingleton(j,$=[]){let J=this._entityManager.getEntitiesWithQuery(j,$);if(J.length===0)throw Error(`getSingleton: no entity matches query with=[${String(j)}] without=[${String($)}]`);if(J.length>1)throw Error(`getSingleton: expected 1 entity but found ${J.length} matching query with=[${String(j)}] without=[${String($)}]`);return J[0]}tryGetSingleton(j,$=[]){let J=this._entityManager.getEntitiesWithQuery(j,$);if(J.length===0)return;if(J.length>1)throw Error(`tryGetSingleton: expected 0 or 1 entity but found ${J.length} matching query with=[${String(j)}] without=[${String($)}]`);return J[0]}removeEntity(j,$){return this._entityManager.removeEntity(j,$)}spawnChild(j,$){let J=this._entityManager.spawnChild(j,$),U=typeof j==="number"?j:j.id;return this._emitHierarchyChanged(J.id,null,U),J}setParent(j,$){let J=typeof j==="number"?j:j.id,U=typeof $==="number"?$:$.id,X=this._entityManager.getParent(J);return this._entityManager.setParent(J,U),this._emitHierarchyChanged(J,X,U),this}removeParent(j){let $=typeof j==="number"?j:j.id,J=this._entityManager.getParent($),U=this._entityManager.removeParent($);if(U)this._emitHierarchyChanged($,J,null);return U}getParent(j){return this._entityManager.getParent(j)}getChildren(j){return this._entityManager.getChildren(j)}getChildAt(j,$){return this._entityManager.getChildAt(j,$)}getChildIndex(j,$){return this._entityManager.getChildIndex(j,$)}getAncestors(j){return this._entityManager.getAncestors(j)}getDescendants(j){return this._entityManager.getDescendants(j)}getRoot(j){return this._entityManager.getRoot(j)}getSiblings(j){return this._entityManager.getSiblings(j)}isDescendantOf(j,$){return this._entityManager.isDescendantOf(j,$)}isAncestorOf(j,$){return this._entityManager.isAncestorOf(j,$)}getRootEntities(){return this._entityManager.getRootEntities()}forEachInHierarchy(j,$){this._entityManager.forEachInHierarchy(j,$)}hierarchyIterator(j){return this._entityManager.hierarchyIterator(j)}_emitHierarchyChanged(j,$,J){this._eventBus.publish("hierarchyChanged",{entityId:j,oldParent:$,newParent:J})}get installedBundles(){return Array.from(this._installedBundles)}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}get commands(){return this._commandBuffer}get currentTick(){return this._currentTick}get changeThreshold(){return this._changeThreshold}enableDiagnostics(j){if(this._diagnosticsEnabled=j,!j)this._systemTimings.clear(),this._phaseTimings={preUpdate:0,fixedUpdate:0,update:0,postUpdate:0,render:0}}get diagnosticsEnabled(){return this._diagnosticsEnabled}get systemTimings(){return this._systemTimings}get phaseTimings(){return this._phaseTimings}get entityCount(){return this._entityManager.entityCount}markChanged(j,$){this._entityManager.markChanged(j,$)}registerDispose(j,$){this._entityManager.registerDispose(j,$)}registerRequired(j,$,J){if(String(j)===String($))throw Error(`Cannot require a component to depend on itself: '${String(j)}'`);let U=this._requiredComponents.get(j)??[];if(U.some((X)=>X.component===$))throw Error(`Required component '${String($)}' already registered for trigger '${String(j)}'`);this._checkRequiredCycle(j,$),U.push({component:$,factory:J}),this._requiredComponents.set(j,U)}_checkRequiredCycle(j,$){T(j,$,(J)=>this._requiredComponents.get(J))}onComponentAdded(j,$){return this._entityManager.onComponentAdded(j,$)}onComponentRemoved(j,$){return this._entityManager.onComponentRemoved(j,$)}addReactiveQuery(j,$){this._reactiveQueryManager.addQuery(j,$)}removeReactiveQuery(j){return this._reactiveQueryManager.removeQuery(j)}on(j,$){return this._eventBus.subscribe(j,$)}off(j,$){return this._eventBus.unsubscribe(j,$)}onPostUpdate(j){return this._postUpdateHooks.push(j),()=>{let $=this._postUpdateHooks.indexOf(j);if($!==-1)this._postUpdateHooks.splice($,1)}}requireAssetManager(){if(!this._assetManager)throw Error("Asset manager not configured. Use withAssets() in builder.");return this._assetManager}getAsset(j){return this.requireAssetManager().get(j)}getAssetOrUndefined(j){return this._assetManager?.getOrUndefined(j)}getAssetHandle(j){return this.requireAssetManager().getHandle(j)}isAssetLoaded(j){return this._assetManager?.isLoaded(j)??!1}async loadAsset(j){return this.requireAssetManager().loadAsset(j)}async loadAssetGroup(j){return this.requireAssetManager().loadAssetGroup(j)}isAssetGroupLoaded(j){return this._assetManager?.isGroupLoaded(j)??!1}getAssetGroupProgress(j){return this._assetManager?.getGroupProgress(j)??0}requireScreenManager(){if(!this._screenManager)throw Error("Screen manager not configured. Use withScreens() in builder.");return this._screenManager}async setScreen(j,$){return this.requireScreenManager().setScreen(j,$)}async pushScreen(j,$){return this.requireScreenManager().pushScreen(j,$)}async popScreen(){return this.requireScreenManager().popScreen()}getCurrentScreen(){return this._screenManager?.getCurrentScreen()??null}getScreenConfig(){return this.requireScreenManager().getConfig()}getScreenConfigOrNull(){return this._screenManager?.getConfigOrNull()??null}getScreenState(){return this.requireScreenManager().getState()}getScreenStateOrNull(){return this._screenManager?.getStateOrNull()??null}updateScreenState(j){this.requireScreenManager().updateState(j)}isCurrentScreen(j){return this._screenManager?.isCurrent(j)??!1}isScreenActive(j){return this._screenManager?.isActive(j)??!1}getScreenStackDepth(){return this._screenManager?.getStackDepth()??0}_setAssetManager(j){this._assetManager=j;for(let[$,J]of this._pendingBundleAssets)this._assetManager.register($,J);this._pendingBundleAssets=[]}_setScreenManager(j){this._screenManager=j;for(let[$,J]of this._pendingBundleScreens)this._screenManager.register($,J);this._pendingBundleScreens=[]}_hasPendingBundleAssets(){return this._pendingBundleAssets.length>0}_hasPendingBundleScreens(){return this._pendingBundleScreens.length>0}_setFixedDt(j){this._fixedDt=j}_installBundle(j){if(this._installedBundles.has(j.id))return this;this._installedBundles.add(j.id),j.registerSystemsWithEcspresso(this);let $=j.getDisposeCallbacks();for(let[Z,D]of $.entries())this._entityManager.registerDispose(Z,D);let J=j.getRequiredComponents();for(let[Z,D]of J.entries())for(let{component:G,factory:_}of D){let z=Z,L=G,F=this._requiredComponents.get(z)??[];if(!F.some((Q)=>Q.component===L))this._checkRequiredCycle(z,L),F.push({component:L,factory:_}),this._requiredComponents.set(z,F)}let U=j.getResources();for(let[Z,D]of U.entries())this._resourceManager.add(Z,D);let X=j.getAssets();for(let[Z,D]of X.entries())this._pendingBundleAssets.push([Z,D]);let Y=j.getScreens();for(let[Z,D]of Y.entries())this._pendingBundleScreens.push([Z,D]);return this}}});b();function $j(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class B{_systems=[];_resources=new Map;_assets=new Map;_assetGroups=new Map;_screens=new Map;_disposeCallbacks=new Map;_requiredComponents=new Map;_id;constructor(j){this._id=j||$j()}get id(){return this._id}set id(j){this._id=j}addSystem(j){if(typeof j==="string"){let $=u(j,this);return this._systems.push($),$}else return this._systems.push(j),j}addResource(j,$){return this._resources.set(j,$),this}addAsset(j,$,J){return this._assets.set(j,{loader:$,eager:J?.eager??!0,group:J?.group}),this}addAssetGroup(j,$){let J=new Map;for(let[U,X]of Object.entries($))J.set(U,X),this._assets.set(U,{loader:X,eager:!1,group:j});return this._assetGroups.set(j,J),this}addScreen(j,$){return this._screens.set(j,$),this}getAssets(){return new Map(this._assets)}getScreens(){return new Map(this._screens)}registerDispose(j,$){return this._disposeCallbacks.set(j,$),this}getDisposeCallbacks(){return new Map(this._disposeCallbacks)}registerRequired(j,$,J){let U=j,X=$;if(U===X)throw Error(`Cannot require a component to depend on itself: '${U}'`);let Y=this._requiredComponents.get(U)??[];if(Y.some((Z)=>Z.component===X))throw Error(`Required component '${X}' already registered for trigger '${U}'`);return this._checkRequiredCycle(U,X),Y.push({component:X,factory:J}),this._requiredComponents.set(U,Y),this}withReactiveQueryNames(){return this}getRequiredComponents(){let j=new Map;for(let[$,J]of this._requiredComponents)j.set($,[...J]);return j}_checkRequiredCycle(j,$){T(j,$,(J)=>this._requiredComponents.get(J))}_setDisposeCallback(j,$){this._disposeCallbacks.set(j,$)}_setResource(j,$){this._resources.set(j,$)}_setAsset(j,$){this._assets.set(j,$)}_setScreen(j,$){this._screens.set(j,$)}_addRequired(j,$,J){let U=this._requiredComponents.get(j)??[];if(!U.some((X)=>X.component===$))U.push({component:$,factory:J}),this._requiredComponents.set(j,U)}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let $ of this._systems)$.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function Jj(j,...$){if($.length===0)return new B(j);let J=new B(j);for(let U of $){for(let X of U.getSystemBuilders())J.addSystem(X);for(let[X,Y]of U.getResources().entries())J._setResource(X,Y);for(let[X,Y]of U.getAssets().entries())J._setAsset(X,Y);for(let[X,Y]of U.getScreens().entries())J._setScreen(X,Y);for(let[X,Y]of U.getDisposeCallbacks().entries())J._setDisposeCallback(X,Y);for(let[X,Y]of U.getRequiredComponents().entries())for(let{component:Z,factory:D}of Y)J._addRequired(X,Z,D)}return J}function kj(j){return j}function gj(j,$){return{x:j,y:$}}function Ij(){return{x:0,y:0}}function uj(j,$){return{x:j.x+$.x,y:j.y+$.y}}function pj(j,$){return{x:j.x-$.x,y:j.y-$.y}}function mj(j,$){return{x:j.x*$,y:j.y*$}}function sj(j){return{x:-j.x,y:-j.y}}function lj(j,$){return j.x*$.x+j.y*$.y}function cj(j,$){return j.x*$.y-j.y*$.x}function yj(j){return j.x*j.x+j.y*j.y}function dj(j){return Math.sqrt(j.x*j.x+j.y*j.y)}function oj(j){let $=Math.sqrt(j.x*j.x+j.y*j.y);if($===0)return{x:0,y:0};return{x:j.x/$,y:j.y/$}}function ij(j,$){let J=j.x-$.x,U=j.y-$.y;return J*J+U*U}function aj(j,$){let J=j.x-$.x,U=j.y-$.y;return Math.sqrt(J*J+U*U)}function rj(j,$,J=0.0000000001){return Math.abs(j.x-$.x)<=J&&Math.abs(j.y-$.y)<=J}var G0=M;export{Ij as vec2Zero,pj as vec2Sub,mj as vec2Scale,oj as vec2Normalize,sj as vec2Negate,yj as vec2LengthSq,dj as vec2Length,rj as vec2Equals,lj as vec2Dot,ij as vec2DistanceSq,aj as vec2Distance,cj as vec2Cross,uj as vec2Add,gj as vec2,Jj as mergeBundles,G0 as default,O as createScreenConfigurator,kj as createQueryDefinition,f as createAssetConfigurator,S as SystemBuilder,A as ScreenManager,B as Bundle,V as AssetManager};
|
|
2
|
+
|
|
3
|
+
//# debugId=DE1912346E2150C064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=index.js.map
|