69 for (
int i = 0; i < m_nodes.size(); ++i) {
70 if (m_nodes[i].
id == nodeId) {
81 for (
int i = 0; i < m_nodes.size(); ++i) {
82 if (m_nodes[i].
id == nodeId) {
94 for (
int i = 0; i < m_nodes.size(); ++i) {
95 if (m_nodes[i].
id == nodeId) {
121 for (
const MnaNode& n : m_nodes) {
122 if (n.id == nodeId) {
134 const QString& dstNodeId,
const QString& dstPortName)
142 bool srcFound =
false;
144 if (p.
name == srcPortName) {
155 for (
int i = 0; i < dstNode.
inputs.size(); ++i) {
156 if (dstNode.
inputs[i].name == dstPortName) {
157 dstNode.
inputs[i].sourceNodeId = srcNodeId;
158 dstNode.
inputs[i].sourcePortName = srcPortName;
173 auto addError = [&](
const QString& msg) {
181 QMap<QString, QSet<QString>> adj;
182 QMap<QString, int> inDegree;
183 for (
const MnaNode& n : m_nodes) {
184 if (!adj.contains(n.id)) {
187 if (!inDegree.contains(n.id)) {
193 for (
const MnaNode& n : m_nodes) {
194 for (
const MnaPort& p : n.inputs) {
197 addError(QStringLiteral(
"Node '%1' input port '%2' references unknown source node '%3'")
210 QQueue<QString> queue;
211 for (
auto it = inDegree.constBegin(); it != inDegree.constEnd(); ++it) {
212 if (it.value() == 0) {
213 queue.enqueue(it.key());
218 while (!queue.isEmpty()) {
219 QString current = queue.dequeue();
221 for (
const QString& neighbor : adj.value(current)) {
222 inDegree[neighbor]--;
223 if (inDegree[neighbor] == 0) {
224 queue.enqueue(neighbor);
229 if (visited != m_nodes.size()) {
230 addError(QStringLiteral(
"Graph contains a cycle"));
235 for (
const MnaNode& n : m_nodes) {
236 if (!registry.
hasOp(n.opType)) {
237 addError(QStringLiteral(
"Node '%1' has unregistered op type '%2'")
238 .arg(n.id, n.opType));
243 QStringList schemaErrors;
244 if (!schema.
validate(n, &schemaErrors)) {
245 for (
const QString& e : schemaErrors) {
246 addError(QStringLiteral(
"Node '%1': %2").arg(n.id, e));
254 for (
const MnaPort& np : n.inputs) {
256 addError(QStringLiteral(
"Node '%1': required input port '%2' is not connected")
257 .arg(n.id, sp.
name));
264 for (
const MnaNode& n : m_nodes) {
265 for (
const MnaPort& inp : n.inputs) {
269 for (
const MnaNode& srcNode : m_nodes) {
272 for (
const MnaPort& srcOut : srcNode.outputs) {
277 addError(QStringLiteral(
"Edge %1.%2 -> %3.%4: data kind mismatch (%5 != %6)")
278 .arg(srcNode.id, srcOut.
name, n.id, inp.
name)
279 .arg(
static_cast<int>(srcOut.
dataKind))
280 .arg(
static_cast<int>(inp.
dataKind)));
298 QMap<QString, QSet<QString>> adj;
299 QMap<QString, int> inDegree;
301 for (
const MnaNode& n : m_nodes) {
306 for (
const MnaNode& n : m_nodes) {
307 for (
const MnaPort& p : n.inputs) {
317 QQueue<QString> queue;
318 for (
auto it = inDegree.constBegin(); it != inDegree.constEnd(); ++it) {
319 if (it.value() == 0) {
320 queue.enqueue(it.key());
325 while (!queue.isEmpty()) {
326 QString current = queue.dequeue();
327 sorted.append(current);
328 for (
const QString& neighbor : adj.value(current)) {
329 inDegree[neighbor]--;
330 if (inDegree[neighbor] == 0) {
331 queue.enqueue(neighbor);
345 QStringList upstream;
351 QSet<QString> visited;
352 QQueue<QString> queue;
361 while (!queue.isEmpty()) {
362 QString current = queue.dequeue();
363 upstream.append(current);
382 QStringList downstream;
388 QMap<QString, QSet<QString>> adj;
389 for (
const MnaNode& n : m_nodes) {
392 for (
const MnaNode& n : m_nodes) {
393 for (
const MnaPort& p : n.inputs) {
400 QSet<QString> visited;
401 QQueue<QString> queue;
402 for (
const QString& neighbor : adj.value(nodeId)) {
403 if (!visited.contains(neighbor)) {
404 visited.insert(neighbor);
405 queue.enqueue(neighbor);
409 while (!queue.isEmpty()) {
410 QString current = queue.dequeue();
411 downstream.append(current);
412 for (
const QString& neighbor : adj.value(current)) {
413 if (!visited.contains(neighbor)) {
414 visited.insert(neighbor);
415 queue.enqueue(neighbor);
428 for (
const MnaNode& n : m_nodes) {
446 for (
const MnaNode& n : m_nodes) {
447 nodesArr.append(n.toJson());
449 json[QStringLiteral(
"nodes")] = nodesArr;
455 arr.append(p.toJson());
457 json[QStringLiteral(
"graph_inputs")] = arr;
464 arr.append(p.toJson());
466 json[QStringLiteral(
"graph_outputs")] = arr;
471 if (!ptJson.isEmpty()) {
472 json[QStringLiteral(
"param_tree")] = ptJson;
484 const QJsonArray nodesArr = json.value(QStringLiteral(
"nodes")).toArray();
485 for (
const QJsonValue& v : nodesArr) {
489 const QJsonArray giArr = json.value(QStringLiteral(
"graph_inputs")).toArray();
490 for (
const QJsonValue& v : giArr) {
494 const QJsonArray goArr = json.value(QStringLiteral(
"graph_outputs")).toArray();
495 for (
const QJsonValue& v : goArr) {
499 if (json.contains(QStringLiteral(
"param_tree"))) {
513 for (
const MnaNode& n : m_nodes) {
514 nodesArr.append(n.toCbor());
516 cbor.insert(QStringLiteral(
"nodes"), nodesArr);
521 arr.append(p.toCbor());
523 cbor.insert(QStringLiteral(
"graph_inputs"), arr);
529 arr.append(p.toCbor());
531 cbor.insert(QStringLiteral(
"graph_outputs"), arr);
543 const QCborArray nodesArr = cbor.value(QStringLiteral(
"nodes")).toArray();
544 for (
const QCborValue& v : nodesArr) {
548 const QCborArray giArr = cbor.value(QStringLiteral(
"graph_inputs")).toArray();
549 for (
const QCborValue& v : giArr) {
553 const QCborArray goArr = cbor.value(QStringLiteral(
"graph_outputs")).toArray();
554 for (
const QCborValue& v : goArr) {
In-memory directed acyclic graph of MnaNode operations — connectivity, validation,...
Process-wide singleton catalog mapping opType strings to their MnaOpSchema and (for built-in ops) exe...
MNE Analysis Container Format (mna/mnx).
@ Custom
User-defined data kind.
MnaNode & node(const QString &nodeId)
void addNode(const MnaNode &node)
bool validate(QStringList *errors=nullptr) const
QStringList dirtyNodes() const
bool hasNode(const QString &nodeId) const
QStringList downstreamNodes(const QString &nodeId) const
void removeNode(const QString &nodeId)
QList< MnaPort > graphInputs
Named, typed entry points.
QList< MnaPort > graphOutputs
Named, typed exit points.
MnaParamTree paramTree
Hierarchical parameter store with formula-driven bindings.
QList< MnaNode > & nodes()
QJsonObject toJson() const
QStringList topologicalSort() const
static MnaGraph fromJson(const QJsonObject &json)
QStringList upstreamNodes(const QString &nodeId) const
bool connect(const QString &srcNodeId, const QString &srcPortName, const QString &dstNodeId, const QString &dstPortName)
static MnaGraph fromCbor(const QCborMap &cbor)
Single executable step in an MNA pipeline graph, with attributes, typed ports, exec mode,...
QList< MnaPort > inputs
Input ports.
static MnaNode fromJson(const QJsonObject &json)
static MnaNode fromCbor(const QCborMap &cbor)
QList< MnaPort > outputs
Output ports.
Process-wide lookup from opType to MnaOpSchema and implementation function.
static MnaOpRegistry & instance()
bool hasOp(const QString &opType) const
MnaOpSchema schema(const QString &opType) const
bool required
Must be connected?
Operation schema for graph validation.
QList< MnaOpSchemaPort > inputPorts
Expected input ports.
bool validate(const MnaNode &node, QStringList *errors=nullptr) const
static MnaParamTree fromJson(const QJsonObject &obj)
Named, typed port on an MNA graph node with upstream link and optional real-time stream binding.
QString name
Port name (unique within a node).
QString sourcePortName
Which output port on that node?
MnaDataKind dataKind
Data kind flowing through this port.
static MnaPort fromJson(const QJsonObject &json)
QString sourceNodeId
Which node produces this input? (empty → graph-level input).
static MnaPort fromCbor(const QCborMap &cbor)