/* 
 * Defining nodes in custom pipelines
 *
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   definepipe.c                                                *
 *                                                                          *
 *  Purpose :   Yes, of course it has                                       *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 Includes
 */

#include <float.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "batypes.h"
#include "balibtyp.h"
#include "badebug.h"
#include "bamemory.h"

#include "p2core.h"
#include "p2define.h"
#include "p2dep.h"

#include "bapipe.h"

#if defined (__MWERKS__)
#if (defined(RWVERBOSE))
#pragma message (__FILE__ "/" _SKY_EXPAND(__LINE__) ": __MWERKS__ == " _SKY_EXPAND(__MWERKS__))
#endif /* (defined (__MWERKS__)) */
#if (__option (global_optimizer))
#pragma always_inline off
#endif /* (__option (global_optimizer)) */
#endif /*  defined (__MWERKS__) */

/****************************************************************************
 Local Types
 */

typedef struct tagTopSortData TopSortData;
struct tagTopSortData
{
    RxPipeline         *pipeline;
    RwUInt32            nodesarrayslot;
    /* forwardmap [original_location] gives current_location  */
    RwUInt32           *forwardmap;
    /* backwardmap[current_location ] gives original_location */
    RwUInt32           *backwardmap;
};

/****************************************************************************
 Local Defines
 */

/* by their NodeRefs shall ye know them */
#define ISNODELIVE(_node) ( NULL != ( (_node)->nodeDef) )

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/*****************************************************************************
 _NodeClone

 called from ...RxPipelineNodeRequestCluster() and RxPipelineNodeReplaceCluster()
 This clones a node's noderef (for subsequent modification) and sets the node
 to point at the clone if successful.
*/

#define ALIGN(n) ( ((n) + 3U) & ~3U )

#if (0)

/*****************************************************************************
 _CleanUpNodesAfterFailedTopSort

 - called from RxLockedPipeUnlock()
*/

static void
_CleanUpNodesAfterFailedTopSort(RxPipeline * pipeline)
{
    RwUInt32            i;

    RWFUNCTION(RWSTRING("_CleanUpNodesAfterFailedTopSort"));

    /* This function is a placeholder in case cleanup needs doing
     * subsequent to future changes to pipeline construction... */

    RWRETURNVOID();
}
#else /* 0 */
#define _CleanUpNodesAfterFailedTopSort(_pipeline) /* No op */
#endif /* 0 */

static RxNodeDefinition *
_NodeClone(RxPipelineNode * node, RxClusterDefinition * cluster2add)
{
    /* optional param.; clone()
     * function doubles as clone&growclusterlist() */
    /*
     * damn, but a node is a complex beast
     * (coz of all the variable size components)
     */

    RxNodeDefinition   *result = (RxNodeDefinition *)NULL;
    RxNodeDefinition   *source;
    RwUInt32            i, size, destnumcli;
    RwUInt8            *block;

    RWFUNCTION(RWSTRING("_NodeClone"));

    RWASSERT(node);
    source = node->nodeDef;
    RWASSERT(source);

    destnumcli = ((cluster2add == NULL) ?
                  source->io.numClustersOfInterest :
                  source->io.numClustersOfInterest + 1);

    size = (ALIGN(sizeof(RxNodeDefinition)) +
            ALIGN(strlen(source->name) + 1) +
            ALIGN(destnumcli * sizeof(RxClusterRef)));

    size += (ALIGN(destnumcli * sizeof(RxClusterValidityReq)) +
             ALIGN(source->io.numOutputs * sizeof(RxOutputSpec)));

    for (i = 0; i < source->io.numOutputs; i++)
    {
        RxOutputSpec       *outputspec = &source->io.outputs[i];

        size += ALIGN(strlen(outputspec->name) + 1);
        size += ALIGN(destnumcli * sizeof(RxClusterValid));
    }

    block = (RwUInt8 *) RwMalloc(size);
    if (block != NULL)
    {
        /* node */
        result = (RxNodeDefinition *) block;
        block += ALIGN(sizeof(RxNodeDefinition));

        /* name */
        result->name = (char *) block;
        block += ALIGN(strlen(source->name) + 1);
        strcpy((char *) (result->name), (const char *) (source->name));

        /* nodemethods */
        result->nodeMethods = source->nodeMethods;

        /* numclustersofinterest */
        result->io.numClustersOfInterest = destnumcli;

        /* clustersofinterest */
        result->io.clustersOfInterest = (RxClusterRef *) block;
        block += ALIGN(destnumcli * sizeof(RxClusterRef));

        for (i = 0; i < source->io.numClustersOfInterest; i++)
        {
            /* forcepresent */
            result->io.clustersOfInterest[i].forcePresent =
                source->io.clustersOfInterest[i].forcePresent;

            /* clusterdef */
            result->io.clustersOfInterest[i].clusterDef =
                source->io.clustersOfInterest[i].clusterDef;

            /* reserved */
            result->io.clustersOfInterest[i].reserved =
                source->io.clustersOfInterest[i].reserved;
        }

        if (cluster2add != NULL)
        {
            /* forcepresent - we're being called by the app to add this
             * cluster, but we give them more debugging feedback (if no
             * node ends up using the cluster) if we *don't* set
             * forcepresent (it fails later on cos the cluster
             * disappears) and if they really need FORCEPRESENT then
             * they can do it themselves */
            result->io.clustersOfInterest[i].forcePresent = 
                (RxClusterForcePresent)FALSE;

            /* clusterdef */
            result->io.clustersOfInterest[i].clusterDef = cluster2add;

            /* reserved */
            result->io.clustersOfInterest[i].reserved = 0;
        }

        /* inputrequirements */
        result->io.inputRequirements = (RxClusterValidityReq *) block;
        block += ALIGN(destnumcli * sizeof(RxClusterValidityReq));
        memcpy(result->io.inputRequirements,
               source->io.inputRequirements,
               source->io.numClustersOfInterest *
               sizeof(RxClusterValidityReq));

        if (cluster2add != NULL)
        {
            result->io.inputRequirements[destnumcli - 1] =
                rxCLREQ_DONTWANT;
        }

        /* numoutputs */
        result->io.numOutputs = source->io.numOutputs;

        /* outputs */
        result->io.outputs = (RxOutputSpec *) block;
        block += ALIGN(source->io.numOutputs * sizeof(RxOutputSpec));

        for (i = 0; i < source->io.numOutputs; i++)
        {
            /* name */
            result->io.outputs[i].name = (char *) block;
            block += ALIGN(strlen(source->io.outputs[i].name) + 1);
            strcpy((char *) result->io.outputs[i].name,
                   (const char *) (source->io.outputs[i].name));

            /* outputclusters */
            result->io.outputs[i].outputClusters =
                (RxClusterValid *) block;
            block += ALIGN(destnumcli * sizeof(RxClusterValid));
            memcpy(result->io.outputs[i].outputClusters,
                   source->io.outputs[i].outputClusters,
                   source->io.numClustersOfInterest *
                   sizeof(RxClusterValid));

            if (cluster2add != NULL)
            {
                result->io.outputs[i].outputClusters[destnumcli - 1] =
                    source->io.outputs[i].allOtherClusters;
            }

            /* allotherclusters */
            result->io.outputs[i].allOtherClusters =
                source->io.outputs[i].allOtherClusters;
        }

        /* pipelinenodeprivatedatasize */
        result->pipelineNodePrivateDataSize =
            source->pipelineNodePrivateDataSize;

        /* editable */
        result->editable = (RxNodeDefEditable)TRUE;

        /* While a pipeline is locked, its nodes do not count as referencing
         * node defs, so we initialise the new node def to have no references
         * and we leave the old node def alone
         * (it's count will have been decremented by RxPipelineLock() anyway),
         * unless it's editable.
         *
         * In this case kill it cos it must have been created during this
         * pipeline-editing session and you cannot
         * (currently, and this mustn't change until the whole
         * reference-counting scheme sorts itself out)
         * (a) clone a locked pipeline or
         * (b) reference an editable node def during editing without
         * cloning a pipe
         * (i.e you can't reference it in two places within the same pipe - if
         * you do, you're doing it behind our backs, which is illegal)
         */

        RWASSERT(0 <= source->InputPipesCnt);

        if ((source->InputPipesCnt == 0) && (source->editable))
        {
            RwFree(source);
        }
        node->nodeDef = result;
        result->InputPipesCnt = 0;
    }
    else
    {
        RWERROR((E_RW_NOMEM, size));
    }

    RWRETURN(result);
}

/****************************************************************************
 IoSpecSearchForCluster()
 */

static              RwUInt32
IoSpecSearchForCluster(RxIoSpec * iospec,
                       RxClusterDefinition * clusterDef)
{
    RwUInt32            result = (RwUInt32) - 1;
    RwUInt32            n;

    RWFUNCTION(RWSTRING("IoSpecSearchForCluster"));

    for (n = 0; n < iospec->numClustersOfInterest; n++)
    {
        if (iospec->clustersOfInterest[n].clusterDef == clusterDef)
        {                      /* success */
            result = n;
            break;
        }
    }

    RWRETURN(result);
}

/*****************************************************************************
 _RwNodeCreate

 - called from RxLockedPipeAddFragment(), RxLockedPipeReplaceNode()
*/

