class SingleTask: """Class for working with single task of execution.""" def __init__(self, button, task_func, parameters): """Class initialization. :param TwoStateButton button: button that activates task. :param task_func: function to execute. :param dict parameters: function's parameters. """ self.run_and_stop_button = button self.task_func = task_func self.parameters = parameters self.threads = ThreadPool() self.process = None self.run_and_stop_button.connect_first_state(self.execute) self.run_and_stop_button.connect_second_state(self.abort) def execute(self): """Execute function in safe thread.""" logger.debug( f"Executing single task: {self.__class__.__name__} {self.task_func.__name__}" ) from lib.gui.widgets.main import MainWindow MainWindow.resume_recorder() worker = self.threads.run_thread(target=self._execute) worker.signals.finished.connect( self.run_and_stop_button.set_first_state) worker.signals.finished.connect(MainWindow.pause_recorder) self.run_and_stop_button.set_second_state() @safe_process_stop def abort(self): """Abort function's execution.""" if self.process: logger.debug("Task was forcibly stopped.") self.process.terminate() self.threads.thread_pool.clear() self.run_and_stop_button.set_first_state() from lib.gui.widgets.main import MainWindow MainWindow.pause_recorder() def _execute(self): """Execute function.""" self.process = Process(target=self.task_func, kwargs=self.parameters) self.process.start() self.process.join() logger.debug("Task completed.")
class AgentNetwork: """ Object for starting a new Agent Network or connect to an existing Agent Network specified by ip & port Provides function to add agents, (un)bind agents, query agent network state, set global agent states Interfaces with an internal _AgentController which is hidden from user """ def __init__(self, ip_addr="127.0.0.1", port=3333, connect=False, dashboard_modules=True, dashboard_update_interval=3, log_filename="log_file.csv"): """ Parameters ---------- ip_addr: str Ip address of server to connect/start port: int Port of server to connect/start connect: bool False sets Agent network to connect mode and will connect to specified address True (Default) sets Agent network to initially try to connect and if it cant find one, it will start a new server at specified address dashboard_modules : list of modules , modules or bool Accepts list of modules which contains the AgentMET4FOF and DataStreamMET4FOF derived classes If set to True, will initiate the dashboard with default agents in AgentMET4FOF dashboard_update_interval : int Regular interval (seconds) to update the dashboard graphs logfile: str Name of log file, acceptable csv format. If set to None or False, then will not save file """ self.ip_addr= ip_addr self.port = port self._controller = None self.log_filename= log_filename if type(self.log_filename) == str and '.csv' in self.log_filename: self.save_logfile = True else: self.save_logfile = False if connect: self.connect(ip_addr,port) else: self.connect(ip_addr,port, verbose=False) if self.ns == 0: self.start_server(ip_addr,port) if dashboard_modules is not False: self.dashboard_proc = Process(target=run_dashboard, args=(dashboard_modules,dashboard_update_interval)) self.dashboard_proc.start() else: self.dashboard_proc = None def connect(self,ip_addr="127.0.0.1", port = 3333,verbose=True): """ Parameters ---------- ip_addr: str IP Address of server to connect to port: int Port of server to connect to """ try: self.ns = NSProxy(nsaddr=ip_addr+':' + str(port)) except: if verbose: print("Unable to connect to existing NameServer...") self.ns = 0 def start_server(self,ip_addr="127.0.0.1", port=3333): """ Parameters ---------- ip_addr: str IP Address of server to start port: int Port of server to start """ print("Starting NameServer...") self.ns = run_nameserver(addr=ip_addr+':' + str(port)) if len(self.ns.agents()) != 0: self.ns.shutdown() self.ns = run_nameserver(addr=ip_addr+':' + str(port)) controller = run_agent("AgentController", base=_AgentController, attributes=dict(log_mode=True), nsaddr=self.ns.addr()) logger = run_agent("Logger", base=_Logger, nsaddr=self.ns.addr()) controller.init_parameters(self.ns) logger.init_parameters(log_filename=self.log_filename,save_logfile=self.save_logfile) def _set_mode(self, state): """ Internal method to set mode of Agent Controller Parameters ---------- state: str State of AgentController to set. """ self._get_controller().set_attr(current_state=state) def _get_mode(self): """ Returns ------- state: str State of Agent Network """ return self._get_controller().get_attr('current_state') def set_running_state(self, filter_agent=None): """ Blanket operation on all agents to set their `current_state` attribute to "Running" Users will need to define their own flow of handling each type of `self.current_state` in the `agent_loop` Parameters ---------- filter_agent : str (Optional) Filter name of agents to set the states """ self.set_agents_state(filter_agent=filter_agent,state="Running") def update_networkx(self): self._get_controller().update_networkx() def get_networkx(self): return self._get_controller().get_attr('G') def get_nodes_edges(self): G = self.get_networkx() return G.nodes, G.edges def get_nodes(self): G = self.get_networkx() return G.nodes def get_edges(self): G = self.get_networkx() return G.edges def set_stop_state(self, filter_agent=None): """ Blanket operation on all agents to set their `current_state` attribute to "Stop" Users will need to define their own flow of handling each type of `self.current_state` in the `agent_loop` Parameters ---------- filter_agent : str (Optional) Filter name of agents to set the states """ self.set_agents_state(filter_agent=filter_agent, state="Stop") def set_agents_state(self, filter_agent=None, state="Idle"): """ Blanket operation on all agents to set their `current_state` attribute to given state Can be used to define different states of operation such as "Running", "Idle, "Stop", etc.. Users will need to define their own flow of handling each type of `self.current_state` in the `agent_loop` Parameters ---------- filter_agent : str (Optional) Filter name of agents to set the states state : str State of agents to set """ self._set_mode(state) for agent_name in self.agents(): if (filter_agent is not None and filter_agent in agent_name) or (filter_agent is None): agent = self.get_agent(agent_name) agent.set_attr(current_state=state) print("SET STATE: ", state) return 0 def reset_agents(self): for agent_name in self.agents(): agent = self.get_agent(agent_name) agent.reset() agent.set_attr(current_state="Reset") self._set_mode("Reset") return 0 def remove_agent(self, agent): if type(agent) == str: agent_proxy = self.get_agent(agent) else: agent_proxy = agent for input_agent in agent_proxy.get_attr("Inputs"): self.get_agent(input_agent).unbind_output(agent_proxy) for output_agent in agent_proxy.get_attr("Outputs"): agent_proxy.unbind_output(self.get_agent(output_agent)) agent_proxy.shutdown() def bind_agents(self, source, target): """ Binds two agents communication channel in a unidirectional manner from `source` Agent to `target` Agent Any subsequent calls of `source.send_output()` will reach `target` Agent's message queue. Parameters ---------- source : AgentMET4FOF Source agent whose Output channel will be binded to `target` target : AgentMET4FOF Target agent whose Input channel will be binded to `source` """ source.bind_output(target) return 0 def unbind_agents(self, source, target): """ Unbinds two agents communication channel in a unidirectional manner from `source` Agent to `target` Agent This is the reverse of `bind_agents()` Parameters ---------- source : AgentMET4FOF Source agent whose Output channel will be unbinded from `target` target : AgentMET4FOF Target agent whose Input channel will be unbinded from `source` """ source.unbind_output(target) return 0 def _get_controller(self): """ Internal method to access the AgentController relative to the nameserver """ if self._controller is None: self._controller = self.ns.proxy('AgentController') return self._controller def get_agent(self,agent_name): """ Returns a particular agent connected to Agent Network. Parameters ---------- agent_name : str Name of agent to search for in the network """ return self._get_controller().get_attr('ns').proxy(agent_name) def agents(self): """ Returns all agent names connected to Agent Network. Returns ------- list : names of all agents """ agent_names = self._get_controller().agents() return agent_names def add_agent(self, name=" ", agentType= AgentMET4FOF, log_mode=True): """ Instantiates a new agent in the network. Parameters ---------- name : str Unique name of agent. If left empty, the name will be automatically set to its class name. There cannot be more than one agent with the same name. agentType : AgentMET4FOF Agent class to be instantiated in the network. log_mode : bool Default is True. Determines if messages will be logged to background Logger Agent. Returns ------- AgentMET4FOF : Newly instantiated agent """ agent = self._get_controller().add_module(name=name, agentType= agentType, log_mode=log_mode) return agent def shutdown(self): """ Shutdowns the entire agent network and all agents """ self._get_controller().get_attr('ns').shutdown() if self.dashboard_proc is not None: self.dashboard_proc.terminate() return 0
class SingleTaskWithOptions: def __init__(self, button, task_func, task_options): """Class initialization. :param TwoStateButton button: button that activates task. :param task_func: function to execute. :param dict task_options: function's parameters by option's key. """ self.run_and_stop_button = button self.task_func = task_func self.task_options = task_options self.threads = ThreadPool() self.process = None for task_name, task_parameters in task_options.items(): def add_action(parameters): self.run_and_stop_button.add_action( task_name, lambda: self.execute(parameters)) add_action(task_parameters) self.menu = self.run_and_stop_button.button.menu() self.run_and_stop_button.connect_second_state(self.abort) def execute(self, parameters): """Execute function in safe thread.""" logger.debug( f"Executing single task: {self.__class__.__name__} {self.task_func.__name__} " f"with parameters {parameters}") from lib.gui.widgets.main import MainWindow MainWindow.resume_recorder() worker = self.threads.run_thread( target=lambda: self._execute(parameters=parameters)) worker.signals.finished.connect( self.run_and_stop_button.set_first_state) worker.signals.finished.connect(self._set_menu) worker.signals.finished.connect(MainWindow.pause_recorder) self._clear_menu() self.run_and_stop_button.set_second_state() def _clear_menu(self): """Clear button menu.""" self.run_and_stop_button.button.setMenu(None) def _set_menu(self): """Set button menu from cache.""" self.run_and_stop_button.button.setMenu(self.menu) @safe_process_stop def abort(self): """Abort function's execution.""" if self.process: logger.debug("Task was forcibly stopped.") self.process.terminate() self.threads.thread_pool.clear() self._set_menu() self.run_and_stop_button.set_first_state() from lib.gui.widgets.main import MainWindow MainWindow.pause_recorder() def _execute(self, parameters): """Execute function.""" self.process = Process(target=self.task_func, kwargs=parameters) self.process.start() self.process.join() logger.debug("Task completed.")
class QueueList: """Class for working with queue list.""" def __init__(self, game, list_widget, run_and_stop_button, add_button, edit_button, remove_button, queue_selector_buttons): """Class initialization. :param PyQt5.QtWidgets.QListWidget.QListWidget list_widget: list widget. :param lib.gui.helper.TwoStateButton run_and_stop_button: button for running the queue. :param PyQt5.QtWidgets.QPushButton.QPushButton add_button: button for adding new element to queue. :param PyQt5.QtWidgets.QPushButton.QPushButton edit_button: button for editing existing element in the queue. :param PyQt5.QtWidgets.QPushButton.QPushButton remove_button: button for removing existing element from the queue. :param list[PyQt5.QtWidgets.QPushButton.QPushButton] queue_selector_buttons: list of buttons for selecting different queues. """ self.game = game self.widget = list_widget self.run_and_stop_button = run_and_stop_button self.add_button = add_button self.edit_button = edit_button self.remove_button = remove_button self.queue_selector_buttons = queue_selector_buttons self.stored_queues = [[], ] * len(self.queue_selector_buttons) # type: List[List[QueueItem]] self.current_queue_index = 0 self.setup_buttons() self.threads = ThreadPool() self.process = None self.add_button.clicked.connect(self.add) if self.widget.count() == 0: self.run_and_stop_button.button.setEnabled(False) self.select_all_item = self.add_select_all_checkbox() self.widget.itemChanged.connect(self.on_item_change) self.load_queue_from_file() self.change_select_all_state() self.stop_queue_flag = False def queue(self): """Queue iterator.""" for i in range(self.widget.count()): item = self.widget.item(i) if isinstance(item, QueueItem): yield item @property def queue_fifo(self): """Creates FIFO representation of current queue. :rtype: deque[QueueItem] """ return deque(list(self.queue())) def clear_queue(self): """Clears queue.""" for _ in range(self.widget.count()): item = self.widget.item(0) self.widget.takeItem(self.widget.row(item)) def store_current_queue(self): """Stores currently selected queue to variable.""" self.stored_queues[self.current_queue_index] = [*self.queue()] def change_queue(self, index): """Changes queue by index. :param int index: queue's index. """ if index != self.current_queue_index: self.store_current_queue() self.current_queue_index = index self.clear_queue() self.select_all_item = self.add_select_all_checkbox() for item in self.stored_queues[index]: self._add(item) for button in self.queue_selector_buttons: button.setChecked(False) self.queue_selector_buttons[index].setChecked(True) def add_select_all_checkbox(self): """Creates 'Select All' checkbox with empty line below.""" select_all = QListWidgetItem() select_all.setText("[Select All]") select_all.setCheckState(Qt.Checked) select_all.setFlags(select_all.flags() | Qt.ItemIsUserCheckable) select_all.setFlags(select_all.flags() ^ Qt.ItemIsDragEnabled) select_all.setFlags(select_all.flags() ^ Qt.ItemIsSelectable) blank_line = QListWidgetItem() blank_line.setFlags(blank_line.flags() ^ Qt.ItemIsDragEnabled) blank_line.setFlags(blank_line.flags() ^ Qt.ItemIsSelectable) self.widget.addItem(select_all) self.widget.addItem(blank_line) return select_all def change_select_all_state(self): """Changes 'Select All' checkbox state by queue item's states.""" queue_states = [queue_item.checkState() for queue_item in self.queue()] all_checked = [state for state in queue_states if state == Qt.Checked] all_unchecked = [state for state in queue_states if state == Qt.Unchecked] partially_checked = all_checked and all_unchecked if all_checked and not all_unchecked: self.select_all_item.setCheckState(Qt.Checked) if all_unchecked and not all_checked: self.select_all_item.setCheckState(Qt.Unchecked) if partially_checked: self.select_all_item.setCheckState(Qt.PartiallyChecked) def on_item_change(self, item): """Selects or deselects items when some item was checked. :param QListWidgetItem item: changed item. """ if item == self.select_all_item: state = item.checkState() if state == Qt.Checked: self.select_all() if state == Qt.Unchecked: self.deselect_all() if isinstance(item, QueueItem): if len(self.widget.selectedItems()) > 1: for selected_item in self.widget.selectedItems(): selected_item.setCheckState(item.checkState()) self.change_select_all_state() def load_queue_from_file(self): """Loads queue list and apply it to GUI.""" queues_list = load_queue_list() if not queues_list: return for queue_index, queue in enumerate(queues_list): logger.debug(f"Loading {len(queue)} items to queue list #{queue_index + 1}.") queue_items = [] for settings in queue: editor = QueueItemEditor.from_settings(game=self.game, settings=settings) if editor: item = editor.render_queue_item() item.set_checked(settings.get("checked", False)) queue_items.append(item) self.stored_queues[queue_index] = queue_items self.change_queue(index=0) def save_queue_to_file(self): """Saves existing queue to JSON-file.""" self.store_current_queue() queues_list = [] for queue_index, queue in enumerate(self.stored_queues): queue_items = [] for item in queue: settings = { "mode_name": item.mode_name, "checked": item.is_checked, **item.parameters } queue_items.append(settings) queues_list.append(queue_items) logger.debug(f"Saving queue #{queue_index + 1} list with {len(queue)} items.") save_queue_list(queues_list) def setup_buttons(self): """Setups button's events.""" self.run_and_stop_button.connect_first_state(self.run_queue) self.run_and_stop_button.connect_second_state(self.stop_queue) self.run_and_stop_button.connect_first_state(self.widget.setDragDropMode, QAbstractItemView.NoDragDrop) self.run_and_stop_button.connect_second_state(self.widget.setDragDropMode, QAbstractItemView.InternalMove) self.remove_button.clicked.connect(self.remove_current_item) self.edit_button.clicked.connect(self.edit_current_item) # Setup Queue #1 and etc. buttons to change queue def change_queue_on_click(button, queue_index): button.clicked.connect(lambda: self.change_queue(queue_index)) for index in range(len(self.queue_selector_buttons)): change_queue_on_click(button=self.queue_selector_buttons[index], queue_index=index) def add(self): """Creates editor window and add queue item from it.""" editor = QueueItemEditor(game=self.game) editor.setWindowTitle("Add queue item") result = editor.exec_() if result and editor.queue_item: self._add(editor.queue_item) def _add(self, item): """Adds item to queue. :param QListWidgetItem item: item to add. """ if self.widget.count() == 2: self.run_and_stop_button.button.setEnabled(True) self.widget.addItem(item) self.change_select_all_state() return item def edit_current_item(self): """Edits current selected item.""" item = self.widget.currentItem() if item and isinstance(item, QueueItem): editor = QueueItemEditor.from_result_item(game=self.game, queue_item=item) editor.setWindowTitle("Edit queue item") result = editor.exec_() if result and editor.queue_item: self.edit_item(old_item=item, new_item=editor.queue_item) def edit_item(self, old_item, new_item): """Edits queue item. :param QListWidgetItem old_item: item before editing. :param QListWidgetItem new_item: item after editing. """ widget_index = self.widget.row(old_item) self.widget.takeItem(widget_index) self.widget.insertItem(widget_index, new_item) self.widget.setCurrentRow(widget_index) def remove_current_item(self): """Removes current selected item from queue.""" item = self.widget.currentItem() if item and isinstance(item, QueueItem): if len(self.widget.selectedItems()) > 1: for selected_item in self.widget.selectedItems(): self.remove_item(selected_item) self.remove_item(item) def remove_item(self, item): """Removes item from queue. :param QListWidgetItem item: queue item. """ self.widget.takeItem(self.widget.row(item)) self.change_select_all_state() if self.widget.count() == 2: self.run_and_stop_button.button.setEnabled(False) def add_queue_by_index(self, queue, queue_index): """Adds items from queue to current queue by queue index. :param deque[QueueItem] queue: current FIFO queue. :param queue_index: index of queue to add. """ logger.debug(f"Running queue by index = {queue_index}") for item in reversed(self.stored_queues[queue_index - 1]): clone = item.clone(mark=True) queue.appendleft(clone) def run_queue(self): """Runs and executes all items in queue.""" logger.debug("Running queue.") self.store_current_queue() from lib.gui.widgets.main import MainWindow MainWindow.resume_recorder() self.run_and_stop_button.set_second_state() self.widget.setDragDropMode(QAbstractItemView.NoDragDrop) self.threads.run_thread(func=self._run_queue, on_finish=[self.run_and_stop_button.set_first_state, self.reset_background, MainWindow.pause_recorder], on_progress=self.mark_execution_background, with_progress=True) def mark_execution_background(self, cur_index): """Marks execution queue items with color. :param int cur_index: index for current item in queue. """ for index, item in enumerate(self.queue()): if index == cur_index: item.setBackground(Qt.yellow) break color = Qt.green if item.is_checked else Qt.gray item.setBackground(color) def reset_background(self): """Resets queue colors.""" for item in self.queue(): item.setBackground(Qt.transparent) @safe_process_stop def stop_queue(self): """Stops queue execution.""" from lib.gui.widgets.main import MainWindow MainWindow.pause_recorder() self.game.clear_modes() self.widget.setDragDropMode(QAbstractItemView.InternalMove) self.stop_queue_flag = True if self.process: logger.debug("Queue was forcibly stopped.") self.process.terminate() self.threads.thread_pool.clear() self.run_and_stop_button.set_first_state() def _run_queue(self, progress_callback): """Runs item's execution. :param PyQt5.QtCore.pyqtSignal.pyqtSignal progress_callback: signal to emit queue progress. """ queue = self.queue_fifo index = -1 while queue: item = queue.popleft() if not item.was_cloned: index += 1 if self.stop_queue_flag: break progress_callback.emit(index) executor, settings = item.get_executor() if item.mode_name == "RUN QUEUE" and item.is_checked: self.add_queue_by_index(queue=queue, **settings) continue if not executor: logger.debug(f"Skipping queue item: {item.mode_name}") continue logger.debug(f"Running {item.mode_name} with settings: {settings}") self.process = Process(target=executor, kwargs=settings) self.process.start() self.process.join() self.stop_queue_flag = False self.widget.setDragDropMode(QAbstractItemView.InternalMove) self.game.clear_modes() logger.debug("Queue completed.") def select_all(self): """Selects all items in queue.""" for item in self.queue(): item.set_checked(True) def deselect_all(self): """Deselects all items in queue.""" for item in self.queue(): item.set_checked(False)