/*
 * Memory tracking plugin
 */

/**
 * \ingroup rpmeminfo
 * \page rpmeminfooverview RpMemInfo Plugin Overview 
 *
 * The RpMemInfo plug-in provides a set of system memory metrics
 * functions. RpMemInfo provides a suite of functions to help you
 * monitor the allocation and deallocation of your target platform’s
 * memory. It uses a stack-based system which associates memory blocks
 * with user-defined IDs to let you keep track of where and how the
 * memory is being used.
 * 
 * Copyright Criterion Software Limited
 */

/**********************************************************************
 *
 * File :     rpmeminf.c
 *
 * Abstract : Memory tracking support
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

/*--- Include files ---*/

#include <stdlib.h>

#include "rpplugin.h"
#include <rpdbgerr.h>
#include "rpmeminf.h"          /* Memory info structures and functions */

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: rpmeminf.c,v 1.19 2001/02/02 14:38:28 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

#define RWMEMINFOGLOBAL(var)                            \
    (RWPLUGINOFFSET(rpMemInfoGlobals,                   \
                    RwEngineInstance,                   \
                    memInfoModule.globalsOffset)->var)

typedef struct _rpMemInfoGlobals rpMemInfoGlobals;
struct _rpMemInfoGlobals
{
    RwMemoryFunctions   oldMemFns;
    RpMemInfoList      *curList;
};

#if (defined(RWDEBUG))
long                rpMemInfStackDepth = 0;
#endif /* (defined(RWDEBUG)) */

#define oldMalloc(_size)                                        \
   ((RWMEMINFOGLOBAL(oldMemFns).rwmalloc)((_size)))

#define oldCalloc(_count, _size)                                 \
   ((RWMEMINFOGLOBAL(oldMemFns).rwcalloc)((_count), (_size)))

#define oldRealloc(_ptr, _size)                                 \
   ((RWMEMINFOGLOBAL(oldMemFns).rwrealloc)((_ptr), (_size)))

#define oldFree(_ptr)                                           \
   ((RWMEMINFOGLOBAL(oldMemFns).rwfree)((_ptr)))

static RwModuleInfo memInfoModule;

/************************************************************************
 *
 *      Function:       MemInfoGrowList()
 *
 *      Description:    Grows a memory logging list to accomodate additional
 *                      entries. 
 *                      Growth is exponential after initial allocation.
 *
 *      Parameters:     List to grow
 *
 *      Return Value:   Pointer to the list if it has been successfully grown.
 *
 ************************************************************************/
static void        *
MemInfoGrowList(RpMemInfoList * list)
{
    RpMemInfoBlockDescriptor *newMemBlocks;
    size_t              newBlockQuantity;

    RWFUNCTION(RWSTRING("MemInfoGrowList"));

    /* Go directly to the unchained memory allocator 
     * so we don't go recursive...! */
    if (list->memBlocks)
    {
        /* Double the size */
        newBlockQuantity = list->numAllocatedBlocks * 2;
        newMemBlocks = (RpMemInfoBlockDescriptor *)
            oldRealloc(list->memBlocks,
                       newBlockQuantity *
                       sizeof(RpMemInfoBlockDescriptor));
    }
    else
    {
        /* Start with 16 blocks (will grow exponentially) */
        newBlockQuantity = 16;
        newMemBlocks = (RpMemInfoBlockDescriptor *)
            oldMalloc(newBlockQuantity *
                      sizeof(RpMemInfoBlockDescriptor));
    }

    if (newMemBlocks)
    {
        list->memBlocks = newMemBlocks;
        list->numAllocatedBlocks = newBlockQuantity;
        RWRETURN((list));
    }

    RWRETURN((NULL));
}

/************************************************************************
 *
 *      Function:       MemInfoGrowAllocatorStack()
 *
 *      Description:    Grows a the allocator ID stack to accomodate additional
 *                      entries. 
 *                      Growth is exponential after initial allocation.
 *
 *      Parameters:     Stack to grow
 *
 *      Return Value:   Pointer to the stack if it has been successfully grown.
 *
 ************************************************************************/