static              RwBool
_NodeCreate(RxPipelineNode * node, RxNodeDefinition * nodespec)
{
    RxPipelineNodeTopSortData *topSortData;
    RwUInt32            n;

    RWFUNCTION(RWSTRING("_NodeCreate"));
    RWASSERT(node != NULL);
    RWASSERT(nodespec != NULL);

    n = nodespec->io.numOutputs;

    memset(node, 0x00U, sizeof(RxPipelineNode));

    if (n != 0)
    {
        RwUInt32           *outputs;
        RwBool              error = FALSE;

        if (n > RXNODEMAXOUTPUTS)
        {
            RWERROR((E_RX_NODETOOMANYOUTPUTS));
            error = TRUE;
        }
        if (nodespec->io.numClustersOfInterest >
            RXNODEMAXCLUSTERSOFINTEREST)
        {
            RWERROR((E_RX_NODETOOMANYCLUSTERSOFINTEREST));
            error = TRUE;
        }
        if (error)
            RWRETURN(FALSE);

        outputs = (RwUInt32 *) RwMalloc(n * sizeof(RwUInt32));

        if (outputs == NULL)
        {
            RWERROR((E_RW_NOMEM, (n * sizeof(RwUInt32))));
            RWRETURN(FALSE);
        }

        node->outputs = outputs;
        node->numOutputs = n;

        do
        {
            *outputs = (RwUInt32) - 1;
        }
        while (outputs++, --n);

    }

    topSortData = (RxPipelineNodeTopSortData *)
        RwMalloc(sizeof(RxPipelineNodeTopSortData));

    if (NULL == topSortData)
    {
        RWERROR((E_RW_NOMEM, sizeof(RxPipelineNodeTopSortData)));
        if (node->outputs != NULL)
        {
            RwFree(node->outputs);
            node->outputs = (RwUInt32 *)NULL;
        }
        RWRETURN(FALSE);
    }

    topSortData->numIns = 0;
    topSortData->numInsVisited = 0;
    topSortData->req = (rxReq *)NULL;
    topSortData->initializationData = NULL;
    topSortData->initializationDataSize = 0;
    node->topSortData = topSortData;

    node->nodeDef = nodespec;  /* ISNODELIVE(node)? YES! */

    RWRETURN(TRUE);
}

/*****************************************************************************
 NodeDestroy

 - called from RxLockedPipeAddFragment(),
               RxLockedPipeReplaceNode(),
               RxLockedPipeDeleteNode()
*/

static void
NodeDestroy(RxPipelineNode * node)
{
    RxPipelineNodeTopSortData *topSortData;

    RWFUNCTION(RWSTRING("NodeDestroy"));

    RWASSERT(node);
    topSortData = node->topSortData;

    if (topSortData != NULL)
    {
        void               *initializationData;

        initializationData = topSortData->initializationData;

        if (initializationData != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(initializationData));
            RwFree(initializationData);
            topSortData->initializationData = NULL;
            topSortData->initializationDataSize = 0;
        }

        RWASSERT(RWCRTISVALIDHEAPPOINTER(topSortData));
        RwFree(topSortData);
        node->topSortData = (RxPipelineNodeTopSortData *)NULL;
    }

    if (node->outputs != NULL)
    {
        RWASSERT(RWCRTISVALIDHEAPPOINTER(node->outputs));
        RwFree(node->outputs);
        node->outputs = (RwUInt32 *)NULL;
    }

    node->numOutputs = 0;

    if (node->nodeDef->editable)
    {
        RWASSERT(RWCRTISVALIDHEAPPOINTER(node->nodeDef));
        RwFree(node->nodeDef);
    }

    node->nodeDef = (RxNodeDefinition *)NULL;      /* ISNODELIVE(node)? NO! */

    RWRETURNVOID();
}

/*****************************************************************************
 PipelineTallyInputs

 - called from RxLockedPipeUnlock()
*/

static void
PipelineTallyInputs(RxPipeline * pipeline)
{
    RxPipelineNode     *nodes;
    RwUInt32            i;

    RWFUNCTION(RWSTRING("PipelineTallyInputs"));

    nodes = &pipeline->nodes[0];
    for (i = 0; i < pipeline->numNodes; i++)
    {
        if (ISNODELIVE(nodes))
        {
            /* clear this down prior to _TopSort() */
            nodes->topSortData->numInsVisited = 0;

            /* cleared for below */
            nodes->topSortData->numIns = 0;
        }
        nodes++;
    }

    nodes = &pipeline->nodes[0];
    for (i = 0; i < pipeline->numNodes; i++)
    {
        if (ISNODELIVE(nodes))
        {
            if (nodes->numOutputs != 0)
            {
                int                 j = nodes->numOutputs;
                RwUInt32           *outputs = &nodes->outputs[0];

                do
                {              /* loop over node outputs */
                    if (*(RwInt32 *) outputs != -1)
                    {
                        pipeline->nodes[*outputs].topSortData->numIns++;
                    }
                }
                while (outputs++, --j);
            }
        }
        nodes++;
    }

    RWRETURNVOID();
}

/*****************************************************************************
 _TopSortDataCreate

 - called from RxLockedPipeUnlock()
*/

static TopSortData *
_TopSortDataCreate(RxPipeline * pipeline)
{
    RwUInt32            bytes;
    RwUInt8            *p;

    RWFUNCTION(RWSTRING("_TopSortDataCreate"));

    RWASSERT(pipeline != NULL);

    bytes =
        sizeof(TopSortData) + 2 * pipeline->numNodes * sizeof(RwUInt32);

    p = (RwUInt8 *) RwMalloc(bytes);

    if (p != NULL)
    {
        TopSortData        *data = (TopSortData *) p;

        data->pipeline = pipeline;
        data->nodesarrayslot = 0;
        data->forwardmap = (RwUInt32 *) (p + sizeof(TopSortData));
        data->backwardmap =
            (RwUInt32 *) (p + sizeof(TopSortData) +
                          pipeline->numNodes * sizeof(RwUInt32));

        if (pipeline->numNodes != 0)
        {
            /* initialize maps to identity */
            int                 n = pipeline->numNodes - 1;

            do
            {
                data->forwardmap[n] = n;
                data->backwardmap[n] = n;
            }
            while (n--);
        }

        RWRETURN(data);
    }

    RWERROR((E_RW_NOMEM, bytes));
    RWRETURN((TopSortData *) NULL);
}

/*****************************************************************************
 _TopSortDataDestroy

 - called from RxLockedPipeUnlock()
*/

static void
_TopSortDataDestroy(TopSortData * data)
{
    RWFUNCTION(RWSTRING("_TopSortDataDestroy"));

    RwFree(data);

    RWRETURNVOID();
}

/*****************************************************************************
 _TopSortSwapNodes

 - called from PipelineTopSort()
*/

static void
_TopSortSwapNodes(TopSortData * data, RwUInt32 i, RwUInt32 j)
{
    RWFUNCTION(RWSTRING("_TopSortSwapNodes"));

    if (i != j)
    {
        RxPipelineNode      tmpnode;
        RwUInt32            tmp;

        /* exchange node data */
        tmpnode = data->pipeline->nodes[i];
        data->pipeline->nodes[i] = data->pipeline->nodes[j];
        data->pipeline->nodes[j] = tmpnode;

        /* update backward map */
        tmp = data->backwardmap[i];
        data->backwardmap[i] = data->backwardmap[j];
        data->backwardmap[j] = tmp;

        /* update forward map */
        data->forwardmap[data->backwardmap[i]] = i;
        data->forwardmap[data->backwardmap[j]] = j;
    }

    RWRETURNVOID();
}

/*****************************************************************************
 PipelineTopSort

 - called from RxLockedPipeUnlock() (& from self!)
*/

static void                     /* recursive */
PipelineTopSort(TopSortData * data, RwUInt32 nodeindex)
{
    RxPipelineNode     *node;

    RWFUNCTION(RWSTRING("PipelineTopSort"));

    /* Move this node to its final destination in the post-sort array */
    _TopSortSwapNodes(data, data->nodesarrayslot, nodeindex);

    node = &data->pipeline->nodes[data->nodesarrayslot];

    data->nodesarrayslot++;

    if (node->numOutputs != 0)
    {
        RwUInt32           *outputs = &node->outputs[0];
        int                 n = node->numOutputs;

        do
        {
            if (*outputs != (RwUInt32) - 1)
            {
                RwUInt32            outindex =

                    data->forwardmap[*outputs];
                RxPipelineNode     *outnode =

                    &data->pipeline->nodes[outindex];

                /* If all of this output node's inputs have been processed
                 * (including the current node, right now) then traverse
                 * down, that node can be next in the post-sort array */
                if (++outnode->topSortData->numInsVisited ==
                    outnode->topSortData->numIns)
                {
                    PipelineTopSort(data, outindex);
                }
            }
        }
        while (outputs++, --n);
    }

    RWRETURNVOID();
}

/*****************************************************************************
 PipelineUpdateOutputIndices

 - called from RxLockedPipeUnlock()
*/

static void
PipelineUpdateOutputIndices(TopSortData * data)
{
    int                 i = data->pipeline->numNodes;
    RxPipelineNode     *nodes = &data->pipeline->nodes[0];

    RWFUNCTION(RWSTRING("PipelineUpdateOutputIndices"));

    do
    {                          /* loop over pipeline nodes */
        if (ISNODELIVE(nodes))
        {
            if (nodes->numOutputs != 0)
            {
                int                 j = nodes->numOutputs;
                RwUInt32           *outputs = &nodes->outputs[0];

                do
                {              /* loop over node outputs */
                    if (*outputs != (RwUInt32) - 1)
                    {
                        *outputs = data->forwardmap[*outputs];
                    }
                }
                while (outputs++, --j);
            }
        }
    }
    while (nodes++, --i);

    RWRETURNVOID();
}

