murow 0.0.42 → 0.0.53

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 (64) hide show
  1. package/dist/core/fixed-ticker/fixed-ticker.d.ts +9 -1
  2. package/dist/core/fixed-ticker/fixed-ticker.js +10 -0
  3. package/dist/core/navmesh/navmesh-worker-pool.d.ts +53 -0
  4. package/dist/core/navmesh/navmesh-worker-pool.js +180 -0
  5. package/dist/core/navmesh/navmesh.d.ts +114 -3
  6. package/dist/core/navmesh/navmesh.js +136 -3
  7. package/dist/core/navmesh/navmesh.worker.d.ts +11 -0
  8. package/dist/core/navmesh/navmesh.worker.js +79 -0
  9. package/dist/core/prediction/prediction.d.ts +1 -0
  10. package/dist/core/prediction/prediction.js +3 -0
  11. package/dist/core.esm.js +1 -1
  12. package/dist/core.js +1 -1
  13. package/dist/ecs/component-store.d.ts +20 -35
  14. package/dist/ecs/component-store.js +84 -81
  15. package/dist/ecs/entity-handle.d.ts +209 -0
  16. package/dist/ecs/entity-handle.js +232 -0
  17. package/dist/ecs/index.d.ts +1 -0
  18. package/dist/ecs/index.js +1 -0
  19. package/dist/ecs/world.d.ts +73 -1
  20. package/dist/ecs/world.js +203 -64
  21. package/dist/net/adapters/browser-websocket.d.ts +2 -0
  22. package/dist/net/adapters/browser-websocket.js +8 -0
  23. package/dist/net/adapters/bun-websocket.d.ts +2 -0
  24. package/dist/net/adapters/bun-websocket.js +13 -0
  25. package/dist/net/client.d.ts +14 -8
  26. package/dist/net/client.js +50 -13
  27. package/dist/net/server.d.ts +10 -10
  28. package/dist/net/server.js +11 -10
  29. package/dist/net/types.d.ts +27 -0
  30. package/dist/protocol/rpc/define-rpc.d.ts +4 -4
  31. package/dist/protocol/rpc/define-rpc.js +3 -3
  32. package/dist/protocol/rpc/rpc-registry.d.ts +5 -5
  33. package/dist/protocol/rpc/rpc-registry.js +2 -2
  34. package/dist/protocol/rpc/rpc.d.ts +3 -3
  35. package/package.json +1 -1
  36. package/src/core/fixed-ticker/README.md +4 -4
  37. package/src/core/fixed-ticker/fixed-ticker.ts +12 -1
  38. package/src/core/navmesh/README.md +40 -0
  39. package/src/core/navmesh/navmesh-worker-pool.ts +236 -0
  40. package/src/core/navmesh/navmesh-workers.test.ts +356 -0
  41. package/src/core/navmesh/navmesh.ts +206 -9
  42. package/src/core/navmesh/navmesh.worker.ts +147 -0
  43. package/src/core/pooled-codec/pooled-codec.test.ts +176 -0
  44. package/src/core/prediction/prediction.ts +4 -0
  45. package/src/ecs/README.md +427 -354
  46. package/src/ecs/benchmark.test.ts +824 -15
  47. package/src/ecs/component-store.ts +87 -113
  48. package/src/ecs/entity-handle.test.ts +393 -0
  49. package/src/ecs/entity-handle.ts +245 -0
  50. package/src/ecs/index.ts +1 -0
  51. package/src/ecs/world.test.ts +7 -6
  52. package/src/ecs/world.ts +242 -62
  53. package/src/net/README.md +7 -3
  54. package/src/net/adapters/browser-websocket.ts +9 -1
  55. package/src/net/adapters/bun-websocket.ts +15 -0
  56. package/src/net/client.ts +60 -17
  57. package/src/net/server.ts +15 -14
  58. package/src/net/types.ts +27 -0
  59. package/src/protocol/README.md +3 -3
  60. package/src/protocol/rpc/define-rpc.test.ts +8 -8
  61. package/src/protocol/rpc/define-rpc.ts +5 -5
  62. package/src/protocol/rpc/rpc-registry.test.ts +3 -3
  63. package/src/protocol/rpc/rpc-registry.ts +5 -5
  64. package/src/protocol/rpc/rpc.ts +3 -3
