From ab09897544f2121dd541b576c388abdd66b44d8e Mon Sep 17 00:00:00 2001 From: Suhas-G Date: Fri, 20 May 2022 15:53:41 +0200 Subject: [PATCH 1/2] new: python based shared memory --- backend/staticfiles/synthesis/console.py | 148 -------- backend/staticfiles/synthesis/lib/block.py | 48 --- .../staticfiles/synthesis/lib/exceptions.py | 9 + backend/staticfiles/synthesis/lib/inputs.py | 74 ++++ backend/staticfiles/synthesis/lib/outputs.py | 77 ++++ .../staticfiles/synthesis/lib/parameters.py | 24 ++ backend/staticfiles/synthesis/lib/utils.py | 20 + backend/staticfiles/synthesis/main.py | 347 +++++------------- .../synthesis/utils/tools/__init__.py | 2 - .../synthesis/utils/tools/freq_monitor.py | 51 --- .../synthesis/utils/tools/messenger.py | 13 - .../synthesis/utils/wires/__init__.py | 2 - .../synthesis/utils/wires/structure_img.py | 10 - .../synthesis/utils/wires/structure_str.py | 8 - .../synthesis/utils/wires/wire_img.py | 102 ----- .../synthesis/utils/wires/wire_str.py | 102 ----- .../blocks/basic/constant/constant-model.ts | 3 +- .../blocks/basic/input/input-model.ts | 3 +- .../blocks/common/base-port/port-model.ts | 4 + frontend/src/core/serialiser/converter.ts | 37 +- frontend/src/core/serialiser/interfaces.ts | 2 + 21 files changed, 320 insertions(+), 766 deletions(-) delete mode 100644 backend/staticfiles/synthesis/console.py delete mode 100644 backend/staticfiles/synthesis/lib/block.py create mode 100644 backend/staticfiles/synthesis/lib/exceptions.py create mode 100644 backend/staticfiles/synthesis/lib/inputs.py create mode 100644 backend/staticfiles/synthesis/lib/outputs.py create mode 100644 backend/staticfiles/synthesis/lib/parameters.py create mode 100644 backend/staticfiles/synthesis/lib/utils.py delete mode 100644 backend/staticfiles/synthesis/utils/tools/__init__.py delete mode 100644 backend/staticfiles/synthesis/utils/tools/freq_monitor.py delete mode 100644 backend/staticfiles/synthesis/utils/tools/messenger.py delete mode 100644 backend/staticfiles/synthesis/utils/wires/__init__.py delete mode 100644 backend/staticfiles/synthesis/utils/wires/structure_img.py delete mode 100644 backend/staticfiles/synthesis/utils/wires/structure_str.py delete mode 100644 backend/staticfiles/synthesis/utils/wires/wire_img.py delete mode 100644 backend/staticfiles/synthesis/utils/wires/wire_str.py diff --git a/backend/staticfiles/synthesis/console.py b/backend/staticfiles/synthesis/console.py deleted file mode 100644 index 0b0f756c..00000000 --- a/backend/staticfiles/synthesis/console.py +++ /dev/null @@ -1,148 +0,0 @@ -from PyQt5.QtCore import Qt,QObject, QThread, pyqtSignal -from PyQt5.QtWidgets import QApplication, QSplitter,QPushButton, QWidget, QVBoxLayout, QMainWindow, QGridLayout, QLayout,QLabel,QToolBar,QToolButton -from PyQt5.QtGui import QPalette, QColor -from multiprocessing import Process,Queue,Pipe - -class Worker(QObject): - signal = pyqtSignal(str) - def __init__(self,queue): - QObject.__init__(self) - self.queue = queue - def run(self): - while True: - text = self.queue.get() - self.signal.emit(text) - -#<---------------------shared memory worker class--------------> -# class Worker1(QObject): -# from utils.wires.wire_str import read_string -# from time import sleep -# signal = pyqtSignal(str) -# def __init__(self): -# QObject.__init__(self) -# def run(self): -# while True: -# txt = self.read_string("ERROR#LOG").get() -# print(txt) -# self.signal.emit(txt[0]) -#______________________________________________________________> - -class Console(QMainWindow,QWidget,QObject): - - - def __init__(self,fmqueue,msgQueue): - def quitfn(self): - msgQueue.put("#QUIT") - super(Console, self).__init__() - - - self.resize(800, 600) - self.queue = fmqueue - - self.ToolBar = self.addToolBar(" ") - self.addToolBar(Qt.BottomToolBarArea, self.ToolBar) - self.quitBtn = QPushButton() - self.quitBtn.move(50, 50) - self.quitBtn.setText("Quit!") - self.ToolBar.addWidget(self.quitBtn) - self.quitBtn.clicked.connect(quitfn) -#------------------------------------------------------------------------------------# - self.console = QVBoxLayout() - self.toolbar = QToolBar() - self.toolbar.addWidget(QLabel("Console")) - self.text_console = QLabel("") - self.text_console.setAutoFillBackground(True) - p = self.text_console.palette() - p.setColor(self.text_console.backgroundRole(), Qt.black) - self.text_console.setPalette(p) - self.text_console.setAlignment(Qt.AlignLeft) - - self.console.addWidget(self.toolbar) - self.console.addWidget(self.text_console) - - - self.left = QWidget() - self.left.setLayout(self.console) -#------------------------------------------------------------------------------------# - self.freqm = QVBoxLayout() - self.toolbar2 = QToolBar() - self.toolbar2.addWidget(QLabel("Frequency Monitor")) - - self.text_freq = QLabel("") - self.text_freq.setAutoFillBackground(True) - p2 = self.text_freq.palette() - p2.setColor(self.text_freq.backgroundRole(), Qt.black) - self.text_freq.setPalette(p2) - self.text_freq.setAlignment(Qt.AlignLeft) - - self.freqm.addWidget(self.toolbar2) - self.freqm.addWidget(self.text_freq) - - self.right = QWidget() - self.right.setLayout(self.freqm) -#------------------------------------------------------------------------------------# - self.splitter = QSplitter(Qt.Horizontal) - self.splitter.addWidget(self.left) - self.splitter.addWidget(self.right) - - self.setCentralWidget(self.splitter) - -#------------------------------------------------------------------------------------# - - def loginfo_console(self, msg): - txt = self.text_console.text() - self.text_console.setText(txt+"\n"+msg) - - def loginfo_fm(self, msg): - end = "END" - txt = self.text_freq.text() - self.text_freq.setText(txt+"\n"+msg) - msg= msg.strip() - if msg == end: - self.text_freq.setText(" ") - - - - - - - -StyleSheet = ''' - -/* QPushButton --------------------------------------------------------------- */ -QPushButton { - spacing: 30px; - padding: 10px 50px; - background-color: rgb(133, 131, 131); - color: rgb(255,255,255); - border-radius: 3px; - margin-right:50px; - subcontrol-position: right center; -} -QPushButton:selected { - background-color: rgb(128, 128, 128); -} -QPushButton:pressed { - background: rgb(255, 179, 179); - color: rgb(255,0,0) -} -''' - - -def set_theme(): - palette = QPalette() - palette.setColor(QPalette.Window, QColor(53, 53, 53)) - palette.setColor(QPalette.WindowText, Qt.white) - palette.setColor(QPalette.Base, QColor(25, 25, 25)) - palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) - palette.setColor(QPalette.ToolTipBase, Qt.black) - palette.setColor(QPalette.ToolTipText, Qt.white) - palette.setColor(QPalette.Text, Qt.white) - palette.setColor(QPalette.Button, QColor(53, 53, 53)) - palette.setColor(QPalette.ButtonText, Qt.white) - palette.setColor(QPalette.BrightText, Qt.red) - palette.setColor(QPalette.Link, QColor(42, 130, 218)) - palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) - palette.setColor(QPalette.HighlightedText, Qt.black) - return palette - diff --git a/backend/staticfiles/synthesis/lib/block.py b/backend/staticfiles/synthesis/lib/block.py deleted file mode 100644 index 16a984b3..00000000 --- a/backend/staticfiles/synthesis/lib/block.py +++ /dev/null @@ -1,48 +0,0 @@ -class Block: - - def __init__(self, id, id_type): - - self.id = id - self.id_type = id_type - self.name = None - self.input_ports = [] - self.port_map = [] - self.output_ports = [] - self.parameters = [] - - def set_name(self, name_dict): - - for name in name_dict: - if self.id_type == name[0]: - self.name = name[1] - break - - def connect_input_wire(self, wire_id, port): - self.input_ports.append({'port':port, 'wire':wire_id}) - - def connect_output_wire(self, wire_id, port): - self.output_ports.append({'port':port, 'wire':wire_id}) - - def add_parameter(self, parameter, port): - self.parameters.append({'port':port, 'parameter':parameter}) - - def sort_ports(self): - - sorter = sorted(self.input_ports, key=lambda k: k['port']) - self.input_ports = [] - - for element in sorter: - self.input_ports.append(element['wire']) - - sorter = sorted(self.output_ports, key=lambda k: k['port']) - self.output_ports = [] - - for element in sorter: - self.output_ports.append(element['wire']) - - sorter = sorted(self.parameters, key=lambda k: k['port']) - self.parameters = [] - - for element in sorter: - self.parameters.append(element['parameter']) - diff --git a/backend/staticfiles/synthesis/lib/exceptions.py b/backend/staticfiles/synthesis/lib/exceptions.py new file mode 100644 index 00000000..37ba359f --- /dev/null +++ b/backend/staticfiles/synthesis/lib/exceptions.py @@ -0,0 +1,9 @@ +class InvalidOutputNameException(Exception): + """Raised when Output name has not been declared in ports""" + + +class InvalidInputNameException(Exception): + """Raised when Input name has not been declared in ports""" + +class InvalidParameterNameException(Exception): + """Raised when Parameter name has not been declared in ports""" diff --git a/backend/staticfiles/synthesis/lib/inputs.py b/backend/staticfiles/synthesis/lib/inputs.py new file mode 100644 index 00000000..444a5501 --- /dev/null +++ b/backend/staticfiles/synthesis/lib/inputs.py @@ -0,0 +1,74 @@ +from multiprocessing import shared_memory + +import numpy as np +from lib.exceptions import InvalidInputNameException +from lib.utils import create_ndbuffer + + +def create_readonly_wire(name): + try: + shm = shared_memory.SharedMemory(name, create=False) + except FileNotFoundError: + shm = None + return shm + + +class Inputs: + def __init__(self, input_data) -> None: + self.inputs = input_data + + def _read_npy_matrix(self, name, dtype): + if self.inputs[name].get("created", False): + dim = create_ndbuffer((1,), np.int64, self.inputs[name]["dim"].buf)[:][0] + shape = create_ndbuffer((dim,), np.int64, self.inputs[name]["shape"].buf) + data = create_ndbuffer(shape, dtype, self.inputs[name]["data"].buf) + else: + wire_name = self.inputs[name]["wire"] + data_wire = create_readonly_wire(wire_name) + shape_wire = create_readonly_wire(wire_name + "_shape") + dim_wire = create_readonly_wire(wire_name + "_dim") + if data_wire is None or shape_wire is None or dim_wire is None: + return None + + self.inputs[name]["dim"] = dim_wire + dim = create_ndbuffer((1,), np.int64, dim_wire.buf)[:][0] + self.inputs[name]["shape"] = shape_wire + shape = create_ndbuffer((dim,), np.int64, shape_wire.buf) + self.inputs[name]["data"] = data_wire + data = create_ndbuffer(shape, dtype, data_wire.buf) + self.inputs[name]["created"] = True + + return data + + def read_image(self, name): + if self.inputs.get(name) is None: + raise InvalidInputNameException(f"{name} is not declared in inputs") + + data = self._read_npy_matrix(name, np.uint8) + return data + + def read_number(self, name): + if self.inputs.get(name) is None: + raise InvalidInputNameException(f"{name} is not declared in inputs") + + number = None + if self.inputs[name].get("created", False): + number = self.inputs[name]["data"][:][0] + else: + wire_name = self.inputs[name]["wire"] + data_wire = create_readonly_wire(wire_name) + if data_wire is not None: + self.inputs[name]["data"] = create_ndbuffer( + (1,), np.float64, data_wire.buf + ) + number = self.inputs[name]["data"][:][0] + self.inputs[name]["created"] = True + + return number + + def read_array(self, name): + if self.inputs.get(name) is None: + raise InvalidInputNameException(f"{name} is not declared in inputs") + + data = self._read_npy_matrix(name, np.float64) + return data diff --git a/backend/staticfiles/synthesis/lib/outputs.py b/backend/staticfiles/synthesis/lib/outputs.py new file mode 100644 index 00000000..26c04335 --- /dev/null +++ b/backend/staticfiles/synthesis/lib/outputs.py @@ -0,0 +1,77 @@ +from multiprocessing import shared_memory + +import numpy as np +from lib.exceptions import InvalidOutputNameException +from lib.utils import create_ndbuffer + + +class Outputs: + def __init__(self, output_data) -> None: + self.outputs = output_data + self.shms = [] + + def _create_wire(self, name, size): + shm = shared_memory.SharedMemory(name=name, create=True, size=size) + self.shms.append(shm) + return shm + + def _share_npy_matrix(self, name, matrix, shape): + dim = np.array([len(matrix.shape)], dtype=np.int64) + if self.outputs[name].get("created", False): + self.outputs[name]["shape"][:] = shape[:] + self.outputs[name]["data"][:] = matrix[:] + else: + wire_name = self.outputs[name]["wire"] + data_wire = self._create_wire(wire_name, matrix.nbytes) + shape_wire = self._create_wire(wire_name + "_shape", shape.nbytes) + dim_wire = self._create_wire(wire_name + "_dim", dim.nbytes) + self.outputs[name]["dim"] = create_ndbuffer((1,), np.int64, dim_wire.buf) + self.outputs[name]["dim"][:] = dim[:] + self.outputs[name]["shape"] = create_ndbuffer( + shape.shape, shape.dtype, shape_wire.buf + ) + self.outputs[name]["shape"][:] = shape[:] + self.outputs[name]["data"] = create_ndbuffer( + shape, matrix.dtype, data_wire.buf + ) + self.outputs[name]["data"][:] = matrix[:] + self.outputs[name]["created"] = True + + def share_image(self, name, image): + if self.outputs.get(name) is None: + raise InvalidOutputNameException(f"{name} is not declared in outputs") + + image = np.array(image, dtype=np.uint8) + if len(image.shape) != 2 and len(image.shape) != 3: + raise ValueError("Image must be 2D or 3D") + + shape = ( + image.shape + if len(image.shape) == 3 + else (image.shape[0], image.shape[1], 1) + ) + shape = np.array(shape, dtype=np.int64) + self._share_npy_matrix(name, image, shape) + + def share_number(self, name, number): + if self.outputs.get(name) is None: + raise InvalidOutputNameException(f"{name} is not declared in outputs") + + if self.outputs[name].get("created", False): + self.outputs[name]["data"][:] = number + else: + wire_name = self.outputs[name]["wire"] + data_wire = self._create_wire( + wire_name, np.array([1], dtype=np.float64).nbytes + ) + self.outputs[name]["data"] = create_ndbuffer( + (1,), np.float64, data_wire.buf + ) + self.outputs[name]["data"][:] = number + self.outputs[name]["created"] = True + + def share_array(self, name, array): + if self.outputs.get(name) is None: + raise InvalidOutputNameException(f"{name} is not declared in outputs") + array = np.array(array, dtype=np.float64) + self._share_npy_matrix(name, array, np.array(array.shape, dtype=np.int64)) diff --git a/backend/staticfiles/synthesis/lib/parameters.py b/backend/staticfiles/synthesis/lib/parameters.py new file mode 100644 index 00000000..21358af1 --- /dev/null +++ b/backend/staticfiles/synthesis/lib/parameters.py @@ -0,0 +1,24 @@ +from lib.exceptions import InvalidParameterNameException + + +class Parameters: + def __init__(self, parameter_data) -> None: + self.parameters = parameter_data + + def read_number(self, name): + if self.parameters.get(name) is None: + raise InvalidParameterNameException(f"{name} is not declared in parameters") + + return float(self.parameters[name]) + + def read_string(self, name): + if self.parameters.get(name) is None: + raise InvalidParameterNameException(f"{name} is not declared in parameters") + + return str(self.parameters[name]) + + def read_bool(self, name): + if self.parameters.get(name) is None: + raise InvalidParameterNameException(f"{name} is not declared in parameters") + + return bool(self.parameters[name]) \ No newline at end of file diff --git a/backend/staticfiles/synthesis/lib/utils.py b/backend/staticfiles/synthesis/lib/utils.py new file mode 100644 index 00000000..7adf42c2 --- /dev/null +++ b/backend/staticfiles/synthesis/lib/utils.py @@ -0,0 +1,20 @@ +import numpy as np +from time import sleep, time + +def create_ndbuffer(shape, dtype, buffer): + return np.ndarray(shape, dtype=dtype, buffer=buffer) + + +class Synchronise: + def __init__(self, interval) -> None: + self.interval = interval + self.prev_time = None + + def __call__(self): + if self.prev_time is None: + self.prev_time = time() + else: + sleep(max(0, self.interval - (time() - self.prev_time))) + self.prev_time = time() + + # print(id(self), 'Executing:', time()) diff --git a/backend/staticfiles/synthesis/main.py b/backend/staticfiles/synthesis/main.py index 477d156d..fca777b1 100644 --- a/backend/staticfiles/synthesis/main.py +++ b/backend/staticfiles/synthesis/main.py @@ -1,286 +1,109 @@ -import os -import numpy as np -from lib.block import Block -from utils.wires.wire_str import read_string,share_string -from multiprocessing import Process,Queue,Pipe -from console import Worker, Console,set_theme,StyleSheet -from PyQt5.QtWidgets import QApplication -from PyQt5.QtCore import QThread +import importlib +import json import multiprocessing -import threading -import logging -from pathlib import Path -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler,LoggingEventHandler +import random +import string +from multiprocessing import shared_memory from time import sleep -#------------------- Reads path from provided arguments and loads files --------------------------# -def initialize(): - data = load_JSON('./data.json') - return data -#-------------------------------------------------------------------------------------------------# +from lib.inputs import Inputs +from lib.outputs import Outputs +from lib.parameters import Parameters +from lib.utils import Synchronise -#---- Loads .vc JSON object ----# -def load_JSON(file_path): - import json - with open(file_path) as obj: - data = json.load(obj) - return data -#-------------------------------# +BLOCK_DIRECTORY = 'modules' +FUNCTION_NAME = 'main' -#----- Program exit Logic ------# -def end_progam(): +def clean_shared_memory(names): + all_names = names[:] + all_names.extend([name + "_dim" for name in names]) + all_names.extend([name + "_shape" for name in names]) + for name in all_names: + try: + shm = shared_memory.SharedMemory(name, create=False) + shm.close() + shm.unlink() + except FileNotFoundError: + pass - for process in processes: - process.terminate() - process.join() - - exit(0) -#-------------------------------# - -#----- Frequency Monitor ------# - -def monitor_frequency(memories): - log_freq("<---------- Frequency Monitor ---------->\n") - for memory in memories: - message = memory.get() - for msg in message: - log_freq(msg) - sleep(0.2) - log_freq("END") - - -#-------------------------------# - - -#--------------------------- Creates Python Application from .vc File -------------------------# -def build(working_dir): - - # Reading Front-End File - data = initialize() - parameters = data['parameters'] - wires = data['wires'] - - - blocks = [] - parameters_list = [] - - for hex_id, block in data['blocks'].items(): - block_type = block['type'] - new_block = Block(hex_id, block_type) - new_block.name = block['name'] - if block_type in parameters: - for parameter in parameters[block_type]: - new_block.add_parameter(parameter['value'], parameter['id']) - blocks.append(new_block) +def main(): + """ + Main function + """ + with open("data.json") as json_file: + data = json.load(json_file) + blocks = data["blocks"] + wires = data["wires"] + parameters = data["parameters"] - ###################### Setting up communication between blocks ###################### - logger.info("Setting up connections...") + block_data = {} + wire_names = [] - for wire in wires: - - src_block, src_port = wire['source']['block'], wire['source']['port'] - tgt_block, tgt_port = wire['target']['block'], wire['target']['port'] - - wire_id = src_block+str(src_port) - - - # Connecting Paramters to Blocks - if src_port == 'constant-out': - param_value = parameters[src_block]['value'] - - for block in blocks: - if tgt_block == block.id: - block.add_parameter(param_value, tgt_port) - break - - # Connecting Wires to Blocks + source = wire["source"] + target = wire["target"] + + if source["block"] in parameters: + block_data[target["block"]] = block_data.get( + target["block"], {"inputs": {}, "outputs": {}, "parameters": {}} + ) + for param in parameters[source["block"]]: + parameter_data = {param["name"]: param["value"]} + block_data[target["block"]]["parameters"].update(parameter_data) else: + wire_name = "".join( + random.choices(string.ascii_uppercase + string.digits, k=10) + ) + wire_names.append(wire_name) + output_data = {source["name"]: {"wire": wire_name}} + input_data = {target["name"]: {"wire": wire_name}} + block_data[source["block"]] = block_data.get( + source["block"], {"inputs": {}, "outputs": {}, "parameters": {}} + ) + block_data[source["block"]]["outputs"].update(output_data) + block_data[target["block"]] = block_data.get( + target["block"], {"inputs": {}, "outputs": {}, "parameters": {}} + ) + block_data[target["block"]]["inputs"].update(input_data) + + + for block in blocks: + if blocks[block]["type"] in parameters: + block_data[block] = block_data.get(block, {"inputs": {}, "outputs": {}, "parameters": {}}) + for param in parameters[ blocks[block]["type"]]: + parameter_data = {param["name"]: param["value"]} + block_data[block]["parameters"].update(parameter_data) - for block in blocks: - if tgt_block == block.id: - block.connect_input_wire(wire_id, tgt_port) - break - - for block in blocks: - if src_block == block.id: - block.connect_output_wire(wire_id, src_port) - break - ###################################################################################### - - return blocks -# Configuring path for working directory -working_dir = '.' - -#function to setup logger -def setLogging(): - - logger = logging.getLogger(__name__) - - file_handler = logging.FileHandler(working_dir+'/logs/console.log',mode='w') - - formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s') - - file_handler.setFormatter(formatter) - logger.setLevel(logging.DEBUG) - logger.addHandler(file_handler) - return logger - -class MyHandler(LoggingEventHandler): - def __init__(self,win,working_dir): - self.dir = working_dir - self.w = win - def on_modified(self, event): - pass - def dispatch(self,event): - path = event.src_path - - if(path != working_dir+'/logs/console.log' ): - path = working_dir+'/logs/console.log' - with open(path) as f: - content = f.readlines() - #check if content is empty before logging else .pop will give error - if content: - self.w.loginfo_console(content.pop()) - - - - -#_____________________________________________________________________________________________________# -# # -# if you want to switch to adding errors to shared memory instead of logging in file # -# to show on console # -# sh_mem_console = share_string("ERROR#LOG") # -# # -# def sendto_console(sh_mem_console,msg): # -# to_send = np.array(msg, dtype=' required_frequency+1 or measured_frequency < required_frequency-1) and update != 0: - control_data[1] = 1.0 - (avg_exec_time*required_frequency) #Delay value - control_data[1] = control_data[1]/required_frequency - - if control_data[1] < 0: - control_data[1] = 0 - - control_data[0] = 0 - message = [name , 'Frequency: '+str(measured_frequency)+' CPU Time: '+str(round(avg_exec_time,3)*1000)+'ms'] - to_send = np.array(message, dtype=' Date: Fri, 20 May 2022 16:44:35 +0200 Subject: [PATCH 2/2] new: handle process synchronisation --- backend/staticfiles/synthesis/main.py | 7 +++- backend/synthesis/synthesis.py | 14 ++++++-- .../blocks/basic/code/code-model.ts | 11 ++++-- .../blocks/basic/code/code-widget.tsx | 36 ++++++++++++++++--- .../components/blocks/basic/code/styles.scss | 36 +++++++++++++++++++ 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/backend/staticfiles/synthesis/main.py b/backend/staticfiles/synthesis/main.py index fca777b1..55dd88e4 100644 --- a/backend/staticfiles/synthesis/main.py +++ b/backend/staticfiles/synthesis/main.py @@ -39,6 +39,7 @@ def main(): blocks = data["blocks"] wires = data["wires"] parameters = data["parameters"] + synchronize_frequency = data["synchronize_frequency"] block_data = {} wire_names = [] @@ -77,6 +78,9 @@ def main(): for param in parameters[ blocks[block]["type"]]: parameter_data = {param["name"]: param["value"]} block_data[block]["parameters"].update(parameter_data) + if block in synchronize_frequency or blocks[block]["type"] in synchronize_frequency: + block_data[block] = block_data.get(block, {"inputs": {}, "outputs": {}, "parameters": {}}) + block_data[block]["frequency"] = synchronize_frequency.get(block, synchronize_frequency.get(blocks[block]["type"], 30)) processes = [] @@ -88,8 +92,9 @@ def main(): inputs = Inputs(block_data[block_id]["inputs"]) outputs = Outputs(block_data[block_id]["outputs"]) parameters = Parameters(block_data[block_id]["parameters"]) + freq = block_data[block_id]["frequency"] processes.append( - multiprocessing.Process(target=method, args=(inputs, outputs, parameters, Synchronise(1/30))) + multiprocessing.Process(target=method, args=(inputs, outputs, parameters, Synchronise(1 / (freq if freq != 0 else 30)))) ) for process in processes: diff --git a/backend/synthesis/synthesis.py b/backend/synthesis/synthesis.py index 5c9725a7..68688757 100644 --- a/backend/synthesis/synthesis.py +++ b/backend/synthesis/synthesis.py @@ -15,6 +15,13 @@ } +def get_number_or_default(num, default): + try: + num = float(num) + return num + except ValueError: + return default + def syntheize_modules(data: dict, zipfile: InMemoryZip) -> Tuple[InMemoryZip, Dict[str, bool]]: '''Synthesize python code for different blocks as well as user code blocks. Different blocks present in the project is collected. @@ -25,6 +32,7 @@ def syntheize_modules(data: dict, zipfile: InMemoryZip) -> Tuple[InMemoryZip, Di dependencies = {} blocks = {} parameters = {} + synhronize_frequency = {} optional_files = {} for key, dependency in data['dependencies'].items(): @@ -34,6 +42,7 @@ def syntheize_modules(data: dict, zipfile: InMemoryZip) -> Tuple[InMemoryZip, Di # If code, generate python file. script = block['data']['code'] script_name = dependency['package']['name'] + synhronize_frequency[key] = get_number_or_default(block['data'].get('frequency', 30), 30) # Mark the optional files required by the current block, if needed. if script_name in OPTIONAL_FILES: optional_files[script_name] = True @@ -60,19 +69,20 @@ def syntheize_modules(data: dict, zipfile: InMemoryZip) -> Tuple[InMemoryZip, Di code_name = "Code_"+str(count) count += 1 script = block['data']['code'] + synhronize_frequency[block_id] = get_number_or_default(block['data'].get('frequency', 30), 30) zipfile.append(f'{BLOCK_DIRECTORY}/{code_name}.py', script) blocks[block_id] = {'name': code_name, 'type': block_type} elif block_type == 'basic.constant': # If constant, it is a parameter. # Since this is a parameter at project level, we make the key as constant block ID - parameters[block_id] = {'name': block['data']['name'], 'value': block['data']['value']} + parameters[block_id] = [{'name': block['data']['name'], 'value': block['data']['value']}] else: # TODO: Check how input and output blocks behave. # This behaviour is for Package blocks only blocks[block_id] = {'name': dependencies[block_type], 'type': block_type} - data = {'blocks': blocks, 'parameters': parameters, 'wires': data['design']['graph']['wires']} + data = {'blocks': blocks, 'parameters': parameters, 'synchronize_frequency': synhronize_frequency, 'wires': data['design']['graph']['wires']} zipfile.append('data.json', json.dumps(data)) return zipfile, optional_files diff --git a/frontend/src/components/blocks/basic/code/code-model.ts b/frontend/src/components/blocks/basic/code/code-model.ts index bd782177..bef1a5ce 100644 --- a/frontend/src/components/blocks/basic/code/code-model.ts +++ b/frontend/src/components/blocks/basic/code/code-model.ts @@ -20,6 +20,7 @@ export interface CodeBlockModelOptions extends BaseModelOptions { */ interface CodeBlockData { code: string; + frequency: string; params?: PortName[], ports: { in: PortName[], @@ -45,6 +46,7 @@ export class CodeBlockModel extends BaseModel { return { name: port } }) || [], @@ -69,7 +71,8 @@ export class CodeBlockModel extends BaseModel
+ + Freq: + Hz, + }} + type="number" + margin="dense" + value={this.state.frequency} + onChange={this.handleFrequencyInput} + onWheel={this.blockScrollEvents} + className='block-basic-code-frequency-input' + /> + + }> +
{this.props.node.getParameters().map((port) => { @@ -149,6 +172,11 @@ export class CodeBlockWidget extends React.Component) => { + this.setState({frequency: event.target.value}); + this.props.node.data.frequency = event.target.value; + } + /** * Block all mouse click events, so that its not handled by the editor. * This is to make sure that dragging or selecting inside the text area is handled within the text area. diff --git a/frontend/src/components/blocks/basic/code/styles.scss b/frontend/src/components/blocks/basic/code/styles.scss index 8251dc1f..3ed15d77 100644 --- a/frontend/src/components/blocks/basic/code/styles.scss +++ b/frontend/src/components/blocks/basic/code/styles.scss @@ -2,6 +2,42 @@ .block-basic-code { + .block-basic-code-frequency { + font-size: small; + input[type=number]::-webkit-inner-spin-button, + input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + margin: 0; + } + + .MuiCardHeader-title { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + } + + .block-basic-code-frequency-text { + font-size: small; + padding-right: 1em; + padding-left: 1em; + } + + .block-basic-code-frequency-input { + display: inline-block; + max-width: 100px; + padding: 2px; + margin: 0; + + input { + padding-top: 5px; + padding-bottom: 5px; + } + } + } + + .block-basic-code-parameters { display: flex; flex-direction: row;