/** \page page_running Node running This document tries to explain how the Nodes in a PipeWire graph become runnable so that they can be scheduled later. It also describes how nodes are grouped together and scheduled together. # Runnable Nodes A runnable node is a node that will participate in the dataflow in the PipeWire graph. Not all nodes should participate by default. For example, filters or device nodes that are not linked to any runnable nodes should not do useless processing. Nodes form one or more groups depending on properties and how they are linked. For each group, one driver is selected to run the group. Inside the group, all runnable nodes are scheduled by the group driver. If there are no runnable nodes in a group, the driver is not started. PipeWire provides mechanisms to precisely describe how and when nodes should be scheduled and grouped using: - port properties to control how port links control runnable state - node properties to control processing - grouping of nodes and internal links between nodes # Port passive modes A Port has 4 passive modes, this depends on the value of the `port.passive` property: - `false`, the port will make the peer active and an active peer will make this port active. - `true`, the port will not make the peer active and an active peer will not make this port active. - `follow`, the port will not make the peer active but an active peer will make this port active. - `follow-suspend`, the port will only make another follow-suspend peer active but any active peer will make this port active. The combination of these 4 modes on the output and input ports of a link results in a wide range of use cases. # Node passive modes A Node can have 10 passive modes, `node.passive` can be set to a comma separated list of the following values: - `false`, both input and output ports have `port.passive = false` - `in`, input ports have `port.passive = true` - `out`, output ports have `port.passive = true` - `true`, both input and output ports have `port.passive = true`. This is the same as `in,out`. - `in-follow`, input ports have `port.passive = follow` - `out-follow`, output ports have `port.passive = follow` - `follow`, input and output ports have `port.passive = follow`. This is the same as `in-follow,out-follow`. - `in-follow-suspend`, input ports have `port.passive = follow-suspend` - `out-follow-suspend`, output ports have `port.passive = follow-suspend` - `follow-suspend`, input and output ports have `port.passive = follow-suspend`. This is the same as `in-follow-suspend,out-follow-suspend`. Nodes by default have the `false` mode but nodes with the `media.class` property containing `Sink`, `Source` or `Duplex` receive the `follow-suspend` mode by default. Unless explicitly configured, ports inherit the mode from their parent node. # Updating the node runnable state We iterate all nodes A in the graph and look at its peers B. Based on the port passive modes of the port links we can decide if the nodes are runnable or not. A link will always make both nodes runnable or none. The following table decides the runnability of the 2 nodes based on the port.passive mode of the link between the 2 ports: ``` B-false B-true B-follow B-follow-suspend A-false X X X X A-true A-follow A-follow-suspend X Table 1 ``` When a node is made runnable, the port passive mode will then decide if the peer ports should become active as well with the following table. ``` B-false B-true B-follow B-follow-suspend A-false X X X A-true X X X A-follow X X X A-follow-suspend X X X Table 2 ``` So when A is runnable, all peers are activated except those with `port.passive=true`. When A is runnable, all the nodes that share the same group or link-group will also be made runnable. # Use cases Let's check some cases that we want to solve with these node and port properties. ## Device nodes ``` +--------+ +--------+ | ALSA | | ALSA | | Source | | Sink | | FL FL | | FR FR | +--------+ +--------+ ``` Unlinked device nodes are supposed to stay suspended when nothing is linked to them. ``` +----------+ +--------+ | playback | | ALSA | | | | Sink | | FL ------ FL | | FR ------ FR | +----------+ +--------+ ``` An (active) player node linked to a device node should make both nodes runnable. Device nodes have the `port.passive = follow-suspend` property by default. The playback node has the `port.passive = false` by default. If we look at the playback node as A and the sink as B, both nodes will be made runnable according to Table 1. The two runnable nodes form a group and will be scheduled together. One of the nodes of a group with the `node.driver = true` property is selected as the driver. In the above case, that will be the ALSA Sink. Likewise, a capture node linked to an ALSA Source should make both nodes runnable. ``` +--------+ +---------+ | ALSA | | capture | | Source | | | | FL ------ FL | | FR ------ FR | +--------+ +---------+ ``` The ALSA Source is now the driver. Also, linking 2 device nodes together should make them runnable: ``` +--------+ +--------+ | ALSA | | ALSA | | Source | | Sink | | FL ----------------------- FL | | FR ----------------------- FR | +--------+ +--------+ ``` This is the case because in Table 1, the two `port.passive = follow-suspend` ports from the Source and Sink activate each other. ## Filter nodes When there is a filter in front of the ALSA Sink, it should not make the filter and sink runnable. ``` +--------+ +--------+ | filter | | ALSA | | | | Sink | FL FL ------ FL | FR FR ------ FR | +--------+ +--------+ ``` The links between the filter and ALSA Sink are `port.passive = true` and don't make the nodes runnable. The filter needs to be made runnable via some other means to also make the ALSA Sink runnable, for example by linking a playback node: ``` +----------+ +--------+ +--------+ | playback | | filter | | ALSA | | | | | | Sink | | FL ------ FL FL ------ FL | | FR ------ FR FR ------ FR | +----------+ +--------+ +--------+ ``` The input port of the filter is `port.passive = follow-suspend' and so it can be activated by the playback node. Likewise, if the ALSA Sink is runnable, it should not automatically make the filter runnable. For example: ``` +--------+ +--------+ | filter | | ALSA | | | | Sink | FL FL ---+-- FL | FR FR ---|+- FR | +--------+ || +--------+ || +----------+ || | playback | || | | || | FL ---+| | FR ----+ +----------+ ``` Here the playback node makes the ALSA Sink runnable but the filter stays not-runnable because the output port is `port.passive = true`. ## Device node monitor Consider the case where we have an ALSA Sink and a monitor stream connected to the sink monitor ports. ``` +-------+ +--------++ | ALSA | | monitor | | Sink | | | FL FL ------ FL | FR FR ------ FR | +-------+ +---------+ ``` We would like to keep the monitor stream and the ALSA sink suspended unless something else activates the ALSA Sink: ``` +----------+ +-------+ +---------+ | playback | | ALSA | | monitor | | | | Sink | | | | FL ------ FL FL ------ FL | | FR ------ FR FR ------ FR | +----------+ +-------+ +---------+ ``` We can do this by making the monitor stream input ports `port.passive = follow` and leave the ALSA Sink monitor output ports as `port.passive = follow-suspend`. According to Table 1, both nodes will not activate each other but when ALSA Sink becomes runnable because of playback, according to Table 2, the monitor will become runnable as well. Note how we need the distinction between `follow` and `follow-suspend` for this use case. ## Node groups Normally when an application makes a capture and playback node, both nodes will be scheduled in different groups, consider: ``` +--------+ +---------+ | ALSA | | capture | | Source | | | | FL ------ FL | | FR ------ FR | +--------+ +---------+ +----------+ +--------+ | playback | | ALSA | | | | Sink | | FL ------ FL | | FR ------ FR | +----------+ +--------+ ``` Here we see 2 groups with the ALSA Source and ALSA Sink respectively as the drivers. Depending on the clocks of the nodes, the capture and playback will not be in sync. They will each run in their own time domain depending on the rate of the drivers. When we place a node.group property with the same value on the capture and playback nodes, they will be grouped together and this whole graph becomes one single group. Because there are 2 potential drivers in the group, the one with the highest `priority.driver` property is selected as the driver in the group. The other nodes in the group (including the other driver) become followers in the group. When a node becomes runnable, all other nodes with the same node.group property become runnable as well. ## Node link groups When we have a filter that is constructed from two nodes, an input and an output node, we could use the `node.group` property to make sure they are both scheduled and made runnable together. ``` +--------+ +-------+ +--------+ +-------+ | ALSA | | input | | output | | ALSA | | Source | | | | | | Sink | | FL ------ FL -- processing-- FL ------ FL | | FR ------ FR | | FR ------ FR | +--------+ +-------+ +--------+ +-------+ ``` This would work fine but it does not describe that there is an implicit internal link between the input and output node. This information is important for the session manager to avoid linking the output node to the input node and make a loop. The `node.link-group` property can be used to both group the nodes together and descibe that they are internally linked together. When a node becomes runnable, all other nodes with the same node.link-group property become runnable as well. For the 2 node filters, like loopback and filter-chain, the same `port.passive` property rules apply as for the filter nodes. Note that for the virtual devices, the Source/Sink nodes will be `follow-suspend` by default and the other node should be set to `node.passive = true` to make the ports passive. ## Want driver When there is no driver node in the group, nothing should be scheduled. This can happen when a playback node is linked to a capture node: ``` +--------+ +---------+ | player | | capture | | | | | | FL ----------- FL | | FR ----------- FR | +--------+ +---------+ ``` None of these nodes is a driver so there is no driver in the group and nothing will be scheduled. When one of the nodes has `node.want-driver = true` they are grouped and scheduled with a random driver node. This is often the driver node with the highest priority (usually the Dummy-Driver) or otherwise a driver that is already scheduling some other nodes. ## Always process nodes A simple node, unlinked to anything should normally not run. ``` +--------+ | player | | | | FL | FR +--------+ ``` When the `node.always-process = true` property is set, the node will however be made runnable even if unlinked. This is done by adding the node to a random driver. `node.always-process = true` implies the `node.want-driver = true` property. ## Sync groups In some cases, you only want to group nodes together depending on some condition. For example, when the JACK transport is activated, all nodes in the graph should share the same driver node, regardless of the grouping or linking of the nodes. This is done by setting the same node.sync-group property on all nodes (by default all nodes have `node.sync-group = group.sync.0`). When a node sets `node.sync = true` all the other nodes with the same `node.sync-group` property are grouped together. This can be used to implement the JACK transport. When the transport is started, the `node.sync=true` property is set and all nodes join one group with a shared driver and timing information. */