/*****************************************************************************
 PipelinePathsReplaceOutputIndex

 - called from RxLockedPipeDeleteNode()
*/

static void
PipelinePathsReplaceOutputIndex(RxPipeline * pipeline,
                                RwUInt32 oldindex, RwUInt32 newindex)
{

    int                 i = pipeline->numNodes;
    RxPipelineNode     *nodes = &pipeline->nodes[0];

    RWFUNCTION(RWSTRING("PipelinePathsReplaceOutputIndex"));

    do
    {                          /* loop over pipeline nodes */
        if (ISNODELIVE(nodes))
        {
            if (nodes->numOutputs != 0)
            {
                int                 j = nodes->numOutputs;
                RwUInt32           *outputs = &nodes->outputs[0];

                do
                {              /* loop over node outputs */
                    if (*outputs == oldindex)
                    {
                        *outputs = newindex;
                    }
                }
                while (outputs++, --j);
            }
        }
    }
    while (nodes++, --i);

    RWRETURNVOID();
}

/*****************************************************************************
 PipelineNode2Index

 - called from RxLockedPipeDeleteNode(),
               RxLockedPipeSetEntryPoint(),
               RxLockedPipeAddPath()
*/

static              RwUInt32
PipelineNode2Index(RxPipeline * pipeline, RxPipelineNode * node)
{

    RwUInt32            index = node - pipeline->nodes;

    RWFUNCTION(RWSTRING("PipelineNode2Index"));

    /* sanity check:
     * *node does fall within this pipeline's Nodes array...?
     */

    RWASSERT(&pipeline->nodes[index] == node);
    RWASSERT(index < pipeline->numNodes);

    if (&pipeline->nodes[index] == node && index < pipeline->numNodes)
    {
        RWRETURN(index);
    }

    RWRETURN((RwUInt32) ~ 0);
}

/*****************************************************************************/
static              RwBool
PipelineCopy(RxPipeline * target, RxPipeline * source)
{
    RxNodeDefinition   *def;
    RwUInt32           *newOutputs;
    RwUInt32            n, i;
    RwBool              error = FALSE;

    RWFUNCTION(RWSTRING("PipelineCopy"));

    target->numNodes = source->numNodes;

    /* Copy source nodes */
    for (i = 0; i < source->numNodes; i++)
    {
        target->nodes[i] = source->nodes[i];
    }

    /* copy arrays NOTE: editable node defs can be referenced by this new
     * pipeline, no problems. We don't increment reference counts until
     * this new pipeline is unlocked */
    for (i = 0; i < source->numNodes; i++)
    {
        def = target->nodes[i].nodeDef;

        target->nodes[i].outputs = (RwUInt32 *)NULL;
        if (def)
        {
            n = def->io.numOutputs * sizeof(RwUInt32);
            if (n != 0)
            {
                newOutputs = (RwUInt32 *) RwMalloc(n);
                if (!newOutputs)
                {
                    error = TRUE;
                    /* Continue in order to fill the other slots with NULL */
                }
                else
                {
                    memcpy(newOutputs, source->nodes[i].outputs, n);
                    target->nodes[i].outputs = newOutputs;
                }
            }
        }
        else
        {
            error = TRUE;
        }
    }

    for (i = 0; i < source->numNodes; i++)
    {
        RxPipelineNodeTopSortData *data = (RxPipelineNodeTopSortData *)
            RwMalloc(sizeof(RxPipelineNodeTopSortData));

        if (data == NULL)
        {
            error = TRUE;
            target->nodes[i].topSortData = (RxPipelineNodeTopSortData *)NULL;
        }
        else
        {
            void               *copyOfInitData;

            memcpy(data,
                   source->nodes[i].topSortData,
                   sizeof(RxPipelineNodeTopSortData));
            target->nodes[i].topSortData = data;
            /* Copy initData across, don't just blithely copy the poiner! */
            copyOfInitData =
                RwMalloc(source->nodes[i].
                         topSortData->initializationDataSize);
            if (copyOfInitData != NULL)
            {
                memcpy(copyOfInitData,
                       source->nodes[i].topSortData->initializationData,
                       source->nodes[i].
                       topSortData->initializationDataSize);
                target->nodes[i].topSortData->initializationData =
                    copyOfInitData;
            }
            else
            {
                error = TRUE;
                RwFree(target->nodes[i].topSortData);
                target->nodes[i].topSortData = (RxPipelineNodeTopSortData *)NULL;
            }
        }
    }

    RWRETURN(error);
}

