@forgeax/engine-ecs 0.0.0-dev.8d955ade1c79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +278 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
- package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
- package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
- package/dist/__tests__/archetype.unit.test.d.ts +2 -0
- package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
- package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
- package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
- package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
- package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/command-buffer.test.d.ts +2 -0
- package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
- package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-catalog.test.d.ts +2 -0
- package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
- package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test-d.d.ts +2 -0
- package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
- package/dist/__tests__/component-schema.test.d.ts +2 -0
- package/dist/__tests__/component-schema.test.d.ts.map +1 -0
- package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
- package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
- package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
- package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
- package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
- package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
- package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
- package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
- package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
- package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
- package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle.test-d.d.ts +2 -0
- package/dist/__tests__/handle.test-d.d.ts.map +1 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
- package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
- package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
- package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
- package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
- package/dist/__tests__/plugin-service.test.d.ts +2 -0
- package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
- package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
- package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
- package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
- package/dist/__tests__/query.unit.test.d.ts +2 -0
- package/dist/__tests__/query.unit.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
- package/dist/__tests__/relationship-index.test.d.ts +2 -0
- package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
- package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
- package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/serialization.unit.test.d.ts +2 -0
- package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
- package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
- package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/stale-error.test-d.d.ts +2 -0
- package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
- package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
- package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
- package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
- package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
- package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
- package/dist/__tests__/time.unit.test.d.ts +2 -0
- package/dist/__tests__/time.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test-d.d.ts +2 -0
- package/dist/__tests__/types.test-d.d.ts.map +1 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
- package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
- package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
- package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
- package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
- package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
- package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/world-health.contract.test.d.ts +2 -0
- package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
- package/dist/__tests__/world-inspection.test.d.ts +2 -0
- package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
- package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
- package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/world.test-d.d.ts +2 -0
- package/dist/__tests__/world.test-d.d.ts.map +1 -0
- package/dist/buffer-pool.d.ts +109 -0
- package/dist/buffer-pool.d.ts.map +1 -0
- package/dist/commands.d.ts +93 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/component-default-fallback.d.ts +88 -0
- package/dist/component-default-fallback.d.ts.map +1 -0
- package/dist/component-schema.d.ts +34 -0
- package/dist/component-schema.d.ts.map +1 -0
- package/dist/component-value-validate.d.ts +29 -0
- package/dist/component-value-validate.d.ts.map +1 -0
- package/dist/component.d.ts +581 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/entity-handle.d.ts +68 -0
- package/dist/entity-handle.d.ts.map +1 -0
- package/dist/entity.d.ts +86 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/errors/query-and-component-errors.d.ts +132 -0
- package/dist/errors/query-and-component-errors.d.ts.map +1 -0
- package/dist/errors/relationship-errors.d.ts +112 -0
- package/dist/errors/relationship-errors.d.ts.map +1 -0
- package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
- package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
- package/dist/errors/validation-errors.d.ts +229 -0
- package/dist/errors/validation-errors.d.ts.map +1 -0
- package/dist/errors.d.ts +763 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/execution/shared-kernel.d.ts +106 -0
- package/dist/execution/shared-kernel.d.ts.map +1 -0
- package/dist/externalization/index.d.ts +31 -0
- package/dist/externalization/index.d.ts.map +1 -0
- package/dist/externalization/index.mjs +298 -0
- package/dist/externalization/index.mjs.map +1 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7422 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal.mjs +479 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/plugin-service.d.ts +11 -0
- package/dist/plugin-service.d.ts.map +1 -0
- package/dist/projection/index.d.ts +103 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.mjs +615 -0
- package/dist/projection/index.mjs.map +1 -0
- package/dist/query/query.d.ts +58 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/relationship-index.d.ts +81 -0
- package/dist/relationship-index.d.ts.map +1 -0
- package/dist/resource.d.ts +70 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/schedule-token.d.ts +16 -0
- package/dist/schedule-token.d.ts.map +1 -0
- package/dist/schedule.d.ts +271 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/shared-ref-store.d.ts +144 -0
- package/dist/shared-ref-store.d.ts.map +1 -0
- package/dist/shared.d.ts +3 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.mjs +364 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/storage/archetype-graph.d.ts +54 -0
- package/dist/storage/archetype-graph.d.ts.map +1 -0
- package/dist/storage/archetype.d.ts +23 -0
- package/dist/storage/archetype.d.ts.map +1 -0
- package/dist/storage/change-detection.d.ts +40 -0
- package/dist/storage/change-detection.d.ts.map +1 -0
- package/dist/storage/column.d.ts +111 -0
- package/dist/storage/column.d.ts.map +1 -0
- package/dist/storage/table.d.ts +29 -0
- package/dist/storage/table.d.ts.map +1 -0
- package/dist/time.d.ts +57 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/unique-ref-store.d.ts +105 -0
- package/dist/unique-ref-store.d.ts.map +1 -0
- package/dist/world-change-journal.d.ts +34 -0
- package/dist/world-change-journal.d.ts.map +1 -0
- package/dist/world-component-access.d.ts +265 -0
- package/dist/world-component-access.d.ts.map +1 -0
- package/dist/world-component-storage.d.ts +282 -0
- package/dist/world-component-storage.d.ts.map +1 -0
- package/dist/world-core.d.ts +39 -0
- package/dist/world-core.d.ts.map +1 -0
- package/dist/world-entity-lifecycle.d.ts +30 -0
- package/dist/world-entity-lifecycle.d.ts.map +1 -0
- package/dist/world-internal.d.ts +14 -0
- package/dist/world-internal.d.ts.map +1 -0
- package/dist/world-scheduling.d.ts +30 -0
- package/dist/world-scheduling.d.ts.map +1 -0
- package/dist/world.d.ts +620 -0
- package/dist/world.d.ts.map +1 -0
- package/package.json +77 -0
- package/src/__fixtures__/field-shape-fixture.ts +30 -0
- package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
- package/src/__tests__/archetype.unit.test.ts +1837 -0
- package/src/__tests__/array-handle-element.test-d.ts +34 -0
- package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
- package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
- package/src/__tests__/buffer-pool.test-d.ts +32 -0
- package/src/__tests__/change-detection.unit.test.ts +86 -0
- package/src/__tests__/command-buffer.test.ts +179 -0
- package/src/__tests__/commands-deferred.unit.test.ts +68 -0
- package/src/__tests__/component-catalog.test.ts +59 -0
- package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
- package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
- package/src/__tests__/component-schema.test-d.ts +119 -0
- package/src/__tests__/component-schema.test.ts +55 -0
- package/src/__tests__/define-component-options.test-d.ts +35 -0
- package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
- package/src/__tests__/despawn-all.unit.test.ts +17 -0
- package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
- package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
- package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
- package/src/__tests__/entity-disabling.unit.test.ts +37 -0
- package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
- package/src/__tests__/errors.unit.test.ts +1456 -0
- package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
- package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
- package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
- package/src/__tests__/externalization-projection.unit.test.ts +325 -0
- package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
- package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
- package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
- package/src/__tests__/field-shape-fixture.test.ts +61 -0
- package/src/__tests__/field-transient-reflection.test.ts +59 -0
- package/src/__tests__/field-value-type.test-d.ts +57 -0
- package/src/__tests__/fixed-update.unit.test.ts +78 -0
- package/src/__tests__/handle.test-d.ts +86 -0
- package/src/__tests__/hierarchy.unit.test.ts +2534 -0
- package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
- package/src/__tests__/minimal-example.test-d.ts +42 -0
- package/src/__tests__/name-read-types.test-d.ts +72 -0
- package/src/__tests__/plugin-service.test.ts +13 -0
- package/src/__tests__/query-combinations.unit.test.ts +42 -0
- package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
- package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
- package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
- package/src/__tests__/query.unit.test.ts +196 -0
- package/src/__tests__/relationship-index.perf.test.ts +126 -0
- package/src/__tests__/relationship-index.test.ts +70 -0
- package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
- package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
- package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
- package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
- package/src/__tests__/schedule-token.unit.test.ts +54 -0
- package/src/__tests__/schema-vocab.test-d.ts +103 -0
- package/src/__tests__/serialization.unit.test.ts +2740 -0
- package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
- package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
- package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
- package/src/__tests__/sparse-tag.unit.test.ts +201 -0
- package/src/__tests__/spawn-field-error.test-d.ts +41 -0
- package/src/__tests__/stale-error.test-d.ts +208 -0
- package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
- package/src/__tests__/string-vocab.test-d.ts +49 -0
- package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
- package/src/__tests__/structure-epoch.unit.test.ts +48 -0
- package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
- package/src/__tests__/time.unit.test.ts +79 -0
- package/src/__tests__/types.test-d.ts +38 -0
- package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
- package/src/__tests__/utils/handle-numeric.ts +30 -0
- package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
- package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
- package/src/__tests__/world-change-journal.unit.test.ts +97 -0
- package/src/__tests__/world-characterization.unit.test.ts +408 -0
- package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
- package/src/__tests__/world-health.contract.test.ts +87 -0
- package/src/__tests__/world-inspection.test.ts +37 -0
- package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
- package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
- package/src/__tests__/world-scheduling.m4.test.ts +52 -0
- package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
- package/src/__tests__/world.test-d.ts +156 -0
- package/src/buffer-pool.ts +357 -0
- package/src/commands.ts +644 -0
- package/src/component-default-fallback.ts +236 -0
- package/src/component-schema.ts +92 -0
- package/src/component-value-validate.ts +135 -0
- package/src/component.ts +1268 -0
- package/src/entity-handle.ts +104 -0
- package/src/entity.ts +140 -0
- package/src/errors/query-and-component-errors.ts +200 -0
- package/src/errors/relationship-errors.ts +198 -0
- package/src/errors/sprite-and-shared-errors.ts +231 -0
- package/src/errors/validation-errors.ts +506 -0
- package/src/errors.ts +1464 -0
- package/src/execution/shared-kernel.ts +392 -0
- package/src/externalization/index.ts +170 -0
- package/src/index.ts +414 -0
- package/src/plugin-service.ts +35 -0
- package/src/projection/index.ts +318 -0
- package/src/query/query.ts +606 -0
- package/src/relationship-index.ts +175 -0
- package/src/resource.ts +106 -0
- package/src/schedule-token.ts +40 -0
- package/src/schedule.ts +846 -0
- package/src/shared-ref-store.ts +418 -0
- package/src/shared.ts +23 -0
- package/src/storage/archetype-graph.ts +166 -0
- package/src/storage/archetype.ts +78 -0
- package/src/storage/change-detection.ts +231 -0
- package/src/storage/column.ts +247 -0
- package/src/storage/table.ts +153 -0
- package/src/time.ts +122 -0
- package/src/unique-ref-store.ts +278 -0
- package/src/world-change-journal.ts +84 -0
- package/src/world-component-access.ts +1421 -0
- package/src/world-component-storage.ts +1178 -0
- package/src/world-core.ts +74 -0
- package/src/world-entity-lifecycle.ts +539 -0
- package/src/world-internal.ts +75 -0
- package/src/world-scheduling.ts +499 -0
- package/src/world.ts +1399 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import type { Component, SchemaFieldType } from '../component';
|
|
2
|
+
import { componentId, componentSchema } from '../component';
|
|
3
|
+
import type { QueryDescriptor, QuerySpan } from '../query/query';
|
|
4
|
+
import type { SystemHandle } from '../schedule';
|
|
5
|
+
import { worldInternal } from '../world-internal';
|
|
6
|
+
|
|
7
|
+
export const SHARED_KERNEL_EXECUTOR_RESOURCE_KEY = 'SharedKernelExecutor';
|
|
8
|
+
|
|
9
|
+
export interface KernelDispatchResult {
|
|
10
|
+
readonly mode: 'forced-inline' | 'shared';
|
|
11
|
+
readonly dispatched: number;
|
|
12
|
+
readonly completed: number;
|
|
13
|
+
readonly waitMs: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface KernelDispatchFailure {
|
|
17
|
+
readonly cause: unknown;
|
|
18
|
+
readonly dispatched: number;
|
|
19
|
+
readonly completed: number;
|
|
20
|
+
readonly partialWrite: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KernelDispatchSpan {
|
|
24
|
+
readonly queryIndex: number;
|
|
25
|
+
readonly span: QuerySpan;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SharedKernelExecutor {
|
|
29
|
+
warmup?(kernel: SharedKernelDispatch): void;
|
|
30
|
+
execute(
|
|
31
|
+
kernel: SharedKernelDispatch,
|
|
32
|
+
spans: readonly KernelDispatchSpan[],
|
|
33
|
+
): KernelDispatchResult | KernelDispatchFailure;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isKernelDispatchFailure(
|
|
37
|
+
value: KernelDispatchResult | KernelDispatchFailure,
|
|
38
|
+
): value is KernelDispatchFailure {
|
|
39
|
+
return 'cause' in value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const SHARED_KERNEL_ELIGIBILITY_REASONS = [
|
|
43
|
+
'callback-not-module-function',
|
|
44
|
+
'dom-access',
|
|
45
|
+
'missing-access-declaration',
|
|
46
|
+
'descriptor-conflict',
|
|
47
|
+
'object-field',
|
|
48
|
+
'span-unavailable',
|
|
49
|
+
] as const;
|
|
50
|
+
export type SharedKernelEligibilityReason = (typeof SHARED_KERNEL_ELIGIBILITY_REASONS)[number];
|
|
51
|
+
|
|
52
|
+
export type WorldExecutionHealth = 'healthy' | 'poisoned';
|
|
53
|
+
|
|
54
|
+
export interface WorldExecutionFault {
|
|
55
|
+
readonly code: 'shared-kernel-failed';
|
|
56
|
+
readonly kernelName: string;
|
|
57
|
+
readonly cause: unknown;
|
|
58
|
+
readonly partialWrite: boolean;
|
|
59
|
+
readonly retryable: false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface WorldExecutionState {
|
|
63
|
+
readonly identity: string;
|
|
64
|
+
readonly health: WorldExecutionHealth;
|
|
65
|
+
readonly fault: WorldExecutionFault | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let nextWorldIdentity = 1;
|
|
69
|
+
|
|
70
|
+
export function createWorldIdentity(): string {
|
|
71
|
+
const identity = `world-${nextWorldIdentity}`;
|
|
72
|
+
nextWorldIdentity += 1;
|
|
73
|
+
return identity;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function healthyWorldExecutionState(identity: string): WorldExecutionState {
|
|
77
|
+
return Object.freeze({ identity, health: 'healthy', fault: null });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function poisonedWorldExecutionState(
|
|
81
|
+
identity: string,
|
|
82
|
+
fault: WorldExecutionFault,
|
|
83
|
+
): WorldExecutionState {
|
|
84
|
+
return Object.freeze({ identity, health: 'poisoned', fault: Object.freeze(fault) });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface SharedKernelDefinition<Qs extends readonly QueryDescriptor[]> {
|
|
88
|
+
readonly name: string;
|
|
89
|
+
readonly queries: Qs;
|
|
90
|
+
readonly run: (spans: readonly QuerySpan[]) => void;
|
|
91
|
+
readonly minimumRows?: number;
|
|
92
|
+
readonly before?: readonly (string | import('../schedule-token').ScheduleToken)[];
|
|
93
|
+
readonly after?: readonly (string | import('../schedule-token').ScheduleToken)[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface SharedKernelDispatch<
|
|
97
|
+
Qs extends readonly QueryDescriptor[] = readonly QueryDescriptor[],
|
|
98
|
+
> {
|
|
99
|
+
readonly kind: 'shared-kernel';
|
|
100
|
+
readonly moduleUrl: string;
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly minimumRows: number;
|
|
103
|
+
readonly queries: Qs;
|
|
104
|
+
readonly run: (spans: readonly QuerySpan[]) => void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface SharedKernelHandle<
|
|
108
|
+
Qs extends readonly QueryDescriptor[] = readonly QueryDescriptor[],
|
|
109
|
+
> extends SystemHandle<Qs>,
|
|
110
|
+
SharedKernelDispatch<Qs> {}
|
|
111
|
+
|
|
112
|
+
export class SharedKernelEligibilityError extends Error {
|
|
113
|
+
readonly code = 'shared-kernel-ineligible' as const;
|
|
114
|
+
readonly expected =
|
|
115
|
+
'a module-loadable named kernel with one or more numeric QuerySpan read/write declarations';
|
|
116
|
+
readonly hint =
|
|
117
|
+
'export a named function from the kernel module and use only dense numeric QuerySpan columns';
|
|
118
|
+
readonly detail: { readonly kernelName: string; readonly reason: SharedKernelEligibilityReason };
|
|
119
|
+
|
|
120
|
+
constructor(kernelName: string, reason: SharedKernelEligibilityReason) {
|
|
121
|
+
super(`Shared kernel "${kernelName}" is ineligible: ${reason}.`);
|
|
122
|
+
this.name = 'SharedKernelEligibilityError';
|
|
123
|
+
this.detail = { kernelName, reason };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class SharedKernelFailureError extends Error {
|
|
128
|
+
readonly code = 'shared-kernel-failed' as const;
|
|
129
|
+
readonly expected = 'every dispatched shard completes without a possible partial write';
|
|
130
|
+
readonly hint =
|
|
131
|
+
'do not retry this World; inspect detail.cause and rebuild with a new World identity';
|
|
132
|
+
readonly detail: {
|
|
133
|
+
readonly kernelName: string;
|
|
134
|
+
readonly worldIdentity: string;
|
|
135
|
+
readonly cause: unknown;
|
|
136
|
+
readonly partialWrite: boolean;
|
|
137
|
+
readonly retryable: false;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
constructor(kernelName: string, worldIdentity: string, cause: unknown, partialWrite: boolean) {
|
|
141
|
+
super(`Shared kernel "${kernelName}" failed; World ${worldIdentity} is poisoned.`);
|
|
142
|
+
this.name = 'SharedKernelFailureError';
|
|
143
|
+
this.detail = { kernelName, worldIdentity, cause, partialWrite, retryable: false };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class WorldPoisonedError extends Error {
|
|
148
|
+
readonly code = 'world-poisoned' as const;
|
|
149
|
+
readonly expected = 'World health is healthy before update';
|
|
150
|
+
readonly hint = 'stop scheduling this World and explicitly bootstrap a new World identity';
|
|
151
|
+
readonly detail: { readonly worldIdentity: string; readonly fault: unknown };
|
|
152
|
+
|
|
153
|
+
constructor(worldIdentity: string, fault: unknown) {
|
|
154
|
+
super(`World ${worldIdentity} is poisoned and cannot update.`);
|
|
155
|
+
this.name = 'WorldPoisonedError';
|
|
156
|
+
this.detail = { worldIdentity, fault };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const NUMERIC_FIELDS = new Set<SchemaFieldType>([
|
|
161
|
+
'f32',
|
|
162
|
+
'f64',
|
|
163
|
+
'i32',
|
|
164
|
+
'u32',
|
|
165
|
+
'i16',
|
|
166
|
+
'u16',
|
|
167
|
+
'i8',
|
|
168
|
+
'u8',
|
|
169
|
+
'bool',
|
|
170
|
+
'enum',
|
|
171
|
+
'ref',
|
|
172
|
+
'entity',
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
function components(descriptor: QueryDescriptor): readonly Component[] {
|
|
176
|
+
return [
|
|
177
|
+
...(descriptor.read ?? []),
|
|
178
|
+
...(descriptor.write ?? []),
|
|
179
|
+
...(descriptor.optional ?? []),
|
|
180
|
+
...(descriptor.with ?? []),
|
|
181
|
+
...(descriptor.without ?? []),
|
|
182
|
+
...(descriptor.changed ?? []),
|
|
183
|
+
...(descriptor.added ?? []),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function descriptorReason(descriptor: QueryDescriptor): SharedKernelEligibilityReason | undefined {
|
|
188
|
+
if ((descriptor.read?.length ?? 0) + (descriptor.write?.length ?? 0) === 0) {
|
|
189
|
+
return 'missing-access-declaration';
|
|
190
|
+
}
|
|
191
|
+
if (
|
|
192
|
+
(descriptor.optional?.length ?? 0) > 0 ||
|
|
193
|
+
(descriptor.changed?.length ?? 0) > 0 ||
|
|
194
|
+
(descriptor.added?.length ?? 0) > 0
|
|
195
|
+
) {
|
|
196
|
+
return 'span-unavailable';
|
|
197
|
+
}
|
|
198
|
+
const seen = new Set<number>();
|
|
199
|
+
for (const component of components(descriptor)) {
|
|
200
|
+
if (seen.has(componentId(component))) return 'descriptor-conflict';
|
|
201
|
+
seen.add(componentId(component));
|
|
202
|
+
if (component.storage === 'sparse') return 'span-unavailable';
|
|
203
|
+
if (Object.values(componentSchema(component)).some((field) => !NUMERIC_FIELDS.has(field))) {
|
|
204
|
+
return 'object-field';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function sharedKernelEligibility(
|
|
211
|
+
moduleUrl: string,
|
|
212
|
+
definition: SharedKernelDefinition<readonly QueryDescriptor[]>,
|
|
213
|
+
): SharedKernelEligibilityReason | undefined {
|
|
214
|
+
try {
|
|
215
|
+
new URL(moduleUrl);
|
|
216
|
+
} catch {
|
|
217
|
+
return 'callback-not-module-function';
|
|
218
|
+
}
|
|
219
|
+
const source = Function.prototype.toString.call(definition.run);
|
|
220
|
+
if (definition.run.name.length === 0 || source.includes('=>')) {
|
|
221
|
+
return 'callback-not-module-function';
|
|
222
|
+
}
|
|
223
|
+
if (/\b(?:document|window|globalThis|HTMLElement|GPUDevice|AudioContext)\b/u.test(source)) {
|
|
224
|
+
return 'dom-access';
|
|
225
|
+
}
|
|
226
|
+
for (const query of definition.queries) {
|
|
227
|
+
const reason = descriptorReason(query);
|
|
228
|
+
if (reason !== undefined) return reason;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function defineSharedKernel<const Qs extends readonly QueryDescriptor[]>(
|
|
234
|
+
moduleUrl: string,
|
|
235
|
+
definition: SharedKernelDefinition<Qs>,
|
|
236
|
+
): SharedKernelHandle<Qs> {
|
|
237
|
+
const reason = sharedKernelEligibility(moduleUrl, definition);
|
|
238
|
+
if (reason !== undefined) throw new SharedKernelEligibilityError(definition.name, reason);
|
|
239
|
+
|
|
240
|
+
const handle: SharedKernelHandle<Qs> = Object.freeze({
|
|
241
|
+
kind: 'shared-kernel' as const,
|
|
242
|
+
moduleUrl,
|
|
243
|
+
name: definition.name,
|
|
244
|
+
queries: definition.queries,
|
|
245
|
+
minimumRows: definition.minimumRows ?? 16_384,
|
|
246
|
+
run: definition.run,
|
|
247
|
+
...(definition.before !== undefined ? { before: definition.before } : {}),
|
|
248
|
+
...(definition.after !== undefined ? { after: definition.after } : {}),
|
|
249
|
+
fn: (world: import('../world').World, queries: Parameters<SystemHandle<Qs>['fn']>[1]) => {
|
|
250
|
+
const dispatchSpans: KernelDispatchSpan[] = [];
|
|
251
|
+
for (const [queryIndex, query] of queries.entries()) {
|
|
252
|
+
const result = query.spans();
|
|
253
|
+
if (!result.ok) throw new SharedKernelEligibilityError(definition.name, 'span-unavailable');
|
|
254
|
+
for (const span of result.value) dispatchSpans.push({ queryIndex, span });
|
|
255
|
+
}
|
|
256
|
+
const spans = dispatchSpans.map((entry) => entry.span);
|
|
257
|
+
const totalRows = spans.reduce((sum, span) => sum + span.length, 0);
|
|
258
|
+
try {
|
|
259
|
+
if (
|
|
260
|
+
totalRows < (definition.minimumRows ?? 16_384) ||
|
|
261
|
+
!world.hasResource(SHARED_KERNEL_EXECUTOR_RESOURCE_KEY)
|
|
262
|
+
) {
|
|
263
|
+
definition.run(spans);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const executor = world.getResource<SharedKernelExecutor>(
|
|
267
|
+
SHARED_KERNEL_EXECUTOR_RESOURCE_KEY,
|
|
268
|
+
);
|
|
269
|
+
const result = executor.execute(handle, dispatchSpans);
|
|
270
|
+
if (isKernelDispatchFailure(result)) {
|
|
271
|
+
if (!result.partialWrite) {
|
|
272
|
+
definition.run(spans);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
world[worldInternal].poisonExecution({
|
|
276
|
+
code: 'shared-kernel-failed',
|
|
277
|
+
kernelName: definition.name,
|
|
278
|
+
cause: result.cause,
|
|
279
|
+
partialWrite: result.partialWrite,
|
|
280
|
+
retryable: false,
|
|
281
|
+
});
|
|
282
|
+
throw new SharedKernelFailureError(
|
|
283
|
+
definition.name,
|
|
284
|
+
world.execution.identity,
|
|
285
|
+
result.cause,
|
|
286
|
+
result.partialWrite,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
} catch (cause) {
|
|
290
|
+
if (world.execution.health !== 'poisoned') {
|
|
291
|
+
world[worldInternal].poisonExecution({
|
|
292
|
+
code: 'shared-kernel-failed',
|
|
293
|
+
kernelName: definition.name,
|
|
294
|
+
cause,
|
|
295
|
+
partialWrite: true,
|
|
296
|
+
retryable: false,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (cause instanceof SharedKernelFailureError) throw cause;
|
|
300
|
+
throw new SharedKernelFailureError(definition.name, world.execution.identity, cause, true);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
return handle;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export type SharedFieldView =
|
|
308
|
+
| Float32Array
|
|
309
|
+
| Float64Array
|
|
310
|
+
| Int32Array
|
|
311
|
+
| Uint32Array
|
|
312
|
+
| Int16Array
|
|
313
|
+
| Uint16Array
|
|
314
|
+
| Int8Array
|
|
315
|
+
| Uint8Array;
|
|
316
|
+
|
|
317
|
+
export interface SharedSpanBinding {
|
|
318
|
+
readonly entities: Readonly<Uint32Array>;
|
|
319
|
+
readonly length: number;
|
|
320
|
+
readonly read: Readonly<Record<string, Readonly<Record<string, SharedFieldView>>>>;
|
|
321
|
+
readonly write: Readonly<Record<string, Readonly<Record<string, SharedFieldView>>>>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function sliceFields(
|
|
325
|
+
fields: Readonly<Record<string, SharedFieldView>>,
|
|
326
|
+
start: number,
|
|
327
|
+
end: number,
|
|
328
|
+
): Readonly<Record<string, SharedFieldView>> {
|
|
329
|
+
return Object.fromEntries(
|
|
330
|
+
Object.entries(fields).map(([name, view]) => [name, view.subarray(start, end)]),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function bindSharedSpan(
|
|
335
|
+
kernel: SharedKernelDispatch,
|
|
336
|
+
span: QuerySpan,
|
|
337
|
+
queryIndex: number,
|
|
338
|
+
): SharedSpanBinding {
|
|
339
|
+
const descriptor = kernel.queries[queryIndex];
|
|
340
|
+
if (descriptor === undefined) throw new Error(`Missing query descriptor ${queryIndex}.`);
|
|
341
|
+
const read = Object.fromEntries(
|
|
342
|
+
(descriptor.read ?? []).map((component) => [
|
|
343
|
+
component.name,
|
|
344
|
+
span.get(component) as unknown as Record<string, SharedFieldView>,
|
|
345
|
+
]),
|
|
346
|
+
);
|
|
347
|
+
const write = Object.fromEntries(
|
|
348
|
+
(descriptor.write ?? []).map((component) => [
|
|
349
|
+
component.name,
|
|
350
|
+
span.mut(component) as unknown as Record<string, SharedFieldView>,
|
|
351
|
+
]),
|
|
352
|
+
);
|
|
353
|
+
return { entities: span.entities, length: span.length, read, write };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function splitSharedSpan(
|
|
357
|
+
binding: SharedSpanBinding,
|
|
358
|
+
shardCount: number,
|
|
359
|
+
): readonly SharedSpanBinding[] {
|
|
360
|
+
if (binding.length === 0 || shardCount <= 0) return [];
|
|
361
|
+
const count = Math.min(binding.length, shardCount);
|
|
362
|
+
const shards: SharedSpanBinding[] = [];
|
|
363
|
+
for (let index = 0; index < count; index += 1) {
|
|
364
|
+
const start = Math.floor((binding.length * index) / count);
|
|
365
|
+
const end = Math.floor((binding.length * (index + 1)) / count);
|
|
366
|
+
shards.push({
|
|
367
|
+
entities: binding.entities.subarray(start, end),
|
|
368
|
+
length: end - start,
|
|
369
|
+
read: Object.fromEntries(
|
|
370
|
+
Object.entries(binding.read).map(([component, fields]) => [
|
|
371
|
+
component,
|
|
372
|
+
sliceFields(fields, start, end),
|
|
373
|
+
]),
|
|
374
|
+
),
|
|
375
|
+
write: Object.fromEntries(
|
|
376
|
+
Object.entries(binding.write).map(([component, fields]) => [
|
|
377
|
+
component,
|
|
378
|
+
sliceFields(fields, start, end),
|
|
379
|
+
]),
|
|
380
|
+
),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return shards;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function isSharedSpan(binding: SharedSpanBinding): boolean {
|
|
387
|
+
if (typeof SharedArrayBuffer === 'undefined') return false;
|
|
388
|
+
if (!(binding.entities.buffer instanceof SharedArrayBuffer)) return false;
|
|
389
|
+
return [...Object.values(binding.read), ...Object.values(binding.write)].every((fields) =>
|
|
390
|
+
Object.values(fields).every((view) => view.buffer instanceof SharedArrayBuffer),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// @forgeax/engine-ecs — externalization module public barrel.
|
|
2
|
+
//
|
|
3
|
+
// Pure ECS kernel: projection, portable validation, and entity remap.
|
|
4
|
+
// No network, peer, wire, profile, or codec policy.
|
|
5
|
+
|
|
6
|
+
import type { Component, ComponentSchema } from '../component';
|
|
7
|
+
import { componentSchema } from '../component';
|
|
8
|
+
import { fillComponentDefaults } from '../component-default-fallback';
|
|
9
|
+
import { componentDefinition } from '../component-schema';
|
|
10
|
+
|
|
11
|
+
export type EntityFieldKind =
|
|
12
|
+
| { readonly kind: 'entity'; readonly isArray: false }
|
|
13
|
+
| { readonly kind: 'entity'; readonly isArray: true };
|
|
14
|
+
|
|
15
|
+
export interface EntityRemapOptions {
|
|
16
|
+
readonly missing?: 'identity' | 'error';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function classifyEntityField(token: Component, fieldName: string): EntityFieldKind | null {
|
|
20
|
+
const field = componentDefinition(token).fields[fieldName];
|
|
21
|
+
if (field === undefined) return null;
|
|
22
|
+
if (field.arrayMeta !== undefined) {
|
|
23
|
+
return field.arrayMeta.elementType === 'entity' ? { kind: 'entity', isArray: true } : null;
|
|
24
|
+
}
|
|
25
|
+
return componentSchema(token)[fieldName] === 'entity' ? { kind: 'entity', isArray: false } : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function remapEntityFieldValue(
|
|
29
|
+
value: unknown,
|
|
30
|
+
kind: EntityFieldKind | null,
|
|
31
|
+
remapFn: (entity: number) => number,
|
|
32
|
+
): unknown {
|
|
33
|
+
if (kind === null) return value;
|
|
34
|
+
if (kind.isArray) return Array.isArray(value) ? value.map((entity) => remapFn(entity)) : value;
|
|
35
|
+
return typeof value === 'number' ? remapFn(value) : value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createEntityRemap(
|
|
39
|
+
mapping: Uint32Array | readonly number[],
|
|
40
|
+
options: EntityRemapOptions = {},
|
|
41
|
+
): (entity: number) => number {
|
|
42
|
+
return (entity) => {
|
|
43
|
+
const mapped = entity >= 0 && entity < mapping.length ? mapping[entity] : undefined;
|
|
44
|
+
if (mapped !== undefined) return mapped;
|
|
45
|
+
if (options.missing === 'error') {
|
|
46
|
+
throw new Error(`Entity mapping is missing source entity ${entity}.`);
|
|
47
|
+
}
|
|
48
|
+
return entity;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deepCopyValue(value: unknown): unknown {
|
|
53
|
+
if (Array.isArray(value)) return [...value];
|
|
54
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
55
|
+
const view = value as unknown as ArrayLike<number>;
|
|
56
|
+
const ViewCtor = value.constructor as unknown as new (v: ArrayLike<number>) => unknown;
|
|
57
|
+
return new ViewCtor(view);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function projectComponentDataInMode<S extends ComponentSchema>(
|
|
63
|
+
token: Component<string, S>,
|
|
64
|
+
raw: Partial<Record<string, unknown>> | undefined,
|
|
65
|
+
entityRemap?: (entity: number) => number,
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
const definition = componentDefinition(token);
|
|
68
|
+
if (definition.policy.transient) return {};
|
|
69
|
+
const fields = definition.fields;
|
|
70
|
+
const rawObj = raw as Record<string, unknown> | undefined;
|
|
71
|
+
const filtered: Record<string, unknown> = {};
|
|
72
|
+
for (const fieldName of Object.keys(componentSchema(token))) {
|
|
73
|
+
const reflection = fields?.[fieldName];
|
|
74
|
+
if (reflection?.transient === true || rawObj === undefined || !(fieldName in rawObj)) continue;
|
|
75
|
+
const value = rawObj[fieldName];
|
|
76
|
+
if (value === undefined) continue;
|
|
77
|
+
const kind = classifyEntityField(token, fieldName);
|
|
78
|
+
if (kind?.isArray && (Array.isArray(value) || ArrayBuffer.isView(value))) {
|
|
79
|
+
filtered[fieldName] = Array.from(value as ArrayLike<number>, (entity) =>
|
|
80
|
+
entityRemap === undefined ? entity : entityRemap(entity),
|
|
81
|
+
);
|
|
82
|
+
} else if (kind !== null && typeof value === 'number') {
|
|
83
|
+
filtered[fieldName] = entityRemap === undefined ? value : entityRemap(value);
|
|
84
|
+
} else {
|
|
85
|
+
filtered[fieldName] = deepCopyValue(value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const result = fillComponentDefaults(token, filtered as Partial<Record<string, unknown>>);
|
|
89
|
+
for (const fieldName of Object.keys(componentSchema(token))) {
|
|
90
|
+
if (fields?.[fieldName]?.transient === true) delete result[fieldName];
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function projectComponentData<S extends ComponentSchema>(
|
|
96
|
+
token: Component<string, S>,
|
|
97
|
+
raw: Partial<Record<string, unknown>> | undefined,
|
|
98
|
+
entityRemap?: (entity: number) => number,
|
|
99
|
+
): Record<string, unknown> {
|
|
100
|
+
return projectComponentDataInMode(token, raw, entityRemap);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function isComponentPortable(token: Component): boolean {
|
|
104
|
+
const definition = componentDefinition(token);
|
|
105
|
+
if (definition.policy.transient) return false;
|
|
106
|
+
const fields = definition.fields;
|
|
107
|
+
if (fields === undefined) return true;
|
|
108
|
+
return Object.keys(componentSchema(token)).some((name) => fields[name]?.transient !== true);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isComponentFullyTransient(token: Component): boolean {
|
|
112
|
+
return !isComponentPortable(token);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isFieldPortable(fieldType: string): boolean {
|
|
116
|
+
if (fieldType === 'ref' || fieldType.startsWith('unique<') || fieldType.startsWith('shared<')) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (fieldType.startsWith('array<') && fieldType.endsWith('>')) {
|
|
120
|
+
const body = fieldType
|
|
121
|
+
.slice(6, -1)
|
|
122
|
+
.replace(/,\s*\d+$/u, '')
|
|
123
|
+
.trim();
|
|
124
|
+
return body.length > 0 && isFieldPortable(body);
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ProfileComponentError {
|
|
130
|
+
readonly component: string;
|
|
131
|
+
readonly code: 'component-fully-transient' | 'field-not-portable';
|
|
132
|
+
readonly field?: string;
|
|
133
|
+
readonly fieldType?: string;
|
|
134
|
+
readonly expected: string;
|
|
135
|
+
readonly hint: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function validateProfileComponents(components: readonly Component[]): {
|
|
139
|
+
readonly valid: boolean;
|
|
140
|
+
readonly errors: readonly ProfileComponentError[];
|
|
141
|
+
} {
|
|
142
|
+
const errors: ProfileComponentError[] = [];
|
|
143
|
+
for (const token of components) {
|
|
144
|
+
if (isComponentFullyTransient(token)) {
|
|
145
|
+
errors.push({
|
|
146
|
+
component: token.name,
|
|
147
|
+
code: 'component-fully-transient',
|
|
148
|
+
expected: `Component '${token.name}' must have at least one non-transient, portable field`,
|
|
149
|
+
hint: 'Remove the component-level transient flag or declare a non-transient field',
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const fields = componentDefinition(token).fields;
|
|
154
|
+
for (const fieldName of Object.keys(componentSchema(token))) {
|
|
155
|
+
const fieldType = componentSchema(token)[fieldName];
|
|
156
|
+
if (fieldType === undefined || fields?.[fieldName]?.transient === true) continue;
|
|
157
|
+
if (!isFieldPortable(fieldType)) {
|
|
158
|
+
errors.push({
|
|
159
|
+
component: token.name,
|
|
160
|
+
code: 'field-not-portable',
|
|
161
|
+
field: fieldName,
|
|
162
|
+
fieldType,
|
|
163
|
+
expected: `Field '${fieldName}' of component '${token.name}' must be portable`,
|
|
164
|
+
hint: `Field type '${fieldType}' is a process-local reference`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { valid: errors.length === 0, errors };
|
|
170
|
+
}
|