@@ -25,7 +25,7 @@ export declare class FixedTicker {
25
25
  * @description
26
26
  * Interval in milliseconds per tick
27
27
  */
28
- private intervalMs;
28
+ intervalMs: number;
29
29
  /**
30
30
  * @description
31
31
  * Maximum amount of ticks to run per frame, to avoid
@@ -83,6 +83,14 @@ export declare class FixedTicker {
83
83
  * @returns {number} Accumulated time in seconds
84
84
  */
85
85
  get accumulatedTime(): number;
86
+ /**
87
+ * @description
88
+ * Returns the interpolation factor between 0 and 1 for smooth rendering between ticks.
89
+ * Clamped to prevent extrapolation when ticks are skipped.
90
+ *
91
+ * @returns {number} Alpha value between 0 and 1
92
+ */
93
+ get alpha(): number;
86
94
  }
87
95
  interface FixedTickerProps {
88
96
  /**
@@ -88,4 +88,14 @@ export class FixedTicker {
88
88
  get accumulatedTime() {
89
89
  return this.accumulator / 1000; // Convert to seconds
90
90
  }
91
+ /**
92
+ * @description
93
+ * Returns the interpolation factor between 0 and 1 for smooth rendering between ticks.
94
+ * Clamped to prevent extrapolation when ticks are skipped.
95
+ *
96
+ * @returns {number} Alpha value between 0 and 1
97
+ */
98
+ get alpha() {
99
+ return Math.min(this.accumulatedTime / (1 / this.rate), 1.0);
100
+ }
91
101
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * NavMesh Worker Pool - Manages multiple workers for parallel pathfinding
3
+ */
4
+ import { ObstacleInput } from './navmesh';
5
+ interface Vec2 {
6
+ x: number;
7
+ y: number;
8
+ }
9
+ export declare class NavMeshWorkerPool {
10
+ private poolSize;
11
+ private workerPath;
12
+ private navType;
13
+ private obstacles;
14
+ private workers;
15
+ private nextWorkerIndex;
16
+ private requestId;
17
+ private pendingRequests;
18
+ private initialized;
19
+ constructor(poolSize: number, workerPath: string, navType?: 'grid' | 'graph', obstacles?: ObstacleInput[]);
20
+ /**
21
+ * Initialize all workers in the pool
22
+ */
23
+ init(): Promise<void>;
24
+ /**
25
+ * Find a path using the next available worker (round-robin)
26
+ */
27
+ findPath(from: Vec2, to: Vec2): Promise<Vec2[]>;
28
+ /**
29
+ * Add an obstacle to all workers
30
+ */
31
+ addObstacle(obstacle: ObstacleInput): Promise<number>;
32
+ /**
33
+ * Remove an obstacle from all workers
34
+ */
35
+ removeObstacle(obstacleId: number): Promise<void>;
36
+ /**
37
+ * Move an obstacle in all workers
38
+ */
39
+ moveObstacle(obstacleId: number, pos: Vec2): Promise<void>;
40
+ /**
41
+ * Terminate all workers
42
+ */
43
+ terminate(): void;
44
+ /**
45
+ * Get the number of pending requests
46
+ */
47
+ get pendingCount(): number;
48
+ /**
49
+ * Get the pool size
50
+ */
51
+ get size(): number;
52
+ }
53
+ export {};
@@ -0,0 +1,180 @@
1
+ /**
2
+ * NavMesh Worker Pool - Manages multiple workers for parallel pathfinding
3
+ */
4
+ export class NavMeshWorkerPool {
5
+ constructor(poolSize, workerPath, navType = 'grid', obstacles = []) {
6
+ this.poolSize = poolSize;
7
+ this.workerPath = workerPath;
8
+ this.navType = navType;
9
+ this.obstacles = obstacles;
10
+ this.workers = [];
11
+ this.nextWorkerIndex = 0;
12
+ this.requestId = 0;
13
+ this.pendingRequests = new Map();
14
+ this.initialized = false;
15
+ }
16
+ /**
17
+ * Initialize all workers in the pool
18
+ */
19
+ async init() {
20
+ if (this.initialized)
21
+ return;
22
+ const initPromises = [];
23
+ for (let i = 0; i < this.poolSize; i++) {
24
+ const worker = new Worker(this.workerPath, { type: 'module' });
25
+ // Set up message handler
26
+ worker.onmessage = (e) => {
27
+ const msg = e.data;
28
+ if (msg.type === 'PATH_RESULT') {
29
+ const request = this.pendingRequests.get(msg.id);
30
+ if (request) {
31
+ request.resolve(msg.path);
32
+ this.pendingRequests.delete(msg.id);
33
+ }
34
+ }
35
+ else if (msg.type === 'ERROR') {
36
+ // Reject all pending requests for this worker
37
+ for (const [id, request] of this.pendingRequests.entries()) {
38
+ request.reject(new Error(msg.error));
39
+ this.pendingRequests.delete(id);
40
+ }
41
+ }
42
+ };
43
+ worker.onerror = (error) => {
44
+ console.error('Worker error:', error);
45
+ // Reject all pending requests for this worker
46
+ for (const [id, request] of this.pendingRequests.entries()) {
47
+ request.reject(new Error('Worker error'));
48
+ this.pendingRequests.delete(id);
49
+ }
50
+ };
51
+ this.workers.push(worker);
52
+ // Initialize worker
53
+ const readyPromise = new Promise((resolve) => {
54
+ const onReady = (e) => {
55
+ if (e.data.type === 'READY') {
56
+ worker.removeEventListener('message', onReady);
57
+ resolve();
58
+ }
59
+ };
60
+ worker.addEventListener('message', onReady);
61
+ });
62
+ worker.postMessage({
63
+ type: 'INIT',
64
+ navType: this.navType,
65
+ obstacles: this.obstacles,
66
+ });
67
+ initPromises.push(readyPromise);
68
+ }
69
+ await Promise.all(initPromises);
70
+ this.initialized = true;
71
+ }
72
+ /**
73
+ * Find a path using the next available worker (round-robin)
74
+ */
75
+ async findPath(from, to) {
76
+ if (!this.initialized) {
77
+ throw new Error('Worker pool not initialized. Call init() first.');
78
+ }
79
+ const id = this.requestId++;
80
+ const worker = this.workers[this.nextWorkerIndex];
81
+ this.nextWorkerIndex = (this.nextWorkerIndex + 1) % this.workers.length;
82
+ return new Promise((resolve, reject) => {
83
+ this.pendingRequests.set(id, { id, from, to, resolve, reject });
84
+ worker.postMessage({
85
+ type: 'FIND_PATH',
86
+ id,
87
+ from,
88
+ to,
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Add an obstacle to all workers
94
+ */
95
+ async addObstacle(obstacle) {
96
+ if (!this.initialized) {
97
+ throw new Error('Worker pool not initialized. Call init() first.');
98
+ }
99
+ // Add to all workers
100
+ const promises = this.workers.map((worker) => {
101
+ return new Promise((resolve) => {
102
+ const onAdded = (e) => {
103
+ if (e.data.type === 'OBSTACLE_ADDED') {
104
+ worker.removeEventListener('message', onAdded);
105
+ resolve(e.data.obstacleId);
106
+ }
107
+ };
108
+ worker.addEventListener('message', onAdded);
109
+ worker.postMessage({ type: 'ADD_OBSTACLE', obstacle });
110
+ });
111
+ });
112
+ const ids = await Promise.all(promises);
113
+ return ids[0]; // All workers should return the same ID
114
+ }
115
+ /**
116
+ * Remove an obstacle from all workers
117
+ */
118
+ async removeObstacle(obstacleId) {
119
+ if (!this.initialized) {
120
+ throw new Error('Worker pool not initialized. Call init() first.');
121
+ }
122
+ const promises = this.workers.map((worker) => {
123
+ return new Promise((resolve) => {
124
+ const onRemoved = (e) => {
125
+ if (e.data.type === 'OBSTACLE_REMOVED') {
126
+ worker.removeEventListener('message', onRemoved);
127
+ resolve();
128
+ }
129
+ };
130
+ worker.addEventListener('message', onRemoved);
131
+ worker.postMessage({ type: 'REMOVE_OBSTACLE', obstacleId });
132
+ });
133
+ });
134
+ await Promise.all(promises);
135
+ }
136
+ /**
137
+ * Move an obstacle in all workers
138
+ */
139
+ async moveObstacle(obstacleId, pos) {
140
+ if (!this.initialized) {
141
+ throw new Error('Worker pool not initialized. Call init() first.');
142
+ }
143
+ const promises = this.workers.map((worker) => {
144
+ return new Promise((resolve) => {
145
+ const onMoved = (e) => {
146
+ if (e.data.type === 'OBSTACLE_MOVED') {
147
+ worker.removeEventListener('message', onMoved);
148
+ resolve();
149
+ }
150
+ };
151
+ worker.addEventListener('message', onMoved);
152
+ worker.postMessage({ type: 'MOVE_OBSTACLE', obstacleId, pos });
153
+ });
154
+ });
155
+ await Promise.all(promises);
156
+ }
157
+ /**
158
+ * Terminate all workers
159
+ */
160
+ terminate() {
161
+ for (const worker of this.workers) {
162
+ worker.terminate();
163
+ }
164
+ this.workers = [];
165
+ this.pendingRequests.clear();
166
+ this.initialized = false;
167
+ }
168
+ /**
169
+ * Get the number of pending requests
170
+ */
171
+ get pendingCount() {
172
+ return this.pendingRequests.size;
173
+ }
174
+ /**
175
+ * Get the pool size
176
+ */
177
+ get size() {
178
+ return this.workers.length;
179
+ }
180
+ }
@@ -1,5 +1,41 @@
1
1
  type ObstacleId = number;
2
2
  export type Obstacle = CircleObstacle | RectObstacle | PolygonObstacle;
3
+ /**
4
+ * NavMesh configuration options
5
+ */
6
+ export interface NavMeshOptions<TWorkers extends boolean | 'auto' = false> {
7
+ /**
8
+ * Enable Web Workers for pathfinding
9
+ *
10
+ * - `false` (default): Synchronous pathfinding on main thread
11
+ * - `true`: Always use worker pool (4 workers)
12
+ * - `'auto'`: Automatically use workers when beneficial (>= 20 pending paths)
13
+ *
14
+ * @default false
15
+ *
16
+ * @remarks
17
+ * Workers provide 3-4.5x speedup for parallel pathfinding (20+ concurrent requests).
18
+ * For single/sequential pathfinding, sync is faster due to message passing overhead (~0.5ms).
19
+ *
20
+ * Use 'auto' for games where pathfinding load varies (e.g., RTS with unit groups).
21
+ */
22
+ workers?: TWorkers;
23
+ /**
24
+ * Number of workers to spawn (only used when workers = true)
25
+ * @default 4
26
+ */
27
+ workerPoolSize?: number;
28
+ /**
29
+ * Path to worker script (required if workers = true and running in browser)
30
+ * For Node.js/Bun, this is handled automatically
31
+ * @example './navmesh.worker.js'
32
+ */
33
+ workerPath?: string;
34
+ }
35
+ /**
36
+ * Helper type to determine return type based on worker configuration
37
+ */
38
+ type PathResult<TWorkers extends boolean | 'auto' | undefined> = TWorkers extends false | undefined ? Vec2[] : TWorkers extends true ? Promise<Vec2[]> : Vec2[] | Promise<Vec2[]>;
3
39
  export type ObstacleInput = Omit<CircleObstacle, 'id'> | Omit<RectObstacle, 'id'> | Omit<PolygonObstacle, 'id'>;
4
40
  interface Vec2 {
5
41
  x: number;
@@ -62,25 +98,54 @@ type NavType = 'grid' | 'graph';
62
98
  * - Version tracking: zero unnecessary rebuilds
63
99
  * - Binary heap A*: handles 10k+ node searches
64
100
  * - Simple full rebuild: correct and fast enough
101
+ * - Optional Web Workers: 3-4.5x speedup for parallel pathfinding
65
102
  *
66
103
  * Performance characteristics:
67
104
  * - Obstacle query: O(1) average via spatial hash
68
105
  * - Grid rebuild: O(n * area), < 1ms for typical games
69
106
  * - Pathfinding: O(b^d * log n) with binary heap
107
+ * - Worker overhead: ~0.5ms per request (use for 20+ concurrent paths)
70
108
  *
71
109
  * Production ready for:
72
110
  * - Grid-based games
73
111
  * - RTS with < 1000 dynamic obstacles
74
112
  * - Turn-based games
75
113
  * - Moderate map sizes (< 1M cells)
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // Simple usage (synchronous) - typed as Vec2[]
118
+ * const navmesh = new NavMesh('grid');
119
+ * const path = navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
120
+ *
121
+ * // With workers (automatic) - typed as Vec2[] | Promise<Vec2[]>
122
+ * const navmesh = new NavMesh('grid', { workers: 'auto' });
123
+ * const path = await navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
124
+ *
125
+ * // With workers (always) - typed as Promise<Vec2[]>
126
+ * const navmesh = new NavMesh('grid', { workers: true });
127
+ * const path = await navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
128
+ * ```
76
129
  */
77
- export declare class NavMesh {
130
+ export declare class NavMesh<TWorkers extends boolean | 'auto' = false> {
78
131
  private type;
79
132
  private grid?;
80
133
  private graph?;
81
134
  private lastVersion;
82
135
  obstacles: Obstacles;
83
- constructor(type: NavType);
136
+ private options;
137
+ private workerPool?;
138
+ private pendingPaths;
139
+ private readonly AUTO_WORKER_THRESHOLD;
140
+ constructor(type: NavType, options?: NavMeshOptions<TWorkers>);
141
+ /**
142
+ * Lazy initialize worker pool
143
+ */
144
+ private initWorkerPool;
145
+ /**
146
+ * Check if we should use workers for this request
147
+ */
148
+ private shouldUseWorkers;
84
149
  /**
85
150
  * Adds an obstacle and returns its unique ID.
86
151
  * For polygons: ensure points are defined relative to (0,0).
@@ -102,15 +167,61 @@ export declare class NavMesh {
102
167
  * Finds a path from start to goal.
103
168
  * Automatically rebuilds navigation data if obstacles changed.
104
169
  * Returns empty array if no path exists.
170
+ *
171
+ * @remarks
172
+ * - If workers are disabled (false): Returns Vec2[] synchronously
173
+ * - If workers are enabled (true): Returns Promise<Vec2[]>
174
+ * - If workers are 'auto': Returns Vec2[] | Promise<Vec2[]> based on load
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * // Synchronous (no workers) - typed as Vec2[]
179
+ * const navmesh = new NavMesh('grid');
180
+ * const path = navmesh.findPath({ from, to });
181
+ *
182
+ * // Asynchronous (with workers) - typed as Promise<Vec2[]>
183
+ * const navmesh = new NavMesh('grid', { workers: true });
184
+ * const path = await navmesh.findPath({ from, to });
185
+ *
186
+ * // Auto mode - typed as Vec2[] | Promise<Vec2[]>
187
+ * const navmesh = new NavMesh('grid', { workers: 'auto' });
188
+ * const result = navmesh.findPath({ from, to });
189
+ * const path = result instanceof Promise ? await result : result;
190
+ * ```
105
191
  */
106
192
  findPath({ from, to }: {
107
193
  from: Vec2;
108
194
  to: Vec2;
109
- }): Vec2[];
195
+ }): PathResult<TWorkers>;
196
+ /**
197
+ * Async pathfinding using worker pool
198
+ */
199
+ private findPathAsync;
110
200
  /**
111
201
  * Smart rebuild - only rebuilds if obstacles changed.
112
202
  * Version checking eliminates unnecessary work.
113
203
  */
114
204
  rebuild(): void;
205
+ /**
206
+ * Cleanup resources (terminate worker pool if active)
207
+ * Call this when you're done with the NavMesh instance.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * const navmesh = new NavMesh('grid', { workers: true });
212
+ * // ... use navmesh ...
213
+ * navmesh.dispose(); // Cleanup workers
214
+ * ```
215
+ */
216
+ dispose(): void;
217
+ /**
218
+ * Get current worker status for debugging/monitoring
219
+ */
220
+ getWorkerStatus(): {
221
+ workersEnabled: TWorkers;
222
+ workerPoolActive: boolean;
223
+ pendingPaths: number;
224
+ usingWorkersNow: boolean;
225
+ };
115
226
  }
116
227
  export {};
@@ -524,27 +524,85 @@ class GraphNav {
524
524
  * - Version tracking: zero unnecessary rebuilds
525
525
  * - Binary heap A*: handles 10k+ node searches
526
526
  * - Simple full rebuild: correct and fast enough
527
+ * - Optional Web Workers: 3-4.5x speedup for parallel pathfinding
527
528
  *
528
529
  * Performance characteristics:
529
530
  * - Obstacle query: O(1) average via spatial hash
530
531
  * - Grid rebuild: O(n * area), < 1ms for typical games
531
532
  * - Pathfinding: O(b^d * log n) with binary heap
533
+ * - Worker overhead: ~0.5ms per request (use for 20+ concurrent paths)
532
534
  *
533
535
  * Production ready for:
534
536
  * - Grid-based games
535
537
  * - RTS with < 1000 dynamic obstacles
536
538
  * - Turn-based games
537
539
  * - Moderate map sizes (< 1M cells)
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * // Simple usage (synchronous) - typed as Vec2[]
544
+ * const navmesh = new NavMesh('grid');
545
+ * const path = navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
546
+ *
547
+ * // With workers (automatic) - typed as Vec2[] | Promise<Vec2[]>
548
+ * const navmesh = new NavMesh('grid', { workers: 'auto' });
549
+ * const path = await navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
550
+ *
551
+ * // With workers (always) - typed as Promise<Vec2[]>
552
+ * const navmesh = new NavMesh('grid', { workers: true });
553
+ * const path = await navmesh.findPath({ from: {x:0, y:0}, to: {x:10, y:10} });
554
+ * ```
538
555
  */
539
556
  export class NavMesh {
540
- constructor(type) {
557
+ constructor(type, options) {
541
558
  this.type = type;
542
559
  this.lastVersion = -1;
560
+ this.pendingPaths = 0;
561
+ this.AUTO_WORKER_THRESHOLD = 20; // Use workers when >= 20 pending paths
543
562
  this.obstacles = new Obstacles();
563
+ // Set defaults - cast to any to avoid complex type gymnastics
564
+ this.options = {
565
+ workers: (options?.workers ?? false),
566
+ workerPoolSize: options?.workerPoolSize ?? 4,
567
+ workerPath: options?.workerPath ?? './navmesh.worker.js',
568
+ };
569
+ // Initialize sync navigation
544
570
  if (type === 'grid')
545
571
  this.grid = new GridNav(this.obstacles);
546
572
  if (type === 'graph')
547
573
  this.graph = new GraphNav(this.obstacles);
574
+ // Initialize worker pool if workers = true
575
+ if (this.options.workers === true) {
576
+ this.initWorkerPool();
577
+ }
578
+ }
579
+ /**
580
+ * Lazy initialize worker pool
581
+ */
582
+ async initWorkerPool() {
583
+ if (this.workerPool)
584
+ return;
585
+ try {
586
+ // Dynamic import to avoid bundling worker pool if not needed
587
+ const { NavMeshWorkerPool } = await import('./navmesh-worker-pool');
588
+ this.workerPool = new NavMeshWorkerPool(this.options.workerPoolSize, this.options.workerPath, this.type, this.obstacles.values);
589
+ await this.workerPool.init();
590
+ }
591
+ catch (error) {
592
+ console.warn('Failed to initialize worker pool, falling back to sync:', error);
593
+ this.options.workers = false; // Disable workers on failure
594
+ }
595
+ }
596
+ /**
597
+ * Check if we should use workers for this request
598
+ */
599
+ shouldUseWorkers() {
600
+ if (this.options.workers === false)
601
+ return false;
602
+ if (this.options.workers === true)
603
+ return true;
604
+ // Auto mode: use workers if we have many pending paths
605
+ return this.pendingPaths >= this.AUTO_WORKER_THRESHOLD;
548
606
  }
549
607
  /**
550
608
  * Adds an obstacle and returns its unique ID.
@@ -575,12 +633,59 @@ export class NavMesh {
575
633
  * Finds a path from start to goal.
576
634
  * Automatically rebuilds navigation data if obstacles changed.
577
635
  * Returns empty array if no path exists.
636
+ *
637
+ * @remarks
638
+ * - If workers are disabled (false): Returns Vec2[] synchronously
639
+ * - If workers are enabled (true): Returns Promise<Vec2[]>
640
+ * - If workers are 'auto': Returns Vec2[] | Promise<Vec2[]> based on load
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * // Synchronous (no workers) - typed as Vec2[]
645
+ * const navmesh = new NavMesh('grid');
646
+ * const path = navmesh.findPath({ from, to });
647
+ *
648
+ * // Asynchronous (with workers) - typed as Promise<Vec2[]>
649
+ * const navmesh = new NavMesh('grid', { workers: true });
650
+ * const path = await navmesh.findPath({ from, to });
651
+ *
652
+ * // Auto mode - typed as Vec2[] | Promise<Vec2[]>
653
+ * const navmesh = new NavMesh('grid', { workers: 'auto' });
654
+ * const result = navmesh.findPath({ from, to });
655
+ * const path = result instanceof Promise ? await result : result;
656
+ * ```
578
657
  */
579
658
  findPath({ from, to }) {
659
+ // Check if we should use workers
660
+ if (this.shouldUseWorkers() && this.workerPool) {
661
+ // Async path (with workers)
662
+ this.pendingPaths++;
663
+ return this.findPathAsync(from, to).finally(() => {
664
+ this.pendingPaths--;
665
+ });
666
+ }
667
+ // Sync path (no workers)
580
668
  this.rebuild();
581
- return this.type === 'grid'
669
+ return (this.type === 'grid'
582
670
  ? this.grid.findPath(from, to)
583
- : this.graph.findPath(from, to);
671
+ : this.graph.findPath(from, to));
672
+ }
673
+ /**
674
+ * Async pathfinding using worker pool
675
+ */
676
+ async findPathAsync(from, to) {
677
+ // Lazy init for 'auto' mode
678
+ if (this.options.workers === 'auto' && !this.workerPool) {
679
+ await this.initWorkerPool();
680
+ }
681
+ if (!this.workerPool) {
682
+ // Fallback to sync if workers failed to init
683
+ this.rebuild();
684
+ return this.type === 'grid'
685
+ ? this.grid.findPath(from, to)
686
+ : this.graph.findPath(from, to);
687
+ }
688
+ return this.workerPool.findPath(from, to);
584
689
  }
585
690
  /**
586
691
  * Smart rebuild - only rebuilds if obstacles changed.
@@ -593,6 +698,34 @@ export class NavMesh {
593
698
  this.graph?.rebuild();
594
699
  this.lastVersion = this.obstacles.version;
595
700
  }
701
+ /**
702
+ * Cleanup resources (terminate worker pool if active)
703
+ * Call this when you're done with the NavMesh instance.
704
+ *
705
+ * @example
706
+ * ```ts
707
+ * const navmesh = new NavMesh('grid', { workers: true });
708
+ * // ... use navmesh ...
709
+ * navmesh.dispose(); // Cleanup workers
710
+ * ```
711
+ */
712
+ dispose() {
713
+ if (this.workerPool) {
714
+ this.workerPool.terminate();
715
+ this.workerPool = undefined;
716
+ }
717
+ }
718
+ /**
719
+ * Get current worker status for debugging/monitoring
720
+ */
721
+ getWorkerStatus() {
722
+ return {
723
+ workersEnabled: this.options.workers,
724
+ workerPoolActive: !!this.workerPool,
725
+ pendingPaths: this.pendingPaths,
726
+ usingWorkersNow: this.shouldUseWorkers(),
727
+ };
728
+ }
596
729
  }
597
730
  /* ---------------------------------- */
598
731
  /* A* */
@@ -0,0 +1,11 @@
1
+ /**
2
+ * NavMesh Worker - Offloads pathfinding to background thread
3
+ *
4
+ * Message Protocol:
5
+ * - INIT: Initialize NavMesh with obstacles
6
+ * - FIND_PATH: Request pathfinding (from, to)
7
+ * - ADD_OBSTACLE: Add obstacle dynamically
8
+ * - REMOVE_OBSTACLE: Remove obstacle dynamically
9
+ * - MOVE_OBSTACLE: Move obstacle
10
+ */
11
+ export {};