static void        *
MemInfoGrowAllocatorStack(RpMemInfoAllocatorStack * allocatorStack)
{
    RwUInt32           *newStack;
    size_t              newStackSize;

    RWFUNCTION(RWSTRING("MemInfoGrowAllocatorStack"));

    /* Go directly to the unchained memory allocator 
     * so we don't go recursive...! */
    if (allocatorStack->stack)
    {
        /* Double the size */
        newStackSize = allocatorStack->stackSize * 2;
        newStack = (RwUInt32 *)
            oldRealloc(allocatorStack->stack,
                       newStackSize * sizeof(RwUInt32));
    }
    else
    {
        /* Start with 16 blocks (will grow exponentially) */
        newStackSize = 16;
        newStack = (RwUInt32 *)oldMalloc(newStackSize * sizeof(RwUInt32));
    }

    if (newStack)
    {
        allocatorStack->stack = newStack;
        allocatorStack->stackSize = newStackSize;
        RWRETURN((allocatorStack));
    }

    RWRETURN((NULL));
}

/************************************************************************
 *
 *      Function:       MemInfoAllocatorStackTop()
 *
 *      Description:    Returns the top entry of the given stack.
 *
 *      Parameters:     Allocator stack
 *
 *      Return Value:   ID on top of stack.
 *
 ************************************************************************/
static              RwUInt32
MemInfoAllocatorStackTop(RpMemInfoAllocatorStack * allocatorStack)
{
    RWFUNCTION(RWSTRING("MemInfoAllocatorStackTop"));

    if (allocatorStack->top == 0)
        RWRETURN(0);
    else
        RWRETURN((allocatorStack->stack[allocatorStack->top - 1]));
}

/************************************************************************
 *
 *      Function:       MemInfoMalloc()
 *
 *      Description:    Logging malloc fuunction interposed into the RenderWare
 *                      allocation chain.
 *
 *      Parameters:     Size to allocate
 *
 *      Return Value:   Pointer to memory allocated on success.
 *
 ************************************************************************/
static void        *
MemInfoMalloc(size_t size)
{
    RpMemInfoList      *list;
    void               *newMem;

    RWFUNCTION(RWSTRING("MemInfoMalloc"));

    list = RWMEMINFOGLOBAL(curList);
    /* First call the old function to do the memory thing */
    newMem = oldMalloc(size);

    /* Then log it if necessary */
    if (list && newMem)
    {
        /* Do we need to allocate more memory for the list */
        if (list->numBlocks >= list->numAllocatedBlocks)
        {
            if (MemInfoGrowList(list))
            {
                /* Now we have enough space */
                list->memBlocks[list->numBlocks].addr = newMem;
                list->memBlocks[list->numBlocks].size = size;
                list->memBlocks[list->numBlocks].allocatorID =
                    MemInfoAllocatorStackTop(&list->allocatorStack);
                list->numBlocks++;
            }
        }
        else
        {
            /* Else just whack it in there */
            list->memBlocks[list->numBlocks].addr = newMem;
            list->memBlocks[list->numBlocks].size = size;
            list->memBlocks[list->numBlocks].allocatorID =
                MemInfoAllocatorStackTop(&list->allocatorStack);
            list->numBlocks++;
        }

        /* Update our allocation counters */
        list->currentUsage += size;
        if (list->currentUsage > list->maxUsage)
        {
            list->maxUsage = list->currentUsage;
        }
    }

    /* Return to sender */
    RWRETURN((newMem));
}

/************************************************************************
 *
 *      Function:       MemInfoRealloc()
 *
 *      Description:    Logging realloc function interposed into 
 *                      the RenderWare allocation chain.
 *
 *      Parameters:     Memory to resize, Size to re-allocate to
 *
 *      Return Value:   Pointer to memory allocated on success.
 *
 ************************************************************************/
