@forgeax/engine-ecs 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +278 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
- package/dist/__tests__/archetype.unit.test.d.ts +2 -0
- package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
- package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
- package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/command-buffer.test.d.ts +2 -0
- package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-catalog.test.d.ts +2 -0
- package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test-d.d.ts +2 -0
- package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test.d.ts +2 -0
- package/dist/__tests__/component-schema.test.d.ts.map +1 -0
- package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
- package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
- package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle.test-d.d.ts +2 -0
- package/dist/__tests__/handle.test-d.d.ts.map +1 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
- package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
- package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
- package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
- package/dist/__tests__/plugin-service.test.d.ts +2 -0
- package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query.unit.test.d.ts +2 -0
- package/dist/__tests__/query.unit.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/serialization.unit.test.d.ts +2 -0
- package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/stale-error.test-d.d.ts +2 -0
- package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
- package/dist/__tests__/time.unit.test.d.ts +2 -0
- package/dist/__tests__/time.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test-d.d.ts +2 -0
- package/dist/__tests__/types.test-d.d.ts.map +1 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
- package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/world-health.contract.test.d.ts +2 -0
- package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
- package/dist/__tests__/world-inspection.test.d.ts +2 -0
- package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world.test-d.d.ts +2 -0
- package/dist/__tests__/world.test-d.d.ts.map +1 -0
- package/dist/buffer-pool.d.ts +109 -0
- package/dist/buffer-pool.d.ts.map +1 -0
- package/dist/commands.d.ts +93 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/component-default-fallback.d.ts +88 -0
- package/dist/component-default-fallback.d.ts.map +1 -0
- package/dist/component-schema.d.ts +34 -0
- package/dist/component-schema.d.ts.map +1 -0
- package/dist/component-value-validate.d.ts +29 -0
- package/dist/component-value-validate.d.ts.map +1 -0
- package/dist/component.d.ts +581 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/entity-handle.d.ts +68 -0
- package/dist/entity-handle.d.ts.map +1 -0
- package/dist/entity.d.ts +86 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/errors/query-and-component-errors.d.ts +132 -0
- package/dist/errors/query-and-component-errors.d.ts.map +1 -0
- package/dist/errors/relationship-errors.d.ts +112 -0
- package/dist/errors/relationship-errors.d.ts.map +1 -0
- package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
- package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
- package/dist/errors/validation-errors.d.ts +229 -0
- package/dist/errors/validation-errors.d.ts.map +1 -0
- package/dist/errors.d.ts +763 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/execution/shared-kernel.d.ts +106 -0
- package/dist/execution/shared-kernel.d.ts.map +1 -0
- package/dist/externalization/index.d.ts +31 -0
- package/dist/externalization/index.d.ts.map +1 -0
- package/dist/externalization/index.mjs +298 -0
- package/dist/externalization/index.mjs.map +1 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7422 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal.mjs +479 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/plugin-service.d.ts +11 -0
- package/dist/plugin-service.d.ts.map +1 -0
- package/dist/projection/index.d.ts +103 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.mjs +615 -0
- package/dist/projection/index.mjs.map +1 -0
- package/dist/query/query.d.ts +58 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/relationship-index.d.ts +81 -0
- package/dist/relationship-index.d.ts.map +1 -0
- package/dist/resource.d.ts +70 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/schedule-token.d.ts +16 -0
- package/dist/schedule-token.d.ts.map +1 -0
- package/dist/schedule.d.ts +271 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/shared-ref-store.d.ts +144 -0
- package/dist/shared-ref-store.d.ts.map +1 -0
- package/dist/shared.d.ts +3 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.mjs +364 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/storage/archetype-graph.d.ts +54 -0
- package/dist/storage/archetype-graph.d.ts.map +1 -0
- package/dist/storage/archetype.d.ts +23 -0
- package/dist/storage/archetype.d.ts.map +1 -0
- package/dist/storage/change-detection.d.ts +40 -0
- package/dist/storage/change-detection.d.ts.map +1 -0
- package/dist/storage/column.d.ts +111 -0
- package/dist/storage/column.d.ts.map +1 -0
- package/dist/storage/table.d.ts +29 -0
- package/dist/storage/table.d.ts.map +1 -0
- package/dist/time.d.ts +57 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/unique-ref-store.d.ts +105 -0
- package/dist/unique-ref-store.d.ts.map +1 -0
- package/dist/world-change-journal.d.ts +34 -0
- package/dist/world-change-journal.d.ts.map +1 -0
- package/dist/world-component-access.d.ts +265 -0
- package/dist/world-component-access.d.ts.map +1 -0
- package/dist/world-component-storage.d.ts +282 -0
- package/dist/world-component-storage.d.ts.map +1 -0
- package/dist/world-core.d.ts +39 -0
- package/dist/world-core.d.ts.map +1 -0
- package/dist/world-entity-lifecycle.d.ts +30 -0
- package/dist/world-entity-lifecycle.d.ts.map +1 -0
- package/dist/world-internal.d.ts +14 -0
- package/dist/world-internal.d.ts.map +1 -0
- package/dist/world-scheduling.d.ts +30 -0
- package/dist/world-scheduling.d.ts.map +1 -0
- package/dist/world.d.ts +620 -0
- package/dist/world.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/__fixtures__/field-shape-fixture.ts +30 -0
- package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
- package/src/__tests__/archetype.unit.test.ts +1837 -0
- package/src/__tests__/array-handle-element.test-d.ts +34 -0
- package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
- package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
- package/src/__tests__/buffer-pool.test-d.ts +32 -0
- package/src/__tests__/change-detection.unit.test.ts +86 -0
- package/src/__tests__/command-buffer.test.ts +179 -0
- package/src/__tests__/commands-deferred.unit.test.ts +68 -0
- package/src/__tests__/component-catalog.test.ts +59 -0
- package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
- package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
- package/src/__tests__/component-schema.test-d.ts +119 -0
- package/src/__tests__/component-schema.test.ts +55 -0
- package/src/__tests__/define-component-options.test-d.ts +35 -0
- package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
- package/src/__tests__/despawn-all.unit.test.ts +17 -0
- package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
- package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
- package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
- package/src/__tests__/entity-disabling.unit.test.ts +37 -0
- package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
- package/src/__tests__/errors.unit.test.ts +1456 -0
- package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
- package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
- package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
- package/src/__tests__/externalization-projection.unit.test.ts +325 -0
- package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
- package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
- package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
- package/src/__tests__/field-shape-fixture.test.ts +61 -0
- package/src/__tests__/field-transient-reflection.test.ts +59 -0
- package/src/__tests__/field-value-type.test-d.ts +57 -0
- package/src/__tests__/fixed-update.unit.test.ts +78 -0
- package/src/__tests__/handle.test-d.ts +86 -0
- package/src/__tests__/hierarchy.unit.test.ts +2534 -0
- package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
- package/src/__tests__/minimal-example.test-d.ts +42 -0
- package/src/__tests__/name-read-types.test-d.ts +72 -0
- package/src/__tests__/plugin-service.test.ts +13 -0
- package/src/__tests__/query-combinations.unit.test.ts +42 -0
- package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
- package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
- package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
- package/src/__tests__/query.unit.test.ts +196 -0
- package/src/__tests__/relationship-index.perf.test.ts +126 -0
- package/src/__tests__/relationship-index.test.ts +70 -0
- package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
- package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
- package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
- package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
- package/src/__tests__/schedule-token.unit.test.ts +54 -0
- package/src/__tests__/schema-vocab.test-d.ts +103 -0
- package/src/__tests__/serialization.unit.test.ts +2740 -0
- package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
- package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
- package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
- package/src/__tests__/sparse-tag.unit.test.ts +201 -0
- package/src/__tests__/spawn-field-error.test-d.ts +41 -0
- package/src/__tests__/stale-error.test-d.ts +208 -0
- package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
- package/src/__tests__/string-vocab.test-d.ts +49 -0
- package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
- package/src/__tests__/structure-epoch.unit.test.ts +48 -0
- package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
- package/src/__tests__/time.unit.test.ts +79 -0
- package/src/__tests__/types.test-d.ts +38 -0
- package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
- package/src/__tests__/utils/handle-numeric.ts +30 -0
- package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
- package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
- package/src/__tests__/world-change-journal.unit.test.ts +97 -0
- package/src/__tests__/world-characterization.unit.test.ts +408 -0
- package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
- package/src/__tests__/world-health.contract.test.ts +87 -0
- package/src/__tests__/world-inspection.test.ts +37 -0
- package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
- package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
- package/src/__tests__/world-scheduling.m4.test.ts +52 -0
- package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
- package/src/__tests__/world.test-d.ts +156 -0
- package/src/buffer-pool.ts +357 -0
- package/src/commands.ts +644 -0
- package/src/component-default-fallback.ts +236 -0
- package/src/component-schema.ts +92 -0
- package/src/component-value-validate.ts +135 -0
- package/src/component.ts +1268 -0
- package/src/entity-handle.ts +104 -0
- package/src/entity.ts +140 -0
- package/src/errors/query-and-component-errors.ts +200 -0
- package/src/errors/relationship-errors.ts +198 -0
- package/src/errors/sprite-and-shared-errors.ts +231 -0
- package/src/errors/validation-errors.ts +506 -0
- package/src/errors.ts +1464 -0
- package/src/execution/shared-kernel.ts +392 -0
- package/src/externalization/index.ts +170 -0
- package/src/index.ts +414 -0
- package/src/plugin-service.ts +35 -0
- package/src/projection/index.ts +318 -0
- package/src/query/query.ts +606 -0
- package/src/relationship-index.ts +175 -0
- package/src/resource.ts +106 -0
- package/src/schedule-token.ts +40 -0
- package/src/schedule.ts +846 -0
- package/src/shared-ref-store.ts +418 -0
- package/src/shared.ts +23 -0
- package/src/storage/archetype-graph.ts +166 -0
- package/src/storage/archetype.ts +78 -0
- package/src/storage/change-detection.ts +231 -0
- package/src/storage/column.ts +247 -0
- package/src/storage/table.ts +153 -0
- package/src/time.ts +122 -0
- package/src/unique-ref-store.ts +278 -0
- package/src/world-change-journal.ts +84 -0
- package/src/world-component-access.ts +1421 -0
- package/src/world-component-storage.ts +1178 -0
- package/src/world-core.ts +74 -0
- package/src/world-entity-lifecycle.ts +539 -0
- package/src/world-internal.ts +75 -0
- package/src/world-scheduling.ts +499 -0
- package/src/world.ts +1399 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// @forgeax/engine-ecs — per-entity and per-resource change ticks.
|
|
2
|
+
|
|
3
|
+
import type { Component } from '../component';
|
|
4
|
+
import * as componentOwner from '../component';
|
|
5
|
+
import { Entity } from '../entity';
|
|
6
|
+
import { type EntityHandle, entityIndex } from '../entity-handle';
|
|
7
|
+
|
|
8
|
+
export { WorldChangeJournal, type WorldChangeRead } from '../world-change-journal';
|
|
9
|
+
|
|
10
|
+
import type { ArchetypeGraph } from './archetype-graph';
|
|
11
|
+
import { getOrCreateSparseTagSet } from './archetype-graph';
|
|
12
|
+
import type { Table } from './table';
|
|
13
|
+
|
|
14
|
+
const INITIAL_SPARSE_CAPACITY = 64;
|
|
15
|
+
|
|
16
|
+
export interface ComponentEpochColumns {
|
|
17
|
+
added: Float64Array;
|
|
18
|
+
changed: Float64Array;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createComponentEpochColumns(capacity: number): ComponentEpochColumns {
|
|
22
|
+
return { added: new Float64Array(capacity), changed: new Float64Array(capacity) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function growComponentEpochColumns(
|
|
26
|
+
columns: ComponentEpochColumns,
|
|
27
|
+
capacity: number,
|
|
28
|
+
): ComponentEpochColumns {
|
|
29
|
+
const added = new Float64Array(capacity);
|
|
30
|
+
const changed = new Float64Array(capacity);
|
|
31
|
+
added.set(columns.added);
|
|
32
|
+
changed.set(columns.changed);
|
|
33
|
+
return { added, changed };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function copyComponentEpoch(
|
|
37
|
+
source: ComponentEpochColumns,
|
|
38
|
+
sourceRow: number,
|
|
39
|
+
target: ComponentEpochColumns,
|
|
40
|
+
targetRow: number,
|
|
41
|
+
): void {
|
|
42
|
+
target.added[targetRow] = source.added[sourceRow] ?? 0;
|
|
43
|
+
target.changed[targetRow] = source.changed[sourceRow] ?? 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SparseTagSet {
|
|
47
|
+
readonly component: Component;
|
|
48
|
+
sparse: Int32Array;
|
|
49
|
+
dense: Uint32Array;
|
|
50
|
+
added: Float64Array;
|
|
51
|
+
changed: Float64Array;
|
|
52
|
+
size: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createSparseTagSet(component: Component): SparseTagSet {
|
|
56
|
+
const sparse = new Int32Array(INITIAL_SPARSE_CAPACITY);
|
|
57
|
+
sparse.fill(-1);
|
|
58
|
+
return {
|
|
59
|
+
component,
|
|
60
|
+
sparse,
|
|
61
|
+
dense: new Uint32Array(INITIAL_SPARSE_CAPACITY),
|
|
62
|
+
added: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
63
|
+
changed: new Float64Array(INITIAL_SPARSE_CAPACITY),
|
|
64
|
+
size: 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sparseTagIndex(set: SparseTagSet, entity: EntityHandle): number {
|
|
69
|
+
const denseIndex = set.sparse[entityIndex(entity)] ?? -1;
|
|
70
|
+
return denseIndex >= 0 && set.dense[denseIndex] === (entity as number) ? denseIndex : -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function sparseTagHas(set: SparseTagSet, entity: EntityHandle): boolean {
|
|
74
|
+
return sparseTagIndex(set, entity) >= 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function insertSparseTag(set: SparseTagSet, entity: EntityHandle, epoch: number): number {
|
|
78
|
+
const present = sparseTagIndex(set, entity);
|
|
79
|
+
if (present >= 0) {
|
|
80
|
+
set.changed[present] = epoch;
|
|
81
|
+
return present;
|
|
82
|
+
}
|
|
83
|
+
growSparseSlots(set, entityIndex(entity) + 1);
|
|
84
|
+
if (set.size === set.dense.length) growSparseDense(set, set.size + 1);
|
|
85
|
+
const denseIndex = set.size;
|
|
86
|
+
set.dense[denseIndex] = entity as number;
|
|
87
|
+
set.added[denseIndex] = epoch;
|
|
88
|
+
set.changed[denseIndex] = epoch;
|
|
89
|
+
set.sparse[entityIndex(entity)] = denseIndex;
|
|
90
|
+
set.size += 1;
|
|
91
|
+
return denseIndex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function removeSparseTag(set: SparseTagSet, entity: EntityHandle): boolean {
|
|
95
|
+
const denseIndex = sparseTagIndex(set, entity);
|
|
96
|
+
if (denseIndex < 0) return false;
|
|
97
|
+
const lastIndex = set.size - 1;
|
|
98
|
+
set.sparse[entityIndex(entity)] = -1;
|
|
99
|
+
if (denseIndex !== lastIndex) {
|
|
100
|
+
const movedEntity = set.dense[lastIndex] as EntityHandle;
|
|
101
|
+
set.dense[denseIndex] = movedEntity as number;
|
|
102
|
+
set.added[denseIndex] = set.added[lastIndex] ?? 0;
|
|
103
|
+
set.changed[denseIndex] = set.changed[lastIndex] ?? 0;
|
|
104
|
+
set.sparse[entityIndex(movedEntity)] = denseIndex;
|
|
105
|
+
}
|
|
106
|
+
set.size = lastIndex;
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function growSparseSlots(set: SparseTagSet, targetCapacity: number): void {
|
|
111
|
+
if (targetCapacity <= set.sparse.length) return;
|
|
112
|
+
let capacity = set.sparse.length;
|
|
113
|
+
while (capacity < targetCapacity) capacity *= 2;
|
|
114
|
+
const sparse = new Int32Array(capacity);
|
|
115
|
+
sparse.fill(-1);
|
|
116
|
+
sparse.set(set.sparse);
|
|
117
|
+
set.sparse = sparse;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function growSparseDense(set: SparseTagSet, targetCapacity: number): void {
|
|
121
|
+
let capacity = set.dense.length;
|
|
122
|
+
while (capacity < targetCapacity) capacity *= 2;
|
|
123
|
+
const dense = new Uint32Array(capacity);
|
|
124
|
+
dense.set(set.dense);
|
|
125
|
+
set.dense = dense;
|
|
126
|
+
const added = new Float64Array(capacity);
|
|
127
|
+
added.set(set.added);
|
|
128
|
+
set.added = added;
|
|
129
|
+
const changed = new Float64Array(capacity);
|
|
130
|
+
changed.set(set.changed);
|
|
131
|
+
set.changed = changed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ChangeTicks {
|
|
135
|
+
added: number;
|
|
136
|
+
changed: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const NEVER_CHANGED_TICK = -1;
|
|
140
|
+
|
|
141
|
+
export function createChangeTicks(tick: number): ChangeTicks {
|
|
142
|
+
return { added: tick, changed: tick };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface EntityLocation {
|
|
146
|
+
readonly archetypeId: number;
|
|
147
|
+
readonly archetypeRow: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function readComponentChange(
|
|
151
|
+
graph: ArchetypeGraph,
|
|
152
|
+
location: EntityLocation,
|
|
153
|
+
entity: EntityHandle,
|
|
154
|
+
componentId: number,
|
|
155
|
+
): ChangeTicks | undefined {
|
|
156
|
+
const sparseSet = graph.sparseTags.get(componentId);
|
|
157
|
+
if (sparseSet !== undefined) {
|
|
158
|
+
const denseIndex = sparseTagIndex(sparseSet, entity);
|
|
159
|
+
if (denseIndex < 0) return undefined;
|
|
160
|
+
return {
|
|
161
|
+
added: sparseSet.added[denseIndex] ?? 0,
|
|
162
|
+
changed: sparseSet.changed[denseIndex] ?? 0,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
166
|
+
if (archetype === undefined) return undefined;
|
|
167
|
+
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId)?.epochs;
|
|
168
|
+
if (epochs === undefined) return undefined;
|
|
169
|
+
const tableRow = archetype.rows[location.archetypeRow] ?? -1;
|
|
170
|
+
return {
|
|
171
|
+
added: epochs.added[tableRow] ?? 0,
|
|
172
|
+
changed: epochs.changed[tableRow] ?? 0,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function markComponentsAdded(
|
|
177
|
+
graph: ArchetypeGraph,
|
|
178
|
+
location: EntityLocation,
|
|
179
|
+
entity: EntityHandle,
|
|
180
|
+
componentIds: readonly number[],
|
|
181
|
+
epoch: number,
|
|
182
|
+
): void {
|
|
183
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
184
|
+
const table = archetype === undefined ? undefined : graph.tables[archetype.tableId];
|
|
185
|
+
const tableRow = archetype?.rows[location.archetypeRow] ?? -1;
|
|
186
|
+
for (const componentId of componentIds) {
|
|
187
|
+
const component = archetype?.components.find(
|
|
188
|
+
(candidate) => componentOwner.componentId(candidate) === componentId,
|
|
189
|
+
);
|
|
190
|
+
if (component?.storage === 'sparse') {
|
|
191
|
+
insertSparseTag(getOrCreateSparseTagSet(graph, component), entity, epoch);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const epochs = table?.storage.get(componentId)?.epochs;
|
|
195
|
+
if (epochs === undefined) continue;
|
|
196
|
+
epochs.added[tableRow] = epoch;
|
|
197
|
+
epochs.changed[tableRow] = epoch;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function markComponentChanged(
|
|
202
|
+
graph: ArchetypeGraph,
|
|
203
|
+
location: EntityLocation,
|
|
204
|
+
entity: EntityHandle,
|
|
205
|
+
componentId: number,
|
|
206
|
+
epoch: () => number,
|
|
207
|
+
): void {
|
|
208
|
+
const sparseSet = graph.sparseTags.get(componentId);
|
|
209
|
+
if (sparseSet !== undefined) {
|
|
210
|
+
const denseIndex = sparseTagIndex(sparseSet, entity);
|
|
211
|
+
if (denseIndex >= 0) sparseSet.changed[denseIndex] = epoch();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const archetype = graph.archetypes[location.archetypeId];
|
|
215
|
+
if (archetype === undefined) return;
|
|
216
|
+
const epochs = graph.tables[archetype.tableId]?.storage.get(componentId)?.epochs;
|
|
217
|
+
if (epochs === undefined) return;
|
|
218
|
+
const tableRow = archetype.rows[location.archetypeRow] ?? -1;
|
|
219
|
+
epochs.changed[tableRow] = epoch();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function readTableEntityRange(
|
|
223
|
+
table: Table,
|
|
224
|
+
rowStart: number,
|
|
225
|
+
rowCount: number,
|
|
226
|
+
): readonly EntityHandle[] {
|
|
227
|
+
const entities = table.storage.get(componentOwner.componentId(Entity))?.fields.get('self')
|
|
228
|
+
?.view as Uint32Array | undefined;
|
|
229
|
+
if (entities === undefined) return [];
|
|
230
|
+
return entities.subarray(rowStart, rowStart + rowCount) as unknown as readonly EntityHandle[];
|
|
231
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// @forgeax/engine-ecs — per-field independent buffer column storage (D-01).
|
|
2
|
+
//
|
|
3
|
+
// Each component field gets its own ArrayBuffer/SharedArrayBuffer + TypedArray view.
|
|
4
|
+
// This enables independent per-column transfer() expansion and
|
|
5
|
+
// supports dynamic column add/remove during archetype migration.
|
|
6
|
+
//
|
|
7
|
+
// `array<T>` (variable-capacity) fields take TWO columns in archetype layout
|
|
8
|
+
// (D-3): the primary u32 column carries the BufferPool slot id; a sidecar
|
|
9
|
+
// column carries the live element count, named `<fieldName>:count` and
|
|
10
|
+
// keyed by `arrayCountColumnName(fieldName)`. `array<T,N>` (fixed-capacity)
|
|
11
|
+
// fields (feat-20260602) take a SINGLE inline stride-N column (arity = N) --
|
|
12
|
+
// no BufferPool slot id, no sidecar count column. The elements live
|
|
13
|
+
// contiguously per row. `buffer<N>` likewise takes a single stride-N u8
|
|
14
|
+
// column. Variable `buffer` still uses a u32 BufferPool slot id column.
|
|
15
|
+
// Capacity is never stored as an independent column: for variable-capacity
|
|
16
|
+
// fields it is derived from `BufferPool.view(slotId).byteLength /
|
|
17
|
+
// elementBytes`; for fixed-capacity fields the schema-declared N is the
|
|
18
|
+
// capacity.
|
|
19
|
+
|
|
20
|
+
import { type ComponentSchema, type ScalarFieldType, TYPE_METADATA } from '../component';
|
|
21
|
+
|
|
22
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// ManagedColumnReader — read-only view onto a managed-vocab column
|
|
24
|
+
// (feat-20260614 M4 / D-4 / AC-08 / AC-09 / AC-10)
|
|
25
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read-only access shape for the 4 managed-vocab column keywords --
|
|
29
|
+
* `'string'` / `` `ref<T>` `` / variable `'buffer'` / variable `` `array<T>` ``.
|
|
30
|
+
*
|
|
31
|
+
* The column carries u32 slot ids (`UniqueRefStore` / `BufferPool`); direct
|
|
32
|
+
* index assignment would corrupt the slot table. The reader exposes
|
|
33
|
+
* `.length` + `.get(i): number` so consumers can walk row indices and
|
|
34
|
+
* retrieve slot ids, then route through the public dispatch (`world.set`,
|
|
35
|
+
* `world.push`, `world.allocUniqueRef`, etc.) for any mutation.
|
|
36
|
+
*
|
|
37
|
+
* Phantom brand `__managed` (D-7 / requirements-decisions q2): the
|
|
38
|
+
* double-underscore + semantic-word pattern matches `Handle.__handle`. The
|
|
39
|
+
* brand is a structural-typing safety net; the class name already
|
|
40
|
+
* disambiguates against `Uint32Array` / `Uint8Array` / fixed inline views.
|
|
41
|
+
*
|
|
42
|
+
* Intentionally minimal -- no iterator, no `toArray`, no `slice`. Adding
|
|
43
|
+
* those invites a "cache the slot ids, mutate later" anti-pattern that
|
|
44
|
+
* silently desyncs from the live column on the next archetype migration
|
|
45
|
+
* (charter P4 / plan-strategy §8.4).
|
|
46
|
+
*/
|
|
47
|
+
export interface ManagedColumnReader<T extends string> {
|
|
48
|
+
readonly length: number;
|
|
49
|
+
get(i: number): number;
|
|
50
|
+
readonly __managed: T;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sidecar column name for the live `count` of an `array<T>` (variable-
|
|
55
|
+
* capacity) field. Format `<fieldName>:count` keeps the suffix outside the
|
|
56
|
+
* legal user-facing schema name space (the colon is forbidden in user
|
|
57
|
+
* field names by convention) so the archetype Map can carry both columns
|
|
58
|
+
* under one `Map<fieldName, Column>` keyed namespace without collision
|
|
59
|
+
* (D-3 double-column allocation in M1 / w7).
|
|
60
|
+
*/
|
|
61
|
+
export function arrayCountColumnName(fieldName: string): string {
|
|
62
|
+
return `${fieldName}:count`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// Column: one field's storage
|
|
67
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** TypedArray view types used by columns. */
|
|
70
|
+
export type FieldView =
|
|
71
|
+
| Float32Array
|
|
72
|
+
| Float64Array
|
|
73
|
+
| Int32Array
|
|
74
|
+
| Uint32Array
|
|
75
|
+
| Int16Array
|
|
76
|
+
| Uint16Array
|
|
77
|
+
| Int8Array
|
|
78
|
+
| Uint8Array;
|
|
79
|
+
|
|
80
|
+
type FieldViewConstructor = new (buffer: ArrayBufferLike) => FieldView;
|
|
81
|
+
|
|
82
|
+
function createFieldView(
|
|
83
|
+
Ctor: NonNullable<(typeof TYPE_METADATA)[string]['viewCtor']>,
|
|
84
|
+
buffer: ArrayBufferLike,
|
|
85
|
+
): FieldView {
|
|
86
|
+
return new (Ctor as FieldViewConstructor)(buffer);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isSharedBuffer(buffer: ArrayBufferLike): buffer is SharedArrayBuffer {
|
|
90
|
+
return typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A single column: one field's ArrayBuffer + TypedArray view. */
|
|
94
|
+
export interface Column {
|
|
95
|
+
buffer: ArrayBuffer | SharedArrayBuffer;
|
|
96
|
+
view: FieldView;
|
|
97
|
+
capacity: number;
|
|
98
|
+
fieldType: ScalarFieldType;
|
|
99
|
+
/**
|
|
100
|
+
* Number of elements per row (stride). Scalar and variable-capacity columns
|
|
101
|
+
* default to 1 (one u32 slot id or one scalar value per row). Fixed-capacity
|
|
102
|
+
* `array<T,N>` / `buffer<N>` inline columns (feat-20260602) have arity = N.
|
|
103
|
+
*
|
|
104
|
+
* For inline columns, the TypedArray view returned by `col.view.subarray()`
|
|
105
|
+
* aliases the column buffer directly. The view is transient: `growColumn`
|
|
106
|
+
* detaches the old `ArrayBuffer` via `transfer()`, so callers must re-fetch
|
|
107
|
+
* the view after any structural change. See `packages/ecs/README.md`
|
|
108
|
+
* Transient view contract section.
|
|
109
|
+
*/
|
|
110
|
+
arity: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create a column for a single field with given capacity.
|
|
115
|
+
*
|
|
116
|
+
* @param fieldType - scalar storage type for the column view ctor
|
|
117
|
+
* @param capacity - number of rows
|
|
118
|
+
* @param arity - elements per row (stride); default 1 for scalar/variable columns
|
|
119
|
+
*/
|
|
120
|
+
export function createColumn(
|
|
121
|
+
fieldType: ScalarFieldType,
|
|
122
|
+
capacity: number,
|
|
123
|
+
arity = 1,
|
|
124
|
+
shared = false,
|
|
125
|
+
): Column {
|
|
126
|
+
const meta = TYPE_METADATA[fieldType];
|
|
127
|
+
if (!meta) throw new Error(`Missing TYPE_METADATA entry for scalar field type ${fieldType}`);
|
|
128
|
+
const bytesPerElement =
|
|
129
|
+
// biome-ignore lint/style/noNonNullAssertion: ScalarFieldType rows always carry a byteSize
|
|
130
|
+
meta.byteSize!;
|
|
131
|
+
const buffer = shared
|
|
132
|
+
? new SharedArrayBuffer(bytesPerElement * capacity * arity)
|
|
133
|
+
: new ArrayBuffer(bytesPerElement * capacity * arity);
|
|
134
|
+
// biome-ignore lint/style/noNonNullAssertion: ScalarFieldType rows always carry a viewCtor
|
|
135
|
+
const Ctor = meta.viewCtor!;
|
|
136
|
+
const view = createFieldView(Ctor, buffer);
|
|
137
|
+
return { buffer, view, capacity, fieldType, arity };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Module-level cache for `ArrayBuffer.prototype.transfer` support (O-1).
|
|
142
|
+
* Checked once at import time; avoids per-call feature detection overhead.
|
|
143
|
+
*
|
|
144
|
+
* Note: `transfer()` is ES2024. We use runtime feature detection + type cast
|
|
145
|
+
* so that tsconfig does not need `lib: ["es2024"]`.
|
|
146
|
+
*/
|
|
147
|
+
export const HAS_TRANSFER: boolean =
|
|
148
|
+
typeof (ArrayBuffer.prototype as { transfer?: unknown }).transfer === 'function';
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Grow a column to a new capacity, preserving existing data.
|
|
152
|
+
* Returns a new Column; the old column is not modified.
|
|
153
|
+
*
|
|
154
|
+
* When the runtime supports `ArrayBuffer.transfer()` (ES2024, Node 22+,
|
|
155
|
+
* modern browsers), uses zero-copy buffer transfer. Otherwise falls back
|
|
156
|
+
* to `new ArrayBuffer` + `Uint8Array.set` copy.
|
|
157
|
+
*
|
|
158
|
+
* Both paths produce data-equivalent results (O-1 acceptance criterion).
|
|
159
|
+
*/
|
|
160
|
+
export function growColumn(col: Column, newCapacity: number): Column {
|
|
161
|
+
const meta = TYPE_METADATA[col.fieldType];
|
|
162
|
+
if (!meta) throw new Error(`Missing TYPE_METADATA entry for scalar field type ${col.fieldType}`);
|
|
163
|
+
const bytesPerElement =
|
|
164
|
+
// biome-ignore lint/style/noNonNullAssertion: ScalarFieldType rows always carry a byteSize
|
|
165
|
+
meta.byteSize!;
|
|
166
|
+
const newByteLength = bytesPerElement * newCapacity * col.arity;
|
|
167
|
+
// biome-ignore lint/style/noNonNullAssertion: ScalarFieldType rows always carry a viewCtor
|
|
168
|
+
const Ctor = meta.viewCtor!;
|
|
169
|
+
|
|
170
|
+
let buffer: ArrayBuffer | SharedArrayBuffer;
|
|
171
|
+
if (isSharedBuffer(col.buffer)) {
|
|
172
|
+
buffer = new SharedArrayBuffer(newByteLength);
|
|
173
|
+
new Uint8Array(buffer).set(new Uint8Array(col.buffer));
|
|
174
|
+
} else if (HAS_TRANSFER) {
|
|
175
|
+
// Zero-copy path: transfer() resizes the underlying buffer in-place
|
|
176
|
+
// and detaches the source. The old column's buffer becomes detached,
|
|
177
|
+
// but we return a new Column object so callers never touch the old one.
|
|
178
|
+
buffer = (col.buffer as unknown as { transfer(newByteLength: number): ArrayBuffer }).transfer(
|
|
179
|
+
newByteLength,
|
|
180
|
+
);
|
|
181
|
+
} /* istanbul ignore next -- fallback for runtimes without ES2024 transfer() */ else {
|
|
182
|
+
// Fallback path: allocate new buffer + byte-level copy.
|
|
183
|
+
buffer = new ArrayBuffer(newByteLength);
|
|
184
|
+
new Uint8Array(buffer).set(new Uint8Array(col.buffer));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const view = createFieldView(Ctor, buffer);
|
|
188
|
+
return { buffer, view, capacity: newCapacity, fieldType: col.fieldType, arity: col.arity };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
192
|
+
// Buffer-write input normalization (feat-20260621 V2 / AC-A4)
|
|
193
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Normalize a `'buffer'` / `'buffer<N>'` write payload to a `Uint8Array` view
|
|
197
|
+
* over its raw bytes. The write surface (`FieldInputType<'buffer'>`) accepts
|
|
198
|
+
* any `AllowSharedBufferSource` (Float32Array / ArrayBuffer / Uint8Array / any
|
|
199
|
+
* TypedArray); the column store and read side speak `Uint8Array` only. This is
|
|
200
|
+
* the single ingestion point that bridges the two — a strict superset of the
|
|
201
|
+
* prior Uint8Array-only behavior (an existing `Uint8Array` caller round-trips
|
|
202
|
+
* through `new Uint8Array(buffer, byteOffset, byteLength)`, same bytes, same
|
|
203
|
+
* length), so existing callers see zero behavior change.
|
|
204
|
+
*
|
|
205
|
+
* Returns `null` for non-buffer-source inputs (e.g. a forced cast feeding a
|
|
206
|
+
* number) so callers keep their existing "no-op / alloc(0)" branch for raw
|
|
207
|
+
* that is not a real buffer view.
|
|
208
|
+
*/
|
|
209
|
+
export function normalizeBufferWrite(raw: unknown): Uint8Array | null {
|
|
210
|
+
if (raw instanceof Uint8Array) {
|
|
211
|
+
return raw;
|
|
212
|
+
}
|
|
213
|
+
if (ArrayBuffer.isView(raw)) {
|
|
214
|
+
return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
215
|
+
}
|
|
216
|
+
if (raw instanceof ArrayBuffer) {
|
|
217
|
+
return new Uint8Array(raw);
|
|
218
|
+
}
|
|
219
|
+
if (typeof SharedArrayBuffer !== 'undefined' && raw instanceof SharedArrayBuffer) {
|
|
220
|
+
return new Uint8Array(raw);
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
226
|
+
// Hot/cold classification
|
|
227
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Determine if a schema is "hot" (all fields are scalar → TypedArray SoA).
|
|
231
|
+
* Returns true if all fields are in the scalar field type set.
|
|
232
|
+
* Empty schema (tag component) returns true by convention (vacuously true).
|
|
233
|
+
*
|
|
234
|
+
* Schema-vocab keywords (`buffer:<N>` / `ref<T>` / `handle<T>` / `entity` /
|
|
235
|
+
* `array<T,N>` / `array<T>`) are explicitly cold — their storage is owned by
|
|
236
|
+
* separate subsystems (UniqueRefStore / BufferPool), not the column SoA path. The keyword check uses an unsafe narrowing because
|
|
237
|
+
* `SchemaFieldType` is wider than the set's `ScalarFieldType` element type;
|
|
238
|
+
* the `has` test is the runtime gate.
|
|
239
|
+
*/
|
|
240
|
+
export function isHotSchema(schema: ComponentSchema): boolean {
|
|
241
|
+
for (const fieldType of Object.values(schema)) {
|
|
242
|
+
if (!TYPE_METADATA[fieldType]?.isScalar) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bufferFieldByteLength,
|
|
3
|
+
type Component,
|
|
4
|
+
type ComponentId,
|
|
5
|
+
componentId,
|
|
6
|
+
componentSchema,
|
|
7
|
+
fieldTypeToMetaKey,
|
|
8
|
+
isManagedBufferField,
|
|
9
|
+
parseManagedArraySchema,
|
|
10
|
+
type ScalarFieldType,
|
|
11
|
+
TYPE_METADATA,
|
|
12
|
+
} from '../component';
|
|
13
|
+
import { Entity, foldEssentials } from '../entity';
|
|
14
|
+
import type { EntityHandle } from '../entity-handle';
|
|
15
|
+
import {
|
|
16
|
+
type ComponentEpochColumns,
|
|
17
|
+
copyComponentEpoch,
|
|
18
|
+
createComponentEpochColumns,
|
|
19
|
+
growComponentEpochColumns,
|
|
20
|
+
} from './change-detection';
|
|
21
|
+
import { arrayCountColumnName, type Column, createColumn, growColumn } from './column';
|
|
22
|
+
|
|
23
|
+
const INITIAL_CAPACITY = 64;
|
|
24
|
+
|
|
25
|
+
export type TableId = number;
|
|
26
|
+
|
|
27
|
+
export interface TableComponentStorage {
|
|
28
|
+
readonly component: Component;
|
|
29
|
+
fields: Map<string, Column>;
|
|
30
|
+
epochs: ComponentEpochColumns;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Table {
|
|
34
|
+
readonly id: TableId;
|
|
35
|
+
readonly key: string;
|
|
36
|
+
readonly components: ReadonlyArray<Component>;
|
|
37
|
+
storage: Map<ComponentId, TableComponentStorage>;
|
|
38
|
+
size: number;
|
|
39
|
+
capacity: number;
|
|
40
|
+
version: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function tableKey(componentIds: ReadonlyArray<ComponentId>): string {
|
|
44
|
+
return [...foldEssentials(componentIds)].sort((a, b) => a - b).join('+');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function canonicalComponents(components: ReadonlyArray<Component>): Component[] {
|
|
48
|
+
const byId = new Map<ComponentId, Component>();
|
|
49
|
+
byId.set(componentId(Entity), Entity as unknown as Component);
|
|
50
|
+
for (const component of components) byId.set(componentId(component), component);
|
|
51
|
+
return [...byId.values()].sort((a, b) => componentId(a) - componentId(b));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createTable(
|
|
55
|
+
components: ReadonlyArray<Component>,
|
|
56
|
+
id: TableId,
|
|
57
|
+
shared = false,
|
|
58
|
+
): Table {
|
|
59
|
+
const sortedComponents = canonicalComponents(components);
|
|
60
|
+
const capacity = INITIAL_CAPACITY;
|
|
61
|
+
const storage = new Map<ComponentId, TableComponentStorage>();
|
|
62
|
+
for (const component of sortedComponents) {
|
|
63
|
+
const fields = new Map<string, Column>();
|
|
64
|
+
for (const [fieldName, fieldType] of Object.entries(componentSchema(component))) {
|
|
65
|
+
const arrayMeta = parseManagedArraySchema(fieldType);
|
|
66
|
+
if (arrayMeta !== null && arrayMeta.length !== undefined) {
|
|
67
|
+
const metaKey = fieldTypeToMetaKey(arrayMeta.elementType);
|
|
68
|
+
const scalarType: ScalarFieldType | null =
|
|
69
|
+
metaKey === null ? null : (TYPE_METADATA[metaKey]?.storage ?? null);
|
|
70
|
+
if (scalarType !== null) {
|
|
71
|
+
fields.set(fieldName, createColumn(scalarType, capacity, arrayMeta.length, shared));
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (isManagedBufferField(fieldType) && fieldType !== 'buffer') {
|
|
76
|
+
fields.set(
|
|
77
|
+
fieldName,
|
|
78
|
+
createColumn('u8', capacity, bufferFieldByteLength(fieldType), shared),
|
|
79
|
+
);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const metaKey = fieldTypeToMetaKey(fieldType);
|
|
83
|
+
const scalarType: ScalarFieldType | null =
|
|
84
|
+
metaKey === null ? null : (TYPE_METADATA[metaKey]?.storage ?? null);
|
|
85
|
+
if (scalarType === null) continue;
|
|
86
|
+
fields.set(fieldName, createColumn(scalarType, capacity, 1, shared));
|
|
87
|
+
if (arrayMeta !== null && arrayMeta.length === undefined) {
|
|
88
|
+
fields.set(arrayCountColumnName(fieldName), createColumn('u32', capacity, 1, shared));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
storage.set(componentId(component), {
|
|
92
|
+
component,
|
|
93
|
+
fields,
|
|
94
|
+
epochs: createComponentEpochColumns(capacity),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
key: tableKey(sortedComponents.map((component) => componentId(component))),
|
|
100
|
+
components: sortedComponents,
|
|
101
|
+
storage,
|
|
102
|
+
size: 0,
|
|
103
|
+
capacity,
|
|
104
|
+
version: 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function appendTableRow(table: Table, entity: EntityHandle): number {
|
|
109
|
+
if (table.size === table.capacity) growTable(table, table.capacity * 2);
|
|
110
|
+
const row = table.size;
|
|
111
|
+
const self = table.storage.get(componentId(Entity))?.fields.get('self');
|
|
112
|
+
if (self !== undefined) self.view[row] = entity as number;
|
|
113
|
+
table.size = row + 1;
|
|
114
|
+
return row;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function removeTableRow(
|
|
118
|
+
table: Table,
|
|
119
|
+
row: number,
|
|
120
|
+
): { movedEntity: EntityHandle; newRow: number } | null {
|
|
121
|
+
const lastRow = table.size - 1;
|
|
122
|
+
if (row === lastRow) {
|
|
123
|
+
table.size = lastRow;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const movedEntity = (table.storage.get(componentId(Entity))?.fields.get('self')?.view[lastRow] ??
|
|
127
|
+
0) as EntityHandle;
|
|
128
|
+
for (const componentStorage of table.storage.values()) {
|
|
129
|
+
for (const column of componentStorage.fields.values()) {
|
|
130
|
+
const arity = column.arity;
|
|
131
|
+
column.view.set(column.view.subarray(lastRow * arity, lastRow * arity + arity), row * arity);
|
|
132
|
+
}
|
|
133
|
+
copyComponentEpoch(componentStorage.epochs, lastRow, componentStorage.epochs, row);
|
|
134
|
+
}
|
|
135
|
+
table.size = lastRow;
|
|
136
|
+
return { movedEntity, newRow: row };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function growTable(table: Table, targetCapacity: number): void {
|
|
140
|
+
let capacity = table.capacity;
|
|
141
|
+
while (capacity < targetCapacity) capacity *= 2;
|
|
142
|
+
if (capacity === table.capacity) return;
|
|
143
|
+
for (const componentStorage of table.storage.values()) {
|
|
144
|
+
const fields = new Map<string, Column>();
|
|
145
|
+
for (const [fieldName, column] of componentStorage.fields) {
|
|
146
|
+
fields.set(fieldName, growColumn(column, capacity));
|
|
147
|
+
}
|
|
148
|
+
componentStorage.fields = fields;
|
|
149
|
+
componentStorage.epochs = growComponentEpochColumns(componentStorage.epochs, capacity);
|
|
150
|
+
}
|
|
151
|
+
table.capacity = capacity;
|
|
152
|
+
table.version += 1;
|
|
153
|
+
}
|