import copy import logging import os import re import common as doc def __doc_obj__(label, obj, id): node = f' {id} [label="{label}\n{obj["Class"]} | ' return node def __doc_common_gam__(name, obj, edges, thread): inputs = "" outputs = "
" if "InputSignals" in obj and len(obj["InputSignals"]): for i, (l, sig) in enumerate(obj["InputSignals"].items()): alias = doc.signal_alias(l, sig) mtype = doc.signal_type(sig) datasrc = sig["DataSource"] edges.append( doc.edge( doc.signal(datasrc, alias, mtype), doc.signal(name, l, mtype), thread, ) ) if i != 0: inputs += "
" inputs += f"" else: inputs += "" if "OutputSignals" in obj and len(obj["OutputSignals"]): for i, (l, sig) in enumerate(obj["OutputSignals"].items()): alias = doc.signal_alias(l, sig) mtype = doc.signal_type(sig) datasrc = sig["DataSource"] edges.append( doc.edge( doc.signal(name, l, mtype), doc.signal(datasrc, alias, mtype), thread, ) ) if i != 0: outputs += "
" outputs += f"" else: outputs += "" return ( "
{l}
{l}
" + inputs + "
" + outputs + "", edges, ) def __doc_triggered_iogam__(id, obj, edges, thread): node = "" if len(obj["InputSignals"]) > 0: name = id[2:] node = __gam__(id, name, "TriggeredIOGAM") recl = copy.deepcopy(obj) trg = list(obj["InputSignals"].keys())[0] sig = obj["InputSignals"][trg] alias = doc.signal_alias(trg, sig) mtype = doc.signal_type(sig) datasrc = sig["DataSource"] edges.append( doc.edge( doc.signal(datasrc, alias, mtype), doc.signal(id, trg, mtype), thread, ) ) node += f"Trigger
" del recl["InputSignals"][trg] sigs, edges = __doc_common_gam__(id, recl, edges, thread) node += sigs node += ">];" return node, edges __set_keys__ = ( "Alias", "Type", "DataSource", "NumberOfElements", "NumberOfDimensions", "Ranges", ) def __doc_iogam__(id, obj, edges, thread): node = "" if "InputSignals" in obj and "OutputSignals" in obj: inputs, outputs = obj["InputSignals"], obj["OutputSignals"] output_names = list(outputs.keys()) input_names = list(inputs.keys()) input_size = 0 output_size = 0 out_i = 0 in_i = 0 counter = 0 while in_i < len(inputs) and out_i < len(outputs): irows = [] orows = [] bold = False while input_size <= output_size and in_i < len(inputs): in_label = input_names[in_i] input = inputs[in_label] input_size += doc.signal_byte_size(input) in_i += 1 alias = doc.signal_alias(in_label, input) mtype = doc.signal_type(input) if mtype[2]: bold = True irow = f"{doc.type_to_string(mtype)}" for key, val in input.items(): if key not in __set_keys__: bold = True irow += f"
{key}: {val}" datasrc = input["DataSource"] edges.append( doc.edge( doc.signal(datasrc, alias, mtype), doc.signal(f"{id}_{counter}", f"{len(irows)}", mtype), thread, ) ) irows.append(irow) while output_size < input_size and out_i < len(outputs): out_label = output_names[out_i] output = outputs[out_label] output_size += doc.signal_byte_size(output) out_i += 1 alias = doc.signal_alias(out_label, output) mtype = doc.signal_type(output) if mtype[2]: bold = True orow = f"{doc.type_to_string(mtype)}" for key, val in output.items(): if key not in __set_keys__: bold = True orow += f"
{key}: {val}" datasrc = output["DataSource"] edges.append( doc.edge( doc.signal(f"{id}_{counter}", f"{len(orows)}", mtype), doc.signal(datasrc, alias, mtype), thread, ) ) orows.append(orow) fill = "#eee" fcolor = "#000" total = max(len(orows), len(irows)) if bold or total > 1: fill = "#eee" bold = True else: fill = "#eee" inode = f"{id}_{counter} [shape=plaintext," inode += f'tooltip=" IOGAM::{id} ", label=<' inode += "" if bold: for i in range(total): inode += "" if i < len(irows): inode += f'" if i < len(orows): inode += f'" inode += "" if i < total - 1: inode += "
" inode += "
len(irows): inode += f' rowspan="{len(orows)}"' inode += ">" inode += f'' inode += irows[i] inode += "' inode += orows[i] inode += "
>" inode += "];\n" if not bold: e_in, e_out = edges[-2:] edges = edges[:-2] edges.append((e_in[0], e_out[1], thread)) inode = "" node += inode irows = [] orows = [] counter += 1 return node, edges def __doc_constgam__(id, obj, edges, thread): node = f" {id} [shape=plaintext, label=<" node += f"" if "OutputSignals" in obj: for i, (l, sig) in enumerate(obj["OutputSignals"].items()): datasrc = sig["DataSource"] alias = doc.signal_alias(l, sig) mtype = doc.signal_type(sig) edges.append( doc.edge( doc.signal(id, l, mtype), doc.signal(datasrc, alias, mtype), thread, ) ) node += "
" node += f'' node += f'' node += "
{id[2:]}
{obj['Class']}
{doc.type_to_string(mtype)}
{l}
' node += f'{sig["Default"]}
>];\n" return node, edges def __doc_message_gam__(id, obj, edges, thread): label = id[2:] node = f' {id} [label="{label}\n{obj["Class"]} | ' inputs = "" if "InputSignals" in obj: for i, (l, sig) in enumerate(obj["InputSignals"].items()): alias = doc.signal_alias(l, sig) mtype = doc.signal_type(sig) datasrc = sig["DataSource"] edges.append( doc.edge( doc.signal(datasrc, alias, mtype), doc.signal(id, l, mtype), thread, ) ) if i != 0: inputs += " | " inputs += f" {l}" if inputs: node += inputs + '", style=filled, fillcolor=lightyellow];' else: node = "" if node: add, edges = __doc_events__(id, obj, edges, thread) node += add return node, edges def __doc_events__(id, obj, edges, thread): nodes = "" bgcolor = "lightyellow" for event_id, event in obj["+Events"].items(): if event_id[0] == "+": event_id = event_id[1:] trigger = event["EventTrigger"] message_ids = [id[1:] for id in event if id[0] == "+"] for msg_id in message_ids: nodes += f" {id}_{event_id}_{msg_id} [shape=plaintext, label=<" nodes += '' nodes += f"" msg = event[f"+{msg_id}"] fn = msg["Function"] nodes += f"" nodes += f"
When" for i, (sig, val) in enumerate(trigger.items()): if i > 0: nodes += "
" nodes += f"{sig} == {val}" nodes += f"
Destination{msg['Destination']}
Message" if fn == "SetOutput": params = msg["+Parameters"] nodes += f"set {params['SignalName']} to {params['SignalValue']}" else: nodes += f"{fn} " nodes += "
>];\n" nodes += f" {id}:e -> {id}_{event_id}_{msg_id}:w" nodes += " [arrowhead=box, constraint=true];\n" return nodes, edges def __sanitize__(expression): expression = expression.strip() expression = re.sub(" +", " ", expression) if expression[0] == "\n": expression = expression[1:] if expression[-1] == "\n": expression = expression[:-1] expression = ( expression.replace("&", "&") .replace(">", ">") .replace("<", "<") .replace("\n", "
") ) return expression def __gam__(id, label, cls): node = f" {id} [shape=plaintext, label=<" node += f"
" return node def __doc_mathgam__(id, obj, edges, thread): label = id[2:] import sys node = __gam__(id, label, obj["Class"]) node += ( "
" ) sigs, edges = __doc_common_gam__(id, obj, edges, thread) if sigs: node += sigs + "
{label}
{cls}
" + __sanitize__(obj["Expression"]) + "
>];" else: node = "" return node, edges __gams_fns__ = { "IOGAM": __doc_iogam__, "ConstantGAM": __doc_constgam__, "TriggeredIOGAM": __doc_triggered_iogam__, "MathExpressionGAM": __doc_mathgam__, "MessageGAM": __doc_message_gam__, } def __doc_gam__(label, obj, edges, thread, classfilter=True): id = f"fn{label}" cls = obj["Class"] if classfilter and cls in __gams_fns__: return __gams_fns__[cls](id, obj, edges, thread) node = __gam__(id, label, cls) sigs, edges = __doc_common_gam__(id, obj, edges, thread) if sigs: node += sigs + ">];" else: node = "" return node, edges def __process_gamds__(name, cls, signals, edges): node = "" outputs = {} inputs = {} done = False to_keep = [] while not done: signals = {} for edge in edges: if edge[0][0] == name: sig_name = edge[0][1] signals[sig_name] = edge[0][2] if sig_name not in outputs: outputs[sig_name] = [] outputs[sig_name].append(edge) elif edge[1][0] == name: sig_name = edge[1][1] signals[sig_name] = edge[1][2] if sig_name not in inputs: inputs[sig_name] = [] inputs[sig_name].append(edge) to_reduce = [] to_keep = [] done = True for sig_name in signals: if sig_name in inputs and sig_name in outputs: to_reduce.append(sig_name) else: to_keep.append(sig_name) for sig_name in to_reduce: ins = inputs[sig_name] outs = outputs[sig_name] for edge in ins: if edge in edges: done = False edges.remove(edge) for edge in outs: if edge in edges: done = False edges.remove(edge) for in_edge in ins: for out_edge in outs: edge = (in_edge[0], out_edge[1], in_edge[-1]) edges.append(edge) for i, sig_name in enumerate(to_keep): if i > 0: node += " | " sig_type = signals[sig_name] node += ( f'' f"{sig_name} ({doc.type_to_string(sig_type)})" "" ) if node: label = f""" {node}
{name}
{cls}
""" node = f'{name} [shape=plaintext, label=<{label}>, color="#0af"];\n' return node, edges def __process_ds__(name, obj, edges): cls = obj["Class"] sigs = {} node = "" thread = "" multi_thread = False for edge in edges: if edge[0][0] == name: sigs[edge[0][1]] = edge[0][2] if not thread: thread = edge[-1] elif thread != edge[-1]: multi_thread = True if edge[1][0] == name: sigs[edge[1][1]] = edge[1][2] if not thread: thread = edge[-1] elif thread != edge[-1]: multi_thread = True if sigs: if cls == "GAMDataSource" or cls == "RealTimeThreadAsyncBridge": node, edges = __process_gamds__(name, cls, sigs, edges) else: order = [] lost = [] if "Signals" in obj: for sig in obj["Signals"]: if sig in sigs: order.append(sig) else: order = list(sigs.keys()) for sig in sigs: if sig not in order: lost.append(sig) node = f"{name} [shape=plaintext; label=<" node += '\n' node += f"\n" for i, sig_name in enumerate(order): node += f'\n" for sig_name in lost: logging.warning(f"Lost signal {name}:{sig_name}") node += f'\n" node += '
{name}
{cls}
' sig_type = sigs[sig_name] node += f"{sig_name} ({doc.type_to_string(sig_type)})" node += "
' sig_type = sigs[sig_name] node += f"{sig_name} ({doc.type_to_string(sig_type)})" node += "
>, color="#0af"];\n' if not multi_thread and node: node = f"subgraph cluster_{thread}" + "{\n" + node + "};\n" return node, edges def __process_edges__(edges, datasources): # remove duplicated edges clean_edges = [] for edge in edges: if edge not in clean_edges: clean_edges.append(edge) edges = clean_edges # generate arrows arrows = "" for source, target, _ in edges: src_obj = source[0] src_sig = doc.format_name(source[1]) trg_obj = target[0] trg_sig = doc.format_name(target[1]) a = f"{src_obj}" b = f"{trg_obj}" ranked = True if src_obj in datasources: a += f":{src_sig}" else: a += f":out_{src_sig}" if trg_obj in datasources: b += f":{trg_sig}" else: b += f":in_{trg_sig}" in_type = doc.type_to_string(source[2]) out_type = doc.type_to_string(target[2]) mtype = in_type if in_type == out_type else f"{in_type} -> {out_type}" arrows += f'{a}:e -> {b}:w [tooltip=" {mtype} ", labelfloat=false' arrows += f", constraint={ranked}];\n" return arrows def __state_full_graph__(label, state, app): graph = f"digraph {label[1:]}" + "{\n" graph += "ranksep = 2;\n" graph += "nodesep = 0.05;\n" graph += 'fontname="mono"\n' graph += "rankdir = LR;\n" graph += "node [shape=Mrecord];\n" graph += f"label={label[1:]}>;\n" graph += "fontsize=40;\n" graph += "\n" arrows = "" edges = [] datasources = {} if "+Threads" in state: for tname, obj in state["+Threads"].items(): if tname[0] == "+": graph += f"subgraph cluster_{tname[1:]}" + "{\n" if "Functions" in obj: fns = obj["Functions"] for fn in fns: obj = app["+Functions"][f"+{fn}"] node, edges = __doc_gam__(fn, obj, edges, tname[1:]) graph += f"{node}\n" graph += f"\n label = {tname[1:]} >;\n" graph += " fontsize = 24.0;\n" graph += " penwidth = 3;\n" graph += ' color = "#1A1A1A";\n' graph += "}\n" if "+Data" in app: for key, obj in app["+Data"].items(): if key[0] == "+": datasources[key[1:]] = obj["Class"] node, edges = __process_ds__(key[1:], obj, edges) graph += node arrows = __process_edges__(edges, datasources) graph += "\n\n" + arrows graph += "}\n" fname = f"{label[1:]}" gpath = os.path.join(doc.path(), f"{fname}_full.dot") with open(gpath, "w") as file: file.write(graph) return gpath def __simple_gam__(label, obj): cls = obj["Class"] id = f"fn{doc.format_name(label)}" style = "style=rounded" if cls == "IOGAM": style = 'style="rounded,filled", fillcolor="#eee"' elif cls == "MessageGAM": style = 'style="rounded,filled", fillcolor=lightyellow' if "InputSignals" in obj: for _, signal in obj["InputSignals"].items(): ds = f'ds{doc.format_name(signal["DataSource"])}' if "OutputSignals" in obj: for _, signal in obj["OutputSignals"].items(): ds = f'ds{doc.format_name(signal["DataSource"])}' return f"{id}[label=<{label}
{cls}>, {style}]\n" def __simple_ds__(label, obj, edges): cls = obj["Class"] thread = "" multi_thread = False sources = [] targets = [] id = f"ds{doc.format_name(label)}" for edge in edges: if edge[0][0] == label: if not thread: thread = edge[-1] elif thread != edge[-1]: multi_thread = True if edge[1][0] == label: if not thread: thread = edge[-1] elif thread != edge[-1]: multi_thread = True fillcolor = "lightblue" bordercolor = "#0af" style = "penwidth=2" if cls == "GAMDataSource": fillcolor = "#eee" node = f"{id}[label=<{label}
{cls}>, " node += f'{style}, fillcolor="{fillcolor}", color="{bordercolor}"]\n' if thread and not multi_thread: node = f"subgraph cluster_{thread}" + "{\n " + node + "}" return node def __simple_id__(name, datasources): if name in datasources: return f"ds{doc.format_name(name)}" return name def __simple_edges__(edges, datasources): arrows = "" done = [] for source, target, _ in edges: src_obj = __simple_id__(source[0], datasources) trg_obj = __simple_id__(target[0], datasources) if (src_obj, trg_obj) not in done: arrows += f"{src_obj}:e -> {trg_obj}:w\n" done.append((src_obj, trg_obj)) return arrows def __state_simple_graph__(label, state, app): graph = f"digraph {label[1:]}" + "{\n" graph += "rankdir = LR;\n" graph += 'node [shape=rect, style="filled", fillcolor=white];\n' graph += f"label={label[1:]}>;\n" graph += "fontsize=40;\n" graph += "\n" arrows = "" edges = [] datasources = {} if "+Threads" in state: for tname, obj in state["+Threads"].items(): if tname[0] == "+": graph += f"subgraph cluster_{tname[1:]}" + "{\n" if "Functions" in obj: fns = obj["Functions"] for fn in fns: obj = app["+Functions"][f"+{fn}"] _, edges = __doc_gam__( fn, obj, edges, tname[1:], classfilter=False ) graph += f" {__simple_gam__(fn, obj)}" graph += f"\n label = {tname[1:]} >;\n" graph += " fontsize = 24.0;\n" graph += " penwidth = 3;\n" graph += ' color = "#1A1A1A";\n' graph += "}\n" if "+Data" in app: for key, obj in app["+Data"].items(): if key[0] == "+": datasources[key[1:]] = obj["Class"] node, edges = __process_ds__(key[1:], obj, edges) if node: graph += __simple_ds__(key[1:], obj, edges) graph += __simple_edges__(edges, datasources) # arrows = __process_edges__(edges, datasources) # graph += "\n" + ext + "\n" + arrows graph += "}\n" fname = f"{label[1:]}" gpath = os.path.join(doc.path(), f"{fname}_simple.dot") with open(gpath, "w") as file: file.write(graph) return gpath def doc_state(label, state, app): fname = f"{label[1:]}" if doc.build(): logging.info(f"Building svg grpah {fname}") src_path = __state_full_graph__(label, state, app) os.system(f"dot -Tsvg -o{os.path.join(doc.path(), fname)}_full.svg {src_path}") src_path = __state_simple_graph__(label, state, app) os.system(f"dot -Tsvg -o{os.path.join(doc.path(), fname)}.svg {src_path}") return f"![{fname} state]({fname}.svg)" + "{width=100% height=90%}\n"