Vuo  2.0.0
VuoCompilerGraphvizParser.cc
Go to the documentation of this file.
1 
10 #include <sstream>
11 #include <stdlib.h>
12 #include <graphviz/gvc.h>
13 
14 #include "VuoCable.hh"
15 #include "VuoCompiler.hh"
16 #include "VuoCompilerCable.hh"
17 #include "VuoCompilerComment.hh"
18 #include "VuoCompilerException.hh"
21 #include "VuoCompilerIssue.hh"
22 #include "VuoCompilerNode.hh"
23 #include "VuoCompilerNodeClass.hh"
26 #include "VuoCompilerType.hh"
27 #include "VuoComposition.hh"
29 #include "VuoComment.hh"
30 #include "VuoException.hh"
31 #include "VuoNode.hh"
32 #include "VuoNodeClass.hh"
33 #include "VuoPublishedPort.hh"
34 #include "VuoStringUtilities.hh"
35 #include "VuoType.hh"
36 
37 
38 extern gvplugin_library_t gvplugin_dot_layout_LTX_library;
39 extern gvplugin_library_t gvplugin_core_LTX_library;
40 
42 lt_symlist_t lt_preloaded_symbols[] =
43 {
44  { "gvplugin_dot_layout_LTX_library", &gvplugin_dot_layout_LTX_library},
45  { "gvplugin_core_LTX_library", &gvplugin_core_LTX_library},
46  { 0, 0}
47 };
48 
49 dispatch_queue_t VuoCompilerGraphvizParser::graphvizQueue = dispatch_queue_create("org.vuo.compiler.graphviz", NULL);
50 
57 {
58  try
59  {
60  string composition = VuoFileUtilities::readFileToString(path);
61  return newParserFromCompositionString(composition, compiler);
62  }
63  catch (VuoException &e)
64  {
65  VuoCompilerIssue issue(VuoCompilerIssue::Error, "opening composition", path,
66  "", e.what());
67  throw VuoCompilerException(issue);
68  }
69  catch (VuoCompilerException &e)
70  {
71  e.getIssues()->setFilePathIfEmpty(path);
72  throw;
73  }
74 }
75 
82 {
83  set<string> nodeClassNames = getNodeClassNamesFromCompositionString(composition);
84 
85  // Get off of graphvizQueue when loading node classes to avoid deadlock when loading subcompositions.
86  for (set<string>::iterator i = nodeClassNames.begin(); i != nodeClassNames.end(); ++i)
87  compiler->getNodeClass(*i);
88 
89  return new VuoCompilerGraphvizParser(composition, compiler, false);
90 }
91 
98 {
99  set<string> nodeClassNames;
100 
101  try
102  {
103  string composition = VuoFileUtilities::readFileToString(path);
104  nodeClassNames = getNodeClassNamesFromCompositionString(composition);
105  }
106  catch (VuoException &e)
107  {
108  VuoCompilerIssue issue(VuoCompilerIssue::Error, "opening composition", path,
109  "", e.what());
110  throw VuoCompilerException(issue);
111  }
112  catch (VuoCompilerException &e)
113  {
114  e.getIssues()->setFilePathIfEmpty(path);
115  throw;
116  }
117 
118  return nodeClassNames;
119 }
120 
127 {
128  VuoCompilerGraphvizParser parser(composition, NULL, true);
129 
130  set<string> nodeClassNames;
131  for (map<string, VuoNodeClass *>::iterator i = parser.dummyNodeClassForName.begin(); i != parser.dummyNodeClassForName.end(); ++i)
132  nodeClassNames.insert(i->first);
133 
134  return nodeClassNames;
135 }
136 
137 
139 
143 static int VuoCompilerGraphvizParser_error(char *message)
144 {
146  if (VuoCompilerGraphvizParser_lastError.find('\n') != string::npos)
148  return 0;
149 }
150 
158 VuoCompilerGraphvizParser::VuoCompilerGraphvizParser(const string &compositionAsString, VuoCompiler *compiler, bool nodeClassNamesOnly)
159 {
160  if (compositionAsString.empty())
161  throw VuoCompilerException(VuoCompilerIssue(VuoCompilerIssue::Error, "parsing composition string", "", "composition string is empty", ""));
162 
163  this->compiler = compiler;
164  publishedInputNode = nullptr;
165  publishedOutputNode = nullptr;
166  manuallyFirableInputNode = nullptr;
167  manuallyFirableInputPort = nullptr;
168  metadata = nullptr;
169 
170  VuoCompilerIssues *issues = new VuoCompilerIssues();
171  dispatch_sync(graphvizQueue, ^{
174 
175  // Use builtin Graphviz plugins, not demand-loaded plugins.
176  bool demandLoading = false;
177  GVC_t *context = gvContextPlugins(lt_preloaded_symbols, demandLoading);
178 
179  graph = agmemread((char *)compositionAsString.c_str());
180  if (!graph)
181  {
182  VuoCompilerIssue issue(VuoCompilerIssue::Error, "parsing composition", "",
183  "Graphviz couldn't parse the composition", VuoCompilerGraphvizParser_lastError);
184  issues->append(issue);
185  return;
186  }
187  agraphattr(graph, (char *)"rankdir", (char *)"LR");
188  agraphattr(graph, (char *)"ranksep", (char *)"0.75");
189  agnodeattr(graph, (char *)"fontsize", (char *)"18");
190  agnodeattr(graph, (char *)"shape", (char *)"Mrecord");
191  gvLayout(context, graph, "dot"); // without this, port names are NULL
192 
193  try
194  {
195  makeDummyNodeClasses();
196  }
197  catch (VuoCompilerException &e)
198  {
199  issues->append(e.getIssues());
200  return;
201  }
202 
203  if (! nodeClassNamesOnly)
204  {
205  makeNodeClasses();
206  makeNodes();
207  makeCables();
208  makeComments();
209  makePublishedPorts();
210  setInputPortConstantValues();
211  setPublishedPortDetails();
212  setTriggerPortEventThrottling();
213  setManuallyFirableInputPort();
214  saveNodeDeclarations(compositionAsString);
215  metadata = new VuoCompositionMetadata(compositionAsString);
216  }
217 
218  gvFreeLayout(context, graph);
219  agclose(graph);
220  gvFreeContext(context);
221  });
222 
223  if (! issues->isEmpty())
224  throw VuoCompilerException(issues, true);
225 }
226 
232 void VuoCompilerGraphvizParser::makeDummyNodeClasses(void)
233 {
234  map<string, bool> nodeClassNamesSeen;
235  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
236  {
237  char *nodeClassNameCstr = agget(n, (char *)"type");
238  if (! nodeClassNameCstr)
239  {
240  VuoCompilerIssue issue(VuoCompilerIssue::Error, "parsing composition", "",
241  "Vuo couldn't parse the composition", "A node lacks a 'type' attribute indicating the node class name.");
242  throw VuoCompilerException(issue);
243  }
244 
245  string nodeClassName = nodeClassNameCstr;
246 
247  if (nodeClassName == VuoComment::commentTypeName)
248  continue;
249 
250  if (nodeClassNamesSeen[nodeClassName])
251  {
252  if (nodeClassName == VuoNodeClass::publishedInputNodeClassName)
253  VUserLog("Error: Composition has more than one node of class '%s'.", VuoNodeClass::publishedInputNodeClassName.c_str());
254  else if (nodeClassName == VuoNodeClass::publishedOutputNodeClassName)
255  VUserLog("Error: Composition has more than one node of class '%s'.", VuoNodeClass::publishedOutputNodeClassName.c_str());
256  else
257  continue; // node class already created
258  }
259 
260  // Add ports listed in the node instance's label.
261  vector<string> inputPortClassNames;
262  vector<string> outputPortClassNames;
263  {
264  field_t *nodeInfo = (field_t *)ND_shape_info(n);
265  int numNodeInfoFields = nodeInfo->n_flds;
266  for (int i = 0; i < numNodeInfoFields; i++)
267  {
268  field_t *nodeInfoField = nodeInfo->fld[i];
269 
270  // Skip the node instance's title.
271  if (! nodeInfoField->id)
272  continue;
273 
274  // The port text should end with '\l' or '\r', indicating whether the port is on left or right side of the node.
275  char * lr = strchr(nodeInfoField->lp->text, '\\');
276  if (! lr)
277  continue;
278 
279  if (lr[1] == 'l') // input port
280  {
281  // Skip the refresh port, which is added by VuoNodeClass's constructor below.
282  if (strcmp(nodeInfoField->id,"refresh") == 0)
283  continue;
284 
285  if (find(inputPortClassNames.begin(), inputPortClassNames.end(), nodeInfoField->id) == inputPortClassNames.end())
286  inputPortClassNames.push_back(nodeInfoField->id);
287  }
288  else // output port
289  {
290  if (find(outputPortClassNames.begin(), outputPortClassNames.end(), nodeInfoField->id) == outputPortClassNames.end())
291  outputPortClassNames.push_back(nodeInfoField->id);
292  }
293  }
294  }
295 
296  VuoNodeClass * nodeClass = new VuoNodeClass(nodeClassName, inputPortClassNames, outputPortClassNames);
297  dummyNodeClassForName[nodeClassName] = nodeClass;
298  }
299 }
300 
304 void VuoCompilerGraphvizParser::makeNodeClasses(void)
305 {
306  for (map<string, VuoNodeClass *>::iterator i = dummyNodeClassForName.begin(), e = dummyNodeClassForName.end(); i != e; ++i)
307  {
308  string dummyNodeClassName = i->first;
309  VuoNodeClass *dummyNodeClass = i->second;
310 
311  VuoCompilerNodeClass *nodeClass = NULL;
312  if (compiler)
313  nodeClass = compiler->getNodeClass(dummyNodeClassName);
314 
315  if (nodeClass)
316  {
317  nodeClassForName[dummyNodeClassName] = nodeClass->getBase();
318 
319  checkPortClasses(dummyNodeClassName, dummyNodeClass->getInputPortClasses(), nodeClass->getBase()->getInputPortClasses());
320  checkPortClasses(dummyNodeClassName, dummyNodeClass->getOutputPortClasses(), nodeClass->getBase()->getOutputPortClasses());
321  }
322  else
323  {
324  nodeClassForName[dummyNodeClassName] = dummyNodeClass;
325 
326 #if VUO_PRO
327  dummyNodeClass->setPro(compiler->isProModule(dummyNodeClassName));
328 #endif
329  }
330  }
331 }
332 
336 void VuoCompilerGraphvizParser::makeNodes(void)
337 {
338  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
339  {
340  string nodeClassName = agget(n, (char *)"type");
341  if (nodeClassName == VuoComment::commentTypeName)
342  continue;
343 
344  double x,y;
345  char * pos = agget(n, (char *)"pos");
346  if (!(pos && sscanf(pos,"%20lf,%20lf",&x,&y) == 2))
347  {
348  // If the 'pos' attribute is unspecified or invalid, use the post-dot-layout coordinates.
349  x = ND_coord(n).x;
350  // Flip origin from bottom-left to top-left, to match Qt's origin.
351  y = GD_bb(graph).UR.y - ND_coord(n).y;
352  }
353 
354  string nodeName = n->name;
355 
356  string nodeTitle;
357  field_t *nodeInfo = (field_t *)ND_shape_info(n);
358  int numNodeInfoFields = nodeInfo->n_flds;
359  for (int i = 0; i < numNodeInfoFields; i++)
360  {
361  field_t *nodeInfoField = nodeInfo->fld[i];
362  if (! nodeInfoField->id) // title, as opposed to a port
363  nodeTitle = VuoStringUtilities::transcodeFromGraphvizIdentifier(nodeInfoField->lp->text);
364  }
365 
366  VuoNodeClass *nodeClass = nodeClassForName[nodeClassName];
367 
368  if (nodeForName[nodeName])
369  {
370  VUserLog("Error: More than one node with name '%s'.", nodeName.c_str());
371  return;
372  }
373 
374  VuoNode *node;
375  if (nodeClass->hasCompiler())
376  node = compiler->createNode(nodeClass->getCompiler(), nodeTitle, x, y);
377  else
378  {
379  node = nodeClass->newNode(nodeTitle, x, y);
380 
381  // Also use this node's display title as the default title for the
382  // uninstalled node class as a whole, for use in node panel documentation.
383  // @todo https://b33p.net/kosada/node/9495 : Display the node-specific title
384  // in the panel, not an educated guess at the node class default title.
385  nodeClass->setDefaultTitle(nodeTitle);
386  }
387 
388  char * nodeTintColor = agget(n, (char *)"fillcolor");
389  if (nodeTintColor)
390  node->setTintColor(VuoNode::getTintWithGraphvizName(nodeTintColor));
391 
392  char *nodeCollapsed = agget(n, (char *)"collapsed");
393  if (nodeCollapsed && strcmp(nodeCollapsed, "true") == 0)
394  node->setCollapsed(true);
395 
396  nodeForName[nodeName] = node;
397  if (nodeClass->hasCompiler())
398  node->getCompiler()->setGraphvizIdentifier(nodeName);
399 
401  publishedInputNode = node;
403  publishedOutputNode = node;
404  else
405  orderedNodes.push_back(node);
406  }
407 }
408 
413 void VuoCompilerGraphvizParser::makeCables(void)
414 {
415  map<string, bool> nodeNamesSeen;
416  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
417  {
418  for (Agedge_t *e = agfstedge(graph, n); e; e = agnxtedge(graph, e, n))
419  {
420  string fromNodeName = e->tail->name;
421  string toNodeName = e->head->name;
422  string fromPortName = e->u.tail_port.name;
423  string toPortName = e->u.head_port.name;
424 
425  if (nodeNamesSeen[fromNodeName] || nodeNamesSeen[toNodeName])
426  continue; // edge N1 -> N2 appears in N1's edge list and in N2's edge list
427 
428  VuoNode *fromNode = nodeForName[fromNodeName];
429  VuoNode *toNode = nodeForName[toNodeName];
430 
431  VuoPort *toPort = toNode->getInputPortWithName(toPortName);
432  VuoPort *fromPort = fromNode->getOutputPortWithName(fromPortName);
433  if (! toPort || ! fromPort)
434  continue;
435 
436  VuoCompilerNode *fromCompilerNode = NULL;
437  VuoCompilerPort *fromCompilerPort = NULL;
438  if (fromNode->hasCompiler())
439  {
440  fromCompilerNode = fromNode->getCompiler();
441  fromCompilerPort = static_cast<VuoCompilerPort *>(fromPort->getCompiler());
442  }
443 
444  VuoCompilerNode *toCompilerNode = NULL;
445  VuoCompilerPort *toCompilerPort = NULL;
446  if (toNode->hasCompiler())
447  {
448  toCompilerNode = toNode->getCompiler();
449  toCompilerPort = static_cast<VuoCompilerPort *>(toPort->getCompiler());
450  }
451 
452  VuoCompilerCable *cable = new VuoCompilerCable(fromCompilerNode, fromCompilerPort, toCompilerNode, toCompilerPort);
453  if (! fromCompilerNode && fromNode != publishedInputNode)
454  cable->getBase()->setFrom(fromNode, fromPort);
455  if (! toCompilerNode && toNode != publishedOutputNode)
456  cable->getBase()->setTo(toNode, toPort);
457 
458  if (fromNode == publishedInputNode || toNode == publishedOutputNode)
459  {
460  publishedCablesInProgress[orderedCables.size()] = make_pair(cable, make_pair(fromPortName, toPortName));
461  orderedCables.push_back(NULL);
462  }
463  else
464  {
465  orderedCables.push_back(cable->getBase());
466  }
467 
468  char *eventOnlyAttribute = agget(e, (char *)"event");
469  if (eventOnlyAttribute && strcmp(eventOnlyAttribute, "true") == 0)
470  cable->setAlwaysEventOnly(true);
471 
472  char *hiddenAttribute = agget(e, (char *)"style");
473  if (hiddenAttribute && strcmp(hiddenAttribute, "invis") == 0)
474  cable->setHidden(true);
475  }
476 
477  nodeNamesSeen[n->name] = true;
478  }
479 }
480 
484 void VuoCompilerGraphvizParser::makeComments(void)
485 {
486  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
487  {
488  string typeName = agget(n, (char *)"type");
489  if (typeName != VuoComment::commentTypeName)
490  continue;
491 
492  double x,y;
493  char * pos = agget(n, (char *)"pos");
494  if (!(pos && sscanf(pos,"%20lf,%20lf",&x,&y) == 2))
495  {
496  // If the 'pos' attribute is unspecified or invalid, use the post-dot-layout coordinates.
497  x = ND_coord(n).x;
498  // Flip origin from bottom-left to top-left, to match Qt's origin.
499  y = GD_bb(graph).UR.y - ND_coord(n).y;
500  }
501 
502  double widthVal, heightVal;
503  char *width = agget(n, (char *)"width");
504  if (!(width && sscanf(width,"%20lf",&widthVal) == 1))
505  width = 0;
506 
507  char *height = agget(n, (char *)"height");
508  if (!(height && sscanf(height,"%20lf",&heightVal) == 1))
509  height = 0;
510 
511  string commentName = n->name;
512 
513  string commentContent;
514  field_t *commentInfo = (field_t *)ND_shape_info(n);
515  int numCommentInfoFields = commentInfo->n_flds;
516  for (int i = 0; i < numCommentInfoFields; i++)
517  {
518  field_t *commentInfoField = commentInfo->fld[i];
519  if (! commentInfoField->id) // text content
520  commentContent = VuoStringUtilities::transcodeFromGraphvizIdentifier(commentInfoField->lp->text);
521  }
522 
523  if (commentForName[commentName])
524  {
525  VUserLog("Error: More than one comment with name '%s'.", commentName.c_str());
526  return;
527  }
528 
529  VuoComment *comment = (new VuoCompilerComment(((widthVal > 0 && heightVal > 0)?
530  new VuoComment(commentContent, x, y, widthVal, heightVal) :
531  new VuoComment(commentContent, x, y))))->getBase();
532  char * commentTintColor = agget(n, (char *)"fillcolor");
533  if (commentTintColor)
534  comment->setTintColor(VuoNode::getTintWithGraphvizName(commentTintColor));
535 
536  commentForName[commentName] = comment;
537 
538  if (comment->hasCompiler())
539  comment->getCompiler()->setGraphvizIdentifier(commentName);
540 
541  orderedComments.push_back(comment);
542  }
543 }
544 
545 
551 void VuoCompilerGraphvizParser::makePublishedPorts(void)
552 {
553  map<string, set<VuoCompilerPort *> > connectedPortsForPublishedInputPort;
554  map<string, set<VuoCompilerPort *> > connectedPortsForPublishedOutputPort;
555  for (map< size_t, pair< VuoCompilerCable *, pair<string, string> > >::iterator i = publishedCablesInProgress.begin(); i != publishedCablesInProgress.end(); ++i)
556  {
557  VuoCompilerCable *cable = i->second.first;
558  string fromPortName = i->second.second.first;
559  string toPortName = i->second.second.second;
560 
561  if (cable->getBase()->getFromNode() == NULL && cable->getBase()->getToPort() != NULL && cable->getBase()->getToPort()->hasCompiler())
562  {
563  VuoCompilerPort *connectedPort = static_cast<VuoCompilerPort *>(cable->getBase()->getToPort()->getCompiler());
564  connectedPortsForPublishedInputPort[fromPortName].insert(connectedPort);
565  }
566  else if (cable->getBase()->getToNode() == NULL && cable->getBase()->getFromPort() != NULL && cable->getBase()->getFromPort()->hasCompiler())
567  {
568  VuoCompilerPort *connectedPort = static_cast<VuoCompilerPort *>(cable->getBase()->getFromPort()->getCompiler());
569  connectedPortsForPublishedOutputPort[toPortName].insert(connectedPort);
570  }
572  }
573 
574  map<string, VuoType *> typeForPublishedInputPort;
575  map<string, VuoType *> typeForPublishedOutputPort;
576  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
577  {
578  string nodeClassName = agget(n, (char *)"type");
579  if (nodeClassName == VuoComment::commentTypeName)
580  continue;
581 
582  string nodeName = n->name;
583  VuoNode *node = nodeForName[nodeName];
584  if (node != publishedInputNode && node != publishedOutputNode)
585  continue;
586 
587  vector<VuoPort *> publishedPorts = (node == publishedInputNode ? node->getOutputPorts() : node->getInputPorts());
588  for (vector<VuoPort *>::iterator i = publishedPorts.begin(); i != publishedPorts.end(); ++i)
589  {
590  string portName = (*i)->getClass()->getName();
591  VuoType *portType = NULL;
592  string portTypeStr = "";
593  parseAttributeOfPort(n, portName, "type", portTypeStr);
594  if (portTypeStr.empty())
595  {
596  set<VuoCompilerPort *> connectedPorts = (node == publishedInputNode ?
597  connectedPortsForPublishedInputPort[portName] :
598  connectedPortsForPublishedOutputPort[portName]);
599  portType = inferTypeForPublishedPort(portName, connectedPorts);
600  }
601  else if (portTypeStr != "event")
602  {
603  VuoCompilerType *portCompilerType = compiler->getType(portTypeStr);
604  if (portCompilerType)
605  portType = portCompilerType->getBase();
606  }
607 
608  if (node == publishedInputNode)
609  typeForPublishedInputPort[portName] = portType;
610  else
611  typeForPublishedOutputPort[portName] = portType;
612  }
613  }
614 
615  map<string, VuoPort *> publishedInputPortForName;
616  map<string, VuoPort *> publishedOutputPortForName;
617  for (int i = 0; i < 2; ++i)
618  {
619  if ((i == 0 && ! publishedInputNode) || (i == 1 && ! publishedOutputNode))
620  continue;
621 
622  vector<VuoPort *> basePorts;
623  int startIndex;
624  if (i == 0)
625  {
626  basePorts = publishedInputNode->getOutputPorts();
628  }
629  else
630  {
631  basePorts = publishedOutputNode->getInputPorts();
633  }
634 
635  for (int j = startIndex; j < basePorts.size(); ++j)
636  {
637  string portName = basePorts[j]->getClass()->getName();
638  VuoType *vuoType = (i == 0 ? typeForPublishedInputPort[portName] : typeForPublishedOutputPort[portName]);
639  Type *llvmType = (vuoType == NULL ? NULL : vuoType->getCompiler()->getType());
641  VuoCompilerPublishedPortClass *portClass = new VuoCompilerPublishedPortClass(portName, eventOrData, llvmType);
642  portClass->setDataVuoType(vuoType);
643  VuoCompilerPublishedPort *publishedPort = static_cast<VuoCompilerPublishedPort *>( portClass->newPort() );
644  if (i == 0)
645  {
646  publishedInputPortForName[portName] = publishedPort->getBase();
647  publishedInputPorts.push_back( static_cast<VuoPublishedPort *>(publishedPort->getBase()) );
648  }
649  else
650  {
651  publishedOutputPortForName[portName] = publishedPort->getBase();
652  publishedOutputPorts.push_back( static_cast<VuoPublishedPort *>(publishedPort->getBase()) );
653  }
654  }
655  }
656 
657  for (map< size_t, pair< VuoCompilerCable *, pair<string, string> > >::iterator i = publishedCablesInProgress.begin(); i != publishedCablesInProgress.end(); ++i)
658  {
659  size_t index = i->first;
660  VuoCompilerCable *cable = i->second.first;
661  string fromPortName = i->second.second.first;
662  string toPortName = i->second.second.second;
663 
664  if (cable->getBase()->getFromNode() == NULL)
665  cable->getBase()->setFrom(publishedInputNode, publishedInputPortForName[fromPortName]);
666  if (cable->getBase()->getToNode() == NULL)
667  cable->getBase()->setTo(publishedOutputNode, publishedOutputPortForName[toPortName]);
668 
669  orderedCables[index] = cable->getBase();
670  }
671 }
672 
676 void VuoCompilerGraphvizParser::setInputPortConstantValues(void)
677 {
678  // Find the constant value of each published input port.
679  map<string, string> constantForPublishedInputPort;
680  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
681  {
682  string nodeClassName = agget(n, (char *)"type");
683  if (nodeClassName == VuoComment::commentTypeName)
684  continue;
685 
686  if (nodeForName[n->name] == publishedInputNode)
687  constantForPublishedInputPort = parsePortConstantValues(n);
688  }
689 
690  // Set the constant value of each published input port.
691  for (vector<VuoPublishedPort *>::iterator i = publishedInputPorts.begin(); i != publishedInputPorts.end(); ++i)
692  {
693  VuoPublishedPort *publishedInputPort = *i;
694 
695  map<string, string>::iterator constantIter = constantForPublishedInputPort.find( publishedInputPort->getClass()->getName() );
696  if (constantIter != constantForPublishedInputPort.end())
697  static_cast<VuoCompilerPublishedPort *>( publishedInputPort->getCompiler() )->setInitialValue( constantIter->second );
698  }
699 
700  // Find and set the constant value of each internal input port.
701  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
702  {
703  string nodeClassName = agget(n, (char *)"type");
704  if (nodeClassName == VuoComment::commentTypeName)
705  continue;
706 
707  VuoNode *node = nodeForName[n->name];
708 
709  map<string, string> constantForInputPort = parsePortConstantValues(n);
710 
711  vector<VuoPort *> inputPorts = node->getInputPorts();
712  for (vector<VuoPort *>::iterator i = inputPorts.begin(); i != inputPorts.end(); ++i)
713  {
714  VuoPort *inputPort = *i;
715 
716  VuoPort *publishedInputPort = NULL;
717  vector<VuoCable *> connectedCables = inputPort->getConnectedCables();
718  for (vector<VuoCable *>::iterator j = connectedCables.begin(); j != connectedCables.end(); ++j)
719  {
720  VuoCable *cable = *j;
721 
722  if (cable->getCompiler()->carriesData())
723  {
724  if (cable->getFromNode() == publishedInputNode)
725  publishedInputPort = cable->getFromPort();
726  break;
727  }
728  }
729 
730  bool hasConstant = false;
731  string constant;
732 
733  if (publishedInputPort)
734  {
735  // If the input port has an incoming data cable from a published port, use the published port's constant or default value.
736  constant = constantForPublishedInputPort[ publishedInputPort->getClass()->getName() ];
737  hasConstant = true;
738  }
739  else
740  {
741  // Otherwise, use the internal input port's constant value.
742  map<string, string>::iterator constantIter = constantForInputPort.find(inputPort->getClass()->getName());
743  hasConstant = (constantIter != constantForInputPort.end());
744  if (hasConstant)
745  constant = constantIter->second;
746  }
747 
748  if (hasConstant)
749  {
750  if (node->hasCompiler())
751  {
752  VuoCompilerInputEventPort *inputEventPort = dynamic_cast<VuoCompilerInputEventPort *>(inputPort->getCompiler());
753  VuoCompilerInputData *data = inputEventPort->getData();
754  if (data)
755  data->setInitialValue(constant);
756  }
757  else
758  inputPort->setRawInitialValue(constant);
759  }
760  }
761  }
762 }
763 
767 void VuoCompilerGraphvizParser::setPublishedPortDetails(void)
768 {
769  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
770  {
771  string nodeClassName = agget(n, (char *)"type");
772 
773  if (nodeClassName == VuoNodeClass::publishedInputNodeClassName)
774  {
775  vector<string> detailKeys;
776  detailKeys.push_back("suggestedMin");
777  detailKeys.push_back("suggestedMax");
778  detailKeys.push_back("suggestedStep");
779 
780  for (vector<VuoPublishedPort *>::iterator i = publishedInputPorts.begin(); i != publishedInputPorts.end(); ++i)
781  {
782  VuoPublishedPort *publishedPort = *i;
783 
784  for (vector<string>::iterator j = detailKeys.begin(); j != detailKeys.end(); ++j)
785  {
786  string detailKey = *j;
787  string detailValue;
788  bool foundAttribute = parseAttributeOfPort(n, publishedPort->getClass()->getName(), detailKey, detailValue);
789  if (foundAttribute)
790  {
791  VuoCompilerPublishedPortClass *portClass = static_cast<VuoCompilerPublishedPortClass *>(publishedPort->getClass()->getCompiler());
792  portClass->setDetail(detailKey, detailValue);
793  }
794  }
795  }
796  }
797  }
798 }
799 
803 void VuoCompilerGraphvizParser::setTriggerPortEventThrottling(void)
804 {
805  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
806  {
807  string nodeClassName = agget(n, (char *)"type");
808  if (nodeClassName == VuoComment::commentTypeName)
809  continue;
810 
811  VuoNode *node = nodeForName[n->name];
812 
813  vector<VuoPort *> outputPorts = node->getOutputPorts();
814  for (vector<VuoPort *>::iterator i = outputPorts.begin(); i != outputPorts.end(); ++i)
815  {
816  VuoPort *port = *i;
818  {
819  string eventThrottlingStr;
820  parseAttributeOfPort(n, port->getClass()->getName(), "eventThrottling", eventThrottlingStr);
821  enum VuoPortClass::EventThrottling eventThrottling;
822  if (eventThrottlingStr == "drop")
823  eventThrottling = VuoPortClass::EventThrottling_Drop;
824  else
825  // If composition was created before event dropping was implemented, default to
826  // event enqueuing for backward compatibility (preserving the original behavior).
827  eventThrottling = VuoPortClass::EventThrottling_Enqueue;
828  port->setEventThrottling(eventThrottling);
829  }
830  }
831  }
832 }
833 
837 void VuoCompilerGraphvizParser::setManuallyFirableInputPort(void)
838 {
839  for (Agnode_t *n = agfstnode(graph); n; n = agnxtnode(graph, n))
840  {
841  string nodeClassName = agget(n, (char *)"type");
842  if (nodeClassName == VuoComment::commentTypeName)
843  continue;
844 
845  VuoNode *node = nodeForName[n->name];
846 
847  for (VuoPort *port : node->getInputPorts())
848  {
849  string unused;
850  bool hasAttribute = parseAttributeOfPort(n, port->getClass()->getName(), "manuallyFirable", unused);
851  if (hasAttribute)
852  {
853  manuallyFirableInputNode = node;
854  manuallyFirableInputPort = port;
855  return;
856  }
857  }
858  }
859 }
860 
866 map<string, string> VuoCompilerGraphvizParser::parsePortConstantValues(Agnode_t *n)
867 {
868  map<string, string> constantForInputPort;
869 
870  field_t *nodeInfo = (field_t *)ND_shape_info(n);
871  int numNodeInfoFields = nodeInfo->n_flds;
872  for (int i = 0; i < numNodeInfoFields; i++)
873  {
874  field_t *nodeInfoField = nodeInfo->fld[i];
875  char *inputPortName = nodeInfoField->id;
876  if (! inputPortName)
877  continue;
878 
879  string constantValue;
880  if (parseAttributeOfPort(n, inputPortName, "", constantValue))
881  constantForInputPort[inputPortName] = constantValue;
882  }
883 
884  return constantForInputPort;
885 }
886 
892 bool VuoCompilerGraphvizParser::parseAttributeOfPort(Agnode_t *n, string portName, string suffix, string &attributeValue)
893 {
894  ostringstream oss;
895  oss << "_" << portName;
896  if (! suffix.empty())
897  oss << "_" << suffix;
898  char *attributeName = strdup(oss.str().c_str());
899 
900  char *rawAttributeValue = agget(n, attributeName);
901  free(attributeName);
902 
903  // The Graphviz parser may return a constant value of the empty string if a constant value was defined
904  // for another identically named port within the same composition, even if it wasn't defined for this port.
905  // Any constant value that has been customized within the Vuo Editor should be non-empty anyway.
906  // Therefore, treat constants consisting of the empty string as if they were absent so that they
907  // don't override node-class-specific defaults.
908  if (rawAttributeValue && strcmp(rawAttributeValue, ""))
909  {
910  attributeValue = VuoStringUtilities::transcodeFromGraphvizIdentifier(rawAttributeValue);
911  return true;
912  }
913 
914  return false;
915 }
916 
920 void VuoCompilerGraphvizParser::checkPortClasses(string nodeClassName, vector<VuoPortClass *> dummy, vector<VuoPortClass *> actual)
921 {
922  for (vector<VuoPortClass *>::iterator i = dummy.begin(); i != dummy.end(); ++i)
923  {
924  string dummyName = (*i)->getName();
925 
926  if (dummyName == "refresh")
927  continue;
928 
929  bool found = false;
930  for (vector<VuoPortClass *>::iterator j = actual.begin(); j != actual.end(); ++j)
931  {
932  if ((*j)->getName() == dummyName)
933  {
934  found = true;
935  break;
936  }
937  }
938  if (! found)
939  {
940  VUserLog("Error: Couldn't find node %s's port '%s'.", nodeClassName.c_str(), dummyName.c_str());
941  return;
942  }
943  }
944 }
945 
949 void VuoCompilerGraphvizParser::saveNodeDeclarations(const string &compositionAsString)
950 {
951  size_t nodesRemaining = nodeForName.size();
952 
953  vector<string> lines = VuoStringUtilities::split(compositionAsString, '\n');
954  for (vector<string>::iterator i = lines.begin(); i != lines.end() && nodesRemaining > 0; ++i)
955  {
956  string line = *i;
957  string identifier;
958  for (int j = 0; j < line.length() && VuoStringUtilities::isValidCharInIdentifier(line[j]); ++j)
959  identifier += line[j];
960 
961  map<string, VuoNode *>::iterator nodeIter = nodeForName.find(identifier);
962  if (nodeIter != nodeForName.end())
963  {
964  VuoNode *node = nodeIter->second;
965  node->setRawGraphvizDeclaration(line);
966  --nodesRemaining;
967  }
968  }
969 }
970 
975 vector<VuoNode *> VuoCompilerGraphvizParser::getNodes(void)
976 {
977  return orderedNodes;
978 }
979 
984 vector<VuoCable *> VuoCompilerGraphvizParser::getCables(void)
985 {
986  return orderedCables;
987 }
988 
992 vector<VuoComment *> VuoCompilerGraphvizParser::getComments(void)
993 {
994  return orderedComments;
995 }
996 
997 
1002 {
1003  return publishedInputPorts;
1004 }
1005 
1010 {
1011  return publishedOutputPorts;
1012 }
1013 
1018 {
1019  return manuallyFirableInputNode;
1020 }
1021 
1026 {
1027  return manuallyFirableInputPort;
1028 }
1029 
1034 {
1035  return metadata;
1036 }
1037 
1042 VuoType * VuoCompilerGraphvizParser::inferTypeForPublishedPort(string name, const set<VuoCompilerPort *> &connectedPorts)
1043 {
1044  if (connectedPorts.empty() || name == "refresh")
1045  return NULL;
1046 
1047  VuoCompilerPort *connectedPort = *connectedPorts.begin();
1048  VuoCompilerPortClass *connectedPortClass = static_cast<VuoCompilerPortClass *>(connectedPort->getBase()->getClass()->getCompiler());
1049  return connectedPortClass->getDataVuoType();
1050 }