/*
 * Core functionailty for custom pipelines
 *
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   p2core.c                                                    *
 *                                                                          *
 *  Purpose :   Pipeline II (core) functionality                            *
 *                                                                          *
 ****************************************************************************/

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

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

#include <float.h>

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

#include "p2renderstate.h"
#include "p2define.h"
#include "p2resort.h"
#include "p2core.h"

#include "p2altmdl.h"
#include "bapipe.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: p2core.c,v 1.212 2001/10/02 17:45:32 iestynb Exp iestynb $";
#endif /* (!defined(DOXYGEN)) */

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

/* #define RXPACKETLOG "packet.log" */

#define P2_KEY_LO 0x00000000U  /* keyLo */
#define P2_KEY_HI 0xFFFFFFFFU  /* keyHi */

#if (defined(RWDEBUG))
#define P2CHECKHEAPPTR(ptr) \
  RWASSERT( (NULL != (ptr)) && (RWCRTISVALIDHEAPPOINTER(ptr)) )
#else /* (defined(RWDEBUG)) */
#define P2CHECKHEAPPTR(ptr) /* No op */
#endif /* (defined(RWDEBUG)) */

#define DUFFSZERO128BYTEALIGNEDMEM(_target, _bytes)                   \
MACRO_START                                                           \
{                                                                     \
    /* Duff's device for dynamic loop unrolling                       \
     * http://www.lysator.liu.se/c/duffs-device.html                  \
     */                                                               \
    const RwInt32 numQWords = ((_bytes)+15) >> 4;                     \
                                                                      \
    if (0<numQWords)                                                  \
    {                                                                 \
        static const RwUInt128 zero128 = RWZERO128 ;                  \
        RwUInt32 offset = (numQWords - 1) & ((RwUInt32)~7);           \
        RwUInt128 *mem128 = &(((RwUInt128 *)(_target))[offset]);      \
                                                                      \
        switch(numQWords & 7)                                         \
        {                                                             \
            case 0: do    {    mem128[7] = zero128;                   \
            case 7:            mem128[6] = zero128;                   \
            case 6:            mem128[5] = zero128;                   \
            case 5:            mem128[4] = zero128;                   \
            case 4:            mem128[3] = zero128;                   \
            case 3:            mem128[2] = zero128;                   \
            case 2:            mem128[1] = zero128;                   \
            case 1:            mem128[0] = zero128;                   \
                          }                                           \
            while ( (mem128 -= 8) >= (RwUInt128 *)(_target) );        \
        }                                                             \
    }                                                                 \
}                                                                     \
MACRO_STOP

#define GENERICZERO128BYTEALIGNEDMEM(mem, size)                       \
MACRO_START                                                           \
{                                                                     \
    RwUInt128 *mem128 = (RwUInt128 *)(mem);                           \
    RwInt32 numQWords = ((size)+15) >> 4;                             \
    static const RwUInt128 zero128; /* This is initialised to zero */ \
                                                                      \
    while (numQWords--)                                               \
    {                                                                 \
        *mem128++ = zero128;                                          \
    }                                                                 \
}                                                                     \
MACRO_STOP

#if (0)
#define ZERO128BYTEALIGNEDMEM(_target, _bytes) \
    DUFFSZERO128BYTEALIGNEDMEM(_target, _bytes)
#endif /* (0) */

#define ZERO128BYTEALIGNEDMEM(_target, _bytes) \
    GENERICZERO128BYTEALIGNEDMEM(_target, _bytes)

#if (defined(PLACEHOLDERS))

/*
 *  e.g to run through gcc -s to see the assembler generated by macros
 */
extern void generic_clear(void *target, int bytes);
extern void duff_clear(void *target, int bytes);

static void
generic_clear(void *target, int bytes)
{
    RWFUNCTION(RWSTRING("generic_clear"));

    GENERICZERO128BYTEALIGNEDMEM(target, bytes);

    RWRETURNVOID();
}

static void
duff_clear(void *target, int bytes)
{
    RWFUNCTION(RWSTRING("duff_clear"));

    DUFFSZERO128BYTEALIGNEDMEM(target, bytes);

    RWRETURNVOID();
}
#endif /* (defined(PLACEHOLDERS)) */

/****************************************************************************
 Globals (across program)
 */

/* Default RxHeap size */
RwInt32  _rwRxHeapInitialSize = RXHEAPPLATFORMDEFAULTSIZE;

/****************************************************************************
 Local (static) Globals
 */

RwBool  RxPipelineInstanced   = FALSE;
RxHeap *_rxHeapGlobal         = (RxHeap *)NULL;

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

   Functions

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

/**********************************************************************
 * Initialises RxPipeline global data:
 */

/**********************************************************************
 * De-initialises RxPipeline global data:
 */
RwBool
_rxPipelineClose(void)
{
    RWFUNCTION(RWSTRING("_rxPipelineClose"));

    if (RxPipelineInstanced)
    {
        /*
         * The default pipelines are destroyed in
         * baim3d.c and bapipew.c (before this)
         */

        /* TODO: Destroy all pipelines created by the application? */

        RxHeapDestroy(_rxHeapGlobal);

        RxPipelineInstanced = FALSE;
    }

    RWRETURN(TRUE);
}

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

