@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/commands.ts
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
// @forgeax/engine-ecs — CommandBuffer: deferred structural changes.
|
|
2
|
+
//
|
|
3
|
+
// System execution queues spawn/despawn/addComponent/removeComponent commands.
|
|
4
|
+
// world.update() flushes the queue at frame end with while(queue.length > 0)
|
|
5
|
+
// cascade support (D-06). Deferred spawn returns pending Entity handle (D-07).
|
|
6
|
+
|
|
7
|
+
import type { Result } from '@forgeax/engine-types';
|
|
8
|
+
import type { Component, ComponentSchema } from './component';
|
|
9
|
+
import { componentId } from './component';
|
|
10
|
+
import { fillComponentDefaults, validateComponentDataKeys } from './component-default-fallback';
|
|
11
|
+
import { Entity } from './entity';
|
|
12
|
+
import { ENTITY_NULL_RAW, type EntityHandle, entityGeneration, entityIndex } from './entity-handle';
|
|
13
|
+
import {
|
|
14
|
+
type CommandCommitEvidence,
|
|
15
|
+
CommandFailedError,
|
|
16
|
+
type CommandKind,
|
|
17
|
+
ComponentAlreadyPresentError,
|
|
18
|
+
ComponentNotPresentError,
|
|
19
|
+
RelationshipSelfCycleError,
|
|
20
|
+
RelationshipTargetReadonlyError,
|
|
21
|
+
RemoveEssentialComponentError,
|
|
22
|
+
StaleEntityError,
|
|
23
|
+
SystemFailedError,
|
|
24
|
+
} from './errors';
|
|
25
|
+
import { isRelationshipTarget, relationshipRole } from './relationship-index';
|
|
26
|
+
import type { ComponentData, EcsError } from './world';
|
|
27
|
+
import { type WorldInternal, worldInternal } from './world-internal';
|
|
28
|
+
|
|
29
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
// Command types
|
|
31
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export type Command =
|
|
34
|
+
| { type: 'spawn'; componentDatas: ComponentData[]; entity: EntityHandle }
|
|
35
|
+
| { type: 'despawn'; entity: EntityHandle }
|
|
36
|
+
| { type: 'addComponent'; entity: EntityHandle; componentData: ComponentData }
|
|
37
|
+
| { type: 'removeComponent'; entity: EntityHandle; component: Component };
|
|
38
|
+
|
|
39
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
// CommandBuffer interface
|
|
41
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* CommandBuffer queues structural changes (spawn/despawn/addComponent/removeComponent)
|
|
45
|
+
* during system execution. Flushed at end of world.update().
|
|
46
|
+
*/
|
|
47
|
+
export interface CommandBuffer {
|
|
48
|
+
readonly status: 'open' | 'committed' | 'aborted';
|
|
49
|
+
/** Deferred spawn: returns pending Entity handle. */
|
|
50
|
+
spawn(...componentDatas: ComponentData[]): EntityHandle;
|
|
51
|
+
/** Deferred despawn. */
|
|
52
|
+
despawn(entity: EntityHandle): unknown;
|
|
53
|
+
/** Deferred addComponent. */
|
|
54
|
+
addComponent<S extends ComponentSchema>(
|
|
55
|
+
entity: EntityHandle,
|
|
56
|
+
componentData: ComponentData<S>,
|
|
57
|
+
): unknown;
|
|
58
|
+
/** Deferred removeComponent. */
|
|
59
|
+
removeComponent<S extends ComponentSchema>(
|
|
60
|
+
entity: EntityHandle,
|
|
61
|
+
component: Component<string, S>,
|
|
62
|
+
): unknown;
|
|
63
|
+
/** Check if an entity is pending (deferred spawn, not yet flushed). */
|
|
64
|
+
isDeferred(entity: EntityHandle): boolean;
|
|
65
|
+
commit(): void;
|
|
66
|
+
abort(error?: unknown): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// World access interface (avoids circular import)
|
|
71
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Minimal world interface needed by CommandBuffer for entity allocation. */
|
|
74
|
+
export interface WorldForCommands {
|
|
75
|
+
readonly [worldInternal]: WorldInternal;
|
|
76
|
+
/**
|
|
77
|
+
* Allocate a pending entity index, returning [entity handle, index slot].
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Mark a pending entity as materialized (flush phase).
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
/** Execute despawn directly (flush phase). */
|
|
85
|
+
despawn(entity: EntityHandle): Result<void, EcsError>;
|
|
86
|
+
/** Execute addComponent directly (flush phase). */
|
|
87
|
+
addComponent<S extends ComponentSchema>(
|
|
88
|
+
entity: EntityHandle,
|
|
89
|
+
componentData: ComponentData<S>,
|
|
90
|
+
): Result<void, EcsError>;
|
|
91
|
+
/** Execute removeComponent directly (flush phase). */
|
|
92
|
+
removeComponent<S extends ComponentSchema>(
|
|
93
|
+
entity: EntityHandle,
|
|
94
|
+
component: Component<string, S>,
|
|
95
|
+
): Result<void, EcsError>;
|
|
96
|
+
/** Fast membership probe used by command preflight. */
|
|
97
|
+
hasComponent(entity: EntityHandle, component: Component): boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Implementation
|
|
102
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
export interface CommandBufferImpl extends CommandBuffer {
|
|
105
|
+
/** @internal */
|
|
106
|
+
_queue: Command[];
|
|
107
|
+
/** @internal */
|
|
108
|
+
_pendingEntities: Set<number>;
|
|
109
|
+
/** @internal First validation failure captured while the system was staging commands. */
|
|
110
|
+
_queueError: {
|
|
111
|
+
readonly index: number;
|
|
112
|
+
readonly kind: CommandKind;
|
|
113
|
+
readonly cause: unknown;
|
|
114
|
+
} | null;
|
|
115
|
+
/** @internal */
|
|
116
|
+
readonly _systemName: string;
|
|
117
|
+
/** @internal */
|
|
118
|
+
readonly _scheduleName: string;
|
|
119
|
+
/** @internal */
|
|
120
|
+
_setStatus(status: 'committed' | 'aborted'): void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a CommandBuffer bound to a World.
|
|
125
|
+
* @param world - the World (for pending entity allocation)
|
|
126
|
+
*/
|
|
127
|
+
export function createCommandBuffer(
|
|
128
|
+
world: WorldForCommands,
|
|
129
|
+
context: { readonly systemName?: string; readonly scheduleName?: string } = {},
|
|
130
|
+
): CommandBufferImpl {
|
|
131
|
+
const queue: Command[] = [];
|
|
132
|
+
const pendingEntities = new Set<number>();
|
|
133
|
+
let status: CommandBuffer['status'] = 'open';
|
|
134
|
+
let queueError: CommandBufferImpl['_queueError'] = null;
|
|
135
|
+
const ensureOpen = (): void => {
|
|
136
|
+
if (status !== 'open') throw new Error(`CommandBuffer is ${status}`);
|
|
137
|
+
};
|
|
138
|
+
const recordQueueError = (index: number, kind: CommandKind, cause: unknown): void => {
|
|
139
|
+
if (queueError === null) queueError = { index, kind, cause };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const buffer: CommandBufferImpl = {
|
|
143
|
+
_queue: queue,
|
|
144
|
+
_pendingEntities: pendingEntities,
|
|
145
|
+
_systemName: context.systemName ?? '<unknown-system>',
|
|
146
|
+
_scheduleName: context.scheduleName ?? '<unknown-schedule>',
|
|
147
|
+
get status() {
|
|
148
|
+
return status;
|
|
149
|
+
},
|
|
150
|
+
get _queueError() {
|
|
151
|
+
return queueError;
|
|
152
|
+
},
|
|
153
|
+
_setStatus(next) {
|
|
154
|
+
if (status === 'open') status = next;
|
|
155
|
+
},
|
|
156
|
+
commit() {
|
|
157
|
+
ensureOpen();
|
|
158
|
+
status = 'committed';
|
|
159
|
+
queue.length = 0;
|
|
160
|
+
pendingEntities.clear();
|
|
161
|
+
queueError = null;
|
|
162
|
+
},
|
|
163
|
+
abort() {
|
|
164
|
+
if (status !== 'open') return;
|
|
165
|
+
for (const raw of pendingEntities) {
|
|
166
|
+
world[worldInternal].cancelPendingEntity(raw as EntityHandle);
|
|
167
|
+
}
|
|
168
|
+
status = 'aborted';
|
|
169
|
+
queue.length = 0;
|
|
170
|
+
pendingEntities.clear();
|
|
171
|
+
queueError = null;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
spawn(...componentDatas: ComponentData[]): EntityHandle {
|
|
175
|
+
ensureOpen();
|
|
176
|
+
// bug-20260615: validate raw spawn keys at queue time (synchronous,
|
|
177
|
+
// points at the calling system's stack) rather than only at flush
|
|
178
|
+
// time. Commands.spawn returns EntityHandle (no Result channel), so
|
|
179
|
+
// unknown-key is captured as a batch preflight failure. The handle is
|
|
180
|
+
// still reserved so callers may compose later commands; abort releases
|
|
181
|
+
// every reservation without leaking a free slot.
|
|
182
|
+
for (const cd of componentDatas) {
|
|
183
|
+
const keyErr = validateComponentDataKeys(cd.component, cd.data as Record<string, unknown>);
|
|
184
|
+
if (keyErr !== null) recordQueueError(queue.length, 'spawn', keyErr);
|
|
185
|
+
}
|
|
186
|
+
const entity = world[worldInternal].allocatePendingEntity();
|
|
187
|
+
pendingEntities.add(entity as unknown as number);
|
|
188
|
+
queue.push({ type: 'spawn', componentDatas, entity });
|
|
189
|
+
return entity;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
despawn(entity: EntityHandle): void {
|
|
193
|
+
ensureOpen();
|
|
194
|
+
queue.push({ type: 'despawn', entity });
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
addComponent<S extends ComponentSchema>(
|
|
198
|
+
entity: EntityHandle,
|
|
199
|
+
componentData: ComponentData<S>,
|
|
200
|
+
): void {
|
|
201
|
+
ensureOpen();
|
|
202
|
+
const keyErr = validateComponentDataKeys(
|
|
203
|
+
componentData.component,
|
|
204
|
+
componentData.data as Record<string, unknown>,
|
|
205
|
+
);
|
|
206
|
+
if (keyErr !== null) recordQueueError(queue.length, 'addComponent', keyErr);
|
|
207
|
+
queue.push({ type: 'addComponent', entity, componentData: componentData as ComponentData });
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
removeComponent<S extends ComponentSchema>(
|
|
211
|
+
entity: EntityHandle,
|
|
212
|
+
component: Component<string, S>,
|
|
213
|
+
): void {
|
|
214
|
+
ensureOpen();
|
|
215
|
+
queue.push({ type: 'removeComponent', entity, component: component as Component });
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
isDeferred(entity: EntityHandle): boolean {
|
|
219
|
+
return pendingEntities.has(entity as unknown as number);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return buffer;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function commandFailure(
|
|
227
|
+
buffer: CommandBufferImpl,
|
|
228
|
+
index: number,
|
|
229
|
+
kind: CommandKind,
|
|
230
|
+
cause: unknown,
|
|
231
|
+
): CommandFailedError {
|
|
232
|
+
return new CommandFailedError(buffer._systemName, buffer._scheduleName, index, kind, cause);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
type RelationshipOverlay = Map<number, Map<number, EntityHandle | null>>;
|
|
236
|
+
|
|
237
|
+
function relationshipTarget(
|
|
238
|
+
component: Component,
|
|
239
|
+
data: Readonly<Record<string, unknown>>,
|
|
240
|
+
): EntityHandle | null {
|
|
241
|
+
const role = relationshipRole(component);
|
|
242
|
+
if (role?.kind !== 'source') return null;
|
|
243
|
+
const raw = data[role.sourceField];
|
|
244
|
+
if (raw === null || raw === undefined || raw === ENTITY_NULL_RAW) return null;
|
|
245
|
+
return raw as EntityHandle;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function overlayTarget(
|
|
249
|
+
overlay: RelationshipOverlay,
|
|
250
|
+
entity: EntityHandle,
|
|
251
|
+
component: Component,
|
|
252
|
+
): EntityHandle | null | undefined {
|
|
253
|
+
return overlay.get(entity as number)?.get(componentId(component));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function setOverlayTarget(
|
|
257
|
+
overlay: RelationshipOverlay,
|
|
258
|
+
entity: EntityHandle,
|
|
259
|
+
component: Component,
|
|
260
|
+
target: EntityHandle | null,
|
|
261
|
+
): void {
|
|
262
|
+
let entries = overlay.get(entity as number);
|
|
263
|
+
if (entries === undefined) {
|
|
264
|
+
entries = new Map();
|
|
265
|
+
overlay.set(entity as number, entries);
|
|
266
|
+
}
|
|
267
|
+
entries.set(componentId(component), target);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check a relationship edge against committed rows plus the batch overlay.
|
|
272
|
+
* The overlay is the only extra state needed: it records at most one target
|
|
273
|
+
* per affected source/component, so a batch remains O(C + A) rather than
|
|
274
|
+
* rescanning every World entity.
|
|
275
|
+
*/
|
|
276
|
+
function relationshipCycle(
|
|
277
|
+
world: WorldForCommands,
|
|
278
|
+
holder: EntityHandle,
|
|
279
|
+
component: Component,
|
|
280
|
+
target: EntityHandle | null,
|
|
281
|
+
overlay: RelationshipOverlay,
|
|
282
|
+
): RelationshipSelfCycleError | undefined {
|
|
283
|
+
if (target === null) return undefined;
|
|
284
|
+
const role = relationshipRole(component);
|
|
285
|
+
const allowSelf = role?.kind === 'source' && role.allowSelf;
|
|
286
|
+
if (holder === target && !allowSelf) {
|
|
287
|
+
return new RelationshipSelfCycleError(component.name, holder as number, target as number);
|
|
288
|
+
}
|
|
289
|
+
const visited = new Set<number>();
|
|
290
|
+
let current: EntityHandle | null = target;
|
|
291
|
+
while (current !== null) {
|
|
292
|
+
const raw = current as number;
|
|
293
|
+
if (raw === (holder as number) && !(holder === target && allowSelf)) {
|
|
294
|
+
return new RelationshipSelfCycleError(component.name, holder as number, raw);
|
|
295
|
+
}
|
|
296
|
+
if (visited.has(raw)) return undefined;
|
|
297
|
+
visited.add(raw);
|
|
298
|
+
const staged = overlayTarget(overlay, current, component);
|
|
299
|
+
if (staged !== undefined || overlay.get(raw)?.has(componentId(component)) === true) {
|
|
300
|
+
current = staged ?? null;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const row = world[worldInternal].getQueryRow(current, component);
|
|
304
|
+
if (!row.ok) return undefined;
|
|
305
|
+
current = relationshipTarget(component, row.value);
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Validate the failures that can be proven from the current World and the
|
|
312
|
+
* command batch without mutating storage. The commit loop remains the only
|
|
313
|
+
* writer; any failure not provable here is treated as an unexpected post-write
|
|
314
|
+
* failure and poisons the World.
|
|
315
|
+
*/
|
|
316
|
+
function preflightCommands(buffer: CommandBufferImpl, world: WorldForCommands): void {
|
|
317
|
+
if (buffer._queueError !== null) {
|
|
318
|
+
const { index, kind, cause } = buffer._queueError;
|
|
319
|
+
throw commandFailure(buffer, index, kind, cause);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
type Shadow = {
|
|
323
|
+
readonly components: Set<number>;
|
|
324
|
+
readonly added: Set<number>;
|
|
325
|
+
readonly removed: Set<number>;
|
|
326
|
+
dead: boolean;
|
|
327
|
+
pending: boolean;
|
|
328
|
+
};
|
|
329
|
+
const shadows = new Map<number, Shadow>();
|
|
330
|
+
const relationshipOverlay: RelationshipOverlay = new Map();
|
|
331
|
+
const unavailableEntities = new Set<number>();
|
|
332
|
+
const availablePendingEntities = new Set<number>();
|
|
333
|
+
const shadowFor = (entity: EntityHandle): Shadow => {
|
|
334
|
+
const raw = entity as unknown as number;
|
|
335
|
+
const current = shadows.get(raw);
|
|
336
|
+
if (current !== undefined) return current;
|
|
337
|
+
const shadow: Shadow = {
|
|
338
|
+
components: new Set<number>(),
|
|
339
|
+
added: new Set<number>(),
|
|
340
|
+
removed: new Set<number>(),
|
|
341
|
+
dead: false,
|
|
342
|
+
pending: false,
|
|
343
|
+
};
|
|
344
|
+
shadows.set(raw, shadow);
|
|
345
|
+
return shadow;
|
|
346
|
+
};
|
|
347
|
+
const validateData = (
|
|
348
|
+
holder: EntityHandle | null,
|
|
349
|
+
componentData: ComponentData,
|
|
350
|
+
): unknown | undefined => {
|
|
351
|
+
const result = world[worldInternal].preflightComponentData(
|
|
352
|
+
holder,
|
|
353
|
+
componentData,
|
|
354
|
+
availablePendingEntities,
|
|
355
|
+
unavailableEntities,
|
|
356
|
+
);
|
|
357
|
+
return result.ok ? undefined : result.error;
|
|
358
|
+
};
|
|
359
|
+
const liveError = (entity: EntityHandle, operation: string, component?: Component): unknown => {
|
|
360
|
+
const result = world[worldInternal].lookupAlive(entity, operation, component?.name);
|
|
361
|
+
return result.ok ? undefined : result.error;
|
|
362
|
+
};
|
|
363
|
+
for (const [index, command] of buffer._queue.entries()) {
|
|
364
|
+
switch (command.type) {
|
|
365
|
+
case 'spawn': {
|
|
366
|
+
const raw = command.entity as unknown as number;
|
|
367
|
+
const shadow = shadowFor(command.entity);
|
|
368
|
+
shadow.pending = true;
|
|
369
|
+
availablePendingEntities.add(raw);
|
|
370
|
+
for (const componentData of command.componentDatas) {
|
|
371
|
+
const dataError = validateData(command.entity, componentData);
|
|
372
|
+
if (dataError !== undefined) throw commandFailure(buffer, index, command.type, dataError);
|
|
373
|
+
if (shadow.components.has(componentId(componentData.component))) {
|
|
374
|
+
throw commandFailure(
|
|
375
|
+
buffer,
|
|
376
|
+
index,
|
|
377
|
+
command.type,
|
|
378
|
+
new ComponentAlreadyPresentError(raw, componentData.component.name),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
shadow.components.add(componentId(componentData.component));
|
|
382
|
+
const role = relationshipRole(componentData.component);
|
|
383
|
+
if (role?.kind === 'source') {
|
|
384
|
+
const filled = fillComponentDefaults(
|
|
385
|
+
componentData.component,
|
|
386
|
+
componentData.data as Record<string, unknown>,
|
|
387
|
+
);
|
|
388
|
+
const target = relationshipTarget(componentData.component, filled);
|
|
389
|
+
setOverlayTarget(relationshipOverlay, command.entity, componentData.component, target);
|
|
390
|
+
const cycle = relationshipCycle(
|
|
391
|
+
world,
|
|
392
|
+
command.entity,
|
|
393
|
+
componentData.component,
|
|
394
|
+
target,
|
|
395
|
+
relationshipOverlay,
|
|
396
|
+
);
|
|
397
|
+
if (cycle !== undefined) throw commandFailure(buffer, index, command.type, cycle);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case 'despawn': {
|
|
403
|
+
const shadow = shadowFor(command.entity);
|
|
404
|
+
if (!shadow.pending && !world.hasComponent(command.entity, Entity)) {
|
|
405
|
+
// A stale handle is a no-op for the direct facade today; preserve
|
|
406
|
+
// that contract instead of inventing a new command error.
|
|
407
|
+
shadow.dead = true;
|
|
408
|
+
unavailableEntities.add(command.entity as unknown as number);
|
|
409
|
+
availablePendingEntities.delete(command.entity as unknown as number);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
shadow.dead = true;
|
|
413
|
+
unavailableEntities.add(command.entity as unknown as number);
|
|
414
|
+
availablePendingEntities.delete(command.entity as unknown as number);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case 'addComponent': {
|
|
418
|
+
const raw = command.entity as unknown as number;
|
|
419
|
+
const shadow = shadowFor(command.entity);
|
|
420
|
+
const dataError = validateData(command.entity, command.componentData);
|
|
421
|
+
if (dataError !== undefined) throw commandFailure(buffer, index, command.type, dataError);
|
|
422
|
+
if (!shadow.pending) {
|
|
423
|
+
const stale = liveError(
|
|
424
|
+
command.entity,
|
|
425
|
+
'command.addComponent',
|
|
426
|
+
command.componentData.component,
|
|
427
|
+
);
|
|
428
|
+
if (stale !== undefined) throw commandFailure(buffer, index, command.type, stale);
|
|
429
|
+
}
|
|
430
|
+
const role = relationshipRole(command.componentData.component);
|
|
431
|
+
const present = shadow.pending
|
|
432
|
+
? shadow.components.has(componentId(command.componentData.component))
|
|
433
|
+
: shadow.removed.has(componentId(command.componentData.component))
|
|
434
|
+
? false
|
|
435
|
+
: shadow.added.has(componentId(command.componentData.component)) ||
|
|
436
|
+
world.hasComponent(command.entity, command.componentData.component);
|
|
437
|
+
if (shadow.dead) {
|
|
438
|
+
throw commandFailure(
|
|
439
|
+
buffer,
|
|
440
|
+
index,
|
|
441
|
+
command.type,
|
|
442
|
+
liveError(command.entity, 'command.addComponent', command.componentData.component) ??
|
|
443
|
+
new StaleEntityError(
|
|
444
|
+
raw,
|
|
445
|
+
entityIndex(command.entity),
|
|
446
|
+
entityGeneration(command.entity),
|
|
447
|
+
{
|
|
448
|
+
operation: 'command.addComponent',
|
|
449
|
+
expectedGeneration: entityGeneration(command.entity),
|
|
450
|
+
actualGeneration: -1,
|
|
451
|
+
},
|
|
452
|
+
),
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
const reparent =
|
|
456
|
+
present && role?.kind === 'source' && role.exclusive === true && !shadow.pending;
|
|
457
|
+
if (present && !reparent) {
|
|
458
|
+
throw commandFailure(
|
|
459
|
+
buffer,
|
|
460
|
+
index,
|
|
461
|
+
command.type,
|
|
462
|
+
new ComponentAlreadyPresentError(raw, command.componentData.component.name),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
shadow.components.add(componentId(command.componentData.component));
|
|
466
|
+
if (!reparent) shadow.added.add(componentId(command.componentData.component));
|
|
467
|
+
shadow.removed.delete(componentId(command.componentData.component));
|
|
468
|
+
if (role?.kind === 'source') {
|
|
469
|
+
const filled = fillComponentDefaults(
|
|
470
|
+
command.componentData.component,
|
|
471
|
+
command.componentData.data as Record<string, unknown>,
|
|
472
|
+
);
|
|
473
|
+
const target = relationshipTarget(command.componentData.component, filled);
|
|
474
|
+
setOverlayTarget(
|
|
475
|
+
relationshipOverlay,
|
|
476
|
+
command.entity,
|
|
477
|
+
command.componentData.component,
|
|
478
|
+
target,
|
|
479
|
+
);
|
|
480
|
+
const cycle = relationshipCycle(
|
|
481
|
+
world,
|
|
482
|
+
command.entity,
|
|
483
|
+
command.componentData.component,
|
|
484
|
+
target,
|
|
485
|
+
relationshipOverlay,
|
|
486
|
+
);
|
|
487
|
+
if (cycle !== undefined) throw commandFailure(buffer, index, command.type, cycle);
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case 'removeComponent': {
|
|
492
|
+
const raw = command.entity as unknown as number;
|
|
493
|
+
if (isRelationshipTarget(command.component)) {
|
|
494
|
+
throw commandFailure(
|
|
495
|
+
buffer,
|
|
496
|
+
index,
|
|
497
|
+
command.type,
|
|
498
|
+
new RelationshipTargetReadonlyError(command.component.name, 'removeComponent'),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
if (componentId(command.component) === 0) {
|
|
502
|
+
throw commandFailure(
|
|
503
|
+
buffer,
|
|
504
|
+
index,
|
|
505
|
+
command.type,
|
|
506
|
+
new RemoveEssentialComponentError(command.component.name),
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
const shadow = shadowFor(command.entity);
|
|
510
|
+
if (shadow.dead) {
|
|
511
|
+
throw commandFailure(
|
|
512
|
+
buffer,
|
|
513
|
+
index,
|
|
514
|
+
command.type,
|
|
515
|
+
liveError(command.entity, 'command.removeComponent', command.component) ??
|
|
516
|
+
new StaleEntityError(
|
|
517
|
+
raw,
|
|
518
|
+
entityIndex(command.entity),
|
|
519
|
+
entityGeneration(command.entity),
|
|
520
|
+
{
|
|
521
|
+
operation: 'command.removeComponent',
|
|
522
|
+
expectedGeneration: entityGeneration(command.entity),
|
|
523
|
+
actualGeneration: -1,
|
|
524
|
+
},
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (!shadow.pending) {
|
|
529
|
+
const stale = liveError(command.entity, 'command.removeComponent', command.component);
|
|
530
|
+
if (stale !== undefined) throw commandFailure(buffer, index, command.type, stale);
|
|
531
|
+
}
|
|
532
|
+
const present = shadow.pending
|
|
533
|
+
? shadow.components.has(componentId(command.component))
|
|
534
|
+
: shadow.removed.has(componentId(command.component))
|
|
535
|
+
? false
|
|
536
|
+
: shadow.added.has(componentId(command.component)) ||
|
|
537
|
+
world.hasComponent(command.entity, command.component);
|
|
538
|
+
if (!present) {
|
|
539
|
+
throw commandFailure(
|
|
540
|
+
buffer,
|
|
541
|
+
index,
|
|
542
|
+
command.type,
|
|
543
|
+
new ComponentNotPresentError(raw, command.component.name),
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
shadow.components.delete(componentId(command.component));
|
|
547
|
+
shadow.removed.add(componentId(command.component));
|
|
548
|
+
shadow.added.delete(componentId(command.component));
|
|
549
|
+
if (relationshipRole(command.component)?.kind === 'source') {
|
|
550
|
+
setOverlayTarget(relationshipOverlay, command.entity, command.component, null);
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Flush all queued commands against the World.
|
|
560
|
+
* Uses while(queue.length > 0) to support cascade (commands spawned during flush).
|
|
561
|
+
*/
|
|
562
|
+
export function flushCommands(buffer: CommandBufferImpl, world: WorldForCommands): void {
|
|
563
|
+
if (buffer.status !== 'open') return;
|
|
564
|
+
const queue = buffer._queue;
|
|
565
|
+
try {
|
|
566
|
+
preflightCommands(buffer, world);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
buffer.abort(error);
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
let applied = 0;
|
|
572
|
+
let commandIndex = -1;
|
|
573
|
+
let currentCommand: Command | undefined;
|
|
574
|
+
let lastCommittedCommand: CommandCommitEvidence | null = null;
|
|
575
|
+
try {
|
|
576
|
+
const requireSuccess = (result: unknown): void => {
|
|
577
|
+
if (
|
|
578
|
+
result !== null &&
|
|
579
|
+
typeof result === 'object' &&
|
|
580
|
+
'ok' in result &&
|
|
581
|
+
(result as { readonly ok: boolean }).ok === false
|
|
582
|
+
) {
|
|
583
|
+
throw (result as { readonly error?: unknown }).error ?? new Error('Command failed');
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
while (queue.length > 0) {
|
|
587
|
+
// biome-ignore lint/style/noNonNullAssertion: queue length is checked
|
|
588
|
+
const cmd = queue.shift()!;
|
|
589
|
+
commandIndex += 1;
|
|
590
|
+
currentCommand = cmd;
|
|
591
|
+
switch (cmd.type) {
|
|
592
|
+
case 'spawn':
|
|
593
|
+
requireSuccess(
|
|
594
|
+
world[worldInternal].materializePendingEntity(cmd.entity, cmd.componentDatas),
|
|
595
|
+
);
|
|
596
|
+
buffer._pendingEntities.delete(cmd.entity as unknown as number);
|
|
597
|
+
applied += 1;
|
|
598
|
+
lastCommittedCommand = { index: commandIndex, kind: cmd.type };
|
|
599
|
+
break;
|
|
600
|
+
case 'despawn':
|
|
601
|
+
requireSuccess(world.despawn(cmd.entity));
|
|
602
|
+
applied += 1;
|
|
603
|
+
lastCommittedCommand = { index: commandIndex, kind: cmd.type };
|
|
604
|
+
break;
|
|
605
|
+
case 'addComponent':
|
|
606
|
+
requireSuccess(world.addComponent(cmd.entity, cmd.componentData));
|
|
607
|
+
applied += 1;
|
|
608
|
+
lastCommittedCommand = { index: commandIndex, kind: cmd.type };
|
|
609
|
+
break;
|
|
610
|
+
case 'removeComponent':
|
|
611
|
+
requireSuccess(world.removeComponent(cmd.entity, cmd.component));
|
|
612
|
+
applied += 1;
|
|
613
|
+
lastCommittedCommand = { index: commandIndex, kind: cmd.type };
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
buffer.commit();
|
|
618
|
+
} catch (error) {
|
|
619
|
+
buffer.abort(error);
|
|
620
|
+
if (error instanceof CommandFailedError) throw error;
|
|
621
|
+
if (applied === 0) {
|
|
622
|
+
throw commandFailure(
|
|
623
|
+
buffer,
|
|
624
|
+
Math.max(commandIndex, 0),
|
|
625
|
+
currentCommand?.type ?? 'spawn',
|
|
626
|
+
error,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const poison = world[worldInternal].poisonExecution;
|
|
630
|
+
poison({
|
|
631
|
+
code: 'shared-kernel-failed',
|
|
632
|
+
kernelName: 'CommandBuffer.flush',
|
|
633
|
+
cause: error,
|
|
634
|
+
partialWrite: true,
|
|
635
|
+
retryable: false,
|
|
636
|
+
});
|
|
637
|
+
throw new SystemFailedError(
|
|
638
|
+
buffer._systemName,
|
|
639
|
+
buffer._scheduleName,
|
|
640
|
+
error,
|
|
641
|
+
lastCommittedCommand,
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|