def load(self, cfg, compositeLookup): """ load graph from config dictionary (inverse operation of save(...)) :param cfg: dictionary loaded from json file :return: None """ # apply subconfig gui state if "_guiState" in cfg and len(cfg["_guiState"]) > 0: guistateCC = self._propertyCollection.getChildCollection( "_guiState") for k in cfg["_guiState"]: PropertyCollectionImpl(k, guistateCC, cfg["_guiState"][k]) for n in cfg["nodes"]: if not n["library"].startswith("composite://"): p = PropertyCollectionImpl(n["name"], self._propertyCollection, n["properties"]) # apply node gui state nextP = PropertyCollectionImpl("_nexxT", p, {"thread": n["thread"]}) logger.debug("loading: subconfig %s / node %s -> thread: %s", self._name, n["name"], n["thread"]) tmp = self._graph.addNode(n["library"], n["factoryFunction"], suggestedName=n["name"]) if tmp != n["name"]: raise NexTInternalError( "addNode(...) has set unexpected name for node.") else: # composite node handling if n["library"] == "composite://port": # the special nodes are already there, nothing to do here pass elif n["library"] == "composite://ref": name = n["factoryFunction"] cf = compositeLookup(name) tmp = self._graph.addNode(cf, "compositeNode", suggestedName=n["name"]) if tmp != n["name"]: raise NexTInternalError( "addNode(...) has set unexpected name for node.") for dip in n["dynamicInputPorts"]: self._graph.addDynamicInputPort(n["name"], dip) for dop in n["dynamicOutputPorts"]: self._graph.addDynamicOutputPort(n["name"], dop) # make sure that the filter is instantiated and the port information is updated immediately self._graph.getMockup(n["name"]).createFilterAndUpdate() for c in cfg["connections"]: contuple = self.connectionStringToTuple(c) self._graph.addConnection(*contuple)
def __init__(self, name): """ Creates a NexTThread instance with a name. If this is not the main thread, create a corresponding QThread and start it (i.e., the event loop). :param name: name of the thread """ super().__init__() self._filters = {} self._filter2name = {} self._mockups = {} self._name = name try: self._profsrv = Services.getService("Profiling") if hasattr(self._profsrv, "data") and self._profsrv.data() is None: self._profsrv = None except KeyError: self._profsrv = None if not self.thread() is QCoreApplication.instance().thread(): raise NexTInternalError("unexpected thread") if name == "main": self._qthread = QCoreApplication.instance().thread() self._qthread.setObjectName(name) else: self._qthread = self.ThreadWithCoverage(parent=self) self._qthread.setObjectName(name) self._qthread.start() self.moveToThread(self._qthread) self.cleanUpCalled = False
def preStateTransition(self, operation): """ State transitions might be explicitely set all filters to the operation state before executing the operation on the filters. This method makes sure that the state transitions are sane. :param operation: The FilterState operation. :return: None """ logger.internal("Performing pre-state transition: %s", FilterState.state2str(operation)) self._assertMyThread() if self.getPlugin() is None or (useCImpl and self.getPlugin().data() is None): raise NexTInternalError("Cannot perform state transitions on uninitialized plugin") operations = { FilterState.CONSTRUCTING: (None, FilterState.CONSTRUCTED, None), FilterState.INITIALIZING: (FilterState.CONSTRUCTED, FilterState.INITIALIZED, self.getPlugin().onInit), FilterState.OPENING: (FilterState.INITIALIZED, FilterState.OPENED, self.getPlugin().onOpen), FilterState.STARTING: (FilterState.OPENED, FilterState.ACTIVE, self.getPlugin().onStart), FilterState.STOPPING: (FilterState.ACTIVE, FilterState.OPENED, self.getPlugin().onStop), FilterState.CLOSING: (FilterState.OPENED, FilterState.INITIALIZED, self.getPlugin().onClose), FilterState.DEINITIALIZING: (FilterState.INITIALIZED, FilterState.CONSTRUCTED, self.getPlugin().onDeinit), FilterState.DESTRUCTING: (FilterState.CONSTRUCTED, None, None), } fromState, toState, function = operations[operation] if self._state != fromState: raise FilterStateMachineError(self._state, operation) self._state = operation
def assertMainThread(): """ assert that function is called in main thread, otherwise, a NexTInternalError is raised :return: None """ if not isMainThread(): raise NexTInternalError("Non thread-safe function is called in unexpected thread.")
def waitForSignal(signal, callback=None, timeout=None): """ Waits for the given signal. If a callback is given, it will be called with the signal's arguments until the return value of the callback evaluates to true. If a timeout is given (in seconds), a TimeoutError will be thrown after the time has elapsed. :param signal: a Qt signal to be waited for, suitable for slot connections. :param callback: a callable called :param timeout: an optional timeout in seconds. :return: None """ _received = False _sigArgs = None def _slot(*args, **kw): nonlocal _received, _sigArgs _sigArgs = args if callback is None: _received = True else: if callback(*args, **kw): _received = True if not signal.connect(_slot, Qt.QueuedConnection): raise NexTInternalError("cannot connect the signal.") t0 = time.perf_counter() while not _received: QCoreApplication.processEvents() if timeout is not None and time.perf_counter() - t0 > timeout: signal.disconnect(_slot) raise TimeoutError() signal.disconnect(_slot) return _sigArgs
def applyLoadedConfig(self, loadedFromConfig): """ applies the loaded configuration after the instance has been already created. This is used for guiState items. :param loadedFromConfig: dictionary loaded from json file :return: None """ if len(self._loadedFromConfig) > 0: raise NexTInternalError("Expected that no loaded config is present.") self._accessed = False self._loadedFromConfig = loadedFromConfig
def __init__(self, name, parentPropColl, loadedFromConfig=None): PropertyCollection.__init__(self) assertMainThread() self._properties = {} self._accessed = False # if no access to properties has been made, we stick with configs from config file. self._loadedFromConfig = loadedFromConfig if loadedFromConfig is not None else {} self._propertyMutex = QMutex(QMutex.Recursive) if parentPropColl is not None: if not isinstance(parentPropColl, PropertyCollectionImpl): raise NexTInternalError("parentPropColl should always be a property collection instance but it isn't") parentPropColl.addChild(name, self)
def subConfigByNameAndTye(self, name, typeid): """ Return the subconfiguration with the given name and type :param name: a string :param typeid: either CONFIG_TYPE_APPLICATION or CONFIG_TYPE_COMPOSITE :return: a SubConfiguration instance """ if typeid == self.CONFIG_TYPE_APPLICATION: return self.applicationByName(name) if typeid == self.CONFIG_TYPE_COMPOSITE: return self.compositeFilterByName(name) raise NexTInternalError("unknown typeid %s" % typeid)
def applicationByName(self, name): """ Search for the application with the given name :param name: a string :return: an Application instance """ match = [app for app in self._applications if app.getName() == name] if len(match) == 1: return match[0] if len(match) == 0: raise NodeNotFoundError(name) raise NexTInternalError("non unique name %s" % name)
def compositeFilterByName(self, name): """ Search for the composite filter with the given name :param name: a string :return: a CompositeFilter instance """ match = [cf for cf in self._compositeFilters if cf.getName() == name] if len(match) == 1: return match[0] if len(match) == 0: raise NodeNotFoundError(name) raise NexTInternalError("non unique name %s" % name)
def _receiveSync(self, dataSample): """ Called from framework only and implements the synchronous receive mechanism. :param dataSample: the transmitted DataSample instance :return: None """ if not QThread.currentThread() is self.thread(): raise NexTInternalError( "InputPort.receiveSync has been called from an unexpected thread." ) self._addToQueue(dataSample) self._transmit()
def adaptLibAndFactory(lib, factory): if not isinstance(lib, str): if factory in ["CompositeInputNode", "CompositeOutputNode"]: ncfg["library"] = "composite://port" ncfg["factoryFunction"] = factory[:-len("Node")] elif factory in ["compositeNode"]: ncfg["library"] = "composite://ref" ncfg["factoryFunction"] = lib.getName() else: raise NexTInternalError("Unexpected factory function '%s'" % factory) else: ncfg["library"] = lib ncfg["factoryFunction"] = factory return lib, factory
def __init__(self, name, configuration): super().__init__(name, configuration) self._configuration = configuration _compositeInputNode = self._graph.addNode(CompositeFilter, "CompositeInputNode", "CompositeInput") _compositeOutputNode = self._graph.addNode(CompositeFilter, "CompositeOutputNode", "CompositeOutput") if _compositeInputNode != "CompositeInput" or _compositeOutputNode != "CompositeOutput": raise NexTInternalError("unexpected node names.") # prevent renaming and deletion of these special nodes self._graph.protect("CompositeInput") self._graph.protect("CompositeOutput") configuration.addComposite(self)
def shutdown(self): """ Transfer graph to DESTRUCTED state. :return: None """ assertMainThread() if self._state == FilterState.ACTIVE: self.stop() # while this is similar to code in FilterEnvironment, the lines here refer to applications # and the lines in FilterEnvironment refer to filters. if self._state == FilterState.OPENED: self.close() if self._state == FilterState.INITIALIZED: self.deinit() if self._state == FilterState.CONSTRUCTED: self.destruct() if not self._state == FilterState.DESTRUCTED: raise NexTInternalError("Unexpected state '%s' after shutdown." % FilterState.state2str(self._state))
def _stateTransition(self, operation): """ Perform state transition according to operation. :param operation: The FilterState operation. :return: None """ logger.internal("Performing state transition: %s", FilterState.state2str(operation)) self._assertMyThread() if self.getPlugin() is None or (useCImpl and self.getPlugin().data() is None): raise NexTInternalError("Cannot perform state transitions on uninitialized plugin") operations = { FilterState.CONSTRUCTING: (None, FilterState.CONSTRUCTED, None), FilterState.INITIALIZING: (FilterState.CONSTRUCTED, FilterState.INITIALIZED, self.getPlugin().onInit), FilterState.OPENING: (FilterState.INITIALIZED, FilterState.OPENED, self.getPlugin().onOpen), FilterState.STARTING: (FilterState.OPENED, FilterState.ACTIVE, self.getPlugin().onStart), FilterState.STOPPING: (FilterState.ACTIVE, FilterState.OPENED, self.getPlugin().onStop), FilterState.CLOSING: (FilterState.OPENED, FilterState.INITIALIZED, self.getPlugin().onClose), FilterState.DEINITIALIZING: (FilterState.INITIALIZED, FilterState.CONSTRUCTED, self.getPlugin().onDeinit), FilterState.DESTRUCTING: (FilterState.CONSTRUCTED, None, None), } fromState, toState, function = operations[operation] # filters must be either in fromState or already in operation state (if preStateTransition has been used) if self._state not in (fromState, operation): raise FilterStateMachineError(self._state, operation) self._state = operation try: function() except Exception as e: # pylint: disable=broad-except # What should be done on errors? # 1. inhibit state transition to higher state # pro: prevent activation of not properly intialized filter # con: some actions of onInit might already be executed, we really want to call onDeinit for cleanup # 2. ignore the exception and perform state transition anyways # pro/con is inverse to 1 # 3. introduce an error state # pro: filters in error state are clearly identifyable and prevented from being used # con: higher complexity, cleanup issues, ... # --> for now we use 2. self._state = fromState logger.exception("Exception while executing operation %s of filter %s", FilterState.state2str(operation), self.propertyCollection().objectName()) self._state = toState
def _receiveAsync(self, dataSample, semaphore): """ Called from framework only and implements the asynchronous receive mechanism using a semaphore. :param dataSample: the transmitted DataSample instance :param semaphore: a QSemaphore instance :return: None """ if not QThread.currentThread() is self.thread(): raise NexTInternalError( "InputPort.receiveAsync has been called from an unexpected thread." ) self._addToQueue(dataSample) if not self._interthreadDynamicQueue: # usual behaviour semaphore.release(1) self._transmit() else: if semaphore not in self._semaphoreN: self._semaphoreN[semaphore] = 1 delta = self._semaphoreN[semaphore] - len(self.queue) if delta <= 0: # the semaphore's N is too small semaphore.release(1 - delta) self._semaphoreN[semaphore] += -delta logger.internal("delta = %d: semaphoreN = %d", delta, self._semaphoreN[semaphore]) self._transmit() elif delta > 0: # first acquire is done by caller self._semaphoreN[semaphore] -= 1 # the semaphore's N is too large, try acquires to reduce the size for i in range(1, delta): if semaphore.tryAcquire(1): self._semaphoreN[semaphore] -= 1 else: break logger.internal("delta = %d: semaphoreN = %d", delta, self._semaphoreN[semaphore]) self._transmit()