static void        *
MemInfoRealloc(void *mem, size_t newSize)
{
    RpMemInfoList      *list;
    void               *newMem;

    RWFUNCTION(RWSTRING("MemInfoRealloc"));

    list = RWMEMINFOGLOBAL(curList);
    /* First call the old function to do the memory thing */
    newMem = oldRealloc(mem, newSize);

    /* Then log it if necessary */
    if (list && newMem)
    {
        RwUInt32            i;

        /* See if it's in the list, and replace it if it is */
        for (i = 0; i < list->numBlocks; i++)
        {
            if (list->memBlocks[i].addr == mem)
            {
                /* Update our allocation counters */
                list->currentUsage += newSize - list->memBlocks[i].size;
                if (list->currentUsage > list->maxUsage)
                {
                    list->maxUsage = list->currentUsage;
                }

                list->memBlocks[i].addr = newMem;
                list->memBlocks[i].size = newSize;
                list->memBlocks[i].allocatorID =
                    MemInfoAllocatorStackTop(&list->allocatorStack);
            }
        }
    }

    /* Return to sender */
    RWRETURN((newMem));
}

/************************************************************************
 *
 *      Function:       MemInfoCalloc()
 *
 *      Description:    Logging calloc fuunction interposed into the RenderWare
 *                      allocation chain.
 *
 *      Parameters:     Size to allocate
 *
 *      Return Value:   Pointer to memory allocated on success.
 *
 ************************************************************************/
static void        *
MemInfoCalloc(size_t numObj, size_t sizeObj)
{
    RpMemInfoList      *list;
    void               *newMem;

    RWFUNCTION(RWSTRING("MemInfoCalloc"));

    list = RWMEMINFOGLOBAL(curList);
    /* First call the old function to do the memory thing */
    newMem = oldCalloc(numObj, sizeObj);

    /* Then log it if necessary */
    if (list && newMem)
    {
        /* Do we need to allocate more memory for the list */
        if (list->numBlocks >= list->numAllocatedBlocks)
        {
            if (MemInfoGrowList(list))
            {
                /* Now we have enough space */
                list->memBlocks[list->numBlocks].addr = newMem;
                list->memBlocks[list->numBlocks].size =
                    numObj * sizeObj;
                list->memBlocks[list->numBlocks].allocatorID =
                    MemInfoAllocatorStackTop(&list->allocatorStack);
                list->numBlocks++;
            }
        }
        else
        {
            /* Else just whack it in there */
            list->memBlocks[list->numBlocks].addr = newMem;
            list->memBlocks[list->numBlocks].size = numObj * sizeObj;
            list->memBlocks[list->numBlocks].allocatorID =
                MemInfoAllocatorStackTop(&list->allocatorStack);
            list->numBlocks++;
        }

        /* Update our allocation counters */
        list->currentUsage += numObj * sizeObj;
        if (list->currentUsage > list->maxUsage)
        {
            list->maxUsage = list->currentUsage;
        }
    }

    /* Return to sender */
    RWRETURN((newMem));
}

/************************************************************************
 *
 *      Function:       MemInfoFree()
 *
 *      Description:    Logging free fuunction interposed into the RenderWare
 *                      allocation chain.
 *
 *      Parameters:     Memory to de-allocate
 *
 *      Return Value:   Nothing
 *
 ************************************************************************/
static void
MemInfoFree(void *mem)
{
    RpMemInfoList      *list;

    RWFUNCTION(RWSTRING("MemInfoFree"));

    list = RWMEMINFOGLOBAL(curList);

    /* First call the old function to do the memory thing */
    oldFree(mem);

    /* Then log it if necessary */
    if (RWMEMINFOGLOBAL(curList))
    {
        RwUInt32            i;

        /* See if it's in the list, and remove it if it is */
        for (i = 0; i < list->numBlocks; i++)
        {
            if (list->memBlocks[i].addr == mem)
            {
                /* Adjust the memory allocated counter */
                list->currentUsage -= list->memBlocks[i].size;

                /* Copy the last element down */
                list->memBlocks[i] =
                    list->memBlocks[list->numBlocks - 1];
                list->numBlocks--;
            }
        }
    }
    RWRETURNVOID();
}

/************************************************************************
 *
 *      Function:       openMeminfoPlugin()
 *
 *      Description:    Plugin initialisation function - interposes the logging
 *                      functions.
 *
 *      Parameters:     RenderWare instance, data offset and size
 *
 *      Return Value:   instance pointer on success, NULL on failure
 *
 ************************************************************************/
