def __init__(self, state_machine, meta=None, load_meta_data=True): """Constructor """ MetaModel.__init__(self) # pass columns as separate parameters assert isinstance(state_machine, StateMachine) self.state_machine = state_machine self.state_machine_id = state_machine.state_machine_id root_state = self.state_machine.root_state if isinstance(root_state, ContainerState): self.root_state = ContainerStateModel( root_state, parent=self, load_meta_data=load_meta_data) else: self.root_state = StateModel(root_state, parent=self, load_meta_data=load_meta_data) if isinstance(meta, Vividict): self.meta = meta else: self.meta = Vividict() # ongoing_complex_actions is updated by ComplexActionObserver -> secure encapsulated observation # and made observable by state machine model here self.ongoing_complex_actions = {} self.complex_action_observer = ComplexActionObserver(self) self.meta_signal = Signal() self.state_meta_signal = Signal() self.action_signal = Signal() self.state_action_signal = Signal() self.sm_selection_changed_signal = Signal() self.destruction_signal = Signal() self.temp = Vividict() if load_meta_data: self.load_meta_data(recursively=False) self.selection = Selection(self.sm_selection_changed_signal) self.storage_lock = threading.Lock( ) # lock can not be substituted by the state machine lock -> maybe because it is a RLock self.history = None if global_gui_config.get_config_value('HISTORY_ENABLED'): from rafcon.gui.models.modification_history import ModificationsHistoryModel self.history = ModificationsHistoryModel(self) else: logger.info("The modification history is disabled") self.auto_backup = None if global_gui_config.get_config_value('AUTO_BACKUP_ENABLED'): from rafcon.gui.models.auto_backup import AutoBackupModel self.auto_backup = AutoBackupModel(self) self.root_state.register_observer(self) self.register_observer(self)
def root_state_assign(self, model, prop_name, info): if info.method_name != 'root_state': return if self.suppress_new_root_state_model_one_time: self.suppress_new_root_state_model_one_time = False return # print("ASSIGN ROOT_STATE", model, prop_name, info) try: self.root_state.unregister_observer(self) except KeyError: pass if isinstance(self.state_machine.root_state, ContainerState): # could not be a LibraryState self.root_state = ContainerStateModel(self.state_machine.root_state) else: assert not isinstance(self.state_machine.root_state, LibraryState) self.root_state = StateModel(self.state_machine.root_state) self.root_state.register_observer(self)
class StateMachineModel(MetaModel, Hashable): """This model class manages a :class:`rafcon.core.state_machine.StateMachine` The model class is part of the MVC architecture. It holds the data to be shown (in this case a state machine). :param StateMachine state_machine: The state machine to be controlled and modified """ state_machine = None selection = None root_state = None meta = None ongoing_complex_actions = None meta_signal = Signal() state_meta_signal = Signal() action_signal = Signal() state_action_signal = Signal() sm_selection_changed_signal = Signal() destruction_signal = Signal() suppress_new_root_state_model_one_time = False __observables__ = ("state_machine", "root_state", "meta_signal", "state_meta_signal", "sm_selection_changed_signal", "action_signal", "state_action_signal", "destruction_signal", "ongoing_complex_actions") @measure_time def __init__(self, state_machine, meta=None, load_meta_data=True): """Constructor """ MetaModel.__init__(self) # pass columns as separate parameters assert isinstance(state_machine, StateMachine) self.state_machine = state_machine self.state_machine_id = state_machine.state_machine_id root_state = self.state_machine.root_state if isinstance(root_state, ContainerState): self.root_state = ContainerStateModel( root_state, parent=self, load_meta_data=load_meta_data) else: self.root_state = StateModel(root_state, parent=self, load_meta_data=load_meta_data) if isinstance(meta, Vividict): self.meta = meta else: self.meta = Vividict() # ongoing_complex_actions is updated by ComplexActionObserver -> secure encapsulated observation # and made observable by state machine model here self.ongoing_complex_actions = {} self.complex_action_observer = ComplexActionObserver(self) self.meta_signal = Signal() self.state_meta_signal = Signal() self.action_signal = Signal() self.state_action_signal = Signal() self.sm_selection_changed_signal = Signal() self.destruction_signal = Signal() self.temp = Vividict() if load_meta_data: self.load_meta_data(recursively=False) self.selection = Selection(self.sm_selection_changed_signal) self.storage_lock = threading.Lock( ) # lock can not be substituted by the state machine lock -> maybe because it is a RLock self.history = None if global_gui_config.get_config_value('HISTORY_ENABLED'): from rafcon.gui.models.modification_history import ModificationsHistoryModel self.history = ModificationsHistoryModel(self) else: logger.info("The modification history is disabled") self.auto_backup = None if global_gui_config.get_config_value('AUTO_BACKUP_ENABLED'): from rafcon.gui.models.auto_backup import AutoBackupModel self.auto_backup = AutoBackupModel(self) self.root_state.register_observer(self) self.register_observer(self) def __eq__(self, other): # logger.info("compare method") if isinstance(other, StateMachineModel): return self.root_state == other.root_state and self.meta == other.meta else: return False def __hash__(self): return id(self) def __copy__(self): sm_m = self.__class__(copy(self.state_machine)) sm_m.root_state.copy_meta_data_from_state_m(self.root_state) sm_m.meta = deepcopy(self.meta) return sm_m def __deepcopy__(self, memo=None, _nil=[]): return self.__copy__() @property def core_element(self): return self.state_machine def __destroy__(self): self.destroy() def destroy(self): if self.auto_backup is not None: self.auto_backup.destroy() self.auto_backup = None def prepare_destruction(self): """Prepares the model for destruction Unregister itself as observer from the state machine and the root state """ if self.state_machine is None: logger.verbose( "Multiple calls of prepare destruction for {0}".format(self)) self.destruction_signal.emit() if self.history is not None: self.history.prepare_destruction() if self.auto_backup is not None: self.auto_backup.prepare_destruction() try: self.unregister_observer(self) self.root_state.unregister_observer(self) except KeyError: # Might happen if the observer was already unregistered pass with self.state_machine.modification_lock(): self.root_state.prepare_destruction() self.root_state = None self.state_machine = None super(StateMachineModel, self).prepare_destruction() def update_hash(self, obj_hash): self.update_hash_from_dict(obj_hash, self.root_state) self.update_hash_from_dict(obj_hash, self.meta) def update_meta_data_hash(self, obj_hash): super(StateMachineModel, self).update_meta_data_hash(obj_hash) self.root_state.update_meta_data_hash(obj_hash) @ModelMT.observe("state", before=True) @ModelMT.observe("income", before=True) @ModelMT.observe("outcomes", before=True) @ModelMT.observe("is_start", before=True) @ModelMT.observe("states", before=True) @ModelMT.observe("transitions", before=True) @ModelMT.observe("data_flows", before=True) @ModelMT.observe("input_data_ports", before=True) @ModelMT.observe("scoped_variables", before=True) def root_state_model_before_change(self, model, prop_name, info): if not self._list_modified(prop_name, info): self._send_root_state_notification(model, prop_name, info) @ModelMT.observe("state", after=True) @ModelMT.observe("income", after=True) @ModelMT.observe("outcomes", after=True) @ModelMT.observe("is_start", after=True) @ModelMT.observe("states", after=True) @ModelMT.observe("transitions", after=True) @ModelMT.observe("data_flows", after=True) @ModelMT.observe("input_data_ports", after=True) @ModelMT.observe("output_data_ports", after=True) @ModelMT.observe("scoped_variables", after=True) def root_state_model_after_change(self, model, prop_name, info): if not self._list_modified(prop_name, info): self._send_root_state_notification(model, prop_name, info) @ModelMT.observe("state_machine", after=True) def state_machine_model_after_change(self, model, prop_name, info): overview = NotificationOverview(info) if overview.caused_modification(): if not self.state_machine.marked_dirty: self.state_machine.marked_dirty = True @ModelMT.observe("meta_signal", signal=True) def meta_changed(self, model, prop_name, info): """When the meta was changed, we have to set the dirty flag, as the changes are unsaved""" self.state_machine.marked_dirty = True msg = info.arg if model is not self and msg.change.startswith( 'sm_notification_'): # Signal was caused by the root state # Emit state_meta_signal to inform observing controllers about changes made to the meta data within the # state machine # -> removes mark of "sm_notification_"-prepend to mark root-state msg forwarded to state machine label msg = msg._replace( change=msg.change.replace('sm_notification_', '', 1)) self.state_meta_signal.emit(msg) @ModelMT.observe("action_signal", signal=True) def action_signal_triggered(self, model, prop_name, info): """When the action was performed, we have to set the dirty flag, as the changes are unsaved""" # print("ACTION_signal_triggered state machine: ", model, prop_name, info) self.state_machine.marked_dirty = True msg = info.arg if model is not self and msg.action.startswith( 'sm_notification_'): # Signal was caused by the root state # Emit state_action_signal to inform observing controllers about changes made to the state within the # state machine # print("DONE1 S", self.state_machine.state_machine_id, msg, model) # -> removes mark of "sm_notification_"-prepend to mark root-state msg forwarded to state machine label msg = msg._replace( action=msg.action.replace('sm_notification_', '', 1)) self.state_action_signal.emit(msg) # print("FINISH DONE1 S", self.state_machine.state_machine_id, msg) else: # print("DONE2 S", self.state_machine.state_machine_id, msg) pass @staticmethod def _list_modified(prop_name, info): """Check whether the given operation is a list operation The function checks whether the property that has been changed is a list. If so, the operation is investigated further. If the operation is a basic list operation, the function return True. :param prop_name: The property that was changed :param info: Dictionary with information about the operation :return: True if the operation was a list operation, False else """ if prop_name in [ "states", "transitions", "data_flows", "input_data_ports", "output_data_ports", "scoped_variables", "outcomes" ]: if info['method_name'] in [ "append", "extend", "insert", "pop", "remove", "reverse", "sort", "__delitem__", "__setitem__" ]: return True return False def get_state_model_by_path(self, path): """Returns the `StateModel` for the given `path` Searches a `StateModel` in the state machine, who's path is given by `path`. :param str path: Path of the searched state :return: The state with that path :rtype: StateModel :raises: ValueError, if path is invalid/not existing with this state machine """ path_elements = path.split('/') path_elements.pop(0) current_state_model = self.root_state for state_id in path_elements: if isinstance(current_state_model, ContainerStateModel): if state_id in current_state_model.states: current_state_model = current_state_model.states[state_id] else: raise ValueError( "Invalid path: State with id '{}' not found in state with id {}" .format(state_id, current_state_model.state.state_id)) elif isinstance(current_state_model, LibraryStateModel): if state_id == current_state_model.state_copy.state.state_id: current_state_model = current_state_model.state_copy else: raise ValueError( "Invalid path: state id '{}' does not coincide with state id '{}' of state_copy " "of library state with id '{}'".format( state_id, current_state_model.state_copy.state.state_id, current_state_model.state.state_id)) else: raise ValueError( "Invalid path: State with id '{}' has no children".format( current_state_model.state.state_id)) return current_state_model @ModelMT.observe("state_machine", after=True) def root_state_assign(self, model, prop_name, info): if info.method_name != 'root_state': return if self.suppress_new_root_state_model_one_time: self.suppress_new_root_state_model_one_time = False return # print("ASSIGN ROOT_STATE", model, prop_name, info) try: self.root_state.unregister_observer(self) except KeyError: pass if isinstance(self.state_machine.root_state, ContainerState): # could not be a LibraryState self.root_state = ContainerStateModel( self.state_machine.root_state) else: assert not isinstance(self.state_machine.root_state, LibraryState) self.root_state = StateModel(self.state_machine.root_state) self.root_state.register_observer(self) @ModelMT.observe("state_machine", after=True, before=True) def change_root_state_type(self, model, prop_name, info): if info.method_name != 'change_root_state_type': return if 'before' in info: self._send_root_state_notification(model, prop_name, info) else: # Do not forward the notification yet, but store its parameters locally at the function # The function helpers.state.change_state_type will forward the notification after some preparation self.change_root_state_type.__func__.suppressed_notification_parameters = [ model, prop_name, info ] def _send_root_state_notification(self, model, prop_name, info): cause = 'root_state_change' try: if 'before' in info: self.state_machine._notify_method_before( self.state_machine, cause, (self.state_machine, ), info) elif 'after' in info: self.state_machine._notify_method_after( self.state_machine, cause, None, (self.state_machine, ), info) except AssertionError as e: # This fixes an AssertionError raised by GTKMVC. It can probably occur, when a controller unregisters # itself from a model, while the notification chain still propagates upwards. logger.exception( "A exception occurs in the _send_root_state_notification method {0}." .format(e)) ####################################################### # --------------------- meta data methods --------------------- ####################################################### def load_meta_data(self, path=None, recursively=True): """Load meta data of state machine model from the file system The meta data of the state machine model is loaded from the file system and stored in the meta property of the model. Existing meta data is removed. Also the meta data of root state and children is loaded. :param str path: Optional path to the meta data file. If not given, the path will be derived from the state machine's path on the filesystem """ meta_data_path = path if path is not None else self.state_machine.file_system_path if meta_data_path: path_meta_data = os.path.join(meta_data_path, storage.FILE_NAME_META_DATA) try: tmp_meta = storage.load_data_file(path_meta_data) except ValueError: tmp_meta = {} else: tmp_meta = {} # JSON returns a dict, which must be converted to a Vividict tmp_meta = Vividict(tmp_meta) if recursively: root_state_path = None if not path else os.path.join( path, self.root_state.state.state_id) self.root_state.load_meta_data(root_state_path) if tmp_meta: # assign the meta data to the state self.meta = tmp_meta self.meta_signal.emit(MetaSignalMsg("load_meta_data", "all", True)) def store_meta_data(self, copy_path=None): """Save meta data of the state machine model to the file system This method generates a dictionary of the meta data of the state machine and stores it on the filesystem. :param str copy_path: Optional, if the path is specified, it will be used instead of the file system path """ if copy_path: meta_file_json = os.path.join(copy_path, storage.FILE_NAME_META_DATA) else: meta_file_json = os.path.join(self.state_machine.file_system_path, storage.FILE_NAME_META_DATA) storage_utils.write_dict_to_json(self.meta, meta_file_json) self.root_state.store_meta_data(copy_path)