Vuo 2.4.4
Loading...
Searching...
No Matches
VuoCompilerGraph.cc
Go to the documentation of this file.
1
10#include "VuoCable.hh"
11#include "VuoCompiler.hh"
12#include "VuoCompilerGraph.hh"
13#include "VuoCompilerCable.hh"
14#include "VuoCompilerChain.hh"
18#include "VuoCompilerIssue.hh"
19#include "VuoCompilerNode.hh"
25#include "VuoComposition.hh"
26#include "VuoNode.hh"
27#include "VuoNodeClass.hh"
28#include "VuoPublishedPort.hh"
29#include "VuoStringUtilities.hh"
30#include <sstream>
31
42VuoCompilerGraph::VuoCompilerGraph(VuoCompilerComposition *composition, VuoCompiler *compiler, set<VuoCompilerCable *> potentialCables)
43{
44 VuoCompilerNode *publishedInputNode = nullptr;
45 VuoCompilerNode *publishedOutputNode = nullptr;
46 VuoCompilerNode *publishedInputTriggerNode = nullptr;
47 bool ownsPublishedNodeClasses = false;
48
49 VuoCompilerNodeClass *triggerNodeClass = nullptr;
50 if (compiler)
51 {
52 triggerNodeClass = compiler->getNodeClass("vuo.event.spinOffEvent2");
53 }
54 else
55 {
56 VuoPortClass *refreshPortClass = (new VuoCompilerInputEventPortClass("refresh"))->getBase();
57 VuoCompilerTriggerPortClass *triggerPortClass = new VuoCompilerTriggerPortClass("spunOff");
58 vector<VuoPortClass *> inputPortClasses;
59 vector<VuoPortClass *> outputPortClasses(1, triggerPortClass->getBase());
60 VuoNodeClass *baseNodeClass = new VuoNodeClass("vuo.event.spinOffEvent2", refreshPortClass, inputPortClasses, outputPortClasses);
62 }
63
64 vector<VuoPublishedPort *> publishedInputPorts = composition->getBase()->getPublishedInputPorts();
65 vector<VuoPublishedPort *> publishedOutputPorts = composition->getBase()->getPublishedOutputPorts();
66
67 if (! (publishedInputPorts.empty() && publishedOutputPorts.empty()) )
68 {
69 if (compiler)
70 {
71 publishedInputNode = compiler->createPublishedInputNode(publishedInputPorts)->getCompiler();
72 publishedOutputNode = compiler->createPublishedOutputNode(publishedOutputPorts)->getCompiler();
73
74 ownsPublishedNodeClasses = false;
75 }
76 else
77 {
78 VuoNodeClass *publishedInputNodeClass = VuoCompilerPublishedInputNodeClass::newNodeClass(publishedInputPorts);
79 VuoNodeClass *publishedOutputNodeClass = VuoCompilerPublishedOutputNodeClass::newNodeClass(publishedOutputPorts);
80 publishedInputNode = publishedInputNodeClass->getCompiler()->newNode()->getCompiler();
81 publishedOutputNode = publishedOutputNodeClass->getCompiler()->newNode()->getCompiler();
82
83 ownsPublishedNodeClasses = true;
84 }
85
86 publishedInputTriggerNode = triggerNodeClass->newNode()->getCompiler();
88 }
89
90 VuoCompilerNode *manuallyFirableTriggerNode = triggerNodeClass->newNode()->getCompiler();
92
93 initializeInstance(composition, potentialCables, publishedInputNode, publishedOutputNode, publishedInputTriggerNode, ownsPublishedNodeClasses,
94 manuallyFirableTriggerNode);
95}
96
121void VuoCompilerGraph::initializeInstance(VuoCompilerComposition *composition, set<VuoCompilerCable *> potentialCables,
122 VuoCompilerNode *publishedInputNode, VuoCompilerNode *publishedOutputNode,
123 VuoCompilerNode *publishedInputTriggerNode, bool ownsPublishedNodeClasses,
124 VuoCompilerNode *manuallyFirableTriggerNode)
125{
126 this->publishedInputNode = publishedInputNode;
127 this->publishedOutputNode = publishedOutputNode;
128 this->ownsPublishedNodeClasses = ownsPublishedNodeClasses;
129 this->publishedInputTrigger = nullptr;
130
131 // Nodes in the composition
132
133 set<VuoNode *> nodesIncludingInvalid = composition->getBase()->getNodes();
134 set<VuoNode *> nodesToAnalyze;
135 for (VuoNode *node : nodesIncludingInvalid)
136 if (node->hasCompiler()) // Ignore nodes with missing node classes.
137 nodesToAnalyze.insert(node);
138
139 if (publishedInputNode && publishedOutputNode)
140 {
141 // Published input/output nodes
142
143 nodesToAnalyze.insert(publishedInputNode->getBase());
144 nodesToAnalyze.insert(publishedOutputNode->getBase());
145
146 // `Spin Off Event` node for published inputs
147
148 nodesToAnalyze.insert(publishedInputTriggerNode->getBase());
149
150 VuoPort *publishedInputTriggerPort = publishedInputTriggerNode->getBase()->getOutputPorts().at(VuoNodeClass::unreservedOutputPortStartIndex);
151 this->publishedInputTrigger = static_cast<VuoCompilerTriggerPort *>(publishedInputTriggerPort->getCompiler());
152 }
153
154 // `Spin Off Event` node for manually firable trigger
155
156 nodesToAnalyze.insert(manuallyFirableTriggerNode->getBase());
157
158 VuoPort *manuallyFirableTriggerPort = manuallyFirableTriggerNode->getBase()->getOutputPorts().at(VuoNodeClass::unreservedOutputPortStartIndex);
159 this->manuallyFirableTrigger = static_cast<VuoCompilerTriggerPort *>(manuallyFirableTriggerPort->getCompiler());
160
161 // Internal cables in the composition
162
163 set<VuoPort *> potentialCableInputs;
164 for (VuoCompilerCable *cable : potentialCables)
165 if (cable->carriesData())
166 potentialCableInputs.insert(cable->getBase()->getToPort());
167
168 set<VuoCable *> cablesIncludingInvalidAndPublished = composition->getBase()->getCables();
169 set<VuoCable *> cablesToAnalyze;
170 for (VuoCable *cable : cablesIncludingInvalidAndPublished)
171 {
172 if ((cable->getFromPort() && cable->getToPort()) // Ignore disconnected cables.
173 && (cable->getFromPort()->hasCompiler() && cable->getToPort()->hasCompiler()) // Ignore cables on nodes with missing node classes.
174 && ! (cable->hasCompiler() && cable->getCompiler()->carriesData() &&
175 potentialCableInputs.find(cable->getToPort()) != potentialCableInputs.end()) ) // Ignore displaced cables.
176 {
177 if (cable->isPublished())
178 publishedCables.insert(cable);
179 else
180 cablesToAnalyze.insert(cable);
181 }
182 }
183
184 // Potential internal cables
185
186 for (VuoCompilerCable *cable : potentialCables)
187 {
188 if (cable->getBase()->isPublished())
189 publishedCables.insert(cable->getBase());
190 else
191 cablesToAnalyze.insert(cable->getBase());
192 }
193
194 // Cables connected to published input/output nodes
195
196 vector<VuoPublishedPort *> publishedInputPorts = composition->getBase()->getPublishedInputPorts();
197 vector<VuoPublishedPort *> publishedOutputPorts = composition->getBase()->getPublishedOutputPorts();
198 for (VuoCable *cable : publishedCables)
199 {
200 VuoNode *fromNode = nullptr;
201 VuoNode *toNode = nullptr;
202 VuoPort *fromPort = nullptr;
203 VuoPort *toPort = nullptr;
204
205 if (cable->isPublishedInputCable())
206 {
207 size_t publishedPortIndex = std::distance(publishedInputPorts.begin(), std::find(publishedInputPorts.begin(), publishedInputPorts.end(), cable->getFromPort()));
208
209 fromNode = publishedInputNode->getBase();
210 fromPort = getOutputPortOnPublishedInputNode(publishedPortIndex);
211 toNode = cable->getToNode();
212 toPort = cable->getToPort();
213 }
214 else
215 {
216 size_t publishedPortIndex = std::distance(publishedOutputPorts.begin(), std::find(publishedOutputPorts.begin(), publishedOutputPorts.end(), cable->getToPort()));
217
218 fromNode = cable->getFromNode();
219 fromPort = cable->getFromPort();
220 toNode = publishedOutputNode->getBase();
221 toPort = getInputPortOnPublishedOutputNode(publishedPortIndex);
222 }
223
224 VuoCompilerCable *replacement = new VuoCompilerCable(fromNode->getCompiler(), static_cast<VuoCompilerPort *>(fromPort->getCompiler()),
225 toNode->getCompiler(), static_cast<VuoCompilerPort *>(toPort->getCompiler()), false);
226 replacement->setAlwaysEventOnly(cable->getCompiler()->getAlwaysEventOnly());
227 cablesToAnalyze.insert(replacement->getBase());
228 }
229
230 // Cable from `Spin Off Event` to published input node
231
232 if (publishedInputTrigger)
233 {
234 VuoPort *toPort = publishedInputNode->getBase()->getInputPorts().at(0);
235 VuoCompilerCable *spinOffCable = new VuoCompilerCable(publishedInputTriggerNode, publishedInputTrigger,
236 publishedInputNode, static_cast<VuoCompilerPort *>(toPort->getCompiler()), false);
237 cablesToAnalyze.insert(spinOffCable->getBase());
238 }
239
240 // Cable from `Spin Off Event` to manually firable input port
241
242 {
243 VuoNode *toNode = composition->getManuallyFirableInputNode();
244 VuoPort *toPort = composition->getManuallyFirableInputPort();
245 if (toNode && toPort && toNode->hasCompiler() && toPort->hasCompiler())
246 {
247 VuoCompilerCable *spinOffCable = new VuoCompilerCable(manuallyFirableTriggerNode, manuallyFirableTrigger,
248 toNode->getCompiler(), static_cast<VuoCompilerPort *>(toPort->getCompiler()), false);
249 cablesToAnalyze.insert(spinOffCable->getBase());
250 }
251 }
252
253 for (VuoNode *node : nodesToAnalyze)
254 nodes.insert(node->getCompiler());
255
256 makeTriggers(nodesToAnalyze);
257 makeVerticesAndEdges(cablesToAnalyze);
258 makeDownstreamVertices();
259 sortVertices();
260 makeVertexDistances();
261 makeDownstreamNodesViaDataOnlyTransmission(nodesToAnalyze, cablesToAnalyze);
262}
263
268{
269 VuoCompilerNode *publishedInputTriggerNode = nodeForTrigger[publishedInputTrigger];
270
271 if (ownsPublishedNodeClasses)
272 {
273 delete publishedInputNode->getBase()->getNodeClass()->getCompiler();
274 delete publishedOutputNode->getBase()->getNodeClass()->getCompiler();
275 delete publishedInputTriggerNode->getBase()->getNodeClass()->getCompiler();
276 }
277
278 delete publishedInputNode;
279 delete publishedOutputNode;
280 delete publishedInputTriggerNode;
281}
282
286void VuoCompilerGraph::makeTriggers(set<VuoNode *> nodes)
287{
288 for (VuoNode *node : nodes)
289 {
290 for (VuoPort *port : node->getOutputPorts())
291 {
292 VuoCompilerTriggerPort *trigger = dynamic_cast<VuoCompilerTriggerPort *>(port->getCompiler());
293 if (trigger)
294 {
295 nodeForTrigger[trigger] = node->getCompiler();
296 triggers.push_back(trigger);
297 }
298 }
299 }
300}
301
305void VuoCompilerGraph::makeVerticesAndEdges(const set<VuoCable *> &cables)
306{
307 // Create vertices to visit for all cables.
308
309 set<Vertex> allVertices;
310 for (VuoCable *cable : cables)
311 {
312 VuoCompilerPort *fromPort = cable->getFromPort() ? static_cast<VuoCompilerPort *>(cable->getFromPort()->getCompiler()) : nullptr;
313 VuoCompilerTriggerPort *fromTrigger = dynamic_cast<VuoCompilerTriggerPort *>(fromPort);
314 VuoCompilerNode *fromNode = cable->getFromNode()->getCompiler();
315 VuoCompilerNode *toNode = cable->getToNode()->getCompiler();
316 Vertex vertex = (fromTrigger ? Vertex(fromTrigger, toNode) : Vertex(fromNode, toNode));
317
318 set<Vertex>::iterator vertexIter = allVertices.find(vertex);
319 if (vertexIter != allVertices.end())
320 {
321 vertex.cableBundle = (*vertexIter).cableBundle;
322 allVertices.erase(vertexIter);
323 }
324 vertex.cableBundle.insert(cable->getCompiler());
325
326 allVertices.insert(vertex);
327 }
328
329 // For each trigger, add all vertices reachable from it.
330
331 for (VuoCompilerTriggerPort *trigger : triggers)
332 {
333 set<Vertex> verticesToVisit;
334 set<Edge> edgesVisited;
335
336 for (set<Vertex>::iterator j = allVertices.begin(); j != allVertices.end(); ++j)
337 if ((*j).fromTrigger == trigger)
338 verticesToVisit.insert(*j);
339
340 map<VuoCompilerNode *, set<Vertex> > outgoingVerticesForNode; // Cached data to speed up search for outgoing vertices.
341 for (set<Vertex>::iterator j = allVertices.begin(); j != allVertices.end(); ++j)
342 outgoingVerticesForNode[(*j).fromNode].insert(*j);
343
344 while (! verticesToVisit.empty())
345 {
346 Vertex vertex = *verticesToVisit.begin();
347 if (find(vertices[trigger].begin(), vertices[trigger].end(), vertex) == vertices[trigger].end())
348 vertices[trigger].push_back(vertex);
349 verticesToVisit.erase(verticesToVisit.begin());
350
351 set<Vertex> potentialOutgoingVertices = outgoingVerticesForNode[vertex.toNode];
352 for (set<Vertex>::iterator j = potentialOutgoingVertices.begin(); j != potentialOutgoingVertices.end(); ++j)
353 {
354 Vertex outgoingVertex = *j;
355
356 if (mayTransmit(vertex.cableBundle, outgoingVertex.cableBundle))
357 {
358 Edge outgoingEdge(vertex, outgoingVertex);
359 if (edgesVisited.find(outgoingEdge) == edgesVisited.end()) // Avoid infinite feedback loops.
360 {
361 edges[trigger].insert(outgoingEdge);
362 edgesVisited.insert(outgoingEdge);
363
364 verticesToVisit.insert(outgoingVertex);
365 }
366 }
367 }
368 }
369 }
370}
371
376void VuoCompilerGraph::makeDownstreamVerticesWithInclusionRule(VuoCompilerTriggerPort *trigger, std::function<bool(Edge)> includeEdge,
377 map<Vertex, set<Vertex> > &_downstreamVertices,
378 set<Vertex> &_repeatedVertices)
379{
380 list<Vertex> verticesToVisit; // Used as a stack, except for a call to find().
381
382 for (Vertex vertex : vertices[trigger])
383 if (vertex.fromTrigger == trigger)
384 verticesToVisit.push_back(vertex);
385
386 map<Vertex, set<Vertex> > outgoingVerticesFromVertex; // Cached data to speed up search for outgoing vertices.
387 for (Edge edge : edges[trigger])
388 if (includeEdge(edge))
389 outgoingVerticesFromVertex[edge.fromVertex].insert(edge.toVertex);
390
391 while (! verticesToVisit.empty())
392 {
393 // Visit the vertex at the top of the stack.
394 Vertex currentVertex = verticesToVisit.back();
395
396 set<Vertex> currentDownstreamVertices;
397 bool areDownstreamVerticesComplete = true;
398
399 // Consider each vertex immediately downstream of this vertex.
400 set<Vertex> outgoingVertices = outgoingVerticesFromVertex[currentVertex];
401 for (set<Vertex>::iterator j = outgoingVertices.begin(); j != outgoingVertices.end(); ++j)
402 {
403 Vertex outgoingVertex = *j;
404 currentDownstreamVertices.insert(outgoingVertex);
405
406 if (_downstreamVertices.find(outgoingVertex) != _downstreamVertices.end())
407 {
408 // The downstream vertex has already been visited, so add its downstream vertices to this vertex's.
409 set<Vertex> furtherDownstreamVertices = _downstreamVertices[outgoingVertex];
410 currentDownstreamVertices.insert( furtherDownstreamVertices.begin(), furtherDownstreamVertices.end() );
411 }
412 else
413 {
414 if (find(verticesToVisit.begin(), verticesToVisit.end(), outgoingVertex) != verticesToVisit.end())
415 {
416 // The downstream vertex is already on the stack, so it's an infinite feedback loop.
417 _repeatedVertices.insert(outgoingVertex);
418 }
419 else
420 {
421 // The downstream vertex has not yet been visited, so add it to the stack.
422 verticesToVisit.push_back(outgoingVertex);
423 areDownstreamVerticesComplete = false;
424 }
425 }
426 }
427
428 if (areDownstreamVerticesComplete)
429 {
430 _downstreamVertices[currentVertex] = currentDownstreamVertices;
431 verticesToVisit.pop_back();
432 }
433 }
434
435 // Clean up repeatedVertices so that it only contains vertices within an infinite feedback loop,
436 // not other outgoing vertices from nodes in the infinite feedback loop.
437 set<Vertex> repeatedVerticesCopy = _repeatedVertices;
438 for (Vertex vertex : repeatedVerticesCopy)
439 if (_downstreamVertices[vertex].find(vertex) == _downstreamVertices[vertex].end())
440 _repeatedVertices.erase(vertex);
441}
442
446void VuoCompilerGraph::makeDownstreamVertices(void)
447{
448 auto includeAllEdges = [] (Edge edge) { return true; };
449
450 for (VuoCompilerTriggerPort *trigger : triggers)
451 {
452 makeDownstreamVerticesWithInclusionRule(trigger, includeAllEdges, downstreamVertices[trigger], repeatedVertices[trigger]);
453
454 if (repeatedVertices[trigger].empty())
455 repeatedVertices.erase(trigger);
456 }
457
458 // For each trigger, add a cable and vertex from each leaf trigger/vertex to the published output node's gather port.
459 // - For the published input trigger and triggers that spin off events from it, this ensures that the
460 // composition doesn't notify its runner (for top-level compositions) or parent composition (for subcompositions)
461 // until the event has finished propagating through the composition.
462 // - For internal triggers that reach a published output, this ensures that published trigger doesn't fire
463 // until the event has finished propagating through the composition.
464
465 if (publishedOutputNode)
466 {
467 for (VuoCompilerTriggerPort *trigger : triggers)
468 {
469 set< pair<VuoCompilerTriggerPort *, Vertex> > leaves;
470 if (trigger != publishedInputTrigger && trigger != manuallyFirableTrigger && downstreamVertices[trigger].empty())
471 leaves.insert({ trigger, Vertex() });
472 else
473 for (auto i : downstreamVertices[trigger])
474 if (i.first.toNode != publishedOutputNode && i.second.empty())
475 leaves.insert({ nullptr, i.first });
476
477 for (auto &leaf : leaves)
478 {
479 VuoCompilerNode *fromNode = (leaf.first ? nodeForTrigger[leaf.first] : leaf.second.toNode);
480 VuoCompilerTriggerPort *fromPort = (leaf.first ? leaf.first : nullptr);
482
483 VuoCompilerCable *gather = new VuoCompilerCable(fromNode, fromPort,
484 publishedOutputNode, static_cast<VuoCompilerPort *>(toPort->getCompiler()), false);
485
486 Vertex gatherVertex = (leaf.first ? Vertex(fromPort, publishedOutputNode) : Vertex(fromNode, publishedOutputNode));
487 gatherVertex.cableBundle.insert(gather);
488 vertices[trigger].push_back(gatherVertex);
489
490 if (! leaf.first)
491 {
492 Vertex leafVertex = leaf.second;
493 Edge gatherEdge(leafVertex, gatherVertex);
494 edges[trigger].insert(gatherEdge);
495 downstreamVertices[trigger][leafVertex].insert(gatherVertex);
496 }
497 }
498 }
499 }
500}
501
505void VuoCompilerGraph::sortVertices(void)
506{
507 for (map<VuoCompilerTriggerPort *, vector<Vertex> >::iterator i = vertices.begin(); i != vertices.end(); ++i)
508 {
509 VuoCompilerTriggerPort *trigger = i->first;
510 vector<Vertex> verticesToSort = i->second;
511
512 map<Vertex, set<Vertex> > dependentVertices;
513 list<Vertex> verticesToVisit; // Used as a stack, except for a call to find().
514 map<Vertex, bool> verticesCompleted;
515
516 for (vector<Vertex>::iterator j = verticesToSort.begin(); j != verticesToSort.end(); ++j)
517 if ((*j).fromTrigger == trigger)
518 verticesToVisit.push_back(*j);
519
520 map<VuoCompilerNode *, set<Vertex> > outgoingVerticesForNode; // Cached data to speed up topological sort.
521 for (vector<Vertex>::iterator j = verticesToSort.begin(); j != verticesToSort.end(); ++j)
522 outgoingVerticesForNode[(*j).fromNode].insert(*j);
523
524 while (! verticesToVisit.empty())
525 {
526 // Visit the vertex at the top of the stack.
527 Vertex currentVertex = verticesToVisit.back();
528
529 set<Vertex> currentDependentVertices;
530 bool areDependentVerticesComplete = true;
531
532 // Form a list of vertices immediately dependent on this vertex's to-node.
533 // This includes vertices that are not downstream of this vertex because of a wall.
534 // But, if this vertex is at the end of a feedback loop, it doesn't include vertices
535 // beyond the end of the feedback loop.
536 set<Vertex> outgoingVertices;
537 set<Vertex> potentialOutgoingVertices = outgoingVerticesForNode[currentVertex.toNode];
538 for (set<Vertex>::iterator j = potentialOutgoingVertices.begin(); j != potentialOutgoingVertices.end(); ++j)
539 {
540 Vertex outgoingVertex = *j;
541 if (downstreamVertices[trigger][outgoingVertex].find(currentVertex) == downstreamVertices[trigger][outgoingVertex].end())
542 outgoingVertices.insert(outgoingVertex);
543 else
544 {
545 outgoingVertices.clear();
546 break;
547 }
548 }
549
550 for (set<Vertex>::iterator j = outgoingVertices.begin(); j != outgoingVertices.end(); ++j)
551 {
552 Vertex outgoingVertex = *j;
553 currentDependentVertices.insert(outgoingVertex);
554
555 if (verticesCompleted[outgoingVertex])
556 {
557 // The dependent vertex has already been visited, so add its dependent vertices to this vertex's.
558 currentDependentVertices.insert( dependentVertices[outgoingVertex].begin(), dependentVertices[outgoingVertex].end() );
559 }
560 else if (find(verticesToVisit.begin(), verticesToVisit.end(), outgoingVertex) == verticesToVisit.end())
561 {
562 // The dependent vertex has not yet been visited, so add it to the stack.
563 verticesToVisit.push_back(outgoingVertex);
564 areDependentVerticesComplete = false;
565 }
566 }
567
568 if (areDependentVerticesComplete)
569 {
570 dependentVertices[currentVertex] = currentDependentVertices;
571 verticesToVisit.pop_back();
572 verticesCompleted[currentVertex] = true;
573 }
574 }
575
576 // Put the vertices in descending order of the number of vertices that can't be reached until after the vertex.
577 vector< pair<size_t, Vertex> > verticesAndDependents;
578 for (map<Vertex, set<Vertex> >::iterator j = dependentVertices.begin(); j != dependentVertices.end(); ++j)
579 verticesAndDependents.push_back( make_pair(j->second.size(), j->first) );
580 sort(verticesAndDependents.begin(), verticesAndDependents.end());
581
582 vector<Vertex> sortedVertices;
583 for (vector< pair<size_t, Vertex> >::reverse_iterator j = verticesAndDependents.rbegin(); j != verticesAndDependents.rend(); ++j)
584 sortedVertices.push_back((*j).second);
585
586 vertices[trigger] = sortedVertices;
587 }
588}
589
593void VuoCompilerGraph::makeVertexDistances(void)
594{
595 for (map<VuoCompilerTriggerPort *, vector<Vertex> >::iterator i = vertices.begin(); i != vertices.end(); ++i)
596 {
597 VuoCompilerTriggerPort *trigger = i->first;
598 vector<Vertex> verticesDownstream = i->second;
599
600 map<VuoCompilerNode *, set<Vertex> > incomingVerticesForNode; // Cached data to speed up search for incoming vertices.
601 for (vector<Vertex>::iterator j = verticesDownstream.begin(); j != verticesDownstream.end(); ++j)
602 incomingVerticesForNode[(*j).toNode].insert(*j);
603
604 // Handle vertices immediately downstream of the trigger.
605 for (vector<Vertex>::iterator j = verticesDownstream.begin(); j != verticesDownstream.end(); ++j)
606 {
607 Vertex vertex = *j;
608 if (vertex.fromTrigger == trigger)
609 {
610 vertexDistanceFromTrigger[trigger][vertex] = 1;
611 triggerMustTransmitToVertex[trigger][vertex] = true;
612 }
613 }
614
615 // Handle vertices further downstream.
616 for (vector<Vertex>::iterator j = verticesDownstream.begin(); j != verticesDownstream.end(); ++j)
617 {
618 Vertex vertex = *j;
619 if (vertex.fromTrigger != trigger)
620 {
621 size_t minDistance = verticesDownstream.size();
622 bool anyMustTransmit = false;
623 for (set<Vertex>::iterator k = incomingVerticesForNode[vertex.fromNode].begin(); k != incomingVerticesForNode[vertex.fromNode].end(); ++k)
624 {
625 Vertex upstreamVertex = *k;
626
627 minDistance = min(vertexDistanceFromTrigger[trigger][upstreamVertex], minDistance);
628 bool currMustTransmit = triggerMustTransmitToVertex[trigger][upstreamVertex] && mustTransmit(upstreamVertex, trigger);
629 anyMustTransmit = currMustTransmit || anyMustTransmit;
630 }
631
632 vertexDistanceFromTrigger[trigger][vertex] = minDistance + 1;
633 triggerMustTransmitToVertex[trigger][vertex] = anyMustTransmit;
634 }
635 }
636 }
637}
638
642void VuoCompilerGraph::makeDownstreamNodesViaDataOnlyTransmission(set<VuoNode *> nodes, set<VuoCable *> cables)
643{
644 // @todo Maybe this could be refactored to call makeDownstreamVerticesWithInclusionRule.
645
646 // Find all nodes that can transmit through their output data cables, and put them in topological order.
647 // (Assumes the nodes don't have walled ports, so they can't have different topological orders for different triggers.)
648
649 map<VuoCompilerNode *, set<VuoCompilerNode *> > remainingIncomingNodes;
650 map<VuoCompilerNode *, set<VuoCompilerNode *> > remainingOutgoingNodes;
651 for (VuoCable *cable : cables)
652 {
653 VuoCompilerPort *fromPort = cable->getFromPort() ? static_cast<VuoCompilerPort *>(cable->getFromPort()->getCompiler()) : nullptr;
654 VuoCompilerTriggerPort *fromTrigger = dynamic_cast<VuoCompilerTriggerPort *>(fromPort);
655 if (fromTrigger || ! cable->getCompiler()->carriesData())
656 continue; // Ignore cables that can't transmit without an event.
657
658 VuoCompilerNode *fromNode = cable->getFromNode()->getCompiler();
659 VuoCompilerNode *toNode = cable->getToNode()->getCompiler();
660
661 if (mayTransmitDataOnly(fromNode))
662 {
663 remainingOutgoingNodes[fromNode].insert(toNode);
664 if (mayTransmitDataOnly(toNode))
665 remainingIncomingNodes[toNode].insert(fromNode);
666 }
667 }
668 map<VuoCompilerNode *, set<VuoCompilerNode *> > outgoingNodes = remainingOutgoingNodes;
669
670 set<VuoCompilerNode *> nodesToVisit;
671 for (VuoNode *node : nodes)
672 {
673 if (mayTransmitDataOnly(node->getCompiler()) &&
674 remainingIncomingNodes.find(node->getCompiler()) == remainingIncomingNodes.end() &&
675 remainingOutgoingNodes.find(node->getCompiler()) != remainingOutgoingNodes.end())
676 nodesToVisit.insert(node->getCompiler());
677 }
678
679 vector<VuoCompilerNode *> sortedNodesAllowingDataOnlyTransmission;
680 while (! nodesToVisit.empty())
681 {
682 VuoCompilerNode *node = *nodesToVisit.begin();
683 nodesToVisit.erase(nodesToVisit.begin());
684
685 sortedNodesAllowingDataOnlyTransmission.push_back(node);
686
687 set<VuoCompilerNode *> outgoingNodesCopy = remainingOutgoingNodes[node];
688 for (VuoCompilerNode *outgoingNode : outgoingNodesCopy)
689 {
690 remainingIncomingNodes[outgoingNode].erase(node);
691 remainingOutgoingNodes[node].erase(outgoingNode);
692
693 if (remainingIncomingNodes[outgoingNode].empty())
694 nodesToVisit.insert(outgoingNode);
695 }
696 }
697
698 // For each of those nodes, find the nodes that are downstream of it via eventless transmission, in topological order.
699
700 for (int firstIndex = sortedNodesAllowingDataOnlyTransmission.size() - 1; firstIndex >= 0; --firstIndex)
701 {
702 VuoCompilerNode *firstNode = sortedNodesAllowingDataOnlyTransmission[firstIndex];
703
704 for (size_t possiblyDownstreamIndex = firstIndex + 1; possiblyDownstreamIndex < sortedNodesAllowingDataOnlyTransmission.size(); ++possiblyDownstreamIndex)
705 {
706 VuoCompilerNode *possiblyDownstreamNode = sortedNodesAllowingDataOnlyTransmission[possiblyDownstreamIndex];
707
708 if (outgoingNodes[firstNode].find(possiblyDownstreamNode) != outgoingNodes[firstNode].end())
709 {
710 vector<VuoCompilerNode *> nodesToAdd = downstreamNodesViaDataOnlyTransmission[possiblyDownstreamNode];
711 nodesToAdd.insert(nodesToAdd.begin(), possiblyDownstreamNode);
712
713 for (VuoCompilerNode *nodeToAdd : nodesToAdd)
714 if (find(downstreamNodesViaDataOnlyTransmission[firstNode].begin(), downstreamNodesViaDataOnlyTransmission[firstNode].end(), nodeToAdd) == downstreamNodesViaDataOnlyTransmission[firstNode].end())
715 downstreamNodesViaDataOnlyTransmission[firstNode].push_back(nodeToAdd);
716 }
717 }
718 }
719}
720
727bool VuoCompilerGraph::mustTransmit(const set<VuoCompilerCable *> &fromCables, const set<VuoCompilerCable *> &toCables)
728{
729 bool fromCablesMustTransmit = false;
730 for (VuoCompilerCable *cable : fromCables)
731 {
732 VuoPort *inputPort = cable->getBase()->getToPort();
734 {
735 fromCablesMustTransmit = true;
736 break;
737 }
738 }
739
740 bool toCablesMayTransmit = false;
741 for (VuoCompilerCable *cable : toCables)
742 {
743 VuoPort *outputPort = cable->getBase()->getFromPort();
744 if (! outputPort || outputPort->getClass()->getPortType() != VuoPortClass::triggerPort)
745 {
746 toCablesMayTransmit = true;
747 break;
748 }
749 }
750
751 return fromCablesMustTransmit && toCablesMayTransmit;
752}
753
757bool VuoCompilerGraph::mustTransmit(Vertex vertex, VuoCompilerTriggerPort *trigger)
758{
759 set<VuoCompilerCable *> cablesBetweenFromNodeAndToNode = vertex.cableBundle;
760
761 set<VuoCompilerCable *> cablesOutOfToNode;
762 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
763 {
764 Vertex otherVertex = *i;
765 if (vertex.toNode == otherVertex.fromNode)
766 cablesOutOfToNode.insert(otherVertex.cableBundle.begin(), otherVertex.cableBundle.end());
767 }
768
769 return mustTransmit(cablesBetweenFromNodeAndToNode, cablesOutOfToNode);
770}
771
772
779bool VuoCompilerGraph::mayTransmit(const set<VuoCompilerCable *> &fromCables, const set<VuoCompilerCable *> &toCables)
780{
781 bool fromCablesMayTransmit = false;
782 for (VuoCompilerCable *cable : fromCables)
783 {
784 VuoPort *inputPort = cable->getBase()->getToPort();
786 {
787 fromCablesMayTransmit = true;
788 break;
789 }
790 }
791
792 bool toCablesMayTransmit = false;
793 for (VuoCompilerCable *cable : toCables)
794 {
795 VuoPort *outputPort = cable->getBase()->getFromPort();
796 if (! outputPort || outputPort->getClass()->getPortType() != VuoPortClass::triggerPort)
797 {
798 toCablesMayTransmit = true;
799 break;
800 }
801 }
802
803 return fromCablesMayTransmit && toCablesMayTransmit;
804}
805
809bool VuoCompilerGraph::mayTransmit(Vertex vertex, VuoCompilerTriggerPort *trigger)
810{
811 set<VuoCompilerCable *> cablesBetweenFromNodeAndToNode = vertex.cableBundle;
812
813 set<VuoCompilerCable *> cablesOutOfToNode;
814 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
815 {
816 Vertex otherVertex = *i;
817 if (vertex.toNode == otherVertex.fromNode)
818 cablesOutOfToNode.insert(otherVertex.cableBundle.begin(), otherVertex.cableBundle.end());
819 }
820
821 return mayTransmit(cablesBetweenFromNodeAndToNode, cablesOutOfToNode);
822}
823
828{
829 set<VuoCompilerCable *> incomingCables;
830 set<VuoCompilerCable *> outgoingCables;
831
832 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
833 {
834 Vertex vertex = *i;
835
836 if (vertex.toNode == node)
837 incomingCables.insert( vertex.cableBundle.begin(), vertex.cableBundle.end() );
838 if (vertex.fromNode == node)
839 outgoingCables.insert( vertex.cableBundle.begin(), vertex.cableBundle.end() );
840 }
841
842 return mayTransmit(incomingCables, outgoingCables);
843}
844
849{
850 if (vertexMayTransmit[trigger].empty() && ! vertices[trigger].empty())
851 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
852 vertexMayTransmit[trigger][(*i).fromNode][(*i).toNode] = true;
853
854 return vertexMayTransmit[trigger][fromNode][toNode];
855}
856
861{
862 return node->getBase()->getNodeClass()->isDrawerNodeClass() || node == publishedInputNode;
863}
864
868vector<VuoCompilerTriggerPort *> VuoCompilerGraph::getTriggerPorts(void)
869{
870 return triggers;
871}
872
877{
878 auto foundIter = nodeForTrigger.find(trigger);
879 if (foundIter != nodeForTrigger.end())
880 return foundIter->second;
881
882 return NULL;
883}
884
889set<VuoCompilerNode *> VuoCompilerGraph::getNodes(void)
890{
891 return nodes;
892}
893
900{
901 return publishedInputNode;
902}
903
910{
911 return publishedOutputNode;
912}
913
918{
919 return publishedInputTrigger;
920}
921
926{
927 VuoCompilerPublishedInputNodeClass *publishedInputNodeClass = static_cast<VuoCompilerPublishedInputNodeClass *>(publishedInputNode->getBase()->getNodeClass()->getCompiler());
928 size_t portIndex = publishedInputNodeClass->getInputPortIndexForPublishedInputPort(publishedInputPortIndex);
929 return publishedInputNode->getBase()->getInputPorts().at(portIndex);
930}
931
935VuoPort * VuoCompilerGraph::getOutputPortOnPublishedInputNode(size_t publishedInputPortIndex)
936{
937 VuoCompilerPublishedInputNodeClass *publishedInputNodeClass = static_cast<VuoCompilerPublishedInputNodeClass *>(publishedInputNode->getBase()->getNodeClass()->getCompiler());
938 size_t portIndex = publishedInputNodeClass->getOutputPortIndexForPublishedInputPort(publishedInputPortIndex);
939 return publishedInputNode->getBase()->getOutputPorts().at(portIndex);
940}
941
946{
947 VuoCompilerPublishedOutputNodeClass *publishedOutputNodeClass = static_cast<VuoCompilerPublishedOutputNodeClass *>(publishedOutputNode->getBase()->getNodeClass()->getCompiler());
948 size_t portIndex = publishedOutputNodeClass->getInputPortIndexForPublishedOutputPort(publishedOutputPortIndex);
949 return publishedOutputNode->getBase()->getInputPorts().at(portIndex);
950}
951
956{
957 VuoCompilerPublishedOutputNodeClass *publishedOutputNodeClass = static_cast<VuoCompilerPublishedOutputNodeClass *>(publishedOutputNode->getBase()->getNodeClass()->getCompiler());
958 size_t portIndex = publishedOutputNodeClass->getGatherInputPortIndex();
959 return publishedOutputNode->getBase()->getInputPorts().at(portIndex);
960}
961
966{
967 return manuallyFirableTrigger;
968}
969
973size_t VuoCompilerGraph::getNumVerticesWithFromNode(VuoCompilerNode *fromNode, VuoCompilerTriggerPort *trigger)
974{
975 int numVertices = 0;
976 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
977 if ((*i).fromNode == fromNode)
978 ++numVertices;
979
980 return numVertices;
981}
982
986size_t VuoCompilerGraph::getNumVerticesWithToNode(VuoCompilerNode *toNode, VuoCompilerTriggerPort *trigger)
987{
988 map<VuoCompilerTriggerPort *, map<VuoCompilerNode *, size_t> >::iterator triggerIter = numVerticesWithToNode.find(trigger);
989 if (triggerIter != numVerticesWithToNode.end())
990 {
991 map<VuoCompilerNode *, size_t>::iterator nodeIter = triggerIter->second.find(toNode);
992 if (nodeIter != triggerIter->second.end())
993 return nodeIter->second;
994 }
995
996 size_t numVertices = 0;
997 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
998 if ((*i).toNode == toNode)
999 ++numVertices;
1000
1001 numVerticesWithToNode[trigger][toNode] = numVertices;
1002 return numVertices;
1003}
1004
1016map<VuoCompilerTriggerPort *, vector<VuoCompilerChain *> > VuoCompilerGraph::getChains(void)
1017{
1018 if (! chains.empty())
1019 return chains;
1020
1021 for (VuoCompilerTriggerPort *trigger : triggers)
1022 {
1023 // Visit the vertices in topological order, and put the nodes into lists (chains-in-progress).
1024 vector< vector<VuoCompilerNode *> > chainsInProgress;
1025 set<VuoCompilerNode *> nodesAdded;
1026 bool skippedPublishedOutputNode = false;
1027 for (vector<Vertex>::iterator j = vertices[trigger].begin(); j != vertices[trigger].end(); ++j)
1028 {
1029 Vertex vertex = *j;
1030 bool addedToChain = false;
1031
1032 if (nodesAdded.find(vertex.toNode) != nodesAdded.end())
1033 continue; // Avoid creating duplicate chains for nodes with gathers (multiple incoming vertices).
1034
1035 if (vertex.toNode == publishedOutputNode)
1036 {
1037 skippedPublishedOutputNode = true;
1038 continue; // Save the published output node for last.
1039 }
1040
1041 nodesAdded.insert(vertex.toNode);
1042
1043 if (vertex.fromNode)
1044 {
1045 if (getNumVerticesWithFromNode(vertex.fromNode, trigger) == 1 &&
1046 getNumVerticesWithToNode(vertex.toNode, trigger) == 1)
1047 {
1048 for (int k = 0; k < chainsInProgress.size(); ++k)
1049 {
1050 VuoCompilerNode *lastNodeInChain = chainsInProgress[k].back();
1051 if (lastNodeInChain == vertex.fromNode)
1052 {
1053 // Add vertex.toNode to an existing chain-in-progress.
1054 chainsInProgress[k].push_back(vertex.toNode);
1055 addedToChain = true;
1056 break;
1057 }
1058 }
1059 }
1060 }
1061
1062 if (! addedToChain)
1063 {
1064 // Create a new chain-in-progress that starts with vertex.toNode.
1065 chainsInProgress.push_back( vector<VuoCompilerNode *>(1, vertex.toNode) );
1066 }
1067 }
1068
1069 // Turn the chains-in-progress into actual chains.
1070 for (vector< vector<VuoCompilerNode *> >::iterator j = chainsInProgress.begin(); j != chainsInProgress.end(); ++j)
1071 {
1072 vector<VuoCompilerNode *> chainNodes = *j;
1073 VuoCompilerChain *chain = new VuoCompilerChain(chainNodes, false);
1074 chains[trigger].push_back(chain);
1075 }
1076
1077 // Create a new chain for each node that is the repeated node in a feedback loop.
1078 map<VuoCompilerNode *, bool> nodesSeen;
1079 for (vector<Vertex>::iterator j = vertices[trigger].begin(); j != vertices[trigger].end(); ++j)
1080 {
1081 VuoCompilerNode *node = (*j).toNode;
1082 if (nodesSeen[node])
1083 continue;
1084 nodesSeen[node] = true;
1085
1086 if (isRepeatedInFeedbackLoop(node, trigger))
1087 {
1088 VuoCompilerChain *chain = new VuoCompilerChain(vector<VuoCompilerNode *>(1, node), true);
1089 chains[trigger].push_back(chain);
1090 }
1091 }
1092
1093 // Create a new chain for the published output node.
1094 if (skippedPublishedOutputNode)
1095 {
1096 VuoCompilerChain *chain = new VuoCompilerChain(vector<VuoCompilerNode *>(1, publishedOutputNode), false);
1097 chains[trigger].push_back(chain);
1098 }
1099 }
1100
1101 return chains;
1102}
1103
1108{
1109 set<VuoCompilerNode *> downstreamNodes;
1110 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
1111 if ((*i).fromTrigger == trigger)
1112 downstreamNodes.insert((*i).toNode);
1113
1114 return vector<VuoCompilerNode *>(downstreamNodes.begin(), downstreamNodes.end());
1115}
1116
1121{
1122 set<VuoCompilerNode *> downstreamNodes;
1123 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
1124 if ((*i).fromNode == node)
1125 downstreamNodes.insert((*i).toNode);
1126
1127 return vector<VuoCompilerNode *>(downstreamNodes.begin(), downstreamNodes.end());
1128}
1129
1134{
1135 map<VuoCompilerTriggerPort *, vector<VuoCompilerNode *> >::iterator triggerIter = downstreamNodesForTrigger.find(trigger);
1136 if (triggerIter != downstreamNodesForTrigger.end())
1137 return triggerIter->second;
1138
1139 set<VuoCompilerNode *> downstreamNodes;
1140 vector<VuoCompilerNode *> immediatelyDownstreamNodes = getNodesImmediatelyDownstream(trigger);
1141 downstreamNodes.insert(immediatelyDownstreamNodes.begin(), immediatelyDownstreamNodes.end());
1142
1143 for (vector<VuoCompilerNode *>::iterator i = immediatelyDownstreamNodes.begin(); i != immediatelyDownstreamNodes.end(); ++i)
1144 {
1145 vector<VuoCompilerNode *> furtherDownstreamNodes = getNodesDownstream(*i, trigger);
1146 downstreamNodes.insert(furtherDownstreamNodes.begin(), furtherDownstreamNodes.end());
1147 }
1148
1149 vector<VuoCompilerNode *> downstreamNodesVector(downstreamNodes.begin(), downstreamNodes.end());
1150 downstreamNodesForTrigger[trigger] = downstreamNodesVector;
1151 return downstreamNodesVector;
1152}
1153
1158{
1159 map<VuoCompilerTriggerPort *, map<VuoCompilerNode *, vector<VuoCompilerNode *> > >::iterator triggerIter = downstreamNodesForNode.find(trigger);
1160 if (triggerIter != downstreamNodesForNode.end())
1161 {
1162 map<VuoCompilerNode *, vector<VuoCompilerNode *> >::iterator nodeIter = triggerIter->second.find(node);
1163 if (nodeIter != triggerIter->second.end())
1164 return nodeIter->second;
1165 }
1166
1167 set<Vertex> verticesWithDownstreamNodes;
1168 for (vector<Vertex>::iterator i = vertices[trigger].begin(); i != vertices[trigger].end(); ++i)
1169 {
1170 Vertex vertex = *i;
1171 if (vertex.fromNode == node)
1172 {
1173 verticesWithDownstreamNodes.insert(vertex);
1174 set<Vertex> downstream = downstreamVertices[trigger][vertex];
1175 verticesWithDownstreamNodes.insert(downstream.begin(), downstream.end());
1176 }
1177 }
1178
1179 set<VuoCompilerNode *> downstreamNodes;
1180 for (set<Vertex>::iterator i = verticesWithDownstreamNodes.begin(); i != verticesWithDownstreamNodes.end(); ++i)
1181 downstreamNodes.insert((*i).toNode);
1182
1183 vector<VuoCompilerNode *> downstreamNodesVector(downstreamNodes.begin(), downstreamNodes.end());
1184 downstreamNodesForNode[trigger][node] = downstreamNodesVector;
1185 return downstreamNodesVector;
1186}
1187
1193{
1194 return downstreamNodesViaDataOnlyTransmission[node];
1195}
1196
1202set<VuoCompilerCable *> VuoCompilerGraph::getOutgoingCables(VuoCompilerPort *outputPort)
1203{
1204 set<VuoCompilerCable *> outgoingCables;
1205
1206 // Add cables from the original composition.
1207
1208 for (VuoCable *cable : outputPort->getBase()->getConnectedCables())
1209 if (cable->getToNode() && cable->getToNode()->hasCompiler())
1210 outgoingCables.insert(cable->getCompiler());
1211
1212 // Add any other cables created for graph analysis.
1213
1214 for (map<VuoCompilerTriggerPort *, vector<Vertex> >::value_type kv : vertices)
1215 for (Vertex vertex : kv.second)
1216 for (VuoCompilerCable *cable : vertex.cableBundle)
1217 if (cable->getBase()->getFromPort() == outputPort->getBase())
1218 outgoingCables.insert(cable);
1219
1220 return outgoingCables;
1221}
1222
1227{
1228 set<VuoCompilerNode *> nodesTransmittingDataOnly;
1229 set<VuoCompilerNode *> nodesDownstreamOfAnother;
1230
1231 for (const map<VuoCompilerNode *, vector<VuoCompilerNode *> >::value_type &i : downstreamNodesViaDataOnlyTransmission)
1232 {
1233 nodesTransmittingDataOnly.insert(i.first);
1234 nodesDownstreamOfAnother.insert(i.second.begin(), i.second.end());
1235 }
1236
1237 set<VuoCompilerNode *> nodesNotDownstream;
1238 std::set_difference(nodesTransmittingDataOnly.begin(), nodesTransmittingDataOnly.end(),
1239 nodesDownstreamOfAnother.begin(), nodesDownstreamOfAnother.end(),
1240 std::inserter(nodesNotDownstream, nodesNotDownstream.end()));
1241 return nodesNotDownstream;
1242}
1243
1248void VuoCompilerGraph::getWorkerThreadsNeeded(VuoCompilerChain *chain, int &minThreadsNeeded, int &maxThreadsNeeded)
1249{
1250 // Non-subcomposition nodes need 1 thread.
1251 minThreadsNeeded = 1;
1252 maxThreadsNeeded = 1;
1253
1254 for (VuoCompilerNode *node : chain->getNodes())
1255 {
1256 VuoCompilerNodeClass *nodeClass = node->getBase()->getNodeClass()->getCompiler();
1257 vector<VuoCompilerTriggerDescription *> triggerDescriptions = nodeClass->getTriggerDescriptions();
1258
1259 for (vector<VuoCompilerTriggerDescription *>::iterator k = triggerDescriptions.begin(); k != triggerDescriptions.end(); ++k)
1260 {
1261 if ((*k)->getNodeIdentifier() == VuoNodeClass::publishedInputNodeIdentifier)
1262 {
1263 // Subcomposition nodes need 1 thread plus those needed within the subcomposition.
1264 int minThreadsNeededForSubcomposition, maxThreadsNeededForSubcomposition;
1265 (*k)->getWorkerThreadsNeeded(minThreadsNeededForSubcomposition, maxThreadsNeededForSubcomposition);
1266 minThreadsNeeded = max(minThreadsNeeded, minThreadsNeededForSubcomposition + 1);
1267 maxThreadsNeeded = max(maxThreadsNeeded, maxThreadsNeededForSubcomposition + 1);
1268 break;
1269 }
1270 }
1271 }
1272}
1273
1279void VuoCompilerGraph::getWorkerThreadsNeeded(VuoCompilerTriggerPort *trigger, int &minThreadsNeeded, int &maxThreadsNeeded)
1280{
1281 // Make a data structure of the number of threads needed for each chain.
1282 vector<VuoCompilerChain *> chainsForTrigger = getChains()[trigger];
1283 map<VuoCompilerChain *, pair<int, int> > threadsNeededForChain;
1284 for (vector<VuoCompilerChain *>::iterator i = chainsForTrigger.begin(); i != chainsForTrigger.end(); ++i)
1285 {
1286 VuoCompilerChain *chain = *i;
1287
1288 int minThreadsNeededForChain, maxThreadsNeededForChain;
1289 getWorkerThreadsNeeded(chain, minThreadsNeededForChain, maxThreadsNeededForChain);
1290
1291 threadsNeededForChain[chain] = make_pair(minThreadsNeededForChain, maxThreadsNeededForChain);
1292 }
1293
1294 // Make a data structure of which chains are downstream of which.
1295 map<VuoCompilerChain *, set<VuoCompilerChain *> > chainsDownstream;
1296 for (vector<VuoCompilerChain *>::iterator i = chainsForTrigger.begin(); i != chainsForTrigger.end(); ++i)
1297 {
1298 VuoCompilerChain *chain = *i;
1299 VuoCompilerNode *lastNodeInThisChain = chain->getNodes().back();
1300
1301 vector<VuoCompilerNode *> nodesDownstream = getNodesDownstream(lastNodeInThisChain, trigger);
1302
1303 for (vector<VuoCompilerChain *>::iterator j = i+1; j != chainsForTrigger.end(); ++j)
1304 {
1305 VuoCompilerChain *otherChain = *j;
1306 VuoCompilerNode *firstNodeInOtherChain = otherChain->getNodes().front();
1307
1308 if (find(nodesDownstream.begin(), nodesDownstream.end(), firstNodeInOtherChain) != nodesDownstream.end())
1309 chainsDownstream[chain].insert(otherChain);
1310 }
1311 }
1312
1313 // Make a data structure of which chains are immediately downstream and upstream of which.
1314 map<VuoCompilerChain *, set<VuoCompilerChain *> > chainsImmediatelyDownstream;
1315 map<VuoCompilerChain *, set<VuoCompilerChain *> > chainsImmediatelyUpstream;
1316 for (vector<VuoCompilerChain *>::iterator i = chainsForTrigger.begin(); i != chainsForTrigger.end(); ++i)
1317 {
1318 VuoCompilerChain *chain = *i;
1319 VuoCompilerNode *lastNodeInThisChain = chain->getNodes().back();
1320
1321 vector<VuoCompilerNode *> nodesDownstream = getNodesImmediatelyDownstream(lastNodeInThisChain, trigger);
1322
1323 for (vector<VuoCompilerChain *>::iterator j = i+1; j != chainsForTrigger.end(); ++j)
1324 {
1325 VuoCompilerChain *otherChain = *j;
1326 VuoCompilerNode *firstNodeInOtherChain = otherChain->getNodes().front();
1327
1328 if (find(nodesDownstream.begin(), nodesDownstream.end(), firstNodeInOtherChain) != nodesDownstream.end())
1329 {
1330 chainsImmediatelyDownstream[chain].insert(otherChain);
1331 chainsImmediatelyUpstream[otherChain].insert(chain);
1332 }
1333 }
1334 }
1335
1336 // Every trigger worker needs at least 1 thread, since it waits on the trigger node's semaphore.
1337 minThreadsNeeded = 1;
1338 maxThreadsNeeded = 1;
1339
1340
1341 // Find the maximum number of threads required — an approximation. Ideally, this is the thread count if
1342 // all chains that can possibly execute concurrently do so. This is approximated by adding up the thread
1343 // count for all scatters, scatters of scatters, etc., without taking into account any gathers. It may
1344 // be an overestimate of the ideal thread count.
1345
1346 // Collect the chains immediately downstream of the trigger.
1347 vector< pair<VuoCompilerChain *, vector<VuoCompilerChain *> > > potentialScatters;
1348 vector<VuoCompilerChain *> scatterForTrigger;
1349 for (vector<VuoCompilerChain *>::iterator i = chainsForTrigger.begin(); i != chainsForTrigger.end(); ++i)
1350 if (chainsImmediatelyUpstream.find(*i) == chainsImmediatelyUpstream.end())
1351 scatterForTrigger.push_back(*i);
1352 potentialScatters.push_back( make_pair((VuoCompilerChain *)NULL, scatterForTrigger) );
1353
1354 // Add in the chains immediately downstream of each chain.
1355 for (vector<VuoCompilerChain *>::iterator i = chainsForTrigger.begin(); i != chainsForTrigger.end(); ++i)
1356 {
1357 vector<VuoCompilerChain *> scatterForChain(chainsImmediatelyDownstream[*i].begin(), chainsImmediatelyDownstream[*i].end());
1358 potentialScatters.push_back( make_pair(*i, scatterForChain) );
1359 }
1360
1361 map<VuoCompilerChain *, int> threadsNeededForScatters;
1362 for (vector< pair<VuoCompilerChain *, vector<VuoCompilerChain *> > >::reverse_iterator i = potentialScatters.rbegin(); i != potentialScatters.rend(); ++i)
1363 {
1364 VuoCompilerChain *chain = (*i).first;
1365 vector<VuoCompilerChain *> scatterChains = (*i).second;
1366
1367 // Discard any chains that are downstream of another in the collection,
1368 // leaving a group of chains that may all execute concurrently.
1369 for (size_t j = 0; j < scatterChains.size(); ++j)
1370 {
1371 VuoCompilerChain *outer = scatterChains[j];
1372
1373 for (size_t k = j+1; k < scatterChains.size(); ++k)
1374 {
1375 VuoCompilerChain *inner = scatterChains[k];
1376
1377 if (chainsDownstream[inner].find(outer) != chainsDownstream[inner].end())
1378 {
1379 scatterChains.erase( scatterChains.begin() + j-- );
1380 break;
1381 }
1382
1383 if (chainsDownstream[outer].find(inner) != chainsDownstream[outer].end())
1384 scatterChains.erase( scatterChains.begin() + k-- );
1385 }
1386 }
1387
1388 // Add up the threads needed for the chains in the scatter.
1389 int threadsNeededForScatter = 0;
1390 for (vector<VuoCompilerChain *>::iterator j = scatterChains.begin(); j != scatterChains.end(); ++j)
1391 threadsNeededForScatter += threadsNeededForScatters[*j];
1392
1393 // Factor in the threads needed for this chain itself.
1394 threadsNeededForScatter = max(threadsNeededForScatter, threadsNeededForChain[chain].second);
1395
1396 threadsNeededForScatters[chain] = threadsNeededForScatter;
1397 }
1398
1399 // Find the maximum threads needed for any chain and its downstream scatters.
1400 for (map<VuoCompilerChain *, int>::iterator i = threadsNeededForScatters.begin(); i != threadsNeededForScatters.end(); ++i)
1401 maxThreadsNeeded = max(maxThreadsNeeded, i->second);
1402
1403
1404 // Find the minimum number of threads required. This is the thread count if all chains execute sequentially,
1405 // which is the maximum of the minimum threads needed across all chains in the composition. If the trigger
1406 // has no downstream chains, the trigger worker still needs 1 thread to wait on the trigger node's semaphore.
1407
1408 for (map<VuoCompilerChain *, pair<int, int> >::iterator i = threadsNeededForChain.begin(); i != threadsNeededForChain.end(); ++i)
1409 minThreadsNeeded = max(minThreadsNeeded, i->second.first);
1410}
1411
1423{
1424 vector< pair<VuoCompilerTriggerPort *, size_t> > distancesForMusts;
1425 vector< pair<VuoCompilerTriggerPort *, size_t> > distancesForMays;
1426
1427 for (map<VuoCompilerTriggerPort *, vector<Vertex> >::iterator i = vertices.begin(); i != vertices.end(); ++i)
1428 {
1429 VuoCompilerTriggerPort *trigger = i->first;
1430 vector<Vertex> verticesDownstream = i->second;
1431
1432 for (vector<Vertex>::iterator j = verticesDownstream.begin(); j != verticesDownstream.end(); ++j)
1433 {
1434 Vertex vertex = *j;
1435 if (vertex.toNode == node)
1436 {
1437 pair<VuoCompilerTriggerPort *, size_t> p = make_pair(trigger, vertexDistanceFromTrigger[trigger][vertex]);
1438 if (triggerMustTransmitToVertex[trigger][vertex])
1439 distancesForMusts.push_back(p);
1440 else
1441 distancesForMays.push_back(p);
1442 }
1443 }
1444 }
1445
1446 if (! distancesForMusts.empty())
1447 {
1448 sort(distancesForMusts.begin(), distancesForMusts.end(), compareTriggers);
1449 return distancesForMusts[0].first;
1450 }
1451 else if (! distancesForMays.empty())
1452 {
1453 sort(distancesForMays.begin(), distancesForMays.end(), compareTriggers);
1454 return distancesForMays[0].first;
1455 }
1456 else
1457 return NULL;
1458}
1459
1464bool VuoCompilerGraph::compareTriggers(const pair<VuoCompilerTriggerPort *, size_t> &lhs, const pair<VuoCompilerTriggerPort *, size_t> &rhs)
1465{
1466 if (lhs.second == rhs.second)
1467 return lhs.first->getIdentifier() < rhs.first->getIdentifier();
1468
1469 return lhs.second < rhs.second;
1470}
1471
1487{
1488 if (! publishedInputTrigger)
1490
1491 // If no event-only or data-and-event published output ports, return "none".
1492
1493 VuoCompilerPublishedOutputNodeClass *publishedOutputNodeClass = static_cast<VuoCompilerPublishedOutputNodeClass *>(publishedOutputNode->getBase()->getNodeClass()->getCompiler());
1494 size_t publishedOutputCount = publishedOutputNodeClass->getGatherInputPortIndex() - VuoNodeClass::unreservedInputPortStartIndex;
1495 set<string> publishedOutputTriggers = getPublishedOutputTriggers();
1496 if (publishedOutputCount - publishedOutputTriggers.size() == 0)
1498
1499 VuoPort *outputPort = getOutputPortOnPublishedInputNode(publishedInputPortIndex);
1500
1501 set<Vertex> verticesFromPublishedInputNode;
1502 for (Vertex vertex : vertices[publishedInputTrigger])
1503 if (vertex.fromNode == publishedInputNode)
1504 verticesFromPublishedInputNode.insert(vertex);
1505
1506 // Find the vertices downstream of the published input node with no intervening doors or walls,
1507 // excluding vertices to the published output node's gather port.
1508
1510
1511 auto includeNonBlocking = [this, gatherPort] (Edge edge)
1512 {
1513 return mustTransmit(edge.fromVertex.cableBundle, edge.toVertex.cableBundle) &&
1514 ! (edge.toVertex.cableBundle.size() == 1 && (*edge.toVertex.cableBundle.begin())->getBase()->getToPort() == gatherPort);
1515 };
1516
1517 if (downstreamVerticesNonBlocking.empty())
1518 {
1519 set<Vertex> unused;
1520 makeDownstreamVerticesWithInclusionRule(publishedInputTrigger, includeNonBlocking, downstreamVerticesNonBlocking, unused);
1521 }
1522
1523 // Find the vertices downstream of the published input node with no intervening walls,
1524 // excluding vertices going into the published output node's gather port.
1525
1526 auto includeNonBlockingAndDoor = [this, gatherPort] (Edge edge)
1527 {
1528 return mayTransmit(edge.fromVertex.cableBundle, edge.toVertex.cableBundle) &&
1529 ! (edge.toVertex.cableBundle.size() == 1 && (*edge.toVertex.cableBundle.begin())->getBase()->getToPort() == gatherPort);
1530 };
1531
1532 if (downstreamVerticesNonBlockingOrDoor.empty())
1533 {
1534 set<Vertex> unused;
1535 makeDownstreamVerticesWithInclusionRule(publishedInputTrigger, includeNonBlockingAndDoor, downstreamVerticesNonBlockingOrDoor, unused);
1536 }
1537
1538 auto minBlocking = [] (VuoPortClass::EventBlocking b1, VuoPortClass::EventBlocking b2)
1539 {
1540 // Returns the one that lets more events through.
1541 return min(b1, b2);
1542 };
1543
1544 auto maxBlocking = [] (VuoPortClass::EventBlocking b1, VuoPortClass::EventBlocking b2)
1545 {
1546 // Returns the one that blocks more events.
1547 return max(b1, b2);
1548 };
1549
1551 vector<Vertex> verticesToPublishedOutputNode;
1552 for (Vertex vertex : verticesFromPublishedInputNode)
1553 {
1555
1556 // Check the event blocking for the exact cables connected to the output port on the published input node.
1557 for (VuoCompilerCable *cable : vertex.cableBundle)
1558 {
1559 if (cable->getBase()->getFromPort() == outputPort)
1560 {
1561 VuoPortClass::EventBlocking toPortBlocking = cable->getBase()->getToPort()->getClass()->getEventBlocking();
1562 blockingForVertex = minBlocking(blockingForVertex, toPortBlocking);
1563 }
1564 }
1565
1566 if (blockingForVertex == VuoPortClass::EventBlocking_Wall)
1567 {
1568 // If the event is blocked at the cables from the published input node's output port, it's a wall.
1569 }
1570 else
1571 {
1572 auto isVertexToPublishedOutputNode = [this] (Vertex downstreamVertex) { return downstreamVertex.toNode == publishedOutputNode; };
1573
1574 vector<Vertex> verticesNonBlocking;
1575 std::copy_if(downstreamVerticesNonBlocking[vertex].begin(), downstreamVerticesNonBlocking[vertex].end(),
1576 std::back_inserter(verticesNonBlocking), isVertexToPublishedOutputNode);
1577
1578 verticesToPublishedOutputNode.insert(verticesToPublishedOutputNode.end(), verticesNonBlocking.begin(), verticesNonBlocking.end());
1579
1580 if (! verticesNonBlocking.empty())
1581 {
1582 // If the event can reach the published outputs without any further blocking, it's whatever blocking
1583 // the cables from the published input node's output port have (none or door).
1584 }
1585 else
1586 {
1587 vector<Vertex> verticesNonBlockingOrDoor;
1588 std::copy_if(downstreamVerticesNonBlockingOrDoor[vertex].begin(), downstreamVerticesNonBlockingOrDoor[vertex].end(),
1589 std::back_inserter(verticesNonBlockingOrDoor), isVertexToPublishedOutputNode);
1590
1591 verticesToPublishedOutputNode.insert(verticesToPublishedOutputNode.end(), verticesNonBlocking.begin(), verticesNonBlocking.end());
1592
1593 if (! verticesNonBlockingOrDoor.empty())
1594 {
1595 // If the event can reach the published outputs via door blocking, it's a door.
1596 blockingForVertex = VuoPortClass::EventBlocking_Door;
1597 }
1598 else
1599 {
1600 // If the event can't reach the published outputs, it's a wall.
1601 blockingForVertex = VuoPortClass::EventBlocking_Wall;
1602 }
1603 }
1604 }
1605
1606 blocking = minBlocking(blocking, blockingForVertex);
1607 }
1608
1609 // If the event can reach some but not all of the non-trigger published outputs, it's a door.
1610
1611 set<VuoPort *> publishedOutputPortsReached;
1612 for (Vertex vertex : verticesToPublishedOutputNode)
1613 for (VuoCompilerCable *cable : vertex.cableBundle)
1614 if (publishedOutputTriggers.find(cable->getBase()->getToPort()->getClass()->getName()) == publishedOutputTriggers.end())
1615 publishedOutputPortsReached.insert(cable->getBase()->getToPort());
1616
1617 if (publishedOutputPortsReached.size() < publishedOutputCount - publishedOutputTriggers.size())
1618 blocking = maxBlocking(blocking, VuoPortClass::EventBlocking_Door);
1619
1620 return blocking;
1621}
1622
1628{
1629 set<string> triggerNames;
1630 for (VuoCompilerTriggerPort *trigger : triggers)
1631 {
1632 set<string> currTriggerNames = getPublishedOutputTriggersDownstream(trigger);
1633 triggerNames.insert(currTriggerNames.begin(), currTriggerNames.end());
1634 }
1635
1636 return triggerNames;
1637}
1638
1644{
1645 if (trigger == publishedInputTrigger)
1646 return set<string>();
1647
1648 set<string> outputNames = getPublishedOutputPortsDownstream(trigger);
1649 set<string> outputNamesForPublishedInputTrigger = getPublishedOutputPortsDownstream(publishedInputTrigger);
1650
1651 set<string> triggerNames;
1652 std::set_difference(outputNames.begin(), outputNames.end(),
1653 outputNamesForPublishedInputTrigger.begin(), outputNamesForPublishedInputTrigger.end(),
1654 std::inserter(triggerNames, triggerNames.begin()));
1655
1656 return triggerNames;
1657}
1658
1663{
1664 auto iter = publishedOutputNames.find(trigger);
1665 if (iter != publishedOutputNames.end())
1666 return iter->second;
1667
1668 set<string> names;
1669 for (Vertex vertex : vertices[trigger])
1670 {
1671 if (vertex.toNode == publishedOutputNode)
1672 {
1673 for (VuoCompilerCable *cable : vertex.cableBundle)
1674 {
1675 VuoPort *toPort = cable->getBase()->getToPort();
1676 if (toPort != getGatherPortOnPublishedOutputNode())
1677 names.insert(toPort->getClass()->getName());
1678 }
1679 }
1680 }
1681
1682 publishedOutputNames[trigger] = names;
1683 return names;
1684}
1685
1691{
1692 for (VuoCompilerTriggerPort *trigger : triggers)
1693 if (! getPublishedOutputPortsDownstream(trigger).empty())
1694 return true;
1695
1696 return false;
1697}
1698
1703{
1704 vector<VuoCompilerNode *> downstreamNodes = getNodesDownstream(node, trigger);
1705 return find(downstreamNodes.begin(), downstreamNodes.end(), node) != downstreamNodes.end();
1706}
1707
1712{
1713 vector<VuoCompilerNode *> downstreamNodes = getNodesDownstream(node, trigger);
1714 for (vector<VuoCompilerNode *>::iterator i = downstreamNodes.begin(); i != downstreamNodes.end(); ++i)
1715 if (getNumVerticesWithToNode(*i, trigger) > 1)
1716 return true;
1717
1718 return false;
1719}
1720
1726{
1727 bool isScatterAtTrigger = getNodesImmediatelyDownstream(trigger).size() > 1;
1728 if (isScatterAtTrigger)
1729 {
1730 vector<VuoCompilerNode *> downstreamNodes = getNodesDownstream(trigger);
1731 return areNodesPartiallyOverlappedByAnotherTrigger(downstreamNodes, trigger);
1732 }
1733
1734 return false;
1735}
1736
1742{
1743 bool isScatterAtNode = getNodesImmediatelyDownstream(node, trigger).size() > 1;
1744 if (isScatterAtNode)
1745 {
1746 vector<VuoCompilerNode *> downstreamNodes = getNodesDownstream(node, trigger);
1747 return areNodesPartiallyOverlappedByAnotherTrigger(downstreamNodes, trigger);
1748 }
1749
1750 return false;
1751}
1752
1756bool VuoCompilerGraph::areNodesPartiallyOverlappedByAnotherTrigger(const vector<VuoCompilerNode *> &nodes, VuoCompilerTriggerPort *trigger)
1757{
1758 for (vector<VuoCompilerTriggerPort *>::iterator i = triggers.begin(); i != triggers.end(); ++i)
1759 {
1760 VuoCompilerTriggerPort *otherTrigger = *i;
1761 if (otherTrigger == trigger)
1762 continue;
1763
1764 vector<VuoCompilerNode *> nodesDownstreamOfOtherTrigger = getNodesDownstream(otherTrigger);
1765 nodesDownstreamOfOtherTrigger.push_back(nodeForTrigger[otherTrigger]);
1766
1767 bool hasOverlappedNode = false;
1768 bool hasNonOverlappedNode = false;
1769 for (vector<VuoCompilerNode *>::const_iterator j = nodes.begin(); j != nodes.end(); ++j)
1770 {
1771 VuoCompilerNode *downstreamNode = *j;
1772
1773 if (find(nodesDownstreamOfOtherTrigger.begin(), nodesDownstreamOfOtherTrigger.end(), downstreamNode) != nodesDownstreamOfOtherTrigger.end())
1774 hasOverlappedNode = true;
1775 else
1776 hasNonOverlappedNode = true;
1777 }
1778
1779 if (hasOverlappedNode && hasNonOverlappedNode)
1780 return true;
1781 }
1782
1783 return false;
1784}
1785
1791{
1792 vector<VuoCompilerNode *> nodesDownstreamOfTrigger = getNodesDownstream(trigger);
1793 vector<VuoCompilerNode *> nodesDownstreamOfSpinOffs;
1794
1795 // Collect all nodes downstream of Spin Off nodes downstream of this trigger
1796 // (including Spin Offs downstream of other Spin Offs downstream of this trigger).
1797 vector<VuoCompilerNode *> nodesToCheck = nodesDownstreamOfTrigger;
1798 map<VuoCompilerNode *, bool> nodesChecked;
1799 while (! nodesToCheck.empty())
1800 {
1801 VuoCompilerNode *node = nodesToCheck.back();
1802 nodesToCheck.pop_back();
1803
1804 if (nodesChecked[node])
1805 continue;
1806 nodesChecked[node] = true;
1807
1808 if (node == nodeForTrigger[publishedInputTrigger])
1809 continue;
1810
1811 string nodeClassName = node->getBase()->getNodeClass()->getClassName();
1812 if (VuoStringUtilities::beginsWith(nodeClassName, "vuo.event.spinOff"))
1813 {
1815 VuoCompilerTriggerPort *spinOffTrigger = static_cast<VuoCompilerTriggerPort *>( spinOffOutput->getCompiler() );
1816 vector<VuoCompilerNode *> nodesDownstream = getNodesDownstream(spinOffTrigger);
1817
1818 nodesDownstreamOfSpinOffs.insert(nodesDownstreamOfSpinOffs.end(), nodesDownstream.begin(), nodesDownstream.end());
1819 nodesToCheck.insert(nodesToCheck.end(), nodesDownstream.begin(), nodesDownstream.end());
1820 }
1821 }
1822
1823 sort(nodesDownstreamOfTrigger.begin(), nodesDownstreamOfTrigger.end());
1824 sort(nodesDownstreamOfSpinOffs.begin(), nodesDownstreamOfSpinOffs.end());
1825
1826 // Do this trigger and the Spin Offs have any downstream nodes in common?
1827 set<VuoCompilerNode *> nodesDownstreamOfBoth;
1828 set_intersection(nodesDownstreamOfTrigger.begin(), nodesDownstreamOfTrigger.end(),
1829 nodesDownstreamOfSpinOffs.begin(), nodesDownstreamOfSpinOffs.end(),
1830 std::inserter(nodesDownstreamOfBoth, nodesDownstreamOfBoth.begin()));
1831 return ! nodesDownstreamOfBoth.empty();
1832}
1833
1844{
1845 if (! repeatedVertices.empty())
1846 {
1847 // Identify the nodes and cables involved in the infinite feedback loop.
1848 vector< set<VuoNode *> > nodesInLoops;
1849 vector< set<VuoCable *> > cablesInLoops;
1850 for (const auto &i : repeatedVertices)
1851 {
1852 VuoCompilerTriggerPort *trigger = i.first;
1853 for (const Vertex &repeatedVertex : i.second)
1854 {
1855 set<VuoNode *> nodesInLoop;
1856 set<VuoCable *> cablesInLoop;
1857 for (const Vertex &otherVertex : vertices[trigger])
1858 {
1859 if (downstreamVertices[trigger][repeatedVertex].find(otherVertex) != downstreamVertices[trigger][repeatedVertex].end() &&
1860 downstreamVertices[trigger][otherVertex].find(repeatedVertex) != downstreamVertices[trigger][otherVertex].end())
1861 {
1862 nodesInLoop.insert(otherVertex.fromNode->getBase());
1863 nodesInLoop.insert(otherVertex.toNode->getBase());
1864
1865 for (set<VuoCompilerCable *>::iterator m = otherVertex.cableBundle.begin(); m != otherVertex.cableBundle.end(); ++m)
1866 cablesInLoop.insert((*m)->getBase());
1867 }
1868 }
1869 nodesInLoops.push_back(nodesInLoop);
1870 cablesInLoops.push_back(cablesInLoop);
1871 }
1872 }
1873
1874 // Coalesce the lists of nodes and cables to avoid reporting the same ones multiple times.
1875 vector< set<VuoNode *> > coalescedNodesInLoops;
1876 vector< set<VuoCable *> > coalescedCablesInLoops;
1877 for (size_t i = 0; i < nodesInLoops.size(); ++i)
1878 {
1879 bool coalesced = false;
1880 for (size_t j = 0; j < coalescedNodesInLoops.size(); ++j)
1881 {
1882 set<VuoNode *> nodesInBoth;
1883 std::set_intersection(nodesInLoops[i].begin(), nodesInLoops[i].end(),
1884 coalescedNodesInLoops[j].begin(), coalescedNodesInLoops[j].end(),
1885 std::inserter(nodesInBoth, nodesInBoth.begin()));
1886
1887 set<VuoCable *> cablesInBoth;
1888 std::set_intersection(cablesInLoops[i].begin(), cablesInLoops[i].end(),
1889 coalescedCablesInLoops[j].begin(), coalescedCablesInLoops[j].end(),
1890 std::inserter(cablesInBoth, cablesInBoth.begin()));
1891
1892 if (nodesInBoth.size() == nodesInLoops[i].size() && cablesInBoth.size() == cablesInLoops[i].size())
1893 {
1894 // One of the coalesced items is a superset of nodesInLoops[i] and cablesInLoops[i]. Already taken care of.
1895 coalesced = true;
1896 break;
1897 }
1898 else if (nodesInBoth.size() == coalescedNodesInLoops[j].size() && cablesInBoth.size() == coalescedCablesInLoops[j].size())
1899 {
1900 // One of the coalesced items is a subset of nodesInLoops[i] and cablesInLoops[i]. Merge in the additional nodes and cables.
1901 coalescedNodesInLoops[j] = nodesInLoops[i];
1902 coalescedCablesInLoops[j] = cablesInLoops[i];
1903 coalesced = true;
1904 break;
1905 }
1906 }
1907
1908 if (! coalesced)
1909 {
1910 // No existing items to coalesce with, so add a new one.
1911 coalescedNodesInLoops.push_back(nodesInLoops[i]);
1912 coalescedCablesInLoops.push_back(cablesInLoops[i]);
1913 }
1914 }
1915
1916 auto exceptionIssues = new VuoCompilerIssues;
1917 for (size_t i = 0; i < coalescedNodesInLoops.size(); ++i)
1918 {
1919 VuoCompilerIssue issue(VuoCompilerIssue::Error, "compiling composition", "",
1920 "Infinite feedback loop",
1921 "An event is not allowed to travel through the same cable more than once.");
1922 issue.setHelpPath("errors-warnings-and-other-issues.html");
1923 issue.setNodes(coalescedNodesInLoops[i]);
1924 issue.setCables(coalescedCablesInLoops[i]);
1925 exceptionIssues->append(issue);
1926 if (issues)
1927 issues->append(issue);
1928 }
1929
1930 throw VuoCompilerException(exceptionIssues, true);
1931 }
1932}
1933
1944{
1945 auto exceptionIssues = new VuoCompilerIssues;
1946 for (VuoCompilerTriggerPort *trigger : triggers)
1947 {
1948 // For each node, find all nodes downstream of it.
1949 map<VuoCompilerNode *, set<VuoCompilerNode *> > downstreamNodes;
1950 for (map<Vertex, set<Vertex> >::iterator j = downstreamVertices[trigger].begin(); j != downstreamVertices[trigger].end(); ++j)
1951 {
1952 Vertex upstreamVertex = j->first;
1953 set<VuoCompilerNode *> downstreamNodesForVertex;
1954 bool isRepeated = false;
1955
1956 for (set<Vertex>::iterator k = j->second.begin(); k != j->second.end(); ++k)
1957 {
1958 Vertex downstreamVertex = *k;
1959 if (upstreamVertex.toNode == downstreamVertex.toNode)
1960 {
1961 isRepeated = true;
1962 break;
1963 }
1964
1965 downstreamNodesForVertex.insert(downstreamVertex.toNode);
1966 }
1967
1968 if (! isRepeated)
1969 downstreamNodes[upstreamVertex.toNode].insert(downstreamNodesForVertex.begin(), downstreamNodesForVertex.end());
1970 }
1971
1972 // Find any pairs of nodes that are mutually downstream.
1973 set< pair<VuoCompilerNode *, VuoCompilerNode *> > mutuallyDownstreamNodePairs;
1974 for (map<VuoCompilerNode *, set<VuoCompilerNode *> >::iterator j = downstreamNodes.begin(); j != downstreamNodes.end(); ++j)
1975 {
1976 VuoCompilerNode *upstreamNode = j->first;
1977 for (set<VuoCompilerNode *>::iterator k = j->second.begin(); k != j->second.end(); ++k)
1978 {
1979 VuoCompilerNode *downstreamNode = *k;
1980 if (downstreamNodes[downstreamNode].find(upstreamNode) != downstreamNodes[downstreamNode].end() &&
1981 mutuallyDownstreamNodePairs.find( make_pair(downstreamNode, upstreamNode) ) == mutuallyDownstreamNodePairs.end())
1982 {
1983 mutuallyDownstreamNodePairs.insert( make_pair(upstreamNode, downstreamNode) );
1984 }
1985 }
1986 }
1987
1988 // Report any errors, with all nodes and cables involved in the deadlocked feedback loops.
1989 for (set< pair<VuoCompilerNode *, VuoCompilerNode *> >::iterator j = mutuallyDownstreamNodePairs.begin(); j != mutuallyDownstreamNodePairs.end(); ++j)
1990 {
1991 VuoCompilerNode *firstNode = j->first;
1992 VuoCompilerNode *secondNode = j->second;
1993
1994 set<VuoNode *> nodesInLoop;
1995 set<VuoCable *> cablesInLoop;
1996 nodesInLoop.insert(firstNode->getBase());
1997 nodesInLoop.insert(secondNode->getBase());
1998
1999 for (set<VuoCompilerNode *>::iterator k = downstreamNodes[firstNode].begin(); k != downstreamNodes[firstNode].end(); ++k)
2000 {
2001 VuoCompilerNode *otherNode = *k;
2002 if (downstreamNodes[otherNode].find(secondNode) != downstreamNodes[otherNode].end())
2003 nodesInLoop.insert(otherNode->getBase());
2004 }
2005 for (set<VuoCompilerNode *>::iterator k = downstreamNodes[secondNode].begin(); k != downstreamNodes[secondNode].end(); ++k)
2006 {
2007 VuoCompilerNode *otherNode = *k;
2008 if (downstreamNodes[otherNode].find(firstNode) != downstreamNodes[otherNode].end())
2009 nodesInLoop.insert(otherNode->getBase());
2010 }
2011
2012 for (vector<Vertex>::iterator k = vertices[trigger].begin(); k != vertices[trigger].end(); ++k)
2013 {
2014 Vertex vertex = *k;
2015 if (vertex.fromNode &&
2016 nodesInLoop.find(vertex.fromNode->getBase()) != nodesInLoop.end() &&
2017 nodesInLoop.find(vertex.toNode->getBase()) != nodesInLoop.end())
2018 {
2019 for (set<VuoCompilerCable *>::iterator m = vertex.cableBundle.begin(); m != vertex.cableBundle.end(); ++m)
2020 cablesInLoop.insert((*m)->getBase());
2021 }
2022 }
2023
2024 VuoCompilerIssue issue(VuoCompilerIssue::Error, "compiling composition", "",
2025 "Deadlocked feedback loop",
2026 "There's more than one possible path for events to travel through this composition. "
2027 "It's unclear which is the correct one.");
2028 issue.setHelpPath("errors-warnings-and-other-issues.html");
2029 issue.setNodes(nodesInLoop);
2030 issue.setCables(cablesInLoop);
2031 exceptionIssues->append(issue);
2032 if (issues)
2033 issues->append(issue);
2034 }
2035 }
2036
2037 if (!exceptionIssues->isEmpty())
2038 throw VuoCompilerException(exceptionIssues, true);
2039}
2040
2045{
2046 ostringstream s;
2047
2048 // nodes — pointers, identifiers
2049 s << "[";
2050 for (VuoNode *node : composition->getBase()->getNodes())
2051 {
2052 VuoCompilerNode *compilerNode = NULL;
2053 string identifier;
2054 if (node->hasCompiler())
2055 {
2056 compilerNode = node->getCompiler();
2057 identifier = compilerNode->getIdentifier();
2058 }
2059 s << "{" << compilerNode << "," << identifier << "},";
2060 }
2061 s << "]";
2062
2063 // cables — pointers, node identifiers, port identifiers
2064 s << "[";
2065 set<VuoCable *> cables = composition->getBase()->getCables();
2066 for (set<VuoCable *>::iterator i = cables.begin(); i != cables.end(); ++i)
2067 {
2069 string fromNode = ((*i)->getFromNode() && (*i)->getFromNode()->hasCompiler()) ? (*i)->getFromNode()->getCompiler()->getIdentifier() : "";
2070 string fromPort = (*i)->getFromPort() ? (*i)->getFromPort()->getClass()->getName() : "";
2071 string toNode = ((*i)->getToNode() && (*i)->getToNode()->hasCompiler()) ? (*i)->getToNode()->getCompiler()->getIdentifier() : "";
2072 string toPort = (*i)->getToPort() ? (*i)->getToPort()->getClass()->getName() : "";
2073 s << "{" << (*i)->getCompiler() << "," << fromNode << "," << fromPort << "," << toNode << "," << toPort << "},";
2074 }
2075 s << "]";
2076
2077 // published ports — pointers, identifiers
2078 s << "[";
2079 vector<VuoPublishedPort *> publishedInputPorts = composition->getBase()->getPublishedInputPorts();
2080 for (vector<VuoPublishedPort *>::iterator i = publishedInputPorts.begin(); i != publishedInputPorts.end(); ++i)
2081 s << "{" << (*i)->getCompiler() << "," << (*i)->getClass()->getName() << "},";
2082 s << "]";
2083 s << "[";
2084 vector<VuoPublishedPort *> publishedOutputPorts = composition->getBase()->getPublishedOutputPorts();
2085 for (vector<VuoPublishedPort *>::iterator i = publishedOutputPorts.begin(); i != publishedOutputPorts.end(); ++i)
2086 s << "{" << (*i)->getCompiler() << "," << (*i)->getClass()->getName() << "},";
2087 s << "]";
2088
2089 // manually firable input port — pointer, identifier
2090 s << "[";
2091 VuoPort *manuallyFirableInputPort = composition->getManuallyFirableInputPort();
2092 if (manuallyFirableInputPort)
2093 s << "{" << manuallyFirableInputPort->getCompiler() << "," << manuallyFirableInputPort->getClass()->getName() << "},";
2094 s << "]";
2095
2096 return VuoStringUtilities::hash(s.str());
2097}
2098
2106
2111{
2112 return "ManuallyFirableTrigger";
2113}
2114
2118VuoCompilerGraph::Vertex::Vertex(VuoCompilerTriggerPort *fromTrigger, VuoCompilerNode *toNode)
2119{
2120 this->fromNode = NULL;
2121 this->fromTrigger = fromTrigger;
2122 this->toNode = toNode;
2123}
2124
2128VuoCompilerGraph::Vertex::Vertex(VuoCompilerNode *fromNode, VuoCompilerNode *toNode)
2129{
2130 this->fromTrigger = NULL;
2131 this->fromNode = fromNode;
2132 this->toNode = toNode;
2133}
2134
2138VuoCompilerGraph::Vertex::Vertex(void)
2139{
2140 this->fromNode = NULL;
2141 this->fromTrigger = NULL;
2142 this->toNode = NULL;
2143}
2144
2148VuoCompilerGraph::Edge::Edge(const VuoCompilerGraph::Vertex &fromVertex, const VuoCompilerGraph::Vertex &toVertex)
2149 : fromVertex(fromVertex), toVertex(toVertex)
2150{
2151}
2152
2156VuoCompilerGraph::Edge::Edge(void)
2157{
2158}
2159
2163bool operator==(const VuoCompilerGraph::Vertex &lhs, const VuoCompilerGraph::Vertex &rhs)
2164{
2165 return (lhs.fromTrigger == rhs.fromTrigger && lhs.fromNode == rhs.fromNode && lhs.toNode == rhs.toNode);
2166}
2167
2171bool operator!=(const VuoCompilerGraph::Vertex &lhs, const VuoCompilerGraph::Vertex &rhs)
2172{
2173 return ! (lhs == rhs);
2174}
2175
2179bool operator<(const VuoCompilerGraph::Vertex &lhs, const VuoCompilerGraph::Vertex &rhs)
2180{
2181 return (lhs.fromTrigger != rhs.fromTrigger ?
2182 lhs.fromTrigger < rhs.fromTrigger :
2183 (lhs.fromNode != rhs.fromNode ?
2184 lhs.fromNode < rhs.fromNode :
2185 lhs.toNode < rhs.toNode));
2186}
2187
2191bool operator<(const VuoCompilerGraph::Edge &lhs, const VuoCompilerGraph::Edge &rhs)
2192{
2193 return (lhs.fromVertex != rhs.fromVertex ?
2194 lhs.fromVertex < rhs.fromVertex :
2195 lhs.toVertex < rhs.toVertex);
2196}
2197
2201string VuoCompilerGraph::Vertex::toString(void) const
2202{
2203 return (fromNode ?
2204 fromNode->getBase()->getTitle() :
2205 fromTrigger->getBase()->getClass()->getName()) +
2206 "->" + toNode->getBase()->getTitle();
2207}
2208
2212string VuoCompilerGraph::Edge::toString(void) const
2213{
2214 return fromVertex.toString() + " -> " + toVertex.toString();
2215}