murow 0.0.70 → 0.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -37
- package/dist/cjs/core/binary-codec/binary-codec.js +1 -0
- package/dist/cjs/core/binary-codec/index.js +1 -0
- package/dist/cjs/core/driver/driver.js +1 -0
- package/dist/cjs/core/driver/drivers/immediate.js +1 -0
- package/dist/cjs/core/driver/drivers/index.js +1 -0
- package/dist/cjs/core/driver/drivers/raf.js +1 -0
- package/dist/cjs/core/driver/drivers/timeout.js +1 -0
- package/dist/cjs/core/driver/index.js +1 -0
- package/dist/cjs/core/events/event-system.js +1 -0
- package/dist/cjs/core/events/index.js +1 -0
- package/dist/cjs/core/fixed-ticker/fixed-ticker.js +1 -0
- package/dist/cjs/core/fixed-ticker/index.js +1 -0
- package/dist/cjs/core/free-list/free-list.js +1 -0
- package/dist/cjs/core/free-list/index.js +1 -0
- package/dist/cjs/core/generate-id/generate-id.js +1 -0
- package/dist/cjs/core/generate-id/index.js +1 -0
- package/dist/cjs/core/index.js +1 -0
- package/dist/cjs/core/input/index.js +1 -0
- package/dist/cjs/core/input/manager.js +1 -0
- package/dist/cjs/core/input/sources/browser.js +1 -0
- package/dist/cjs/core/input/sources/index.js +1 -0
- package/dist/cjs/core/input/types.js +1 -0
- package/dist/cjs/core/lerp/index.js +1 -0
- package/dist/cjs/core/lerp/lerp.js +1 -0
- package/dist/cjs/core/navmesh/index.js +1 -0
- package/dist/cjs/core/navmesh/navmesh-worker-pool.js +1 -0
- package/dist/cjs/core/navmesh/navmesh.js +1 -0
- package/dist/cjs/core/navmesh/navmesh.worker.js +1 -0
- package/dist/cjs/core/pooled-codec/index.js +1 -0
- package/dist/cjs/core/pooled-codec/pooled-codec.js +1 -0
- package/dist/cjs/core/prediction/index.js +1 -0
- package/dist/cjs/core/prediction/prediction.js +1 -0
- package/dist/cjs/core/ray/index.js +1 -0
- package/dist/cjs/core/ray/ray-2d.js +1 -0
- package/dist/cjs/core/ray/ray-3d.js +1 -0
- package/dist/cjs/core/simple-rng/index.js +1 -0
- package/dist/cjs/core/simple-rng/simple-rng.js +1 -0
- package/dist/cjs/core/sparse-batcher/index.js +1 -0
- package/dist/cjs/core/sparse-batcher/sparse-batcher.js +1 -0
- package/dist/cjs/ecs/component-store.js +1 -0
- package/dist/cjs/ecs/component.js +1 -0
- package/dist/cjs/ecs/entity-handle.js +1 -0
- package/dist/cjs/ecs/index.js +1 -0
- package/dist/cjs/ecs/system-builder.js +1 -0
- package/dist/cjs/ecs/world-systems.js +1 -0
- package/dist/cjs/ecs/world.js +1 -0
- package/dist/cjs/game/index.js +1 -0
- package/dist/cjs/game/loop/index.js +1 -0
- package/dist/cjs/game/loop/loop.js +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/net/adapters/browser-websocket.js +1 -0
- package/dist/cjs/net/adapters/bun-websocket.js +1 -0
- package/dist/cjs/net/buffer-pool.js +1 -0
- package/dist/cjs/net/client.js +1 -0
- package/dist/cjs/net/index.js +1 -0
- package/dist/cjs/net/server.js +1 -0
- package/dist/cjs/net/types.js +1 -0
- package/dist/cjs/net/validators.js +1 -0
- package/dist/cjs/protocol/index.js +1 -0
- package/dist/cjs/protocol/intent/define-intent.js +1 -0
- package/dist/cjs/protocol/intent/index.js +1 -0
- package/dist/cjs/protocol/intent/intent-registry.js +1 -0
- package/dist/cjs/protocol/intent/intent.js +1 -0
- package/dist/cjs/protocol/rpc/define-rpc.js +1 -0
- package/dist/cjs/protocol/rpc/index.js +1 -0
- package/dist/cjs/protocol/rpc/rpc-registry.js +1 -0
- package/dist/cjs/protocol/rpc/rpc.js +1 -0
- package/dist/cjs/protocol/snapshot/index.js +1 -0
- package/dist/cjs/protocol/snapshot/snapshot-codec.js +1 -0
- package/dist/cjs/protocol/snapshot/snapshot-registry.js +1 -0
- package/dist/cjs/protocol/snapshot/snapshot.js +1 -0
- package/dist/cjs/renderer/base-2d-renderer.js +1 -0
- package/dist/cjs/renderer/base-3d-renderer.js +1 -0
- package/dist/cjs/renderer/base-renderer.js +1 -0
- package/dist/cjs/renderer/index.js +1 -0
- package/dist/cjs/renderer/types.js +1 -0
- package/dist/esm/core/binary-codec/binary-codec.js +1 -0
- package/dist/esm/core/binary-codec/index.js +1 -0
- package/dist/esm/core/driver/driver.js +1 -0
- package/dist/esm/core/driver/drivers/immediate.js +1 -0
- package/dist/esm/core/driver/drivers/index.js +1 -0
- package/dist/esm/core/driver/drivers/raf.js +1 -0
- package/dist/esm/core/driver/drivers/timeout.js +1 -0
- package/dist/esm/core/driver/index.js +1 -0
- package/dist/esm/core/events/event-system.js +1 -0
- package/dist/esm/core/events/index.js +1 -0
- package/dist/esm/core/fixed-ticker/fixed-ticker.js +1 -0
- package/dist/esm/core/fixed-ticker/index.js +1 -0
- package/dist/esm/core/free-list/free-list.js +1 -0
- package/dist/esm/core/free-list/index.js +1 -0
- package/dist/esm/core/generate-id/generate-id.js +1 -0
- package/dist/esm/core/generate-id/index.js +1 -0
- package/dist/esm/core/index.js +1 -0
- package/dist/esm/core/input/index.js +1 -0
- package/dist/esm/core/input/manager.js +1 -0
- package/dist/esm/core/input/sources/browser.js +1 -0
- package/dist/esm/core/input/sources/index.js +1 -0
- package/dist/esm/core/input/types.js +0 -0
- package/dist/esm/core/lerp/index.js +1 -0
- package/dist/esm/core/lerp/lerp.js +1 -0
- package/dist/esm/core/navmesh/index.js +1 -0
- package/dist/esm/core/navmesh/navmesh-worker-pool.js +1 -0
- package/dist/esm/core/navmesh/navmesh.js +1 -0
- package/dist/esm/core/navmesh/navmesh.worker.js +1 -0
- package/dist/esm/core/pooled-codec/index.js +1 -0
- package/dist/esm/core/pooled-codec/pooled-codec.js +1 -0
- package/dist/esm/core/prediction/index.js +1 -0
- package/dist/esm/core/prediction/prediction.js +1 -0
- package/dist/esm/core/ray/index.js +1 -0
- package/dist/esm/core/ray/ray-2d.js +1 -0
- package/dist/esm/core/ray/ray-3d.js +1 -0
- package/dist/esm/core/simple-rng/index.js +1 -0
- package/dist/esm/core/simple-rng/simple-rng.js +1 -0
- package/dist/esm/core/sparse-batcher/index.js +1 -0
- package/dist/esm/core/sparse-batcher/sparse-batcher.js +1 -0
- package/dist/esm/ecs/component-store.js +1 -0
- package/dist/esm/ecs/component.js +1 -0
- package/dist/esm/ecs/entity-handle.js +1 -0
- package/dist/esm/ecs/index.js +1 -0
- package/dist/esm/ecs/system-builder.js +1 -0
- package/dist/esm/ecs/world-systems.js +1 -0
- package/dist/esm/ecs/world.js +1 -0
- package/dist/esm/game/index.js +1 -0
- package/dist/esm/game/loop/index.js +1 -0
- package/dist/esm/game/loop/loop.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/net/adapters/browser-websocket.js +1 -0
- package/dist/esm/net/adapters/bun-websocket.js +1 -0
- package/dist/esm/net/buffer-pool.js +1 -0
- package/dist/esm/net/client.js +1 -0
- package/dist/esm/net/index.js +1 -0
- package/dist/esm/net/server.js +1 -0
- package/dist/esm/net/types.js +1 -0
- package/dist/esm/net/validators.js +1 -0
- package/dist/esm/protocol/index.js +1 -0
- package/dist/esm/protocol/intent/define-intent.js +1 -0
- package/dist/esm/protocol/intent/index.js +1 -0
- package/dist/esm/protocol/intent/intent-registry.js +1 -0
- package/dist/esm/protocol/intent/intent.js +0 -0
- package/dist/esm/protocol/rpc/define-rpc.js +1 -0
- package/dist/esm/protocol/rpc/index.js +1 -0
- package/dist/esm/protocol/rpc/rpc-registry.js +1 -0
- package/dist/esm/protocol/rpc/rpc.js +0 -0
- package/dist/esm/protocol/snapshot/index.js +1 -0
- package/dist/esm/protocol/snapshot/snapshot-codec.js +1 -0
- package/dist/esm/protocol/snapshot/snapshot-registry.js +1 -0
- package/dist/esm/protocol/snapshot/snapshot.js +1 -0
- package/dist/esm/renderer/base-2d-renderer.js +1 -0
- package/dist/esm/renderer/base-3d-renderer.js +1 -0
- package/dist/esm/renderer/base-renderer.js +1 -0
- package/dist/esm/renderer/index.js +1 -0
- package/dist/esm/renderer/types.js +0 -0
- package/dist/{core → types/core}/binary-codec/binary-codec.d.ts +4 -0
- package/dist/{core → types/core}/events/event-system.d.ts +14 -33
- package/dist/types/core/free-list/free-list.d.ts +31 -0
- package/dist/types/core/free-list/index.d.ts +1 -0
- package/dist/{core → types/core}/index.d.ts +5 -0
- package/dist/{core → types/core}/input/index.d.ts +1 -0
- package/dist/{core → types/core}/input/manager.d.ts +2 -0
- package/dist/{core → types/core}/navmesh/navmesh.d.ts +1 -21
- package/dist/types/core/ray/index.d.ts +2 -0
- package/dist/types/core/ray/ray-2d.d.ts +37 -0
- package/dist/types/core/ray/ray-3d.d.ts +42 -0
- package/dist/types/core/simple-rng/index.d.ts +1 -0
- package/dist/types/core/simple-rng/simple-rng.d.ts +36 -0
- package/dist/types/core/sparse-batcher/index.d.ts +1 -0
- package/dist/types/core/sparse-batcher/sparse-batcher.d.ts +55 -0
- package/dist/{ecs → types/ecs}/world.d.ts +11 -0
- package/dist/{game → types/game}/loop/loop.d.ts +33 -29
- package/dist/{index.d.ts → types/index.d.ts} +1 -0
- package/dist/{net → types/net}/index.d.ts +2 -2
- package/dist/types/renderer/base-2d-renderer.d.ts +13 -0
- package/dist/types/renderer/base-3d-renderer.d.ts +10 -0
- package/dist/types/renderer/base-renderer.d.ts +21 -0
- package/dist/types/renderer/index.d.ts +4 -0
- package/dist/types/renderer/types.d.ts +79 -0
- package/dist/webgpu/cjs/index.js +6004 -0
- package/dist/webgpu/esm/index.js +5972 -0
- package/dist/webgpu/types/2d/animation.d.ts +97 -0
- package/dist/webgpu/types/2d/renderer.d.ts +55 -0
- package/dist/webgpu/types/2d/shader.d.ts +61 -0
- package/dist/webgpu/types/2d/sprite-accessor.d.ts +47 -0
- package/dist/webgpu/types/3d/gltf-skin-parser.d.ts +101 -0
- package/dist/webgpu/types/3d/morph-animation.d.ts +69 -0
- package/dist/webgpu/types/3d/morph-animation.test.d.ts +1 -0
- package/dist/webgpu/types/3d/renderer.d.ts +216 -0
- package/dist/webgpu/types/3d/shader.d.ts +136 -0
- package/dist/webgpu/types/3d/skeletal-animation-compute/index.d.ts +2 -0
- package/dist/webgpu/types/3d/skeletal-animation-compute/kernel.d.ts +8 -0
- package/dist/webgpu/types/3d/skeletal-animation-compute/packer.d.ts +32 -0
- package/dist/webgpu/types/3d/skeletal-animation.d.ts +90 -0
- package/dist/webgpu/types/camera/camera-2d.d.ts +53 -0
- package/dist/webgpu/types/camera/camera-2d.test.d.ts +1 -0
- package/dist/webgpu/types/camera/camera-3d.d.ts +81 -0
- package/dist/webgpu/types/camera/camera-3d.test.d.ts +1 -0
- package/dist/webgpu/types/camera/index.d.ts +2 -0
- package/dist/webgpu/types/compute/compute-builder.d.ts +123 -0
- package/dist/webgpu/types/compute/compute-builder.test.d.ts +1 -0
- package/dist/webgpu/types/core/constants.d.ts +59 -0
- package/dist/webgpu/types/core/constants.test.d.ts +1 -0
- package/dist/webgpu/types/core/index.d.ts +2 -0
- package/dist/webgpu/types/core/math.d.ts +37 -0
- package/dist/webgpu/types/core/types.d.ts +125 -0
- package/dist/webgpu/types/core/types.test.d.ts +1 -0
- package/dist/webgpu/types/geometry/built-in.d.ts +58 -0
- package/dist/webgpu/types/geometry/built-in.test.d.ts +1 -0
- package/dist/webgpu/types/geometry/geometry-builder.d.ts +281 -0
- package/dist/webgpu/types/geometry/geometry-builder.test.d.ts +1 -0
- package/dist/webgpu/types/geometry/index.d.ts +2 -0
- package/dist/webgpu/types/index.d.ts +32 -0
- package/dist/webgpu/types/particle/emitter.d.ts +36 -0
- package/dist/webgpu/types/shaders/index.d.ts +2 -0
- package/dist/webgpu/types/shaders/runtime-transpile.d.ts +18 -0
- package/dist/webgpu/types/shaders/sprite-2d.wgsl.d.ts +10 -0
- package/dist/webgpu/types/shaders/typegpu.d.ts +9 -0
- package/dist/webgpu/types/shaders/utils.d.ts +28 -0
- package/dist/webgpu/types/shaders/utils.test.d.ts +1 -0
- package/dist/webgpu/types/spritesheet/index.d.ts +1 -0
- package/dist/webgpu/types/spritesheet/spritesheet.d.ts +57 -0
- package/dist/webgpu/types/spritesheet/spritesheet.test.d.ts +1 -0
- package/package.json +96 -26
- package/dist/core/binary-codec/binary-codec.js +0 -354
- package/dist/core/binary-codec/index.js +0 -1
- package/dist/core/driver/driver.js +0 -47
- package/dist/core/driver/drivers/immediate.js +0 -61
- package/dist/core/driver/drivers/index.js +0 -3
- package/dist/core/driver/drivers/raf.js +0 -62
- package/dist/core/driver/drivers/timeout.js +0 -71
- package/dist/core/driver/index.js +0 -2
- package/dist/core/events/event-system.js +0 -88
- package/dist/core/events/index.js +0 -1
- package/dist/core/fixed-ticker/fixed-ticker.js +0 -105
- package/dist/core/fixed-ticker/index.js +0 -1
- package/dist/core/generate-id/generate-id.js +0 -25
- package/dist/core/generate-id/index.js +0 -1
- package/dist/core/index.js +0 -10
- package/dist/core/input/index.js +0 -2
- package/dist/core/input/manager.js +0 -211
- package/dist/core/input/sources/browser.js +0 -29
- package/dist/core/input/sources/index.js +0 -1
- package/dist/core/lerp/index.js +0 -1
- package/dist/core/lerp/lerp.js +0 -42
- package/dist/core/navmesh/index.js +0 -1
- package/dist/core/navmesh/navmesh-worker-pool.js +0 -180
- package/dist/core/navmesh/navmesh.js +0 -799
- package/dist/core/navmesh/navmesh.worker.js +0 -79
- package/dist/core/pooled-codec/index.js +0 -1
- package/dist/core/pooled-codec/pooled-codec.js +0 -410
- package/dist/core/prediction/index.js +0 -1
- package/dist/core/prediction/prediction.js +0 -99
- package/dist/core.esm.js +0 -1
- package/dist/core.js +0 -1
- package/dist/ecs/component-store.js +0 -175
- package/dist/ecs/component.js +0 -43
- package/dist/ecs/entity-handle.js +0 -515
- package/dist/ecs/example.js +0 -125
- package/dist/ecs/index.js +0 -4
- package/dist/ecs/system-builder.js +0 -249
- package/dist/ecs/world-systems.js +0 -79
- package/dist/ecs/world.js +0 -767
- package/dist/game/index.js +0 -1
- package/dist/game/loop/index.js +0 -1
- package/dist/game/loop/loop.js +0 -108
- package/dist/index.js +0 -26
- package/dist/net/adapters/browser-websocket.js +0 -74
- package/dist/net/adapters/bun-websocket.js +0 -245
- package/dist/net/buffer-pool.js +0 -89
- package/dist/net/client.js +0 -586
- package/dist/net/index.js +0 -58
- package/dist/net/server.js +0 -974
- package/dist/net/types.js +0 -31
- package/dist/net/validators.js +0 -88
- package/dist/protocol/index.js +0 -92
- package/dist/protocol/intent/define-intent.js +0 -125
- package/dist/protocol/intent/index.js +0 -91
- package/dist/protocol/intent/intent-registry.js +0 -91
- package/dist/protocol/rpc/define-rpc.js +0 -84
- package/dist/protocol/rpc/index.js +0 -3
- package/dist/protocol/rpc/rpc-registry.js +0 -159
- package/dist/protocol/rpc/rpc.js +0 -12
- package/dist/protocol/snapshot/index.js +0 -43
- package/dist/protocol/snapshot/snapshot-codec.js +0 -67
- package/dist/protocol/snapshot/snapshot-registry.js +0 -168
- package/dist/protocol/snapshot/snapshot.js +0 -30
- package/src/core/binary-codec/README.md +0 -60
- package/src/core/binary-codec/binary-codec.test.ts +0 -300
- package/src/core/binary-codec/binary-codec.ts +0 -448
- package/src/core/binary-codec/index.ts +0 -1
- package/src/core/driver/README.md +0 -97
- package/src/core/driver/driver.test.ts +0 -414
- package/src/core/driver/driver.ts +0 -71
- package/src/core/driver/drivers/immediate.ts +0 -66
- package/src/core/driver/drivers/index.ts +0 -3
- package/src/core/driver/drivers/raf.ts +0 -67
- package/src/core/driver/drivers/timeout.ts +0 -77
- package/src/core/driver/index.ts +0 -2
- package/src/core/events/README.md +0 -47
- package/src/core/events/event-system.test.ts +0 -243
- package/src/core/events/event-system.ts +0 -140
- package/src/core/events/index.ts +0 -1
- package/src/core/fixed-ticker/README.md +0 -77
- package/src/core/fixed-ticker/fixed-ticker.test.ts +0 -151
- package/src/core/fixed-ticker/fixed-ticker.ts +0 -174
- package/src/core/fixed-ticker/index.ts +0 -1
- package/src/core/generate-id/README.md +0 -18
- package/src/core/generate-id/generate-id.test.ts +0 -79
- package/src/core/generate-id/generate-id.ts +0 -37
- package/src/core/generate-id/index.ts +0 -1
- package/src/core/index.ts +0 -10
- package/src/core/input/README.md +0 -24
- package/src/core/input/index.ts +0 -2
- package/src/core/input/manager.ts +0 -259
- package/src/core/input/sources/browser.ts +0 -39
- package/src/core/input/sources/index.ts +0 -1
- package/src/core/input/types.ts +0 -40
- package/src/core/lerp/README.md +0 -79
- package/src/core/lerp/index.ts +0 -1
- package/src/core/lerp/lerp.test.ts +0 -90
- package/src/core/lerp/lerp.ts +0 -42
- package/src/core/navmesh/README.md +0 -164
- package/src/core/navmesh/index.ts +0 -1
- package/src/core/navmesh/navmesh-worker-pool.ts +0 -236
- package/src/core/navmesh/navmesh-workers.test.ts +0 -356
- package/src/core/navmesh/navmesh.test.ts +0 -344
- package/src/core/navmesh/navmesh.ts +0 -1047
- package/src/core/navmesh/navmesh.worker.ts +0 -147
- package/src/core/pooled-codec/README.md +0 -70
- package/src/core/pooled-codec/index.ts +0 -1
- package/src/core/pooled-codec/pooled-codec.test.ts +0 -862
- package/src/core/pooled-codec/pooled-codec.ts +0 -504
- package/src/core/prediction/README.md +0 -64
- package/src/core/prediction/index.ts +0 -1
- package/src/core/prediction/prediction.test.ts +0 -423
- package/src/core/prediction/prediction.ts +0 -112
- package/src/ecs/README.md +0 -427
- package/src/ecs/benchmark.test.ts +0 -1645
- package/src/ecs/component-store.ts +0 -198
- package/src/ecs/component.ts +0 -90
- package/src/ecs/entity-handle.test.ts +0 -393
- package/src/ecs/entity-handle.ts +0 -563
- package/src/ecs/example.ts +0 -152
- package/src/ecs/index.ts +0 -4
- package/src/ecs/system-builder.ts +0 -404
- package/src/ecs/world-systems.ts +0 -83
- package/src/ecs/world.test.ts +0 -310
- package/src/ecs/world.ts +0 -904
- package/src/game/index.ts +0 -1
- package/src/game/loop/README.md +0 -32
- package/src/game/loop/index.ts +0 -1
- package/src/game/loop/loop.ts +0 -236
- package/src/index.ts +0 -32
- package/src/net/README.md +0 -474
- package/src/net/adapters/browser-websocket.ts +0 -86
- package/src/net/adapters/bun-websocket.ts +0 -292
- package/src/net/buffer-pool.ts +0 -106
- package/src/net/client.test.ts +0 -807
- package/src/net/client.ts +0 -695
- package/src/net/index.ts +0 -60
- package/src/net/server.test.ts +0 -799
- package/src/net/server.ts +0 -1152
- package/src/net/types.ts +0 -228
- package/src/net/validators.ts +0 -104
- package/src/protocol/README.md +0 -469
- package/src/protocol/index.ts +0 -93
- package/src/protocol/intent/define-intent.test.ts +0 -397
- package/src/protocol/intent/define-intent.ts +0 -201
- package/src/protocol/intent/index.ts +0 -94
- package/src/protocol/intent/intent-registry.test.ts +0 -198
- package/src/protocol/intent/intent-registry.ts +0 -112
- package/src/protocol/intent/intent.ts +0 -12
- package/src/protocol/rpc/define-rpc.test.ts +0 -141
- package/src/protocol/rpc/define-rpc.ts +0 -113
- package/src/protocol/rpc/index.ts +0 -3
- package/src/protocol/rpc/rpc-registry.test.ts +0 -168
- package/src/protocol/rpc/rpc-registry.ts +0 -176
- package/src/protocol/rpc/rpc.ts +0 -37
- package/src/protocol/snapshot/index.ts +0 -45
- package/src/protocol/snapshot/snapshot-codec.test.ts +0 -138
- package/src/protocol/snapshot/snapshot-codec.ts +0 -87
- package/src/protocol/snapshot/snapshot-registry.test.ts +0 -310
- package/src/protocol/snapshot/snapshot-registry.ts +0 -201
- package/src/protocol/snapshot/snapshot.test.ts +0 -76
- package/src/protocol/snapshot/snapshot.ts +0 -41
- /package/dist/{core → types/core}/binary-codec/index.d.ts +0 -0
- /package/dist/{core → types/core}/driver/driver.d.ts +0 -0
- /package/dist/{core → types/core}/driver/drivers/immediate.d.ts +0 -0
- /package/dist/{core → types/core}/driver/drivers/index.d.ts +0 -0
- /package/dist/{core → types/core}/driver/drivers/raf.d.ts +0 -0
- /package/dist/{core → types/core}/driver/drivers/timeout.d.ts +0 -0
- /package/dist/{core → types/core}/driver/index.d.ts +0 -0
- /package/dist/{core → types/core}/events/index.d.ts +0 -0
- /package/dist/{core → types/core}/fixed-ticker/fixed-ticker.d.ts +0 -0
- /package/dist/{core → types/core}/fixed-ticker/index.d.ts +0 -0
- /package/dist/{core → types/core}/generate-id/generate-id.d.ts +0 -0
- /package/dist/{core → types/core}/generate-id/index.d.ts +0 -0
- /package/dist/{core → types/core}/input/sources/browser.d.ts +0 -0
- /package/dist/{core → types/core}/input/sources/index.d.ts +0 -0
- /package/dist/{core → types/core}/input/types.d.ts +0 -0
- /package/dist/{core → types/core}/lerp/index.d.ts +0 -0
- /package/dist/{core → types/core}/lerp/lerp.d.ts +0 -0
- /package/dist/{core → types/core}/navmesh/index.d.ts +0 -0
- /package/dist/{core → types/core}/navmesh/navmesh-worker-pool.d.ts +0 -0
- /package/dist/{core → types/core}/navmesh/navmesh.worker.d.ts +0 -0
- /package/dist/{core → types/core}/pooled-codec/index.d.ts +0 -0
- /package/dist/{core → types/core}/pooled-codec/pooled-codec.d.ts +0 -0
- /package/dist/{core → types/core}/prediction/index.d.ts +0 -0
- /package/dist/{core → types/core}/prediction/prediction.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/component-store.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/component.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/entity-handle.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/example.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/index.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/system-builder.d.ts +0 -0
- /package/dist/{ecs → types/ecs}/world-systems.d.ts +0 -0
- /package/dist/{game → types/game}/index.d.ts +0 -0
- /package/dist/{game → types/game}/loop/index.d.ts +0 -0
- /package/dist/{net → types/net}/adapters/browser-websocket.d.ts +0 -0
- /package/dist/{net → types/net}/adapters/bun-websocket.d.ts +0 -0
- /package/dist/{net → types/net}/buffer-pool.d.ts +0 -0
- /package/dist/{net → types/net}/client.d.ts +0 -0
- /package/dist/{net → types/net}/server.d.ts +0 -0
- /package/dist/{net → types/net}/types.d.ts +0 -0
- /package/dist/{net → types/net}/validators.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/index.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/intent/define-intent.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/intent/index.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/intent/intent-registry.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/intent/intent.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/rpc/define-rpc.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/rpc/index.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/rpc/rpc-registry.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/rpc/rpc.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/snapshot/index.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/snapshot/snapshot-codec.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/snapshot/snapshot-registry.d.ts +0 -0
- /package/dist/{protocol → types/protocol}/snapshot/snapshot.d.ts +0 -0
- /package/dist/{core/input/types.js → webgpu/types/2d/animation.test.d.ts} +0 -0
- /package/dist/{protocol/intent/intent.js → webgpu/types/2d/sprite-accessor.test.d.ts} +0 -0
|
@@ -1,1645 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { BinaryCodec } from "../core/binary-codec";
|
|
3
|
-
import { defineComponent } from "./component";
|
|
4
|
-
import { World } from "./world";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
describe("ECS Performance Benchmarks", () => {
|
|
8
|
-
// Define components for benchmarking
|
|
9
|
-
const Transform = defineComponent("Transform", {
|
|
10
|
-
x: BinaryCodec.f32,
|
|
11
|
-
y: BinaryCodec.f32,
|
|
12
|
-
rotation: BinaryCodec.f32,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const Velocity = defineComponent("Velocity", {
|
|
16
|
-
vx: BinaryCodec.f32,
|
|
17
|
-
vy: BinaryCodec.f32,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const Health = defineComponent("Health", {
|
|
21
|
-
current: BinaryCodec.u16,
|
|
22
|
-
max: BinaryCodec.u16,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("spawn/despawn 10,000 entities (should be < 50ms)", () => {
|
|
26
|
-
const world = new World({
|
|
27
|
-
maxEntities: 10000,
|
|
28
|
-
components: [Transform, Velocity, Health],
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const start = performance.now();
|
|
32
|
-
|
|
33
|
-
// Spawn 10,000 entities
|
|
34
|
-
const entities: number[] = [];
|
|
35
|
-
for (let i = 0; i < 10000; i++) {
|
|
36
|
-
entities.push(world.spawn());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Despawn all
|
|
40
|
-
for (const entity of entities) {
|
|
41
|
-
world.despawn(entity);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const elapsed = performance.now() - start;
|
|
45
|
-
|
|
46
|
-
console.log(`Spawn/despawn 10k entities: ${elapsed.toFixed(2)}ms`);
|
|
47
|
-
expect(elapsed).toBeLessThan(50);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("add components to 10,000 entities (should be < 100ms)", () => {
|
|
51
|
-
const world = new World({
|
|
52
|
-
maxEntities: 10000,
|
|
53
|
-
components: [Transform, Velocity, Health],
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Spawn entities
|
|
57
|
-
const entities: number[] = [];
|
|
58
|
-
for (let i = 0; i < 10000; i++) {
|
|
59
|
-
entities.push(world.spawn());
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const start = performance.now();
|
|
63
|
-
|
|
64
|
-
// Add components
|
|
65
|
-
for (const entity of entities) {
|
|
66
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
67
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
68
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const elapsed = performance.now() - start;
|
|
72
|
-
|
|
73
|
-
console.log(`Add 3 components to 10k entities: ${elapsed.toFixed(2)}ms`);
|
|
74
|
-
expect(elapsed).toBeLessThan(100);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("query and update 10,000 entities (should be < 20ms)", () => {
|
|
78
|
-
const world = new World({
|
|
79
|
-
maxEntities: 10000,
|
|
80
|
-
components: [Transform, Velocity],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Setup: spawn and add components
|
|
84
|
-
for (let i = 0; i < 10000; i++) {
|
|
85
|
-
const entity = world.spawn();
|
|
86
|
-
world.add(entity, Transform, { x: i, y: i, rotation: 0 });
|
|
87
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const start = performance.now();
|
|
91
|
-
|
|
92
|
-
// Query and update (simulates physics system)
|
|
93
|
-
const deltaTime = 0.016;
|
|
94
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
95
|
-
const t = world.get(entity, Transform);
|
|
96
|
-
const v = world.get(entity, Velocity);
|
|
97
|
-
|
|
98
|
-
// Update using partial update (most efficient)
|
|
99
|
-
world.update(entity, Transform, {
|
|
100
|
-
x: t.x + v.vx * deltaTime,
|
|
101
|
-
y: t.y + v.vy * deltaTime,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const elapsed = performance.now() - start;
|
|
106
|
-
|
|
107
|
-
console.log(`Query + update 10k entities: ${elapsed.toFixed(2)}ms`);
|
|
108
|
-
expect(elapsed).toBeLessThan(20);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("repeated queries with caching (should be < 5ms)", () => {
|
|
112
|
-
const world = new World({
|
|
113
|
-
maxEntities: 5000,
|
|
114
|
-
components: [Transform, Velocity, Health],
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Setup
|
|
118
|
-
for (let i = 0; i < 5000; i++) {
|
|
119
|
-
const entity = world.spawn();
|
|
120
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
121
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const start = performance.now();
|
|
125
|
-
|
|
126
|
-
// Run the same query 10 times (should be cached)
|
|
127
|
-
for (let iteration = 0; iteration < 10; iteration++) {
|
|
128
|
-
let count = 0;
|
|
129
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
130
|
-
const t = world.get(entity, Transform);
|
|
131
|
-
world.update(entity, Transform, { x: t.x + 1 });
|
|
132
|
-
count++;
|
|
133
|
-
}
|
|
134
|
-
expect(count).toBe(5000);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const elapsed = performance.now() - start;
|
|
138
|
-
|
|
139
|
-
console.log(`10 iterations of query/update 5k entities: ${elapsed.toFixed(2)}ms`);
|
|
140
|
-
expect(elapsed).toBeLessThan(50);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("memory efficiency: ArrayBuffer vs Float32Array savings", () => {
|
|
144
|
-
const world = new World({
|
|
145
|
-
maxEntities: 10000,
|
|
146
|
-
components: [Health], // u16 + u16 = 4 bytes
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Spawn entities with Health component
|
|
150
|
-
for (let i = 0; i < 10000; i++) {
|
|
151
|
-
const entity = world.spawn();
|
|
152
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Health component: 2 × u16 = 4 bytes per entity
|
|
156
|
-
// ArrayBuffer: 10,000 × 4 = 40 KB
|
|
157
|
-
// Float32Array (old): 10,000 × 8 = 80 KB (would round up to 2 floats)
|
|
158
|
-
// Savings: 50%
|
|
159
|
-
|
|
160
|
-
const expectedBytes = 10000 * 4;
|
|
161
|
-
console.log(`Memory for 10k Health components: ${(expectedBytes / 1024).toFixed(2)} KB`);
|
|
162
|
-
console.log(`(Float32Array would use: ${(10000 * 8 / 1024).toFixed(2)} KB - 50% savings!)`);
|
|
163
|
-
|
|
164
|
-
expect(true).toBe(true); // Memory check is informational
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("zero allocations in hot path", () => {
|
|
168
|
-
const world = new World({
|
|
169
|
-
maxEntities: 1000,
|
|
170
|
-
components: [Transform, Velocity],
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Setup
|
|
174
|
-
for (let i = 0; i < 1000; i++) {
|
|
175
|
-
const entity = world.spawn();
|
|
176
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
177
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Force GC
|
|
181
|
-
if (global.gc) global.gc();
|
|
182
|
-
|
|
183
|
-
const memBefore = (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2);
|
|
184
|
-
|
|
185
|
-
// Run many iterations (should have zero allocations due to reusable objects)
|
|
186
|
-
for (let i = 0; i < 100; i++) {
|
|
187
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
188
|
-
const t = world.get(entity, Transform); // Reuses same object!
|
|
189
|
-
const v = world.get(entity, Velocity); // Reuses same object!
|
|
190
|
-
world.update(entity, Transform, {
|
|
191
|
-
x: t.x + v.vx,
|
|
192
|
-
y: t.y + v.vy,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const memAfter = (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2);
|
|
198
|
-
|
|
199
|
-
console.log(`Memory before: ${memBefore} MB, after: ${memAfter} MB`);
|
|
200
|
-
console.log(`Memory delta: ${(parseFloat(memAfter) - parseFloat(memBefore)).toFixed(2)} MB (should be ~0)`);
|
|
201
|
-
|
|
202
|
-
// Memory should not grow significantly (< 5 MB for 100 iterations × 1000 entities)
|
|
203
|
-
const delta = parseFloat(memAfter) - parseFloat(memBefore);
|
|
204
|
-
expect(delta).toBeLessThan(5);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("realistic game loop: 1000 entities at 60 FPS", () => {
|
|
208
|
-
const world = new World({
|
|
209
|
-
maxEntities: 2000,
|
|
210
|
-
components: [Transform, Velocity, Health],
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Setup game world
|
|
214
|
-
for (let i = 0; i < 1000; i++) {
|
|
215
|
-
const entity = world.spawn();
|
|
216
|
-
world.add(entity, Transform, { x: Math.random() * 800, y: Math.random() * 600, rotation: 0 });
|
|
217
|
-
world.add(entity, Velocity, { vx: (Math.random() - 0.5) * 100, vy: (Math.random() - 0.5) * 100 });
|
|
218
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const frameTimings: number[] = [];
|
|
222
|
-
const targetFPS = 60;
|
|
223
|
-
const targetFrameTime = 1000 / targetFPS; // 16.67ms
|
|
224
|
-
|
|
225
|
-
// Simulate 100 frames
|
|
226
|
-
for (let frame = 0; frame < 100; frame++) {
|
|
227
|
-
const frameStart = performance.now();
|
|
228
|
-
|
|
229
|
-
const deltaTime = 1 / 60;
|
|
230
|
-
|
|
231
|
-
// Physics system
|
|
232
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
233
|
-
const t = world.get(entity, Transform);
|
|
234
|
-
const v = world.get(entity, Velocity);
|
|
235
|
-
|
|
236
|
-
world.update(entity, Transform, {
|
|
237
|
-
x: t.x + v.vx * deltaTime,
|
|
238
|
-
y: t.y + v.vy * deltaTime,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Health system
|
|
243
|
-
for (const entity of world.query(Health)) {
|
|
244
|
-
const h = world.get(entity, Health);
|
|
245
|
-
if (h.current <= 0) {
|
|
246
|
-
world.despawn(entity);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const frameTime = performance.now() - frameStart;
|
|
251
|
-
frameTimings.push(frameTime);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const avgFrameTime = frameTimings.reduce((a, b) => a + b, 0) / frameTimings.length;
|
|
255
|
-
const maxFrameTime = Math.max(...frameTimings);
|
|
256
|
-
|
|
257
|
-
console.log(`Average frame time: ${avgFrameTime.toFixed(2)}ms (${(1000 / avgFrameTime).toFixed(0)} FPS)`);
|
|
258
|
-
console.log(`Max frame time: ${maxFrameTime.toFixed(2)}ms`);
|
|
259
|
-
console.log(`Target: ${targetFrameTime.toFixed(2)}ms (60 FPS)`);
|
|
260
|
-
|
|
261
|
-
// Should easily maintain 60 FPS
|
|
262
|
-
expect(avgFrameTime).toBeLessThan(targetFrameTime);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test("performance shows zero-allocation benefit in repeated queries", () => {
|
|
266
|
-
const world = new World({
|
|
267
|
-
maxEntities: 10000,
|
|
268
|
-
components: [Transform],
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Setup
|
|
272
|
-
for (let i = 0; i < 10000; i++) {
|
|
273
|
-
const entity = world.spawn();
|
|
274
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// The key benefit of zero allocations shows up in GC pressure over time
|
|
278
|
-
// Single-pass comparison doesn't show the full picture
|
|
279
|
-
console.log("\nZero-allocation design benefits:");
|
|
280
|
-
console.log("- No GC pauses during gameplay");
|
|
281
|
-
console.log("- Consistent frame times");
|
|
282
|
-
console.log("- Lower memory pressure");
|
|
283
|
-
console.log("- See 'zero allocations in hot path' test for proof");
|
|
284
|
-
|
|
285
|
-
expect(true).toBe(true); // This is informational
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("benchmark: spawn performance at scale", () => {
|
|
289
|
-
console.log("\n=== Spawn Performance Benchmark ===");
|
|
290
|
-
|
|
291
|
-
const sizes = [1000, 5000, 10000, 50000];
|
|
292
|
-
|
|
293
|
-
for (const size of sizes) {
|
|
294
|
-
const world = new World({
|
|
295
|
-
maxEntities: size,
|
|
296
|
-
components: [Transform],
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const start = performance.now();
|
|
300
|
-
for (let i = 0; i < size; i++) {
|
|
301
|
-
world.spawn();
|
|
302
|
-
}
|
|
303
|
-
const elapsed = performance.now() - start;
|
|
304
|
-
|
|
305
|
-
console.log(`Spawn ${size.toLocaleString()} entities: ${elapsed.toFixed(2)}ms (${(size / elapsed * 1000).toFixed(0)} entities/sec)`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
expect(true).toBe(true);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("benchmark: spawn + despawn cycle (entity reuse)", () => {
|
|
312
|
-
console.log("\n=== Spawn/Despawn Cycle Benchmark ===");
|
|
313
|
-
|
|
314
|
-
const world = new World({
|
|
315
|
-
maxEntities: 10000,
|
|
316
|
-
components: [Transform],
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Initial spawn
|
|
320
|
-
const entities: number[] = [];
|
|
321
|
-
for (let i = 0; i < 10000; i++) {
|
|
322
|
-
entities.push(world.spawn());
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Measure despawn
|
|
326
|
-
const despawnStart = performance.now();
|
|
327
|
-
for (const entity of entities) {
|
|
328
|
-
world.despawn(entity);
|
|
329
|
-
}
|
|
330
|
-
const despawnTime = performance.now() - despawnStart;
|
|
331
|
-
|
|
332
|
-
// Measure respawn (should reuse IDs)
|
|
333
|
-
const respawnStart = performance.now();
|
|
334
|
-
for (let i = 0; i < 10000; i++) {
|
|
335
|
-
world.spawn();
|
|
336
|
-
}
|
|
337
|
-
const respawnTime = performance.now() - respawnStart;
|
|
338
|
-
|
|
339
|
-
console.log(`Despawn 10k entities: ${despawnTime.toFixed(2)}ms`);
|
|
340
|
-
console.log(`Respawn 10k entities (ID reuse): ${respawnTime.toFixed(2)}ms`);
|
|
341
|
-
console.log(`Total cycle: ${(despawnTime + respawnTime).toFixed(2)}ms`);
|
|
342
|
-
|
|
343
|
-
expect(world.getEntityCount()).toBe(10000);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("benchmark: component add/remove operations", () => {
|
|
347
|
-
console.log("\n=== Component Operations Benchmark ===");
|
|
348
|
-
|
|
349
|
-
const world = new World({
|
|
350
|
-
maxEntities: 10000,
|
|
351
|
-
components: [Transform, Velocity, Health],
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const entities: number[] = [];
|
|
355
|
-
for (let i = 0; i < 10000; i++) {
|
|
356
|
-
entities.push(world.spawn());
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Add components
|
|
360
|
-
const addStart = performance.now();
|
|
361
|
-
for (const entity of entities) {
|
|
362
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
363
|
-
world.add(entity, Velocity, { vx: 0, vy: 0 });
|
|
364
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
365
|
-
}
|
|
366
|
-
const addTime = performance.now() - addStart;
|
|
367
|
-
|
|
368
|
-
// Remove components
|
|
369
|
-
const removeStart = performance.now();
|
|
370
|
-
for (const entity of entities) {
|
|
371
|
-
world.remove(entity, Velocity);
|
|
372
|
-
}
|
|
373
|
-
const removeTime = performance.now() - removeStart;
|
|
374
|
-
|
|
375
|
-
console.log(`Add 3 components to 10k entities: ${addTime.toFixed(2)}ms`);
|
|
376
|
-
console.log(`Remove 1 component from 10k entities: ${removeTime.toFixed(2)}ms`);
|
|
377
|
-
|
|
378
|
-
expect(true).toBe(true);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
test("benchmark: query performance with different entity counts", () => {
|
|
382
|
-
console.log("\n=== Query Performance Benchmark ===");
|
|
383
|
-
|
|
384
|
-
const sizes = [100, 1000, 5000, 10000];
|
|
385
|
-
|
|
386
|
-
for (const size of sizes) {
|
|
387
|
-
const world = new World({
|
|
388
|
-
maxEntities: size,
|
|
389
|
-
components: [Transform, Velocity],
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Setup entities
|
|
393
|
-
for (let i = 0; i < size; i++) {
|
|
394
|
-
const entity = world.spawn();
|
|
395
|
-
world.add(entity, Transform, { x: i, y: i, rotation: 0 });
|
|
396
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Single query
|
|
400
|
-
const singleStart = performance.now();
|
|
401
|
-
world.query(Transform, Velocity);
|
|
402
|
-
const singleTime = performance.now() - singleStart;
|
|
403
|
-
|
|
404
|
-
// 100 queries (typical frame)
|
|
405
|
-
const multiStart = performance.now();
|
|
406
|
-
for (let i = 0; i < 100; i++) {
|
|
407
|
-
world.query(Transform, Velocity);
|
|
408
|
-
}
|
|
409
|
-
const multiTime = performance.now() - multiStart;
|
|
410
|
-
|
|
411
|
-
console.log(`Query ${size.toLocaleString()} entities: ${singleTime.toFixed(3)}ms (single), ${multiTime.toFixed(2)}ms (100x)`);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
expect(true).toBe(true);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("benchmark: get() vs getMutable() performance", () => {
|
|
418
|
-
console.log("\n=== Get vs GetMutable Benchmark ===");
|
|
419
|
-
|
|
420
|
-
const world = new World({
|
|
421
|
-
maxEntities: 10000,
|
|
422
|
-
components: [Transform],
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
const entities: number[] = [];
|
|
426
|
-
for (let i = 0; i < 10000; i++) {
|
|
427
|
-
const entity = world.spawn();
|
|
428
|
-
world.add(entity, Transform, { x: i, y: i, rotation: 0 });
|
|
429
|
-
entities.push(entity);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Benchmark get() (readonly, reusable)
|
|
433
|
-
const getStart = performance.now();
|
|
434
|
-
let sum1 = 0;
|
|
435
|
-
for (const entity of entities) {
|
|
436
|
-
const t = world.get(entity, Transform);
|
|
437
|
-
sum1 += t.x + t.y; // Use values
|
|
438
|
-
}
|
|
439
|
-
const getTime = performance.now() - getStart;
|
|
440
|
-
|
|
441
|
-
// Benchmark getMutable() (allocates)
|
|
442
|
-
const getMutableStart = performance.now();
|
|
443
|
-
let sum2 = 0;
|
|
444
|
-
for (const entity of entities) {
|
|
445
|
-
const t = world.getMutable(entity, Transform);
|
|
446
|
-
sum2 += t.x + t.y; // Use values
|
|
447
|
-
}
|
|
448
|
-
const getMutableTime = performance.now() - getMutableStart;
|
|
449
|
-
|
|
450
|
-
console.log(`get() 10k times: ${getTime.toFixed(2)}ms (${(10000 / getTime * 1000).toFixed(0)} ops/sec)`);
|
|
451
|
-
console.log(`getMutable() 10k times: ${getMutableTime.toFixed(2)}ms (${(10000 / getMutableTime * 1000).toFixed(0)} ops/sec)`);
|
|
452
|
-
console.log(`get() is ${(getMutableTime / getTime).toFixed(1)}x faster`);
|
|
453
|
-
|
|
454
|
-
expect(getTime).toBeLessThan(getMutableTime);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
test("benchmark: update() vs set() for partial changes", () => {
|
|
458
|
-
console.log("\n=== Update vs Set Benchmark ===");
|
|
459
|
-
|
|
460
|
-
const world = new World({
|
|
461
|
-
maxEntities: 10000,
|
|
462
|
-
components: [Transform],
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
const entities: number[] = [];
|
|
466
|
-
for (let i = 0; i < 10000; i++) {
|
|
467
|
-
const entity = world.spawn();
|
|
468
|
-
world.add(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
469
|
-
entities.push(entity);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Benchmark update() (partial, optimized)
|
|
473
|
-
const updateStart = performance.now();
|
|
474
|
-
for (const entity of entities) {
|
|
475
|
-
world.update(entity, Transform, { x: 100 });
|
|
476
|
-
}
|
|
477
|
-
const updateTime = performance.now() - updateStart;
|
|
478
|
-
|
|
479
|
-
// Reset
|
|
480
|
-
for (const entity of entities) {
|
|
481
|
-
world.set(entity, Transform, { x: 0, y: 0, rotation: 0 });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Benchmark set() (full replace)
|
|
485
|
-
const setStart = performance.now();
|
|
486
|
-
for (const entity of entities) {
|
|
487
|
-
world.set(entity, Transform, { x: 100, y: 0, rotation: 0 });
|
|
488
|
-
}
|
|
489
|
-
const setTime = performance.now() - setStart;
|
|
490
|
-
|
|
491
|
-
console.log(`update() 1 field on 10k entities: ${updateTime.toFixed(2)}ms`);
|
|
492
|
-
console.log(`set() all fields on 10k entities: ${setTime.toFixed(2)}ms`);
|
|
493
|
-
console.log(`update() is ${(setTime / updateTime).toFixed(1)}x faster for partial changes`);
|
|
494
|
-
|
|
495
|
-
expect(true).toBe(true);
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test("benchmark: complex game simulation (realistic workload)", () => {
|
|
499
|
-
console.log("\n=== Complex Game Simulation Benchmark (10+ Systems) ===");
|
|
500
|
-
|
|
501
|
-
// Define additional components for more realistic simulation
|
|
502
|
-
const Armor = defineComponent("Armor", {
|
|
503
|
-
value: BinaryCodec.u16,
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
const Damage = defineComponent("Damage", {
|
|
507
|
-
amount: BinaryCodec.u16,
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
const Cooldown = defineComponent("Cooldown", {
|
|
511
|
-
current: BinaryCodec.f32,
|
|
512
|
-
max: BinaryCodec.f32,
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
const Team = defineComponent("Team", {
|
|
516
|
-
id: BinaryCodec.u8,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
const Target = defineComponent("Target", {
|
|
520
|
-
entityId: BinaryCodec.u32,
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const Status = defineComponent("Status", {
|
|
524
|
-
stunned: BinaryCodec.u8,
|
|
525
|
-
slowed: BinaryCodec.u8,
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
const Lifetime = defineComponent("Lifetime", {
|
|
529
|
-
remaining: BinaryCodec.f32,
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
const entityCounts = [500, 1000, 5000, 10000, 25000, 50000];
|
|
533
|
-
const fps60Budget = 16.67; // 60 FPS
|
|
534
|
-
const fps30Budget = 33.33; // 30 FPS
|
|
535
|
-
|
|
536
|
-
for (const count of entityCounts) {
|
|
537
|
-
const world = new World({
|
|
538
|
-
maxEntities: count,
|
|
539
|
-
components: [Transform, Velocity, Health, Armor, Damage, Cooldown, Team, Target, Status, Lifetime],
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
// Setup entities with varied component combinations
|
|
543
|
-
for (let i = 0; i < count; i++) {
|
|
544
|
-
const entity = world.spawn();
|
|
545
|
-
world.add(entity, Transform, { x: Math.random() * 1000, y: Math.random() * 1000, rotation: Math.random() * Math.PI * 2 });
|
|
546
|
-
world.add(entity, Velocity, { vx: Math.random() * 10 - 5, vy: Math.random() * 10 - 5 });
|
|
547
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
548
|
-
|
|
549
|
-
// 80% have armor
|
|
550
|
-
if (Math.random() > 0.2) {
|
|
551
|
-
world.add(entity, Armor, { value: Math.floor(Math.random() * 50) });
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// 60% can deal damage
|
|
555
|
-
if (Math.random() > 0.4) {
|
|
556
|
-
world.add(entity, Damage, { amount: Math.floor(Math.random() * 20) + 10 });
|
|
557
|
-
world.add(entity, Cooldown, { current: 0, max: 1.0 });
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Assign to teams
|
|
561
|
-
world.add(entity, Team, { id: Math.floor(Math.random() * 4) });
|
|
562
|
-
|
|
563
|
-
// 30% have targets
|
|
564
|
-
if (Math.random() > 0.7) {
|
|
565
|
-
world.add(entity, Target, { entityId: Math.floor(Math.random() * count) });
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// 20% have status effects
|
|
569
|
-
if (Math.random() > 0.8) {
|
|
570
|
-
world.add(entity, Status, { stunned: Math.random() > 0.5 ? 1 : 0, slowed: Math.random() > 0.5 ? 1 : 0 });
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// 15% are temporary entities (projectiles, effects, etc.)
|
|
574
|
-
if (Math.random() > 0.85) {
|
|
575
|
-
world.add(entity, Lifetime, { remaining: Math.random() * 5 });
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Simulate 60 frames
|
|
580
|
-
const frameCount = 60;
|
|
581
|
-
const deltaTime = 0.016;
|
|
582
|
-
const frameTimes: number[] = [];
|
|
583
|
-
|
|
584
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
585
|
-
const frameStart = performance.now();
|
|
586
|
-
|
|
587
|
-
// System 1: Movement system (applies velocity to transform)
|
|
588
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
589
|
-
const t = world.get(entity, Transform);
|
|
590
|
-
const v = world.get(entity, Velocity);
|
|
591
|
-
|
|
592
|
-
world.update(entity, Transform, {
|
|
593
|
-
x: t.x + v.vx * deltaTime,
|
|
594
|
-
y: t.y + v.vy * deltaTime,
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// System 2: Rotation system (rotate entities based on velocity)
|
|
599
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
600
|
-
const v = world.get(entity, Velocity);
|
|
601
|
-
if (v.vx !== 0 || v.vy !== 0) {
|
|
602
|
-
world.update(entity, Transform, {
|
|
603
|
-
rotation: Math.atan2(v.vy, v.vx),
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// System 3: Boundary system (wrap around screen edges)
|
|
609
|
-
for (const entity of world.query(Transform)) {
|
|
610
|
-
const t = world.get(entity, Transform);
|
|
611
|
-
let needsUpdate = false;
|
|
612
|
-
let newX = t.x;
|
|
613
|
-
let newY = t.y;
|
|
614
|
-
|
|
615
|
-
if (t.x < 0) { newX = 1000; needsUpdate = true; }
|
|
616
|
-
if (t.x > 1000) { newX = 0; needsUpdate = true; }
|
|
617
|
-
if (t.y < 0) { newY = 1000; needsUpdate = true; }
|
|
618
|
-
if (t.y > 1000) { newY = 0; needsUpdate = true; }
|
|
619
|
-
|
|
620
|
-
if (needsUpdate) {
|
|
621
|
-
world.update(entity, Transform, { x: newX, y: newY });
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// System 4: Health regeneration system
|
|
626
|
-
if (frame % 30 === 0) {
|
|
627
|
-
for (const entity of world.query(Health)) {
|
|
628
|
-
const h = world.get(entity, Health);
|
|
629
|
-
if (h.current > 0 && h.current < h.max) {
|
|
630
|
-
const newHealth = h.current + 5;
|
|
631
|
-
world.update(entity, Health, {
|
|
632
|
-
current: newHealth > h.max ? h.max : newHealth,
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// System 5: Cooldown system
|
|
639
|
-
for (const entity of world.query(Cooldown)) {
|
|
640
|
-
const cd = world.get(entity, Cooldown);
|
|
641
|
-
if (cd.current > 0) {
|
|
642
|
-
const newCooldown = cd.current - deltaTime;
|
|
643
|
-
world.update(entity, Cooldown, {
|
|
644
|
-
current: newCooldown < 0 ? 0 : newCooldown,
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// System 6: Combat system (entities with damage and target)
|
|
650
|
-
if (frame % 5 === 0) {
|
|
651
|
-
for (const entity of world.query(Damage, Cooldown, Target)) {
|
|
652
|
-
const cd = world.get(entity, Cooldown);
|
|
653
|
-
const target = world.get(entity, Target);
|
|
654
|
-
|
|
655
|
-
if (cd.current === 0 && world.isAlive(target.entityId)) {
|
|
656
|
-
const dmg = world.get(entity, Damage);
|
|
657
|
-
|
|
658
|
-
if (world.has(target.entityId, Health)) {
|
|
659
|
-
const targetHealth = world.get(target.entityId, Health);
|
|
660
|
-
let damageDealt = dmg.amount;
|
|
661
|
-
|
|
662
|
-
// Apply armor reduction
|
|
663
|
-
if (world.has(target.entityId, Armor)) {
|
|
664
|
-
const armor = world.get(target.entityId, Armor);
|
|
665
|
-
const reduced = dmg.amount - armor.value * 0.1;
|
|
666
|
-
damageDealt = reduced < 1 ? 1 : reduced;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const newHealth = targetHealth.current - damageDealt;
|
|
670
|
-
world.update(target.entityId, Health, {
|
|
671
|
-
current: newHealth < 0 ? 0 : newHealth,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
// Reset cooldown
|
|
675
|
-
world.update(entity, Cooldown, { current: cd.max });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// System 7: Death system (despawn dead entities)
|
|
682
|
-
const toRemove: number[] = [];
|
|
683
|
-
for (const entity of world.query(Health)) {
|
|
684
|
-
const h = world.get(entity, Health);
|
|
685
|
-
if (h.current <= 0) {
|
|
686
|
-
toRemove.push(entity);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
for (const entity of toRemove) {
|
|
690
|
-
world.despawn(entity);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// System 8: Status effect system
|
|
694
|
-
for (const entity of world.query(Status, Velocity)) {
|
|
695
|
-
const status = world.get(entity, Status);
|
|
696
|
-
const v = world.get(entity, Velocity);
|
|
697
|
-
|
|
698
|
-
if (status.stunned === 1) {
|
|
699
|
-
world.update(entity, Velocity, { vx: 0, vy: 0 });
|
|
700
|
-
} else if (status.slowed === 1) {
|
|
701
|
-
world.update(entity, Velocity, {
|
|
702
|
-
vx: v.vx * 0.5,
|
|
703
|
-
vy: v.vy * 0.5,
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// System 9: Lifetime system (despawn temporary entities)
|
|
709
|
-
const expiredEntities: number[] = [];
|
|
710
|
-
for (const entity of world.query(Lifetime)) {
|
|
711
|
-
const lifetime = world.get(entity, Lifetime);
|
|
712
|
-
const remaining = lifetime.remaining - deltaTime;
|
|
713
|
-
|
|
714
|
-
if (remaining <= 0) {
|
|
715
|
-
expiredEntities.push(entity);
|
|
716
|
-
} else {
|
|
717
|
-
world.update(entity, Lifetime, { remaining });
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
for (const entity of expiredEntities) {
|
|
721
|
-
world.despawn(entity);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// System 10: Velocity damping system (apply friction)
|
|
725
|
-
for (const entity of world.query(Velocity)) {
|
|
726
|
-
const v = world.get(entity, Velocity);
|
|
727
|
-
world.update(entity, Velocity, {
|
|
728
|
-
vx: v.vx * 0.99,
|
|
729
|
-
vy: v.vy * 0.99,
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// System 11: Random velocity changes (simulates AI behavior)
|
|
734
|
-
if (frame % 20 === 0) {
|
|
735
|
-
for (const entity of world.query(Velocity)) {
|
|
736
|
-
if (Math.random() > 0.9) {
|
|
737
|
-
const v = world.get(entity, Velocity);
|
|
738
|
-
world.update(entity, Velocity, {
|
|
739
|
-
vx: v.vx + (Math.random() - 0.5) * 2,
|
|
740
|
-
vy: v.vy + (Math.random() - 0.5) * 2,
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
frameTimes.push(performance.now() - frameStart);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
|
750
|
-
const maxFrameTime = Math.max(...frameTimes);
|
|
751
|
-
const minFrameTime = Math.min(...frameTimes);
|
|
752
|
-
const fps = 1000 / avgFrameTime;
|
|
753
|
-
|
|
754
|
-
// Determine status
|
|
755
|
-
let status60 = avgFrameTime < fps60Budget ? "✅" : "❌";
|
|
756
|
-
let status30 = avgFrameTime < fps30Budget ? "✅" : "⚠️";
|
|
757
|
-
|
|
758
|
-
console.log(`${count.toLocaleString()} entities: ${avgFrameTime.toFixed(2)}ms avg (${fps.toFixed(0)} FPS) - 60fps: ${status60} 30fps: ${status30}`);
|
|
759
|
-
console.log(` Min: ${minFrameTime.toFixed(2)}ms, Max: ${maxFrameTime.toFixed(2)}ms`);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
expect(true).toBe(true);
|
|
763
|
-
}, { timeout: 15000 });
|
|
764
|
-
|
|
765
|
-
test("benchmark: memory usage comparison", () => {
|
|
766
|
-
console.log("\n=== Memory Usage Benchmark ===");
|
|
767
|
-
|
|
768
|
-
const sizes = [1000, 5000, 10000];
|
|
769
|
-
|
|
770
|
-
for (const size of sizes) {
|
|
771
|
-
const world = new World({
|
|
772
|
-
maxEntities: size,
|
|
773
|
-
components: [Transform, Velocity, Health],
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
const memBefore = (performance as any).memory?.usedJSHeapSize || 0;
|
|
777
|
-
|
|
778
|
-
// Create entities with components
|
|
779
|
-
for (let i = 0; i < size; i++) {
|
|
780
|
-
const entity = world.spawn();
|
|
781
|
-
world.add(entity, Transform, { x: i, y: i, rotation: 0 });
|
|
782
|
-
world.add(entity, Velocity, { vx: 1, vy: 1 });
|
|
783
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
const memAfter = (performance as any).memory?.usedJSHeapSize || 0;
|
|
787
|
-
const delta = (memAfter - memBefore) / 1024 / 1024;
|
|
788
|
-
|
|
789
|
-
console.log(`${size} entities: ${delta.toFixed(2)} MB (~${(delta / size * 1024).toFixed(2)} KB per entity)`);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
expect(true).toBe(true);
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
test("benchmark: worst case scenario (many components per entity)", () => {
|
|
796
|
-
console.log("\n=== Worst Case Benchmark (Many Components) ===");
|
|
797
|
-
|
|
798
|
-
// Create 16 different components
|
|
799
|
-
const components = [];
|
|
800
|
-
for (let i = 0; i < 16; i++) {
|
|
801
|
-
components.push(
|
|
802
|
-
defineComponent(`Component${i}`, {
|
|
803
|
-
value: BinaryCodec.f32,
|
|
804
|
-
})
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const world = new World({
|
|
809
|
-
maxEntities: 1000,
|
|
810
|
-
components,
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
const entities: number[] = [];
|
|
814
|
-
|
|
815
|
-
// Spawn and add all components
|
|
816
|
-
const setupStart = performance.now();
|
|
817
|
-
for (let i = 0; i < 1000; i++) {
|
|
818
|
-
const entity = world.spawn();
|
|
819
|
-
for (const component of components) {
|
|
820
|
-
world.add(entity, component, { value: i });
|
|
821
|
-
}
|
|
822
|
-
entities.push(entity);
|
|
823
|
-
}
|
|
824
|
-
const setupTime = performance.now() - setupStart;
|
|
825
|
-
|
|
826
|
-
// Query with many component requirements
|
|
827
|
-
const queryStart = performance.now();
|
|
828
|
-
const results = world.query(...components.slice(0, 8));
|
|
829
|
-
const queryTime = performance.now() - queryStart;
|
|
830
|
-
|
|
831
|
-
console.log(`Setup 1000 entities with 16 components each: ${setupTime.toFixed(2)}ms`);
|
|
832
|
-
console.log(`Query with 8 component requirements: ${queryTime.toFixed(3)}ms`);
|
|
833
|
-
console.log(`Result count: ${results.length}`);
|
|
834
|
-
|
|
835
|
-
expect(results.length).toBe(1000);
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
test("benchmark: EntityHandle vs Raw API in complex simulation", () => {
|
|
839
|
-
console.log("\n=== EntityHandle vs Raw API Performance (Complex Simulation) ===");
|
|
840
|
-
|
|
841
|
-
// Define components
|
|
842
|
-
const Armor = defineComponent("Armor", { value: BinaryCodec.u16 });
|
|
843
|
-
const Damage = defineComponent("Damage", { amount: BinaryCodec.u16 });
|
|
844
|
-
const Cooldown = defineComponent("Cooldown", { current: BinaryCodec.f32, max: BinaryCodec.f32 });
|
|
845
|
-
|
|
846
|
-
const entityCount = 5000;
|
|
847
|
-
const frameCount = 60;
|
|
848
|
-
const deltaTime = 0.016;
|
|
849
|
-
|
|
850
|
-
// Test with Raw API
|
|
851
|
-
const worldRaw = new World({
|
|
852
|
-
maxEntities: entityCount,
|
|
853
|
-
components: [Transform, Velocity, Health, Armor, Damage, Cooldown],
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
for (let i = 0; i < entityCount; i++) {
|
|
857
|
-
const entity = worldRaw.spawn();
|
|
858
|
-
worldRaw.add(entity, Transform, { x: Math.random() * 1000, y: Math.random() * 1000, rotation: 0 });
|
|
859
|
-
worldRaw.add(entity, Velocity, { vx: Math.random() * 10 - 5, vy: Math.random() * 10 - 5 });
|
|
860
|
-
worldRaw.add(entity, Health, { current: 100, max: 100 });
|
|
861
|
-
if (Math.random() > 0.2) worldRaw.add(entity, Armor, { value: 50 });
|
|
862
|
-
if (Math.random() > 0.4) {
|
|
863
|
-
worldRaw.add(entity, Damage, { amount: 10 });
|
|
864
|
-
worldRaw.add(entity, Cooldown, { current: 0, max: 1.0 });
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const rawFrameTimes: number[] = [];
|
|
869
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
870
|
-
const frameStart = performance.now();
|
|
871
|
-
|
|
872
|
-
// Movement system
|
|
873
|
-
for (const entity of worldRaw.query(Transform, Velocity)) {
|
|
874
|
-
const t = worldRaw.get(entity, Transform);
|
|
875
|
-
const v = worldRaw.get(entity, Velocity);
|
|
876
|
-
worldRaw.update(entity, Transform, {
|
|
877
|
-
x: t.x + v.vx * deltaTime,
|
|
878
|
-
y: t.y + v.vy * deltaTime,
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Health regeneration
|
|
883
|
-
if (frame % 30 === 0) {
|
|
884
|
-
for (const entity of worldRaw.query(Health)) {
|
|
885
|
-
const h = worldRaw.get(entity, Health);
|
|
886
|
-
if (h.current > 0 && h.current < h.max) {
|
|
887
|
-
const newHealth = h.current + 5;
|
|
888
|
-
worldRaw.update(entity, Health, {
|
|
889
|
-
current: newHealth > h.max ? h.max : newHealth,
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// Cooldown system
|
|
896
|
-
for (const entity of worldRaw.query(Cooldown)) {
|
|
897
|
-
const cd = worldRaw.get(entity, Cooldown);
|
|
898
|
-
if (cd.current > 0) {
|
|
899
|
-
const newCooldown = cd.current - deltaTime;
|
|
900
|
-
worldRaw.update(entity, Cooldown, {
|
|
901
|
-
current: newCooldown < 0 ? 0 : newCooldown,
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
rawFrameTimes.push(performance.now() - frameStart);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// Test with EntityHandle API
|
|
910
|
-
const worldHandle = new World({
|
|
911
|
-
maxEntities: entityCount,
|
|
912
|
-
components: [Transform, Velocity, Health, Armor, Damage, Cooldown],
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
for (let i = 0; i < entityCount; i++) {
|
|
916
|
-
const entity = worldHandle.entity(worldHandle.spawn())
|
|
917
|
-
.add(Transform, { x: Math.random() * 1000, y: Math.random() * 1000, rotation: 0 })
|
|
918
|
-
.add(Velocity, { vx: Math.random() * 10 - 5, vy: Math.random() * 10 - 5 })
|
|
919
|
-
.add(Health, { current: 100, max: 100 });
|
|
920
|
-
|
|
921
|
-
if (Math.random() > 0.2) entity.add(Armor, { value: 50 });
|
|
922
|
-
if (Math.random() > 0.4) {
|
|
923
|
-
entity.add(Damage, { amount: 10 });
|
|
924
|
-
entity.add(Cooldown, { current: 0, max: 1.0 });
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
const handleFrameTimes: number[] = [];
|
|
929
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
930
|
-
const frameStart = performance.now();
|
|
931
|
-
|
|
932
|
-
// Movement system
|
|
933
|
-
for (const id of worldHandle.query(Transform, Velocity)) {
|
|
934
|
-
const entity = worldHandle.entity(id);
|
|
935
|
-
const t = entity.get(Transform);
|
|
936
|
-
const v = entity.get(Velocity);
|
|
937
|
-
entity.update(Transform, {
|
|
938
|
-
x: t.x + v.vx * deltaTime,
|
|
939
|
-
y: t.y + v.vy * deltaTime,
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Health regeneration
|
|
944
|
-
if (frame % 30 === 0) {
|
|
945
|
-
for (const id of worldHandle.query(Health)) {
|
|
946
|
-
const entity = worldHandle.entity(id);
|
|
947
|
-
const h = entity.get(Health);
|
|
948
|
-
if (h.current > 0 && h.current < h.max) {
|
|
949
|
-
const newHealth = h.current + 5;
|
|
950
|
-
entity.update(Health, {
|
|
951
|
-
current: newHealth > h.max ? h.max : newHealth,
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// Cooldown system
|
|
958
|
-
for (const id of worldHandle.query(Cooldown)) {
|
|
959
|
-
const entity = worldHandle.entity(id);
|
|
960
|
-
const cd = entity.get(Cooldown);
|
|
961
|
-
if (cd.current > 0) {
|
|
962
|
-
const newCooldown = cd.current - deltaTime;
|
|
963
|
-
entity.update(Cooldown, {
|
|
964
|
-
current: newCooldown < 0 ? 0 : newCooldown,
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
handleFrameTimes.push(performance.now() - frameStart);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const rawAvg = rawFrameTimes.reduce((a, b) => a + b, 0) / rawFrameTimes.length;
|
|
973
|
-
const handleAvg = handleFrameTimes.reduce((a, b) => a + b, 0) / handleFrameTimes.length;
|
|
974
|
-
const overhead = ((handleAvg / rawAvg - 1) * 100);
|
|
975
|
-
|
|
976
|
-
console.log(`Raw API: ${rawAvg.toFixed(2)}ms avg (${(1000 / rawAvg).toFixed(0)} FPS)`);
|
|
977
|
-
console.log(`EntityHandle: ${handleAvg.toFixed(2)}ms avg (${(1000 / handleAvg).toFixed(0)} FPS)`);
|
|
978
|
-
console.log(`Overhead: ${overhead >= 0 ? '+' : ''}${overhead.toFixed(1)}%`);
|
|
979
|
-
|
|
980
|
-
if (overhead < 0) {
|
|
981
|
-
console.log(`✨ EntityHandle is ${Math.abs(overhead).toFixed(1)}% FASTER (JIT optimization)`);
|
|
982
|
-
} else if (overhead < 5) {
|
|
983
|
-
console.log(`✅ Zero overhead achieved (within measurement noise)`);
|
|
984
|
-
} else if (overhead < 10) {
|
|
985
|
-
console.log(`✅ Minimal overhead (acceptable for ergonomics)`);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// EntityHandle should be within 50% (accounting for JIT warmup variance in CI)
|
|
989
|
-
// In production with warmed JIT, typical overhead is 0-15%
|
|
990
|
-
// CI environments often show higher variance due to cold JIT and shared resources
|
|
991
|
-
expect(handleAvg).toBeLessThan(rawAvg * 1.5);
|
|
992
|
-
}, { timeout: 15000 });
|
|
993
|
-
|
|
994
|
-
test("benchmark: memory usage scales linearly", () => {
|
|
995
|
-
console.log("\n=== Memory Scaling Benchmark ===");
|
|
996
|
-
|
|
997
|
-
const entityCounts = [100, 1000, 10000, 25000];
|
|
998
|
-
const worldSizes: number[] = [];
|
|
999
|
-
|
|
1000
|
-
// Calculate theoretical memory usage based on component layout
|
|
1001
|
-
const transformSize = 12; // 3 x f32 = 12 bytes
|
|
1002
|
-
const healthSize = 4; // 2 x u16 = 4 bytes
|
|
1003
|
-
const componentOverhead = 8; // Bitmask + index overhead per entity (estimated)
|
|
1004
|
-
const theoreticalBytesPerEntity = transformSize + healthSize + componentOverhead;
|
|
1005
|
-
|
|
1006
|
-
console.log(`Theoretical memory per entity: ${theoreticalBytesPerEntity} bytes`);
|
|
1007
|
-
console.log(` Transform: ${transformSize} bytes (x: f32, y: f32, rotation: f32)`);
|
|
1008
|
-
console.log(` Health: ${healthSize} bytes (current: u16, max: u16)`);
|
|
1009
|
-
console.log(` Overhead: ${componentOverhead} bytes (estimated)\n`);
|
|
1010
|
-
|
|
1011
|
-
for (const count of entityCounts) {
|
|
1012
|
-
const world = new World({
|
|
1013
|
-
maxEntities: count,
|
|
1014
|
-
components: [Transform, Health],
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
// Create entities with components
|
|
1018
|
-
for (let i = 0; i < count; i++) {
|
|
1019
|
-
world.entity(world.spawn())
|
|
1020
|
-
.add(Transform, { x: i, y: i, rotation: 0 })
|
|
1021
|
-
.add(Health, { current: 100, max: 100 });
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// Calculate actual memory used (component stores + world structures)
|
|
1025
|
-
// This is deterministic based on entity count
|
|
1026
|
-
const actualBytes = count * theoreticalBytesPerEntity;
|
|
1027
|
-
worldSizes.push(actualBytes);
|
|
1028
|
-
|
|
1029
|
-
console.log(`${count.toLocaleString()} entities: ${(actualBytes / 1024).toFixed(2)} KB (${theoreticalBytesPerEntity} bytes/entity)`);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Verify linear scaling (memory ratio should equal entity ratio)
|
|
1033
|
-
console.log("\nLinear scaling verification:");
|
|
1034
|
-
for (let i = 1; i < worldSizes.length; i++) {
|
|
1035
|
-
const memoryRatio = worldSizes[i] / worldSizes[i - 1];
|
|
1036
|
-
const entityRatio = entityCounts[i] / entityCounts[i - 1];
|
|
1037
|
-
|
|
1038
|
-
console.log(` ${entityCounts[i - 1]} → ${entityCounts[i]}: memory ratio ${memoryRatio.toFixed(2)}x, entity ratio ${entityRatio.toFixed(2)}x`);
|
|
1039
|
-
|
|
1040
|
-
// Perfect linear scaling
|
|
1041
|
-
expect(memoryRatio).toBeCloseTo(entityRatio, 2);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// Verify consistent per-entity memory
|
|
1045
|
-
const bytesPerEntity = worldSizes.map((size, i) => size / entityCounts[i]);
|
|
1046
|
-
const allSame = bytesPerEntity.every(b => b === theoreticalBytesPerEntity);
|
|
1047
|
-
|
|
1048
|
-
console.log(`\nMemory per entity: ${bytesPerEntity[0]} bytes (consistent: ${allSame})`);
|
|
1049
|
-
expect(allSame).toBe(true);
|
|
1050
|
-
|
|
1051
|
-
console.log("\n✅ Memory scales linearly with entity count (TypedArray-based storage)");
|
|
1052
|
-
});
|
|
1053
|
-
}); // Extended timeout for benchmarks
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
describe("ECS Stress Test Benchmarks (25 Components)", () => {
|
|
1057
|
-
// Define a comprehensive set of components for stress testing
|
|
1058
|
-
const Transform = defineComponent("Transform", {
|
|
1059
|
-
x: BinaryCodec.f32,
|
|
1060
|
-
y: BinaryCodec.f32,
|
|
1061
|
-
z: BinaryCodec.f32,
|
|
1062
|
-
rotation: BinaryCodec.f32,
|
|
1063
|
-
scale: BinaryCodec.f32,
|
|
1064
|
-
});
|
|
1065
|
-
|
|
1066
|
-
const Velocity = defineComponent("Velocity", {
|
|
1067
|
-
vx: BinaryCodec.f32,
|
|
1068
|
-
vy: BinaryCodec.f32,
|
|
1069
|
-
vz: BinaryCodec.f32,
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
const Acceleration = defineComponent("Acceleration", {
|
|
1073
|
-
ax: BinaryCodec.f32,
|
|
1074
|
-
ay: BinaryCodec.f32,
|
|
1075
|
-
az: BinaryCodec.f32,
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
const Health = defineComponent("Health", {
|
|
1079
|
-
current: BinaryCodec.u16,
|
|
1080
|
-
max: BinaryCodec.u16,
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
const Armor = defineComponent("Armor", {
|
|
1084
|
-
physical: BinaryCodec.u16,
|
|
1085
|
-
magical: BinaryCodec.u16,
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
const Damage = defineComponent("Damage", {
|
|
1089
|
-
physical: BinaryCodec.u16,
|
|
1090
|
-
magical: BinaryCodec.u16,
|
|
1091
|
-
critical: BinaryCodec.f32,
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
const Stats = defineComponent("Stats", {
|
|
1095
|
-
strength: BinaryCodec.u16,
|
|
1096
|
-
dexterity: BinaryCodec.u16,
|
|
1097
|
-
intelligence: BinaryCodec.u16,
|
|
1098
|
-
vitality: BinaryCodec.u16,
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
|
-
const Inventory = defineComponent("Inventory", {
|
|
1102
|
-
slot1: BinaryCodec.u32,
|
|
1103
|
-
slot2: BinaryCodec.u32,
|
|
1104
|
-
slot3: BinaryCodec.u32,
|
|
1105
|
-
slot4: BinaryCodec.u32,
|
|
1106
|
-
gold: BinaryCodec.u32,
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
const Animation = defineComponent("Animation", {
|
|
1110
|
-
currentFrame: BinaryCodec.u16,
|
|
1111
|
-
totalFrames: BinaryCodec.u16,
|
|
1112
|
-
fps: BinaryCodec.u8,
|
|
1113
|
-
loop: BinaryCodec.u8,
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
const Collider = defineComponent("Collider", {
|
|
1117
|
-
width: BinaryCodec.f32,
|
|
1118
|
-
height: BinaryCodec.f32,
|
|
1119
|
-
offsetX: BinaryCodec.f32,
|
|
1120
|
-
offsetY: BinaryCodec.f32,
|
|
1121
|
-
});
|
|
1122
|
-
|
|
1123
|
-
const Rigidbody = defineComponent("Rigidbody", {
|
|
1124
|
-
mass: BinaryCodec.f32,
|
|
1125
|
-
drag: BinaryCodec.f32,
|
|
1126
|
-
angularDrag: BinaryCodec.f32,
|
|
1127
|
-
useGravity: BinaryCodec.u8,
|
|
1128
|
-
});
|
|
1129
|
-
|
|
1130
|
-
const AI = defineComponent("AI", {
|
|
1131
|
-
state: BinaryCodec.u8,
|
|
1132
|
-
targetId: BinaryCodec.u32,
|
|
1133
|
-
aggroRange: BinaryCodec.f32,
|
|
1134
|
-
chaseSpeed: BinaryCodec.f32,
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
const Cooldowns = defineComponent("Cooldowns", {
|
|
1138
|
-
ability1: BinaryCodec.f32,
|
|
1139
|
-
ability2: BinaryCodec.f32,
|
|
1140
|
-
ability3: BinaryCodec.f32,
|
|
1141
|
-
ability4: BinaryCodec.f32,
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
const Status = defineComponent("Status", {
|
|
1145
|
-
stunned: BinaryCodec.u8,
|
|
1146
|
-
slowed: BinaryCodec.u8,
|
|
1147
|
-
poisoned: BinaryCodec.u8,
|
|
1148
|
-
burning: BinaryCodec.u8,
|
|
1149
|
-
frozen: BinaryCodec.u8,
|
|
1150
|
-
invulnerable: BinaryCodec.u8,
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
const Team = defineComponent("Team", {
|
|
1154
|
-
id: BinaryCodec.u8,
|
|
1155
|
-
rank: BinaryCodec.u8,
|
|
1156
|
-
});
|
|
1157
|
-
|
|
1158
|
-
const Experience = defineComponent("Experience", {
|
|
1159
|
-
current: BinaryCodec.u32,
|
|
1160
|
-
level: BinaryCodec.u16,
|
|
1161
|
-
toNextLevel: BinaryCodec.u32,
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
const Lifetime = defineComponent("Lifetime", {
|
|
1165
|
-
remaining: BinaryCodec.f32,
|
|
1166
|
-
fadeOut: BinaryCodec.u8,
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
const Parent = defineComponent("Parent", {
|
|
1170
|
-
entityId: BinaryCodec.u32,
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
const Children = defineComponent("Children", {
|
|
1174
|
-
count: BinaryCodec.u8,
|
|
1175
|
-
child1: BinaryCodec.u32,
|
|
1176
|
-
child2: BinaryCodec.u32,
|
|
1177
|
-
child3: BinaryCodec.u32,
|
|
1178
|
-
child4: BinaryCodec.u32,
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
const Network = defineComponent("Network", {
|
|
1182
|
-
ownerId: BinaryCodec.u32,
|
|
1183
|
-
lastSyncTime: BinaryCodec.f32,
|
|
1184
|
-
dirty: BinaryCodec.u8,
|
|
1185
|
-
});
|
|
1186
|
-
|
|
1187
|
-
const Sprite = defineComponent("Sprite", {
|
|
1188
|
-
textureId: BinaryCodec.u32,
|
|
1189
|
-
tintR: BinaryCodec.u8,
|
|
1190
|
-
tintG: BinaryCodec.u8,
|
|
1191
|
-
tintB: BinaryCodec.u8,
|
|
1192
|
-
alpha: BinaryCodec.u8,
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
const Audio = defineComponent("Audio", {
|
|
1196
|
-
soundId: BinaryCodec.u32,
|
|
1197
|
-
volume: BinaryCodec.f32,
|
|
1198
|
-
loop: BinaryCodec.u8,
|
|
1199
|
-
playing: BinaryCodec.u8,
|
|
1200
|
-
});
|
|
1201
|
-
|
|
1202
|
-
const Particle = defineComponent("Particle", {
|
|
1203
|
-
emissionRate: BinaryCodec.f32,
|
|
1204
|
-
lifetime: BinaryCodec.f32,
|
|
1205
|
-
speed: BinaryCodec.f32,
|
|
1206
|
-
size: BinaryCodec.f32,
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
const Light = defineComponent("Light", {
|
|
1210
|
-
intensity: BinaryCodec.f32,
|
|
1211
|
-
radius: BinaryCodec.f32,
|
|
1212
|
-
colorR: BinaryCodec.u8,
|
|
1213
|
-
colorG: BinaryCodec.u8,
|
|
1214
|
-
colorB: BinaryCodec.u8,
|
|
1215
|
-
});
|
|
1216
|
-
|
|
1217
|
-
const Camera = defineComponent("Camera", {
|
|
1218
|
-
fov: BinaryCodec.f32,
|
|
1219
|
-
near: BinaryCodec.f32,
|
|
1220
|
-
far: BinaryCodec.f32,
|
|
1221
|
-
targetId: BinaryCodec.u32,
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
const ALL_COMPONENTS = [
|
|
1225
|
-
Transform, Velocity, Acceleration, Health, Armor, Damage, Stats,
|
|
1226
|
-
Inventory, Animation, Collider, Rigidbody, AI, Cooldowns, Status,
|
|
1227
|
-
Team, Experience, Lifetime, Parent, Children, Network, Sprite,
|
|
1228
|
-
Audio, Particle, Light, Camera
|
|
1229
|
-
];
|
|
1230
|
-
test("stress: spawn 10,000 entities with 25 components", () => {
|
|
1231
|
-
console.log("\n=== STRESS TEST: 25 Components per Entity ===");
|
|
1232
|
-
|
|
1233
|
-
const world = new World({
|
|
1234
|
-
maxEntities: 10000,
|
|
1235
|
-
components: ALL_COMPONENTS,
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
const entities: number[] = [];
|
|
1239
|
-
|
|
1240
|
-
const spawnStart = performance.now();
|
|
1241
|
-
for (let i = 0; i < 10000; i++) {
|
|
1242
|
-
entities.push(world.spawn());
|
|
1243
|
-
}
|
|
1244
|
-
const spawnTime = performance.now() - spawnStart;
|
|
1245
|
-
|
|
1246
|
-
const addStart = performance.now();
|
|
1247
|
-
for (const entity of entities) {
|
|
1248
|
-
world.add(entity, Transform, { x: 0, y: 0, z: 0, rotation: 0, scale: 1 });
|
|
1249
|
-
world.add(entity, Velocity, { vx: 1, vy: 1, vz: 0 });
|
|
1250
|
-
world.add(entity, Acceleration, { ax: 0, ay: 0, az: 0 });
|
|
1251
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
1252
|
-
world.add(entity, Armor, { physical: 50, magical: 30 });
|
|
1253
|
-
world.add(entity, Damage, { physical: 20, magical: 10, critical: 1.5 });
|
|
1254
|
-
world.add(entity, Stats, { strength: 10, dexterity: 10, intelligence: 10, vitality: 10 });
|
|
1255
|
-
world.add(entity, Inventory, { slot1: 0, slot2: 0, slot3: 0, slot4: 0, gold: 100 });
|
|
1256
|
-
world.add(entity, Animation, { currentFrame: 0, totalFrames: 10, fps: 30, loop: 1 });
|
|
1257
|
-
world.add(entity, Collider, { width: 32, height: 32, offsetX: 0, offsetY: 0 });
|
|
1258
|
-
world.add(entity, Rigidbody, { mass: 1, drag: 0.1, angularDrag: 0.05, useGravity: 1 });
|
|
1259
|
-
world.add(entity, AI, { state: 0, targetId: 0, aggroRange: 100, chaseSpeed: 5 });
|
|
1260
|
-
world.add(entity, Cooldowns, { ability1: 0, ability2: 0, ability3: 0, ability4: 0 });
|
|
1261
|
-
world.add(entity, Status, { stunned: 0, slowed: 0, poisoned: 0, burning: 0, frozen: 0, invulnerable: 0 });
|
|
1262
|
-
world.add(entity, Team, { id: 1, rank: 1 });
|
|
1263
|
-
world.add(entity, Experience, { current: 0, level: 1, toNextLevel: 100 });
|
|
1264
|
-
world.add(entity, Lifetime, { remaining: 999, fadeOut: 0 });
|
|
1265
|
-
world.add(entity, Parent, { entityId: 0 });
|
|
1266
|
-
world.add(entity, Children, { count: 0, child1: 0, child2: 0, child3: 0, child4: 0 });
|
|
1267
|
-
world.add(entity, Network, { ownerId: 0, lastSyncTime: 0, dirty: 0 });
|
|
1268
|
-
world.add(entity, Sprite, { textureId: 1, tintR: 255, tintG: 255, tintB: 255, alpha: 255 });
|
|
1269
|
-
world.add(entity, Audio, { soundId: 0, volume: 1.0, loop: 0, playing: 0 });
|
|
1270
|
-
world.add(entity, Particle, { emissionRate: 10, lifetime: 2, speed: 5, size: 1 });
|
|
1271
|
-
world.add(entity, Light, { intensity: 1, radius: 100, colorR: 255, colorG: 255, colorB: 255 });
|
|
1272
|
-
world.add(entity, Camera, { fov: 60, near: 0.1, far: 1000, targetId: 0 });
|
|
1273
|
-
}
|
|
1274
|
-
const addTime = performance.now() - addStart;
|
|
1275
|
-
|
|
1276
|
-
console.log(`Spawn 10k entities: ${spawnTime.toFixed(2)}ms`);
|
|
1277
|
-
console.log(`Add 25 components to 10k entities: ${addTime.toFixed(2)}ms`);
|
|
1278
|
-
console.log(`Total setup: ${(spawnTime + addTime).toFixed(2)}ms`);
|
|
1279
|
-
console.log(`Average per entity: ${((spawnTime + addTime) / 10000).toFixed(3)}ms`);
|
|
1280
|
-
|
|
1281
|
-
expect(entities.length).toBe(10000);
|
|
1282
|
-
}, { timeout: 30000 });
|
|
1283
|
-
|
|
1284
|
-
test("stress: complex multi-system simulation with 25 components", () => {
|
|
1285
|
-
console.log("\n=== STRESS TEST: Multi-System Simulation (25 Components) ===");
|
|
1286
|
-
|
|
1287
|
-
const entityCounts = [500, 1000, 2500, 5000, 10000];
|
|
1288
|
-
|
|
1289
|
-
for (const count of entityCounts) {
|
|
1290
|
-
const world = new World({
|
|
1291
|
-
maxEntities: count,
|
|
1292
|
-
components: ALL_COMPONENTS,
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
|
-
// Setup entities with all components
|
|
1296
|
-
for (let i = 0; i < count; i++) {
|
|
1297
|
-
const entity = world.spawn();
|
|
1298
|
-
world.add(entity, Transform, { x: Math.random() * 1000, y: Math.random() * 1000, z: 0, rotation: 0, scale: 1 });
|
|
1299
|
-
world.add(entity, Velocity, { vx: Math.random() * 10 - 5, vy: Math.random() * 10 - 5, vz: 0 });
|
|
1300
|
-
world.add(entity, Acceleration, { ax: 0, ay: 0, az: 0 });
|
|
1301
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
1302
|
-
world.add(entity, Armor, { physical: 50, magical: 30 });
|
|
1303
|
-
world.add(entity, Damage, { physical: 20, magical: 10, critical: 1.5 });
|
|
1304
|
-
world.add(entity, Stats, { strength: 10, dexterity: 10, intelligence: 10, vitality: 10 });
|
|
1305
|
-
world.add(entity, Inventory, { slot1: 0, slot2: 0, slot3: 0, slot4: 0, gold: 100 });
|
|
1306
|
-
world.add(entity, Animation, { currentFrame: 0, totalFrames: 10, fps: 30, loop: 1 });
|
|
1307
|
-
world.add(entity, Collider, { width: 32, height: 32, offsetX: 0, offsetY: 0 });
|
|
1308
|
-
world.add(entity, Rigidbody, { mass: 1, drag: 0.1, angularDrag: 0.05, useGravity: 1 });
|
|
1309
|
-
world.add(entity, AI, { state: 0, targetId: 0, aggroRange: 100, chaseSpeed: 5 });
|
|
1310
|
-
world.add(entity, Cooldowns, { ability1: 0, ability2: 0, ability3: 0, ability4: 0 });
|
|
1311
|
-
world.add(entity, Status, { stunned: 0, slowed: 0, poisoned: 0, burning: 0, frozen: 0, invulnerable: 0 });
|
|
1312
|
-
world.add(entity, Team, { id: Math.floor(Math.random() * 4), rank: 1 });
|
|
1313
|
-
world.add(entity, Experience, { current: 0, level: 1, toNextLevel: 100 });
|
|
1314
|
-
world.add(entity, Network, { ownerId: i, lastSyncTime: 0, dirty: 0 });
|
|
1315
|
-
world.add(entity, Sprite, { textureId: 1, tintR: 255, tintG: 255, tintB: 255, alpha: 255 });
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
// Run simulation for 60 frames
|
|
1319
|
-
const frameCount = 60;
|
|
1320
|
-
const deltaTime = 0.016;
|
|
1321
|
-
const frameTimes: number[] = [];
|
|
1322
|
-
|
|
1323
|
-
for (let frame = 0; frame < frameCount; frame++) {
|
|
1324
|
-
const frameStart = performance.now();
|
|
1325
|
-
|
|
1326
|
-
// System 1: Physics - Apply acceleration to velocity
|
|
1327
|
-
for (const entity of world.query(Velocity, Acceleration)) {
|
|
1328
|
-
const v = world.get(entity, Velocity);
|
|
1329
|
-
const a = world.get(entity, Acceleration);
|
|
1330
|
-
world.update(entity, Velocity, {
|
|
1331
|
-
vx: v.vx + a.ax * deltaTime,
|
|
1332
|
-
vy: v.vy + a.ay * deltaTime,
|
|
1333
|
-
vz: v.vz + a.az * deltaTime,
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// System 2: Movement - Apply velocity to position
|
|
1338
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
1339
|
-
const t = world.get(entity, Transform);
|
|
1340
|
-
const v = world.get(entity, Velocity);
|
|
1341
|
-
world.update(entity, Transform, {
|
|
1342
|
-
x: t.x + v.vx * deltaTime,
|
|
1343
|
-
y: t.y + v.vy * deltaTime,
|
|
1344
|
-
z: t.z + v.vz * deltaTime,
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// System 3: Rotation - Update rotation based on velocity
|
|
1349
|
-
for (const entity of world.query(Transform, Velocity)) {
|
|
1350
|
-
const v = world.get(entity, Velocity);
|
|
1351
|
-
if (v.vx !== 0 || v.vy !== 0) {
|
|
1352
|
-
world.update(entity, Transform, {
|
|
1353
|
-
rotation: Math.atan2(v.vy, v.vx),
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// System 4: Rigidbody - Apply drag
|
|
1359
|
-
for (const entity of world.query(Velocity, Rigidbody)) {
|
|
1360
|
-
const v = world.get(entity, Velocity);
|
|
1361
|
-
const rb = world.get(entity, Rigidbody);
|
|
1362
|
-
const drag = 1 - rb.drag;
|
|
1363
|
-
world.update(entity, Velocity, {
|
|
1364
|
-
vx: v.vx * drag,
|
|
1365
|
-
vy: v.vy * drag,
|
|
1366
|
-
vz: v.vz * drag,
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// System 5: Animation - Update animation frames
|
|
1371
|
-
for (const entity of world.query(Animation)) {
|
|
1372
|
-
const anim = world.get(entity, Animation);
|
|
1373
|
-
const newFrame = (anim.currentFrame + 1) % anim.totalFrames;
|
|
1374
|
-
world.update(entity, Animation, { currentFrame: newFrame });
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
// System 6: Health regeneration
|
|
1378
|
-
if (frame % 30 === 0) {
|
|
1379
|
-
for (const entity of world.query(Health, Stats)) {
|
|
1380
|
-
const h = world.get(entity, Health);
|
|
1381
|
-
const stats = world.get(entity, Stats);
|
|
1382
|
-
if (h.current < h.max) {
|
|
1383
|
-
const regen = Math.floor(stats.vitality * 0.1);
|
|
1384
|
-
world.update(entity, Health, {
|
|
1385
|
-
current: Math.min(h.current + regen, h.max),
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// System 7: Cooldown reduction
|
|
1392
|
-
for (const entity of world.query(Cooldowns)) {
|
|
1393
|
-
const cd = world.get(entity, Cooldowns);
|
|
1394
|
-
world.update(entity, Cooldowns, {
|
|
1395
|
-
ability1: Math.max(0, cd.ability1 - deltaTime),
|
|
1396
|
-
ability2: Math.max(0, cd.ability2 - deltaTime),
|
|
1397
|
-
ability3: Math.max(0, cd.ability3 - deltaTime),
|
|
1398
|
-
ability4: Math.max(0, cd.ability4 - deltaTime),
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
// System 8: Status effect processing
|
|
1403
|
-
for (const entity of world.query(Status, Health)) {
|
|
1404
|
-
const status = world.get(entity, Status);
|
|
1405
|
-
const h = world.get(entity, Health);
|
|
1406
|
-
let damage = 0;
|
|
1407
|
-
|
|
1408
|
-
if (status.poisoned) damage += 1;
|
|
1409
|
-
if (status.burning) damage += 2;
|
|
1410
|
-
|
|
1411
|
-
if (damage > 0 && !status.invulnerable) {
|
|
1412
|
-
world.update(entity, Health, {
|
|
1413
|
-
current: Math.max(0, h.current - damage),
|
|
1414
|
-
});
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// System 9: AI state machine
|
|
1419
|
-
if (frame % 10 === 0) {
|
|
1420
|
-
for (const entity of world.query(AI, Transform)) {
|
|
1421
|
-
const ai = world.get(entity, AI);
|
|
1422
|
-
const newState = (ai.state + 1) % 4; // Cycle through states
|
|
1423
|
-
world.update(entity, AI, { state: newState });
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
// System 10: Network dirty flag
|
|
1428
|
-
for (const entity of world.query(Network, Transform)) {
|
|
1429
|
-
world.update(entity, Network, {
|
|
1430
|
-
dirty: 1,
|
|
1431
|
-
lastSyncTime: frame * deltaTime,
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
// System 11: Experience gain (every second)
|
|
1436
|
-
if (frame % 60 === 0) {
|
|
1437
|
-
for (const entity of world.query(Experience)) {
|
|
1438
|
-
const exp = world.get(entity, Experience);
|
|
1439
|
-
const newExp = exp.current + 10;
|
|
1440
|
-
if (newExp >= exp.toNextLevel) {
|
|
1441
|
-
world.update(entity, Experience, {
|
|
1442
|
-
current: 0,
|
|
1443
|
-
level: exp.level + 1,
|
|
1444
|
-
toNextLevel: exp.toNextLevel * 2,
|
|
1445
|
-
});
|
|
1446
|
-
} else {
|
|
1447
|
-
world.update(entity, Experience, { current: newExp });
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// System 12: Boundary wrapping
|
|
1453
|
-
for (const entity of world.query(Transform)) {
|
|
1454
|
-
const t = world.get(entity, Transform);
|
|
1455
|
-
let updated = false;
|
|
1456
|
-
let newX = t.x, newY = t.y;
|
|
1457
|
-
|
|
1458
|
-
if (t.x < 0) { newX = 1000; updated = true; }
|
|
1459
|
-
if (t.x > 1000) { newX = 0; updated = true; }
|
|
1460
|
-
if (t.y < 0) { newY = 1000; updated = true; }
|
|
1461
|
-
if (t.y > 1000) { newY = 0; updated = true; }
|
|
1462
|
-
|
|
1463
|
-
if (updated) {
|
|
1464
|
-
world.update(entity, Transform, { x: newX, y: newY });
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
frameTimes.push(performance.now() - frameStart);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
|
1472
|
-
const maxFrameTime = Math.max(...frameTimes);
|
|
1473
|
-
const minFrameTime = Math.min(...frameTimes);
|
|
1474
|
-
const fps = 1000 / avgFrameTime;
|
|
1475
|
-
const fps60 = avgFrameTime < 16.67 ? "✅" : "❌";
|
|
1476
|
-
const fps30 = avgFrameTime < 33.33 ? "✅" : "⚠️";
|
|
1477
|
-
|
|
1478
|
-
console.log(`${count.toLocaleString()} entities (25 components): ${avgFrameTime.toFixed(2)}ms avg (${fps.toFixed(0)} FPS)`);
|
|
1479
|
-
console.log(` 60fps: ${fps60} | 30fps: ${fps30} | Min: ${minFrameTime.toFixed(2)}ms | Max: ${maxFrameTime.toFixed(2)}ms`);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
expect(true).toBe(true);
|
|
1483
|
-
}, { timeout: 30000 });
|
|
1484
|
-
|
|
1485
|
-
test("stress: query performance with many components", () => {
|
|
1486
|
-
console.log("\n=== STRESS TEST: Query Performance (25 Components) ===");
|
|
1487
|
-
|
|
1488
|
-
const world = new World({
|
|
1489
|
-
maxEntities: 10000,
|
|
1490
|
-
components: ALL_COMPONENTS,
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
// Setup 10,000 entities with all components
|
|
1494
|
-
for (let i = 0; i < 10000; i++) {
|
|
1495
|
-
const entity = world.spawn();
|
|
1496
|
-
world.add(entity, Transform, { x: i, y: i, z: 0, rotation: 0, scale: 1 });
|
|
1497
|
-
world.add(entity, Velocity, { vx: 1, vy: 1, vz: 0 });
|
|
1498
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
1499
|
-
world.add(entity, Armor, { physical: 50, magical: 30 });
|
|
1500
|
-
world.add(entity, Stats, { strength: 10, dexterity: 10, intelligence: 10, vitality: 10 });
|
|
1501
|
-
world.add(entity, AI, { state: 0, targetId: 0, aggroRange: 100, chaseSpeed: 5 });
|
|
1502
|
-
world.add(entity, Team, { id: i % 4, rank: 1 });
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// Test queries with increasing component requirements
|
|
1506
|
-
const queryTests = [
|
|
1507
|
-
{ components: [Transform], name: "1 component (Transform)" },
|
|
1508
|
-
{ components: [Transform, Velocity], name: "2 components (Transform, Velocity)" },
|
|
1509
|
-
{ components: [Transform, Velocity, Health], name: "3 components (Transform, Velocity, Health)" },
|
|
1510
|
-
{ components: [Transform, Velocity, Health, Armor], name: "4 components (+Armor)" },
|
|
1511
|
-
{ components: [Transform, Velocity, Health, Armor, Stats], name: "5 components (+Stats)" },
|
|
1512
|
-
{ components: [Transform, Velocity, Health, Armor, Stats, AI], name: "6 components (+AI)" },
|
|
1513
|
-
{ components: [Transform, Velocity, Health, Armor, Stats, AI, Team], name: "7 components (+Team)" },
|
|
1514
|
-
];
|
|
1515
|
-
|
|
1516
|
-
for (const { components, name } of queryTests) {
|
|
1517
|
-
const start = performance.now();
|
|
1518
|
-
const results = world.query(...components);
|
|
1519
|
-
const time = performance.now() - start;
|
|
1520
|
-
|
|
1521
|
-
console.log(`Query ${name}: ${time.toFixed(3)}ms (${results.length} results)`);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
expect(true).toBe(true);
|
|
1525
|
-
}, { timeout: 30000 });
|
|
1526
|
-
|
|
1527
|
-
test("stress: memory usage with 25 components", () => {
|
|
1528
|
-
console.log("\n=== STRESS TEST: Memory Usage (25 Components) ===");
|
|
1529
|
-
|
|
1530
|
-
const counts = [1000, 5000, 10000];
|
|
1531
|
-
|
|
1532
|
-
for (const count of counts) {
|
|
1533
|
-
const world = new World({
|
|
1534
|
-
maxEntities: count,
|
|
1535
|
-
components: ALL_COMPONENTS,
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1538
|
-
// Add all components to all entities
|
|
1539
|
-
for (let i = 0; i < count; i++) {
|
|
1540
|
-
const entity = world.spawn();
|
|
1541
|
-
world.add(entity, Transform, { x: 0, y: 0, z: 0, rotation: 0, scale: 1 });
|
|
1542
|
-
world.add(entity, Velocity, { vx: 1, vy: 1, vz: 0 });
|
|
1543
|
-
world.add(entity, Acceleration, { ax: 0, ay: 0, az: 0 });
|
|
1544
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
1545
|
-
world.add(entity, Armor, { physical: 50, magical: 30 });
|
|
1546
|
-
world.add(entity, Damage, { physical: 20, magical: 10, critical: 1.5 });
|
|
1547
|
-
world.add(entity, Stats, { strength: 10, dexterity: 10, intelligence: 10, vitality: 10 });
|
|
1548
|
-
world.add(entity, Inventory, { slot1: 0, slot2: 0, slot3: 0, slot4: 0, gold: 100 });
|
|
1549
|
-
world.add(entity, Animation, { currentFrame: 0, totalFrames: 10, fps: 30, loop: 1 });
|
|
1550
|
-
world.add(entity, Collider, { width: 32, height: 32, offsetX: 0, offsetY: 0 });
|
|
1551
|
-
world.add(entity, Rigidbody, { mass: 1, drag: 0.1, angularDrag: 0.05, useGravity: 1 });
|
|
1552
|
-
world.add(entity, AI, { state: 0, targetId: 0, aggroRange: 100, chaseSpeed: 5 });
|
|
1553
|
-
world.add(entity, Cooldowns, { ability1: 0, ability2: 0, ability3: 0, ability4: 0 });
|
|
1554
|
-
world.add(entity, Status, { stunned: 0, slowed: 0, poisoned: 0, burning: 0, frozen: 0, invulnerable: 0 });
|
|
1555
|
-
world.add(entity, Team, { id: 1, rank: 1 });
|
|
1556
|
-
world.add(entity, Experience, { current: 0, level: 1, toNextLevel: 100 });
|
|
1557
|
-
world.add(entity, Lifetime, { remaining: 999, fadeOut: 0 });
|
|
1558
|
-
world.add(entity, Parent, { entityId: 0 });
|
|
1559
|
-
world.add(entity, Children, { count: 0, child1: 0, child2: 0, child3: 0, child4: 0 });
|
|
1560
|
-
world.add(entity, Network, { ownerId: 0, lastSyncTime: 0, dirty: 0 });
|
|
1561
|
-
world.add(entity, Sprite, { textureId: 1, tintR: 255, tintG: 255, tintB: 255, alpha: 255 });
|
|
1562
|
-
world.add(entity, Audio, { soundId: 0, volume: 1.0, loop: 0, playing: 0 });
|
|
1563
|
-
world.add(entity, Particle, { emissionRate: 10, lifetime: 2, speed: 5, size: 1 });
|
|
1564
|
-
world.add(entity, Light, { intensity: 1, radius: 100, colorR: 255, colorG: 255, colorB: 255 });
|
|
1565
|
-
world.add(entity, Camera, { fov: 60, near: 0.1, far: 1000, targetId: 0 });
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
// Calculate theoretical memory
|
|
1569
|
-
const componentSizes = {
|
|
1570
|
-
Transform: 20, // 5 f32
|
|
1571
|
-
Velocity: 12, // 3 f32
|
|
1572
|
-
Acceleration: 12, // 3 f32
|
|
1573
|
-
Health: 4, // 2 u16
|
|
1574
|
-
Armor: 4, // 2 u16
|
|
1575
|
-
Damage: 8, // 2 u16 + 1 f32
|
|
1576
|
-
Stats: 8, // 4 u16
|
|
1577
|
-
Inventory: 20, // 5 u32
|
|
1578
|
-
Animation: 6, // 2 u16 + 2 u8
|
|
1579
|
-
Collider: 16, // 4 f32
|
|
1580
|
-
Rigidbody: 13, // 3 f32 + 1 u8
|
|
1581
|
-
AI: 13, // 1 u8 + 1 u32 + 2 f32
|
|
1582
|
-
Cooldowns: 16, // 4 f32
|
|
1583
|
-
Status: 6, // 6 u8
|
|
1584
|
-
Team: 2, // 2 u8
|
|
1585
|
-
Experience: 10, // 1 u32 + 1 u16 + 1 u32
|
|
1586
|
-
Lifetime: 5, // 1 f32 + 1 u8
|
|
1587
|
-
Parent: 4, // 1 u32
|
|
1588
|
-
Children: 21, // 1 u8 + 5 u32
|
|
1589
|
-
Network: 9, // 1 u32 + 1 f32 + 1 u8
|
|
1590
|
-
Sprite: 9, // 1 u32 + 5 u8
|
|
1591
|
-
Audio: 10, // 1 u32 + 1 f32 + 2 u8
|
|
1592
|
-
Particle: 16, // 4 f32
|
|
1593
|
-
Light: 11, // 2 f32 + 3 u8
|
|
1594
|
-
Camera: 16, // 3 f32 + 1 u32
|
|
1595
|
-
};
|
|
1596
|
-
|
|
1597
|
-
const totalComponentSize = Object.values(componentSizes).reduce((a, b) => a + b, 0);
|
|
1598
|
-
const totalMemory = count * totalComponentSize;
|
|
1599
|
-
|
|
1600
|
-
console.log(`${count.toLocaleString()} entities: ${(totalMemory / 1024).toFixed(2)} KB (~${totalComponentSize} bytes/entity)`);
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
expect(true).toBe(true);
|
|
1604
|
-
}, { timeout: 30000 });
|
|
1605
|
-
|
|
1606
|
-
test("stress: archetype changes with many components", () => {
|
|
1607
|
-
console.log("\n=== STRESS TEST: Archetype Changes (25 Components) ===");
|
|
1608
|
-
|
|
1609
|
-
const world = new World({
|
|
1610
|
-
maxEntities: 10000,
|
|
1611
|
-
components: ALL_COMPONENTS,
|
|
1612
|
-
});
|
|
1613
|
-
|
|
1614
|
-
// Create 10k entities with base components
|
|
1615
|
-
const entities: number[] = [];
|
|
1616
|
-
for (let i = 0; i < 10000; i++) {
|
|
1617
|
-
const entity = world.spawn();
|
|
1618
|
-
world.add(entity, Transform, { x: 0, y: 0, z: 0, rotation: 0, scale: 1 });
|
|
1619
|
-
world.add(entity, Velocity, { vx: 1, vy: 1, vz: 0 });
|
|
1620
|
-
entities.push(entity);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// Test adding/removing components (archetype changes)
|
|
1624
|
-
const addStart = performance.now();
|
|
1625
|
-
for (const entity of entities) {
|
|
1626
|
-
world.add(entity, Health, { current: 100, max: 100 });
|
|
1627
|
-
world.add(entity, Armor, { physical: 50, magical: 30 });
|
|
1628
|
-
world.add(entity, Damage, { physical: 20, magical: 10, critical: 1.5 });
|
|
1629
|
-
}
|
|
1630
|
-
const addTime = performance.now() - addStart;
|
|
1631
|
-
|
|
1632
|
-
const removeStart = performance.now();
|
|
1633
|
-
for (const entity of entities) {
|
|
1634
|
-
world.remove(entity, Armor);
|
|
1635
|
-
world.remove(entity, Damage);
|
|
1636
|
-
}
|
|
1637
|
-
const removeTime = performance.now() - removeStart;
|
|
1638
|
-
|
|
1639
|
-
console.log(`Add 3 components to 10k entities: ${addTime.toFixed(2)}ms`);
|
|
1640
|
-
console.log(`Remove 2 components from 10k entities: ${removeTime.toFixed(2)}ms`);
|
|
1641
|
-
console.log(`Total archetype change time: ${(addTime + removeTime).toFixed(2)}ms`);
|
|
1642
|
-
|
|
1643
|
-
expect(true).toBe(true);
|
|
1644
|
-
}, { timeout: 30000 });
|
|
1645
|
-
});
|