Vuo  2.3.2
VuoCompilerGraphvizParser.cc
Go to the documentation of this file.
1 
10 #include <regex>
11 #include <sstream>
12 #include <stdlib.h>
13 #include <graphviz/gvc.h>
14 
15 #include "VuoCable.hh"
16 #include "VuoCompiler.hh"
17 #include "VuoCompilerCable.hh"
18 #include "VuoCompilerComment.hh"
19 #include "VuoCompilerException.hh"
22 #include "VuoCompilerIssue.hh"
23 #include "VuoCompilerNode.hh"
24 #include "VuoCompilerNodeClass.hh"
27 #include "VuoCompilerType.hh"
28 #include "VuoComposition.hh"
30 #include "VuoComment.hh"
31 #include "VuoException.hh"
32 #include "VuoNode.hh"
33 #include "VuoNodeClass.hh"
34 #include "VuoPublishedPort.hh"
35 #include "VuoStringUtilities.hh"
36 #include "VuoType.hh"
37 
38 
39 extern gvplugin_library_t gvplugin_dot_layout_LTX_library;
40 extern gvplugin_library_t gvplugin_core_LTX_library;
41 
43 lt_symlist_t lt_preloaded_symbols[] =
44 {
45  { "gvplugin_dot_layout_LTX_library", &gvplugin_dot_layout_LTX_library},
46  { "gvplugin_core_LTX_library", &gvplugin_core_LTX_library},
47  { 0, 0}
48 };
49 
50 dispatch_queue_t VuoCompilerGraphvizParser::graphvizQueue = dispatch_queue_create("org.vuo.compiler.graphviz", NULL);
51 
58 {
59  try
60  {
61  string composition = VuoFileUtilities::readFileToString(path);
62  return newParserFromCompositionString(composition, compiler);
63  }
64  catch (VuoCompilerException &e)
65  {
66  e.getIssues()->setFilePathIfEmpty(path);
67  throw;
68  }
69  catch (VuoException &e)
70  {
71  VuoCompilerIssue issue(VuoCompilerIssue::Error, "opening composition", path,
72  "", e.what());
73  throw VuoCompilerException(issue);
74  }
75 }
76 
83 {
84  // First pass: Just parse the names of node classes and types.
85 
86  VuoCompilerGraphvizParser partialParser;
87  partialParser.parse(composition);
88 
89  // Look up the node classes and types from the compiler. Doing this now, instead of later when we're on graphvizQueue,
90  // avoids deadlock when loading subcompositions. (VuoCompiler::environmentQueue can call graphvizQueue, but not vice versa.)
91 
92  map<string, VuoCompilerNodeClass *> compilerNodeClasses;
93  map<string, VuoCompilerType *> compilerTypes;
94  set<string> compilerProNodeClassNames;
95 
96  auto addPortTypes = [&](const vector<VuoPortClass *> &portClasses)
97  {
98  for (VuoPortClass *portClass : portClasses)
99  {
100  VuoType *type = static_cast<VuoCompilerPortClass *>(portClass->getCompiler())->getDataVuoType();
101  if (type)
102  compilerTypes[type->getModuleKey()] = type->getCompiler();
103  }
104  };
105 
106  for (auto nodeClass : partialParser.dummyNodeClassForName)
107  {
108  VuoCompilerNodeClass *compilerNodeClass = compiler->getNodeClass(nodeClass.first);
109  compilerNodeClasses[nodeClass.first] = compilerNodeClass;
110 
111  if (compilerNodeClass)
112  {
113  addPortTypes(compilerNodeClass->getBase()->getInputPortClasses());
114  addPortTypes(compilerNodeClass->getBase()->getOutputPortClasses());
115  }
116  }
117 
118  for (auto type : partialParser.typeForPublishedInputPort)
119  if (type.second != "event")
120  compilerTypes[type.second] = compiler->getType(type.second);
121 
122  for (auto type : partialParser.typeForPublishedOutputPort)
123  if (type.second != "event")
124  compilerTypes[type.second] = compiler->getType(type.second);
125 
126 #if VUO_PRO
127  for (auto nodeClass : partialParser.dummyNodeClassForName)
128  if (compiler->isProModule(nodeClass.first))
129  compilerProNodeClassNames.insert(nodeClass.first);
130 #endif
131 
132  // Second pass: Do the full parsing.
133 
134  VuoCompilerGraphvizParser *fullParser = new VuoCompilerGraphvizParser(compiler, compilerNodeClasses, compilerTypes, compilerProNodeClassNames);
135  fullParser->parse(composition);
136  return fullParser;
137 }
138 
145 {
146  set<string> nodeClassNames;
147 
148  try
149  {
150  string composition = VuoFileUtilities::readFileToString(path);
151 
153  parser.parse(composition);
154 
155  for (auto i : parser.dummyNodeClassForName)
156  nodeClassNames.insert(i.first);
157  }
158  catch (VuoCompilerException &e)
159  {
160  e.getIssues()->setFilePathIfEmpty(path);
161  throw;
162  }
163  catch (VuoException &e)
164  {
165  VuoCompilerIssue issue(VuoCompilerIssue::Error, "opening composition", path,
166  "", e.what());
167  throw VuoCompilerException(issue);
168  }
169 
170  return nodeClassNames;
171 }
172 
174 
178 static int VuoCompilerGraphvizParser_error(char *message)
179 {
181  if (VuoCompilerGraphvizParser_lastError.find('\n') != string::npos)
183  return 0;
184 }
185 
190 VuoCompilerGraphvizParser::VuoCompilerGraphvizParser(void) :
191  compiler(nullptr)
192 {
193  init();
194 }
195 
201 VuoCompilerGraphvizParser::VuoCompilerGraphvizParser(VuoCompiler *compiler,
202  const map<string, VuoCompilerNodeClass *> &compilerNodeClasses,
203  const map<string, VuoCompilerType *> &compilerTypes,
204  const set<string> &compilerProNodeClassNames) :
205  compiler(compiler),
206  compilerNodeClasses(compilerNodeClasses),
207  compilerTypes(compilerTypes),
208  compilerProNodeClassNames(compilerProNodeClassNames)
209 {
210  init();
211 }
212 
216 void VuoCompilerGraphvizParser::init(void)
217 {
218  publishedInputNode = nullptr;
219  publishedOutputNode = nullptr;
220  manuallyFirableInputNode = nullptr;
221  manuallyFirableInputPort = nullptr;
222  metadata = nullptr;
223 }
224 
232 void VuoCompilerGraphvizParser::parse(const string &compositionAsStringOrig)
233 {
234  if (compositionAsStringOrig.empty())
235  throw VuoCompilerException(VuoCompilerIssue(VuoCompilerIssue::Error, "parsing composition string", "", "composition string is empty", ""));
236 
237  // Backwards compatibility:
238  // If the composition contains the 'manuallyFirable' attribute name without a value, add an empty value so graphviz can parse it.
239  string compositionAsString = std::regex_replace(compositionAsStringOrig, std::regex("(_\\w+_manuallyFirable)(\\s*[^=])"), "$1=\"yes\"$2");
240 
241  VuoCompilerIssues *issues = new VuoCompilerIssues();
242  dispatch_sync(graphvizQueue, ^{
245 
246  // Use builtin Graphviz plugins, not demand-loaded plugins.
247  bool demandLoading = false;
248  GVC_t *context = gvContextPlugins(lt_preloaded_symbols, demandLoading);
249 
250  graph = agmemread((char *)compositionAsString.c_str());
251  if (!graph)
252  {
253  VuoCompilerIssue issue(VuoCompilerIssue::Error, "parsing composition", "",
254  "Graphviz couldn't parse the composition", VuoCompilerGraphvizParser_lastError);
255  issues->append(issue);
256 
257  gvFreeContext(context);
258  return;
259  }
260  agattr(graph, AGRAPH, (char *)"rankdir", (char *)"LR");
261  agattr(graph, AGRAPH, (char *)"ranksep", (char *)"0.75");
262  agattr(graph, AGNODE, (char *)"fontsize", (char *)"18");
263  agattr(graph, AGNODE, (char *)"shape", (char *)"Mrecord");
264  if (gvLayout(context, graph, "dot")) // without this, port names are NULL
265  {
266  VuoCompilerIssue issue(VuoCompilerIssue::Error, "parsing composition", "",
267  "Graphviz couldn't lay out the composition", VuoCompilerGraphvizParser_lastError);
268  issues->append(issue);
269  agclose(graph);
270  gvFreeContext(context);
271  return;
272  }
273 
274  try
275  {
276  makeDummyNodeClasses();
277  parsePublishedPortTypes();
278  }
279  catch (VuoCompilerException &e)
280  {
281  issues->append(e.getIssues());
282  gvFreeLayout(context, graph);
283  agclose(graph);
284  gvFreeContext(context);
285  return;
286  }
287 
288  if (compiler)
289  {
290  makeNodeClasses();
291  makeNodes();
292  makeCables();
293  makeComments();
294  makePublishedPorts();
295  setInputPortConstantValues();
296  setPublishedPortDetails();
297  setTriggerPortEventThrottling();
298  setManuallyFirableInputPort();
299  saveNodeDeclarations(compositionAsString);
300  metadata = new VuoCompositionMetadata(compositionAsString);
301  }
302 
303  gvFreeLayout(context, graph);
304  agclose(graph);
305  gvFreeContext(context);
306  });
307 
308  if (! issues->isEmpty())
309  throw VuoCompilerException(issues, true);
310 }
311 
317 void VuoCompilerGraphvizParser::makeDummyNodeClasses(void)
318 {
319  map<string, bool> nodeClassNamesSeen;
320  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
321  {
322  char *nodeClassNameCstr = agget(n, (char *)"type");
323  if (! nodeClassNameCstr)
324  {
325  VuoCompilerIssue issue(VuoCompilerIssue::Error, "parsing composition", "",
326  "Vuo couldn't parse the composition", "A node lacks a 'type' attribute indicating the node class name.");
327  throw VuoCompilerException(issue);
328  }
329 
330  string nodeClassName = nodeClassNameCstr;
331 
332  if (nodeClassName == VuoComment::commentTypeName)
333  continue;
334 
335  if (nodeClassNamesSeen[nodeClassName])
336  {
337  if (nodeClassName == VuoNodeClass::publishedInputNodeClassName)
338  VUserLog("Error: Composition has more than one node of class '%s'.", VuoNodeClass::publishedInputNodeClassName.c_str());
339  else if (nodeClassName == VuoNodeClass::publishedOutputNodeClassName)
340  VUserLog("Error: Composition has more than one node of class '%s'.", VuoNodeClass::publishedOutputNodeClassName.c_str());
341  else
342  continue; // node class already created
343  }
344 
345  // Add ports listed in the node instance's label.
346  vector<string> inputPortClassNames;
347  vector<string> outputPortClassNames;
348  {
349  field_t *nodeInfo = (field_t *)ND_shape_info(n);
350  int numNodeInfoFields = nodeInfo->n_flds;
351  for (int i = 0; i < numNodeInfoFields; i++)
352  {
353  field_t *nodeInfoField = nodeInfo->fld[i];
354 
355  // Skip the node instance's title.
356  if (! nodeInfoField->id)
357  continue;
358 
359  // The port text should end with '\l' or '\r', indicating whether the port is on left or right side of the node.
360  char * lr = strchr(nodeInfoField->lp->text, '\\');
361  if (! lr)
362  continue;
363 
364  if (lr[1] == 'l') // input port
365  {
366  // Skip the refresh port, which is added by VuoNodeClass's constructor below.
367  if (strcmp(nodeInfoField->id,"refresh") == 0)
368  continue;
369 
370  if (find(inputPortClassNames.begin(), inputPortClassNames.end(), nodeInfoField->id) == inputPortClassNames.end())
371  inputPortClassNames.push_back(nodeInfoField->id);
372  }
373  else // output port
374  {
375  if (find(outputPortClassNames.begin(), outputPortClassNames.end(), nodeInfoField->id) == outputPortClassNames.end())
376  outputPortClassNames.push_back(nodeInfoField->id);
377  }
378  }
379  }
380 
381  VuoNodeClass * nodeClass = new VuoNodeClass(nodeClassName, inputPortClassNames, outputPortClassNames);
382  dummyNodeClassForName[nodeClassName] = nodeClass;
383  }
384 }
385 
389 void VuoCompilerGraphvizParser::makeNodeClasses(void)
390 {
391  for (map<string, VuoNodeClass *>::iterator i = dummyNodeClassForName.begin(), e = dummyNodeClassForName.end(); i != e; ++i)
392  {
393  string dummyNodeClassName = i->first;
394  VuoNodeClass *dummyNodeClass = i->second;
395 
396  VuoCompilerNodeClass *nodeClass = compilerNodeClasses[dummyNodeClassName];
397 
398  if (nodeClass)
399  {
400  nodeClassForName[dummyNodeClassName] = nodeClass->getBase();
401 
402  checkPortClasses(dummyNodeClassName, dummyNodeClass->getInputPortClasses(), nodeClass->getBase()->getInputPortClasses());
403  checkPortClasses(dummyNodeClassName, dummyNodeClass->getOutputPortClasses(), nodeClass->getBase()->getOutputPortClasses());
404  }
405  else
406  {
407  nodeClassForName[dummyNodeClassName] = dummyNodeClass;
408 
409 #if VUO_PRO
410  if (compilerProNodeClassNames.find(dummyNodeClassName) != compilerProNodeClassNames.end())
411  dummyNodeClass->setPro(true);
412 #endif
413  }
414  }
415 }
416 
420 void VuoCompilerGraphvizParser::makeNodes(void)
421 {
422  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
423  {
424  string nodeClassName = agget(n, (char *)"type");
425  if (nodeClassName == VuoComment::commentTypeName)
426  continue;
427 
428  double x,y;
429  char * pos = agget(n, (char *)"pos");
430  if (!(pos && sscanf(pos,"%20lf,%20lf",&x,&y) == 2))
431  {
432  // If the 'pos' attribute is unspecified or invalid, use the post-dot-layout coordinates.
433  x = ND_coord(n).x;
434  // Flip origin from bottom-left to top-left, to match Qt's origin.
435  y = GD_bb(graph).UR.y - ND_coord(n).y;
436  }
437 
438  string nodeName(agnameof(n));
439 
440  string nodeTitle;
441  field_t *nodeInfo = (field_t *)ND_shape_info(n);
442  int numNodeInfoFields = nodeInfo->n_flds;
443  for (int i = 0; i < numNodeInfoFields; i++)
444  {
445  field_t *nodeInfoField = nodeInfo->fld[i];
446  if (! nodeInfoField->id) // title, as opposed to a port
447  nodeTitle = VuoStringUtilities::transcodeFromGraphvizIdentifier(nodeInfoField->lp->text);
448  }
449 
450  VuoNodeClass *nodeClass = nodeClassForName[nodeClassName];
451 
452  if (nodeForName[nodeName])
453  {
454  VUserLog("Error: More than one node with name '%s'.", nodeName.c_str());
455  return;
456  }
457 
458  VuoNode *node;
459  if (nodeClass->hasCompiler())
460  node = compiler->createNode(nodeClass->getCompiler(), nodeTitle, x, y);
461  else
462  {
463  node = nodeClass->newNode(nodeTitle, x, y);
464 
465  // Also use this node's display title as the default title for the
466  // uninstalled node class as a whole, for use in node panel documentation.
467  // @todo https://b33p.net/kosada/node/9495 : Display the node-specific title
468  // in the panel, not an educated guess at the node class default title.
469  nodeClass->setDefaultTitle(nodeTitle);
470  }
471 
472  char * nodeTintColor = agget(n, (char *)"fillcolor");
473  if (nodeTintColor)
474  node->setTintColor(VuoNode::getTintWithGraphvizName(nodeTintColor));
475 
476  char *nodeCollapsed = agget(n, (char *)"collapsed");
477  if (nodeCollapsed && strcmp(nodeCollapsed, "true") == 0)
478  node->setCollapsed(true);
479 
480  nodeForName[nodeName] = node;
481  if (nodeClass->hasCompiler())
482  node->getCompiler()->setGraphvizIdentifier(nodeName);
483 
485  publishedInputNode = node;
487  publishedOutputNode = node;
488  else
489  orderedNodes.push_back(node);
490  }
491 }
492 
497 void VuoCompilerGraphvizParser::makeCables(void)
498 {
499  map<string, bool> nodeNamesSeen;
500  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
501  {
502  for (Agedge_t *e = agfstedge(graph, n); e; e = agnxtedge(graph, e, n))
503  {
504  string fromNodeName(agnameof(agtail(e)));
505  string toNodeName(agnameof(aghead(e)));
506  string fromPortName(ED_tail_port(e).name);
507  string toPortName(ED_head_port(e).name);
508 
509  if (nodeNamesSeen[fromNodeName] || nodeNamesSeen[toNodeName])
510  continue; // edge N1 -> N2 appears in N1's edge list and in N2's edge list
511 
512  VuoNode *fromNode = nodeForName[fromNodeName];
513  VuoNode *toNode = nodeForName[toNodeName];
514 
515  VuoPort *toPort = toNode->getInputPortWithName(toPortName);
516  VuoPort *fromPort = fromNode->getOutputPortWithName(fromPortName);
517  if (! toPort || ! fromPort)
518  continue;
519 
520  VuoCompilerNode *fromCompilerNode = NULL;
521  VuoCompilerPort *fromCompilerPort = NULL;
522  if (fromNode->hasCompiler())
523  {
524  fromCompilerNode = fromNode->getCompiler();
525  fromCompilerPort = static_cast<VuoCompilerPort *>(fromPort->getCompiler());
526  }
527 
528  VuoCompilerNode *toCompilerNode = NULL;
529  VuoCompilerPort *toCompilerPort = NULL;
530  if (toNode->hasCompiler())
531  {
532  toCompilerNode = toNode->getCompiler();
533  toCompilerPort = static_cast<VuoCompilerPort *>(toPort->getCompiler());
534  }
535 
536  VuoCompilerCable *cable = new VuoCompilerCable(fromCompilerNode, fromCompilerPort, toCompilerNode, toCompilerPort);
537  if (! fromCompilerNode && fromNode != publishedInputNode)
538  cable->getBase()->setFrom(fromNode, fromPort);
539  if (! toCompilerNode && toNode != publishedOutputNode)
540  cable->getBase()->setTo(toNode, toPort);
541 
542  if (fromNode == publishedInputNode || toNode == publishedOutputNode)
543  {
544  publishedCablesInProgress[orderedCables.size()] = make_pair(cable, make_pair(fromPortName, toPortName));
545  orderedCables.push_back(NULL);
546  }
547  else
548  {
549  orderedCables.push_back(cable->getBase());
550  }
551 
552  char *eventOnlyAttribute = agget(e, (char *)"event");
553  if (eventOnlyAttribute && strcmp(eventOnlyAttribute, "true") == 0)
554  cable->setAlwaysEventOnly(true);
555 
556  char *hiddenAttribute = agget(e, (char *)"style");
557  if (hiddenAttribute && strcmp(hiddenAttribute, "invis") == 0)
558  cable->setHidden(true);
559  }
560 
561  nodeNamesSeen[agnameof(n)] = true;
562  }
563 }
564 
568 void VuoCompilerGraphvizParser::makeComments(void)
569 {
570  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
571  {
572  string typeName = agget(n, (char *)"type");
573  if (typeName != VuoComment::commentTypeName)
574  continue;
575 
576  double x,y;
577  char * pos = agget(n, (char *)"pos");
578  if (!(pos && sscanf(pos,"%20lf,%20lf",&x,&y) == 2))
579  {
580  // If the 'pos' attribute is unspecified or invalid, use the post-dot-layout coordinates.
581  x = ND_coord(n).x;
582  // Flip origin from bottom-left to top-left, to match Qt's origin.
583  y = GD_bb(graph).UR.y - ND_coord(n).y;
584  }
585 
586  double widthVal, heightVal;
587  char *width = agget(n, (char *)"width");
588  if (!(width && sscanf(width,"%20lf",&widthVal) == 1))
589  width = 0;
590 
591  char *height = agget(n, (char *)"height");
592  if (!(height && sscanf(height,"%20lf",&heightVal) == 1))
593  height = 0;
594 
595  string commentName(agnameof(n));
596 
597  string commentContent;
598  field_t *commentInfo = (field_t *)ND_shape_info(n);
599  int numCommentInfoFields = commentInfo->n_flds;
600  for (int i = 0; i < numCommentInfoFields; i++)
601  {
602  field_t *commentInfoField = commentInfo->fld[i];
603  if (! commentInfoField->id) // text content
604  commentContent = VuoStringUtilities::transcodeFromGraphvizIdentifier(commentInfoField->lp->text);
605  }
606 
607  if (commentForName[commentName])
608  {
609  VUserLog("Error: More than one comment with name '%s'.", commentName.c_str());
610  return;
611  }
612 
613  VuoComment *comment = (new VuoCompilerComment(((widthVal > 0 && heightVal > 0)?
614  new VuoComment(commentContent, x, y, widthVal, heightVal) :
615  new VuoComment(commentContent, x, y))))->getBase();
616  char * commentTintColor = agget(n, (char *)"fillcolor");
617  if (commentTintColor)
618  comment->setTintColor(VuoNode::getTintWithGraphvizName(commentTintColor));
619 
620  commentForName[commentName] = comment;
621 
622  if (comment->hasCompiler())
623  comment->getCompiler()->setGraphvizIdentifier(commentName);
624 
625  orderedComments.push_back(comment);
626  }
627 }
628 
632 void VuoCompilerGraphvizParser::parsePublishedPortTypes(void)
633 {
634  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
635  {
636  string nodeClassName = agget(n, (char *)"type");
638  continue;
639 
640  bool isPublishedInputNode = (nodeClassName == VuoNodeClass::publishedInputNodeClassName);
641  VuoNodeClass *nodeClass = dummyNodeClassForName[nodeClassName];
642  vector<VuoPortClass *> publishedPorts = isPublishedInputNode ?
643  nodeClass->getOutputPortClasses() :
644  nodeClass->getInputPortClasses();
645 
646  for (VuoPortClass *port : publishedPorts)
647  {
648  string portName = port->getName();
649  string typeName;
650  parseAttributeOfPort(n, portName, "type", typeName);
651 
652  if (! typeName.empty())
653  {
654  if (isPublishedInputNode)
655  typeForPublishedInputPort[portName] = typeName;
656  else
657  typeForPublishedOutputPort[portName] = typeName;
658  }
659  }
660  }
661 }
662 
668 void VuoCompilerGraphvizParser::makePublishedPorts(void)
669 {
670  map<string, set<VuoCompilerPort *> > connectedPortsForPublishedInputPort;
671  map<string, set<VuoCompilerPort *> > connectedPortsForPublishedOutputPort;
672  for (map< size_t, pair< VuoCompilerCable *, pair<string, string> > >::iterator i = publishedCablesInProgress.begin(); i != publishedCablesInProgress.end(); ++i)
673  {
674  VuoCompilerCable *cable = i->second.first;
675  string fromPortName = i->second.second.first;
676  string toPortName = i->second.second.second;
677 
678  if (cable->getBase()->getFromNode() == NULL && cable->getBase()->getToPort() != NULL && cable->getBase()->getToPort()->hasCompiler())
679  {
680  VuoCompilerPort *connectedPort = static_cast<VuoCompilerPort *>(cable->getBase()->getToPort()->getCompiler());
681  connectedPortsForPublishedInputPort[fromPortName].insert(connectedPort);
682  }
683  else if (cable->getBase()->getToNode() == NULL && cable->getBase()->getFromPort() != NULL && cable->getBase()->getFromPort()->hasCompiler())
684  {
685  VuoCompilerPort *connectedPort = static_cast<VuoCompilerPort *>(cable->getBase()->getFromPort()->getCompiler());
686  connectedPortsForPublishedOutputPort[toPortName].insert(connectedPort);
687  }
689  }
690 
691  auto inferPublishedPortTypes = [&](VuoNode *publishedNode)
692  {
693  if (! publishedNode)
694  return;
695 
696  bool isPublishedInputNode = (publishedNode == publishedInputNode);
697 
698  vector<VuoPortClass *> publishedPorts = isPublishedInputNode ?
699  publishedNode->getNodeClass()->getOutputPortClasses() :
700  publishedNode->getNodeClass()->getInputPortClasses();
701 
702  for (VuoPortClass *port : publishedPorts)
703  {
704  string portName = port->getName();
705  string typeName = isPublishedInputNode ?
706  typeForPublishedInputPort[portName] :
707  typeForPublishedOutputPort[portName];
708 
709  if (typeName.empty())
710  {
711  set<VuoCompilerPort *> connectedPorts = isPublishedInputNode ?
712  connectedPortsForPublishedInputPort[portName] :
713  connectedPortsForPublishedOutputPort[portName];
714  VuoType *type = inferTypeForPublishedPort(portName, connectedPorts);
715  if (type)
716  {
717  typeName = type->getModuleKey();
718 
719  if (isPublishedInputNode)
720  typeForPublishedInputPort[portName] = typeName;
721  else
722  typeForPublishedOutputPort[portName] = typeName;
723  }
724  }
725  }
726  };
727  inferPublishedPortTypes(publishedInputNode);
728  inferPublishedPortTypes(publishedOutputNode);
729 
730  map<string, VuoPort *> publishedInputPortForName;
731  map<string, VuoPort *> publishedOutputPortForName;
732  for (int i = 0; i < 2; ++i)
733  {
734  if ((i == 0 && ! publishedInputNode) || (i == 1 && ! publishedOutputNode))
735  continue;
736 
737  vector<VuoPort *> basePorts;
738  int startIndex;
739  if (i == 0)
740  {
741  basePorts = publishedInputNode->getOutputPorts();
743  }
744  else
745  {
746  basePorts = publishedOutputNode->getInputPorts();
748  }
749 
750  for (int j = startIndex; j < basePorts.size(); ++j)
751  {
752  string portName = basePorts[j]->getClass()->getName();
753  string typeName = (i == 0 ? typeForPublishedInputPort[portName] : typeForPublishedOutputPort[portName]);
754  VuoCompilerType *type = (typeName.empty() || typeName == "event" ? nullptr : compilerTypes[typeName]);
756  VuoCompilerPublishedPortClass *portClass = new VuoCompilerPublishedPortClass(portName, eventOrData);
757  if (type)
758  portClass->setDataVuoType(type->getBase());
759 
760  VuoCompilerPublishedPort *publishedPort = static_cast<VuoCompilerPublishedPort *>( portClass->newPort() );
761  if (i == 0)
762  {
763  publishedInputPortForName[portName] = publishedPort->getBase();
764  publishedInputPorts.push_back( static_cast<VuoPublishedPort *>(publishedPort->getBase()) );
765  }
766  else
767  {
768  publishedOutputPortForName[portName] = publishedPort->getBase();
769  publishedOutputPorts.push_back( static_cast<VuoPublishedPort *>(publishedPort->getBase()) );
770  }
771  }
772  }
773 
774  for (map< size_t, pair< VuoCompilerCable *, pair<string, string> > >::iterator i = publishedCablesInProgress.begin(); i != publishedCablesInProgress.end(); ++i)
775  {
776  size_t index = i->first;
777  VuoCompilerCable *cable = i->second.first;
778  string fromPortName = i->second.second.first;
779  string toPortName = i->second.second.second;
780 
781  if (cable->getBase()->getFromNode() == NULL)
782  cable->getBase()->setFrom(publishedInputNode, publishedInputPortForName[fromPortName]);
783  if (cable->getBase()->getToNode() == NULL)
784  cable->getBase()->setTo(publishedOutputNode, publishedOutputPortForName[toPortName]);
785 
786  orderedCables[index] = cable->getBase();
787  }
788 }
789 
793 void VuoCompilerGraphvizParser::setInputPortConstantValues(void)
794 {
795  // Find the constant value of each published input port.
796  map<string, string> constantForPublishedInputPort;
797  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
798  {
799  string nodeClassName = agget(n, (char *)"type");
800  if (nodeClassName == VuoComment::commentTypeName)
801  continue;
802 
803  if (nodeForName[agnameof(n)] == publishedInputNode)
804  constantForPublishedInputPort = parsePortConstantValues(n);
805  }
806 
807  // Set the constant value of each published input port.
808  for (vector<VuoPublishedPort *>::iterator i = publishedInputPorts.begin(); i != publishedInputPorts.end(); ++i)
809  {
810  VuoPublishedPort *publishedInputPort = *i;
811 
812  map<string, string>::iterator constantIter = constantForPublishedInputPort.find( publishedInputPort->getClass()->getName() );
813  if (constantIter != constantForPublishedInputPort.end())
814  static_cast<VuoCompilerPublishedPort *>( publishedInputPort->getCompiler() )->setInitialValue( constantIter->second );
815  }
816 
817  // Find and set the constant value of each internal input port.
818  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
819  {
820  string nodeClassName = agget(n, (char *)"type");
821  if (nodeClassName == VuoComment::commentTypeName)
822  continue;
823 
824  VuoNode *node = nodeForName[agnameof(n)];
825 
826  map<string, string> constantForInputPort = parsePortConstantValues(n);
827 
828  vector<VuoPort *> inputPorts = node->getInputPorts();
829  for (vector<VuoPort *>::iterator i = inputPorts.begin(); i != inputPorts.end(); ++i)
830  {
831  VuoPort *inputPort = *i;
832 
833  VuoPort *publishedInputPort = NULL;
834  vector<VuoCable *> connectedCables = inputPort->getConnectedCables();
835  for (vector<VuoCable *>::iterator j = connectedCables.begin(); j != connectedCables.end(); ++j)
836  {
837  VuoCable *cable = *j;
838 
839  if (cable->getCompiler()->carriesData())
840  {
841  if (cable->getFromNode() == publishedInputNode)
842  publishedInputPort = cable->getFromPort();
843  break;
844  }
845  }
846 
847  bool hasConstant = false;
848  string constant;
849 
850  if (publishedInputPort)
851  {
852  // If the input port has an incoming data cable from a published port, use the published port's constant or default value.
853  constant = constantForPublishedInputPort[ publishedInputPort->getClass()->getName() ];
854  hasConstant = true;
855  }
856  else
857  {
858  // Otherwise, use the internal input port's constant value.
859  map<string, string>::iterator constantIter = constantForInputPort.find(inputPort->getClass()->getName());
860  hasConstant = (constantIter != constantForInputPort.end());
861  if (hasConstant)
862  constant = constantIter->second;
863  }
864 
865  if (hasConstant)
866  {
867  if (node->hasCompiler())
868  {
869  VuoCompilerInputEventPort *inputEventPort = dynamic_cast<VuoCompilerInputEventPort *>(inputPort->getCompiler());
870  VuoCompilerInputData *data = inputEventPort->getData();
871  if (data)
872  data->setInitialValue(constant);
873  }
874  else
875  inputPort->setRawInitialValue(constant);
876  }
877  }
878  }
879 }
880 
884 void VuoCompilerGraphvizParser::setPublishedPortDetails(void)
885 {
886  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
887  {
888  string nodeClassName = agget(n, (char *)"type");
889 
890  if (nodeClassName == VuoNodeClass::publishedInputNodeClassName)
891  {
892  vector<string> detailKeys;
893  detailKeys.push_back("suggestedMin");
894  detailKeys.push_back("suggestedMax");
895  detailKeys.push_back("suggestedStep");
896 
897  for (vector<VuoPublishedPort *>::iterator i = publishedInputPorts.begin(); i != publishedInputPorts.end(); ++i)
898  {
899  VuoPublishedPort *publishedPort = *i;
900 
901  for (vector<string>::iterator j = detailKeys.begin(); j != detailKeys.end(); ++j)
902  {
903  string detailKey = *j;
904  string detailValue;
905  bool foundAttribute = parseAttributeOfPort(n, publishedPort->getClass()->getName(), detailKey, detailValue);
906  if (foundAttribute)
907  {
908  VuoCompilerPublishedPortClass *portClass = static_cast<VuoCompilerPublishedPortClass *>(publishedPort->getClass()->getCompiler());
909  portClass->setDetail(detailKey, detailValue);
910  }
911  }
912  }
913  }
914  }
915 }
916 
920 void VuoCompilerGraphvizParser::setTriggerPortEventThrottling(void)
921 {
922  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
923  {
924  string nodeClassName = agget(n, (char *)"type");
925  if (nodeClassName == VuoComment::commentTypeName)
926  continue;
927 
928  VuoNode *node = nodeForName[agnameof(n)];
929 
930  vector<VuoPort *> outputPorts = node->getOutputPorts();
931  for (vector<VuoPort *>::iterator i = outputPorts.begin(); i != outputPorts.end(); ++i)
932  {
933  VuoPort *port = *i;
935  {
936  string eventThrottlingStr;
937  parseAttributeOfPort(n, port->getClass()->getName(), "eventThrottling", eventThrottlingStr);
938  enum VuoPortClass::EventThrottling eventThrottling;
939  if (eventThrottlingStr == "drop")
940  eventThrottling = VuoPortClass::EventThrottling_Drop;
941  else
942  // If composition was created before event dropping was implemented, default to
943  // event enqueuing for backward compatibility (preserving the original behavior).
944  eventThrottling = VuoPortClass::EventThrottling_Enqueue;
945  port->setEventThrottling(eventThrottling);
946  }
947  }
948  }
949 }
950 
954 void VuoCompilerGraphvizParser::setManuallyFirableInputPort(void)
955 {
956  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
957  {
958  string nodeClassName = agget(n, (char *)"type");
959  if (nodeClassName == VuoComment::commentTypeName)
960  continue;
961 
962  VuoNode *node = nodeForName[agnameof(n)];
963 
964  for (VuoPort *port : node->getInputPorts())
965  {
966  string unused;
967  bool hasAttribute = parseAttributeOfPort(n, port->getClass()->getName(), "manuallyFirable", unused);
968  if (hasAttribute)
969  {
970  manuallyFirableInputNode = node;
971  manuallyFirableInputPort = port;
972  return;
973  }
974  }
975  }
976 }
977 
983 map<string, string> VuoCompilerGraphvizParser::parsePortConstantValues(Agnode_t *n)
984 {
985  map<string, string> constantForInputPort;
986 
987  field_t *nodeInfo = (field_t *)ND_shape_info(n);
988  int numNodeInfoFields = nodeInfo->n_flds;
989  for (int i = 0; i < numNodeInfoFields; i++)
990  {
991  field_t *nodeInfoField = nodeInfo->fld[i];
992  char *inputPortName = nodeInfoField->id;
993  if (! inputPortName)
994  continue;
995 
996  string constantValue;
997  if (parseAttributeOfPort(n, inputPortName, "", constantValue))
998  constantForInputPort[inputPortName] = constantValue;
999  }
1000 
1001  return constantForInputPort;
1002 }
1003 
1009 bool VuoCompilerGraphvizParser::parseAttributeOfPort(Agnode_t *n, string portName, string suffix, string &attributeValue)
1010 {
1011  ostringstream oss;
1012  oss << "_" << portName;
1013  if (! suffix.empty())
1014  oss << "_" << suffix;
1015  char *attributeName = strdup(oss.str().c_str());
1016 
1017  char *rawAttributeValue = agget(n, attributeName);
1018  free(attributeName);
1019 
1020  // The Graphviz parser may return a constant value of the empty string if a constant value was defined
1021  // for another identically named port within the same composition, even if it wasn't defined for this port.
1022  // Any constant value that has been customized within the Vuo Editor should be non-empty anyway.
1023  // Therefore, treat constants consisting of the empty string as if they were absent so that they
1024  // don't override node-class-specific defaults.
1025  if (rawAttributeValue && strcmp(rawAttributeValue, ""))
1026  {
1027  attributeValue = VuoStringUtilities::transcodeFromGraphvizIdentifier(rawAttributeValue);
1028  return true;
1029  }
1030 
1031  return false;
1032 }
1033 
1037 void VuoCompilerGraphvizParser::checkPortClasses(string nodeClassName, vector<VuoPortClass *> dummy, vector<VuoPortClass *> actual)
1038 {
1039  for (vector<VuoPortClass *>::iterator i = dummy.begin(); i != dummy.end(); ++i)
1040  {
1041  string dummyName = (*i)->getName();
1042 
1043  if (dummyName == "refresh")
1044  continue;
1045 
1046  bool found = false;
1047  for (vector<VuoPortClass *>::iterator j = actual.begin(); j != actual.end(); ++j)
1048  {
1049  if ((*j)->getName() == dummyName)
1050  {
1051  found = true;
1052  break;
1053  }
1054  }
1055  if (! found)
1056  {
1057  VUserLog("Error: Couldn't find node %s's port '%s'.", nodeClassName.c_str(), dummyName.c_str());
1058  return;
1059  }
1060  }
1061 }
1062 
1066 void VuoCompilerGraphvizParser::saveNodeDeclarations(const string &compositionAsString)
1067 {
1068  size_t nodesRemaining = nodeForName.size();
1069 
1070  vector<string> lines = VuoStringUtilities::split(compositionAsString, '\n');
1071  for (vector<string>::iterator i = lines.begin(); i != lines.end() && nodesRemaining > 0; ++i)
1072  {
1073  string line = *i;
1074  string identifier;
1075  for (int j = 0; j < line.length() && VuoStringUtilities::isValidCharInIdentifier(line[j]); ++j)
1076  identifier += line[j];
1077 
1078  map<string, VuoNode *>::iterator nodeIter = nodeForName.find(identifier);
1079  if (nodeIter != nodeForName.end())
1080  {
1081  VuoNode *node = nodeIter->second;
1082  node->setRawGraphvizDeclaration(line);
1083  --nodesRemaining;
1084  }
1085  }
1086 }
1087 
1093 {
1094  return orderedNodes;
1095 }
1096 
1102 {
1103  return orderedCables;
1104 }
1105 
1109 vector<VuoComment *> VuoCompilerGraphvizParser::getComments(void)
1110 {
1111  return orderedComments;
1112 }
1113 
1114 
1119 {
1120  return publishedInputPorts;
1121 }
1122 
1127 {
1128  return publishedOutputPorts;
1129 }
1130 
1135 {
1136  return manuallyFirableInputNode;
1137 }
1138 
1143 {
1144  return manuallyFirableInputPort;
1145 }
1146 
1151 {
1152  return metadata;
1153 }
1154 
1159 VuoType * VuoCompilerGraphvizParser::inferTypeForPublishedPort(string name, const set<VuoCompilerPort *> &connectedPorts)
1160 {
1161  if (connectedPorts.empty() || name == "refresh")
1162  return NULL;
1163 
1164  VuoCompilerPort *connectedPort = *connectedPorts.begin();
1165  VuoCompilerPortClass *connectedPortClass = static_cast<VuoCompilerPortClass *>(connectedPort->getBase()->getClass()->getCompiler());
1166  return connectedPortClass->getDataVuoType();
1167 }