/*****************************************************************************/
static RxPipeline  *
PipelineUnlockTopSort(RxPipeline * pipeline)
{
    TopSortData        *data;
    RwUInt32            i;

    RWFUNCTION(RWSTRING("PipelineUnlockTopSort"));
    RWASSERT(pipeline != NULL);

    data = _TopSortDataCreate(pipeline);
    if (data == NULL)
    {
        /* couldn't alloc. top. sort temporaries;
         * fail (pipeline remains Lock()ed) */
        RWRETURN((RxPipeline *)NULL);
    }

    /* Add up inputs for each node by
     * enumerating outputs in all nodes */
    PipelineTallyInputs(pipeline);

    /* entry point has zero in-degrees? */
    if (pipeline->nodes[pipeline->entryPoint].topSortData->numIns != 0)
    {
        _CleanUpNodesAfterFailedTopSort(pipeline);
        _TopSortDataDestroy(data);

        /* entry point is invalid;
         * fail (pipeline remains Lock()ed) */
        RWERROR((E_RX_INVALIDENTRYPOINT));
        RWRETURN((RxPipeline *)NULL);
    }
    /* all nodes form one connected graph? */
    for (i = 0; i < pipeline->numNodes; i++)
    {
        if ((i != pipeline->entryPoint) &&
            (pipeline->nodes[i].topSortData->numIns == 0))
        {
            _CleanUpNodesAfterFailedTopSort(pipeline);
            _TopSortDataDestroy(data);

            /* the pipeline is fragmented;
             * fail (pipeline remains Lock()ed) */
            RWERROR((E_RX_FRAGMENTEDPIPELINE));
            RWRETURN((RxPipeline *)NULL);
        }
    }

    /* Sort nodes so that dependencies always
     * go in the same direction */
    PipelineTopSort(data, pipeline->entryPoint);

    /* Now we moved all the nodes about,
     * update the outputs so they
     * still point to the right place.
     */
    /* Note that we do this even if the sort has failed -
     * preserves the integrity of the graph, boyo
     */
    PipelineUpdateOutputIndices(data);

    /* cycle-free graph? */
    for (i = 0; i < pipeline->numNodes; i++)
    {
        /* If a cycle is present, not all the
         * nodes will be visited along all their
         * inputs during the PipelineTopSort */
        if (pipeline->nodes[i].topSortData->numIns !=
            pipeline->nodes[i].topSortData->numInsVisited)
        {
            _CleanUpNodesAfterFailedTopSort(pipeline);
            _TopSortDataDestroy(data);

            /* pipeline had cycles;
             * fail (pipeline remains Lock()ed, partially sorted) */
            RWERROR((E_RX_CYCLICPIPELINE));
            RWRETURN((RxPipeline *)NULL);
        }
    }

    /* realloc() to free space resulting from NULL slots
     * or slots that don't appear in graph */
    if (pipeline->numNodes != data->nodesarrayslot)
    {
        RwUInt32            newSize =

            data->nodesarrayslot * sizeof(RxPipelineNode);
        RxPipelineNode     *newNodes;

        newNodes =
            (RxPipelineNode *) RwRealloc(pipeline->nodes, newSize);
        if (newNodes)
        {
            pipeline->nodes = newNodes;
            pipeline->numNodes = data->nodesarrayslot;
        }
    }

    _TopSortDataDestroy(data);

    pipeline->entryPoint = 0;

    RWRETURN(pipeline);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeCloneDefinition 
 * creates a private copy of the \ref RxNodeDefinition 
 * of a given an \ref RxPipelineNode, so that the
 * definition can be modified. It optionally adds a new cluster_of_interest.
 *
 * The same cluster may not appear twice in a node's clusters_of_interest
 * array, so it is an error to add a cluster that is already present.
 *
 * This function is generally used by nodes to assist in the provision of
 * construction-time "customization" APIs.
 *
 * \param node  A pointer to the source pipeline node
 * \param cluster2add  An optional pointer to
 * a cluster definition to add
 * \return A pointer to the clone node definition.
 *
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeRequestCluster
 * \see RxPipelineNodeReplaceCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxNodeDefinition   *
RxPipelineNodeCloneDefinition(RxPipelineNode * node,
                              RxClusterDefinition * cluster2add)
{
    /* API-grade wrapper for internal function _NodeClone() */

    RxNodeDefinition   *result = (RxNodeDefinition *)NULL;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodeCloneDefinition"));

    RWASSERT(node != NULL);

    if (node != NULL)
    {
        if (cluster2add != NULL)
        {
            RwUInt32            clusterindex;
            RwBool              clusteralreadypresent;

            clusterindex = IoSpecSearchForCluster(&node->nodeDef->io,
                                                  cluster2add);

            clusteralreadypresent = (clusterindex != ((RwUInt32) - 1));

            RWASSERT(!clusteralreadypresent);

            if (clusteralreadypresent)
            {
                goto Exit;
            }
        }

        result = _NodeClone(node, cluster2add);
    }

  Exit:
    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeFindOutputByName 
 * searches through the node's outputs, 
 * as defined in its struct RxNodeDefinition, returning a handle to the
 * requested output - requested with a string in the \ref RxNodeDefinition .
 * This handle can be used with \ref RxLockedPipeAddPath, etc.
 *
 * \param node  A pointer to a pipeline node
 * \param outputname  A pointer to a string identifying an output of the node
 *
 * \return A \ref RxNodeOutput handle for the output on success,
 * NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RwUInt32           *
RxPipelineNodeFindOutputByName(RxPipelineNode * node,
                               const char *outputname)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeFindOutputByName"));

    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));
    RWASSERT(outputname != NULL);

    if (node != NULL && ISNODELIVE(node) && outputname != NULL)
    {
        int                 n;
        int                 numouts = node->nodeDef->io.numOutputs;
        RxOutputSpec       *out = node->nodeDef->io.outputs;

        for (n = 0, out = node->nodeDef->io.outputs;
             n < numouts; n++, out++)
        {
            if (strcmp(out->name, outputname) == 0)
            {
                RWRETURN(&node->outputs[n]);
            }
        }
    }

    RWRETURN((RwUInt32 *) NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeFindOutputByIndex 
 * searches through the node's
 * outputs, as defined in its \ref RxNodeDefinition, returning a handle
 * to the requested output (requested with an index into the node's array
 * of outputs, in the order defined in the node's \ref RxNodeDefinition).
 * This handle can be used with \ref RxLockedPipeAddPath, etc.
 *
 * \param node  A pointer to a pipeline node
 * \param outputindex  The index of the output to retrieve
 *
 * \return A \ref RxNodeOutput handle for the output on success,
 * NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RwUInt32           *
RxPipelineNodeFindOutputByIndex(RxPipelineNode * node,
                                RwUInt32 outputindex)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeFindOutputByIndex"));

    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));
    RWASSERT(outputindex < node->numOutputs);

    if (node != NULL && ISNODELIVE(node)
        && outputindex < node->numOutputs)
    {
        RWRETURN(&node->outputs[outputindex]);
    }

    RWRETURN((RwUInt32 *) NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeFindInput 
 * returns a handle to the node's input,
 * suitable for use with \ref RxLockedPipeAddPath, etc.
 *
 * \param node  A pointer to a pipeline node
 *
 * \return A \ref RxNodeInput handle for the node's input on success,
 * NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipelineNode     *
RxPipelineNodeFindInput(RxPipelineNode * node)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeFindInput"));

    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));

    if (node != NULL && ISNODELIVE(node))
    {
        RWRETURN(node);
    }

    RWRETURN((RxPipelineNode *) NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeRequestCluster 
 * causes a node to request a cluster.
 *
 * When pipelines are unlocked, a requirements/data flow analysis is
 * performed within the pipeline, which is intended to validate the
 * pipeline and eliminate redundant effort.
 *
 * This analysis can not, of itself, factor in the requirements of any
 * pipelines to which this pipeline might dispatch (such dispatches are
 * dependent on the execution-time behaviour of node body methods).
 *
 * For this reason, it is important that terminal nodes which dispatch to
 * other pipelines require (in their \ref RxNodeDefinition's ) the clusters
 * that the destination pipelines will require. Without this, it might be
 * determined that certain clusters are no longer required, and they may
 * be terminated before packets reach the dispatching node.
 *
 * \ref RxPipelineNodeRequestCluster causes the node to request a cluster.
 * A copy of the node's \ref RxNodeDefinition is made, and this is edited,
 * so other RxPipelineNodes instanced from the same RxNodeDefinition are
 * not impacted. The cluster is always requested as rxCLREQ_OPTIONAL
 * because it is more flexible than rxCLREQ_REQUIRED. If this function
 * is used and the cluster is not present, rxCLREQ_REQUIRED would cause
 * dependency chasing to fail, whereas with rxCLREQ_OPTIONAL no harm is
 * done.
 *
 * This function should be called prior to unlocking the containing pipeline.
 *
 * \param pipeline  A pointer to the pipeline to act upon.
 * \param node  A pointer to the node to request the specified cluster.
 * \param clusterDef  A pointer to the cluster definition of the cluster to request.
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 * The function fails if the node already requests the cluster.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxPipelineNodeRequestCluster(RxPipeline * pipeline,
                             RxPipelineNode * node,
                             RxClusterDefinition * clusterDef)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeRequestCluster"));

    RWASSERT(pipeline && (pipeline->locked));
    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));
    RWASSERT(clusterDef != NULL);

    if ( pipeline && 
         pipeline->locked &&
         node != NULL && 
         ISNODELIVE(node) &&
         clusterDef != NULL )
    {
        RxIoSpec           *iospec = &node->nodeDef->io;
        RwUInt32            i;

        /* loop over node's clusters of interest */
        for (i = 0; i < iospec->numClustersOfInterest; i++)
        {
            if (iospec->clustersOfInterest[i].clusterDef == clusterDef)
            {
                RWRETURN((RxPipeline *)NULL); /* fail if node already handles cluster */
            }
        }

        {
            RxNodeDefinition   *nodespec;

            /* Clone and replace the old nodeDef */
            nodespec = _NodeClone(node, clusterDef);

            if (nodespec != NULL) /* created clone with new cluster...? */
            {
                /* fix up a [couple of] fields in cloned nodespec */
                nodespec->io.inputRequirements[nodespec->
                                               io.numClustersOfInterest
                                               - 1] = rxCLREQ_OPTIONAL;

                RWRETURN(pipeline);
            }
        }
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeReplaceCluster 
 * replaces a cluster in a
 * pipeline node during pipeline definition.
 *
 * This can be used in multipass pipelines, which use the same
 * node several times and want to pass in a different cluster to each
 * instance (a different set of RxUVs or a different RxMeshStateVector,
 * for instance).
 *
 * This function can also be used when the node designer has no means of
 * knowing what a cluster will be called (e.g. a node which simply copies
 * the contents of 'a cluster' into memory somewhere - it reads stride at
 * run-time and can potentially work on any cluster); a place-holder
 * cluster (&clusterPlaceHolder) is then used, which is invariably
 * replaced during pipeline definition.
 *
 * \param pipeline  A pointer to the target pipeline
 * \param node  A pointer to the target node
 * \param oldClusterDef  A pointer to the cluster definition of the cluster to be removed
 * \param newClusterDef  A pointer to the cluster definition of the cluster to be added
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxPipelineNodeReplaceCluster(RxPipeline * pipeline,
                             RxPipelineNode * node,
                             RxClusterDefinition * oldClusterDef,
                             RxClusterDefinition * newClusterDef)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeReplaceCluster"));

    RWASSERT(pipeline && (pipeline->locked));
    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));
    RWASSERT(oldClusterDef != NULL);
    RWASSERT(newClusterDef != NULL);

    if (pipeline && 
        pipeline->locked &&
        node != NULL && 
        ISNODELIVE(node) && 
        oldClusterDef != NULL &&
        newClusterDef != NULL)
    {
        RxIoSpec           *iospec = &node->nodeDef->io;
        RwUInt32            i;

        /* loop over node's clusters of interest */
        for (i = 0; i < iospec->numClustersOfInterest; i++)
        {
            if (iospec->clustersOfInterest[i].clusterDef ==
                oldClusterDef)
            {
                break;
            }
        }

        if (i != iospec->numClustersOfInterest) /* a match...? */
        {
            RxNodeDefinition   *nodespec;

            /* Clone and replace the old nodeDef */
            nodespec = _NodeClone(node, (RxClusterDefinition *)NULL);

            if (nodespec != NULL) /* cloned okay...? */
            {
                /* fix up one more field... */
                nodespec->io.clustersOfInterest[i].clusterDef =
                    newClusterDef;

                RWRETURN(pipeline);
            }
        }
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeCreateInitData 
 * creates space for initialization
 * data for a pipeline node.
 *
 * Initialization data can be supplied before a pipeline is unlocked, so
 * that the pipeline node initialization function has some data to work
 * with when it is called at unlock time.
 *
 * This function creates space for initialization data in a node and
 * returns a pointer to that space so that the application can fill it.
 * It creates the space locally so that it can safely be freed when the
 * node is destroyed. If this function is called subsequently, any prior
 * data will be freed.
 *
 * Initialization data was introduced to support \ref RxPipelineClone.
 * It is used to 'remember' the effects of pipeline node setup
 * API functions, such that a pipeline node's \ref RxPipelineNodeInitFn
 * may use it to perform automatic initialization of the node in a clone
 * pipeline.
 *
 * \param node  A pointer to a pipeline node
 * \param size  The size (size > 0) of the initialization data
 *
 * \return a pointer to the space for the initialization data on success,
 * NULL otherwise.
 *
 * \see RxPipelineNodeGetInitData
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
void               *
RxPipelineNodeCreateInitData(RxPipelineNode * node, RwUInt32 size)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeCreateInitData"));

    RWASSERT(node != NULL);
    RWASSERT(size > 0);

    if (node->topSortData->initializationData != NULL)
    {
        RwFree(node->topSortData->initializationData);
        node->topSortData->initializationData = NULL;
        node->topSortData->initializationDataSize = 0;
    }

    node->topSortData->initializationData = RwMalloc(size);
    if (node->topSortData->initializationData == NULL)
    {
        RWERROR((E_RW_NOMEM, size));
        RWRETURN(NULL);
    }
    node->topSortData->initializationDataSize = size;

    RWRETURN(node->topSortData->initializationData);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeGetInitData 
 * gets the initialization data pointer
 * of a pipeline node.
 *
 * Initialization data may be supplied before a pipeline is unlocked, so
 * that the pipeline node initialization function has some data to work
 * with when it is called.
 *
 * When the pipeline node is destroyed (this occurs when its containing
 * pipeline is destroyed or the node removed from it) or the data is
 * replaced (this occurs whenever \ref RxPipelineNodeCreateInitData is called),
 * the data is freed.
 *
 * \param node  A pointer to a pipeline node
 *
 * \return A pointer to the initialization data on success, NULL otherwise.
 *
 * \see RxPipelineNodeCreateInitData
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
void               *
RxPipelineNodeGetInitData(RxPipelineNode * node)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeGetInitData"));

    RWASSERT(node != NULL);

    RWRETURN(node->topSortData->initializationData);
}