static void        *
openMeminfoPlugin(void *instance, RwInt32 offset,
                  RwInt32 __RWUNUSED__ size)
{
    RwMemoryFunctions  *memFns;

    RWFUNCTION(RWSTRING("openMeminfoPlugin"));
    RWASSERT(instance);

    /* Cache the globals offset */
    memInfoModule.globalsOffset = offset;

    /* Get the old memory functions */
    memFns = RwOsGetMemoryInterface();
    if (!memFns)
    {
        /* Failed to find the old memory interface */
        RWRETURN(NULL);
    }

    /* And cache them for chaining */
    RWMEMINFOGLOBAL(oldMemFns) = *memFns;

    /* Install the new ones */
    memFns->rwmalloc = MemInfoMalloc;
    memFns->rwrealloc = MemInfoRealloc;
    memFns->rwcalloc = MemInfoCalloc;
    memFns->rwfree = MemInfoFree;

    /* We're not currently adding to any list */
    RWMEMINFOGLOBAL(curList) = (RpMemInfoList *)NULL;

    /* One more instance */
    memInfoModule.numInstances++;

    RWRETURN(instance);
}

/************************************************************************
 *
 *      Function:       closeMemInfoPlugin()
 *
 *      Description:    Plugin de-initialisation function - puts things back
 *                      the way they were.
 *
 *      Parameters:     RenderWare instance, data offset and size
 *
 *      Return Value:   instance pointer on success, NULL on failure
 *
 ************************************************************************/