RwBool
_rxPipelineOpen(void)
{
    RWFUNCTION(RWSTRING("_rxPipelineOpen"));

    if (!RxPipelineInstanced)
    {
        /*
         * Temporary... ish.... measure...
         * not sure where we'll go eventually
         */
        _rxHeapGlobal = RxHeapCreate(_rwRxHeapInitialSize);

        /*
         * The default pipelines are created in
         * baim3d.c and bapipew.c (after this)
         */

        RxRenderStateVectorSetDefaultRenderStateVector(&RXPIPELINEGLOBAL
                                                       (defaultRenderState));

        /* Not implemented yet */
        RXPIPELINEGLOBAL(allPipelines).link.prev = (RwLLLink *)NULL;
        RXPIPELINEGLOBAL(allPipelines).link.next = (RwLLLink *)NULL;

        RxPipelineInstanced = TRUE;

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/*****************************************************************************/
static RxPipelineNode *
PipelineNodeDestroy(RxPipelineNode * const node, RxPipeline * pipeline)
{
    RxNodeDefinition   *nodeDef;

    RWFUNCTION(RWSTRING("PipelineNodeDestroy"));

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

    nodeDef = node->nodeDef;

    /*
     * remember, node ref reference counts are to
     * UNLOCKED pipeline nodes, and also that
     * nodeinit/pipelinenodeinit are called in unlock.
     * Deal with these things for UNLOCKED pipelines:
     */
    if (pipeline->locked == FALSE)
    {
        /* Allow the node to deinitialise any memory it's set up */
        if (nodeDef->nodeMethods.pipelineNodeTerm != NULL)
        {
            nodeDef->nodeMethods.pipelineNodeTerm(node);
        }

        RWASSERT(0 < nodeDef->InputPipesCnt);
        /*
         * nodeterm (called for the last pipelinenode
         * referencing a given node definition)
         * and free the node definition if it is 'editable'
         */
        if (0 == --(nodeDef->InputPipesCnt))
        {
            if (nodeDef->nodeMethods.nodeTerm != NULL)
            {
                nodeDef->nodeMethods.nodeTerm(nodeDef);
            }
            if (nodeDef->editable)
            {
                RWASSERT(RWCRTISVALIDHEAPPOINTER(nodeDef));
                RwFree(nodeDef);
                nodeDef = (RxNodeDefinition *)NULL;
            }
        }
    }
    else
    {
        /*
         * For a LOCKED pipeline, inpipes only references nodes
         * in other (UNLOCKED)pipelines (RxPipelineLock()
         * decrements reference counts).
         * Nodeterm will have been called in lock() already,
         * if (inpipes == 0)
         */
        RWASSERT(0 <= nodeDef->InputPipesCnt);
        if (0 == nodeDef->InputPipesCnt)
        {
            /*
             * Looks like this node def was only
             * referenced by node(s) in this pipeline
             */
            if (nodeDef->editable)
            {
                RWASSERT(RWCRTISVALIDHEAPPOINTER(nodeDef));
                RwFree(nodeDef);
                nodeDef = (RxNodeDefinition *)NULL;
            }
        }
    }

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

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

    node->numOutputs = 0;

    RWRETURN(node);
}

/*****************************************************************************
 _rxPipelineExecuteNode

 This is purely for debugging purposes. It wraps the call to a node so that
 we can output node entry/exit debug-stream messages, set a global for the
 'current node' and 'current pipeline', etc...

 In release mode this is a macro that boils away to nothing.
*/

#if (defined(RWDEBUG) && defined(RXMONITOR))

static              RwBool
PL2PipelineExecuteNodeMonitor(RxPipelineNode * currentNode,
                              RxPipelineNodeParam * params)
{
    RwBool              result = FALSE;
    const RxNodeDefinition *const NodeRef;
    const RwChar       *const name;

    RWFUNCTION(RWSTRING("PL2PipelineExecuteNodeMonitor"));

    RWASSERT(currentNode != NULL);
    RWASSERT(memory != NULL);

    NodeRef = currentNode->nodeRef->nodeRef;
    name = NodeRef->name;

    RWMESSAGE(("Entering Node", name));

    result = NodeRef->nodeMethods.nodeBody(currentNode, params);

    if (TRUE == result)
    {
        RWMESSAGE(("Exited node with return value TRUE", name));
    }
    else
    {
        RWMESSAGE(("Exited node with return value FALSE", name));
    }

    RWRETURN(result);
}

#define _rxPipelineExecuteNode(node, mem, data)                  \
    PL2PipelineExecuteNodeMonitor(node, mem, data)

#else /* (defined(RWDEBUG) && defined(RXMONITOR)) */

#define _rxPipelineExecuteNode(_node, _params)                  \
    (node->nodeRef->nodeRef->nodeMethods.nodeBody(_currentNode, _params))

#endif /* (defined(RWDEBUG) && defined(RXMONITOR)) */

#if (defined(RWDEBUG))

#if (defined(RXPACKETLOG))

#define RXPACKETLOGOPEN(_result, _name) \
  _result = fopen(_name, "at")

#define RXPACKETLOGFPRINTF(_args) \
   fprintf _args

#define RXPACKETLOGCLOSE(_file) \
   fclose(_file)

#endif /* defined(RXPACKETLOG) */

#if (!defined(RXPACKETLOGOPEN))
#define RXPACKETLOGOPEN(_result, _name) /* No op */
#endif /* (!defined(RXPACKETLOGOPEN)) */

#if (!defined(RXPACKETLOGFPRINTF))
#define RXPACKETLOGFPRINTF(_args) /* No op */
#endif /* (!defined(RXPACKETLOGFPRINTF)) */

#if (!defined(RXPACKETLOGCLOSE))
#define RXPACKETLOGCLOSE(_file) /* No op */
#endif /* (!defined(RXPACKETLOGCLOSE)) */

#if (defined(WIN32))

/*
 * IsBadReadPtr() is defined in
 * /Program Files/Microsoft Visual Studio/VC98/Include/WINBASE.H
 */
#define RWISBADREADPTR(lp,ucb) IsBadReadPtr(lp,ucb)
#endif /* (defined(WIN32)) */

#if (!defined(RWISBADREADPTR))
#define RWISBADREADPTR(lp,ucb) (!(RWCRTISVALIDPOINTER(lp,ucb,FALSE)))
#endif /* (!defined(RWISBADREADPTR)) */

/*****************************************************************************
 RXPIPELINESANITYCHECKPACKETS(node)

 This macro is called from RxPipelineExecute() when RWDEBUG is defined.

 It will RWASSERT() on a couple of "broken cluster" conditions.

 If RWDEBUG and RXPACKETLOG are both defined  -- and the latter is defined to be
 a filename, e.g. "packet.log" --, you get a record of packets flowing down the
 pipe.

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

#define VALIDATEEXTERNAL(cl)                                                \
MACRO_START                                                                 \
{                                                                           \
    /* external */                                                          \
    RwBool ValidExternal = !RWISBADREADPTR((cl->data),                      \
                                            minBlockSize);                  \
                                                                            \
    RWASSERT(ValidExternal);                                                \
    if(!ValidExternal)                                                      \
    {                                                                       \
        RXPACKETLOGFPRINTF((fp,                                             \
                          "%s(%d): 'VALID' CLUSTER HAS BAD DATA PTR!!!\n",  \
                              __FILE__, __LINE__));                         \
    }                                                                       \
}                                                                           \
MACRO_STOP

#if (!defined(DISABLERWHEAP))
#define VALIDATEINTERNAL(ptr)                                               \
MACRO_START                                                                 \
{                                                                           \
    /* internal */                                                          \
    rxHeapBlockHeader *blkhdr =                                             \
        ((rxHeapBlockHeader *) (cl->data)) - 1;                             \
    RwBool ValidInternal =                                                  \
        ( blkhdr->size >= minBlockSize &&                                   \
          blkhdr->freeEntry == NULL &&                                      \
          blkhdr->prev->next == blkhdr &&                                   \
          blkhdr->next->prev == blkhdr );                                   \
                                                                            \
    RWASSERT(ValidInternal);                                                \
                                                                            \
    if(!ValidInternal)                                                      \
    {                                                                       \
        RXPACKETLOGFPRINTF((fp,                                           \
                          "%s(%d): 'VALID' CLUSTER HAS BAD DATA PTR!!!\n",  \
                              __FILE__, __LINE__));                         \
    }                                                                       \
}                                                                           \
MACRO_STOP

#endif /*(!defined(DISABLERWHEAP)) */

#if (!defined(VALIDATEINTERNAL))
#define VALIDATEINTERNAL(ptr)  /* No op */
#endif /* (!defined(VALIDATEINTERNAL)) */

#if (defined(RXPACKETLOG))
#define FILEDECLARE(_fp) FILE *_fp
#else /* (defined(RXPACKETLOG)) */
#define FILEDECLARE(_fp)       /* No op */
#endif /* (defined(RXPACKETLOG)) */

#define RXPIPELINESANITYCHECKPACKETS(node)                                  \
MACRO_START                                                                 \
{                                                                           \
    RxPipelineCluster **slotClusterRefs;                                    \
    RxPacket *pk;                                                           \
    int packetindex;                                                        \
    FILEDECLARE(fp);                                                        \
                                                                            \
    RWASSERT(NULL != node);                                                 \
                                                                            \
    slotClusterRefs =                                                       \
        (node)->nodeRef->slotClusterRefs;                                   \
                                                                            \
    RXPACKETLOGOPEN(fp, RXPACKETLOG);                                       \
                                                                            \
    RXPACKETLOGFPRINTF((fp,                                                 \
                          "%s\n",                                           \
                          (node)->nodeRef->nodeRef->name));                 \
                                                                            \
    for (pk = (node)->inputHead, packetindex = 0;                           \
         pk != NULL;                                                        \
         pk = pk->prev,  packetindex++)                                     \
    {                                                                       \
        int n;                                                              \
                                                                            \
        RXPACKETLOGFPRINTF((fp,                                             \
                              "  %d\n",                                     \
                              packetindex));                                \
                                                                            \
        for (n = 0; n < pk->numClusters; n++)                               \
        {                                                                   \
            if ( NULL != slotClusterRefs[n] )                               \
            {                                                               \
                RxCluster *cl = &pk->clusters[n];                           \
                                                                            \
                RXPACKETLOGFPRINTF((fp,                                     \
                                      "    %36s",                           \
                                      slotClusterRefs[n]->clusterRef->name));\
                                                                            \
                if ( !(cl->flags & rxCLFLAGS_CLUSTERVALID) )                \
                {                                                           \
                    RXPACKETLOGFPRINTF((fp,                                 \
                                          " NV\n"));                        \
                }                                                           \
                else                                                        \
                {                                                           \
                    RwUInt32 minBlockSize;                                  \
                    RwBool numUsed_within_numAlloced;                       \
                                                                            \
                    RXPACKETLOGFPRINTF((fp,                                 \
                                          "  V %s 0x%08X 0x%04X %d%s/%d\n", \
                                          ( cl->flags & rxCLFLAGS_EXTERNAL ) ?\
                                          " X" : "NX",                      \
                                          (RwUInt32) cl->data,              \
                                          cl->stride,                       \
                                          cl->numUsed,                      \
                                          (cl->numUsed > cl->numAlloced) ?  \
                                          "!!!" : "",                       \
                                          cl->numAlloced));                 \
                                                                            \
                    numUsed_within_numAlloced =                             \
                        ( cl->numUsed <= cl->numAlloced );                  \
                                                                            \
                    RWASSERT(numUsed_within_numAlloced);                    \
                                                                            \
                    minBlockSize =                                          \
                        ~3U & ( (((RwUInt32) cl->numAlloced) *              \
                                 ((RwUInt32) cl->stride)) + 3U ) ;          \
                                                                            \
                    if ( cl->flags & rxCLFLAGS_EXTERNAL )                   \
                        VALIDATEEXTERNAL(cl);                               \
                    else                                                    \
                        VALIDATEINTERNAL(cl);                               \
                }                                                           \
            }                                                               \
        }                                                                   \
    }                                                                       \
                                                                            \
    RXPACKETLOGCLOSE(fp);                                                   \
}                                                                           \
MACRO_STOP

#endif /* (defined(RWDEBUG)) */

#if (!defined(RXPIPELINESANITYCHECKPACKETS))
#define RXPIPELINESANITYCHECKPACKETS(node) /* No op */
#endif /* (!defined(RXPIPELINESANITYCHECKPACKETS)) */

/**
 * \ingroup rwcoregeneric
 * \ref RxHeapGetGlobalHeap
 * returns the global PowerPipe heap.
 *
 * The pipeline execution code maintains an execution heap which is
 * tightly bound to the current invocation of \ref RxPipelineExecute
 *
 * This heap is used to rapidly service allocations for "transient" memory;
 * such allocations are not preserved between pipeline executions - the
 * heap is cleared down on exit from \ref RxPipelineExecute, with a call
 * to \ref RxHeapReset.
 *
 * \ref RxClusterInitializeData and \ref RxClusterResizeData, for example,
 * use the execution heap to allocate cluster memory.
 *
 * The returned heap of type \ref RxHeap
 *  can be used in calls to
 * \ref RxHeapAlloc, \ref RxHeapRealloc and \ref RxHeapFree. During the
 * execution of the node body method of a \ref RxPipelineNode, 
 * the current heap - we may have multiple heaps eventually to execute 
 * more than one pipe simultaneously on multiprocessor systems -  may 
 * be obtained through \ref RxPipelineNodeParamGetHeap.
 *
 * \verbatim
   Use of execution heap.
   {
           RxHeap *heap;
           int *intArray;

           heap = RxHeapGetGlobalHeap();

           intArray = (int *) RxHeapAlloc(heap, 200 * sizeof(int));

           intArray[199] = 3;

           RxHeapFree(heap, intArray);
   }
   \endverbatim
 *
 * \return A pointer to the global pipeline execution heap.
 *
 * \see RxPipelineExecute
 * \see RxPipelineNodeParamGetHeap
 * \see RxHeapAlloc
 * \see RxHeapRealloc
 * \see RxHeapFree
 */
RxHeap             *
RxHeapGetGlobalHeap(void)
{
    RWAPIFUNCTION(RWSTRING("RxHeapGetGlobalHeap"));

    RWRETURN(_rxHeapGlobal);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketCreate
 * creates an empty packet.
 *
 * This function will fail if there is insufficient memory on the
 * pipeline execution heap or if a packet is already in existence
 * [ due to the nested pipeline executed model, only one packet
 * exists at a time, see \ref RxPacketFetch for details ].
 *
 * Data for all clusters in the created packet will be flagged invalid
 * !(RxCluster.flags & rxCLFLAGS_CLUSTERVALID). Use \ref RxClusterLockWrite
 * then \ref RxClusterInitializeData, \ref RxClusterSetExternalData or
 * \ref RxClusterSetData to initialize clusters as required.
 *
 * This function is for use within node body methods.
 *
 * \param self  A pointer to the calling pipeline node.
 *
 * \return A pointer to an empty packet on success, otherwise NULL.
 *
 * \see RxPacketDestroy
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxPacket           *
RxPacketCreate(RxPipelineNode * self)
{
    RxPacket           *createdPacket;

    RWAPIFUNCTION(RWSTRING("RxPacketCreate"));

    RWASSERT(NULL != (self));
    RWASSERT(NULL != (_rxExecCtxGlobal.pipeline));

    createdPacket = _rxPacketCreate(_rxExecCtxGlobal.pipeline);
    RWASSERT(NULL != createdPacket);

    createdPacket->inputToClusterSlot = self->inputToClusterSlot;
    createdPacket->slotsContinue = self->slotsContinue;
    createdPacket->slotClusterRefs = self->slotClusterRefs;

    RWRETURN(createdPacket);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetStride
 * sets a cluster's stride (the increment
 * in bytes from one element of the data array to the next).
 *
 * This function only stores the stride value; it does not reorganize the
 * data array, nor does it resize it. It is common practice to follow
 * \ref RxClusterSetStride with an \ref RxClusterResizeData. When creating a
 * cluster, it is more natural to use the \ref RxClusterInitializeData call,
 * which sets the stride of, and allocates memory for, the data array.
 *
 * \param cluster  A pointer to the cluster to act upon.
 * \param stride  The new stride for the cluster's data.
 *
 * \return Pointer to the modified cluster on success, NULL otherwise.
 *
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 */
RxCluster          *
RxClusterSetStride(RxCluster * cluster, RwInt32 stride)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetStride"));

    RWASSERT(NULL != cluster);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);

    /* TODO: er... MACRO anyone??? */

    cluster->stride = (RwUInt16) stride;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetExternalData
 * initializes a cluster as referencing
 * external data.
 *
 * Clusters are the means by which packets convey data. A packet will usually
 * contain a number of clusters, e.g. { RenderState, ObjVerts, Triangles }.
 *
 * Clusters are classified as internal or external (to pipeline execution).
 * Nodes treat internal or external clusters in exactly the same fashion;
 * indeed, as a rule, nodes need not know whether a given cluster is
 * internal or external. This function sets the data to EXTERNAL.
 *
 * INTERNAL clusters: A cluster created by \ref RxClusterInitializeData is
 * internal - the cluster data is allocated from the pipeline execution
 * heap, and the cluster may be resized and its data modified as the packet
 * flows down the pipeline. When the containing packet is destroyed, or the
 * cluster is terminated - because subsequent nodes have no interest in it,
 * as identified by dependency chasing during \ref RxLockedPipeUnlock - the
 * cluster data is freed.
 *
 * EXTERNAL clusters: These are used to convey a reference to data external
 * to the pipeline execution heap. Should a node attempt to modify (i.e.
 * call \ref RxClusterLockWrite) or resize (\ref RxClusterResizeData) an
 * external cluster, then the cluster will be transparently "internalized"
 * (the referenced data will be copied into the pipeline execution heap,
 * and the cluster will become internal). In the event of internalization,
 * it is the copied data that is subsequently propagated. Note that this
 * scheme makes it essential that you use \ref RxClusterLockWrite for a
 * cluster instead of \ref RxClusterLockRead, if you are subsequently to
 * edit the cluster's data.
 *
 * Note that in some cases it may be useful to flag cluster data that is
 * allocated from the pipeline execution heap as external. Doing so will
 * protect the original data array from modification by any subsequent
 * nodes in the pipeline, which might be useful if the same array is to
 * be used in multiple packets which a node creates.
 *
 * Non-array data is treated as a single element array; it is still
 * necessary to correctly set the stride for non-array data.
 *
 * \param cluster  Cluster to act upon.
 * \param data  Data to reference.
 * \param stride  Stride of data array (increment in bytes from
 *       one element of the data array to the next).
 * \param numElements  Number of elements in data array (1 if non-array data).
 *
 * \return Pointer to the modified cluster on success, NULL otherwise.
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetData
 */
RxCluster          *
RxClusterSetExternalData(RxCluster * cluster,
                         void *data,
                         RwInt32 stride, RwInt32 numElements)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetExternalData"));

    RWASSERT(NULL != cluster);
    RWASSERT(NULL != data);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);
    RWASSERT(numElements > 0);

    /* TODO: see setData below */
    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)) &&
        (cluster->data != data))
    {
        /* If current and incoming data are equal, we're essentially just
         * setting a flag, and maybe changing stride, numalloced etc... */

        /* Free the previous block of data */
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
        cluster->data = NULL;
    }

    cluster->data = data;
    cluster->currentData = data;
    cluster->stride = (RwUInt16) stride;
    /* The cluster now has data */
    cluster->flags |= (rxCLFLAGS_EXTERNAL | rxCLFLAGS_CLUSTERVALID);
    cluster->numAlloced = numElements;
    cluster->numUsed = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetData
 * sets a cluster's data to an existing data block.
 *
 * Clusters are the means by which packets convey data. A packet will usually
 * contain a number of clusters, e.g. { RenderState, ObjVerts, Indices }.
 *
 * Clusters are classified as internal or external (to pipeline execution).
 * Nodes treat internal or external clusters in exactly the same fashion;
 * indeed, as a rule, nodes need not know whether a given cluster is
 * internal or external. This function sets the data to INTERNAL.
 *
 * INTERNAL clusters: A cluster created by \ref RxClusterInitializeData is
 * internal - the cluster data is allocated from the pipeline execution
 * heap, and the cluster may be resized and its data modified as the packet
 * flows down the pipeline. When the containing packet is destroyed, or the
 * cluster is terminated - because subsequent nodes have no interest in it,
 * as identified by dependency chasing during \ref RxLockedPipeUnlock - the
 * cluster data is freed.
 *
 * EXTERNAL clusters: These are used to convey a reference to data external
 * to the pipeline execution heap. Should a node attempt to modify (i.e.
 * call \ref RxClusterLockWrite) or resize (\ref RxClusterResizeData) an
 * external cluster, then the cluster will be transparently "internalized"
 * (the referenced data will be copied into the pipeline execution heap,
 * and the cluster will become internal). In the event of internalization,
 * it is the copied data that is subsequently propagated. Note that this
 * scheme makes it essential that you use \ref RxClusterLockWrite for a
 * cluster instead of \ref RxClusterLockRead, if you are subsequently to
 * edit the cluster's data.
 *
 * Non-array data is treated as a single element array; it is still
 * necessary to correctly set the stride for non-array data.
 *
 * \param cluster  Cluster to act upon.
 * \param data  Data to reference.
 * \param stride Stride of data array (increment in bytes from one element
 *               of the data array to the next).
 * \param numElements  Number of elements in data array (1 if non-array data).
 *
 * \return Pointer to the modified cluster on success, NULL otherwise.
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetExternalData
 */
