vrack2-core 1.0.4 → 1.1.0
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 +15 -6
- package/lib/Bootstrap.js +12 -23
- package/lib/Container.js +180 -200
- package/lib/ImportManager.js +36 -51
- package/lib/MainProcess.js +6 -19
- package/lib/ReactiveRef.d.ts +48 -0
- package/lib/ReactiveRef.js +74 -0
- package/lib/UniversalWorkers.d.ts +59 -0
- package/lib/UniversalWorkers.js +119 -0
- package/lib/Utility.js +11 -15
- package/lib/boot/BootClass.js +1 -12
- package/lib/boot/DeviceFileStorage.js +20 -33
- package/lib/boot/DeviceManager.js +42 -55
- package/lib/boot/StructureStorage.js +20 -35
- package/lib/errors/ErrorManager.js +1 -1
- package/lib/service/Device.d.ts +11 -0
- package/lib/service/Device.js +11 -14
- package/lib/service/DevicePort.d.ts +1 -0
- package/lib/service/DevicePort.js +5 -0
- package/package.json +1 -1
- package/src/Container.ts +6 -1
- package/src/ReactiveRef.ts +83 -0
- package/src/UniversalWorkers.ts +127 -0
- package/src/service/Device.ts +19 -2
- package/src/service/DevicePort.ts +5 -0
- package/tsconfig.json +3 -2
|
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
27
|
};
|
|
@@ -124,35 +115,33 @@ class DeviceManager extends BootClass_1.default {
|
|
|
124
115
|
*
|
|
125
116
|
* @see IDeivceInfo
|
|
126
117
|
*/
|
|
127
|
-
getDeviceInfo(vendor, device) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return result;
|
|
155
|
-
});
|
|
118
|
+
async getDeviceInfo(vendor, device) {
|
|
119
|
+
const di = [vendor, device].join('.');
|
|
120
|
+
const DeviceClass = await this.get(di);
|
|
121
|
+
const dev = new DeviceClass('1', di, this);
|
|
122
|
+
const result = { actions: {}, metrics: {}, inputs: {}, outputs: {}, options: {}, description: '' };
|
|
123
|
+
try {
|
|
124
|
+
const preOptions = dev.checkOptions();
|
|
125
|
+
for (const oName in preOptions)
|
|
126
|
+
result.options[oName] = preOptions[oName].export();
|
|
127
|
+
const dInputs = dev.inputs();
|
|
128
|
+
for (const iName in dInputs)
|
|
129
|
+
result.inputs[iName] = dInputs[iName].export();
|
|
130
|
+
const dOutputs = dev.outputs();
|
|
131
|
+
for (const oName in dOutputs)
|
|
132
|
+
result.outputs[oName] = dOutputs[oName].export();
|
|
133
|
+
const dActions = dev.actions();
|
|
134
|
+
for (const aName in dActions)
|
|
135
|
+
result.actions[aName] = dActions[aName].export();
|
|
136
|
+
const dMetrics = dev.metrics();
|
|
137
|
+
for (const mName in dMetrics)
|
|
138
|
+
result.metrics[mName] = dMetrics[mName].export();
|
|
139
|
+
result.description = dev.description();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw ErrorManager_1.default.make('DM_GET_INFO_EXCEPTION').setTrace(new Error).add(error);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
156
145
|
}
|
|
157
146
|
/**
|
|
158
147
|
* Method for updating the device list
|
|
@@ -226,23 +215,21 @@ class DeviceManager extends BootClass_1.default {
|
|
|
226
215
|
* example "vrack.System" where "vrack" is vendor and "System" is device class
|
|
227
216
|
* @param {string} device Device path string
|
|
228
217
|
*/
|
|
229
|
-
get(device, updateList = true) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
throw ErrorManager_1.default.make('DM_DEVICE_NOT_FOUND', { device });
|
|
245
|
-
});
|
|
218
|
+
async get(device, updateList = true) {
|
|
219
|
+
const p = this.devices.get(device); // return device path or undefined
|
|
220
|
+
if (typeof p === 'string') {
|
|
221
|
+
const deviceClass = await Promise.resolve(`${p}`).then(s => __importStar(require(s))); // try import
|
|
222
|
+
if (deviceClass.default)
|
|
223
|
+
return deviceClass.default;
|
|
224
|
+
}
|
|
225
|
+
// Устройство не найдено, но не исключено что если перестроить дерево
|
|
226
|
+
// вендоров и устройств все будет так же
|
|
227
|
+
// По умолчанию мы попытаемся обновить дерево
|
|
228
|
+
if (updateList) {
|
|
229
|
+
await this.updateDeviceList();
|
|
230
|
+
return await this.get(device, false);
|
|
231
|
+
}
|
|
232
|
+
throw ErrorManager_1.default.make('DM_DEVICE_NOT_FOUND', { device });
|
|
246
233
|
}
|
|
247
234
|
/**
|
|
248
235
|
* Find vendor by name
|
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
* Copyright © 2024 Boris Bobylev. All rights reserved.
|
|
4
4
|
* Licensed under the Apache License, Version 2.0
|
|
5
5
|
*/
|
|
6
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
8
|
};
|
|
@@ -59,33 +50,29 @@ class StructureStorage extends BootClass_1.default {
|
|
|
59
50
|
*
|
|
60
51
|
* @see StructureStorage.process
|
|
61
52
|
*/
|
|
62
|
-
beforeLoadedUpdate() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
});
|
|
53
|
+
async beforeLoadedUpdate() {
|
|
54
|
+
const fp = this.makeFilePath(this.Container.id);
|
|
55
|
+
let structure = {};
|
|
56
|
+
try {
|
|
57
|
+
if ((0, fs_1.existsSync)(fp))
|
|
58
|
+
structure = ImportManager_1.default.importJSON(fp);
|
|
59
|
+
const cStruct = await this.Container.getStructure();
|
|
60
|
+
this.updateStructure(cStruct, structure, this.Container.id);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.error(error);
|
|
64
|
+
}
|
|
76
65
|
}
|
|
77
66
|
/**
|
|
78
67
|
* Returns structure by container identifier
|
|
79
68
|
*
|
|
80
69
|
* @param id Container ID
|
|
81
70
|
*/
|
|
82
|
-
getById(id) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return ImportManager_1.default.importJSON(fp);
|
|
88
|
-
});
|
|
71
|
+
async getById(id) {
|
|
72
|
+
const fp = this.makeFilePath(id);
|
|
73
|
+
if (!(0, fs_1.existsSync)(fp))
|
|
74
|
+
throw ErrorManager_1.default.make('SS_STRUCT_NOT_FOUND', { id });
|
|
75
|
+
return ImportManager_1.default.importJSON(fp);
|
|
89
76
|
}
|
|
90
77
|
/**
|
|
91
78
|
* Updating the container structure
|
|
@@ -93,11 +80,9 @@ class StructureStorage extends BootClass_1.default {
|
|
|
93
80
|
* @param id Container ID
|
|
94
81
|
* @param structure updated container structure object
|
|
95
82
|
*/
|
|
96
|
-
updateById(id, structure) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.updateStructure(cStruct, structure, id);
|
|
100
|
-
});
|
|
83
|
+
async updateById(id, structure) {
|
|
84
|
+
const cStruct = await this.getById(id);
|
|
85
|
+
this.updateStructure(cStruct, structure, id);
|
|
101
86
|
}
|
|
102
87
|
/**
|
|
103
88
|
* Updates the display structure parameter from the file structure
|
|
@@ -33,7 +33,7 @@ class ErrorManager {
|
|
|
33
33
|
const reg2 = this.getRegistered(short);
|
|
34
34
|
if (reg1 !== null || reg2 !== null) {
|
|
35
35
|
// Если уже есть идентичная запись - просто игнорим
|
|
36
|
-
if (
|
|
36
|
+
if (reg1?.code === reg2?.code && reg1?.short === reg2?.short) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
else {
|
package/lib/service/Device.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export default class Device {
|
|
|
40
40
|
* @example 'vrack.KeyManager'
|
|
41
41
|
*/
|
|
42
42
|
type: string;
|
|
43
|
+
/**
|
|
44
|
+
* Флаг общей работы
|
|
45
|
+
* Если флаг === false = все порты перестают принимать или отправлять данные/события
|
|
46
|
+
*
|
|
47
|
+
*/
|
|
48
|
+
works: boolean;
|
|
43
49
|
/**
|
|
44
50
|
* Allows access to port management.
|
|
45
51
|
*/
|
|
@@ -136,6 +142,11 @@ export default class Device {
|
|
|
136
142
|
* @param data data for action
|
|
137
143
|
*/
|
|
138
144
|
beforeAction(action: string, data: any): boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Должен вызываться перед завершением сервиса
|
|
147
|
+
* Но может не вызываться (зависит от реализации)
|
|
148
|
+
*/
|
|
149
|
+
beforeTerminate(): void;
|
|
139
150
|
/**
|
|
140
151
|
* Prepare options
|
|
141
152
|
*
|
package/lib/service/Device.js
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
* Copyright © 2022 Boris Bobylev. All rights reserved.
|
|
4
4
|
* Licensed under the Apache License, Version 2.0
|
|
5
5
|
*/
|
|
6
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
8
|
};
|
|
@@ -58,6 +49,7 @@ class Device {
|
|
|
58
49
|
this.id = id;
|
|
59
50
|
this.type = type;
|
|
60
51
|
this.Container = Container;
|
|
52
|
+
this.works = true;
|
|
61
53
|
this.ports = {
|
|
62
54
|
input: {},
|
|
63
55
|
output: {}
|
|
@@ -118,6 +110,11 @@ class Device {
|
|
|
118
110
|
* @param data data for action
|
|
119
111
|
*/
|
|
120
112
|
beforeAction(action, data) { return true; }
|
|
113
|
+
/**
|
|
114
|
+
* Должен вызываться перед завершением сервиса
|
|
115
|
+
* Но может не вызываться (зависит от реализации)
|
|
116
|
+
*/
|
|
117
|
+
beforeTerminate() { return; }
|
|
121
118
|
/**
|
|
122
119
|
* Prepare options
|
|
123
120
|
*
|
|
@@ -189,9 +186,7 @@ class Device {
|
|
|
189
186
|
* Used when there is a need to execute before starting the rack
|
|
190
187
|
* and wait for asynchronous code to execute (initialization of some file databases, etc.)
|
|
191
188
|
*/
|
|
192
|
-
processPromise() {
|
|
193
|
-
return __awaiter(this, void 0, void 0, function* () { return; });
|
|
194
|
-
}
|
|
189
|
+
async processPromise() { return; }
|
|
195
190
|
/**
|
|
196
191
|
* Myby todo?
|
|
197
192
|
*
|
|
@@ -268,7 +263,8 @@ class Device {
|
|
|
268
263
|
*/
|
|
269
264
|
addInputHandler(name, action) {
|
|
270
265
|
name = ImportManager_1.default.camelize('input.' + name);
|
|
271
|
-
|
|
266
|
+
const a = this;
|
|
267
|
+
a[name] = action;
|
|
272
268
|
}
|
|
273
269
|
/**
|
|
274
270
|
* Adding a handle for the action
|
|
@@ -278,7 +274,8 @@ class Device {
|
|
|
278
274
|
*/
|
|
279
275
|
addActionHandler(name, action) {
|
|
280
276
|
name = ImportManager_1.default.camelize('action.' + name);
|
|
281
|
-
|
|
277
|
+
const a = this;
|
|
278
|
+
a[name] = action;
|
|
282
279
|
}
|
|
283
280
|
/**
|
|
284
281
|
* Informs the rack that the unit cannot continue to operate.
|
|
@@ -18,6 +18,7 @@ export default class DevicePort {
|
|
|
18
18
|
required: boolean;
|
|
19
19
|
/** Ссылка на устройсто владельца */
|
|
20
20
|
Device: Device;
|
|
21
|
+
bind: ((data: any) => {}) | null;
|
|
21
22
|
/**
|
|
22
23
|
* Список слушателей порта
|
|
23
24
|
* Используется для захвата порта. Если какие либо данные будут проброшены
|
|
@@ -18,6 +18,7 @@ class DevicePort {
|
|
|
18
18
|
this.connected = false;
|
|
19
19
|
/** Port connection list. One port can have multiple connections */
|
|
20
20
|
this.connections = [];
|
|
21
|
+
this.bind = null;
|
|
21
22
|
/**
|
|
22
23
|
* Список слушателей порта
|
|
23
24
|
* Используется для захвата порта. Если какие либо данные будут проброшены
|
|
@@ -40,6 +41,10 @@ class DevicePort {
|
|
|
40
41
|
* Calling the incoming port when calling a connection
|
|
41
42
|
*/
|
|
42
43
|
push(data) {
|
|
44
|
+
if (!this.Device.works)
|
|
45
|
+
return;
|
|
46
|
+
if (this.bind !== null)
|
|
47
|
+
return this.bind(data);
|
|
43
48
|
// Если у нас есть слушатели порта
|
|
44
49
|
// Передаем им данные и пересоздаем Map
|
|
45
50
|
if (this.listens.size) {
|
package/package.json
CHANGED
package/src/Container.ts
CHANGED
|
@@ -107,6 +107,9 @@ ErrorManager.register('Container', 'XR1K10R0OOUC', 'CTR_INCOMPATIBLE_PORTS', 'In
|
|
|
107
107
|
ErrorManager.register('Container', 'MmVoDOQwaYkx', 'CTR_INCORRECT_BOOSTRAP', 'The required DeviceManager class is not specified correctly', {})
|
|
108
108
|
ErrorManager.register('Container', 'e090R0MLyb7y', 'CTR_CONF_EXTENDS_PROBLEM', 'Problem with extending service configuration.', {})
|
|
109
109
|
|
|
110
|
+
ErrorManager.register('Container', 'LYC0VA1AWYKU', 'CTR_IGNORE_SERVICE_AUTORELOAD', 'Error that ignores service restart flag', {})
|
|
111
|
+
|
|
112
|
+
|
|
110
113
|
|
|
111
114
|
/**
|
|
112
115
|
* Contains the structure of the service
|
|
@@ -474,7 +477,9 @@ export default class Container extends EventEmitter {
|
|
|
474
477
|
this.structure[dconf.id].ports.push(
|
|
475
478
|
Object.assign({ port: subkey, direct: 'input' }, pList[subkey])
|
|
476
479
|
)
|
|
477
|
-
|
|
480
|
+
|
|
481
|
+
// биндимся а не заменяем push для контроля внутри push
|
|
482
|
+
if (handler in dev) ndp.bind = dev[handler].bind(dev)
|
|
478
483
|
}
|
|
479
484
|
}
|
|
480
485
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Простой реактивный ref-аналог (как в Vue 3), но только для объектов.
|
|
3
|
+
* Поддерживает глубокую реактивность вложенных plain-объектов.
|
|
4
|
+
* Массивы НЕ отслеживаются внутри — только при переприсвоении свойства целиком.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const state = new ReactiveRef({ user: { name: 'Alice' }, items: [1, 2] });
|
|
9
|
+
*
|
|
10
|
+
* state.watch(() => console.log('изменилось!'));
|
|
11
|
+
*
|
|
12
|
+
* state.value.user.name = 'Bob'; // вызовет callback
|
|
13
|
+
* state.value.items = [1, 2, 3]; // вызовет callback
|
|
14
|
+
* state.value.items.push(4); // НЕ вызовет (мутация массива)
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export default class ReactiveRef<T extends object> {
|
|
18
|
+
/**
|
|
19
|
+
* Хранимое значение
|
|
20
|
+
*/
|
|
21
|
+
private _value: T;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Калбек для обработки при измении значения
|
|
25
|
+
*/
|
|
26
|
+
private watcher: () => void = () => {};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* При создании иницируем объектом
|
|
30
|
+
*/
|
|
31
|
+
constructor(initialValue: T) {
|
|
32
|
+
this._value = this.makeReactive(initialValue);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Getter value
|
|
37
|
+
*/
|
|
38
|
+
get value(): T {
|
|
39
|
+
return this._value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Назначает обработчик который будет вызван при изменении объекта
|
|
44
|
+
*/
|
|
45
|
+
watch(callback: () => void) {
|
|
46
|
+
this.watcher = callback;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Возвращает true если это простой объект не являющийся массивом или null
|
|
51
|
+
* Null кстати тоже объект внутри JS из-за чего эта проверка очень актуальна
|
|
52
|
+
*/
|
|
53
|
+
private isPlainObject(val: unknown): val is Record<string, unknown> {
|
|
54
|
+
return val !== null && typeof val === 'object' && !Array.isArray(val);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Делает переданный объект (если он объект) реактивным
|
|
59
|
+
* Причем делает свойства объекта тоже реактивными рекурсивно
|
|
60
|
+
*/
|
|
61
|
+
private makeReactive<TObj extends object>(obj: TObj): TObj {
|
|
62
|
+
if ((obj as any).__isReactive) return obj;
|
|
63
|
+
|
|
64
|
+
const handler: ProxyHandler<TObj> = {
|
|
65
|
+
set: (target, key, value) => {
|
|
66
|
+
const oldValue = target[key as keyof TObj];
|
|
67
|
+
const isOwn = Object.prototype.hasOwnProperty.call(target, key);
|
|
68
|
+
|
|
69
|
+
// Рекурсивно реактивизируем только обычные объекты (не массивы!)
|
|
70
|
+
const nextValue = this.isPlainObject(value) ? this.makeReactive(value) : value;
|
|
71
|
+
target[key as keyof TObj] = nextValue;
|
|
72
|
+
// Уведомляем только при изменении значения
|
|
73
|
+
if (!isOwn || oldValue !== nextValue) this.watcher();
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const proxy = new Proxy(obj, handler);
|
|
79
|
+
(proxy as any).__isReactive = true;
|
|
80
|
+
return proxy;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// UniversalWorker.ts
|
|
2
|
+
import { ChildProcess, fork } from 'child_process';
|
|
3
|
+
import { Worker as WorkerThread, parentPort, workerData, isMainThread } from 'worker_threads';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
type UniversalWorkerOptions = {
|
|
7
|
+
isolated?: boolean;
|
|
8
|
+
scriptPath: string;
|
|
9
|
+
workerData?: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default class UniversalWorker {
|
|
13
|
+
|
|
14
|
+
private impl: {
|
|
15
|
+
send: (msg: any) => void;
|
|
16
|
+
on: (event: 'message' | 'exit' | 'error', handler: (...args: any[]) => void) => void;
|
|
17
|
+
kill: () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Создает новые воркер
|
|
22
|
+
*
|
|
23
|
+
* Если параметр isolated = true будет создан форк
|
|
24
|
+
* иначе будет использоваться WorkerThread
|
|
25
|
+
*/
|
|
26
|
+
constructor(options: UniversalWorkerOptions) {
|
|
27
|
+
const { isolated = true, scriptPath, workerData } = options;
|
|
28
|
+
|
|
29
|
+
if (isolated) {
|
|
30
|
+
// child_process
|
|
31
|
+
const proc = fork(scriptPath, [], {
|
|
32
|
+
env: { ...process.env, VRACK2_WORKER_DATA: JSON.stringify(workerData) }
|
|
33
|
+
});
|
|
34
|
+
this.impl = {
|
|
35
|
+
send: (msg) => proc.send(msg),
|
|
36
|
+
on: (ev, handler) => proc.on(ev, handler),
|
|
37
|
+
kill: () => proc.kill()
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
// worker_threads
|
|
41
|
+
const wt = new WorkerThread(scriptPath, { workerData });
|
|
42
|
+
this.impl = {
|
|
43
|
+
send: (msg) => wt.postMessage(msg),
|
|
44
|
+
on: (ev, handler) => {
|
|
45
|
+
if (ev === 'message') wt.on('message', handler);
|
|
46
|
+
else if (ev === 'error') wt.on('error', handler);
|
|
47
|
+
else if (ev === 'exit') wt.on('exit', handler as (code: number) => void);
|
|
48
|
+
},
|
|
49
|
+
kill: () => wt.terminate()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Отправляет внутрь воркера обхект способный к сериализации
|
|
56
|
+
*/
|
|
57
|
+
send(msg: any): void {
|
|
58
|
+
this.impl.send(msg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Подписывает на события воркера
|
|
63
|
+
*
|
|
64
|
+
* Доступно только message exit error
|
|
65
|
+
*/
|
|
66
|
+
on(event: 'message' | 'exit' | 'error', handler: (...args: any[]) => void): void {
|
|
67
|
+
this.impl.on(event, handler);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Убивает процесс
|
|
72
|
+
*/
|
|
73
|
+
kill() {
|
|
74
|
+
this.impl.kill();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @see kill
|
|
79
|
+
*/
|
|
80
|
+
terminate() {
|
|
81
|
+
this.impl.kill();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
export type WorkerMessageHandler = (message: any) => void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Определяет - является ли процесс форкнутым
|
|
90
|
+
* Основанно на проверке метода process.send которая есть только у фокрнутого процесса
|
|
91
|
+
*/
|
|
92
|
+
export const isForked = typeof process.send === 'function';
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Определяет - является ли данный инстанс дочерним - не важно используя worker_threads или fork
|
|
96
|
+
* Проверяет !isMainThread || isForked
|
|
97
|
+
*/
|
|
98
|
+
export const isChild = !isMainThread || isForked;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Получение данных сверху, которые были переданны при создании воркера
|
|
102
|
+
* В случае использования worker_threads - workerData
|
|
103
|
+
* Если же это форк - парсит устаноленнюу переменную окружения VRACK2_WORKER_DATA
|
|
104
|
+
*/
|
|
105
|
+
export function getWorkerData(): any {
|
|
106
|
+
if (!isMainThread) return workerData;
|
|
107
|
+
if (isForked) return JSON.parse(process.env.VRACK2_WORKER_DATA || '{}');
|
|
108
|
+
throw new Error('getWorkerData() called in main process');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Отправка данных наверх в родительский процесс
|
|
113
|
+
*/
|
|
114
|
+
export function sendMessage(message: any): void {
|
|
115
|
+
if (!isMainThread) return parentPort?.postMessage(message);
|
|
116
|
+
if (isForked) { process.send?.(message); return }
|
|
117
|
+
throw new Error('sendMessage() called in main process');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Позволяет подписаться на данные которые приходят сверху
|
|
122
|
+
*/
|
|
123
|
+
export function onMessage(handler: WorkerMessageHandler): void {
|
|
124
|
+
if (!isMainThread) parentPort?.on('message', handler);
|
|
125
|
+
else if (isForked) process.on('message', handler);
|
|
126
|
+
else throw new Error('onMessage() called in main process');
|
|
127
|
+
}
|
package/src/service/Device.ts
CHANGED
|
@@ -54,6 +54,13 @@ export default class Device {
|
|
|
54
54
|
*/
|
|
55
55
|
type: string;
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Флаг общей работы
|
|
59
|
+
* Если флаг === false = все порты перестают принимать или отправлять данные/события
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
62
|
+
works: boolean
|
|
63
|
+
|
|
57
64
|
/**
|
|
58
65
|
* Allows access to port management.
|
|
59
66
|
*/
|
|
@@ -83,6 +90,7 @@ export default class Device {
|
|
|
83
90
|
this.id = id
|
|
84
91
|
this.type = type
|
|
85
92
|
this.Container = Container
|
|
93
|
+
this.works = true
|
|
86
94
|
this.ports = {
|
|
87
95
|
input: {},
|
|
88
96
|
output: {}
|
|
@@ -165,6 +173,12 @@ export default class Device {
|
|
|
165
173
|
* @param data data for action
|
|
166
174
|
*/
|
|
167
175
|
beforeAction(action: string, data: any) { return true }
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Должен вызываться перед завершением сервиса
|
|
179
|
+
* Но может не вызываться (зависит от реализации)
|
|
180
|
+
*/
|
|
181
|
+
beforeTerminate(){ return }
|
|
168
182
|
|
|
169
183
|
/**
|
|
170
184
|
* Prepare options
|
|
@@ -330,7 +344,8 @@ export default class Device {
|
|
|
330
344
|
*/
|
|
331
345
|
addInputHandler(name: string, action: (data: any) => any) {
|
|
332
346
|
name = ImportManager.camelize('input.' + name)
|
|
333
|
-
|
|
347
|
+
const a = this as any
|
|
348
|
+
a[name] = action
|
|
334
349
|
}
|
|
335
350
|
|
|
336
351
|
/**
|
|
@@ -341,7 +356,8 @@ export default class Device {
|
|
|
341
356
|
*/
|
|
342
357
|
addActionHandler(name: string, action: (data: any) => any) {
|
|
343
358
|
name = ImportManager.camelize('action.' + name)
|
|
344
|
-
|
|
359
|
+
const a = this as any
|
|
360
|
+
a[name] = action
|
|
345
361
|
}
|
|
346
362
|
|
|
347
363
|
/**
|
|
@@ -353,4 +369,5 @@ export default class Device {
|
|
|
353
369
|
terminate(error: Error, action: string) {
|
|
354
370
|
return this.makeEvent('device.terminate', action, error, [])
|
|
355
371
|
}
|
|
372
|
+
|
|
356
373
|
}
|
|
@@ -30,6 +30,8 @@ export default class DevicePort {
|
|
|
30
30
|
/** Ссылка на устройсто владельца */
|
|
31
31
|
Device: Device
|
|
32
32
|
|
|
33
|
+
bind: ((data: any) => {}) | null = null;
|
|
34
|
+
|
|
33
35
|
/**
|
|
34
36
|
* Список слушателей порта
|
|
35
37
|
* Используется для захвата порта. Если какие либо данные будут проброшены
|
|
@@ -56,6 +58,9 @@ export default class DevicePort {
|
|
|
56
58
|
* Calling the incoming port when calling a connection
|
|
57
59
|
*/
|
|
58
60
|
push(data: any): any {
|
|
61
|
+
if (!this.Device.works) return
|
|
62
|
+
if (this.bind !== null) return this.bind(data)
|
|
63
|
+
|
|
59
64
|
// Если у нас есть слушатели порта
|
|
60
65
|
// Передаем им данные и пересоздаем Map
|
|
61
66
|
if (this.listens.size) {
|