@openhps/core
Version:
Open Hybrid Positioning System - Core component
277 lines • 11.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkerHandler = void 0;
const threads_1 = require("threads");
const DataSerializer_1 = require("../data/DataSerializer");
const DataService_1 = require("../service/DataService");
const WorkerServiceProxy_1 = require("../service/WorkerServiceProxy");
const AsyncEventEmitter_1 = require("../_internal/AsyncEventEmitter");
const DummyDataService_1 = require("../service/DummyDataService");
const DummyService_1 = require("../service/DummyService");
class WorkerHandler extends AsyncEventEmitter_1.AsyncEventEmitter {
constructor(model, options, config) {
var _a;
super();
this._serviceOutputResponse = new Map();
this.model = model;
this.config = config;
this.options = options;
this.options.timeout = (_a = this.options.timeout) !== null && _a !== void 0 ? _a : 10000;
}
build() {
return new Promise((resolve) => {
if (typeof process.env.NODE_ENV === 'undefined') {
// eslint-disable-next-line
const NativeWorker = typeof __non_webpack_require__ === "function" ? __non_webpack_require__("worker_threads").Worker : eval("require")("worker_threads").Worker;
if (NativeWorker) {
// NodeJS
NativeWorker.defaultMaxListeners = 0;
const resolvedPath = require.resolve(this.options.worker);
if (resolvedPath.match(/\.tsx?$/i)) {
// Transpile
this.options.worker = `
require('ts-node').register();
require(${JSON.stringify(resolvedPath)});
`;
this.options.blob = true;
}
}
}
this._pool = (0, threads_1.Pool)(() => this._spawnWorker(), {
size: this.options.poolSize || 4,
concurrency: this.options.poolConcurrency || 2,
});
this._pool.events().subscribe((value) => {
if (value.type === 'initialized') {
resolve();
}
});
});
}
destroy() {
return new Promise((resolve, reject) => {
if (this._pool === undefined) {
return resolve();
}
const timeout = setTimeout(() => {
this._pool
.terminate(true)
.then(() => {
resolve();
})
.catch((ex) => {
reject(ex);
});
}, 2500);
this._pool
.terminate()
.then(() => {
clearTimeout(timeout);
resolve();
})
.catch((ex) => {
clearTimeout(timeout);
reject(ex);
});
});
}
pull(options) {
return new Promise((resolve, reject) => {
// Pass the pull request to the worker
this._pool
.queue((worker) => {
const pullFn = worker.pull;
return pullFn(options);
})
.then(resolve)
.catch(reject);
});
}
push(frame, options) {
return new Promise((resolve, reject) => {
this._pool
.queue((worker) => {
const pushFn = worker.push;
return pushFn(DataSerializer_1.DataSerializer.serialize(frame), options);
})
.then(() => {
resolve();
})
.catch(reject);
});
}
invokeMethod(methodName, ...args) {
return new Promise((resolve, reject) => {
this._pool
.queue((worker) => {
const invokeMethod = worker.invokeMethod;
return invokeMethod(methodName, ...args.map((a) => DataSerializer_1.DataSerializer.serialize(a)));
})
.then((result) => {
if (result !== undefined) {
resolve(DataSerializer_1.DataSerializer.deserialize(result));
}
else {
resolve(result);
}
})
.catch(reject);
});
}
createWorker() {
if (this.options.blob) {
const worker = new threads_1.BlobWorker(this.options.worker, {
type: this.options.type === 'typescript' ? 'classic' : this.options.type,
});
return worker;
}
else {
const worker = new threads_1.Worker(this.options.worker, {
type: this.options.type === 'typescript' ? 'classic' : this.options.type,
});
return worker;
}
}
/**
* Spawn a single worker
* This method can be called multiple times in a pool
* @returns {Promise<Thread>} Thread spawn promise
*/
_spawnWorker() {
return new Promise((resolve, reject) => {
const worker = this.createWorker();
(0, threads_1.spawn)(worker, {
timeout: this.options.timeout,
})
.then((thread) => {
const init = thread.init;
const pushOutput = thread.pushOutput;
const pullOutput = thread.pullOutput;
const serviceOutputCall = thread.serviceOutputCall;
const serviceInputCall = thread.serviceInputCall;
const eventOutput = thread.eventOutput;
const findAllServices = thread.findAllServices;
const threadId = worker.threadId;
this._serviceOutputResponse.set(threadId, thread.serviceOutputResponse);
// Subscribe to the workers pull, push and service functions
pullOutput().subscribe(this._onWorkerPull.bind(this));
pushOutput().subscribe(this._onWorkerPush.bind(this));
serviceOutputCall().subscribe(this._onWorkerService.bind(this, threadId));
eventOutput().subscribe(this._onWorkerEvent.bind(this));
// Initialize the worker
init(Object.assign({ directory: this.options.directory || __dirname, services: this._getServices(), imports: this.options.imports || [], args: this.options.args || {}, type: this.options.type || 'classic', methods: this.options.methods
? this.options.methods.map((method) => {
return {
name: method.name,
handlerFn: method.handler.toString(),
};
})
: [] }, this.config))
.then(() => {
return findAllServices();
})
.then((services) => {
this._addServices(services, serviceInputCall);
resolve(thread);
})
.catch(reject);
})
.catch(reject);
});
}
/**
* Serialize the services of this model
* @returns {any[]} Services array
*/
_getServices() {
// Serialize this model services to the worker
const services = this.options.services || this.model.findAllServices();
const servicesArray = services.map((service) => {
// Services are wrapped in a proxy. Get prototype
const serviceBase = Object.getPrototypeOf(service);
return {
uid: service.uid,
type: serviceBase.constructor.name,
dataType: service instanceof DataService_1.DataService ? (service.dataType ? service.dataType.name : undefined) : undefined,
};
});
return servicesArray;
}
_addServices(services, call) {
const model = this.model;
services
.filter((service) => {
const internalService = this.model.findService(service.name) || this.model.findDataService(service.name);
return internalService === undefined;
})
.forEach((service) => {
if (service.dataType) {
const DataType = DataSerializer_1.DataSerializer.findTypeByName(service.dataType);
model.addService(new DummyDataService_1.DummyDataService(service.uid, DataType), new WorkerServiceProxy_1.WorkerServiceProxy({
uid: service.uid,
callFunction: call,
}));
}
else {
model.addService(new DummyService_1.DummyService(service.uid), new WorkerServiceProxy_1.WorkerServiceProxy({
uid: service.uid,
callFunction: call,
}));
}
});
}
_onWorkerService(threadId, value) {
const service = this.model.findDataService(value.serviceUID) || this.model.findService(value.serviceUID);
if (service[value.method]) {
const serializedParams = value.parameters;
const params = [];
serializedParams.forEach((param) => {
if (param['__type']) {
params.push(DataSerializer_1.DataSerializer.deserialize(param));
}
else {
params.push(param);
}
});
const promise = service[value.method](...params);
Promise.resolve(promise)
.then((_) => {
if (Array.isArray(_)) {
const result = [];
_.forEach((r) => {
result.push(DataSerializer_1.DataSerializer.serialize(r));
});
this._serviceOutputResponse.get(threadId)({ id: value.id, success: true, result });
}
else {
const result = DataSerializer_1.DataSerializer.serialize(_);
this._serviceOutputResponse.get(threadId)({ id: value.id, success: true, result });
}
})
.catch((ex) => {
this._serviceOutputResponse.get(threadId)({ id: value.id, success: false, result: ex });
});
}
}
_onWorkerEvent(value) {
this.emit('event', value);
}
/**
* Triggered for each worker that requests a pull
* @param {PullOptions} options Pull options
*/
_onWorkerPull(options) {
this.emit('pull', options);
}
/**
* Triggered for each worker that pushes data
* @param {any} value Serialized data
* @param {PushOptions} options Push options
*/
_onWorkerPush(value, options) {
const deserializedFrame = DataSerializer_1.DataSerializer.deserialize(value);
this.emit('push', deserializedFrame, options);
}
}
exports.WorkerHandler = WorkerHandler;
//# sourceMappingURL=WorkerHandler.js.map