2022-01-07 17:12:08 +00:00
|
|
|
/**
|
|
|
|
* @author Martin Karkowski
|
|
|
|
* @email m.karkowski@zema.de
|
|
|
|
* @create date 2022-01-03 19:31:44
|
|
|
|
* @modify date 2022-01-03 19:31:44
|
|
|
|
* @desc [description]
|
|
|
|
*/
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
import { ILogger } from "js-logger";
|
2022-01-07 17:12:08 +00:00
|
|
|
import { waitFor } from "../../helpers/async";
|
|
|
|
import { generateId } from "../../helpers/idMethods";
|
|
|
|
import { MapBasedMergeData } from "../../helpers/mergedData";
|
|
|
|
import { SPLITCHAR } from "../../helpers/objectMethods";
|
|
|
|
import { defineNopeLogger } from "../../logger/getLogger";
|
2022-01-16 19:38:45 +00:00
|
|
|
import { DEBUG } from "../../logger/index.browser";
|
2022-01-07 17:12:08 +00:00
|
|
|
import { NopeGenericWrapper } from "../../module/index";
|
|
|
|
import {
|
|
|
|
IAvailableInstancesMsg,
|
|
|
|
ICommunicationBridge,
|
2022-01-16 19:38:45 +00:00
|
|
|
IDisposeInstanceMsg,
|
2022-01-07 17:12:08 +00:00
|
|
|
IGenericNopeModule,
|
|
|
|
IInstanceCreationMsg,
|
|
|
|
IInstanceDescriptionMsg,
|
|
|
|
IMapBasedMergeData,
|
|
|
|
INopeConnectivityManager,
|
|
|
|
INopeCore,
|
|
|
|
INopeDispatcherOptions,
|
|
|
|
INopeModule,
|
|
|
|
INopeModuleDescription,
|
|
|
|
INopeObservable,
|
|
|
|
INopeRpcManager,
|
|
|
|
INopeStatusInfo,
|
|
|
|
TValidAsssignmentChecker,
|
|
|
|
ValidSelectorFunction,
|
|
|
|
} from "../../types/nope";
|
|
|
|
import {
|
|
|
|
INopeInstanceManager,
|
|
|
|
TConstructorCallback,
|
|
|
|
TGenerateWrapperCallback,
|
|
|
|
} from "../../types/nope/nopeInstanceManager.interface";
|
|
|
|
import { NopeConnectivityManager } from "../ConnectivityManager";
|
|
|
|
import { NopeRpcManager } from "../RpcManager/NopeRpcManager";
|
|
|
|
|
|
|
|
export class NopeInstanceManager implements INopeInstanceManager {
|
|
|
|
protected _logger: ILogger;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The used Communication interface
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected readonly _communicator: ICommunicationBridge;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag to indicate, that the system is ready.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {INopeObservable<boolean>}
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
public readonly ready: INopeObservable<boolean>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Element holding the Mapping of the Dispatcher and its instance
|
|
|
|
* generators
|
|
|
|
*
|
|
|
|
* Key = Dispatcher-ID
|
|
|
|
* Value = Available Generators
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @type {Map<
|
|
|
|
* string,
|
|
|
|
* IAvailableInstanceGeneratorsMsg
|
|
|
|
* >}
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
protected _mappingOfRemoteDispatchersAndGenerators: Map<string, string[]>;
|
|
|
|
|
|
|
|
protected _internalWrapperGenerators: Map<
|
|
|
|
string,
|
|
|
|
TGenerateWrapperCallback<INopeModule>
|
|
|
|
>;
|
|
|
|
|
|
|
|
protected _registeredConstructors: Map<
|
|
|
|
string,
|
|
|
|
(...args: any[]) => Promise<IInstanceDescriptionMsg>
|
|
|
|
>;
|
|
|
|
|
|
|
|
protected _instances: Map<
|
|
|
|
string,
|
|
|
|
{
|
|
|
|
instance: INopeModule;
|
|
|
|
usedBy: Array<string>;
|
|
|
|
manual?: boolean;
|
|
|
|
}
|
|
|
|
>;
|
|
|
|
|
|
|
|
protected _internalInstances: Set<string>;
|
|
|
|
|
|
|
|
protected _mappingOfRemoteDispatchersAndInstances: Map<
|
|
|
|
string,
|
|
|
|
IAvailableInstancesMsg
|
|
|
|
>;
|
|
|
|
protected _externalInstancesNames: Set<string>;
|
|
|
|
protected _externalInstances: Map<string, INopeModuleDescription>;
|
|
|
|
protected _initializingInstance: Map<string, string>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Element showing the available services.
|
|
|
|
* Its more or less a map, that maps the
|
|
|
|
* services with their dispatchers.
|
|
|
|
*
|
|
|
|
* T = services name.
|
|
|
|
* K = dispatcher - ids
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {IMapBasedMergeData<string>}
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
public readonly constructors: IMapBasedMergeData<string, string, string[]>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Element showing the available instances.
|
|
|
|
* Its more or less a map, that maps the
|
|
|
|
* instances with their dispatchers.
|
|
|
|
*
|
|
|
|
* T = INopeModuleDescription.
|
|
|
|
* K = dispatcher - ids
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @type {IMapBasedMergeData<
|
|
|
|
* string,
|
|
|
|
* string,
|
|
|
|
* IAvailableInstancesMsg
|
|
|
|
* >}
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
public readonly instances: IMapBasedMergeData<
|
|
|
|
INopeModuleDescription,
|
|
|
|
string,
|
|
|
|
IAvailableInstancesMsg
|
|
|
|
>;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
public options: INopeDispatcherOptions,
|
|
|
|
protected _generateObservable: <T>() => INopeObservable<T>,
|
|
|
|
protected _defaultSelector: ValidSelectorFunction,
|
|
|
|
protected readonly _id: string = null,
|
|
|
|
protected _statusManager: INopeConnectivityManager = null,
|
|
|
|
protected _rpcManager: INopeRpcManager = null,
|
|
|
|
protected _core: INopeCore = null
|
|
|
|
) {
|
|
|
|
this._communicator = options.communicator;
|
|
|
|
|
|
|
|
if (_id == null) {
|
|
|
|
this._id = generateId();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_statusManager == null) {
|
|
|
|
// Creating a new Status-Manager.
|
|
|
|
this._statusManager = new NopeConnectivityManager(
|
|
|
|
options,
|
|
|
|
_generateObservable,
|
|
|
|
this._id
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_rpcManager == null) {
|
|
|
|
// Creating a new Status-Manager.
|
|
|
|
this._rpcManager = new NopeRpcManager(
|
|
|
|
options,
|
|
|
|
_generateObservable,
|
|
|
|
async () => "test",
|
|
|
|
this._id
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._logger = defineNopeLogger(options.logger, `core.instance-manager`);
|
|
|
|
|
|
|
|
// Flag to show if the system is ready or not.
|
|
|
|
this.ready = this._generateObservable();
|
|
|
|
this.ready.setContent(false);
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// Define teh constructors
|
2022-01-07 17:12:08 +00:00
|
|
|
const _this = this;
|
|
|
|
this._mappingOfRemoteDispatchersAndGenerators = new Map();
|
|
|
|
this.constructors = new MapBasedMergeData(
|
|
|
|
this._mappingOfRemoteDispatchersAndGenerators
|
|
|
|
);
|
2022-01-10 06:52:05 +00:00
|
|
|
|
|
|
|
this._mappingOfRemoteDispatchersAndInstances = new Map();
|
|
|
|
this.instances = new MapBasedMergeData(
|
|
|
|
this._mappingOfRemoteDispatchersAndInstances
|
|
|
|
);
|
|
|
|
|
2022-01-07 17:12:08 +00:00
|
|
|
// We will subscribe to some generators.
|
|
|
|
this._rpcManager.services.data.subscribe((_) => {
|
|
|
|
// Clear the Mapping of the Generators
|
|
|
|
_this._mappingOfRemoteDispatchersAndGenerators.clear();
|
|
|
|
for (const [
|
|
|
|
dispatcher,
|
|
|
|
services,
|
|
|
|
] of _this._rpcManager.services.originalData.entries()) {
|
|
|
|
// Filter the Generators based on the existing services
|
|
|
|
const generators = services.services.filter((svc) =>
|
|
|
|
svc.startsWith("nope.core.constructors.")
|
|
|
|
);
|
|
|
|
|
|
|
|
// If the Dispatcher has a generator we will add it.
|
|
|
|
if (generators.length) {
|
|
|
|
_this._mappingOfRemoteDispatchersAndGenerators.set(
|
|
|
|
dispatcher,
|
|
|
|
generators
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the Generators
|
|
|
|
_this.constructors.update();
|
|
|
|
});
|
|
|
|
|
|
|
|
if (this._logger) {
|
2022-01-16 19:38:45 +00:00
|
|
|
this._logger.info("core.instance-manager online");
|
2022-01-07 17:12:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.reset();
|
|
|
|
this._init().catch((error) => {
|
|
|
|
if (_this._logger) {
|
|
|
|
_this._logger.error("Failed to intialize the Dispatcher", error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the Available Instances
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
protected _sendAvailableInstances(): void {
|
|
|
|
const _this = this;
|
|
|
|
// Update the Instances provided by this module.
|
2022-01-16 19:38:45 +00:00
|
|
|
this._communicator.emit("InstancesChanged", {
|
2022-01-07 17:12:08 +00:00
|
|
|
dispatcher: this._id,
|
|
|
|
// We will send the descriptions.
|
|
|
|
instances: Array.from(this._internalInstances).map((identifier) =>
|
|
|
|
// Generate the Module Description for every identifier:
|
|
|
|
_this._instances.get(identifier).instance.toDescription()
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal Function, used to initialize the Dispatcher.
|
|
|
|
* It subscribes to the "Messages" of the communicator.
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @memberof NopeInstanceManager
|
|
|
|
*/
|
|
|
|
protected async _init(): Promise<void> {
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Wait until the Element is connected.
|
|
|
|
await this._communicator.connected.waitFor();
|
|
|
|
await this._statusManager.ready.waitFor();
|
|
|
|
await this._rpcManager.ready.waitFor();
|
|
|
|
|
|
|
|
this.registerInternalWrapperGenerator(
|
|
|
|
"*",
|
|
|
|
async (dispather, description) => {
|
|
|
|
const mod = new NopeGenericWrapper(
|
|
|
|
dispather,
|
|
|
|
_this._generateObservable
|
|
|
|
);
|
|
|
|
await mod.fromDescription(description, "overwrite");
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// We will use our status-manager to listen to changes.
|
|
|
|
this._statusManager.dispatchers.onChange.subscribe((changes) => {
|
|
|
|
if (changes.added.length) {
|
|
|
|
// If there are dispatchers online,
|
|
|
|
// We will emit our available services.
|
|
|
|
_this._sendAvailableInstances();
|
|
|
|
}
|
|
|
|
if (changes.removed.length) {
|
|
|
|
// Remove the dispatchers.
|
|
|
|
changes.removed.map((item) => _this.removeDispatcher(item.id));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Listen to newly created instances.
|
2022-01-16 19:38:45 +00:00
|
|
|
await this._communicator.on("InstancesChanged", (message) => {
|
2022-01-07 17:12:08 +00:00
|
|
|
// Store the instances:
|
|
|
|
_this._mappingOfRemoteDispatchersAndInstances.set(
|
|
|
|
message.dispatcher,
|
|
|
|
message
|
|
|
|
);
|
|
|
|
// Update the Mapping:
|
|
|
|
_this.instances.update();
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
if (_this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
// If there is a Logger:
|
|
|
|
_this._logger.debug(
|
|
|
|
'Remote Dispatcher "' +
|
|
|
|
message.dispatcher +
|
|
|
|
'" updated its available instances'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (this._logger) {
|
2022-01-16 19:38:45 +00:00
|
|
|
this._logger.info("core.instance-manager", this._id, "initialized");
|
2022-01-07 17:12:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.ready.setContent(true);
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
/**
|
|
|
|
* Helper to get the corresponding Service name
|
|
|
|
* @param {string} name name
|
|
|
|
* @param {"constructor" | "dispose"} type The desired type of the requested service name
|
|
|
|
* @returns {string} the name.
|
|
|
|
*/
|
2022-01-07 17:12:08 +00:00
|
|
|
protected _getServiceName(name: string, type: "constructor" | "dispose") {
|
|
|
|
switch (type) {
|
|
|
|
case "constructor":
|
|
|
|
return `nope${SPLITCHAR}core${SPLITCHAR}constructor.${name}`;
|
|
|
|
case "dispose":
|
|
|
|
return `nope${SPLITCHAR}core${SPLITCHAR}destructor.${name}`;
|
|
|
|
default:
|
|
|
|
throw Error("The given type is not correct.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function, that will extract the information of the instance and the
|
|
|
|
* the providing dispatcher.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {string} identifier The identifier of instance
|
|
|
|
* @return {*} {(INopeModuleDescription & { dispatcher: IDispatcherInfo })}
|
|
|
|
* @memberof nopeDispatcher
|
|
|
|
*/
|
|
|
|
protected _getInstanceInfo(identifier: string):
|
|
|
|
| {
|
|
|
|
description: INopeModuleDescription;
|
|
|
|
dispatcher: INopeStatusInfo;
|
|
|
|
}
|
|
|
|
| undefined {
|
|
|
|
// First check if the instance exists.
|
|
|
|
if (!this.instanceExists(identifier, false)) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define the return type
|
|
|
|
const ret: {
|
|
|
|
description: INopeModuleDescription;
|
|
|
|
dispatcher: INopeStatusInfo;
|
|
|
|
} = {} as any;
|
|
|
|
|
|
|
|
// First we check if we are taking care of an internal instance, if so
|
|
|
|
// we will use this instance to enrich the description, otherwise, we
|
|
|
|
// will look in the external instances.
|
|
|
|
if (this._instances.has(identifier)) {
|
|
|
|
ret.description = this._instances
|
|
|
|
.get(identifier)
|
|
|
|
.instance.toDescription();
|
|
|
|
} else {
|
|
|
|
// Now extract teh
|
|
|
|
for (const {
|
|
|
|
instances,
|
|
|
|
} of this._mappingOfRemoteDispatchersAndInstances.values()) {
|
|
|
|
for (const instance of instances) {
|
|
|
|
if (instance.identifier == identifier) {
|
|
|
|
ret.description = instance;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Additionally add some information about the used dispatcher.
|
|
|
|
ret.dispatcher = this.getManagerOfInstance(identifier);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to remove a dispatcher.
|
|
|
|
*
|
|
|
|
* @author M.Karkowski
|
|
|
|
* @param {string} dispatcher
|
|
|
|
* @memberof NopeRpcIn
|
|
|
|
*/
|
|
|
|
public removeDispatcher(dispatcher: string): void {
|
|
|
|
// Delete the Generators of the Instances.
|
|
|
|
this._mappingOfRemoteDispatchersAndInstances.delete(dispatcher);
|
|
|
|
this.instances.update();
|
|
|
|
|
|
|
|
// Now we need to delete every Instance of the dispatcher.
|
|
|
|
// All open calls will be handeled by the RpcManager.
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// this.cancelRunningTasksOfDispatcher(
|
|
|
|
// dispatcher,
|
|
|
|
// new Error(
|
|
|
|
// "Dispatcher has been removed! Tasks cannot be executed any more."
|
|
|
|
// )
|
|
|
|
// );
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public async registerConstructor<I extends INopeModule>(
|
|
|
|
identifier: string,
|
|
|
|
cb: TConstructorCallback<I>
|
|
|
|
): Promise<void> {
|
|
|
|
const _this = this;
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'Adding instance generator for "' +
|
|
|
|
identifier +
|
|
|
|
'" to external Generators. Other Elements can now create instances of this type.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const _cb = await this._rpcManager.registerService(
|
|
|
|
async (data: IInstanceCreationMsg) => {
|
|
|
|
// Check if an instance exists or not.
|
|
|
|
// if not => create an instance an store it.
|
|
|
|
if (!_this._instances.has(data.identifier)) {
|
|
|
|
const hashable = [data.identifier, data.params, data.type];
|
|
|
|
const hash = "hashedSettings";
|
|
|
|
|
|
|
|
// It might happen, that an instance is requested multiple times.
|
|
|
|
// therefore we have to make shure, we wont create them multiple times:
|
|
|
|
// We will test it by using the "_internalInstances" set
|
|
|
|
if (!_this._initializingInstance.has(data.identifier)) {
|
|
|
|
// Mark the Instance as available.
|
|
|
|
_this._initializingInstance.set(data.identifier, hash);
|
|
|
|
|
|
|
|
// Create an Instance
|
|
|
|
const _instance = await cb(_this._core, data.identifier);
|
|
|
|
|
|
|
|
// Make shure the Data is expressed as Array.
|
|
|
|
if (!Array.isArray(data.params)) {
|
|
|
|
data.params = [data.params];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the instance with the parameters.
|
|
|
|
await _instance.init(...data.params);
|
|
|
|
|
|
|
|
// A Function is registered, taking care of removing
|
|
|
|
// an instances, if it isnt needed any more.
|
|
|
|
_this._rpcManager.registerService(
|
2022-01-10 06:52:05 +00:00
|
|
|
async (_data: IDisposeInstanceMsg) => {
|
2022-01-07 17:12:08 +00:00
|
|
|
if (_this._instances.get(data.identifier)?.usedBy) {
|
|
|
|
// Get the Index of the dispatcher, which is using
|
|
|
|
// the element
|
|
|
|
const idx = _this._instances
|
|
|
|
.get(data.identifier)
|
|
|
|
.usedBy.indexOf(_data.dispatcherId);
|
|
|
|
|
|
|
|
if (idx > -1) {
|
|
|
|
_this._instances.get(data.identifier).usedBy.splice(idx, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
_this._instances.get(data.identifier).usedBy.length == 0
|
|
|
|
) {
|
|
|
|
// Unmark as internal instance
|
|
|
|
_this._internalInstances.delete(data.identifier);
|
|
|
|
|
|
|
|
// Remove the Instance.
|
|
|
|
await _instance.dispose();
|
|
|
|
|
|
|
|
// Delete the Entry.
|
|
|
|
_this._instances.delete(data.identifier);
|
|
|
|
|
|
|
|
// Remove the Function itself
|
|
|
|
_this._rpcManager.unregisterService(
|
|
|
|
_this._getServiceName(data.identifier, "dispose")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: _this._getServiceName(data.identifier, "dispose"),
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Store the Instance.
|
|
|
|
_this._instances.set(data.identifier, {
|
|
|
|
instance: _instance,
|
|
|
|
usedBy: [data.dispatcherId],
|
|
|
|
});
|
|
|
|
|
|
|
|
_this._internalInstances.add(data.identifier);
|
|
|
|
|
|
|
|
// Update the available instances:
|
|
|
|
_this._sendAvailableInstances();
|
|
|
|
|
|
|
|
// Make shure, we remove this instance.hash
|
|
|
|
_this._initializingInstance.delete(data.identifier);
|
|
|
|
} else if (_this._initializingInstance.get(data.identifier) != hash) {
|
|
|
|
throw Error(
|
|
|
|
"Providing different Parameters for the same Identifier"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
// Check if the Instance is ready.
|
|
|
|
let firstHint = true;
|
|
|
|
await waitFor(
|
|
|
|
() => {
|
|
|
|
if (firstHint) {
|
|
|
|
_this._logger.warn(
|
|
|
|
`Parallel request for the same Instance "${data.identifier}" => Waiting until the Instance has been initialized`
|
|
|
|
);
|
|
|
|
firstHint = false;
|
|
|
|
}
|
|
|
|
return _this._instances.has(data.identifier);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
testFirst: true,
|
|
|
|
delay: 100,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If an Element exists => Add the Element.
|
|
|
|
_this._instances.get(data.identifier).usedBy.push(data.dispatcherId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define the Response.
|
|
|
|
const response: IInstanceDescriptionMsg = {
|
|
|
|
description: _this._instances
|
|
|
|
.get(data.identifier)
|
|
|
|
.instance.toDescription(),
|
|
|
|
type: data.type,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Send the Response
|
|
|
|
return response;
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// We will add the Name to our service.
|
|
|
|
id: this._getServiceName(identifier, "constructor"),
|
|
|
|
// We dont want to add a prefix
|
|
|
|
addNopeServiceIdPrefix: false,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
// Store the Generator.
|
|
|
|
this._registeredConstructors.set(identifier, _cb);
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public async unregisterConstructor(identifier: string): Promise<void> {
|
|
|
|
if (this._registeredConstructors.has(identifier)) {
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'Removing instance generator for "' +
|
|
|
|
identifier +
|
|
|
|
'" from external Generators. Other Elements cant create instances of this type anymore.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We will just unregister the service from our
|
|
|
|
// system. Therefore we just use the rpcManager
|
|
|
|
await this._rpcManager.unregisterService(
|
|
|
|
this._registeredConstructors.get(identifier)
|
|
|
|
);
|
|
|
|
this._registeredConstructors.delete(identifier);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public registerInternalWrapperGenerator<I extends INopeModule>(
|
|
|
|
identifier: string,
|
|
|
|
cb: TGenerateWrapperCallback<I>
|
|
|
|
): void {
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'Adding instance generator for "' +
|
|
|
|
identifier +
|
|
|
|
'" as internal Generator. This Generator wont be used externally.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._internalWrapperGenerators.set(identifier, cb);
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public unregisterInternalWrapperGenerator(identifier: string): void {
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'Rmoving instance generator for "' +
|
|
|
|
identifier +
|
|
|
|
'" from internal Generator. The sytem cant create elements of this type any more.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._internalWrapperGenerators.delete(identifier);
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public instanceExists(identifier: string, externalOnly = true): boolean {
|
|
|
|
if (externalOnly) {
|
|
|
|
return this._externalInstances.has(identifier);
|
|
|
|
} else {
|
|
|
|
this._externalInstances.has(identifier) ||
|
|
|
|
this._instances.has(identifier);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public getManagerOfInstance(identifier: string): INopeStatusInfo | undefined {
|
|
|
|
// Check if the instance exists in general
|
|
|
|
if (!this.instanceExists(identifier, false)) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// First we will check if the instance is available internally.
|
|
|
|
if (this._internalInstances.has(identifier)) {
|
|
|
|
return this._statusManager.info;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If that isnt the case, we will check all dispatchers and search the instance.
|
|
|
|
for (const [
|
|
|
|
dispatcher,
|
|
|
|
{ instances },
|
|
|
|
] of this._mappingOfRemoteDispatchersAndInstances.entries()) {
|
|
|
|
for (const instance of instances) {
|
|
|
|
if (instance.identifier == identifier) {
|
|
|
|
return this._statusManager.getStatus(dispatcher);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public getInstanceDescription(
|
|
|
|
instanceIdentifier: string
|
|
|
|
): false | INopeModuleDescription {
|
|
|
|
if (this._instances.has(instanceIdentifier)) {
|
|
|
|
return this._instances.get(instanceIdentifier).instance.toDescription();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public constructorExists(typeIdentifier: string): boolean {
|
|
|
|
return this.constructors.data.getContent().includes(typeIdentifier);
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public async createInstance<I extends INopeModule = IGenericNopeModule>(
|
|
|
|
description: Partial<IInstanceCreationMsg>,
|
|
|
|
options: {
|
|
|
|
selector?: ValidSelectorFunction;
|
|
|
|
assignmentValid?: TValidAsssignmentChecker;
|
|
|
|
} = {}
|
|
|
|
): Promise<I> {
|
|
|
|
// Define the Default Description
|
|
|
|
// which will lead to an error.
|
|
|
|
const _defDescription: IInstanceCreationMsg = {
|
|
|
|
dispatcherId: this._id,
|
|
|
|
identifier: "error",
|
|
|
|
params: [],
|
|
|
|
type: "unkown",
|
|
|
|
};
|
|
|
|
|
|
|
|
// Assign the provided Description
|
|
|
|
const _description = Object.assign(_defDescription, description, {
|
|
|
|
dispatcherId: this._id,
|
|
|
|
}) as IInstanceCreationMsg;
|
|
|
|
|
|
|
|
// Check if the description is complete
|
|
|
|
if (
|
|
|
|
_defDescription.type === "unkown" ||
|
|
|
|
_description.identifier === "error"
|
|
|
|
) {
|
|
|
|
throw Error(
|
|
|
|
'Please Provide at least a "type" and "identifier" in the paremeters'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'Requesting an Instance of type: "' +
|
|
|
|
_defDescription.type +
|
|
|
|
'" with the identifier: "' +
|
|
|
|
_defDescription.identifier +
|
|
|
|
'"'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
let _type = _description.type;
|
|
|
|
|
|
|
|
if (!this._internalWrapperGenerators.has(_type)) {
|
|
|
|
// No default type is present for a remote
|
|
|
|
// => assing the default type which is "*""
|
|
|
|
_type = "*";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.constructorExists(_description.type)) {
|
|
|
|
throw Error(
|
|
|
|
'Generator "' + _description.type + '" isnt present in the network!'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._internalWrapperGenerators.has(_type)) {
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
'No instance with the identifiert: "' +
|
|
|
|
_defDescription.identifier +
|
|
|
|
'" found, but an internal generator is available. Using the internal one for creating the instance and requesting the "real" instance externally'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now test if there is allready an instance with this name and type.
|
|
|
|
// If so, we check if we have the correct type etc. Additionally we
|
|
|
|
// try to extract its dispatcher-id and will use that as selector
|
|
|
|
// to allow the function be called.
|
|
|
|
const _instanceDetails = this._getInstanceInfo(_description.identifier);
|
|
|
|
if (
|
|
|
|
_instanceDetails !== undefined &&
|
|
|
|
_instanceDetails?.description.type !== _description.type
|
|
|
|
) {
|
|
|
|
throw Error(
|
|
|
|
"There exists an Instance named: '" +
|
|
|
|
_description.identifier +
|
|
|
|
"' but it uses a different type. Requested type: '" +
|
|
|
|
_description.type +
|
|
|
|
"', given type: '" +
|
|
|
|
_instanceDetails?.description.type +
|
|
|
|
"'"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const usedDispatcher = _instanceDetails?.dispatcher.id;
|
|
|
|
|
|
|
|
if (usedDispatcher && options.assignmentValid) {
|
|
|
|
// If we have an dispatcher, which was been used to create the instance,
|
|
|
|
// we have to check, the selected Dispatcher Matches our criteria.
|
|
|
|
if (
|
|
|
|
!(await options.assignmentValid(
|
|
|
|
_instanceDetails.description,
|
|
|
|
_instanceDetails.dispatcher
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
throw Error("Assignment is invalid.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const definedInstance =
|
|
|
|
await this._rpcManager.performCall<IInstanceDescriptionMsg>(
|
|
|
|
// Extract our Service Name:
|
|
|
|
this._getServiceName(_description.type, "constructor"),
|
|
|
|
// We will use our Description as Parameter.
|
|
|
|
[_description],
|
|
|
|
// Additionally we share the options:
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
`Received a description for the instance "${definedInstance.description.identifier}"`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the Wrapper for our instance.
|
|
|
|
const wrapper = (await this._internalWrapperGenerators.get(_type)(
|
|
|
|
this._core,
|
|
|
|
definedInstance.description
|
|
|
|
)) as I;
|
|
|
|
|
2022-01-16 19:38:45 +00:00
|
|
|
if (this._logger?.enabledFor(DEBUG)) {
|
2022-01-07 17:12:08 +00:00
|
|
|
this._logger.debug(
|
|
|
|
`Created a Wrapper for the instance "${definedInstance.description.identifier}"`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the Instances.
|
|
|
|
this._instances.set(_description.identifier, {
|
|
|
|
instance: wrapper,
|
|
|
|
usedBy: [_description.dispatcherId],
|
|
|
|
});
|
|
|
|
|
|
|
|
return wrapper;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw Error("No internal generator Available!");
|
|
|
|
} catch (e) {
|
|
|
|
if (this._logger) {
|
|
|
|
this._logger.error(
|
|
|
|
"During creating an Instance, the following error Occurd"
|
|
|
|
);
|
|
|
|
this._logger.error(e);
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public async registerInstance<I extends INopeModule>(
|
|
|
|
instance: I
|
|
|
|
): Promise<I> {
|
|
|
|
// Store the Instances.
|
|
|
|
this._instances.set(instance.identifier, {
|
|
|
|
instance,
|
|
|
|
usedBy: [],
|
|
|
|
manual: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public async deleteInstance<I extends INopeModule>(
|
|
|
|
instance: I | string,
|
|
|
|
preventSendingUpdate = false
|
|
|
|
): Promise<boolean> {
|
|
|
|
// Block to find the instance.
|
|
|
|
// Based on the property (string or instance)
|
|
|
|
// the corresponding instance object has to be select.
|
|
|
|
let _instance: { instance: INopeModule; usedBy: Array<string> };
|
|
|
|
if (typeof instance === "string") {
|
|
|
|
_instance = this._instances.get(instance);
|
|
|
|
} else {
|
|
|
|
for (const data of this._instances.values()) {
|
|
|
|
if (instance == data.instance) {
|
|
|
|
_instance = data;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the instance has been found => delete the instance.
|
|
|
|
if (_instance) {
|
|
|
|
_instance.usedBy.pop();
|
|
|
|
|
|
|
|
if (_instance.usedBy.length === 0) {
|
2022-01-10 06:52:05 +00:00
|
|
|
const params: IDisposeInstanceMsg = {
|
2022-01-07 17:12:08 +00:00
|
|
|
dispatcherId: this._id,
|
|
|
|
identifier: _instance.instance.identifier,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Call the corresponding Dispose Function for the "real" instance
|
|
|
|
// All other elements are just accessors.
|
|
|
|
await this._rpcManager.performCall(
|
|
|
|
"instance_dispose_" + _instance.instance.identifier,
|
|
|
|
[params]
|
|
|
|
);
|
|
|
|
|
|
|
|
// Delete the Identifier
|
|
|
|
this._instances.delete(_instance.instance.identifier);
|
|
|
|
|
|
|
|
// Check if an update should be emitted or not.
|
|
|
|
if (!preventSendingUpdate) {
|
|
|
|
// Update the Instances provided by this module.
|
|
|
|
this._sendAvailableInstances();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dispose the Handler;
|
|
|
|
await _instance.instance.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
|
|
|
public async getInstancesOfType<I extends INopeModule>(type: string) {
|
|
|
|
const indentifier = this.instances.data
|
|
|
|
.getContent()
|
|
|
|
.filter((item) => item.type == type)
|
|
|
|
.map((item) => item.identifier);
|
|
|
|
|
|
|
|
const promises: Promise<I>[] = [];
|
|
|
|
|
|
|
|
for (const identifier of indentifier) {
|
|
|
|
promises.push(
|
|
|
|
this.createInstance({
|
|
|
|
identifier,
|
|
|
|
type,
|
|
|
|
params: [],
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait to generate all Instances.
|
|
|
|
const result = await Promise.all(promises);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See interface description
|
2022-01-07 17:12:08 +00:00
|
|
|
public reset(): void {
|
|
|
|
this._mappingOfRemoteDispatchersAndGenerators = new Map();
|
|
|
|
this._mappingOfRemoteDispatchersAndInstances = new Map();
|
|
|
|
|
|
|
|
this._internalWrapperGenerators = new Map();
|
|
|
|
this._registeredConstructors = new Map();
|
|
|
|
|
|
|
|
// If Instances Exists => Delete them.
|
|
|
|
if (this._instances) {
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Dispose all Instances.
|
|
|
|
for (const [name, instance] of this._instances.entries()) {
|
|
|
|
// Remove the Instance.
|
|
|
|
this.deleteInstance(name, true).catch((e) => {
|
|
|
|
if (_this._logger) {
|
|
|
|
_this._logger.error('Failed Removing Instance "' + name + '"');
|
|
|
|
_this._logger.error(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._instances = new Map();
|
|
|
|
this._externalInstances = new Map();
|
|
|
|
this._internalInstances = new Set();
|
|
|
|
this._initializingInstance = new Map();
|
|
|
|
this._externalInstancesNames = new Set();
|
|
|
|
|
|
|
|
if (this._communicator.connected.getContent()) {
|
|
|
|
// Update the Instances
|
|
|
|
this._sendAvailableInstances();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 06:52:05 +00:00
|
|
|
// See interface description
|
|
|
|
public async dispose() {
|
|
|
|
this.instances.dispose();
|
|
|
|
}
|
2022-01-07 17:12:08 +00:00
|
|
|
}
|