murow 0.0.60 → 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/loop → types/core/driver}/drivers/immediate.d.ts +1 -1
- package/dist/{core/loop → types/core/driver}/drivers/raf.d.ts +1 -1
- package/dist/{core/loop → types/core/driver}/drivers/timeout.d.ts +1 -1
- package/dist/{core/loop → types/core/driver}/index.d.ts +1 -1
- package/dist/{core → types/core}/events/event-system.d.ts +14 -33
- package/dist/{core → types/core}/fixed-ticker/fixed-ticker.d.ts +1 -1
- 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 +7 -1
- package/dist/types/core/input/index.d.ts +3 -0
- package/dist/types/core/input/manager.d.ts +56 -0
- package/dist/types/core/input/sources/browser.d.ts +9 -0
- package/dist/types/core/input/sources/index.d.ts +1 -0
- package/dist/types/core/input/types.d.ts +36 -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}/system-builder.d.ts +20 -9
- package/dist/{ecs → types/ecs}/world.d.ts +11 -0
- package/dist/types/game/index.d.ts +1 -0
- package/dist/types/game/loop/index.d.ts +1 -0
- package/dist/types/game/loop/loop.d.ts +175 -0
- package/dist/{index.d.ts → types/index.d.ts} +2 -0
- package/dist/{net → types/net}/index.d.ts +2 -2
- package/dist/{net → types/net}/server.d.ts +39 -19
- package/dist/{protocol → types/protocol}/intent/define-intent.d.ts +15 -0
- package/dist/{protocol → types/protocol}/intent/index.d.ts +1 -1
- 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/2d/sprite-accessor.test.d.ts +1 -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/events/event-system.js +0 -88
- package/dist/core/events/index.js +0 -1
- package/dist/core/fixed-ticker/fixed-ticker.js +0 -101
- 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 -9
- package/dist/core/lerp/index.js +0 -1
- package/dist/core/lerp/lerp.js +0 -42
- package/dist/core/loop/drivers/immediate.js +0 -61
- package/dist/core/loop/drivers/index.js +0 -3
- package/dist/core/loop/drivers/raf.js +0 -62
- package/dist/core/loop/drivers/timeout.js +0 -71
- package/dist/core/loop/index.js +0 -2
- package/dist/core/loop/loop.js +0 -47
- 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 -180
- package/dist/ecs/system.d.ts +0 -63
- package/dist/ecs/system.js +0 -92
- package/dist/ecs/world-systems.js +0 -79
- package/dist/ecs/world.js +0 -684
- package/dist/index.js +0 -24
- 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 -938
- 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/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 -169
- 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 -9
- 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/loop/README.md +0 -97
- package/src/core/loop/drivers/immediate.ts +0 -66
- package/src/core/loop/drivers/index.ts +0 -3
- package/src/core/loop/drivers/raf.ts +0 -67
- package/src/core/loop/drivers/timeout.ts +0 -77
- package/src/core/loop/index.ts +0 -2
- package/src/core/loop/loop.test.ts +0 -414
- package/src/core/loop/loop.ts +0 -71
- 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 -309
- package/src/ecs/system.ts +0 -111
- package/src/ecs/world-systems.ts +0 -83
- package/src/ecs/world.test.ts +0 -310
- package/src/ecs/world.ts +0 -828
- package/src/index.ts +0 -28
- 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 -1116
- 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 -182
- 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/loop/loop.d.ts → types/core/driver/driver.d.ts} +0 -0
- /package/dist/{core/loop → types/core/driver}/drivers/index.d.ts +0 -0
- /package/dist/{core → types/core}/events/index.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}/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}/world-systems.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}/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/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/{protocol/intent/intent.js → webgpu/types/2d/animation.test.d.ts} +0 -0
package/dist/net/server.js
DELETED
|
@@ -1,938 +0,0 @@
|
|
|
1
|
-
import { MessageType, MessagePriority } from "./types";
|
|
2
|
-
import { MessageWrapperPool } from "./buffer-pool";
|
|
3
|
-
/**
|
|
4
|
-
* Generic game server that manages multiple peer connections
|
|
5
|
-
* Each peer gets its own snapshot registry for per-peer state tracking (fog of war, interest management)
|
|
6
|
-
*
|
|
7
|
-
* @template TPeer The transport adapter type for peer connections
|
|
8
|
-
* @template TSnapshots Union type of all possible snapshot update types
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* **Client-Side Prediction Support:**
|
|
12
|
-
* - Automatically tracks the last processed client tick for each peer
|
|
13
|
-
* - All intents include a 'tick' field (added by defineIntent())
|
|
14
|
-
* - Use `getConfirmedClientTick(peerId)` to get the confirmed tick for snapshots
|
|
15
|
-
* - Use `onAnyIntent()` to track which peers need snapshot responses
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* type GameSnapshots = PlayerUpdate | ScoreUpdate | ProjectileUpdate;
|
|
20
|
-
*
|
|
21
|
-
* const server = new ServerNetwork<WebSocketPeer, GameSnapshots>({
|
|
22
|
-
* transport: wsServerTransport,
|
|
23
|
-
* intentRegistry,
|
|
24
|
-
* createPeerSnapshotRegistry: () => {
|
|
25
|
-
* const registry = new SnapshotRegistry<GameSnapshots>();
|
|
26
|
-
* registry.register('players', playerCodec);
|
|
27
|
-
* registry.register('score', scoreCodec);
|
|
28
|
-
* return registry;
|
|
29
|
-
* },
|
|
30
|
-
* });
|
|
31
|
-
*
|
|
32
|
-
* // Track which peers need responses (fires for ALL intents)
|
|
33
|
-
* const pendingResponses = new Set<string>();
|
|
34
|
-
* server.onAnyIntent((peerId) => {
|
|
35
|
-
* pendingResponses.add(peerId);
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* // Type-safe intent handlers
|
|
39
|
-
* server.onIntent<MoveIntent>(IntentKind.Move, (peerId, intent) => {
|
|
40
|
-
* intent.tick // ✅ Automatically included in all intents
|
|
41
|
-
* intent.dx // ✅ Correctly typed
|
|
42
|
-
* });
|
|
43
|
-
*
|
|
44
|
-
* // Send snapshot with confirmed client tick (for client-side prediction)
|
|
45
|
-
* const confirmedTick = server.getConfirmedClientTick(peerId);
|
|
46
|
-
* server.sendSnapshotToPeer(peerId, 'players', {
|
|
47
|
-
* tick: confirmedTick, // Client can reconcile based on this
|
|
48
|
-
* updates: { players: [...] }
|
|
49
|
-
* });
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
export class ServerNetwork {
|
|
53
|
-
constructor(config) {
|
|
54
|
-
/** Per-peer state tracking */
|
|
55
|
-
this.peers = new Map();
|
|
56
|
-
/** Per-peer snapshot registries - this is the key feature! */
|
|
57
|
-
this.peerSnapshotRegistries = new Map();
|
|
58
|
-
/** Track last processed client tick per peer (for client-side prediction) */
|
|
59
|
-
this.lastProcessedClientTick = new Map();
|
|
60
|
-
/** Track last sent snapshot hashes per peer per type (for delta detection) */
|
|
61
|
-
this.lastSnapshotHashes = new Map();
|
|
62
|
-
/** Intent handlers: kind -> handler[] (supports multiple handlers) */
|
|
63
|
-
this.intentHandlers = new Map();
|
|
64
|
-
/** Global intent handler called for ALL intents before specific handlers */
|
|
65
|
-
this.anyIntentHandlers = [];
|
|
66
|
-
/** RPC method handlers: method -> handler[] (supports multiple handlers) */
|
|
67
|
-
this.rpcHandlers = new Map();
|
|
68
|
-
/** Connection lifecycle handlers */
|
|
69
|
-
this.connectionHandlers = [];
|
|
70
|
-
this.disconnectionHandlers = [];
|
|
71
|
-
/** Message wrapper pool for zero-allocation message wrapping */
|
|
72
|
-
this.messagePool = null;
|
|
73
|
-
/** Heartbeat interval timer */
|
|
74
|
-
this.heartbeatTimer = null;
|
|
75
|
-
this.transport = config.transport;
|
|
76
|
-
this.intentRegistry = config.intentRegistry;
|
|
77
|
-
this.createPeerSnapshotRegistry = config.createPeerSnapshotRegistry;
|
|
78
|
-
this.rpcRegistry = config.rpcRegistry;
|
|
79
|
-
this.config = {
|
|
80
|
-
maxMessageSize: config.config?.maxMessageSize ?? 65536,
|
|
81
|
-
debug: config.config?.debug ?? false,
|
|
82
|
-
maxMessagesPerSecond: config.config?.maxMessagesPerSecond ?? 100,
|
|
83
|
-
maxSendQueueSize: config.config?.maxSendQueueSize ?? 100,
|
|
84
|
-
enableBufferPooling: config.config?.enableBufferPooling ?? true,
|
|
85
|
-
heartbeatInterval: config.config?.heartbeatInterval ?? 30000,
|
|
86
|
-
heartbeatTimeout: config.config?.heartbeatTimeout ?? 60000,
|
|
87
|
-
lagSimulation: config.config?.lagSimulation ?? 0,
|
|
88
|
-
};
|
|
89
|
-
// Initialize message pool if buffer pooling is enabled
|
|
90
|
-
if (this.config.enableBufferPooling) {
|
|
91
|
-
this.messagePool = new MessageWrapperPool();
|
|
92
|
-
}
|
|
93
|
-
this.setupTransportHandlers();
|
|
94
|
-
this.setupHeartbeat();
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Get the snapshot registry for a specific peer
|
|
98
|
-
*/
|
|
99
|
-
getPeerSnapshotRegistry(peerId) {
|
|
100
|
-
return this.peerSnapshotRegistries.get(peerId);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Register a handler for a specific intent kind (type-safe)
|
|
104
|
-
* Supports multiple handlers per intent type
|
|
105
|
-
* @template T The intent type for this handler
|
|
106
|
-
* @param validator Optional validation function - if it returns false, intent is rejected
|
|
107
|
-
* @returns Unsubscribe function to remove this handler
|
|
108
|
-
*/
|
|
109
|
-
onIntent(intent, handler, validator) {
|
|
110
|
-
let handlers = this.intentHandlers.get(intent.kind);
|
|
111
|
-
if (!handlers) {
|
|
112
|
-
handlers = [];
|
|
113
|
-
this.intentHandlers.set(intent.kind, handlers);
|
|
114
|
-
}
|
|
115
|
-
// Wrap handler with validator if provided
|
|
116
|
-
const wrappedHandler = (peerId, intent) => {
|
|
117
|
-
if (validator) {
|
|
118
|
-
if (!validator(peerId, intent)) {
|
|
119
|
-
this.log(`Intent validation failed for peer ${peerId}, kind ${intent.kind}`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
handler(peerId, intent);
|
|
124
|
-
};
|
|
125
|
-
handlers.push(wrappedHandler);
|
|
126
|
-
// Return unsubscribe function
|
|
127
|
-
return () => {
|
|
128
|
-
const handlers = this.intentHandlers.get(intent.kind);
|
|
129
|
-
if (handlers) {
|
|
130
|
-
const index = handlers.indexOf(wrappedHandler);
|
|
131
|
-
if (index > -1) {
|
|
132
|
-
handlers.splice(index, 1);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Register a handler that fires for ALL intents, regardless of kind.
|
|
139
|
-
* Useful for tracking which peers sent intents this tick.
|
|
140
|
-
*
|
|
141
|
-
* @param handler Callback invoked for every intent before specific handlers
|
|
142
|
-
* @returns Unsubscribe function
|
|
143
|
-
*
|
|
144
|
-
* @remarks
|
|
145
|
-
* This handler is called BEFORE the specific intent handlers registered via onIntent().
|
|
146
|
-
* Common use case: Track which peers need snapshot responses this tick.
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* ```ts
|
|
150
|
-
* const pendingResponses = new Set<string>();
|
|
151
|
-
*
|
|
152
|
-
* server.onAnyIntent((peerId, intent) => {
|
|
153
|
-
* // Mark this peer as needing a response on next tick
|
|
154
|
-
* pendingResponses.add(peerId);
|
|
155
|
-
* });
|
|
156
|
-
* ```
|
|
157
|
-
*/
|
|
158
|
-
onAnyIntent(handler) {
|
|
159
|
-
this.anyIntentHandlers.push(handler);
|
|
160
|
-
// Return unsubscribe function
|
|
161
|
-
return () => {
|
|
162
|
-
const index = this.anyIntentHandlers.indexOf(handler);
|
|
163
|
-
if (index > -1) {
|
|
164
|
-
this.anyIntentHandlers.splice(index, 1);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Register a handler for new connections
|
|
170
|
-
*/
|
|
171
|
-
onConnection(handler) {
|
|
172
|
-
this.connectionHandlers.push(handler);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Register a handler for disconnections
|
|
176
|
-
*/
|
|
177
|
-
onDisconnection(handler) {
|
|
178
|
-
this.disconnectionHandlers.push(handler);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Send an RPC to a specific peer (type-safe)
|
|
182
|
-
*
|
|
183
|
-
* @template TSchema The RPC data type
|
|
184
|
-
* @param peerId The peer to send to
|
|
185
|
-
* @param rpc The RPC definition created by defineRPC()
|
|
186
|
-
* @param data The RPC data to send
|
|
187
|
-
* @param priority Message priority (default: NORMAL)
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```ts
|
|
191
|
-
* const MatchCountdown = defineRPC({
|
|
192
|
-
* method: 'matchCountdown',
|
|
193
|
-
* schema: { secondsRemaining: BinaryCodec.u8 }
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* server.sendRpc(peerId, MatchCountdown, { secondsRemaining: 10 });
|
|
197
|
-
* ```
|
|
198
|
-
*/
|
|
199
|
-
sendRPC(peerId, rpc, data, priority = MessagePriority.NORMAL) {
|
|
200
|
-
if (!this.rpcRegistry) {
|
|
201
|
-
throw new Error('RpcRegistry not configured. Pass rpcRegistry to ServerNetworkConfig.');
|
|
202
|
-
}
|
|
203
|
-
const peer = this.peers.get(peerId);
|
|
204
|
-
if (!peer) {
|
|
205
|
-
this.log(`Cannot send RPC to unknown peer: ${peerId}`);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
// Encode RPC
|
|
210
|
-
const rpcData = this.rpcRegistry.encode(rpc, data);
|
|
211
|
-
// Wrap with message type header (use pool if enabled)
|
|
212
|
-
let message;
|
|
213
|
-
if (this.messagePool) {
|
|
214
|
-
message = this.messagePool.wrap(MessageType.CUSTOM, rpcData);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
message = new Uint8Array(1 + rpcData.byteLength);
|
|
218
|
-
message[0] = MessageType.CUSTOM;
|
|
219
|
-
message.set(rpcData, 1);
|
|
220
|
-
}
|
|
221
|
-
// Check backpressure and queue if necessary
|
|
222
|
-
if (peer.isBackpressured || peer.sendQueue.length > 0) {
|
|
223
|
-
// Peer is experiencing backpressure, queue the message with priority
|
|
224
|
-
this.queueMessage(peer, message, priority);
|
|
225
|
-
// Release pooled buffer since we copied it in queueMessage
|
|
226
|
-
if (this.messagePool) {
|
|
227
|
-
this.messagePool.release(message);
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
// Try to send immediately
|
|
232
|
-
this.sendMessageToPeer(peer, message);
|
|
233
|
-
// Release pooled buffer after send
|
|
234
|
-
if (this.messagePool) {
|
|
235
|
-
this.messagePool.release(message);
|
|
236
|
-
}
|
|
237
|
-
this.log(`Sent RPC (method: ${rpc.method}) to peer: ${peerId}`);
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
this.log(`Failed to send RPC to peer ${peerId}: ${error}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Send an RPC to all connected peers (broadcast)
|
|
245
|
-
*
|
|
246
|
-
* @template TSchema The RPC data type
|
|
247
|
-
* @param rpc The RPC definition created by defineRPC()
|
|
248
|
-
* @param data The RPC data to send
|
|
249
|
-
* @param priority Message priority (default: NORMAL)
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* ```ts
|
|
253
|
-
* server.sendRpcBroadcast(MatchCountdown, { secondsRemaining: 3 });
|
|
254
|
-
* ```
|
|
255
|
-
*/
|
|
256
|
-
sendRpcBroadcast(rpc, data, priority = MessagePriority.NORMAL) {
|
|
257
|
-
for (const peerId of this.getPeerIds()) {
|
|
258
|
-
this.sendRPC(peerId, rpc, data, priority);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Register a handler for incoming RPCs from clients (type-safe)
|
|
263
|
-
* Supports multiple handlers per RPC method
|
|
264
|
-
*
|
|
265
|
-
* @template TSchema The RPC data type
|
|
266
|
-
* @param rpc The RPC definition created by defineRPC()
|
|
267
|
-
* @param handler Callback function to handle the RPC
|
|
268
|
-
* @returns Unsubscribe function to remove this handler
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* ```ts
|
|
272
|
-
* const BuyItem = defineRPC({
|
|
273
|
-
* method: 'buyItem',
|
|
274
|
-
* schema: { itemId: BinaryCodec.string(32) }
|
|
275
|
-
* });
|
|
276
|
-
*
|
|
277
|
-
* server.onRpc(BuyItem, (peerId, rpc) => {
|
|
278
|
-
* console.log(`${peerId} wants to buy ${rpc.itemId}`);
|
|
279
|
-
* });
|
|
280
|
-
* ```
|
|
281
|
-
*/
|
|
282
|
-
onRPC(rpc, handler) {
|
|
283
|
-
if (!this.rpcRegistry) {
|
|
284
|
-
throw new Error('RpcRegistry not configured. Pass rpcRegistry to ServerNetworkConfig.');
|
|
285
|
-
}
|
|
286
|
-
let handlers = this.rpcHandlers.get(rpc.method);
|
|
287
|
-
if (!handlers) {
|
|
288
|
-
handlers = [];
|
|
289
|
-
this.rpcHandlers.set(rpc.method, handlers);
|
|
290
|
-
}
|
|
291
|
-
handlers.push(handler);
|
|
292
|
-
// Return unsubscribe function
|
|
293
|
-
return () => {
|
|
294
|
-
const handlers = this.rpcHandlers.get(rpc.method);
|
|
295
|
-
if (handlers) {
|
|
296
|
-
const index = handlers.indexOf(handler);
|
|
297
|
-
if (index > -1) {
|
|
298
|
-
handlers.splice(index, 1);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Send a snapshot to a specific peer using their dedicated snapshot registry (type-safe)
|
|
305
|
-
* @template T The specific snapshot update type
|
|
306
|
-
* @param priority Message priority (default: NORMAL)
|
|
307
|
-
*/
|
|
308
|
-
sendSnapshotToPeer(peerId, type, snapshot, priority = MessagePriority.NORMAL) {
|
|
309
|
-
const peer = this.peers.get(peerId);
|
|
310
|
-
if (!peer) {
|
|
311
|
-
this.log(`Cannot send snapshot to unknown peer: ${peerId}`);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const registry = this.peerSnapshotRegistries.get(peerId);
|
|
315
|
-
if (!registry) {
|
|
316
|
-
throw new Error(`No snapshot registry registered for peer: ${peerId}`);
|
|
317
|
-
}
|
|
318
|
-
// Encode snapshot using peer-specific registry
|
|
319
|
-
const snapshotData = registry.encode(type, snapshot);
|
|
320
|
-
// Wrap with message type header (use pool if enabled)
|
|
321
|
-
let message;
|
|
322
|
-
if (this.messagePool) {
|
|
323
|
-
// Zero-copy path: reuse pooled buffer
|
|
324
|
-
message = this.messagePool.wrap(MessageType.SNAPSHOT, snapshotData);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// Fallback path: allocate new buffer
|
|
328
|
-
message = new Uint8Array(1 + snapshotData.byteLength);
|
|
329
|
-
message[0] = MessageType.SNAPSHOT;
|
|
330
|
-
message.set(snapshotData, 1);
|
|
331
|
-
}
|
|
332
|
-
// Check backpressure and queue if necessary
|
|
333
|
-
if (peer.isBackpressured || peer.sendQueue.length > 0) {
|
|
334
|
-
// Peer is experiencing backpressure, queue the message with priority
|
|
335
|
-
this.queueMessage(peer, message, priority);
|
|
336
|
-
// Release pooled buffer since we copied it in queueMessage
|
|
337
|
-
if (this.messagePool) {
|
|
338
|
-
this.messagePool.release(message);
|
|
339
|
-
}
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
// Try to send immediately
|
|
343
|
-
this.sendMessageToPeer(peer, message);
|
|
344
|
-
// Update tracking
|
|
345
|
-
peer.lastSentTick = snapshot.tick;
|
|
346
|
-
this.log(`Sent snapshot (type: ${type}, tick: ${snapshot.tick}) to peer ${peerId}`);
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Send a snapshot to a peer only if it has changed since the last send.
|
|
350
|
-
* Uses fast binary hash comparison with zero allocations.
|
|
351
|
-
*
|
|
352
|
-
* @template T The specific snapshot update type
|
|
353
|
-
* @param peerId The peer to send to
|
|
354
|
-
* @param type The snapshot type identifier
|
|
355
|
-
* @param snapshot The snapshot to send
|
|
356
|
-
* @param priority Message priority (default: NORMAL)
|
|
357
|
-
* @returns true if snapshot was sent, false if skipped (no change)
|
|
358
|
-
*
|
|
359
|
-
* @remarks
|
|
360
|
-
* This method encodes the snapshot and computes a hash of the binary data.
|
|
361
|
-
* This is more efficient than JSON.stringify because:
|
|
362
|
-
* 1. No string allocations (hash is a number)
|
|
363
|
-
* 2. Binary hashing is faster than string hashing
|
|
364
|
-
* 3. Reuses the encoding work (binary data needed for sending anyway)
|
|
365
|
-
*
|
|
366
|
-
* @example
|
|
367
|
-
* ```ts
|
|
368
|
-
* // In your game tick loop
|
|
369
|
-
* for (const peerId of server.getPeerIds()) {
|
|
370
|
-
* const wasSent = server.sendSnapshotToPeerIfChanged(peerId, 'gameState', {
|
|
371
|
-
* tick: currentTick,
|
|
372
|
-
* updates: gameState
|
|
373
|
-
* });
|
|
374
|
-
* if (wasSent) sentCount++;
|
|
375
|
-
* }
|
|
376
|
-
* ```
|
|
377
|
-
*/
|
|
378
|
-
sendSnapshotToPeerIfChanged(peerId, type, snapshot, priority = MessagePriority.NORMAL) {
|
|
379
|
-
const peer = this.peers.get(peerId);
|
|
380
|
-
if (!peer) {
|
|
381
|
-
this.log(`Cannot send snapshot to unknown peer: ${peerId}`);
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
const registry = this.peerSnapshotRegistries.get(peerId);
|
|
385
|
-
if (!registry) {
|
|
386
|
-
throw new Error(`No snapshot registry registered for peer: ${peerId}`);
|
|
387
|
-
}
|
|
388
|
-
// Encode snapshot (needed for both hashing and sending)
|
|
389
|
-
const snapshotData = registry.encode(type, snapshot);
|
|
390
|
-
// Get or create hash map for this peer
|
|
391
|
-
let peerHashes = this.lastSnapshotHashes.get(peerId);
|
|
392
|
-
if (!peerHashes) {
|
|
393
|
-
peerHashes = new Map();
|
|
394
|
-
this.lastSnapshotHashes.set(peerId, peerHashes);
|
|
395
|
-
}
|
|
396
|
-
// Compute hash of only the updates portion (skip typeId + tick)
|
|
397
|
-
// Binary format: [typeId(1) + tick(4) + updates(...)]
|
|
398
|
-
// We only want to hash the updates, not the tick
|
|
399
|
-
const updatesData = snapshotData.subarray(5); // Skip first 5 bytes
|
|
400
|
-
const currentHash = this.hashBinary(updatesData);
|
|
401
|
-
const lastHash = peerHashes.get(type);
|
|
402
|
-
// Only send if changed (or first time)
|
|
403
|
-
if (lastHash === undefined || currentHash !== lastHash) {
|
|
404
|
-
// Wrap with message type header (use pool if enabled)
|
|
405
|
-
let message;
|
|
406
|
-
if (this.messagePool) {
|
|
407
|
-
// Zero-copy path: reuse pooled buffer
|
|
408
|
-
message = this.messagePool.wrap(MessageType.SNAPSHOT, snapshotData);
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
// Fallback path: allocate new buffer
|
|
412
|
-
message = new Uint8Array(1 + snapshotData.byteLength);
|
|
413
|
-
message[0] = MessageType.SNAPSHOT;
|
|
414
|
-
message.set(snapshotData, 1);
|
|
415
|
-
}
|
|
416
|
-
// Check backpressure and queue if necessary
|
|
417
|
-
if (peer.isBackpressured || peer.sendQueue.length > 0) {
|
|
418
|
-
this.queueMessage(peer, message, priority);
|
|
419
|
-
if (this.messagePool) {
|
|
420
|
-
this.messagePool.release(message);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
// Try to send immediately
|
|
425
|
-
this.sendMessageToPeer(peer, message);
|
|
426
|
-
}
|
|
427
|
-
// Update tracking
|
|
428
|
-
peer.lastSentTick = snapshot.tick;
|
|
429
|
-
peerHashes.set(type, currentHash);
|
|
430
|
-
this.log(`Sent snapshot (type: ${type}, tick: ${snapshot.tick}) to peer ${peerId}`);
|
|
431
|
-
return true; // Sent
|
|
432
|
-
}
|
|
433
|
-
this.log(`Skipped snapshot (type: ${type}) to peer ${peerId} - no change detected`);
|
|
434
|
-
return false; // Skipped (no change)
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Fast binary hash function with zero allocations.
|
|
438
|
-
* Uses FNV-1a algorithm optimized for binary data.
|
|
439
|
-
*
|
|
440
|
-
* @param data The binary data to hash
|
|
441
|
-
* @returns A 32-bit hash value
|
|
442
|
-
*
|
|
443
|
-
* @remarks
|
|
444
|
-
* This is significantly faster than JSON.stringify + string hashing because:
|
|
445
|
-
* - No string allocations
|
|
446
|
-
* - Direct byte-level hashing
|
|
447
|
-
* - Operates on data that's already needed for encoding
|
|
448
|
-
*
|
|
449
|
-
* FNV-1a is chosen for its speed and good distribution properties.
|
|
450
|
-
*/
|
|
451
|
-
hashBinary(data) {
|
|
452
|
-
let hash = 2166136261; // FNV-1a 32-bit offset basis
|
|
453
|
-
for (let i = 0; i < data.length; i++) {
|
|
454
|
-
hash ^= data[i];
|
|
455
|
-
hash = Math.imul(hash, 16777619); // FNV-1a 32-bit prime
|
|
456
|
-
}
|
|
457
|
-
return hash >>> 0; // Ensure unsigned 32-bit integer
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Queue a message with priority
|
|
461
|
-
*/
|
|
462
|
-
queueMessage(peer, message, priority) {
|
|
463
|
-
const queuedMessage = {
|
|
464
|
-
data: new Uint8Array(message), // Copy to avoid pool reuse issues
|
|
465
|
-
priority,
|
|
466
|
-
timestamp: Date.now(),
|
|
467
|
-
};
|
|
468
|
-
// If queue is full, drop lowest priority message
|
|
469
|
-
if (peer.sendQueue.length >= this.config.maxSendQueueSize) {
|
|
470
|
-
// Sort by priority (ascending) to find lowest priority message
|
|
471
|
-
peer.sendQueue.sort((a, b) => a.priority - b.priority);
|
|
472
|
-
// Drop the lowest priority message (first in sorted array)
|
|
473
|
-
const dropped = peer.sendQueue.shift();
|
|
474
|
-
this.log(`Send queue full for peer ${peer.peerId}, dropping ${MessagePriority[dropped.priority]} priority message`);
|
|
475
|
-
}
|
|
476
|
-
// Insert message in priority order (higher priority first)
|
|
477
|
-
let insertIndex = peer.sendQueue.length;
|
|
478
|
-
for (let i = 0; i < peer.sendQueue.length; i++) {
|
|
479
|
-
if (queuedMessage.priority > peer.sendQueue[i].priority) {
|
|
480
|
-
insertIndex = i;
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
peer.sendQueue.splice(insertIndex, 0, queuedMessage);
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Internal: Send a message to a peer, handling pooling and bandwidth tracking
|
|
488
|
-
*/
|
|
489
|
-
sendMessageToPeer(peer, message) {
|
|
490
|
-
// Track bandwidth
|
|
491
|
-
this.trackBandwidth(peer, message.byteLength);
|
|
492
|
-
// Send to peer
|
|
493
|
-
const sendResult = peer.transport.send(message);
|
|
494
|
-
// Handle both sync and async transports for pool cleanup
|
|
495
|
-
if (this.messagePool) {
|
|
496
|
-
if (sendResult instanceof Promise) {
|
|
497
|
-
// Async transport: wait for send to complete before releasing
|
|
498
|
-
sendResult.then(() => {
|
|
499
|
-
this.messagePool.release(message);
|
|
500
|
-
// Try to flush queue if there are pending messages
|
|
501
|
-
this.flushSendQueue(peer.peerId);
|
|
502
|
-
}).catch(() => {
|
|
503
|
-
// Mark peer as backpressured on send failure
|
|
504
|
-
peer.isBackpressured = true;
|
|
505
|
-
this.messagePool.release(message);
|
|
506
|
-
this.log(`Send failed for peer ${peer.peerId}, marking as backpressured`);
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
// Sync transport: release immediately
|
|
511
|
-
this.messagePool.release(message);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
else if (sendResult instanceof Promise) {
|
|
515
|
-
// No pool but async transport - still handle failures
|
|
516
|
-
sendResult.catch(() => {
|
|
517
|
-
peer.isBackpressured = true;
|
|
518
|
-
this.log(`Send failed for peer ${peer.peerId}, marking as backpressured`);
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Flush send queue for a peer (called after successful sends)
|
|
524
|
-
* Sends messages in priority order (highest priority first)
|
|
525
|
-
*/
|
|
526
|
-
flushSendQueue(peerId) {
|
|
527
|
-
const peer = this.peers.get(peerId);
|
|
528
|
-
if (!peer || peer.sendQueue.length === 0) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
// Mark as no longer backpressured
|
|
532
|
-
peer.isBackpressured = false;
|
|
533
|
-
// Send queued messages (up to a limit per flush to avoid blocking)
|
|
534
|
-
// Queue is already sorted by priority (highest first)
|
|
535
|
-
const maxMessagesPerFlush = 10;
|
|
536
|
-
let sent = 0;
|
|
537
|
-
while (peer.sendQueue.length > 0 && sent < maxMessagesPerFlush) {
|
|
538
|
-
const queuedMessage = peer.sendQueue.shift();
|
|
539
|
-
this.sendMessageToPeer(peer, queuedMessage.data);
|
|
540
|
-
sent++;
|
|
541
|
-
// If we hit backpressure again, stop flushing
|
|
542
|
-
if (peer.isBackpressured) {
|
|
543
|
-
break;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
if (peer.sendQueue.length > 0) {
|
|
547
|
-
this.log(`Peer ${peerId} still has ${peer.sendQueue.length} queued messages`);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Track bandwidth usage for a peer
|
|
552
|
-
*/
|
|
553
|
-
trackBandwidth(peer, bytes) {
|
|
554
|
-
const now = Date.now();
|
|
555
|
-
const windowStart = Math.floor(now / 1000) * 1000;
|
|
556
|
-
// Reset counter if we're in a new time window
|
|
557
|
-
if (peer.bandwidthWindow !== windowStart) {
|
|
558
|
-
peer.bandwidthWindow = windowStart;
|
|
559
|
-
peer.bytesSent = 0;
|
|
560
|
-
}
|
|
561
|
-
peer.bytesSent += bytes;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Broadcast a snapshot to all connected peers (type-safe)
|
|
565
|
-
* Each peer receives the snapshot encoded with their own snapshot registry
|
|
566
|
-
* @template T The specific snapshot update type
|
|
567
|
-
* @param priority Message priority (default: NORMAL)
|
|
568
|
-
*/
|
|
569
|
-
broadcastSnapshot(type, snapshot, filter, priority = MessagePriority.NORMAL) {
|
|
570
|
-
for (const peerId of this.peers.keys()) {
|
|
571
|
-
if (filter && !filter(peerId)) {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
this.sendSnapshotToPeer(peerId, type, snapshot, priority);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Advanced: Broadcast with per-peer snapshot customization (type-safe)
|
|
579
|
-
* Allows you to modify the snapshot for each peer (e.g., fog of war, interest management)
|
|
580
|
-
* @template T The specific snapshot update type
|
|
581
|
-
* @param priority Message priority (default: NORMAL)
|
|
582
|
-
*/
|
|
583
|
-
broadcastSnapshotWithCustomization(type, baseSnapshot, customize, priority = MessagePriority.NORMAL) {
|
|
584
|
-
for (const peerId of this.peers.keys()) {
|
|
585
|
-
const customSnapshot = customize(peerId, baseSnapshot);
|
|
586
|
-
this.sendSnapshotToPeer(peerId, type, customSnapshot, priority);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Get all connected peer IDs
|
|
591
|
-
*/
|
|
592
|
-
getPeerIds() {
|
|
593
|
-
return Array.from(this.peers.keys());
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Get peer state for a specific peer
|
|
597
|
-
*/
|
|
598
|
-
getPeerState(peerId) {
|
|
599
|
-
return this.peers.get(peerId);
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Update peer metadata
|
|
603
|
-
*/
|
|
604
|
-
setPeerMetadata(peerId, key, value) {
|
|
605
|
-
const peer = this.peers.get(peerId);
|
|
606
|
-
if (peer) {
|
|
607
|
-
peer.metadata[key] = value;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Get bandwidth usage for a peer (bytes per second in current window)
|
|
612
|
-
*/
|
|
613
|
-
getPeerBandwidth(peerId) {
|
|
614
|
-
const peer = this.peers.get(peerId);
|
|
615
|
-
return peer ? peer.bytesSent : 0;
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Get total bandwidth usage across all peers
|
|
619
|
-
*/
|
|
620
|
-
getTotalBandwidth() {
|
|
621
|
-
let total = 0;
|
|
622
|
-
for (const peer of this.peers.values()) {
|
|
623
|
-
total += peer.bytesSent;
|
|
624
|
-
}
|
|
625
|
-
return total;
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Check if a peer is experiencing backpressure
|
|
629
|
-
*/
|
|
630
|
-
isPeerBackpressured(peerId) {
|
|
631
|
-
const peer = this.peers.get(peerId);
|
|
632
|
-
return peer ? peer.isBackpressured || peer.sendQueue.length > 0 : false;
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Get the last confirmed client tick for a peer.
|
|
636
|
-
* Used for client-side prediction reconciliation.
|
|
637
|
-
*
|
|
638
|
-
* @param peerId The peer ID to query
|
|
639
|
-
* @returns The last processed client tick number, or 0 if peer not found
|
|
640
|
-
*
|
|
641
|
-
* @remarks
|
|
642
|
-
* This is automatically tracked when intents arrive, as all intents
|
|
643
|
-
* include a 'tick' field (added by defineIntent).
|
|
644
|
-
*/
|
|
645
|
-
getConfirmedClientTick(peerId) {
|
|
646
|
-
return this.lastProcessedClientTick.get(peerId) ?? 0;
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Set the confirmed client tick for a peer.
|
|
650
|
-
* Rarely needed as this is automatically tracked by handleIntent.
|
|
651
|
-
*
|
|
652
|
-
* @param peerId The peer ID
|
|
653
|
-
* @param tick The client tick number to set
|
|
654
|
-
*
|
|
655
|
-
* @remarks
|
|
656
|
-
* All intents automatically include a 'tick' field via defineIntent(),
|
|
657
|
-
* so this is tracked automatically when intents are received.
|
|
658
|
-
*/
|
|
659
|
-
setConfirmedClientTick(peerId, tick) {
|
|
660
|
-
this.lastProcessedClientTick.set(peerId, tick);
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Close the server and all connections
|
|
664
|
-
*/
|
|
665
|
-
close() {
|
|
666
|
-
this.log("Closing server...");
|
|
667
|
-
// Stop heartbeat timer
|
|
668
|
-
if (this.heartbeatTimer) {
|
|
669
|
-
clearInterval(this.heartbeatTimer);
|
|
670
|
-
this.heartbeatTimer = null;
|
|
671
|
-
}
|
|
672
|
-
return this.transport.close();
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Setup transport event handlers
|
|
676
|
-
*/
|
|
677
|
-
setupTransportHandlers() {
|
|
678
|
-
this.transport.onConnection((peer, peerId) => {
|
|
679
|
-
this.handleConnection(peer, peerId);
|
|
680
|
-
});
|
|
681
|
-
this.transport.onDisconnection((peerId) => {
|
|
682
|
-
this.handleDisconnection(peerId);
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Setup heartbeat mechanism
|
|
687
|
-
*/
|
|
688
|
-
setupHeartbeat() {
|
|
689
|
-
if (this.config.heartbeatInterval === 0) {
|
|
690
|
-
return; // Heartbeats disabled
|
|
691
|
-
}
|
|
692
|
-
this.heartbeatTimer = setInterval(() => {
|
|
693
|
-
this.checkHeartbeats();
|
|
694
|
-
}, this.config.heartbeatInterval);
|
|
695
|
-
}
|
|
696
|
-
/**
|
|
697
|
-
* Check all peers for heartbeat timeout and send heartbeats
|
|
698
|
-
*/
|
|
699
|
-
checkHeartbeats() {
|
|
700
|
-
const now = Date.now();
|
|
701
|
-
const heartbeatMessage = new Uint8Array([MessageType.HEARTBEAT]);
|
|
702
|
-
for (const [peerId, peer] of this.peers.entries()) {
|
|
703
|
-
// Check if peer has timed out
|
|
704
|
-
const timeSinceLastMessage = now - peer.lastMessageReceivedAt;
|
|
705
|
-
if (timeSinceLastMessage > this.config.heartbeatTimeout) {
|
|
706
|
-
this.log(`Peer ${peerId} timed out (no message for ${timeSinceLastMessage}ms)`);
|
|
707
|
-
// Close the connection - this will trigger handleDisconnection
|
|
708
|
-
peer.transport.close();
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
// Send heartbeat to peer
|
|
712
|
-
try {
|
|
713
|
-
peer.transport.send(heartbeatMessage);
|
|
714
|
-
this.trackBandwidth(peer, heartbeatMessage.byteLength);
|
|
715
|
-
}
|
|
716
|
-
catch (error) {
|
|
717
|
-
this.log(`Failed to send heartbeat to peer ${peerId}: ${error}`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Handle new peer connection
|
|
723
|
-
*/
|
|
724
|
-
handleConnection(peer, peerId) {
|
|
725
|
-
this.log(`Peer connected: ${peerId}`);
|
|
726
|
-
// Create peer state
|
|
727
|
-
const now = Date.now();
|
|
728
|
-
const peerState = {
|
|
729
|
-
peerId,
|
|
730
|
-
transport: peer,
|
|
731
|
-
lastSentTick: 0,
|
|
732
|
-
connectedAt: now,
|
|
733
|
-
metadata: {},
|
|
734
|
-
messageCount: 0,
|
|
735
|
-
messageCountWindow: now,
|
|
736
|
-
sendQueue: [],
|
|
737
|
-
bytesSent: 0,
|
|
738
|
-
bandwidthWindow: now,
|
|
739
|
-
isBackpressured: false,
|
|
740
|
-
lastMessageReceivedAt: now,
|
|
741
|
-
};
|
|
742
|
-
this.peers.set(peerId, peerState);
|
|
743
|
-
// Create per-peer snapshot registry
|
|
744
|
-
const snapshotRegistry = this.createPeerSnapshotRegistry();
|
|
745
|
-
this.peerSnapshotRegistries.set(peerId, snapshotRegistry);
|
|
746
|
-
// Initialize client tick tracking (for client-side prediction)
|
|
747
|
-
this.lastProcessedClientTick.set(peerId, 0);
|
|
748
|
-
// Setup message handler for this peer
|
|
749
|
-
peer.onMessage((data) => {
|
|
750
|
-
this.handlePeerMessage(peerId, data);
|
|
751
|
-
});
|
|
752
|
-
// Notify handlers
|
|
753
|
-
for (const handler of this.connectionHandlers) {
|
|
754
|
-
try {
|
|
755
|
-
handler(peerId);
|
|
756
|
-
}
|
|
757
|
-
catch (error) {
|
|
758
|
-
// Don't call log here as it might throw, use console.error directly
|
|
759
|
-
if (this.config.debug) {
|
|
760
|
-
console.error(`[ServerNetwork] Error in connection handler: ${error}`);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Handle peer disconnection
|
|
767
|
-
*/
|
|
768
|
-
handleDisconnection(peerId) {
|
|
769
|
-
this.log(`Peer disconnected: ${peerId}`);
|
|
770
|
-
this.peers.delete(peerId);
|
|
771
|
-
this.peerSnapshotRegistries.delete(peerId);
|
|
772
|
-
this.lastProcessedClientTick.delete(peerId);
|
|
773
|
-
this.lastSnapshotHashes.delete(peerId);
|
|
774
|
-
// Notify handlers
|
|
775
|
-
for (const handler of this.disconnectionHandlers) {
|
|
776
|
-
try {
|
|
777
|
-
handler(peerId);
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
// Don't call log here as it might throw, use console.error directly
|
|
781
|
-
if (this.config.debug) {
|
|
782
|
-
console.error(`[ServerNetwork] Error in disconnection handler: ${error}`);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Handle incoming message from a peer
|
|
789
|
-
*/
|
|
790
|
-
handlePeerMessage(peerId, data) {
|
|
791
|
-
// Update last message received timestamp
|
|
792
|
-
const peer = this.peers.get(peerId);
|
|
793
|
-
if (peer) {
|
|
794
|
-
peer.lastMessageReceivedAt = Date.now();
|
|
795
|
-
}
|
|
796
|
-
if (data.byteLength === 0) {
|
|
797
|
-
this.log(`Received empty message from peer ${peerId}`);
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
if (data.byteLength > this.config.maxMessageSize) {
|
|
801
|
-
this.log(`Message from peer ${peerId} exceeds max size: ${data.byteLength} > ${this.config.maxMessageSize}`);
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
const messageType = data[0];
|
|
805
|
-
const payload = data.subarray(1);
|
|
806
|
-
switch (messageType) {
|
|
807
|
-
case MessageType.INTENT:
|
|
808
|
-
this.handleIntent(peerId, payload);
|
|
809
|
-
break;
|
|
810
|
-
case MessageType.HEARTBEAT:
|
|
811
|
-
// Heartbeat received - already updated lastMessageReceivedAt above
|
|
812
|
-
this.log(`Received heartbeat from peer ${peerId}`);
|
|
813
|
-
break;
|
|
814
|
-
case MessageType.CUSTOM:
|
|
815
|
-
this.handleRPC(peerId, payload);
|
|
816
|
-
break;
|
|
817
|
-
default:
|
|
818
|
-
this.log(`Unknown message type ${messageType} from peer ${peerId}`);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* Decode and handle an intent from a peer
|
|
823
|
-
*/
|
|
824
|
-
handleIntent(peerId, data) {
|
|
825
|
-
// Rate limiting check
|
|
826
|
-
if (!this.checkRateLimit(peerId)) {
|
|
827
|
-
this.log(`Rate limit exceeded for peer ${peerId}, dropping intent`);
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
try {
|
|
831
|
-
// Decode using intent registry (extracts kind from first byte internally)
|
|
832
|
-
const intent = this.intentRegistry.decode(data);
|
|
833
|
-
this.log(`Received intent (kind: ${intent.kind}) from peer ${peerId}`);
|
|
834
|
-
// Automatically track client tick for client-side prediction
|
|
835
|
-
// All intents have a 'tick' field added by defineIntent()
|
|
836
|
-
this.lastProcessedClientTick.set(peerId, intent.tick);
|
|
837
|
-
// Call global intent handlers first (e.g., for tracking pending responses)
|
|
838
|
-
for (const handler of this.anyIntentHandlers) {
|
|
839
|
-
try {
|
|
840
|
-
handler(peerId, intent);
|
|
841
|
-
}
|
|
842
|
-
catch (error) {
|
|
843
|
-
this.log(`Error in global intent handler: ${error}`);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
const handlers = this.intentHandlers.get(intent.kind);
|
|
847
|
-
if (handlers && handlers.length > 0) {
|
|
848
|
-
// Call all registered handlers
|
|
849
|
-
for (const handler of handlers) {
|
|
850
|
-
try {
|
|
851
|
-
handler(peerId, intent);
|
|
852
|
-
}
|
|
853
|
-
catch (error) {
|
|
854
|
-
this.log(`Error in intent handler: ${error}`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
else {
|
|
859
|
-
this.log(`No handler registered for intent kind: ${intent.kind}`);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
catch (error) {
|
|
863
|
-
this.log(`Failed to decode intent from peer ${peerId}: ${error}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* Handle incoming RPC message from a peer
|
|
868
|
-
*/
|
|
869
|
-
handleRPC(peerId, data) {
|
|
870
|
-
if (!this.rpcRegistry) {
|
|
871
|
-
this.log("Received RPC but RpcRegistry not configured");
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
// Rate limiting check
|
|
875
|
-
if (!this.checkRateLimit(peerId)) {
|
|
876
|
-
this.log(`Rate limit exceeded for peer ${peerId}, dropping RPC`);
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
try {
|
|
880
|
-
// Decode using RPC registry (returns { method, data })
|
|
881
|
-
const decoded = this.rpcRegistry.decode(data);
|
|
882
|
-
this.log(`Received RPC (method: ${decoded.method}) from peer ${peerId}`);
|
|
883
|
-
// Call all method-specific handlers if registered
|
|
884
|
-
const handlers = this.rpcHandlers.get(decoded.method);
|
|
885
|
-
if (handlers && handlers.length > 0) {
|
|
886
|
-
for (const handler of handlers) {
|
|
887
|
-
try {
|
|
888
|
-
handler(peerId, decoded.data);
|
|
889
|
-
}
|
|
890
|
-
catch (error) {
|
|
891
|
-
this.log(`Error in RPC handler: ${error}`);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
else {
|
|
896
|
-
this.log(`No handler registered for RPC method: ${decoded.method}`);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
catch (error) {
|
|
900
|
-
this.log(`Failed to decode RPC from peer ${peerId}: ${error}`);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* Check rate limit for a peer
|
|
905
|
-
* Returns true if message should be processed, false if rate limit exceeded
|
|
906
|
-
*/
|
|
907
|
-
checkRateLimit(peerId) {
|
|
908
|
-
if (this.config.maxMessagesPerSecond === 0) {
|
|
909
|
-
return true; // Rate limiting disabled
|
|
910
|
-
}
|
|
911
|
-
const peer = this.peers.get(peerId);
|
|
912
|
-
if (!peer) {
|
|
913
|
-
return false;
|
|
914
|
-
}
|
|
915
|
-
const now = Date.now();
|
|
916
|
-
const windowStart = Math.floor(now / 1000) * 1000; // Start of current second
|
|
917
|
-
// Reset counter if we're in a new time window
|
|
918
|
-
if (peer.messageCountWindow !== windowStart) {
|
|
919
|
-
peer.messageCountWindow = windowStart;
|
|
920
|
-
peer.messageCount = 0;
|
|
921
|
-
}
|
|
922
|
-
// Check if limit would be exceeded BEFORE incrementing
|
|
923
|
-
if (peer.messageCount >= this.config.maxMessagesPerSecond) {
|
|
924
|
-
return false;
|
|
925
|
-
}
|
|
926
|
-
// Only increment if we're allowing this message
|
|
927
|
-
peer.messageCount++;
|
|
928
|
-
return true;
|
|
929
|
-
}
|
|
930
|
-
/**
|
|
931
|
-
* Debug logging
|
|
932
|
-
*/
|
|
933
|
-
log(message) {
|
|
934
|
-
if (this.config.debug) {
|
|
935
|
-
console.log(`[ServerNetwork] ${message}`);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|