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"| {l} |
"
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"| {l} |
"
else:
outputs += " |
"
return (
"| " + 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",
"Samples",
"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'| len(irows):
inode += f' rowspan="{len(orows)}"'
inode += ">"
inode += f''
inode += irows[i]
inode += " | "
if i < len(orows):
inode += f'"
inode += f''
inode += orows[i]
inode += " | "
inode += "
"
if i < total - 1:
inode += "
"
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"{id[2:]} {obj['Class']} |
"
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'{doc.type_to_string(mtype)} {l} | '
node += f''
node += f'{sig["Default"]} |
'
node += "
>];\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"| When | "
for i, (sig, val) in enumerate(trigger.items()):
if i > 0:
nodes += " "
nodes += f"{sig} == {val}"
nodes += f" |
| Destination | "
msg = event[f"+{msg_id}"]
fn = msg["Function"]
nodes += f"{msg['Destination']} |
"
nodes += f"| 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"{label} {cls} |
"
return node
def __doc_mathgam__(id, obj, edges, thread):
label = id[2:]
import sys
node = __gam__(id, label, obj["Class"])
node += (
"| "
+ __sanitize__(obj["Expression"])
+ " |
"
)
sigs, edges = __doc_common_gam__(id, obj, edges, thread)
if sigs:
node += sigs + "
>];"
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 = 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"{name} {cls} |
\n"
for i, sig_name in enumerate(order):
node += f'| '
sig_type = sigs[sig_name]
node += f"{sig_name} ({doc.type_to_string(sig_type)})"
node += " |
\n"
for sig_name in lost:
logging.warning(f"Lost signal {name}:{sig_name}")
node += f'| '
sig_type = sigs[sig_name]
node += f"{sig_name} ({doc.type_to_string(sig_type)})"
node += " |
\n"
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"" + "{width=100% height=90%}\n"