/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeUnlock 
 * unlocks a pipeline (switches, in effect,
 * pipeline state from EDITABLE to EXECUTABLE).
 *
 * Pipeline unlocking is computationally intensive (see
 * \ref RxPipelineExecute), and can fail if it is determined that the
 * requirements of all nodes cannot be met by the nodes feeding them.
 *
 * \param pipeline  A pointer to a locked pipeline.
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 */
RxPipeline         *
RxLockedPipeUnlock(RxPipeline * pipeline)
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeUnlock"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);

    if (pipeline != NULL && 
        pipeline->locked )
    {
        if (pipeline->numNodes != 0)
        {
            RWASSERT(pipeline->entryPoint < pipeline->numNodes);
            RWASSERT(ISNODELIVE
                     (&pipeline->nodes[pipeline->entryPoint]));

            if (!(pipeline->entryPoint < pipeline->numNodes &&
                  ISNODELIVE(&pipeline->nodes[pipeline->entryPoint])))
            {
                /* entry point is invalid;
                 * fail (pipeline remains Lock()ed) */
                RWERROR((E_RX_INVALIDENTRYPOINT));
                RWRETURN((RxPipeline *)NULL);
            }

            pipeline = PipelineUnlockTopSort(pipeline);
            if (NULL == pipeline)
                RWRETURN((RxPipeline *)NULL);

            if (_rxChaseDependencies(pipeline) != 0)
            {
                /* dep. chasing failed;
                 * fail (pipeline remains Lock()ed, sorted) */
                RWRETURN((RxPipeline *)NULL);
            }

            RXCHECKFORUSERTRAMPLING(pipeline);

            {
                RwUInt32            n;

                /* init & config method calls flow from bottom to
                 * top (i.e. consumer -> producer) */

                n = pipeline->numNodes - 1;
                do
                {
                    RxNodeDefinition   *node =

                        pipeline->nodes[n].nodeDef;

                    /* nodeinit (called first time node is used in an
                     * application) */
                    if (node->InputPipesCnt++ == 0)
                    {
                        if (node->nodeMethods.nodeInit != NULL)
                        {
                            if (node->nodeMethods.nodeInit(node) ==
                                FALSE)
                            {
                                goto fail;
                            }
                        }
                    }

                    /* pipelinenodeinit */
                    if (node->nodeMethods.pipelineNodeInit != NULL)
                    {
                        if (node->
                            nodeMethods.pipelineNodeInit(&pipeline->
                                                         nodes[n]) ==
                            FALSE)
                        {
                            goto fail;
                        }
                    }
                }
                while (n--);

                n = pipeline->numNodes - 1;
                do
                {
                    RxNodeDefinition   *node =

                        pipeline->nodes[n].nodeDef;

                    /* pipelinenodeconfig */
                    if (node->nodeMethods.pipelineNodeConfig != NULL)
                    {
                        if (node->
                            nodeMethods.pipelineNodeConfig(&pipeline->
                                                           nodes[n],
                                                           pipeline) ==
                            FALSE)
                        {
                            goto fail;
                        }
                    }
                }
                while (n--);
            }

            RXCHECKFORUSERTRAMPLING(pipeline);
        }

        pipeline->locked = FALSE;

        RWRETURN(pipeline);    /* succeed */
    }

    if (pipeline == NULL)
    {
        RWERROR((E_RW_NULLP));
    }
    else
    {
        RWERROR((E_RX_UNLOCKEDPIPE));
    }

    RWRETURN((RxPipeline *)NULL);            /* no pipeline or not locked; fail */

  fail:

    if (pipeline->superBlock != NULL)
    {
        /* created by _rxChaseDependencies() */
        RwFree(pipeline->superBlock);
        pipeline->superBlock = (RwUInt32 *)NULL;
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineLock 
 * locks a pipeline (switches, in effect,
 * pipeline state from EXECUTABLE to EDITABLE).
 *
 * \param pipeline  A pointer to an unlocked pipeline.
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxPipelineLock(RxPipeline * pipeline)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineLock"));

    RWASSERT(pipeline != NULL);

    if (pipeline->locked == FALSE)
    {
        pipeline->locked = TRUE;
        if (pipeline->nodes != NULL)
        {
            RwUInt32            n;

            for (n = 0; n < pipeline->numNodes; n++)
            {
                const RxNodeMethods *const nodeMethods =
                    &pipeline->nodes[n].nodeDef->nodeMethods;

                /* pipelinenodeterm */
                if (nodeMethods->pipelineNodeTerm != NULL)
                {
                    nodeMethods->pipelineNodeTerm(&pipeline->nodes[n]);
                }

                /* nodeterm (called when no more UNLOCKED pipeline nodes
                 * reference this node def) */
                if (--(pipeline->nodes[n].nodeDef->InputPipesCnt) == 0)
                {
                    if (nodeMethods->nodeTerm != NULL)
                    {
                        nodeMethods->nodeTerm(pipeline->
                                              nodes[n].nodeDef);
                    }
                }

                /* honour the promise in the field description (qv) */
                pipeline->nodes[n].slotClusterRefs = (RxPipelineCluster **)NULL;
            }
        }

        if (pipeline->superBlock != NULL)
        {
            RwFree(pipeline->superBlock);
            pipeline->superBlock = (RwUInt32 *)NULL;
        }
    }

    RWRETURN(pipeline);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineClone "deep copies" an unlocked pipeline; the
 * resultant copy may then form the basis for subsequent edits.
 *
 * Cloning a pipeline is non-trivial. This is because the nodes
 * within a pipeline that was created in external code will have
 * been set up by unknown API calls. Initialization data (see
 * \ref RxPipelineNodeCreateInitData) is used to 'remember' the
 * effects of pipeline node setup API functions, such that a
 * pipeline node's \ref RxPipelineNodeInitFn may use it to
 * perform automatic initialization of the node in a clone
 * pipeline.
 *
 * The use of this function is no longer recommended. It may be
 * removed from the library in subsequent versions.
 *
 * \param pipeline  A pointer to the unlocked pipeline to clone
 *
 * \return A pointer to the clone on success, otherwise NULL.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxPipelineClone(RxPipeline * pipeline)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineClone"));

    /* "deep" copy a pipeline */

    RWASSERT((pipeline != NULL) && (pipeline->locked == FALSE));

    if ((pipeline != NULL) && (pipeline->locked == FALSE))
    {
        RxPipeline         *clonepipe;

        clonepipe = RxPipelineCreate();

        if (clonepipe != NULL)
        {
            if (pipeline->numNodes == 0)
            {
                RWRETURN(clonepipe);
            }

            RxPipelineLock(clonepipe);

            /* Oi!! TODO: this should allocate with the same alignment as for
             * the nodes array in RxPipelineAddFragment() !! */
            clonepipe->nodes =
                (RxPipelineNode *) RwMalloc(pipeline->numNodes *
                                            sizeof(RxPipelineNode));
            clonepipe->nodesBlock = clonepipe->nodes;

            if (clonepipe->nodes != NULL)
            {
                RwBool              Error = FALSE;

                Error = PipelineCopy(clonepipe, pipeline);

                if ((Error == FALSE)
                    && (RxLockedPipeUnlock(clonepipe) != NULL))
                {
                    RWRETURN(clonepipe);
                }
            }

            _rxPipelineDestroy(clonepipe);
        }
    }

    RWRETURN((RxPipeline *) NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineFindNodeByName
 *  finds the first PipelineNode
 * in a given pipeline which has a given name specified in its
 * \ref RxNodeDefinition. 
 * If a non-NULL reference to a \ref RxPipelineNode
 * within the pipeline is passed in, the search starts immediately after
 * that pipeline node. If a non-NULL reference to a \ref RwInt32 index is
 * passed in, it will be filled, on success, with the index of the
 * pipeline node. On failure it will be filled with -1.
 *
 * Pipeline node pointers are not valid across an
 * \ref RxLockedPipeUnlock, as this function causes the nodes to be
 * re-ordered.
 *
 * \param pipeline  A pointer to a pipeline to search.
 * \param name  A pointer to a string to search with.
 * \param start  An optional pointer to a pipeline node to start the search after.
 * \param index  An optional reference to an index to put the pipeline node's index into.
 *
 * \return A pointer to the found pipeline node on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipelineNode     *
RxPipelineFindNodeByName(RxPipeline * pipeline,
                         const RwChar * name,
                         RxPipelineNode * start, RwInt32 * index)
{
    RwBool              check;

    RWAPIFUNCTION(RWSTRING("RxPipelineFindNodeByName"));

    RWASSERT(pipeline != NULL);
    RWASSERT(name != NULL);

    check = (pipeline != NULL) && (name != NULL)
        && (pipeline->numNodes);

    if (check != 0)
    {
        RxPipelineNode     *node = &pipeline->nodes[0];
        RwInt32             n = pipeline->numNodes;

        /* Search from just after a given pipelinenode -
         * allows finding all the nodes within
         * a given pipeline with the specified nodedefinition */
        if (start)
        {
            while ((node != start) && (n > 0))
            {
                node++;
                n--;
            }
            node++;
            n--;
        }

        /* loop over pipeline nodes */
        while (n > 0)
        {
            if (ISNODELIVE(node))
            {
                if (!rwstrcmp(node->nodeDef->name, name))
                {
                    if (index != NULL)
                        *index = n;
                    RWRETURN(node);
                }
            }
            node++;
            n--;
        }
    }

    /* Nope, didn't see it missus */
    if (index != NULL)
        *index = -1;
    RWRETURN((RxPipelineNode *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineFindNodeByIndex
 *  returns the node in a pipeline at
 * index "nodeindex". Indices increment from zero with the first fragment
 * through each subsequent pipeline node added.
 *
 * Neither pipeline node indices nor pipeline node pointers are valid
 * across an \ref RxLockedPipeUnlock, as this function causes the pipeline
 * nodes to be re-ordered.
 *
 * \param pipeline  A pointer to the target pipeline
 * \param nodeindex  The index of the pipeline node.
 *
 * \return A pointer to the pipeline node on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipelineNode     *
RxPipelineFindNodeByIndex(RxPipeline * pipeline, RwUInt32 nodeindex)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineFindNodeByIndex"));

    RWASSERT(pipeline != NULL);
    RWASSERT(nodeindex < pipeline->numNodes);

    RWRETURN((pipeline != NULL
              && nodeindex <
              pipeline->numNodes) ? &pipeline->
             nodes[nodeindex] : (RxPipelineNode *) NULL);
}


#define PIPELINENODEALIGNMENT (32U) /* (64U) */

#define PNAL (PIPELINENODEALIGNMENT - 1)

/* Only works for {2^n - 1} _align */

#define _PTRALIGN(_ptr, _type, _align) \
   (_ptr = (_type *) ((((RwUInt32)_ptr) + (_align)) & ~(_align)) )

#define _PTRINC(_ptr, _type, _numbytes) \
   (_ptr = (_type *)(((RwUInt8 *)_ptr) + (_numbytes)) )

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeAddFragment 
 * adds a fragment to a locked pipeline.
 *
 * A fragment is a chain of nodes, interconnected on their first (default)
 * outputs.
 *
 * Constructing a pipeline is typically a process of adding a number of
 * fragments (describing linear sections of the pipeline graph), and then
 * adding additional paths between fragments using \ref RxLockedPipeAddPath.
 *
 * \param pipeline  A pointer to a locked pipeline.
 * \param firstIndex  An optional pointer to a RwUInt32 to recieve the index of the first added node.
 * \param nodeDef0  Pointer(s) to node definitions: variable length, NULL terminated.
 *
 * \return NULL on failure, non-NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */

RxPipeline         *
RxLockedPipeAddFragment(RxPipeline * pipeline,
                        RwUInt32 * firstIndex,
                        RxNodeDefinition * nodeDef0, ...)
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeAddFragment"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);

    if (pipeline != NULL && pipeline->locked != FALSE)
    {
        va_list             va;
        RxNodeDefinition   *nodeDef;
        RwInt32             fraglength = 0;

        va_start(va, nodeDef0);
        for (nodeDef = nodeDef0; nodeDef != NULL;
             nodeDef = va_arg(va, RxNodeDefinition *))
        {
            fraglength++;
        }
        va_end(va);

        if (fraglength != 0)
        {
            RxPipelineNode     *allocresult;
            RwUInt32            newSize = PNAL +
                sizeof(RxPipelineNode) *

                (pipeline->numNodes + fraglength);

            allocresult = (RxPipelineNode *) RwMalloc(newSize);

            if (allocresult != NULL)
            {
                RwUInt32            oldnumnodes;
                RwInt32             n;
                RxPipelineNode     *prevnode = (RxPipelineNode *)NULL;
                RxPipelineNode     *oldNodes = pipeline->nodes;
                void               *oldResult = pipeline->nodesBlock;

                oldnumnodes = pipeline->numNodes;

                pipeline->numNodes += fraglength;

                pipeline->nodesBlock = allocresult;
                pipeline->nodes =
                    _PTRALIGN(allocresult, RxPipelineNode, PNAL);
                pipeline->nodes = allocresult;

                if (oldResult != NULL)
                {
                    /* Copying across the old nodes (including alloc'd
                     * members, note) must be done carefully. Padding
                     * due to alignment may have changed */
                    memcpy(pipeline->nodes,
                           oldNodes,
                           oldnumnodes * sizeof(RxPipelineNode));
                    RwFree(oldResult);
                }

                n = 0;
                va_start(va, nodeDef0);
                for (nodeDef = nodeDef0; nodeDef != NULL;
                     nodeDef = va_arg(va, RxNodeDefinition *))
                {
                    RxPipelineNode     *node =
                        &pipeline->nodes[oldnumnodes + n];

                    if (!_NodeCreate(node, nodeDef))
                    {
                        break;
                    }
                    if (prevnode != NULL)
                    {
                        /* connect to previous node */

                        if (!RxLockedPipeAddPath
                            (pipeline,
                             RxPipelineNodeFindOutputByIndex(prevnode,
                                                             0),
                             RxPipelineNodeFindInput(node)))
                        {
                            NodeDestroy(node);

                            break;
                        }
                    }

                    prevnode = node;

                    n++;
                }
                va_end(va);

                if (n == fraglength)
                {
                    if (firstIndex != 0)
                    {
                        *firstIndex = oldnumnodes;
                    }
                    RWRETURN(pipeline); /* success */
                }
                else
                {
                    RwDebugSendMessage(rwDEBUGMESSAGE,
                                       "RxLockedPipeAddFragment",
                                       "Failed to create and link all nodes in new fragment");

                    pipeline->numNodes = oldnumnodes;

                    /* fragment construction did not complete: unwind */

                    while (n--)
                    {
                        NodeDestroy(&pipeline->nodes
                                    [pipeline->numNodes + n]);
                    }
                }
            }
            else
            {
                RWERROR((E_RW_NOMEM, newSize));
            }
        }
        else
        {
            RwDebugSendMessage(rwDEBUGMESSAGE,
                               "RxLockedPipeAddFragment",
                               "No nodes specified to add!");
        }
    }
    else
    {
        if (pipeline == NULL)
        {
            RWERROR((E_RW_NULLP));
        }
        else
        {
            RWERROR((E_RX_UNLOCKEDPIPE));
        }
    }

    RWRETURN((RxPipeline *)NULL);            /* failure */
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeReplaceNode 
 * replaces an existing pipeline node
 * with a new pipeline node instanced from a specified node definition.
 * Paths into and out of the node are retained to the greatest extent
 * possible.
 *
 * Pipeline nodes can be located within pipelines using
 * \ref RxPipelineFindNodeByName or \ref RxPipelineFindNodeByIndex.
 *
 * \param pipeline  A pointer to a locked pipeline
 * \param node  A pointer to the pipeline node to be replaced
 * \param nodeDef  A pointer to the node definition of the pipeline node to be added
 *
 * \return A pointer to the pipeline added.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxLockedPipeReplaceNode(RxPipeline * pipeline,
                        RxPipelineNode * node,
                        RxNodeDefinition * nodeDef)
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeReplaceNode"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);
    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));
    RWASSERT(nodeDef != NULL);

    if (pipeline != NULL && 
        pipeline->locked && 
        node != NULL &&
        ISNODELIVE(node) && 
        nodeDef != NULL)
    {
        RxPipelineNode      newnode;

        /* construct new node */

        if (!_NodeCreate(&newnode, nodeDef))
        {
            RWRETURN((RxPipeline *)NULL);
        }

        /* copy output assignments from predecessor */

        {
            RwUInt32            commonouts =
                (newnode.numOutputs < node->numOutputs) ?
                newnode.numOutputs : node->numOutputs;

            if (commonouts > 0)
            {
                memcpy(newnode.outputs,
                       node->outputs, commonouts * sizeof(RwUInt32));
            }
        }

        NodeDestroy(node);

        *node = newnode;

        RWRETURN(pipeline);
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeDeleteNode 
 * deletes the specified node from a locked pipeline.
 *  Paths into and out of the node are deleted.
 *
 * \param pipeline  A pointer to a locked pipeline
 * \param node  A pointer to the pipeline node to be deleted
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxLockedPipeDeleteNode(RxPipeline * pipeline, RxPipelineNode * node)
{
    RwBool              deletable;

    RWAPIFUNCTION(RWSTRING("RxLockedPipeDeleteNode"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);
    RWASSERT(node != NULL);
    RWASSERT(ISNODELIVE(node));

    deletable = (pipeline != NULL &&
                 pipeline->locked &&
                 node != NULL && 
                 ISNODELIVE(node));

    if (deletable)
    {
        NodeDestroy(node);

        /* traverse pipeline & remove all references
         * to (delete all paths to) this node
         */

        PipelinePathsReplaceOutputIndex(pipeline,
                                        PipelineNode2Index
                                        (pipeline, node),
                                        (RwUInt32) - 1);

        RWRETURN(pipeline);
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeSetEntryPoint 
 * sets a locked pipeline's entry point - 
 * that is, the index of node that forms the head of the pipeline
 * graph, where pipeline processing will commence. After
 * \ref RxLockedPipeUnlock, the specified node will be the first node in the
 * flattened node array.
 *
 * Note that you should not normally need to call this function. Each
 * pipeline has one entry point and dependency-chasing should identify
 * it unambiguously. This allows the decision to be overriden.
 *
 * \param pipeline  A pointer to a locked pipeline
 * \param in  A pointer to the pipeline node to set as the pipeline's entry point
 *
 * \return A pointer to the input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxLockedPipeSetEntryPoint(RxPipeline * pipeline, RxPipelineNode * in)
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeSetEntryPoint"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);
    RWASSERT(in != NULL);
    RWASSERT(ISNODELIVE(in));

    if (pipeline != NULL && pipeline->locked && in != NULL
        && ISNODELIVE(in))
    {
        RwUInt32            index = PipelineNode2Index(pipeline, in);

        if (index != (RwUInt32) - 1)
        {
            pipeline->entryPoint = index;

            RWRETURN(pipeline);
        }
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeGetEntryPoint 
 * gets the locked pipeline's entry
 * point - that is, the pipeline node that forms the head of the pipeline
 * graph, where pipeline processing will commence. After
 * \ref RxLockedPipeUnlock, the specified node will be the first node in the
 * flattened node array.
 *
 * \param pipeline  A pointer to a locked pipeline
 *
 * \return A pointer to the entry-point pipeline node on success,
 * NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipelineNode     *
RxLockedPipeGetEntryPoint(RxPipeline * pipeline)
{
    RxPipelineNode     *entryNode;

    RWAPIFUNCTION(RWSTRING("RxLockedPipeGetEntryPoint"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);

    entryNode = (RxPipelineNode *)NULL;

    if (pipeline != NULL && pipeline->locked)
    {
        if (pipeline->entryPoint != (RwUInt32) - 1)
        {
            entryNode = RxPipelineFindNodeByIndex(pipeline,
                                                  pipeline->entryPoint);
        }
    }

    RWRETURN(entryNode);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeAddPath 
 * adds a path in the specified locked pipeline, 
 * between the given \ref RxNodeOutput and the given \ref RxNodeInput.
 *
 * \param pipeline  A pointer to a locked pipeline
 * \param out A reference to a pipeline node output
 * \param in A reference to a pipeline node input
 *
 * \return Input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxLockedPipeAddPath(RxPipeline * pipeline, RwUInt32 * out, /* A reference to a pipeline node output */
                    RxPipelineNode * in) /* A reference to a pipeline node input */
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeAddPath"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);
    RWASSERT(out != NULL);
    RWASSERT(*out == (RwUInt32) - 1);
    RWASSERT(in != NULL);
    RWASSERT(ISNODELIVE(in));

    if (pipeline != NULL && pipeline->locked &&
        out != NULL && *out == (RwUInt32) - 1 &&
        in != NULL && ISNODELIVE(in))
    {
        RwUInt32            index = PipelineNode2Index(pipeline, in);

        if (index != (RwUInt32) - 1)
        {
            *out = index;

            RWRETURN(pipeline);
        }
    }

    if (!ISNODELIVE(in))
    {
        RWMESSAGE(("Node has been deleted from pipeline"));
    }
    if (*out != (RwUInt32) - 1)
    {
        RWMESSAGE(
                  ("Path already in use, use RxLockedPipeDeletePath()"));
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxLockedPipeDeletePath 
 * deletes a path in the specified
 * locked pipeline, between the given \ref RxNodeOutput and the given
 * \ref RxNodeInput.
 *
 * \param pipeline  A pointer to a locked pipeline
 * \param out A reference to a pipeline node output
 * \param in A reference to a pipeline node input
 *
 * \return A pointer to the pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxPipelineInsertDebugNode
 * \see RxLockedPipeDeleteNode
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxLockedPipeDeletePath(RxPipeline * pipeline, RwUInt32 * out, /* A reference to a pipeline node output */
                       RxPipelineNode * in) /* A reference to a pipeline node input */
{
    RWAPIFUNCTION(RWSTRING("RxLockedPipeDeletePath"));

    RWASSERT(pipeline != NULL);
    RWASSERT(pipeline->locked);
    RWASSERT(out != NULL);
    RWASSERT(in != NULL);
    RWASSERT(&pipeline->nodes[*out] == in);

    if (pipeline != NULL && pipeline->locked && out != NULL
        && in != NULL && &pipeline->nodes[*out] == in)
    {
        *out = (RwUInt32) - 1;

        RWRETURN(pipeline);
    }

    RWRETURN((RxPipeline *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineInsertDebugNode 
 * inserts a 'debug' pipeline node
 * inbetween two specified pipeline nodes.
 *
 * For all clusters active in either of the two specified pipeline
 * nodes, the added pipeline node will have rxCLREQ_REQUIRED specified
 * for them on input and rxCLVALID_VALID on output.
 *
 * Debug nodes are constructed to monitor traffic by "packet sniffing".
 *
 * \param pipeline  A pointer to an unlocked pipeline
 * \param before  A pointer to a pipeline node to feed into the debug node
 * \param after  A pointer to a pipeline node for the debug node to feed into
 * \param debugNodeDef  A pointer to the node definition for the debug node
 *
 * \return Input pipeline on success, NULL otherwise.
 *
 * \see RxPipelineNodeCloneDefinition
 * \see RxPipelineNodeFindInput
 * \see RxPipelineNodeFindOutputByIndex
 * \see RxPipelineNodeFindOutputByName
 * \see RxPipelineNodeReplaceCluster
 * \see RxPipelineNodeRequestCluster
 * \see RxLockedPipeAddFragment
 * \see RxLockedPipeAddPath
 * \see RxPipelineClone
 * \see RxLockedPipeDeleteNode
 * \see RxLockedPipeDeletePath
 * \see RxPipelineFindNodeByIndex
 * \see RxPipelineFindNodeByName
 * \see RxLockedPipeGetEntryPoint
 * \see RxPipelineLock
 * \see RxLockedPipeReplaceNode
 * \see RxLockedPipeSetEntryPoint
 * \see RxLockedPipeUnlock
 */
RxPipeline         *
RxPipelineInsertDebugNode(RxPipeline * pipeline,
                          RxPipelineNode * before,
                          RxPipelineNode * after,
                          RxNodeDefinition * debugNodeDef)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineInsertDebugNode"));

    RWASSERT(pipeline);
    RWASSERT(before || after);
    RWASSERT(debugNodeDef);
    if ((pipeline != NULL) && ((before != NULL) || (after != NULL))
        && (debugNodeDef != NULL))
    {
        RxLockedPipe       *lpipe = (RxPipeline *)NULL;
        RxPipelineNode     *debugNode = (RxPipelineNode *)NULL;
        RxClusterDefinition **clusterRefs = (RxClusterDefinition **)NULL;
        RwUInt32            i, j, numClustersToRequest;

        /* after is the 'outputNum'th output of before :) */
        RwUInt32            outputNum = (RwUInt32) - 1;

        /* index of after in the pipeline's pipelinenode array */
        RwUInt32            afterNum = (RwUInt32) - 1;

        /* index of the debug node after it's been added */
        RwUInt32            debugNum = (RwUInt32) - 1;

        if (before == NULL)
        {
            if (after != (&pipeline->nodes[0]))
            {
                RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                                   "before node NULL, but after node not the start of the pipeline");
                RWRETURN(FALSE);
            }
        }
        else
        {
            /* Ensure before is a member of this pipeline */
            if (PipelineNode2Index(pipeline, before) == (RwUInt32) ~ 0)
            {
                RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                                   "before node not a member of specified pipeline!");
                RWRETURN(FALSE);
            }
        }

        if (after == NULL)
        {
            if (before->numOutputs != 0)
            {
                RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                                   "after node NULL, but before node not a terminal node");
                RWRETURN(FALSE);
            }
        }
        else
        {
            /* Find after's position in pipeline's array of pipeline
             * nodes and cache it in afterNum
             */
            afterNum = PipelineNode2Index(pipeline, after);
            if (afterNum == (RwUInt32) - 1)
            {
                RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                                   "after node not a member of specified pipeline!");
                RWRETURN(FALSE);
            }
        }

        if (before && after)
        {
            /* Find which output of before outputs to after and
             * cache it in outputNum */
            for (i = 0; i < before->numOutputs; i++)
            {
                if (before->outputs[i] == afterNum)
                {
                    outputNum = i;
                    i = before->numOutputs;
                }
            }
            if (outputNum == (RwUInt32) - 1)
            {
                RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                                   "after node not an output of before node");
                RWRETURN(FALSE);
            }
        }

        /* Find out which clusters are active during before and after
         * and make sure the debug Node requests them (do this now cos
         * this info disappears when you lock - it's still valid until
         * you change the pipeline. Luckily for us we don't care about
         * the *order* of the clusters in their slots) */
        if ((clusterRefs = (RxClusterDefinition **)
             RwMalloc(2 * pipeline->packetNumClusterSlots *
                      sizeof(RxClusterDefinition *))) == NULL)
        {
            RWERROR((E_RW_NOMEM, (2 * pipeline->packetNumClusterSlots *
                                  sizeof(RxClusterDefinition *))));
            RWRETURN(FALSE);
        }
        numClustersToRequest = 0;

        if (before)
        {
            for (i = 0; i < pipeline->packetNumClusterSlots; i++)
            {
                if (before->slotClusterRefs[i])
                {
                    RxClusterDefinition *newClusterRef =
                        before->slotClusterRefs[i]->clusterRef;

                    for (j = 0; j < numClustersToRequest; j++)
                    {
                        if (clusterRefs[j] == newClusterRef)
                            j = numClustersToRequest + 1;
                    }
                    if (j == numClustersToRequest)
                    {
                        clusterRefs[j] = newClusterRef;
                        numClustersToRequest++;
                    }
                }
            }
        }
        if (after)
        {
            for (i = 0; i < pipeline->packetNumClusterSlots; i++)
            {
                if (after->slotClusterRefs[i])
                {
                    RxClusterDefinition *newClusterRef =
                        after->slotClusterRefs[i]->clusterRef;

                    for (j = 0; j < numClustersToRequest; j++)
                    {
                        if (clusterRefs[j] == newClusterRef)
                            j = numClustersToRequest + 1;
                    }
                    if (j == numClustersToRequest)
                    {
                        clusterRefs[j] = newClusterRef;
                        numClustersToRequest++;
                    }
                }
            }
        }

        lpipe = RxPipelineLock(pipeline);
        RWASSERT(lpipe != NULL);
        if (lpipe != NULL)
        {
            /* Add the debug node to the pipeline */
            lpipe =
                RxLockedPipeAddFragment(lpipe, &debugNum, debugNodeDef,
                                        NULL);
            debugNode = &(lpipe->nodes[debugNum]);

            /* Request all the clusters active
             * during, before and after for the debug node */
            for (i = 0; i < numClustersToRequest; i++)
            {
                RxPipelineNodeRequestCluster(pipeline, debugNode,
                                             clusterRefs[i]);
            }

            if (before)
            {
                /* Disconnect before and after from one another */
                RxLockedPipeDeletePath(lpipe,
                                       RxPipelineNodeFindOutputByIndex
                                       (before, outputNum), after);
                /* Connect before to the debug node */
                RxLockedPipeAddPath(lpipe,
                                    RxPipelineNodeFindOutputByIndex
                                    (before, outputNum), debugNode);
            }
            if (after)
            {
                /* Connect the debug node to after */
                RxLockedPipeAddPath(lpipe,
                                    RxPipelineNodeFindOutputByIndex
                                    (debugNode, 0), after);
            }

            pipeline = RxLockedPipeUnlock(lpipe);

            if (!pipeline)
            {
                _rxPipelineDestroy(lpipe);
            }

            RwFree(clusterRefs);

            RWRETURN(pipeline);
        }
    }

    if (pipeline == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                           "pipeline pointer NULL");
    }
    if (debugNodeDef == NULL)
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                           "debug node definition pointer NULL");
    }
    if ((before == NULL) && (after == NULL))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE, "Debug node insert",
                           "both before and after node pointers NULL");
    }
    RWRETURN((RxPipeline *)NULL);
}

#if (0)

/*****************************************************************************
 DefinePipeExercise
*/

#include "p2stdcls.h"

static const char   rcsid[] __RWUNUSED__ =

    "@@(#)$Id: p2define.c,v 1.107 2001/09/21 12:16:59 iestynb Exp iestynb $";

static void
DefinePipeExercise(void)
{
    RWFUNCTION(RWSTRING("DefinePipeExercise"));

    static RxNodeDefinition Trevor = {
        "Trevor.csl",           /* Name */
        {(NodeBodyFn) NULL, (NodeInitFn) NULL, (NodeTermFn) NULL,
         (PipelineNodeInitFn) NULL, (PipelineNodeTermFn) NULL}, /* nodemethods */
        {(RwUInt32) 0, (RxClusterRef *) NULL,
         (RxClusterValidityReq *) NULL, (RwUInt32) 1,
         (RxOutputSpec *) NULL}, /* Io */
        (RwUInt32) 0,          /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,        /* Editable */
        (RwInt32) 0            /* inpipes */
    };
    static RxNodeDefinition James = {
        "James.csl",            /* Name */
        {(NodeBodyFn) NULL, (NodeInitFn) NULL, (NodeTermFn) NULL,
         (PipelineNodeInitFn) NULL, (PipelineNodeTermFn) NULL}, /* nodemethods */
        {(RwUInt32) 0, (RxClusterRef *) NULL,
         (RxClusterValidityReq *) NULL, (RwUInt32) 1,
         (RxOutputSpec *) NULL}, /* Io */
        (RwUInt32) 0,          /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,        /* Editable */
        (RwInt32) 0            /* inpipes */
    };
    static RxNodeDefinition Horace = {
        "Horace.csl",           /* Name */
        {(NodeBodyFn) NULL, (NodeInitFn) NULL, (NodeTermFn) NULL,
         (PipelineNodeInitFn) NULL, (PipelineNodeTermFn) NULL}, /* nodemethods */
        {(RwUInt32) 0, (RxClusterRef *) NULL,
         (RxClusterValidityReq *) NULL, (RwUInt32) 0,
         (RxOutputSpec *) NULL}, /* Io */
        (RwUInt32) 0,          /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,        /* Editable */
        (RwInt32) 0            /* inpipes */
    };
    static RxNodeDefinition Ahab = {
        "Ahab.csl",             /* Name */
        {(NodeBodyFn) NULL, (NodeInitFn) NULL, (NodeTermFn) NULL,
         (PipelineNodeInitFn) NULL, (PipelineNodeTermFn) NULL}, /* nodemethods */
        {(RwUInt32) 0, (RxClusterRef *) NULL,
         (RxClusterValidityReq *) NULL, (RwUInt32) 0,
         (RxOutputSpec *) NULL}, /* Io */
        (RwUInt32) 0,          /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,        /* Editable */
        (RwInt32) 0            /* inpipes */
    };
    RxPipeline         *pipeline;
    RxLockedPipe       *lckp;
    RxPipelineNode     *trev, *jim;

    RxPipelineNode /* RxNodeDefinition */  * horahab;

    pipeline = RxPipelineCreate();

    lckp = RxPipelineLock(pipeline);

    /* NB/ return value from AddFragment() is a NodeRef & can be used in
     * same manner as in prototype */
    RxLockedPipeAddFragment(lckp, NULL, &Trevor, &James, &Horace, NULL);

    horahab = RxPipelineFindNodeByName(lckp, Horace.name, NULL, NULL);
    RxLockedPipeReplaceNode(lckp, horahab, &Ahab); /* Ahab replaces Horace */

    trev = RxPipelineFindNodeByName(lckp, Trevor.name, NULL, NULL);
    jim = RxPipelineFindNodeByName(lckp, James.name, NULL, NULL);

    RxLockedPipeDeletePath(lckp,
                           RxPipelineNodeFindOutputByIndex(trev, 0),
                           RxPipelineNodeFindInput(jim)); /* delete Trevor -> James */
    RxLockedPipeAddPath(lckp, RxPipelineNodeFindOutputByIndex(trev, 0),
                        RxPipelineNodeFindInput(horahab)); /* connect Trevor -> Ahab */
    RxLockedPipeDeletePath(lckp,
                           RxPipelineNodeFindOutputByIndex(jim, 0),
                           RxPipelineNodeFindInput(horahab)); /* delete James -> Ahab */
    RxLockedPipeAddPath(lckp, RxPipelineNodeFindOutputByIndex(jim, 0),
                        RxPipelineNodeFindInput(trev)); /* connect James -> Trevor */

    /* as Iestyn has pointed out, entry pt. can be determined by top. sort,
     * but having user explicitly set is good for clarity & sanity checking */
    RxLockedPipeSetEntryPoint(lckp, RxPipelineNodeFindInput(jim)); /* now pipeline reads James -> Trevor -> Ahab */

    RxLockedPipeDeleteNode(lckp, trev); /* now pipeline reads James    Ahab */
    RxLockedPipeAddPath(lckp, RxPipelineNodeFindOutputByIndex(jim, 0),
                        RxPipelineNodeFindInput(horahab));

    RxLockedPipeUnlock(lckp);

    _rxPipelineDestroy(pipeline);

    RWRETURNVOID();
}

#endif /* (0) */

/*****************************************************************************/

#if defined (__MWERKS__)
#if (defined(RWVERBOSE))
#pragma message (__FILE__ "/" _SKY_EXPAND(__LINE__) ": __MWERKS__ == " _SKY_EXPAND(__MWERKS__))
#endif /* (defined (__MWERKS__)) */
#if (__option (global_optimizer))
#pragma always_inline on
#endif /* (__option (global_optimizer)) */
#endif /*  defined (__MWERKS__) */

