-
Notifications
You must be signed in to change notification settings - Fork 0
VPL notes
Nodes have inputs and outputs (AKA inlets and outlets, or in/out ports). Ports can be strongly typed, i.e. accepting/sending specific types of data. Or they may be generic, accepting/sending a range of types of data, or even any type of data.
Strongly-typed ports can help assist users in limiting connections or suggestions for nodes to connect to.
See notes below about execution order and hot/cold inlets.
Inlets could also limit or conform incoming data, e.g. limiting to certain ranges. Inlets might also have default values if the node will be invoked but no data has yet been received.
Inputs could also be considered as "properties" of an operation, particularly if the operation is more of a persistent stateful nature. See attributes below.
In asynchronous contexts, there may be a specific order in which outlets 'fire'.
In a sync context, all nodes conceptually output at the same moment (though there may be a procedural order derived by dataflow), usually at some regular interval. In async, nodes may output at different rates and more or less sporadic intervals. E.g. in Max audio & gen cables are sync, everything else is async.
Joins in sync need to be interpreted in a certain way. In Max, audio joins implicitly sum their values.
Generally need special handling to avoid infinite loops. E.g. placing a 'delay' item in the feedback loop, or only connecting to "cold" inlets.
A definition outlines the structural content of an object or operation, but does not allocate anything. An instance of a definition allocates it, and may have its own independently varying data.
Stateful vs. stateless nodes (process vs. operation, nontrivial vs. trivial, non-const vs const functions)
A stateless node is like a chemical reaction: the output depends only on the immediate input. It does not depend on previous inputs. A stateless node can be duplicated in code, or inlined, since it has no state to keep track of.
A stateful node is like a living cell: it carries some internal data between invocations, and the output depends both in on the input as well as the internal state. It has contingent history. A stateful node needs to allocate/manage memory for the code.
Impure if a node's operation has side effects; i.e. whether its invocation also changes state somewhere other than their immediate outputs. Stateful nodes are impure simce they carry internal state.
Advantage of pure inputs is that they can be computed on demand.
Exec connections determine the order of operation. An exec output can connect to only one exec input, which defines the next operation to take place, but several exec cables may connect to the same exec input (i.e. a control flow join). Control flow nodes may have multiple exec outputs, e.g. for the true/false paths of an if block.
In many VPLs, exec is not explicit, but derived from the data flow; sometimes using the concept of 'hot' and 'cold' inlets (data received on a hot inlet will trigger the operation; data on a cold inlet will cache until the next invocation). Special objects can be used to make control-flow order explicit (e.g. trigger object in Max).
Exec rules are needed for any operations with side effects (including stateful ops). Exec can be implicit for pure stateless operations.
Related concept:
Data flow helps define where the data needed by an operation comes from. Control-flow defines what order operations take place. Data-flow tends to have a more functional-programming flavour. Control-flow has a more procedural-programming flavour.
In text-based languages, control flow operations include: goto (generic jump), if/elseif/else AKA switch (forward jumps), for/forEach/while/until etc. (backward jumps), subroutine/function call + return (jump & return), wait/resume (coroutine/fiber pause and restore), and dynamic load/exec (i.e. interpret new data as code).
- Tree: A node only has one parent/source. There is one node that is the ancestor of all others (the root). Pathways from the root are only splits, there are no joins. The tree can be traversed easily using depth-first or breadth-first algorithms.
- DAG: There can be multiple parents/sources for each node. That is, there are both splits and joins in the pathways. Traversal requires some kind of memoization to avoid infinite loops.
- DG: A node may be its own ancestor (perhaps with several steps in between). Even more care (memoization) is needed to avoid infinite loops in traversal, as well as infinite loops in execution.
- UG: Nodes may be related but it is not given whether one is ancestor to the other.
- Push (data-driven): the update of a source then propagates to (invokes) every sink it is connected to.
- Pull (demand-driven): a sink's need pulls from every source it is connected to.
These are trivial for trees, but require care for graphs.
Sources of exec (often with data) that come from outside the system, such as: starting the patch, the passing of time (rendering frames, scheduled clock events, etc.), UI triggers, file changes, network data, etc.
A whole patch can also act like a node within another patch; the top-level patch is both definition and singular instance. Instances of patchers can be embedded as nodes within another patcher (in Max these are called "abstractions"). Editing one instance actually updates the definition, and thus updates all other instances.
Parts of a patch can be 'grouped', and possibly encapsulated, for ease of editing and self-documentation. It has no effect on the process output (in Max these are called "subpatchers").
A node may have some configuration arguments, which shape how the node operates. It could be to set initial internal state, or it might modify the internal operations.
Distinguish between static (const) and dynamic configuration: static is permanent (between edits), dynamic could be changed according to data the node receives in normal operation. Static is like macro or templates: allows optimization at code generation time.
Example: Max @attributes are usually dynamic, but in Gen they are static.