@open-core/framework 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/adapters/contracts/IEngineEvents.d.ts +1 -1
- package/dist/adapters/contracts/IPlayerServer.d.ts +10 -0
- package/dist/adapters/fivem/fivem-engine-events.d.ts +1 -1
- package/dist/adapters/fivem/fivem-engine-events.js +4 -1
- package/dist/adapters/fivem/fivem-player-server.d.ts +1 -0
- package/dist/adapters/fivem/fivem-player-server.js +3 -0
- package/dist/adapters/node/node-engine-events.d.ts +1 -1
- package/dist/adapters/node/node-engine-events.js +4 -1
- package/dist/adapters/node/node-player-server.d.ts +1 -0
- package/dist/adapters/node/node-player-server.js +3 -0
- package/dist/runtime/client/system/processors/nui.processor.js +4 -3
- package/dist/runtime/server/bootstrap.js +99 -2
- package/dist/runtime/server/decorators/command.d.ts +1 -0
- package/dist/runtime/server/decorators/guard.d.ts +8 -6
- package/dist/runtime/server/decorators/guard.js +8 -6
- package/dist/runtime/server/decorators/onNet.d.ts +3 -3
- package/dist/runtime/server/runtime.d.ts +34 -1
- package/dist/runtime/server/runtime.js +1 -0
- package/dist/runtime/server/services/core/session-recovery.service.d.ts +39 -0
- package/dist/runtime/server/services/core/session-recovery.service.js +108 -0
- package/dist/runtime/server/services/index.d.ts +1 -0
- package/dist/runtime/server/services/index.js +3 -1
- package/dist/runtime/server/services/parallel/index.d.ts +1 -45
- package/dist/runtime/server/services/parallel/index.js +0 -44
- package/dist/runtime/server/services/parallel/native-worker.entry.d.ts +1 -0
- package/dist/runtime/server/services/parallel/native-worker.entry.js +12 -0
- package/dist/runtime/server/services/parallel/parallel-compute.service.d.ts +17 -17
- package/dist/runtime/server/services/parallel/parallel-compute.service.js +10 -3
- package/dist/runtime/server/services/parallel/types.d.ts +5 -2
- package/dist/runtime/server/services/parallel/worker-pool.d.ts +5 -9
- package/dist/runtime/server/services/parallel/worker-pool.js +167 -57
- package/dist/runtime/server/services/parallel/worker.d.ts +8 -5
- package/dist/runtime/server/services/parallel/worker.js +8 -5
- package/dist/runtime/server/services/services.register.js +2 -0
- package/dist/runtime/server/types/internal-events.d.ts +6 -0
- package/package.json +2 -2
|
@@ -1,47 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
* ParallelCompute Module
|
|
3
|
-
*
|
|
4
|
-
* Provides parallel computation capabilities using worker threads.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import {
|
|
9
|
-
* initParallelCompute,
|
|
10
|
-
* defineTask,
|
|
11
|
-
* filterByDistance,
|
|
12
|
-
* } from '@open-core/framework/server'
|
|
13
|
-
*
|
|
14
|
-
* // Initialize the worker pool (call once at startup)
|
|
15
|
-
* initParallelCompute({ maxWorkers: 4 })
|
|
16
|
-
*
|
|
17
|
-
* // Use built-in tasks
|
|
18
|
-
* const nearby = await filterByDistance.run({
|
|
19
|
-
* entities: allEntities,
|
|
20
|
-
* position: player.coords,
|
|
21
|
-
* radius: 100,
|
|
22
|
-
* })
|
|
23
|
-
*
|
|
24
|
-
* // Define custom tasks
|
|
25
|
-
* const processPlayers = defineTask({
|
|
26
|
-
* name: 'processPlayers',
|
|
27
|
-
* estimateCost: (input) => input.length * 10,
|
|
28
|
-
* workerThreshold: 500,
|
|
29
|
-
* compute: (players) => players.map(p => ({
|
|
30
|
-
* ...p,
|
|
31
|
-
* processed: true,
|
|
32
|
-
* })),
|
|
33
|
-
* })
|
|
34
|
-
*
|
|
35
|
-
* // Run with automatic mode selection
|
|
36
|
-
* const result = await processPlayers.run(players)
|
|
37
|
-
*
|
|
38
|
-
* // Force sync execution
|
|
39
|
-
* const resultSync = processPlayers.sync(players)
|
|
40
|
-
*
|
|
41
|
-
* // Force parallel execution
|
|
42
|
-
* const resultParallel = await processPlayers.parallel(players)
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
export { defineBatchFilter, defineBatchReduce, defineBatchTransform, defineTask, filterByDistance, findClosest, getParallelComputeService, initParallelCompute, ParallelComputeService, shutdownParallelCompute, sortByDistance, type Vector3Like, } from './parallel-compute.service';
|
|
1
|
+
export { defineBatchFilter, defineBatchReduce, defineBatchTransform, defineTask, filterByDistance, findClosest, getParallelComputeService, initParallelCompute, ParallelComputeService, shutdownParallelCompute, sortByDistance, } from './parallel-compute.service';
|
|
46
2
|
export type { ExecutionMode, ParallelComputeMetrics, ParallelTask, ParallelTaskOptions, TaskResult, WorkerInfo, WorkerPoolConfig, WorkerStatus, } from './types';
|
|
47
3
|
export { WorkerPool } from './worker-pool';
|
|
@@ -1,48 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* ParallelCompute Module
|
|
4
|
-
*
|
|
5
|
-
* Provides parallel computation capabilities using worker threads.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import {
|
|
10
|
-
* initParallelCompute,
|
|
11
|
-
* defineTask,
|
|
12
|
-
* filterByDistance,
|
|
13
|
-
* } from '@open-core/framework/server'
|
|
14
|
-
*
|
|
15
|
-
* // Initialize the worker pool (call once at startup)
|
|
16
|
-
* initParallelCompute({ maxWorkers: 4 })
|
|
17
|
-
*
|
|
18
|
-
* // Use built-in tasks
|
|
19
|
-
* const nearby = await filterByDistance.run({
|
|
20
|
-
* entities: allEntities,
|
|
21
|
-
* position: player.coords,
|
|
22
|
-
* radius: 100,
|
|
23
|
-
* })
|
|
24
|
-
*
|
|
25
|
-
* // Define custom tasks
|
|
26
|
-
* const processPlayers = defineTask({
|
|
27
|
-
* name: 'processPlayers',
|
|
28
|
-
* estimateCost: (input) => input.length * 10,
|
|
29
|
-
* workerThreshold: 500,
|
|
30
|
-
* compute: (players) => players.map(p => ({
|
|
31
|
-
* ...p,
|
|
32
|
-
* processed: true,
|
|
33
|
-
* })),
|
|
34
|
-
* })
|
|
35
|
-
*
|
|
36
|
-
* // Run with automatic mode selection
|
|
37
|
-
* const result = await processPlayers.run(players)
|
|
38
|
-
*
|
|
39
|
-
* // Force sync execution
|
|
40
|
-
* const resultSync = processPlayers.sync(players)
|
|
41
|
-
*
|
|
42
|
-
* // Force parallel execution
|
|
43
|
-
* const resultParallel = await processPlayers.parallel(players)
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
3
|
exports.WorkerPool = exports.sortByDistance = exports.shutdownParallelCompute = exports.ParallelComputeService = exports.initParallelCompute = exports.getParallelComputeService = exports.findClosest = exports.filterByDistance = exports.defineTask = exports.defineBatchTransform = exports.defineBatchReduce = exports.defineBatchFilter = void 0;
|
|
48
4
|
// Service
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
4
|
+
const worker_1 = require("./worker");
|
|
5
|
+
const port = node_worker_threads_1.parentPort;
|
|
6
|
+
if (!port) {
|
|
7
|
+
throw new Error('native-worker.entry.ts must be executed inside a Worker thread');
|
|
8
|
+
}
|
|
9
|
+
port.on('message', (message) => {
|
|
10
|
+
const response = (0, worker_1.processMessage)(message);
|
|
11
|
+
port.postMessage(response);
|
|
12
|
+
});
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
* ParallelCompute Service
|
|
3
3
|
*
|
|
4
4
|
* Provides an ergonomic API for parallel computation.
|
|
5
|
-
* Uses
|
|
6
|
-
* that yields to the event loop for better performance.
|
|
5
|
+
* Uses native `node:worker_threads` to run CPU-bound work off the main thread.
|
|
7
6
|
*
|
|
8
|
-
* Automatically decides whether to run synchronously or
|
|
7
|
+
* Automatically decides whether to run synchronously or in a worker
|
|
9
8
|
* based on estimated computational cost.
|
|
9
|
+
*
|
|
10
|
+
* Requirements/limitations:
|
|
11
|
+
* - The compute function is serialized with `Function#toString()`.
|
|
12
|
+
* - The compute function must be pure (no closures / no external references).
|
|
13
|
+
* - Inputs/outputs must be structured-cloneable.
|
|
10
14
|
*/
|
|
15
|
+
import { Vector3 } from '../../../../kernel';
|
|
11
16
|
import { ParallelComputeMetrics, ParallelTask, ParallelTaskOptions, TaskResult, WorkerPoolConfig } from './types';
|
|
12
17
|
/**
|
|
13
18
|
* Service for managing parallel computation
|
|
@@ -91,33 +96,28 @@ export declare function shutdownParallelCompute(): Promise<void>;
|
|
|
91
96
|
* Returns a task object with run, sync, parallel, and distributed methods
|
|
92
97
|
*/
|
|
93
98
|
export declare function defineTask<TInput, TOutput>(options: ParallelTaskOptions<TInput, TOutput>): ParallelTask<TInput, TOutput>;
|
|
94
|
-
export interface Vector3Like {
|
|
95
|
-
x: number;
|
|
96
|
-
y: number;
|
|
97
|
-
z: number;
|
|
98
|
-
}
|
|
99
99
|
/**
|
|
100
100
|
* Built-in task: Filter entities by distance from a position
|
|
101
101
|
*/
|
|
102
102
|
export declare const filterByDistance: ParallelTask<{
|
|
103
|
-
entities:
|
|
104
|
-
position:
|
|
103
|
+
entities: Vector3[];
|
|
104
|
+
position: Vector3;
|
|
105
105
|
radius: number;
|
|
106
|
-
},
|
|
106
|
+
}, Vector3[]>;
|
|
107
107
|
/**
|
|
108
108
|
* Built-in task: Sort entities by distance from a position
|
|
109
109
|
*/
|
|
110
110
|
export declare const sortByDistance: ParallelTask<{
|
|
111
|
-
entities:
|
|
112
|
-
position:
|
|
113
|
-
},
|
|
111
|
+
entities: Vector3[];
|
|
112
|
+
position: Vector3;
|
|
113
|
+
}, Vector3[]>;
|
|
114
114
|
/**
|
|
115
115
|
* Built-in task: Find closest entity to a position
|
|
116
116
|
*/
|
|
117
117
|
export declare const findClosest: ParallelTask<{
|
|
118
|
-
entities:
|
|
119
|
-
position:
|
|
120
|
-
},
|
|
118
|
+
entities: Vector3[];
|
|
119
|
+
position: Vector3;
|
|
120
|
+
}, Vector3 | null>;
|
|
121
121
|
/**
|
|
122
122
|
* Built-in task: Batch transform items
|
|
123
123
|
*/
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* ParallelCompute Service
|
|
4
4
|
*
|
|
5
5
|
* Provides an ergonomic API for parallel computation.
|
|
6
|
-
* Uses
|
|
7
|
-
* that yields to the event loop for better performance.
|
|
6
|
+
* Uses native `node:worker_threads` to run CPU-bound work off the main thread.
|
|
8
7
|
*
|
|
9
|
-
* Automatically decides whether to run synchronously or
|
|
8
|
+
* Automatically decides whether to run synchronously or in a worker
|
|
10
9
|
* based on estimated computational cost.
|
|
10
|
+
*
|
|
11
|
+
* Requirements/limitations:
|
|
12
|
+
* - The compute function is serialized with `Function#toString()`.
|
|
13
|
+
* - The compute function must be pure (no closures / no external references).
|
|
14
|
+
* - Inputs/outputs must be structured-cloneable.
|
|
11
15
|
*/
|
|
12
16
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
13
17
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -308,6 +312,9 @@ function defineTask(options) {
|
|
|
308
312
|
},
|
|
309
313
|
};
|
|
310
314
|
}
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// Built-in Tasks
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
318
|
/**
|
|
312
319
|
* Built-in task: Filter entities by distance from a position
|
|
313
320
|
*/
|
|
@@ -39,8 +39,11 @@ export interface ParallelTaskOptions<TInput, TOutput> {
|
|
|
39
39
|
workerThreshold?: number;
|
|
40
40
|
/**
|
|
41
41
|
* The compute function to execute.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* Requirements/limitations:
|
|
44
|
+
* - MUST be pure (no closures / no external references).
|
|
45
|
+
* - Inputs/outputs MUST be structured-cloneable.
|
|
46
|
+
* - The function body is serialized with `Function#toString()` and evaluated in a worker.
|
|
44
47
|
*/
|
|
45
48
|
compute: (input: TInput) => TOutput;
|
|
46
49
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worker Pool
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Native worker thread pool for CPU-bound computations.
|
|
5
|
+
* Tasks are executed in `node:worker_threads` and communicated through
|
|
6
|
+
* structured-cloned messages.
|
|
7
7
|
*/
|
|
8
8
|
import { WorkerInfo, WorkerMessage, WorkerPoolConfig } from './types';
|
|
9
9
|
type EventCallback = (...args: unknown[]) => void;
|
|
@@ -23,12 +23,8 @@ export declare class WorkerPool extends SimpleEventEmitter {
|
|
|
23
23
|
private workerIdCounter;
|
|
24
24
|
private cleanupInterval;
|
|
25
25
|
private isShuttingDown;
|
|
26
|
-
private
|
|
26
|
+
private workerScriptPath;
|
|
27
27
|
constructor(config?: Partial<WorkerPoolConfig>);
|
|
28
|
-
/**
|
|
29
|
-
* Detect if native worker threads are available
|
|
30
|
-
*/
|
|
31
|
-
private detectWorkerSupport;
|
|
32
28
|
/**
|
|
33
29
|
* Check if using native workers
|
|
34
30
|
*/
|
|
@@ -56,7 +52,7 @@ export declare class WorkerPool extends SimpleEventEmitter {
|
|
|
56
52
|
*/
|
|
57
53
|
shutdown(): Promise<void>;
|
|
58
54
|
/**
|
|
59
|
-
* Spawn a new
|
|
55
|
+
* Spawn a new native worker
|
|
60
56
|
*/
|
|
61
57
|
private spawnWorker;
|
|
62
58
|
/**
|
|
@@ -2,12 +2,47 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Worker Pool
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Native worker thread pool for CPU-bound computations.
|
|
6
|
+
* Tasks are executed in `node:worker_threads` and communicated through
|
|
7
|
+
* structured-cloned messages.
|
|
8
8
|
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
9
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
43
|
exports.WorkerPool = void 0;
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
11
46
|
const DEFAULT_CONFIG = {
|
|
12
47
|
minWorkers: 0,
|
|
13
48
|
maxWorkers: 4,
|
|
@@ -46,16 +81,41 @@ class SimpleEventEmitter {
|
|
|
46
81
|
}
|
|
47
82
|
}
|
|
48
83
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
84
|
+
* Native worker backed by `node:worker_threads`.
|
|
85
|
+
*
|
|
86
|
+
* Executes one task at a time and returns `WorkerResponse` coming from the
|
|
87
|
+
* worker entrypoint.
|
|
51
88
|
*/
|
|
52
|
-
class
|
|
53
|
-
constructor(id) {
|
|
89
|
+
class NativeWorker {
|
|
90
|
+
constructor(id, workerScriptPath, callbacks) {
|
|
54
91
|
this._status = 'idle';
|
|
55
92
|
this.tasksCompleted = 0;
|
|
56
93
|
this.lastActiveAt = Date.now();
|
|
57
94
|
this.currentTask = null;
|
|
95
|
+
this.pendingResponses = new Map();
|
|
58
96
|
this.id = id;
|
|
97
|
+
this.worker = new node_worker_threads_1.Worker(workerScriptPath);
|
|
98
|
+
this.worker.on('message', (response) => {
|
|
99
|
+
const handlers = this.pendingResponses.get(response.id);
|
|
100
|
+
if (handlers) {
|
|
101
|
+
this.pendingResponses.delete(response.id);
|
|
102
|
+
handlers.resolve(response);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this.worker.on('error', (error) => {
|
|
106
|
+
this.failAllPending(error instanceof Error ? error : new Error(String(error)));
|
|
107
|
+
this._status = 'terminated';
|
|
108
|
+
this.currentTask = null;
|
|
109
|
+
callbacks.onError(this.id, error instanceof Error ? error : new Error(String(error)));
|
|
110
|
+
});
|
|
111
|
+
this.worker.on('exit', (code) => {
|
|
112
|
+
if (this._status !== 'terminated') {
|
|
113
|
+
this.failAllPending(new Error(`Worker exited with code ${code}`));
|
|
114
|
+
}
|
|
115
|
+
this._status = 'terminated';
|
|
116
|
+
this.currentTask = null;
|
|
117
|
+
callbacks.onExit(this.id, code);
|
|
118
|
+
});
|
|
59
119
|
}
|
|
60
120
|
get status() {
|
|
61
121
|
return this._status;
|
|
@@ -71,40 +131,52 @@ class VirtualWorker {
|
|
|
71
131
|
};
|
|
72
132
|
}
|
|
73
133
|
async execute(task) {
|
|
134
|
+
if (this._status === 'terminated') {
|
|
135
|
+
throw new Error('Worker is terminated');
|
|
136
|
+
}
|
|
74
137
|
this._status = 'busy';
|
|
75
138
|
this.currentTask = task;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
const executionTime = performance.now() - startTime;
|
|
95
|
-
this._status = 'idle';
|
|
96
|
-
this.currentTask = null;
|
|
97
|
-
return {
|
|
98
|
-
id: task.id,
|
|
99
|
-
success: false,
|
|
100
|
-
error: error instanceof Error ? error.message : String(error),
|
|
101
|
-
executionTime,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
this.pendingResponses.set(task.id, {
|
|
141
|
+
resolve: (response) => {
|
|
142
|
+
this.tasksCompleted++;
|
|
143
|
+
this.lastActiveAt = Date.now();
|
|
144
|
+
this._status = 'idle';
|
|
145
|
+
this.currentTask = null;
|
|
146
|
+
resolve(response);
|
|
147
|
+
},
|
|
148
|
+
reject: (error) => {
|
|
149
|
+
this._status = 'idle';
|
|
150
|
+
this.currentTask = null;
|
|
151
|
+
reject(error);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
this.worker.postMessage(task.message);
|
|
155
|
+
});
|
|
104
156
|
}
|
|
105
157
|
terminate() {
|
|
106
158
|
this._status = 'terminated';
|
|
107
159
|
this.currentTask = null;
|
|
160
|
+
void this.worker.terminate();
|
|
161
|
+
}
|
|
162
|
+
cancelTask(taskId, reason) {
|
|
163
|
+
const handlers = this.pendingResponses.get(taskId);
|
|
164
|
+
if (handlers) {
|
|
165
|
+
this.pendingResponses.delete(taskId);
|
|
166
|
+
handlers.reject(reason);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
failAllPending(error) {
|
|
170
|
+
const handlers = Array.from(this.pendingResponses.values());
|
|
171
|
+
this.pendingResponses.clear();
|
|
172
|
+
for (const h of handlers) {
|
|
173
|
+
try {
|
|
174
|
+
h.reject(error);
|
|
175
|
+
}
|
|
176
|
+
catch (_a) {
|
|
177
|
+
// ignore
|
|
178
|
+
}
|
|
179
|
+
}
|
|
108
180
|
}
|
|
109
181
|
}
|
|
110
182
|
class WorkerPool extends SimpleEventEmitter {
|
|
@@ -115,10 +187,8 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
115
187
|
this.workerIdCounter = 0;
|
|
116
188
|
this.cleanupInterval = null;
|
|
117
189
|
this.isShuttingDown = false;
|
|
118
|
-
this.useNativeWorkers = false;
|
|
119
190
|
this.config = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
|
|
120
|
-
|
|
121
|
-
this.detectWorkerSupport();
|
|
191
|
+
this.workerScriptPath = path.join(__dirname, 'native-worker.entry.js');
|
|
122
192
|
// Start cleanup interval
|
|
123
193
|
this.cleanupInterval = setInterval(() => this.cleanupIdleWorkers(), 10000);
|
|
124
194
|
// Spawn minimum workers
|
|
@@ -126,24 +196,11 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
126
196
|
this.spawnWorker();
|
|
127
197
|
}
|
|
128
198
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Detect if native worker threads are available
|
|
131
|
-
*/
|
|
132
|
-
detectWorkerSupport() {
|
|
133
|
-
try {
|
|
134
|
-
// Try to detect worker_threads module
|
|
135
|
-
// In FiveM this will fail, which is expected
|
|
136
|
-
this.useNativeWorkers = false;
|
|
137
|
-
}
|
|
138
|
-
catch (_a) {
|
|
139
|
-
this.useNativeWorkers = false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
199
|
/**
|
|
143
200
|
* Check if using native workers
|
|
144
201
|
*/
|
|
145
202
|
get isNative() {
|
|
146
|
-
return
|
|
203
|
+
return true;
|
|
147
204
|
}
|
|
148
205
|
/**
|
|
149
206
|
* Execute a task
|
|
@@ -160,6 +217,7 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
160
217
|
reject,
|
|
161
218
|
startTime: Date.now(),
|
|
162
219
|
timeoutId: null,
|
|
220
|
+
settled: false,
|
|
163
221
|
};
|
|
164
222
|
// Set timeout
|
|
165
223
|
task.timeoutId = setTimeout(() => {
|
|
@@ -201,7 +259,7 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
201
259
|
idleWorkers: idle,
|
|
202
260
|
busyWorkers: busy,
|
|
203
261
|
queuedTasks: this.taskQueue.length,
|
|
204
|
-
isNative:
|
|
262
|
+
isNative: true,
|
|
205
263
|
};
|
|
206
264
|
}
|
|
207
265
|
/**
|
|
@@ -233,12 +291,33 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
233
291
|
this.workers.clear();
|
|
234
292
|
}
|
|
235
293
|
/**
|
|
236
|
-
* Spawn a new
|
|
294
|
+
* Spawn a new native worker
|
|
237
295
|
*/
|
|
238
296
|
spawnWorker() {
|
|
239
297
|
try {
|
|
240
298
|
const id = this.workerIdCounter++;
|
|
241
|
-
const worker = new
|
|
299
|
+
const worker = new NativeWorker(id, this.workerScriptPath, {
|
|
300
|
+
onExit: (workerId, code) => {
|
|
301
|
+
const existing = this.workers.get(workerId);
|
|
302
|
+
if (!existing)
|
|
303
|
+
return;
|
|
304
|
+
this.workers.delete(workerId);
|
|
305
|
+
this.emit('workerExit', { workerId, code: code !== null && code !== void 0 ? code : 0 });
|
|
306
|
+
if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
|
|
307
|
+
this.spawnWorker();
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
onError: (workerId, error) => {
|
|
311
|
+
const existing = this.workers.get(workerId);
|
|
312
|
+
if (!existing)
|
|
313
|
+
return;
|
|
314
|
+
this.workers.delete(workerId);
|
|
315
|
+
this.emit('workerExit', { workerId, code: 1, error: error.message });
|
|
316
|
+
if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
|
|
317
|
+
this.spawnWorker();
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
});
|
|
242
321
|
this.workers.set(id, worker);
|
|
243
322
|
this.emit('workerSpawned', id);
|
|
244
323
|
return worker;
|
|
@@ -266,10 +345,17 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
266
345
|
const response = await worker.execute(task);
|
|
267
346
|
if (task.timeoutId)
|
|
268
347
|
clearTimeout(task.timeoutId);
|
|
348
|
+
if (task.settled) {
|
|
349
|
+
// Task was already rejected (e.g. timeout). Ignore late responses.
|
|
350
|
+
this.processQueue();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
269
353
|
if (response.success) {
|
|
354
|
+
task.settled = true;
|
|
270
355
|
task.resolve(response.result);
|
|
271
356
|
}
|
|
272
357
|
else {
|
|
358
|
+
task.settled = true;
|
|
273
359
|
task.reject(new Error(response.error || 'Unknown worker error'));
|
|
274
360
|
}
|
|
275
361
|
this.emit('taskCompleted', {
|
|
@@ -283,14 +369,38 @@ class WorkerPool extends SimpleEventEmitter {
|
|
|
283
369
|
catch (error) {
|
|
284
370
|
if (task.timeoutId)
|
|
285
371
|
clearTimeout(task.timeoutId);
|
|
286
|
-
task.
|
|
372
|
+
if (!task.settled) {
|
|
373
|
+
task.settled = true;
|
|
374
|
+
task.reject(error instanceof Error ? error : new Error(String(error)));
|
|
375
|
+
}
|
|
287
376
|
}
|
|
288
377
|
}
|
|
289
378
|
/**
|
|
290
379
|
* Handle task timeout
|
|
291
380
|
*/
|
|
292
381
|
handleTaskTimeout(task) {
|
|
293
|
-
|
|
382
|
+
const timeoutError = new Error('Task timeout');
|
|
383
|
+
if (task.timeoutId) {
|
|
384
|
+
clearTimeout(task.timeoutId);
|
|
385
|
+
task.timeoutId = null;
|
|
386
|
+
}
|
|
387
|
+
if (!task.settled) {
|
|
388
|
+
task.settled = true;
|
|
389
|
+
task.reject(timeoutError);
|
|
390
|
+
}
|
|
391
|
+
// Try to terminate the worker that was executing this task.
|
|
392
|
+
for (const [id, worker] of this.workers) {
|
|
393
|
+
if (worker.info.currentTaskId === task.id) {
|
|
394
|
+
worker.cancelTask(task.id, timeoutError);
|
|
395
|
+
worker.terminate();
|
|
396
|
+
this.workers.delete(id);
|
|
397
|
+
this.emit('workerExit', { workerId: id, code: 1 });
|
|
398
|
+
if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
|
|
399
|
+
this.spawnWorker();
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
294
404
|
// Remove from queue if present
|
|
295
405
|
const queueIndex = this.taskQueue.findIndex((t) => t.id === task.id);
|
|
296
406
|
if (queueIndex !== -1) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worker Script
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Currently, the WorkerPool uses virtual workers that execute
|
|
6
|
-
* tasks asynchronously on the main thread with yielding.
|
|
4
|
+
* Utilities used by the native worker thread entrypoint.
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The worker pool executes user-provided compute functions by serializing the
|
|
7
|
+
* function body (`Function#toString()`) and evaluating it with `new Function`.
|
|
8
|
+
*
|
|
9
|
+
* Requirements/limitations:
|
|
10
|
+
* - The compute function must be pure (no closures / no external references).
|
|
11
|
+
* - Inputs/outputs must be structured-cloneable.
|
|
12
|
+
* - Do not pass untrusted code as compute functions.
|
|
10
13
|
*/
|
|
11
14
|
import { WorkerMessage, WorkerResponse } from './types';
|
|
12
15
|
/**
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Worker Script
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* Currently, the WorkerPool uses virtual workers that execute
|
|
7
|
-
* tasks asynchronously on the main thread with yielding.
|
|
5
|
+
* Utilities used by the native worker thread entrypoint.
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* The worker pool executes user-provided compute functions by serializing the
|
|
8
|
+
* function body (`Function#toString()`) and evaluating it with `new Function`.
|
|
9
|
+
*
|
|
10
|
+
* Requirements/limitations:
|
|
11
|
+
* - The compute function must be pure (no closures / no external references).
|
|
12
|
+
* - Inputs/outputs must be structured-cloneable.
|
|
13
|
+
* - Do not pass untrusted code as compute functions.
|
|
11
14
|
*/
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.executeCompute = executeCompute;
|
|
@@ -7,6 +7,7 @@ const chat_service_1 = require("./chat.service");
|
|
|
7
7
|
const command_service_1 = require("./core/command.service");
|
|
8
8
|
const player_service_1 = require("./core/player.service");
|
|
9
9
|
const principal_service_1 = require("./core/principal.service");
|
|
10
|
+
const session_recovery_service_1 = require("./core/session-recovery.service");
|
|
10
11
|
const http_service_1 = require("./http/http.service");
|
|
11
12
|
const persistence_service_1 = require("./persistence.service");
|
|
12
13
|
const command_execution_port_1 = require("./ports/command-execution.port");
|
|
@@ -60,6 +61,7 @@ function registerServicesServer(ctx) {
|
|
|
60
61
|
}
|
|
61
62
|
if (features.sessionLifecycle.enabled && mode !== 'RESOURCE') {
|
|
62
63
|
index_1.di.registerSingleton(persistence_service_1.PlayerPersistenceService, persistence_service_1.PlayerPersistenceService);
|
|
64
|
+
index_1.di.registerSingleton(session_recovery_service_1.SessionRecoveryService, session_recovery_service_1.SessionRecoveryService);
|
|
63
65
|
}
|
|
64
66
|
if (features.principal.enabled) {
|
|
65
67
|
if (features.principal.provider === 'local' || mode === 'CORE' || mode === 'STANDALONE') {
|
|
@@ -14,9 +14,15 @@ export interface TransferCompletedPayload {
|
|
|
14
14
|
export interface PlayerFullyConnectedPayload {
|
|
15
15
|
player: Player;
|
|
16
16
|
}
|
|
17
|
+
export interface PlayerSessionRecoveredPayload {
|
|
18
|
+
clientId: number;
|
|
19
|
+
player: Player;
|
|
20
|
+
license: string | undefined;
|
|
21
|
+
}
|
|
17
22
|
export type InternalEventMap = {
|
|
18
23
|
'internal:playerSessionCreated': PlayerSessionCreatedPayload;
|
|
19
24
|
'internal:playerSessionDestroyed': PlayerSessionDestroyedPayload;
|
|
20
25
|
'internal:transfer:completed': TransferCompletedPayload;
|
|
21
26
|
'internal:playerFullyConnected': PlayerFullyConnectedPayload;
|
|
27
|
+
'internal:playerSessionRecovered': PlayerSessionRecoveredPayload;
|
|
22
28
|
};
|