Skip to content

Pipeline

chillersanim edited this page Aug 9, 2019 · 2 revisions

The pipeline framework offers functionality to pipeline a workload efficiently.
It offers some specialized pipeline modules for commonly used tasks.
To keep the application responsive, it works in multiple bursts, between which it pauses the calculation.

Setup:

A pipeline consists of two components:

  • PipelineGraph
  • PipelineNode

The pipeline graph manages the workload and contains all PipelineNodes.
The pipeline node has a single task and recieves and/or forwards work items.

To setup a pipeline, one has to create a new pipeline graph instance.
Then the required pipeline nodes have to be setup and added to the pipeline instance.
Lastly, the node connections have to be setup.

Example:

If you wanted to replace all existing colliders in a scene with automatically selected colliders, you could create a pipeline like this:
ExamplePipeline

The equivalent pipeline code would look like this:

public PipelineGraph BuildPipeline()
{
    // Create the pipeline graph
    var graph = new PipelineGraph();

    // Create the pipeline nodes
    var getGameObjects = new PP_GameObjectCollector();
    var meshRendererFilter = new PP_FilterByComponent<Renderer>();
    var removeOldCollider = new PP_RemoveComponents<Collider>();
    var addCollider = new PP_AddCollider();

    // Add nodes to graph
    graph.AddNode(getGameObjects);
    graph.AddNode(meshRendererFilter);
    graph.AddNode(removeOldCollider);
    graph.AddNode(addCollider);

    // Add node connections
    getGameObjects.AddFollowupStep(meshRendererFilter);
    meshRendererFilter.AddFollowupStep(removeOldCollider);
    removeOldCollider.AddFollowupStep(addCollider);
    // removeOldCollider doesn't need a followup step, as it is the last node

    return graph;
}

To use the pipeline, one could call it in every update to process a bit of the workload:

public class ReplaceCollidersExample : MonoBehaviour
{
    public PipelineGraph graph;

    void OnEnable()
    {
        graph = BuildPipeline();
        graph.Initialize();
    }

    void Update()
    {
        if(graph == null)
        {        
            return;
        }

        var hadWork = graph.DoWork(2.0f);
        
        // Remove the graph as it's no longer needed.
        if(!hadWork)
        {
            graph = null;
        }
    }

    [...]
}

This code would collect all game objects in the scene.
Then it filters them by the Renderer component and discards all game objects that don't have a renderer.
Then it removes all Collider components from the game objects.
Finally, it adds an appropriate collider to these game objects.

To keep the application responsive, the pipeline only works for up to 2 milliseconds in every update call.
When the workload has been finished, the graph gets discarded.

Custom nodes

All nodes in the above example were specialized nodes.
To create your own specialized node, you can implement it using one of the base classes:

  • PipelineBase
    The most basic node.
    Very flexible, but should only be choosen when no other base node type is sufficient.
  • PipelineStart
    A start node that provides the initial dataset.
    Either from a stream, list or other source.
    Shouldn't do any work on the items.
  • PipelineWorker<Tin, Tout>
    A simple worker node, that modifies items.
    Isn't required to output the same type of items as were recived.
    Handles basic item reciving, but has no way of forwarding processed items.
  • PipelineFilter
    A filter that forwards only items that match a filter condition.
  • PipelineItemWorker
    A more specialized version of the PipelineWorker, which outputs the same type of items as recived.
    Handles forwarding of processed items.
  • PipelineItemFactory<Tin, Tout>
    A more specialized version of the PipelineWorker, which output different types of items as recived.
    Handles forwarding of processed items.
  • PipelineEnd
    The end of a pipeline, can't forward output items.
    Can be used as the output of a pipeline.