This page provides a brief description of how the dependency graph works.
The dependency graph (DG) is a collection of entities connected together. Unlike the DAG, these connections can be cyclic, and do not represent a parenting relationship. Instead, the connections in the graph allow data to move from one entity in the graph to another. The entities in the graph which accept input, perform computations, and output new data, are called dependency graph nodes. Dependency graph nodes are used for almost everything in Maya such as model creation, deformation, animation, simulation, and audio processing.
Nodes are the engines which drive the dependency graph. Data comes in to nodes, they perform an operation on the data, and they make the new data available again. The data comes in through the input plugs (instantiations of the nodes attributes) and goes out through the output plugs. At no time should a node require any additional external data beyond what is available through its plugs.
Most objects in Maya are dependency graph nodes, or networks of nodes (several nodes connected together). For example, DAG nodes are dependency graph nodes, and shaders are networks of nodes.
When dependency graph nodes are connected together they can affect DAG nodes and thus affect what is rendered.
This illustration combines a DAG hierarchy with a dependency graph. Transform1, Transform2, Transform3, and Sphere are all DAG nodes (and also dependency graph nodes) while Time is just a dependency graph node.
The x, y, and z scale parameters of Transform3 are driven by time. Alternatively, you can think of the output of Time being plugged into the x, y, and z scale connections of Transform3. When the animation is played back, the two instances of the sphere increase in size.
The data which flows through the graph can be as simple as numbers, or as complicated as a surface. It can also be a completely user-defined object.
The dependency graph consists of a very complex architecture, and a complete explanation of how it works would require a separate manual. A brief explanation is provided instead.
As noted before, the dependency graph is a directed graph, the edges of the graph connect plugs on different nodes. Data is sent along these edges, and includes basic types such as numbers, vectors, and matrices, and complex types such as curves, surfaces, and user defined types.
As part of the definition of Maya nodes, you are required to specify which input attributes affect which output attributes (in the API this is done with the MPxNode::attributeAffects() method).
When an attribute of a node is changed, the dependency graph checks to see if that attribute affects any output. If it does, each of those outputs is marked dirty, meaning that its cached value is stale and needs to be recomputed. Then for each of those output attributes, the dependency graph checks to see if they are the source for a connection. If so, then the connection is followed, and the destination attribute is marked dirty also. This process recurs, and at the end all attributes of nodes in the graph which need to be recomputed are marked dirty. It is important to note that at this time no attributes have been recomputed, instead the state has just been updated so we know which data is no longer valid. The evaluation and re-computation of those invalid attributes occurs as part of a separate step.
Certain events cause the dependency graph to re-evaluate itself, examples being screen refresh, and animation playback. During a refresh for example, the system will walk down the DAG and for each DAG node check to see whether it needs to be re-evaluated (by checking if any plugs on it are dirty), if so, the compute method of the node affecting the plug will be called. This compute method may be dependent on plugs which may also be dirty, so the affecting nodes’ compute methods would also be called, and in this way the pertinent parts of the DG will be re-evaluated, but only those parts that require re-evaluation.
One optimization is that the DG does not re-evaluate the graph unless it needs to. For example, imagine a revolved surface where there are three nodes, a curve DAG node used as input to the second node, a node which revolves the curve and generates a surface, which is the output to the third node, a DAG node which puts the surface into the DAG. If the input curve was modified, the surface would not be regenerated immediately, it may not happen until the next screen refresh. To make sure that the surface does eventually get rebuilt, modifying the curve would cause all plugs connected to the curve’s output plug to be marked dirty, hence the input to the revolve node would be marked dirty (the curve’s output plug itself would not be marked dirty since it has just been recomputed). When declaring attributes it’s necessary to indicate what attributes affect each other, so in the revolve node, the output attribute is dependent on the input attribute, then marking the input attribute dirty causes the output attribute to be marked dirty. The output of the revolve node is connected to the surface node, marking the revolve node’s output dirty marks the surface as being dirty. So, when the DAG is walked during a screen refresh, since the surface is marked dirty, everything that it is dependent on which has also been marked dirty needs to be re-evaluated.
Re-evaluation stops at the first node which doesn’t have any dirty inputs. For example if the number of degrees to revolve a curve was changed but the curve itself was not, then rebuilding the revolved surface would cause the revolve node to be recomputed, but the curve would not be affected.
This is a high level description, the actual implementation provides a great deal of intelligence so that unnecessary evaluations are avoided.
The data flowing through the graph is analogous to water flowing through pipes. The pipes themselves are the connections but unless they have data to redirect and modify they are not actually doing anything.
Extending this analogy, the nodes are like taps, showers, or fountains. They all do something with the water in their own unique way but they must have water to operate.
An interesting side effect of using the DG is that it can make it difficult to directly affect an object.
For example, consider the sphere in the above figure. If no DG node is connected to Transform3 to affect the sphere’s scale, any values set through the UI or API will be the new scale. However, if as in the figure Time affects the scale of the sphere, the effect would be different when Transform3’s scale is additionally modified through the UI or API. If you set the scale it would override the scale being set by Time only until the next re-evaluation of the dependency graph, when the scale of the sphere would again be set by Time and the value you set through the UI or API would be lost.
A more complex example would be a revolved surface. What would happen if you tried to move a CV on the generated surface? The CV would be moved to its new position only until the DG is re-evaluated, at which time the CV would be moved back to the position dictated by the revolve node.
However, fine-tuning or "tweaking" a model is a necessary operation for building complex models and scenes. So Maya has designed a mechanism for handling these tweaks. Mesh shapes have an attribute, pnts, which stores local changes made to the mesh vertices. Any upstream connection to the mesh shape node which generates a new set of vertices for the mesh will not disturb the pnts attribute. The values in the pnts attribute are added to the coordinates of the mesh. For NURBS surfaces and other control point based nodes, the controlPoints attribute stores tweak information. Maya also has implemented a tweak node which will store tweak information for a control point based node. The tweak node is placed between the control point based node and an upstream deforming node which operates on the control points. The tweak node integrates the tweak information in with the deformed control points to generate the final set of control points that is then passed to the shape. Refer to the manual pages for the tweak node as well as the mesh and NURBS surface shape nodes for more information on the attributes which handle tweak information.
Every dependency graph node has a state value that indicates how it should be evaluated. Maya users can set and retrieve these values in the Maya UI, through MEL or Python commands, and through the API. The state is stored as an integer in the MPxNode::state attribute.
state value | state | description |
---|---|---|
0 |
Normal |
This is the normal, default node state. A node in this state should be evaluated normally. |
1 |
HasNoEffect |
The HasNoEffect, or pass-through, state indicates that the node should allow its input data to pass through the node unchanged. It is used in cases where the node performs some computations on an input value and produces an output of the same type. This allows a user to easily disable the computations in a node without disconnecting the node from the surrounding graph. For example, nearly all deformers support this state by sending the input geometry directly to the output attribute without deforming it. However, nodes that produce different types of outputs from the inputs they accept typically do not support this state. When you write a custom dependency node class, you may choose to support this state. For details, see Implementing the compute() method for a dependency graph node. |
2 |
Blocking |
The Blocking state is implemented in the dependency node base class, and applies to all nodes. Blocking is applied to connections during the evaluation phase. An evaluation request to a blocked connection will fail, causing the destination plug to retain its current value. Since blocked connections are never cleaned, this state affects dirty propagation. When a node is set to Blocking, the behavior is supposed to be the same as if all outgoing connections were broken. As long as nobody requests evaluation of the blocked node directly, it will not evaluate again. Note that a blocked node will still respond to getAttr() requests, but a getAttr() of a downstream node will not re-evaluate the blocked node. Setting the root transform of a hierarchy to Blocking won’t automatically influence child transforms in the hierarchy. Instead, you must explicitly set each child node to the Blocking state. For example, you could use the following script: import maya.cmds as cmds def blockTree(root): nodesToBlock = [] for node in {child:1 for child in cmds.listRelatives( root, path=True, allDescendents=True )}.keys(): nodesToBlock += cmds.listConnections(node, source=True, destination=True ) for node in {source:1 for source in nodesToBlock}.keys(): cmds.setAttr( '%s.nodeState' % node, 2 ) Applying this script would continue to draw objects, but child nodes would not be animated. When you write a custom dependency node class, you do not need to check for this case, since it is handled automatically by the base MPxNode class. |
Three other states exist for internal use only: Waiting-Normal, Waiting-HasNoEffect, and Waiting-Blocking. These nodes temporarily shut off parts of the graph during user interaction (e.g. while manipulating the graph). Once the interaction is done, Maya will reset the node state appropriately. You should never need to worry about these states.