RxCluster          *
RxClusterSetData(RxCluster * cluster,
                 void *data, RwInt32 stride, RwInt32 numElements)
{
    RWAPIFUNCTION(RWSTRING("RxClusterSetData"));

    RWASSERT(NULL != cluster);
    RWASSERT(NULL != data);
    RWASSERT(stride > 0);
    RWASSERT(stride <= 65535);
    RWASSERT(numElements > 0);

    /* TODO:
     * (implement this function (a) as a macro
     *  and (b) so it takes a flag parameter (external, external modifiable
     *  or local - add that one) - so we only need setData and not
     *  setExternalData)
     */
    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)) &&
        (cluster->data != data))
    {
        /* If current and incoming data are equal, we're essentially just
         * setting a flag, and maybe changing stride, numalloced etc... */
        /* Free the previous block of data */
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
        cluster->data = NULL;
    }

    cluster->data = data;
    cluster->currentData = data;
    cluster->stride = (RwUInt16) stride;
    cluster->flags |= rxCLFLAGS_CLUSTERVALID; /* The cluster now has data */
    cluster->numAlloced = numElements;
    cluster->numUsed = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterInitializeData
 * creates a cluster's data array.
 *
 * In the same manner as the run-time library alloc() call,
 * RxClusterInitializeData can be used to allocate cluster data. However,
 * it is probably best to think of this function's purpose as to
 * "(re)initialize a cluster's data" - it sets the cluster's stride (values
 * greater than zero are valid) and allocates data. If the cluster already
 * has data then it will be freed. If you know that you do not need the old
 * data then it is better to call this function rather than
 * \ref RxClusterResizeData, because the copy of the data that that function
 * will do is entirely unnecessary and may be costly.
 *
 * Both the stride and number of elements specified must be greater than zero.
 *
 * \param cluster  A pointer to the cluster to act upon.
 * \param numElements  The number of elements to alloc
 * \param stride  The stride of the cluster's data array
 *
 * \return Pointer to the modified cluster on success, NULL otherwise.
 *
 * \see RxClusterResizeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterInitializeData(RxCluster * cluster,
                        RwUInt32 numElements, RwUInt16 stride)
{
    void               *allocResult;
    RwUInt32            size = numElements * stride;

    RWAPIFUNCTION(RWSTRING("RxClusterInitializeData"));
    RWASSERT(cluster != NULL);
    RWASSERT(numElements > 0);
    RWASSERT(stride > 0);

    if ((cluster->data != NULL) &&
        (!(cluster->flags & rxCLFLAGS_EXTERNAL)))
    {
        RxHeapFree(_rxHeapGlobal, cluster->data);
    }

    allocResult = RxHeapAlloc(_rxHeapGlobal, size);

#if (defined(RWDEBUG))
    if (allocResult == NULL)
    {
        RWERROR((E_RW_NOMEM, size));
        RWRETURN((RxCluster *)NULL);
    }
#endif /* (defined(RWDEBUG)) */

    P2CHECKHEAPPTR(allocResult);

    cluster->data = allocResult;
    cluster->currentData = allocResult;

    cluster->numAlloced = numElements;
    cluster->numUsed = 0;

    /* We definitely have data now */
    cluster->flags |= rxCLFLAGS_CLUSTERVALID;
    cluster->stride = stride;

    RWRETURN(cluster);

}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterResizeData
 *  resizes a cluster's data array.
 *
 * In the same manner as the run-time library realloc() call,
 * \ref RxClusterResizeData can be used to reallocate cluster data.
 * \ref RxClusterDestroyData should be used to deallocate data, not this
 * function. Equally, data should be initially allocated by
 * \ref RxClusterInitializeData, not this function.
 *
 * The number of elements specified must be greater than zero.
 *
 * \param cluster  Cluster to act upon.
 * \param numElements  New number of elements in the cluster's
 * data array; should be greater than zero
 *
 * \return Pointer to the modified cluster on success, NULL otherwise.
 *
 * \see RxClusterInitializeData
 * \see RxClusterDestroyData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterResizeData(RxCluster * cluster, RwUInt32 numElements)
{
    RwUInt32            newSize;
    void               *allocresult;

    RWAPIFUNCTION(RWSTRING("RxClusterResizeData"));

    /* The cluster must be valid (i.e one of RxClusterLockRead/Write()
     * has been called)
     *
     * We need to resolve when/why these flags are set/checked and
     * implement something consistent
     *  - the new clusters bitfield thing looks promising [6.6.2000]
     *
     * RWASSERT(cluster->flags & rxCLFLAGS_CLUSTERVALID);
     */

    RWASSERT(cluster != NULL);
    RWASSERT(numElements != 0);
    RWASSERT(!(cluster->flags & rxCLFLAGS_EXTERNAL));
    RWASSERT(cluster->stride > 0);
    RWASSERT(cluster->data != NULL);

    newSize = numElements * ((RwUInt32) cluster->stride);

    P2CHECKHEAPPTR(cluster->data);

    allocresult = RxHeapRealloc(_rxHeapGlobal, cluster->data, newSize, TRUE);
#if (defined(RWDEBUG))
    if (allocresult == NULL)
    {
        RWRETURN((RxCluster *) NULL);
    }
#endif /* (defined(RWDEBUG)) */

    P2CHECKHEAPPTR(allocresult);

#if (defined(RWDEBUG) && defined(RXMONITOR))
    if (allocresult != cluster->data)
    {
        RWMESSAGE(("%ld bytes copied!", newSize));
    }
#endif /* (defined(RWDEBUG) && defined(RXMONITOR)) */

    cluster->data = allocresult;
    cluster->currentData = allocresult;

    /* We definitely have data now */
    cluster->flags |= rxCLFLAGS_CLUSTERVALID;

    if (cluster->numUsed > numElements)
    {
        cluster->numUsed = numElements;
    }
    cluster->numAlloced = numElements;

    RWRETURN(cluster);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterDestroyData
 * destroys a cluster's data array.
 *
 * In the same manner as the run-time library free() call,
 * RxClusterDestroyData can be used to deallocate cluster data. A cluster
 * should have some data to free on entering this function, naturally - if it
 * is external data, it won't actually be freed, but the cluster's pointer to
 * the data will be cleared, as will its flags.
 *
 * \param cluster  Cluster to act upon.
 *
 * \see RxClusterInitializeData
 * \see RxClusterResizeData
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxClusterSetStride
 */
RxCluster          *
RxClusterDestroyData(RxCluster * cluster)
{
    RWAPIFUNCTION(RWSTRING("RxClusterDestroyData"));

    RWASSERT(cluster != NULL);
    RWASSERT(cluster->data != NULL);

    if (!(cluster->flags & rxCLFLAGS_EXTERNAL))
    {
        P2CHECKHEAPPTR(cluster->data);
        RxHeapFree(_rxHeapGlobal, cluster->data);
    }

    cluster->data = NULL;

    cluster->numAlloced = 0;
    cluster->numUsed = 0;

    /* No valid data */
    cluster->flags = rxCLFLAGS_NULL;

    RWRETURN((RxCluster *)NULL);
}

#if (defined(DOXYGEN) || defined(RWDEBUG))

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterLockRead
 * locks a cluster for reading in an \ref RxPacket.
 *
 * RxClusterLockRead will fail (indicated by a NULL return) if the packet
 * does not contain the requested cluster. To ensure that this does not
 * happen, the node's \ref RxNodeDefinition should flag the cluster as
 * required [rxCLREQ_REQUIRED or rxCLREQ_DONTWANT - the latter means that
 * the cluster is required but any prior data it contains is not wanted].
 * Even if this is done, the cluster may not be present if no subsequent
 * nodes in the pipeline require it and no prior nodes create it. The node
 * can ensure that it definitely is there by specifying rxCLFORCEPRESENT
 * for the forcePresent member of the cluster's \ref RxClusterRef in the
 * clustersOfInterest array in the \ref RxNodeDefinition. This is important
 * if the cluster is to be dispatched in the packet to another pipeline.
 *
 * It is possible for an rxCLREQ_REQUIRED cluster to be empty, and to
 * guard against this nodes should test (cluster->numUsed > 0).
 * rxCLREQ_OPTIONAL clusters may not be present and should always be
 * tested for presence.
 *
 * Clusters locked for reading may not be resized, and modification of
 * their data is not permitted.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param  packet   A pointer to the packet containing the cluster.
 * \param  clusterIndex   The cluster's index within the node's
 *                        RxNodeDefinition.io.clustersOfInterest array.
 *
 * \verbatim
   Self-contained example
   that iterates through a packet's ObjVerts cluster. |
   RwBool
   ProcessPacket(RxPacket *pk)
   {
           RxCluster *clObjVerts;

           // cluster we wish to LockRead() is identified by its index
           // within the node's RxNodeDefinition.io.clustersOfInterest array

           clObjVerts = RxClusterLockRead(pk, 3);
           if ( (clObjVerts != NULL ) && (clObjVerts->numUsed > 0))
           {
                   RwUInt32 n;

                   for (n = 0; n < clObjVerts->numUsed; n++) // iterate over ObjVerts array
                   {
                           const RwObjSpace3DVertex *objVert =
                               RxClusterGetCursorData(clObjVerts, const RwObjSpace3DVertex);

                           ... // do something with objVert

                           RxClusterIncCursor(clObjVerts);
                   }

                   RxClusterUnlock(clObjVerts);
           }
   }
   \endverbatim
 *
 * \return A pointer to the cluster on success, otherwise NULL.
 *
 * \see RxClusterLockWrite
 * \see RxClusterUnlock
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxCluster          *
RxClusterLockRead(RxPacket * packet, RwUInt32 clusterIndex)
{
    RwUInt32            slot = packet->inputToClusterSlot[clusterIndex];

    RWAPIFUNCTION(RWSTRING("RxClusterLockRead"));

    if (slot != (RwUInt32) - 1)
    {
        RxCluster          *cluster = &packet->clusters[slot];

        /*if (cluster->flags & rxCLFLAGS_CLUSTERVALID)
         * { */
        RxClusterResetCursor(cluster);

        RWRETURN(cluster);
        /*} */
    }

    RWRETURN((RxCluster *) NULL);
}
#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterLockWrite 
 * locks a cluster for writing.
 *
 * Returns NULL if the packet does not contain the requested cluster. [The
 * node's \ref RxNodeDefinition record may list a cluster as rxCLVALID_VALID,
 * but if dependency chasing identifies that no subsequent nodes have any
 * interest in the cluster and no prior nodes create the cluster, it will
 * be eliminated: packets will not make provision for the cluster, and it
 * will not be possible to create it. To override this elimination behaviour,
 * set the RxClusterRef.forcePresent flag to rxCLFORCEPRESENT - see also
 * \ref RxClusterLockRead]
 *
 * Clusters locked for writing may have their data initialized, resized,
 * destroyed or modified. This function sets the rxCLFLAGS_MODIFIED bit in
 * the cluster's flags so that it can later be determined that the cluster's
 * data has been edited.
 *
 * If RxClusterLockWrite is called on an external (by reference) cluster,
 * "internalization" occurs - the cluster data is copied to permit
 * modification, and the cluster becomes internal (dropping the reference
 * altogether). It is this internal cluster that will subsequently be
 * propagated. For a more detailed discussion of the internal/external
 * distinction, see \ref RxClusterSetExternalData or \ref RxClusterSetData.
 *
 * \param  packet  A pointer to the packet containing the cluster.
 * \param  clusterIndex   The cluster's index within
 * the node's RxNodeDefinition.io.clustersOfInterest array.
 * \param  node   A pointer to the calling pipeline node.
 *
 * \verbatim
   Creating a cluster.
   RwBool
   ProcessPacket(RxPacket *pk)
   {
           RxCluster *clRenderState;

           // cluster we wish to LockWrite() is identified by its index
           // within the node's RxNodeDefinition.io.clustersOfInterest array

           if ( (clRenderState = RxClusterLockWrite(pk, 0)) != NULL )
           {
                   if ( RxClusterInitializeData(clRenderState, 1, sizeof(RwRenderState)) )
                   {
                           RwRenderState *renderState = RxClusterGetCursorData(clRenderState, RwRenderState);

                           memset(renderState, 0, sizeof(RwRenderState));

                           renderState->texture = WoodTexture; // ... or whatever
                   }

                   RxClusterUnlock(clRenderState);
           }
   }
   \endverbatim
 *
 * \return A pointer to the cluster on success, otherwise NULL.
 *
 * \see RxClusterLockRead
 * \see RxClusterUnlock
 * \see RxClusterInitializeData
 * \see RxClusterDestroyData
 * \see RxClusterResizeData
 * \see RxClusterSetData
 * \see RxClusterSetExternalData
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 */
RxCluster          *
RxClusterLockWrite(RxPacket * packet,
                   RwUInt32 clusterIndex,
                   RxPipelineNode * __RWUNUSED__ node)
{
    RwUInt32            slot = packet->inputToClusterSlot[clusterIndex];

    RWAPIFUNCTION("RxClusterLockWrite");

    /* management of RxCluster.clusterRef:
     * (o) ... is set from node's SlotClusterRefs on packetcreate()/packetbetweenpipelines().
     * (o) ... is NULLed when cluster terminated.
     * (o) ... when ClusterLockWrite() is called, if NULL, then we're witnessing the birth
     * of a new cluster, so ClusterRef set correspondingly.
     *
     * similar logic applies to setting of cluster attributes.
     *
     * one consequence is that cluster attributes are "undefined" (in C jargon)
     * prior to RwClusterLock[Read/Write], which requires no special considerations
     * from the node perspective as they have to call RwClusterLock[Read/Write]
     * in order to get an RxCluster *.
     */

    /* No error message - returning NULL is fine. In some cases the
     * node handles NULL ok, so we don't want piles of error messages */
    if (slot != (RwUInt32) - 1)
    {
        RxCluster          *cluster = &packet->clusters[slot];

        if (cluster->clusterRef == NULL)
        {
            RxPipelineCluster  *pipelineClusterRef =
                packet->slotClusterRefs[slot];

            RWASSERT(pipelineClusterRef != NULL);

            /* as above, "birth of a new cluster" */

            cluster->clusterRef = pipelineClusterRef;
            cluster->attributes = pipelineClusterRef->creationAttributes;
        }

        /* The cluster HAS been modified */
        cluster->flags |= rxCLFLAGS_MODIFIED;

        /*if (cluster->flags & rxCLFLAGS_CLUSTERVALID)
         * { */
        if ((cluster->flags & rxCLFLAGS_EXTERNALMODIFIABLE) ==
            rxCLFLAGS_EXTERNAL)
        {
            /* copy rxCLFLAGS_EXTERNAL cluster to create a non-rxCLFLAGS_EXTERNAL
             * cluster, which we will subsequently propagate
             *
             * note that if rxCLFLAGS_EXTERNALMODIFIABLE, we don't do a copy,
             * but simply return the data pointer
             *
             * this means that if you LockWrite() a rxCLFLAGS_EXTERNAL cluster,
             * you will be able to resize/free afterwards (coz of the copy
             * to non-rxCLFLAGS_EXTERNAL)
             *
             * BUT: if you LockWrite() a rxCLFLAGS_EXTERNALMODIFIABLE cluster,
             * subsequent attempts to resize/free will fail
             */
            void               *p;
            RwUInt32            numAlloced;
            RwUInt32            numUsed;

            p = cluster->data;
            numAlloced = cluster->numAlloced;
            numUsed = cluster->numUsed;

            cluster->flags &= ~rxCLFLAGS_EXTERNAL;
            cluster->data = NULL;

            if (RxClusterInitializeData
                (cluster, numAlloced, cluster->stride) == NULL)
            {
                cluster->flags |= rxCLFLAGS_EXTERNAL;
                cluster->data = p;
                RWRETURN((RxCluster *)NULL);
            }

            cluster->numUsed = numUsed;

            memcpy(cluster->data, p, numUsed * cluster->stride);
        }

        RxClusterResetCursor(cluster);

        RWRETURN(cluster);
        /*} */
    }

    RWRETURN((RxCluster *)NULL);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterUnlock 
 * unlocks a cluster.
 *
 * Counterpart to \ref RxClusterLockRead and \ref RxClusterLockWrite ;
 * call when the \ref RxCluster 
 * is no longer required  - when reading, writing or resizing is
 * complete.
 *
 * \param cluster  A pointer to the cluster to unlock.
 *
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxClusterUnlock(RxCluster * __RWUNUSED__ cluster)
{
    RWAPIFUNCTION(RWSTRING("RxClusterUnlock"));

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeSendConfigMsg 
 * requests information on node requirements.
 *
 * In the final stages of pipeline Unlock()-ing, (i) the pipelinenodeinit
 * method of each node in the pipeline is called [in bottom to top (consumer
 * -> producer) order]; (ii) the pipelinenodeconfig method of each node is
 * called, again in bottom to top order.
 *
 * It is expected that nodes will use the pipelinenodeinit method to
 * initialize their private data. Once the pipelinenodeinit phase is complete,
 * it is assumed that all nodes are initialized as necessary. Nodes may then
 * use the pipelinenodeconfig method as an opportunity to communicate with
 * other nodes in the pipeline and further ascertain requirements.
 *
 * A typical node's pipelinenodeconfig method might use
 * \ref RxPipelineNodeForAllConnectedOutputs (perhaps recursively) to identify
 * the nodes to which it outputs, and then use \ref RxPipelineNodeSendConfigMsg
 * to request information on their requirements.
 *
 * Nodes receive messages via their configmsghandler method. The return value
 * of \ref RxPipelineNodeSendConfigMsg is the node's response, or zero if the
 * message is unserviced.
 *
 * No default msg values are currently defined.
 *
 * The use of this function (and the related callback types,
 * \ref RxPipelineNodeConfigFn and \ref RxConfigMsgHandlerFn) is no longer
 * recommended. It may be removed from the library in subsequent versions.
 *
 * \param dest  Destination node for the message.
 * \param msg  Message ID.
 * \param intparam  Meaning is message-specific.
 * \param ptrparam  Meaning is message-specific.
 *
 * \return 0: unserviced; -ve: error; +ve: informative success
 *
 * \see RxPipelineNodeForAllConnectedOutputs
 * \see RxPipelineNodeGetInitData
 * \see RxLockedPipeUnlock
 */
RwUInt32
RxPipelineNodeSendConfigMsg(RxPipelineNode * dest,
                            RwUInt32 msg,
                            RwUInt32 intparam, void *ptrparam)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeSendConfigMsg"));

    RWASSERT(dest != NULL);

    if (dest != NULL)
    {
        RxConfigMsgHandlerFn configmsghandler =
            dest->nodeDef->nodeMethods.configMsgHandler;

        if (configmsghandler != NULL)
        {
            RwUInt32            result;

            result = configmsghandler(dest, msg, intparam, ptrparam);

            /* 0: unserviced;
             * -ve: error;
             * +ve: informative success
             */
            RWRETURN(result);
        }
        else
        {
            RWRETURN(0);       /* message unserviced */
        }
    }

    RWRETURN((RwUInt32) - 1);  /* error: dest == NULL */
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeForAllConnectedOutputs
 * enumerates over the connected outputs of a node.
 *
 * Enumeration function. The callback is executed on every connected output
 * of the specified node. The callback may return NULL to terminate
 * enumeration, or a non-NULL value to continue.
 *
 * Expected use is in pipelinenodeconfig methods.
 *
 * Callback prototype:
 *
 * \verbatim
   typedef RxPipelineNode *
   (*RxPipelineNodeOutputCallBack)(RxPipelineNode *node,
                                   RxPipelineNode *outputnode,
                                   void *callbackdata);
   \endverbatim
 *
 * \param node  A pointer to the pipeline node.
 * \param pipeline  A pointer to pipeline node's containing pipeline.
 * \param callbackfn  Per-connected-output callback function.
 * \param callbackdata  User data for enumeration - passed into callback function.
 *
 * \return A pointer to the specified pipeline node on success, NULL otherwise.
 *
 * \see RxPipelineNodeSendConfigMsg
 */
RxPipelineNode     *
RxPipelineNodeForAllConnectedOutputs(RxPipelineNode * node,
                                     RxPipeline * pipeline,
                                     RxPipelineNodeOutputCallBack
                                     callbackfn, void *callbackdata)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineNodeForAllConnectedOutputs"));

    RWASSERT(node != NULL);
    RWASSERT(pipeline != NULL);
    /* node within pipeline...? */
    RWASSERT((node >= &pipeline->nodes[0]) &&
             (node < &pipeline->nodes[pipeline->numNodes]));
    RWASSERT(callbackfn != NULL);

    if ((node != NULL) &&
        (pipeline != NULL) &&
        (node >= &pipeline->nodes[0]) &&
        (node < &pipeline->nodes[pipeline->numNodes]) &&
        (callbackfn != NULL))
    {
        RwUInt32            n;

        for (n = 0; n < node->numOutputs; n++)
        {
            RwUInt32            outputindex = node->outputs[n];

            /* unconnected outputs are signified by
             * node index == (RwUInt32) -1 [see _NodeCreate()] */
            if (outputindex < pipeline->numNodes)
            {
                if (callbackfn(node,
                               &pipeline->nodes[outputindex],
                               callbackdata) == NULL /* enumeration early out */
                    )
                {
                    break;
                }
            }
        }

        RWRETURN(node);
    }

    RWRETURN((RxPipelineNode *)NULL);
}

void
_rwPipelineCheckForTramplingOfNodePrivateSpace(RxPipeline * pipeline)
{
    RWFUNCTION(RWSTRING("_rwPipelineCheckForTramplingOfNodePrivateSpace"));

    RWASSERT(pipeline != NULL);

    if (pipeline != NULL)
    {
        RwUInt32            n;

        for (n = 0; n < pipeline->numNodes; n++)
        {
            RxPipelineNode     *node = &pipeline->nodes[n];

            /* okay, let's see what this node's up to with its private space */

            if (node->privateData != NULL)
            {
                RwUInt32            privatedatasize =

                    node->nodeDef->pipelineNodePrivateDataSize;
                RwUInt32            readmagic;
                RwBool              tramplingofnodeprivatespace;

                /* try and read back magic written in p2dep.c, _ForAllNodesWriteClusterAllocations() */

                readmagic =
                    *(RwUInt32 *) (((RwUInt8 *) node->privateData) +
                                   ((privatedatasize + 3U) & ~3U));

                tramplingofnodeprivatespace =
                    (readmagic != RXNODEPRIVATESPACEMAGIC);

                RWASSERT(!tramplingofnodeprivatespace);
            }
        }
    }

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineNodeGetPipelineCluster 
 * gets a pipeline cluster from a pipeline node.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData),
 * the cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes.
 *
 * \ref RxPipelineNodeGetPipelineCluster is most commonly used in a node's
 * pipelinenodeinit method, as a precursor to modifying a cluster's
 * creationAttributes.
 *
 * \param node  Pipeline node.
 * \param clustersOfInterestIndex  The cluster's index within the node's
 * RxNodeDefinition.io.clustersOfInterest array
 *
 * \return A pointer to the pipeline cluster on success, otherwise NULL
 *
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RxPipelineCluster  *
RxPipelineNodeGetPipelineCluster(RxPipelineNode * node,
                                 RwUInt32 clustersOfInterestIndex)
{
    RxPipelineCluster  *result = (RxPipelineCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineNodeGetPipelineCluster"));

    /* validate parameters */

    RWASSERT(node != NULL);
    RWASSERT(clustersOfInterestIndex <
             node->nodeDef->io.numClustersOfInterest);

    if (node != NULL &&
        clustersOfInterestIndex <
        node->nodeDef->io.numClustersOfInterest)
    {
        RwUInt32            packetSlotIndex =

            node->inputToClusterSlot[clustersOfInterestIndex];

        if (packetSlotIndex != ((RwUInt32) - 1))
        {
            result = node->slotClusterRefs[packetSlotIndex];

            RWASSERT(result != NULL); /* should have succeeded */
        }
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineClusterGetCreationAttributes 
 * gets creation attributes from a pipeline cluster.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData), the
 * cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes. Nodes may subsequently modify the
 * cluster's attributes as the packet flows down the pipe.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PS2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values
 * are constructed from the PS2 cluster attributes (CL_ATTRIB_OPAQUE,
 * CL_V4_16, etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the pipeline cluster to
 * retrieve creation attributes from.
 *
 * \return -1: failure; >= 0: the cluster's creationAttributes
 *
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RwUInt32
RxPipelineClusterGetCreationAttributes(RxPipelineCluster * cluster)
{
    RwUInt32            result = (RwUInt32) - 1; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineClusterGetCreationAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        result = cluster->creationAttributes; /* ... a result */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineClusterSetCreationAttributes
 *  sets creation attributes.
 *
 * Each pipeline maintains a collection of \ref RxPipelineCluster records,
 * one for each cluster used by the pipeline.
 *
 * An \ref RxPipelineCluster record holds a creationAttributes value - when
 * a cluster is created in the pipeline (\ref RxClusterInitializeData), the
 * cluster's attributes field is initialized from
 * RxPipelineCluster.creationAttributes. nodes may subsequently modify the
 * cluster's attributes as the packet flows down the pipe.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PS2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values are
 * constructed from the PS2 cluster attributes (CL_ATTRIB_OPAQUE, CL_V4_16,
 * etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  The pipeline cluster to set creation attributes of.
 * \param creationAttributes  New creation attributes value.
 *
 * \return A pointer to the modified pipeline cluster on success, otherwise NULL.
 *
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 */

RxPipelineCluster  *
RxPipelineClusterSetCreationAttributes(RxPipelineCluster * cluster,
                                       RwUInt32 creationAttributes)
{
    RxPipelineCluster  *result = (RxPipelineCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxPipelineClusterSetCreationAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        cluster->creationAttributes = creationAttributes;

        result = cluster;      /* success */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterGetAttributes
 * gets the attributes of a cluster.
 *
 * Each \ref RxCluster 
 * in each \ref RxPacket 
 * has an attributes field.
 * \ref RxClusterInitializeData initializes the attributes field to a
 * creationAttributes value specific to the cluster and the pipeline
 * in which creation occurs. Using RxClusterGetAttributes or
 * \ref RxClusterSetAttributes, this attributes value may be read and
 * modified as the packet flows down the pipeline.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PS2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values are
 * constructed from the PS2 cluster attributes (CL_ATTRIB_OPAQUE, CL_V4_16,
 * etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the cluster to retrieve attributes from.
 *
 * \return -1: failure; >= 0: the attributes of the cluster
 *
 * \see RxClusterSetAttributes
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 */
RwUInt32
RxClusterGetAttributes(RxCluster * cluster)
{
    RwUInt32            result = (RwUInt32) - 1; /* failure */

    RWAPIFUNCTION(RWSTRING("RxClusterGetAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        result = cluster->attributes; /* ... a result */
    }

    RWRETURN(result);
}

/**
 * \ingroup rwcoregeneric
 * \ref RxClusterSetAttributes 
 * sets the attributes of a cluster.
 *
 * Each \ref RxCluster 
 * in each \ref RxPacket 
 * has an attributes field.
 * \ref RxClusterInitializeData initializes the attributes field to a
 * creationAttributes value specific to the cluster and the pipeline
 * in which creation occurs. Using the \ref RxClusterGetAttributes and
 * \ref RxClusterSetAttributes functions, this attributes value may be
 * read and modified as the packet flows down the pipeline.
 *
 * Interpretation of the cluster attributes field is with reference to the
 * cluster's attributeSet identifier. For example, PS2 specific pipelines
 * tend to employ clusters with the attributeSet identifier "PS2"; given
 * the attributeSet identifier "PS2", we know that the attribute values are
 * constructed from the PS2 cluster attributes (CL_ATTRIB_OPAQUE, CL_V4_16,
 * etc.).
 *
 * It is good practice for a node's pipelinenodeinit/pipelinenodeconfig
 * methods to test a cluster's attributeSet identifier before reading or
 * writing attribute values (e.g.
 * RxPipelineClusterAssertAttributeSet(cl, RWSTRING("PS2"))).
 *
 * \param cluster  A pointer to the cluster to set attributes of.
 * \param attributes  New attributes value.
 *
 * \return A pointer to the modified cluster on success, otherwise NULL.
 *
 * \see RxPipelineNodeGetPipelineCluster
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 */

RxCluster          *
RxClusterSetAttributes(RxCluster * cluster, RwUInt32 attributes)
{
    RxCluster          *result = (RxCluster *)NULL; /* failure */

    RWAPIFUNCTION(RWSTRING("RxClusterSetAttributes"));

    RWASSERT(cluster != NULL);

    if (cluster != NULL)
    {
        cluster->attributes = attributes;

        result = cluster;      /* success */
    }

    RWRETURN(result);
}

#if ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) )

static void
ExecuteNode(RxPipeline * _pipeline,
            RxPipelineNode * _node, 
            RxPipelineNodeParam * _params)
{
    RWFUNCTION(RWSTRING("ExecuteNode"));

    _rxExecuteNodeMacro(_pipeline, _node, _params);

    RWRETURNVOID();
}

#else /* ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) ) */

#define ExecuteNode(_pipeline, _node, _params) \
    _rxExecuteNodeMacro(_pipeline, _node, _params)

#endif /* ( ( defined(RWDEBUG) || defined(RWSUPPRESSINLINE) || defined(EXECUTENODEFUNCTION) ) ) */

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineExecute
 * executes a pipeline.
 *
 * The \ref RxPipeline object describes a directed acylic graph of nodes,
 * which must be in an unlocked state for execution [\ref RxPipelineLock
 * and \ref RxLockedPipeUnlock calls bracket edits of the DAG; inside the
 * \ref RxLockedPipeUnlock call, analysis is made of data flow through the
 * pipeline, which is then used to optimize \ref RxPipelineExecute
 * performance].
 *
 * Execution begins at the head node of the pipeline, proceeding downwards
 * through the flattened ("topologically sorted") pipeline. Parameter "data"
 * is passed into each node. Typically, a node near the top of the pipeline
 * will process the object referenced by this data pointer to generate
 * one or more \ref RxPacket's, 
 * which are then directed down the graph for further processing.
 *
 * \ref RxPipelineNode's may invoke other pipelines by dispatching \ref RxPacket s
 * with the inter-DAG \ref RxPacketDispatchToPipeline function rather than the
 * \ref RxPacketDispatch intra-DAG function [which outputs the \ref RxPacket to
 * the node connected to output "outputIndex"].
 *
 * Pipeline execution proceeds in a nested manner such that inside a dispatch,
 * the \ref RxPacket in fact proceeds along the rest of the pipeline and is
 * destroyed explicitly by a node or automatically on dispatch to an
 * unconnected node output or on exit of the terminal node. Hence, a dispatch
 * is equivalent to calling \ref RxPacketDestroy from the point of view of the
 * calling node - the packet can no longer be used within that node and
 * another must be created. \ref RxPacketDispatchToPipeline and
 * \ref RxPacketDispatch are the only means of causing subsequent nodes to
 * execute (i.e pipeline execution to continue).
 *
 * Nodes executing within a pipeline should use the \ref RxHeap structure
 * (as detailed in \ref RxHeapGetGlobalHeap and \ref RxHeapCreate) to allocate
 * "transient" memory, where "transient" means that the memory can safely be
 * lost as soon as the pipeline execution terminates or sooner (the heap is
 * cleared with a call to \ref RxHeapReset before execution begins, unless the
 * heapReset parameter to \ref RxPipelineExecute is FALSE, as used by
 * \ref RwIm3DTransform for example). Allocations of cluster memory, for
 * instance (through \ref RxClusterInitializeData and \ref RxClusterResizeData),
 * use the pipeline execution heap.
 *
 * The heap attached to the current pipeline execution is accessible via
 * the \ref RxPipelineNodeParam parameter of pipeline node \ref RxNodeBodyFn's,
 * with the aid of \ref RxPipelineNodeParamGetHeap. While there is currently
 * one global heap, the system may in the future be extended to use different
 * heaps for each pipeline execution, such that many pipelines could be
 * executed simultaneously on multiprocessor systems.
 *
 * Early termination: the nested execution model descibed above means that
 * terminating pipeline execution is achieved merely by not dispatching to
 * a subsequent node or pipeline. However, there is no guarantee that nodes
 * above the current node will not create and dispatch further packets. So,
 * if a node returns FALSE (signifying an error) then pipeline execution will
 * terminate as soon as all nodes above it can exit and no further dispatches
 * will be allowed.
 *
 * If the pipeline (or any invoked pipelines) terminates with an error,
 * RxPipelineExecute will return NULL.
 *
 * \param pipeline  A pointer to the pipeline to execute.
 * \param data  A pointer to data to pass to node body
 * methods (the purpose is pipeline-specific).
 * \param heapReset  TRUE in normal usage; FALSE to carry over heap
 * contents from the previous pipeline execution, i.e not
 * call \ref RxHeapReset before executing.
 *
 * \verbatim
   Invoke the default atomic pipeline on "atomic". All things being equal, the atomic
   pipeline will generate atomic->numMaterialsUsed packets, each packet corresponding to
   one RwMaterial, and containing the subset of the atomic's geometry that specifies that material.
   These packets will then be scattered to material pipelines (see RpMaterialSetPipeline,
   RpMaterialSetDefaultPipeline) for transform, lighting and rendering. 
   if ( !RxPipelineExecute(defaultAtomicPipeline, (void *) atomic, TRUE) )
   {
           // pipeline execution incurred an error
   }
   \endverbatim
 *
 * \return A pointer to the \ref RxPipeline executed if it runs to completion
 * without error, otherwise NULL
 *
 * \see RxPipelineCreate
 * \see RxPipelineDestroy
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxLockedPipeAddFragment
 */
RxPipeline         *
RxPipelineExecute(RxPipeline * pipeline, void *data, RwBool heapReset)
{
    RxPipelineNode     *node;

    RWAPIFUNCTION(RWSTRING("RxPipelineExecute"));
    RWASSERT(NULL != pipeline);

    /* We're not threadsafe! */
    RWASSERT(_rxExecCtxGlobal.pipeline == NULL);

    if (FALSE != heapReset)
    {
        RxHeapReset(_rxHeapGlobal);
    }

    _rxExecCtxGlobal.exitCode = TRUE;
    _rxExecCtxGlobal.pipeline = pipeline;
    _rxExecCtxGlobal.params.dataParam = data;
    /* Yay! This can now be changed per pipeline execute!
     * The vital step in enabling multithreadable PowerPipe */
    _rxExecCtxGlobal.params.heap = RxHeapGetGlobalHeap();
    RWASSERT(NULL != _rxExecCtxGlobal.params.heap);

    /* Check the pipeline isn't being executed already */
    RWASSERT(pipeline->embeddedPacketState <= rxPKST_UNUSED);
    /* OI!! TODO: You should check the pipeline to see if it
     * has any (non-optional) input requirements - if so
     * output "Pipeline has non-optional input requirements,
     * it can only be called from another pipeline"
     * Now that runto/runfrom are gone, this is OK */

    /* No packet created yet */
    pipeline->embeddedPacketState = rxPKST_PACKETLESS;

    node = &pipeline->nodes[0];

    ExecuteNode(pipeline, node, &(_rxExecCtxGlobal.params));

    _rxExecCtxGlobal.pipeline = (RxPipeline *)NULL;
    _rxExecCtxGlobal.params.dataParam = NULL;
    _rxExecCtxGlobal.params.heap = (RxHeap *)NULL;

    if (FALSE != _rxExecCtxGlobal.exitCode)
    {
        RWRETURN(pipeline);
    }

    RWRETURN((RxPipeline *)NULL);
}


/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineCreate 
 * creates an \ref RxPipeline.
 *
 * \ref RxPipelineCreate may return NULL in the event of insufficient memory.
 *
 * \verbatim
   The following code creates a worldsector object pipeline containing
   the three library nodes WorldSectorInstance.csl,
   WorldSectorEnumerateLights.csl and MaterialScatter.csl. 
   RxPipeline *
   CreateWorldSectorPipeline(void)
   {
           RxPipeline *pipe;
           RxLockedPipe *lpipe;

           pipe = RxPipelineCreate();
           if ( pipe != NULL )
           {
                   lpipe = RxPipelineLock(pipe);
                   if ( lpipe != NULL )
                   {
                           if ( RxLockedPipeAddFragment(lpipe, NULL,
                                                        RxNodeGetNodeWorldSectorInstance(),
                                                        RxNodeGetNodeWorldSectorEnumerateLights(),
                                                        RxNodeGetNodeMaterialScatter(),
                                                        NULL) )
                           {
                                   if ( RxLockedPipeUnlock(lpipe) )
                                   {
                                           return (pipe); // success
                                   }
                           }
                   }

                   // fall through to here for cleanup if pipeline was
                   // Create()d okay, but one of the construction calls
                   // [Lock(), AddFragment(), Unlock()] has failed

                   _rxPipelineDestroy(pipe);
           }

           return NULL; // failure
   }
   \endverbatim
 *
 * \return A pointer to the created pipeline on success, otherwise NULL.
 *
 * \see RxPipelineDestroy
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxPipelineExecute
 */
RxPipeline         *
RxPipelineCreate(void)
{
    RxPipeline         *pipeline;

    RWAPIFUNCTION(RWSTRING("RxPipelineCreate"));

    pipeline = (RxPipeline *) RwMalloc(sizeof(RxPipeline));

    if (pipeline != NULL)
    {
        memset(pipeline, 0x00U, sizeof(RxPipeline));

        /* We shouldn't assume this is zero I guess */
        pipeline->locked = FALSE;

        RWRETURN(pipeline);
    }
    RWERROR((E_RW_NOMEM, sizeof(RxPipeline)));

    RWRETURN((RxPipeline *) NULL);
}

void
_rxPipelineDestroy(RxPipeline * Pipeline)
{
    RWFUNCTION(RWSTRING("_rxPipelineDestroy"));

    if (Pipeline)
    {
        /*
         * If any PipelineNodeInstances reference PipelineNodes in
         * this pipeline or packets reference this pipeline then
         * they're not gonna be happy, but ach who cares...
         */

        /* Destroy all the Nodes */
        RwUInt32            i;
        RxPipelineNode     *Node = Pipeline->nodes;

        for (i = 0; i < Pipeline->numNodes; i++)
        {
            PipelineNodeDestroy(Node, Pipeline);
            Node++;
        }
        Pipeline->numNodes = 0;
        Pipeline->nodes = (RxPipelineNode *)NULL;

        /* Finally destroy the pipeline */
        if (Pipeline->superBlock != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(Pipeline->superBlock));
            RwFree(Pipeline->superBlock);
            Pipeline->superBlock = (RwUInt32 *)NULL;
        }

        if (Pipeline->nodesBlock != NULL)
        {
            RWASSERT(RWCRTISVALIDHEAPPOINTER(Pipeline->nodesBlock));
            RwFree(Pipeline->nodesBlock);
            Pipeline->nodesBlock = NULL;
        }

        RWASSERT(RWCRTISVALIDHEAPPOINTER(Pipeline));
        RwFree(Pipeline);
        Pipeline = (RxPipeline *)NULL;
    }

    RWRETURNVOID();
}

#if (defined(DOXYGEN) || defined(RWDEBUG))

#if (defined(DOXYGEN))

/**
 * \ingroup rwcoregeneric
 * \ref RxPipelineDestroy 
 * destroys a \ref RxPipeline, 
 * freeing all associated resources.
 *
 * Counterpart to \ref RxPipelineCreate.
 *
 * \param  Pipeline   A pointer to the pipeline to destroy.
 *
 * \warning This is infact implemented as a macro
 *
 * \see RxPipelineCreate
 * \see RxPipelineLock
 * \see RxLockedPipeUnlock
 * \see RxPipelineExecute
 */
void
RxPipelineDestroy(RxPipeline * Pipeline)
{
    RWAPIFUNCTION(RWSTRING("RxPipelineDestroy"));
    
    RWRETURNVOID();
}
#endif /* (defined(DOXYGEN)) */


/**
 * \ingroup rwcoregeneric
 * \ref RxPacketFetch 
 * fetches the current packet if there is one,
 * returning a pointer to an \ref RxPacket 
 * or NULL if there is no packet currently.
 *
 * The \ref RxPacketFetch -ed packet should be \ref RxPacketDispatchToPipeline -ed,
 * \ref RxPacketDispatch -ed or \ref RxPacketDestroy -ed. If a node fails to do so,
 * the packet will automatically be freed on exit of the node. Pipeline
 * execution proceeds in a nested manner such that inside a dispatch, the
 * \ref RxPacket in fact proceeds along the rest of the pipeline and is
 * destroyed explicitly by a node or automatically on dispatch to an
 * unconnected node output or on exit of the terminal node. Hence, a dispatch
 * is equivalent to calling \ref RxPacketDestroy from the point of view of the
 * current node - the packet can no longer be used within this node and
 * another must be created.
 *
 * The convention is to give terminal nodes an output to which
 * they dispatch, while recognizing that this output will usually remain
 * unconnected - dispatch to an unconnected output causes the packet to be
 * destroyed.
 *
 * \ref RxPacketFetch should only be called once within a node, given the
 * nested pipeline execution structure described above. \ref RxPacketDispatch
 * and \ref RxPacketDispatchToPipeline are the only mechanisms to progress
 * along a pipeline and they pass along at most one \ref RxPacket.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param Node  A pointer to the calling pipeline node
 *
 * \verbatim
   \ref RxPacketFetch is used within node body methods. This example
   shows the bones of a typical node. 
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatch(pk, 0, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \return A pointer to the current packet or NULL if there isn't one.
 *
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
RxPacket           *
RxPacketFetch(RxPipelineNode * Node __RWUNUSED__)
{
    RWAPIFUNCTION(RWSTRING("RxPacketFetch"));

    /* Has a packet been dispatched to the current node? */
    if ((_rxExecCtxGlobal.pipeline)->embeddedPacketState == rxPKST_PENDING)
    {
        (_rxExecCtxGlobal.pipeline)->embeddedPacketState = rxPKST_INUSE;
        RWRETURN((_rxExecCtxGlobal.pipeline)->embeddedPacket);
    }

    /* The packet should be in the PACKETLESS or UNUSED states atm */
    RWASSERT((_rxExecCtxGlobal.pipeline)->embeddedPacketState != rxPKST_INUSE);

    /* There is no packet to fetch! */
    RWRETURN((RxPacket *)NULL);
}

#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

#if (defined(DOXYGEN))

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDestroy 
 * destroys a packet.
 *
 * Data for all valid clusters is also freed, excepting "external"
 * [by reference] clusters. \ref RxPacketCreate must be called in
 * order to do further \ref RxPacket
 * processing in the current node.
 *
 * \param Packet  A pointer to the packet to destroy.
 *
 * \verbatim
   RxPacketDestroy is used within node body methods. This example
   shows the bones of an atypical node. 
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           RxPacketDestroy(pk, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \see RxPacketCreate
 * \see RxPacketFetch
 * \see RxPacketDispatch
 * \see RxPacketDispatchToPipeline
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDestroy(RxPacket * Packet)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDestroy"));

    RWRETURNVOID();
}

#endif /* (defined(DOXYGEN)) */

#if (defined(DOXYGEN) || defined(RWDEBUG))

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDispatch 
 * dispatches an \ref RxPacketFetch -ed 
 * or \ref RxPacketCreate -ed packet to
 *  an output of the current node.
 *
 * \ref RxPacketDispatch dispatches packets to one of the outputs of the
 * current node [\ref RxPacketDispatchToPipeline is used to dispatch packets
 * to another pipeline].
 *
 * If a packet is dispatched to an unconnected output, it will be destroyed.
 * NOTE, however, that this happens anyway from the point of view of the
 * calling node due to the nested nature of pipeline execution - when
 * \ref RxPacketDispatch is called, the packet is sent down the rest of
 * the pipeline within this call and will have been destroyed [as described
 * in \ref RxPacketFetch] by the time the dispatch function returns (you can
 * no longer operate on this packet, you will need to \ref RxPacketCreate
 * another).
 *
 * If the current node has not \ref RxPacketCreate -ed or \ref RxPacketFetch -ed a
 * packet then it is valid to pass NULL as the packet parameter to this
 * function (if a packet exists and you simply haven't checked, it will be
 * passed on anyway).
 *
 * As noted in the documentation for \ref RxPacketFetch, you should not
 * \ref RxPacketCreate or \ref RxPacketFetch a packet unless you subsequently
 * \ref RxPacketDispatch, \ref RxPacketDispatchToPipeline or \ref RxPacketDestroy
 * it.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param packet  A pointer to the packet to dispatch.
 * \param output  RwInt32, the number of the output to dispatch the packet to.
 * \param self    A pointer to the calling \ref RxPipelineNode
 *
 * \verbatim
   RxPacketFetch is used within node body methods. This example
   shows the bones of a typical node. 
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatch(pk, 0, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \see RxPacketDispatchToPipeline
 * \see RxPacketFetch
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDispatch(RxPacket * packet __RWUNUSED__,
                 RwUInt32 output, RxPipelineNode * self)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDispatch"));

    rxPacketDispatchMacro(packet, output, self);

    RWRETURNVOID();
}

/**
 * \ingroup rwcoregeneric
 * \ref RxPacketDispatchToPipeline 
 * dispatches a \ref RxPacketFetch -ed
 * or \ref RxPacketCreate -ed packet to an output of the current node.
 *
 * \ref RxPacketDispatchToPipeline dispatches packets to another pipeline
 * [\ref RxPacketDispatch is used to dispatch packets to one of the outputs
 * of the current node].
 *
 * When \ref RxPacketDispatchToPipeline is called, the packet is sent
 * down the subsequent pipeline immediately due to the nested nature of
 * pipeline execution - when \ref RxPacketDispatchToPipeline is called,
 * the packet is sent down the destination pipeline within this call and
 * will have been destroyed [as described in \ref RxPacketFetch] by the
 * time the dispatch function returns (you can no longer operate on this
 * packet, you will need to \ref RxPacketCreate another).
 *
 * If the current node has not \ref RxPacketCreate -ed or \ref RxPacketFetch -ed a
 * packet then it is valid to pass NULL as the packet parameter to this
 * function (if a packet exists and you simply haven't checked, it will be
 * passed on anyway).
 *
 * As noted in the documentation for \ref RxPacketFetch, it is an error to
 * \ref RxPacketCreate or \ref RxPacketFetch a packet and not to subsequently
 * \ref RxPacketDispatch, \ref RxPacketDispatchToPipeline or \ref RxPacketDestroy
 * it.
 *
 * Note that this function is used for debug purposes only and, for
 * efficiency, is available as a macro for final release versions of an
 * application.
 *
 * \param  packet   A pointer to the packet to dispatch.
 * \param  dest   A pointer to the destination pipeline.
 * \param  self   A pointer to the calling \ref RxPipelineNode
 *
 * \verbatim
   RxPacketFetch is used within node body methods. This example
   shows the bones of a typical node. 
   {
           RxPacket *pk;

           pk = RxPacketFetch(self);
           RWASSERT (NULL != pk);

           ... // process packet

           // dispatch packet to output 0 (default)
           RxPacketDispatchToPipeline(pk, fancyPipeline, self);

           return(TRUE);
   }
   \endverbatim
 *
 * \see RxPacketDispatch
 * \see RxPacketFetch
 * \see RxPacketCreate
 * \see RxPacketDestroy
 * \see RxClusterLockRead
 * \see RxClusterLockWrite
 */
void
RxPacketDispatchToPipeline(RxPacket * packet __RWUNUSED__,
                           RxPipeline * dest,
                           RxPipelineNode * __RWUNUSED__ self)
{
    RWAPIFUNCTION(RWSTRING("RxPacketDispatchToPipeline"));

    rxPacketDispatchToPipelineMacro(packet, dest, self);

    RWRETURNVOID();
}

#endif /* (defined(DOXYGEN) || defined(RWDEBUG)) */