static void        *
closeMemInfoPlugin(void *instance,
                   RwInt32 __RWUNUSED__ offset,
                   RwInt32 __RWUNUSED__ size)
{
    RwMemoryFunctions  *memFns;

    RWFUNCTION(RWSTRING("closeMemInfoPlugin"));
    RWASSERT(instance);

    /* Re-instate the old functions */
    memFns = RwOsGetMemoryInterface();
    if (!memFns)
    {
        /* Failed to find the old memory interface 
         * - we can't shut down properly */
        RWRETURN(NULL);
    }

    /* Put 'em back */
    *memFns = RWMEMINFOGLOBAL(oldMemFns);

    /* If we were still logging we may have memory allocated */
    if (RWMEMINFOGLOBAL(curList))
    {
        RpMemInfoList      *list = RWMEMINFOGLOBAL(curList);

        /* Free up any memory we allocated for this one */
        if (list->memBlocks)
        {
            oldFree(list->memBlocks);
        }

        list->memBlocks = (RpMemInfoBlockDescriptor *)NULL;
        list->numBlocks = 0;
        list->numAllocatedBlocks = 0;

        /* And the allocator stack */
        if (list->allocatorStack.stack)
        {
            oldFree(list->allocatorStack.stack);
        }

        list->allocatorStack.stack = (RwUInt32 *)NULL;
        list->allocatorStack.stackSize = 0;
        list->allocatorStack.top = 0;
    }

    /* One less instance */
    memInfoModule.numInstances--;

    RWRETURN(instance);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoPluginAttach is used to attach the memory info
 * plug-in to the RenderWare system to enable the tracking of memory
 * allocated by an application. The memory info plug-in must be attached
 * between initializing the system with \ref RwEngineInit and opening it
 * with \ref RwEngineOpen.
 * Note that the include file rpmeminf.h is required and must be included
 * by an application wishing to use this plug-in. The memory info plug-in
 * library is contained in the file rpmeminf.lib.
 *
 * \return TRUE on success, FALSE on failure.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwBool
RpMemInfoPluginAttach(void)
{
    RwInt32             offset;

    RWAPIFUNCTION(RWSTRING("RpMemInfoPluginAttach"));

    offset =
        RwEngineRegisterPlugin(sizeof(rpMemInfoGlobals),
                               rwID_MEMINFOPLUGIN,
                               openMeminfoPlugin, closeMemInfoPlugin);

    if (offset < 0)
    {
        /* Failed to attach plugin */
        RWRETURN(FALSE);
    }

    RWRETURN(TRUE);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoStartTrace is used to start the capture of memory
 * allocation information connected with the use of RenderWare API
 * functions. Used in conjunction with \ref RpMemInfoEndTrace, this
 * function allows an application to control the capture of memory
 * allocation data so that only areas of interest need to be monitored.
 *
 * \param list  Pointer to a memory info list that will hold the location and
 * size of all allocated memory blocks. Memory for the list is allocated
 * by the plug-in as and when required to accommodate the memory data.
 *
 * \return Returns a pointer to the memory info list if successful or
 * NULL if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 */
RpMemInfoList      *
RpMemInfoStartTrace(RpMemInfoList * list)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoStartTrace"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);
    RWASSERT(!RWMEMINFOGLOBAL(curList));

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            if (!RWMEMINFOGLOBAL(curList))
            {
                /* We're now logging memory in this list */
                RWMEMINFOGLOBAL(curList) = list;

                /* So empty the list too */
                list->memBlocks = (RpMemInfoBlockDescriptor *)NULL;
                list->numBlocks = 0;
                list->numAllocatedBlocks = 0;

                /* Setup the allocator stack */
                list->allocatorStack.stack = (RwUInt32 *)NULL;
                list->allocatorStack.stackSize = 0;
                list->allocatorStack.top = 0;

                /* And set our current and max used to 0 */
                list->currentUsage = 0;
                list->maxUsage = 0;

                RWRETURN(list);
            }

            RWERROR((E_RP_MEMINFO_SESSIONSTARTED));
            RWRETURN((RpMemInfoList *)NULL);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpMemInfoList *)NULL);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN((RpMemInfoList *)NULL);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoEndTrace is used to stop the capture of memory
 * allocation information connected with the use of RenderWare API
 * functions. Used in conjunction with RpMemInfoStartTrace, this function
 * allows an application to control the capture of memory data so that
 * only areas of interest need to be monitored.
 * Stopping memory tracing initiates a series of callbacks, one for every
 * memory block allocated by RenderWare. The format of the callback is:
 * 
 * void *(*RpMemInfoCallBack)(void *addr, size_t size);
 * 
 * where addr is the starting address of the block of memory and size is
 * its size in bytes.
 * The callback may return NULL to terminate further callbacks.
 *
 * Note that this function will clear the list of all entries before
 * returning.
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the memory info list used to hold information on all
 * allocated memory blocks.
 * \param callback  Pointer to the callback function to apply to each memory block.
 *
 * \return Returns a pointer to the memory info list if successful or
 * NULL if there is an error.
 *
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RpMemInfoList      *
RpMemInfoEndTrace(RpMemInfoList * list, RpMemInfoCallBack callback)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoEndTrace"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);
    RWASSERT(callback);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            if (callback)
            {
                RwUInt32            i;

                /* Call the callback for each memory block that is 
                 * still in the list */
                for (i = 0; i < list->numBlocks; i++)
                {
                    if (!callback(list->memBlocks[i].addr,
                                  list->memBlocks[i].size))
                    {
                        break;
                    }
                }
            }

            /* Reset the list */
            if (list->memBlocks)
            {
                oldFree(list->memBlocks);
            }

            list->memBlocks = (RpMemInfoBlockDescriptor *)NULL;
            list->numBlocks = 0;
            list->numAllocatedBlocks = 0;

            /* Reset the allocator stack */
            if (list->allocatorStack.stack)
            {
                oldFree(list->allocatorStack.stack);
            }

            list->allocatorStack.stack = (RwUInt32 *)NULL;
            list->allocatorStack.stackSize = 0;
            list->allocatorStack.top = 0;

            /* Now we are not logging */
            RWMEMINFOGLOBAL(curList) = (RpMemInfoList *)NULL;

            RWRETURN(list);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpMemInfoList *)NULL);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN((RpMemInfoList *)NULL);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoPushAllocatorID is used to associate any subsequent
 * memory allocation in the specified list with the given allocator ID.
 * Memory allocations will be identified as such until the next call to
 * \ref RpMemInfoPopAllocatorID.
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  A RwUInt32 value equal to the ID of the memory allocation.
 *
 * \return Returns a pointer to the memory info list if successful or
 * NULL if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoStartTrace
 */
