murow 0.0.70 → 0.0.71

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