@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
package/src/time.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export interface TimeResource {
|
|
2
|
+
readonly delta: number;
|
|
3
|
+
readonly elapsed: number;
|
|
4
|
+
readonly maxDeltaSeconds: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FixedTimeResource {
|
|
8
|
+
readonly delta: number;
|
|
9
|
+
readonly maxStepsPerUpdate: number;
|
|
10
|
+
readonly tick: number;
|
|
11
|
+
/** Seconds accumulated toward the next fixed update. */
|
|
12
|
+
readonly overstep: number;
|
|
13
|
+
readonly droppedSeconds: number;
|
|
14
|
+
readonly droppedUpdates: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type MutableTimeResource = { -readonly [K in keyof TimeResource]: TimeResource[K] };
|
|
18
|
+
export type MutableFixedTimeResource = {
|
|
19
|
+
-readonly [K in keyof FixedTimeResource]: FixedTimeResource[K];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Scheduler-only mutable clock capability; World never exposes this publicly. */
|
|
23
|
+
export interface ClockWriter {
|
|
24
|
+
readonly time: MutableTimeResource;
|
|
25
|
+
readonly fixed: MutableFixedTimeResource;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WorldClock {
|
|
29
|
+
readonly time: TimeResource;
|
|
30
|
+
readonly fixed: FixedTimeResource;
|
|
31
|
+
readonly writer: ClockWriter;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ResourceToken<T> {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly __resourceType?: T;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ResourceValue<Key> = Key extends ResourceToken<infer Value> ? Value : never;
|
|
40
|
+
|
|
41
|
+
export interface TimePolicy {
|
|
42
|
+
readonly fixedDeltaSeconds?: number;
|
|
43
|
+
readonly maxStepsPerUpdate?: number;
|
|
44
|
+
readonly maxDeltaSeconds?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface WorldOptions {
|
|
48
|
+
readonly time?: TimePolicy;
|
|
49
|
+
/** SAB-backed dense numeric columns for a SharedKernel-capable Engine Realm. */
|
|
50
|
+
readonly storage?: 'local' | 'shared';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** World-owned variable-rate clock resource key. */
|
|
54
|
+
export const Time = Object.freeze({ name: 'Time' }) as ResourceToken<TimeResource> & TimeResource;
|
|
55
|
+
/** World-owned fixed-rate clock, policy, and catch-up metric resource key. */
|
|
56
|
+
export const FixedTime = Object.freeze({ name: 'FixedTime' }) as ResourceToken<FixedTimeResource> &
|
|
57
|
+
FixedTimeResource;
|
|
58
|
+
|
|
59
|
+
export const TIME_RESOURCE_KEY = Time.name;
|
|
60
|
+
export const FIXED_TIME_RESOURCE_KEY = FixedTime.name;
|
|
61
|
+
|
|
62
|
+
export const DEFAULT_TIME_POLICY: Required<TimePolicy> = {
|
|
63
|
+
fixedDeltaSeconds: 1 / 60,
|
|
64
|
+
maxStepsPerUpdate: 4,
|
|
65
|
+
maxDeltaSeconds: 0.1,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function createTimeResource(policy: Required<TimePolicy>): TimeResource {
|
|
69
|
+
return { delta: 0, elapsed: 0, maxDeltaSeconds: policy.maxDeltaSeconds };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createFixedTimeResource(policy: Required<TimePolicy>): FixedTimeResource {
|
|
73
|
+
return {
|
|
74
|
+
delta: policy.fixedDeltaSeconds,
|
|
75
|
+
maxStepsPerUpdate: policy.maxStepsPerUpdate,
|
|
76
|
+
tick: 0,
|
|
77
|
+
overstep: 0,
|
|
78
|
+
droppedSeconds: 0,
|
|
79
|
+
droppedUpdates: 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createWorldClock(policy: Required<TimePolicy>): WorldClock {
|
|
84
|
+
const time = createTimeResource(policy) as MutableTimeResource;
|
|
85
|
+
const fixed = createFixedTimeResource(policy) as MutableFixedTimeResource;
|
|
86
|
+
const timeView: TimeResource = Object.freeze({
|
|
87
|
+
get delta() {
|
|
88
|
+
return time.delta;
|
|
89
|
+
},
|
|
90
|
+
get elapsed() {
|
|
91
|
+
return time.elapsed;
|
|
92
|
+
},
|
|
93
|
+
get maxDeltaSeconds() {
|
|
94
|
+
return time.maxDeltaSeconds;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const fixedView: FixedTimeResource = Object.freeze({
|
|
98
|
+
get delta() {
|
|
99
|
+
return fixed.delta;
|
|
100
|
+
},
|
|
101
|
+
get maxStepsPerUpdate() {
|
|
102
|
+
return fixed.maxStepsPerUpdate;
|
|
103
|
+
},
|
|
104
|
+
get tick() {
|
|
105
|
+
return fixed.tick;
|
|
106
|
+
},
|
|
107
|
+
get overstep() {
|
|
108
|
+
return fixed.overstep;
|
|
109
|
+
},
|
|
110
|
+
get droppedSeconds() {
|
|
111
|
+
return fixed.droppedSeconds;
|
|
112
|
+
},
|
|
113
|
+
get droppedUpdates() {
|
|
114
|
+
return fixed.droppedUpdates;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
time: timeView,
|
|
119
|
+
fixed: fixedView,
|
|
120
|
+
writer: { time, fixed },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// @forgeax/engine-ecs - UniqueRefStore.
|
|
2
|
+
//
|
|
3
|
+
// Per-slot singleton store for ECS-managed asset handles. Schema vocab
|
|
4
|
+
// `ref<T>` fields derive `Handle<T, 'unique'>` whose lifecycle is owned by
|
|
5
|
+
// the ECS - World hooks `despawn` / `removeComponent` / `set` into
|
|
6
|
+
// `UniqueRefStore.release` so dropping a holder entity reliably releases the
|
|
7
|
+
// referenced asset slot. AssetRegistry handles (`handle<T>` schema vocab) take
|
|
8
|
+
// the orthogonal `'shared'` mode and never enter this store.
|
|
9
|
+
//
|
|
10
|
+
// §contract — handles carry a generation; stale-by-reuse is detected
|
|
11
|
+
// The handle u32 packs `(generation << 24) | slot` via the shared codec in
|
|
12
|
+
// `@forgeax/engine-types` (the same SSOT codec ECS `EntityHandle` uses). The
|
|
13
|
+
// slot occupies the low 24 bits (capped at MAX_SLOT == ENTITY_MAX_INDEX);
|
|
14
|
+
// the generation occupies the high 8 bits. `alloc` welds the slot's current
|
|
15
|
+
// generation into the returned handle; `resolve` / `release` compare the
|
|
16
|
+
// handle's generation against the store's `_generations[slot]` BEFORE any
|
|
17
|
+
// payload work, so a handle whose slot was released and re-allocated is
|
|
18
|
+
// caught deterministically and returns `UniqueRefStaleError` ('unique-ref-
|
|
19
|
+
// stale') — it no longer silently resolves to the next payload.
|
|
20
|
+
//
|
|
21
|
+
// Generation advances on release and retires the slot when gen would exceed
|
|
22
|
+
// MAX_GEN (255) — gen 255 is still usable; the bump to 256 triggers retire —
|
|
23
|
+
// rather than wrapping, so a re-used slot can never collide with an old
|
|
24
|
+
// handle's generation (mirrors EntityHandle via the isRetiredSlot SSOT).
|
|
25
|
+
//
|
|
26
|
+
// AC-06 (unique-ref-double-release) remains payload-presence detected
|
|
27
|
+
// (`payloads.has(raw)`) for a handle whose generation still matches; the
|
|
28
|
+
// generation check catches the orthogonal stale-by-reuse case first.
|
|
29
|
+
//
|
|
30
|
+
// Storage shape:
|
|
31
|
+
// - `payloads: Map<number, unknown>` - key = handle u32 = slot index.
|
|
32
|
+
// Strong reference; deleted on release.
|
|
33
|
+
// Type-erased (the store never inspects
|
|
34
|
+
// payload fields); `Handle<T, 'unique'>`
|
|
35
|
+
// carries `T` at the type level and the
|
|
36
|
+
// single read site (World.get's ref path)
|
|
37
|
+
// narrows `unknown -> T` at the boundary.
|
|
38
|
+
// - `freeSlots: number[]` - LIFO stack of recyclable slot indices.
|
|
39
|
+
// - `releaseCallbacks: Map<...>` - per-slot onRelease cb cleared before fire.
|
|
40
|
+
// - `nextSlot` - bump counter for the never-recycled tail.
|
|
41
|
+
//
|
|
42
|
+
// 24 bits = 16_777_215 simultaneous live managed handles - the same ceiling
|
|
43
|
+
// as Entity, so an ECS that hit the entity ceiling cannot grow past the
|
|
44
|
+
// managed-ref ceiling either. Ceiling-exhaust handling is a future concern;
|
|
45
|
+
// the store deliberately panics on overflow to fail fast rather than
|
|
46
|
+
// silently wrap.
|
|
47
|
+
//
|
|
48
|
+
// Release path (D-1 codes):
|
|
49
|
+
// - resolve(h): err(UniqueRefReleasedError) if payload absent.
|
|
50
|
+
// - release(h): err(UniqueRefDoubleReleaseError) on second release.
|
|
51
|
+
//
|
|
52
|
+
// Object.is identity invariant (AC-04 prelude):
|
|
53
|
+
// resolve(h) returns the SAME payload object on every call until release
|
|
54
|
+
// - the store does not clone, wrap, or proxy the payload. M4 carry-over
|
|
55
|
+
// reuses this invariant: archetype migrate moves the row index, not the
|
|
56
|
+
// payload reference, so wrapper identity survives migration.
|
|
57
|
+
|
|
58
|
+
import type { Handle } from '@forgeax/engine-types';
|
|
59
|
+
import {
|
|
60
|
+
err,
|
|
61
|
+
handleGeneration,
|
|
62
|
+
handleSlot,
|
|
63
|
+
isRetiredSlot,
|
|
64
|
+
MAX_SLOT,
|
|
65
|
+
ok,
|
|
66
|
+
pack,
|
|
67
|
+
type Result,
|
|
68
|
+
toUnique,
|
|
69
|
+
unwrapHandle,
|
|
70
|
+
} from '@forgeax/engine-types';
|
|
71
|
+
import { UniqueRefDoubleReleaseError, UniqueRefReleasedError, UniqueRefStaleError } from './errors';
|
|
72
|
+
|
|
73
|
+
// MAX_SLOT is now imported from @forgeax/engine-types (codec SSOT, D-1).
|
|
74
|
+
// The local constant is removed to avoid drift (AC-15).
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Per-slot singleton store for ECS-managed `Handle<T, 'unique'>` lifecycles.
|
|
78
|
+
* World hooks the three release paths (despawn, removeComponent, set) into
|
|
79
|
+
* `release(handle)`; `resolve(handle)` is the read path AI users reach
|
|
80
|
+
* indirectly through `world.get(e, C)` for `ref<T>` schema fields.
|
|
81
|
+
*
|
|
82
|
+
* §contract — handles carry a generation; stale-by-reuse is detected: the
|
|
83
|
+
* handle u32 packs `(gen << 24) | slot` via the shared codec; `resolve` /
|
|
84
|
+
* `release` compare the handle generation against `_generations[slot]` and
|
|
85
|
+
* return `UniqueRefStaleError` ('unique-ref-stale') when a released slot has
|
|
86
|
+
* been re-allocated, instead of silently resolving to the next payload.
|
|
87
|
+
* README §"Managed handles carry a generation" is the canonical AI-user-
|
|
88
|
+
* facing statement.
|
|
89
|
+
*
|
|
90
|
+
* Identity invariant (AC-04 prelude): the payload returned by `resolve` is
|
|
91
|
+
* the same object reference on every call until `release`. Carry-over (M4)
|
|
92
|
+
* relies on this - archetype migrate moves the row index, not the payload.
|
|
93
|
+
*
|
|
94
|
+
* Storage is type-erased (`unknown`): the store routes lifecycle without ever
|
|
95
|
+
* inspecting payload fields. Per-call payload typing flows through method
|
|
96
|
+
* generics: `alloc<T>` accepts a `T` payload + matching `onRelease(T)`, and
|
|
97
|
+
* `resolve<T>` returns `Result<T, ...>`. Both narrow at the call boundary
|
|
98
|
+
* (the single read site is World.get's ref path); making the class generic
|
|
99
|
+
* on `T` was decoration with no internal use, so the parameter was dropped
|
|
100
|
+
* (architecture-principles.md §1 SSOT).
|
|
101
|
+
*/
|
|
102
|
+
export class UniqueRefStore {
|
|
103
|
+
private readonly payloads = new Map<number, unknown>();
|
|
104
|
+
private readonly freeSlots: number[] = [];
|
|
105
|
+
private readonly releaseCallbacks = new Map<number, ((payload: unknown) => void) | undefined>();
|
|
106
|
+
private nextSlot = 1;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generation table indexed by slot (D-6). Each entry tracks the current
|
|
110
|
+
* generation for the slot — written to during alloc (welded into the
|
|
111
|
+
* returned handle via pack) and incremented on release (M4).
|
|
112
|
+
*
|
|
113
|
+
* Slot 0 is the sentinel and always has gen=0 — it is never allocated
|
|
114
|
+
* and never incremented (R6).
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
// biome-ignore lint/style/useNamingConvention: internal field — @internal JSDoc suppresses lint:internal gate
|
|
119
|
+
private readonly _generations: number[] = [];
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Allocate a fresh managed handle for `payload`, branded against `target`.
|
|
123
|
+
*
|
|
124
|
+
* Slot 0 is reserved as the "null/unset" sentinel - schema-vocab `ref<T>`
|
|
125
|
+
* fields default to this sentinel before the first write, and World's
|
|
126
|
+
* release loop uses it to short-circuit (`shouldRelease(0) === false`).
|
|
127
|
+
*
|
|
128
|
+
* The returned handle carries a generation tag welded via codec.pack
|
|
129
|
+
* (D-8, OOS-2): first allocation gen=0 (AC-06), reused slot gen = the
|
|
130
|
+
* current generation from _generations[slot]. The toUnique brand cast
|
|
131
|
+
* happens internally — external callers no longer construct Handle<...>
|
|
132
|
+
* directly.
|
|
133
|
+
*
|
|
134
|
+
* @returns a `Handle<T, 'unique'>` u32. The branded number is safe to
|
|
135
|
+
* widen to `number` for GPU upload (charter consistent abstraction).
|
|
136
|
+
*/
|
|
137
|
+
alloc<Target extends string, T = unknown>(
|
|
138
|
+
target: Target,
|
|
139
|
+
payload: T,
|
|
140
|
+
onRelease?: (payload: T) => void,
|
|
141
|
+
): Handle<Target, 'unique'> {
|
|
142
|
+
void target; // target is a phantom - tag flows only at the type level via Handle<Target,_>.
|
|
143
|
+
const slot = this.freeSlots.pop() ?? this.nextSlot++;
|
|
144
|
+
if (slot > MAX_SLOT) {
|
|
145
|
+
throw new RangeError(
|
|
146
|
+
`UniqueRefStore: slot index ${slot} exceeds 24-bit max (${MAX_SLOT}). ` +
|
|
147
|
+
'Reduce simultaneous managed handles or investigate handle leaks.',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const gen = this._generations[slot] ?? 0;
|
|
151
|
+
const raw = pack(slot, gen);
|
|
152
|
+
this.payloads.set(raw, payload);
|
|
153
|
+
if (onRelease !== undefined) {
|
|
154
|
+
this.releaseCallbacks.set(raw, onRelease as (payload: unknown) => void);
|
|
155
|
+
}
|
|
156
|
+
return toUnique(raw);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Look up `payload` by handle. Returns `err(unique-ref-released)` when
|
|
161
|
+
* the handle's slot has no live payload (release happened in between, and
|
|
162
|
+
* no re-alloc has filled the slot). Resolves the same payload object on
|
|
163
|
+
* every call - identity-stable until release.
|
|
164
|
+
*
|
|
165
|
+
* §contract: a stale handle whose slot has been released and re-allocated
|
|
166
|
+
* returns `err(unique-ref-stale)` — the generation welded into the handle
|
|
167
|
+
* no longer matches `_generations[slot]`. The gen check runs before the
|
|
168
|
+
* payload lookup, so stale-by-reuse is caught deterministically — see
|
|
169
|
+
* README §"Managed handles carry a generation".
|
|
170
|
+
*
|
|
171
|
+
* `T` flows from the caller's `Handle<T, 'unique'>` type; the store erases
|
|
172
|
+
* payload types at the storage layer and the call boundary re-narrows.
|
|
173
|
+
*/
|
|
174
|
+
resolve<Target extends string, T = unknown>(
|
|
175
|
+
handle: Handle<Target, 'unique'>,
|
|
176
|
+
): Result<T, UniqueRefReleasedError | UniqueRefStaleError> {
|
|
177
|
+
const raw = unwrapHandle(handle);
|
|
178
|
+
// Gen comparison (M4): extract slot + handle gen, compare against
|
|
179
|
+
// store's current gen. Runs BEFORE payload lookup.
|
|
180
|
+
const slot = handleSlot(handle);
|
|
181
|
+
const handleGen = handleGeneration(handle);
|
|
182
|
+
const storeGen = this._generations[slot] ?? 0;
|
|
183
|
+
if (handleGen !== storeGen) {
|
|
184
|
+
return err(new UniqueRefStaleError(slot, handleGen, storeGen));
|
|
185
|
+
}
|
|
186
|
+
const payload = this.payloads.get(raw);
|
|
187
|
+
if (payload === undefined) {
|
|
188
|
+
// Slot 0 (sentinel) and any handle whose payload was already removed
|
|
189
|
+
// both surface as released. The phantom Target is unavailable at
|
|
190
|
+
// runtime - the error carries '<unknown>' to keep the structured
|
|
191
|
+
// shape consistent.
|
|
192
|
+
return err(new UniqueRefReleasedError(raw, '<unknown>'));
|
|
193
|
+
}
|
|
194
|
+
return ok(payload as T);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Release `handle` - drop the payload and push the slot onto the free list.
|
|
199
|
+
* Releasing the same handle twice surfaces `unique-ref-double-release`
|
|
200
|
+
* (D-1) so World's release loop can route the second release to Layer 3
|
|
201
|
+
* ErrorHandler without aborting the despawn chain. Detection is by
|
|
202
|
+
* payload-presence, not generation (§contract — managed handles are
|
|
203
|
+
* operational, not persistent).
|
|
204
|
+
*
|
|
205
|
+
* Releasing the slot-0 sentinel is a no-op - World's release loop calls
|
|
206
|
+
* `shouldRelease` first to filter sentinels.
|
|
207
|
+
*/
|
|
208
|
+
release<Target extends string>(
|
|
209
|
+
handle: Handle<Target, 'unique'>,
|
|
210
|
+
): Result<void, UniqueRefDoubleReleaseError | UniqueRefStaleError> {
|
|
211
|
+
const raw = unwrapHandle(handle);
|
|
212
|
+
// Gen comparison runs FIRST (q12: all operations compare gen first).
|
|
213
|
+
const slot = handleSlot(handle);
|
|
214
|
+
const handleGen = handleGeneration(handle);
|
|
215
|
+
const storeGen = this._generations[slot] ?? 0;
|
|
216
|
+
if (handleGen !== storeGen) {
|
|
217
|
+
// AC-03: stale release MUST NOT drop payload / fire callback / touch freeSlots.
|
|
218
|
+
return err(new UniqueRefStaleError(slot, handleGen, storeGen));
|
|
219
|
+
}
|
|
220
|
+
const payload = this.payloads.get(raw);
|
|
221
|
+
if (payload === undefined) {
|
|
222
|
+
return err(new UniqueRefDoubleReleaseError(raw, '<unknown>'));
|
|
223
|
+
}
|
|
224
|
+
// Order (feat-20260614 M1 D-1): clean up ALL store state BEFORE invoking
|
|
225
|
+
// onRelease. The payload is captured on the stack above so cb still
|
|
226
|
+
// observes the value (D-5 RAII-equivalent semantics preserved); both
|
|
227
|
+
// releaseCallbacks and payloads entries are dropped first, then the
|
|
228
|
+
// freelist push happens, and only then is cb invoked. A throwing cb
|
|
229
|
+
// leaves the store fully consistent: a second release sees
|
|
230
|
+
// `payloads.has(raw) === false` and returns UniqueRefDoubleReleaseError
|
|
231
|
+
// without re-firing the callback (AC-01, AC-02, AC-06). try/finally is
|
|
232
|
+
// explicitly NOT used (D-1 alternative (a) rejected — would swallow the
|
|
233
|
+
// first-release throw and violate spec section 3.1).
|
|
234
|
+
const cb = this.releaseCallbacks.get(raw);
|
|
235
|
+
this.releaseCallbacks.delete(raw);
|
|
236
|
+
this.payloads.delete(raw);
|
|
237
|
+
// Gen increment + retire (AC-07): bump gen; once it would exceed MAX_GEN
|
|
238
|
+
// (gen 255 is still usable; the bump to 256 triggers retire) the slot is
|
|
239
|
+
// permanently retired — NOT pushed to freeSlots.
|
|
240
|
+
this._generations[slot] = storeGen + 1;
|
|
241
|
+
if (!isRetiredSlot(this._generations[slot])) {
|
|
242
|
+
this.freeSlots.push(slot);
|
|
243
|
+
}
|
|
244
|
+
// else: slot retired (gen exceeded MAX_GEN) — never returns to freeSlots.
|
|
245
|
+
if (cb !== undefined) {
|
|
246
|
+
cb(payload);
|
|
247
|
+
}
|
|
248
|
+
return ok(undefined);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** @internal Replace the payload of a live slot without changing its handle. */
|
|
252
|
+
_setPayload<Target extends string, T>(handle: Handle<Target, 'unique'>, payload: T): void {
|
|
253
|
+
const raw = unwrapHandle(handle);
|
|
254
|
+
if (!this.payloads.has(raw)) {
|
|
255
|
+
throw new Error(`UniqueRefStore: cannot replace released payload ${raw}.`);
|
|
256
|
+
}
|
|
257
|
+
this.payloads.set(raw, payload);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* `true` if `handle` references a live slot - used by World's release loop
|
|
262
|
+
* to decide whether to call `release` (skips sentinel + already-released).
|
|
263
|
+
*
|
|
264
|
+
* The release loop differentiates "expected sentinel" (no error) from
|
|
265
|
+
* "leaked already-released handle" (`unique-ref-double-release`) at the
|
|
266
|
+
* caller layer; this helper handles only the no-op short-circuit.
|
|
267
|
+
*/
|
|
268
|
+
isLive<Target extends string>(handle: Handle<Target, 'unique'>): boolean {
|
|
269
|
+
const raw = unwrapHandle(handle);
|
|
270
|
+
if (raw === 0) return false;
|
|
271
|
+
return this.payloads.has(raw);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** @internal Diagnostic count of live slots. Exposed for tests + inspector. */
|
|
275
|
+
_liveCount(): number {
|
|
276
|
+
return this.payloads.size;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { EntityHandle } from './entity-handle';
|
|
2
|
+
|
|
3
|
+
export type WorldChangeKind =
|
|
4
|
+
| 'component-added'
|
|
5
|
+
| 'component-changed'
|
|
6
|
+
| 'component-removed'
|
|
7
|
+
| 'derived-component-changed'
|
|
8
|
+
| 'entity-removed';
|
|
9
|
+
|
|
10
|
+
export interface WorldChangeRecord {
|
|
11
|
+
readonly sequence: number;
|
|
12
|
+
readonly kind: WorldChangeKind;
|
|
13
|
+
readonly entity: EntityHandle;
|
|
14
|
+
readonly componentId?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type WorldChange = Omit<WorldChangeRecord, 'sequence'>;
|
|
18
|
+
|
|
19
|
+
export type WorldChangeRead =
|
|
20
|
+
| {
|
|
21
|
+
readonly status: 'ok';
|
|
22
|
+
readonly cursor: number;
|
|
23
|
+
readonly records: readonly WorldChangeRecord[];
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
readonly status: 'overflow';
|
|
27
|
+
readonly cursor: number;
|
|
28
|
+
readonly oldestAvailable: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const EMPTY_CHANGES: readonly WorldChangeRecord[] = Object.freeze([]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bounded mutation evidence for engine-owned projections.
|
|
35
|
+
*
|
|
36
|
+
* Readers retain a cursor and rebuild their projection when the ring has
|
|
37
|
+
* overwritten unread records. An overflow never returns a partial delta.
|
|
38
|
+
*/
|
|
39
|
+
export class WorldChangeJournal {
|
|
40
|
+
private readonly records: Array<WorldChangeRecord | undefined>;
|
|
41
|
+
private nextSequence = 1;
|
|
42
|
+
|
|
43
|
+
constructor(readonly capacity = 65_536) {
|
|
44
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
45
|
+
throw new RangeError('WorldChangeJournal capacity must be a positive safe integer');
|
|
46
|
+
}
|
|
47
|
+
this.records = new Array<WorldChangeRecord | undefined>(capacity);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
cursor(): number {
|
|
51
|
+
return this.nextSequence - 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
append(change: WorldChange): number {
|
|
55
|
+
const sequence = this.nextSequence;
|
|
56
|
+
this.nextSequence += 1;
|
|
57
|
+
this.records[(sequence - 1) % this.capacity] = { ...change, sequence };
|
|
58
|
+
return sequence;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
readAfter(cursor: number): WorldChangeRead {
|
|
62
|
+
const latest = this.cursor();
|
|
63
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0 || cursor > latest) {
|
|
64
|
+
throw new RangeError(`WorldChangeJournal cursor ${cursor} is outside 0..${latest}`);
|
|
65
|
+
}
|
|
66
|
+
const oldestAvailable = Math.max(1, latest - this.capacity + 1);
|
|
67
|
+
if (cursor < oldestAvailable - 1) {
|
|
68
|
+
return { status: 'overflow', cursor: latest, oldestAvailable };
|
|
69
|
+
}
|
|
70
|
+
if (cursor === latest) {
|
|
71
|
+
return { status: 'ok', cursor: latest, records: EMPTY_CHANGES };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const records: WorldChangeRecord[] = [];
|
|
75
|
+
for (let sequence = cursor + 1; sequence <= latest; sequence += 1) {
|
|
76
|
+
const record = this.records[(sequence - 1) % this.capacity];
|
|
77
|
+
if (record === undefined || record.sequence !== sequence) {
|
|
78
|
+
return { status: 'overflow', cursor: latest, oldestAvailable };
|
|
79
|
+
}
|
|
80
|
+
records.push(record);
|
|
81
|
+
}
|
|
82
|
+
return { status: 'ok', cursor: latest, records };
|
|
83
|
+
}
|
|
84
|
+
}
|