RpMemInfoList      *
RpMemInfoPushAllocatorID(RpMemInfoList * list, RwUInt32 allocatorID)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoPushAllocatorID"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            if (list->allocatorStack.top ==
                list->allocatorStack.stackSize)
            {
                if (MemInfoGrowAllocatorStack(&list->allocatorStack))
                {
                    /* Now we have enough space */
                    list->allocatorStack.stack[list->allocatorStack.top]
                        = allocatorID;
                    list->allocatorStack.top++;
                }
            }
            else
            {
                /* Else just whack it in there */
                list->allocatorStack.stack[list->allocatorStack.top] =
                    allocatorID;
                list->allocatorStack.top++;
            }
            RWRETURN(list);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpMemInfoList *)NULL);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN((RpMemInfoList *)NULL);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoPopAllocatorID is used to end the memory allocation
 * associated with the current allocator ID for the specified list.
 * Subequent memory allocations will be identified with the ID, if any,
 * in force before the current one was registered.
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 *
 * \return Returns a RwUInt32 value equal to the current allocator ID
 * if successful or zero if the there is no current ID or if there is
 * an error.
 *
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwUInt32
RpMemInfoPopAllocatorID(RpMemInfoList * list)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoPopAllocatorID"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            if (list->allocatorStack.top != 0)
            {
                list->allocatorStack.top--;
                RWRETURN(list->
                         allocatorStack.stack[list->allocatorStack.
                                              top]);
            }
            else
            {
                RWRETURN(0);
            }
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(0);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(0);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetMemoryStatus is used to enumerate all entries in
 * the specified list, executing the given callback function for each
 * allocated memory block in the list. The format of the callback is:
 * 
 * void *(*RpMemInfoStatusCallBack)(void *addr, size_t size, RwUInt32 allocatorID);
 * 
 * where addr is the starting address of the block of memory, size is its
 * size in bytes and allocatorID is the allocation identifier associated
 * with the memory block. The callback may return NULL to terminate
 * further callbacks.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  pointer to the callback function.
 *
 * \return pointer to the list on success, NULL on failure.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RpMemInfoList      *
RpMemInfoGetMemoryStatus(RpMemInfoList * list,
                         RpMemInfoStatusCallBack callback)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetMemoryStatus"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);
    RWASSERT(callback);

    if (memInfoModule.numInstances)
    {
        if (list && callback)
        {
            RwUInt32            i;

            /* Call the callback for each memory block that is in the list */
            for (i = 0; i < list->numBlocks; i++)
            {
                if (!callback(list->memBlocks[i].addr,
                              list->memBlocks[i].size,
                              list->memBlocks[i].allocatorID))
                {
                    break;
                }
            }

            RWRETURN(list);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN((RpMemInfoList *)NULL);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN((RpMemInfoList *)NULL);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetCurrentMemoryUsage is used to retrieve the total
 * size of memory currently allocated in the specified list. The returned
 * size only refers to the memory allocated since the last call to
 * \ref RpMemInfoStartTrace.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * 
 * \return Returns a RwInt32 value equal to the memory usage in bytes if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetCurrentMemoryUsage(RpMemInfoList * list)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetCurrentMemoryUsage"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RWRETURN(list->currentUsage);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetMaxMemoryUsage is used to retrieve the maximum
 * size of allocated memory recorded by the specified list. The returned
 * size only refers to the memory allocated since the last call to
 * \ref RpMemInfoStartTrace.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 *
 * \return Returns a RwInt32 value equal to the memory usage in bytes if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetMaxMemoryUsage(RpMemInfoList * list)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetMaxMemoryUsage"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RWRETURN(list->maxUsage);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetAllocatorMemoryUsage is used to retrieve the
 * total size of memory currently allocated in the specified list for the
 * given allocator ID.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  A RwUInt32 value equal to the ID of the memory allocation.
 *
 * \return Returns a RwInt32 value equal to the memory usage in bytes if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetAllocatorMemoryUsage(RpMemInfoList * list,
                                 RwUInt32 allocatorID)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetAllocatorMemoryUsage"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RwUInt32            i;
            RwUInt32            size = 0;

            /* Call the callback for each memory block that is in the list */
            for (i = 0; i < list->numBlocks; i++)
            {
                if (list->memBlocks[i].allocatorID == allocatorID)
                {
                    size += list->memBlocks[i].size;
                }
            }

            RWRETURN(size);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetAllocatorGroupMemoryUsage is used to retrieve
 * the total size of memory currently allocated in the specified list for
 * the given allocator group ID.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  A RwUInt32 value equal to the ID of the memory allocation group.
 *
 * \return Returns a RwInt32 value equal to the memory usage in bytes if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetAllocatorGroupMemoryUsage(RpMemInfoList * list,
                                      RwUInt32 allocatorID)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetAllocatorGroupMemoryUsage"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RwUInt32            i;
            RwUInt32            size = 0;

            /* Call the callback for each memory block that is in the list */
            for (i = 0; i < list->numBlocks; i++)
            {
                if ((list->memBlocks[i].allocatorID & allocatorID) ==
                    allocatorID)
                {
                    size += list->memBlocks[i].size;
                }
            }

            RWRETURN(size);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}

/************************************************************************
 *
 *      Function:       RpMemInfoGetAllocatorNumBlocks()
 *
 *************************************************************************/

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetAllocatorNumBlocks is used to retrieve the total
 * number of blocks of memory currently allocated in the specified list
 * for the given allocator ID.
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  A RwUInt32 value equal to the ID of the memory allocation.
 *
 * \return Returns a RwInt32 value equal to the number of blocks if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorGroupNumBlocks
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetAllocatorNumBlocks(RpMemInfoList * list,
                               RwUInt32 allocatorID)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetAllocatorNumBlocks"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RwUInt32            i;
            RwUInt32            numBlocks = 0;

            /* Call the callback for each memory block that is in the list */
            for (i = 0; i < list->numBlocks; i++)
            {
                if (list->memBlocks[i].allocatorID == allocatorID)
                {
                    numBlocks++;
                }
            }

            RWRETURN(numBlocks);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}

