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