/************************************************************************
 *
 *      Function:       RpMemInfoGetAllocatorGroupNumBlocks()
 *
 *************************************************************************/

/**
 * \ingroup rpmeminfo
 * \ref RpMemInfoGetAllocatorGroupNumBlocks is used to retrieve the
 * total number of blocks of memory currently allocated in the specified
 * list for the given allocator group ID.
 *
 * 
 * The memory info plug-in must be attached before using this function.
 *
 * \param list  Pointer to the list of memory allocations.
 * \param allocatorID  A RwUInt32 value equal to the ID of the memory allocation group.
 *
 * \return Returns a RwInt32 value equal to the number of blocks if
 * successful or -1 if there is an error.
 *
 * \see RpMemInfoEndTrace
 * \see RpMemInfoGetAllocatorGroupMemoryUsage
 * \see RpMemInfoGetAllocatorMemoryUsage
 * \see RpMemInfoGetAllocatorNumBlocks
 * \see RpMemInfoGetCurrentMemoryUsage
 * \see RpMemInfoGetMaxMemoryUsage
 * \see RpMemInfoGetMemoryStatus
 * \see RpMemInfoPluginAttach
 * \see RpMemInfoPopAllocatorID
 * \see RpMemInfoPushAllocatorID
 * \see RpMemInfoStartTrace
 */
RwInt32
RpMemInfoGetAllocatorGroupNumBlocks(RpMemInfoList * list,
                                    RwUInt32 allocatorID)
{
    RWAPIFUNCTION(RWSTRING("RpMemInfoGetAllocatorGroupNumBlocks"));
    RWASSERT(memInfoModule.numInstances);
    RWASSERT(list);

    if (memInfoModule.numInstances)
    {
        if (list)
        {
            RwUInt32            i;
            RwUInt32            numBlocks = 0;

            /* Call the callback for each memory block that is in the list */
            for (i = 0; i < list->numBlocks; i++)
            {
                if ((list->memBlocks[i].allocatorID & allocatorID) ==
                    allocatorID)
                {
                    numBlocks++;
                }
            }

            RWRETURN(numBlocks);
        }

        RWERROR((E_RW_NULLP));
        RWRETURN(-1);
    }

    RWERROR((E_RW_PLUGINNOTINIT));
    RWRETURN(-1);
}
