/*
 * Resource handling.
 * Resources are used to instance objects into.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 * 
 */

/**
 * \ingroup rwresources
 * \page rwresourcesoverview RwResources Overview
 *
 * This object represents the RenderWare Graphics Resource Arena cache.
 *
 * During rendering RenderWare Graphics stores platform-dependent instances of model geometry within 
 * this cache. If the model is not changed substantially from one cycle to the next, the 
 * instanced data is simply fetched and transferred to the rendering hardware, rather than being 
 * recomputed. 
 * 
 * This system improves the speed of the rendering process, but the size of the Resource 
 * Arena needs to be fine-tuned for best results. It is possible for a badly-tuned Resource 
 * Arena to affect performance.
 * 
*/


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

#include <stdlib.h>

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

#include "baresour.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: baresour.c,v 1.73 2001/03/15 16:28:42 katherinet Exp $";
#endif /* (!defined(DOXYGEN)) */

/* A switch to use dynamic but capped memory allocation rather than single
 * block managed by _rwResHeap functions
 */
#define RWRESOURCESDYNAMICx

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

/****************************************************************************
 Local (Static) Prototypes
 */

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

/* Default resource arena size 2Mb - update docs for 
 * RwResourcesSetArenaSize if changing this. 
 */
#define rwRESOURCESDEFAULTARENASIZE (2<<20)

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

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

static RwModuleInfo resourcesModule;

/* Initial resource arena size: 
 * We need a static global variable so that RpResourcesSetArenaSize() 
 * can be used to set a new initial arena size before RwEngineStart()
 * is called where the module is opened and the memory allocated.
 */
static RwUInt32     rwResourcesOverrideDefaultArenaSize = 0;

/****************************************************************************
 Local Macros
 */

#define RWRESOURCESGLOBAL(var) (RWPLUGINOFFSET(rwResourcesGlobals,  \
    RwEngineInstance, resourcesModule.globalsOffset)->var)

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

   Functions

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

/****************************************************************************
 rwResourcesInit

 On entry   : Reosurces to init, size
 On exit    : Resources pointer on success
 */

static rwResources *
ResourcesInit(rwResources * res, RwUInt32 size)
{
    RWFUNCTION(RWSTRING("ResourcesInit"));

#if (!defined(RWRESOURCESDYNAMIC))

    /* Set up a single block of memory for the resources heap */
    res->memHeap = RwMalloc(size);
    if (!res->memHeap)
    {
        RWERROR((E_RW_NOMEM, size));
        RWRETURN((rwResources *) NULL);
    }

    if (!_rwResHeapInit(res->memHeap, size))
    {
        RwFree(res->memHeap);
        RWERROR((E_RW_INSUFFICIENTRESOURCES, 0));
        RWRETURN((rwResources *) NULL);
    }

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

    rwLinkListInitialize(&res->entriesA);
    rwLinkListInitialize(&res->entriesB);

    res->usedEntries = &res->entriesA;
    res->freeEntries = &res->entriesB;

    res->maxSize = size;
    res->currentSize = 0;
    res->reusageSize = 0;

    /* All done */
    RWRETURN(res);
}

/****************************************************************************
 _rwResourcesOpen

 On entry   : instance, offset, size
 On exit    : instance pointer on success
 */

void               *
_rwResourcesOpen(void *instance, RwInt32 offset,
                 RwInt32 __RWUNUSED__ size)
{
    RwUInt32            arenaSize;

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

    /* Grab the globals offset (same for all instances) */
    resourcesModule.globalsOffset = offset;

    /* Has the default arena size been overridden? */
    if (rwResourcesOverrideDefaultArenaSize > 0)
    {
        arenaSize = rwResourcesOverrideDefaultArenaSize;
    }
    else
    {
        arenaSize = rwRESOURCESDEFAULTARENASIZE;

        RWMESSAGE((RWSTRING
                   ("Default %d bytes initially allocated for resources arena."),
                   rwRESOURCESDEFAULTARENASIZE));
    }

    /* Then continue with the initialization */
    if (!ResourcesInit(&RWRESOURCESGLOBAL(res), arenaSize))
    {
        RWRETURN(NULL);
    }

    /* One more module instance */
    resourcesModule.numInstances++;

    RWRETURN(instance);
}

/****************************************************************************
 _rwResourcesClose

 On entry   : instance, offset, size
 On exit    : instance pointer on success
 */

void               *
_rwResourcesClose(void *instance,
                  RwInt32 __RWUNUSED__ offset,
                  RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("_rwResourcesClose"));
    RWASSERT(instance);

    /* Release all the resources */
    RwResourcesEmptyArena();

#if (!defined(RWRESOURCESDYNAMIC))

    /* Free the block of memory used for the resources heap */
    _rwResHeapClose(RWRESOURCESGLOBAL(res.memHeap));
    RwFree(RWRESOURCESGLOBAL(res.memHeap));
    RWRESOURCESGLOBAL(res.memHeap) = NULL;

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

    /* One less module instance */
    resourcesModule.numInstances--;

    RWRETURN(instance);
}

/**
 * \ingroup rwresources
 * \ref RwResourcesFreeResEntry is used to release the memory allocated
 * to the specified resources entry. The destroy notify callback (if one was
 * specified at allocation time) will be called before the memory is freed. 
 * The pointer that was filled in at creation time will be set back to NULL 
 * to indicate that the memory has been freed.
 * 
 * \param entry  Pointer to the resources entry.
 *
 * \return Returns TRUE is successful or FALSE if there is an error.
 *
 * \see RwResourcesAllocateResEntry
 * \see RwResourcesUseResEntry
 *
 */
RwBool
RwResourcesFreeResEntry(RwResEntry * entry)
{
    RWAPIFUNCTION(RWSTRING("RwResourcesFreeResEntry"));
    RWASSERT(resourcesModule.numInstances);
    RWASSERT(entry);

    /* About to go away, call the destroy notify if applicable */
    if (entry->destroyNotify)
    {
        entry->destroyNotify(entry);
    }

    /* unlink user of resource */
    /* Do not remove ths test. We now permit orphan ResEntries which have
     * no ownerRef. This is to provide procrastinated destroy */
    if (entry->ownerRef)
    {
        *(entry->ownerRef) = (RwResEntry *) NULL;
    }

    if (entry->link.next)
    {
        /* Remove the link list attachment */
        rwLinkListRemoveLLLink(&entry->link);

        /* Adjust the current size */
        RWRESOURCESGLOBAL(res.currentSize) -= entry->size;

#ifdef RWRESOURCESDYNAMIC

        /* Free from main memory */
        RwFree(entry);

#else /* RWRESOURCESDYNAMIC */

        /* Free from resources heap */
        _rwResHeapFree(entry);

#endif /* RWRESOURCESDYNAMIC */

    }
    else
    {
        /* This entry is not in a list and must be in main memory */
        RwFree(entry);
    }

    /* All done */
    RWRETURN(TRUE);
}

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

   Get some resources

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

/****************************************************************************
 _rwResourcesPurge

 Mark all used entries as free

 */

void
_rwResourcesPurge(void)
{
    RwLinkList         *usedEntries, *freeEntries;

    RWFUNCTION(RWSTRING("_rwResourcesPurge"));

    /* Stick the current free on the end of the used list */
    usedEntries = RWRESOURCESGLOBAL(res.usedEntries);
    freeEntries = RWRESOURCESGLOBAL(res.freeEntries);

    /* Copy from free into used */
    if (!rwLinkListEmpty(freeEntries))
    {
        if (rwLinkListEmpty(usedEntries))
        {
            usedEntries->link.next = freeEntries->link.next;
            (usedEntries->link.next)->prev = &(usedEntries->link);

            usedEntries->link.prev = freeEntries->link.prev;
            (usedEntries->link.prev)->next = &(usedEntries->link);

            /* Empty the list */
            rwLinkListInitialize(freeEntries);
        }
        else
        {
            RwLLLink           *first, *last;

            /* Stick together middle */
            last = rwLinkListGetLastLLLink(usedEntries);
            first = rwLinkListGetFirstLLLink(freeEntries);

            last->next = first;
            first->prev = last;

            /* Attach end */
            last = rwLinkListGetLastLLLink(freeEntries);

            last->next = rwLinkListGetTerminator(usedEntries);
            usedEntries->link.prev = last;

            /* Empty the list */
            rwLinkListInitialize(freeEntries);
        }
    }

    /* Swap over */
    RWRESOURCESGLOBAL(res.usedEntries) = freeEntries;
    RWRESOURCESGLOBAL(res.freeEntries) = usedEntries;

    /* Reset the reusage */
    RWRESOURCESGLOBAL(res.reusageSize) = 0;

    /* Done */
    RWRETURNVOID();
}

RwResEntry         *
_rwResourcesAllocateResEntry(void *owner,
                             RwResEntry ** ownerRef,
                             RwInt32 size,
                             rwResEntryMemoryPool pool,
                             RwResEntryDestroyNotify destroyNotify)
{
    RwResEntry         *entry;
    RwBool              exhaustedOptions;

    RWFUNCTION(RWSTRING("_rwResourcesAllocateResEntry"));
    RWASSERT(resourcesModule.numInstances);
    RWASSERT(owner);

    if (pool == rwMEMORYPOOLDEFAULT)
    {
        /* Allocate a persistent res entry in main memory */
        entry = (RwResEntry *) RwMalloc(sizeof(RwResEntry) + size);
        if (entry)
        {
            /* We have an entry */
            entry->link.next = (RwLLLink *) NULL;
            entry->link.prev = (RwLLLink *) NULL;
            entry->owner = owner;
            entry->size = size;
            entry->ownerRef = ownerRef;
            entry->destroyNotify = destroyNotify;

            /* Do not remove this test. We now permit orphaned ResEntries */
            if (ownerRef)
            {
                *ownerRef = entry;
            }

            RWRETURN(entry);
        }

        RWRETURN((RwResEntry *) NULL);
    }

    exhaustedOptions = FALSE;
    while (!exhaustedOptions)
    {
        RwLLLink           *cur, *end;

#ifdef RWRESOURCESDYNAMIC

        /* Do capped dynamic memory allocation */
        if ((RWRESOURCESGLOBAL(res.currentSize) + size) <=
            RWRESOURCESGLOBAL(res.maxSize))
        {
            entry = (RwResEntry *) RwMalloc(sizeof(RwResEntry) + size);
            if (!entry)
            {
                RWERROR((E_RW_NOMEM, (sizeof(RwResEntry) + size)));
                RWRETURN(NULL);
            }
        }
        else
        {
            entry = (RwResEntry *) NULL;
        }

#else /* RWRESOURCESDYNAMIC */

        /* Allocate from the resources heap */
        entry =
            (RwResEntry *)
            _rwResHeapAlloc(RWRESOURCESGLOBAL(res.memHeap),
                            sizeof(RwResEntry) + size);

#endif /* RWRESOURCESDYNAMIC */

        if (entry)
        {
            /* We have an entry */
            rwLinkListAddLLLink(RWRESOURCESGLOBAL(res.usedEntries),
                                &entry->link);
            entry->owner = owner;
            entry->size = size;
            entry->ownerRef = ownerRef;
            entry->destroyNotify = destroyNotify;

            RWRESOURCESGLOBAL(res.currentSize) += size;

            /* Do not remove this test. We now permit orphaned ResEntries */
            if (ownerRef)
            {
                *ownerRef = entry;
            }

#ifdef RWMETRICS
            RWSRCGLOBAL(metrics)->numResourceAllocs++;
#endif /* RWMETRICS */

            RWRETURN(entry);
        }

        /* Try freeing up some memory from the not used list */
        cur =
            rwLinkListGetLastLLLink(RWRESOURCESGLOBAL(res.freeEntries));
        end =
            rwLinkListGetTerminator(RWRESOURCESGLOBAL(res.freeEntries));
        if (cur != end)
        {
            /* Free its memory */
            entry = rwLLLinkGetData(cur, RwResEntry, link);

            /* Deallocate the resources */
            RwResourcesFreeResEntry(entry);
        }
        else
        {
            /* Try freeing up some memory from the used list and try to alloc */
            cur =
                rwLinkListGetLastLLLink(RWRESOURCESGLOBAL
                                        (res.usedEntries));
            end =
                rwLinkListGetTerminator(RWRESOURCESGLOBAL
                                        (res.usedEntries));
            if (cur != end)
            {
                /* Free its memory */
                entry = rwLLLinkGetData(cur, RwResEntry, link);

                /* Before we free it though, add its size to the usage, since we wont
                 * be able to count this one later.
                 */
                RWRESOURCESGLOBAL(res.reusageSize) += entry->size;

                /* Deallocate the resources */
                RwResourcesFreeResEntry(entry);
            }
            else
            {
                /* We've emptied both lists, yet still we can't allocate resources */
                exhaustedOptions = TRUE;
            }
        }
    }

    /* Couldn't do it */
    /* Do not remove this test. We now permit orphaned ResEntries */
    if (ownerRef)
    {
        *ownerRef = (RwResEntry *) NULL;
    }
    RWERROR((E_RW_INSUFFICIENTRESOURCES, size));
    RWRETURN((RwResEntry *) NULL);
}

/**
 * \ingroup rwresources
 * \ref RwResourcesUseResEntry is used to mark the specified resources
 * entry as still in use. It should be called every rendering frame that a 
 * resources entry is used to protect the resource against being 
 * unnecessarily freed.
 * 
 * \param entry  Pointer to the resources entry.
 *
 * \return Returns pointer to the resources entry if successful or NULL if
 * there is an error.
 *
 * \see RwResourcesAllocateResEntry
 * \see RwResourcesFreeResEntry
 *
 */
RwResEntry         *
RwResourcesUseResEntry(RwResEntry * entry)
{
    RWAPIFUNCTION(RWSTRING("RwResourcesUseResEntry"));
    RWASSERT(resourcesModule.numInstances);
    RWASSERT(entry);

    if (entry->link.next)
    {
        /* Remove from list -> attach to used list */
        rwLinkListRemoveLLLink(&entry->link);

        /* Attach to used */
        rwLinkListAddLLLink(RWRESOURCESGLOBAL(res.usedEntries),
                            &entry->link);
    }

    /* All done */
    RWRETURN(entry);
}

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

   Resources

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

/**
 * \ingroup rwresources
 * \ref RwResourcesSetArenaSize is used to specify the size of the memory
 * block reserved for the resources instancing arena. The default size is
 * otherwise 2Mb.
 *
 * This should be used in between \ref RwEngineInit and 
 * \ref RwEngineStart to set the default size for the arena before the
 * memory is allocated. If used at a later stage, the arena will be emptied
 * and the memory reallocated.
 *
 * \param size  A RwInt32 value equal to the arena size (in bytes).
 *
 * \return Returns TRUE if successful or FALSE if there is an error.
 * 
 * \see RwResourcesEmptyArena
 * \see RwResourcesGetArenaSize
 * \see RwResourcesGetArenaUsage
 *
 */
RwBool
RwResourcesSetArenaSize(RwInt32 size)
{
    rwResources        *res;

    RWAPIFUNCTION(RWSTRING("RwResourcesSetArenaSize"));
    RWASSERT(size > 0);

    /* If module has not yet been opened, then override the default size */
    if (resourcesModule.numInstances == 0)
    {
        rwResourcesOverrideDefaultArenaSize = size;
        RWRETURN(TRUE);
    }

    /* Otherwise modify the existing arena size */
    res = &RWRESOURCESGLOBAL(res);
    res->maxSize = size;

#ifdef RWRESOURCESDYNAMIC

    /* Shrink down dynamically allocated resources to specified size
     * if necessary */
    while (res->currentSize > res->maxSize)
    {
        RwLLLink           *cur, *end;

        /* Try freeing up some memory from the not used list */
        cur = rwLinkListGetLastLLLink(res->freeEntries);
        end = rwLinkListGetTerminator(res->freeEntries);
        if (cur != end)
        {
            /* Free its memory */
            RwResEntry         *entry =
                rwLLLinkGetData(cur, RwResEntry, link);

            /* Deallocate the resources */
            RwResourcesFreeResEntry(entry);
        }
        else
        {
            /* Try freeing up some memory from the used list */
            cur = rwLinkListGetLastLLLink(res->usedEntries);
            end = rwLinkListGetTerminator(res->usedEntries);
            if (cur != end)
            {
                /* Free its memory */
                RwResEntry         *entry =
                    rwLLLinkGetData(cur, RwResEntry, link);

                /* Deallocate the resources */
                RwResourcesFreeResEntry(entry);
            }
            else
            {
                /* We've emptied both lists, 
                 * yet still we can't meet the size target */
                RWRETURN(FALSE);
            }
        }
    }

#else /* RWRESOURCESDYNAMIC */

    /* Empty the arena */
    RwResourcesEmptyArena();

    /* Close and free the resources heap */
    _rwResHeapClose(res->memHeap);
    RwFree(res->memHeap);

    /* Allocate a new heap */
    res->memHeap = RwMalloc(size);
    if (!res->memHeap)
    {
        res->maxSize = 0;
        RWERROR((E_RW_NOMEM, size));
        RWRETURN(FALSE);
    }

    /* Initialise the heap */
    if (!_rwResHeapInit(res->memHeap, size))
    {
        RwFree(res->memHeap);
        RWERROR((E_RW_INSUFFICIENTRESOURCES, 0));
        RWRETURN(FALSE);
    }

#endif /* RWRESOURCESDYNAMIC */

    RWRETURN(TRUE);
}

/**
 * \ingroup rwresources
 * \ref RwResourcesGetArenaSize is used to determine the size
 * (in bytes) of the current instancing arena.
 * 
 * \return Returns a RwInt32 value equal to the size of the resources 
 *        arena (in bytes) if successful or zero if there is an error.
 * 
 * \see RwResourcesEmptyArena
 * \see RwResourcesGetArenaUsage
 * \see RwResourcesSetArenaSize
 *
 */
RwInt32
RwResourcesGetArenaSize(void)
{
    RWAPIFUNCTION(RWSTRING("RwResourcesGetArenaSize"));
    RWASSERT(resourcesModule.numInstances);

    RWRETURN(RWRESOURCESGLOBAL(res.maxSize));
}

/**
 * \ingroup rwresources
 * \ref RwResourcesGetArenaUsage is used to calculate the total amount
 * of instance arena memory used in the last rendering frame. The number 
 * returned may be larger than the arena size if the arena has started 
 * thrashing: this will cause performance to degrade and the application 
 * may wish to resize the arena to prevent this.
 *
 * Note that the value returned by this function is only meaningful before a 
 * call to \ref RwCameraShowRaster (which purges the resources cache).
 * 
 * \return Returns a RwInt32 value equal to arena usage in bytes if successful
 * or zero if there is an error.
 *
 * \see RwResourcesEmptyArena
 * \see RwResourcesGetArenaSize
 * \see RwResourcesSetArenaSize
 *
 */
RwInt32
RwResourcesGetArenaUsage(void)
{
    RwLLLink           *cur, *end;
    RwInt32             usage = RWRESOURCESGLOBAL(res.reusageSize);

    RWAPIFUNCTION(RWSTRING("RwResourcesGetArenaUsage"));
    RWASSERT(resourcesModule.numInstances);

    cur = rwLinkListGetFirstLLLink(RWRESOURCESGLOBAL(res.usedEntries));
    end = rwLinkListGetTerminator(RWRESOURCESGLOBAL(res.usedEntries));
    while (cur != end)
    {
        /* Count them all */
        RwResEntry         *entry =
            rwLLLinkGetData(cur, RwResEntry, link);

        /* Tot up sizes */
        usage += entry->size;

        /* Get next */
        cur = rwLLLinkGetNext(cur);
    }

    RWRETURN(usage);
}

/**
 * \ingroup rwresources
 * \ref RwResourcesEmptyArena is used to release all the memory in 
 * the instancing arena. Any resources entries that have registered a 
 * destroy notify callback will have their callback function executed 
 * before the arena memory is freed.
 * 
 * \return Returns TRUE.
 * 
 * \see RwResourcesAllocateResEntry
 * \see RwResourcesGetArenaSize
 * \see RwResourcesGetArenaUsage
 * \see RwResourcesSetArenaSize
 *
 */
RwBool
RwResourcesEmptyArena(void)
{
    RwLLLink           *cur, *end;

    RWAPIFUNCTION(RWSTRING("RwResourcesEmptyArena"));
    RWASSERT(resourcesModule.numInstances);

    /* Attach list B onto list A */
    rwLinkListGetLastLLLink(&RWRESOURCESGLOBAL(res.entriesA))->next =
        rwLinkListGetFirstLLLink(&RWRESOURCESGLOBAL(res.entriesB));

    cur = rwLinkListGetFirstLLLink(&RWRESOURCESGLOBAL(res.entriesA));
    end = rwLinkListGetTerminator(&RWRESOURCESGLOBAL(res.entriesB));
    while (cur != end)
    {
        /* Remove them all ! */
        RwResEntry         *entry =
            rwLLLinkGetData(cur, RwResEntry, link);

        /* Next (must come before the resource release) */
        cur = rwLLLinkGetNext(cur);

        /* Deallocate the resources */
        RwResourcesFreeResEntry(entry);
    }

    /* Empty the lists */
    rwLinkListInitialize(&RWRESOURCESGLOBAL(res.entriesA));
    rwLinkListInitialize(&RWRESOURCESGLOBAL(res.entriesB));

    /* reset the usage counter */
    RWRESOURCESGLOBAL(res.reusageSize) = 0;

    /* Should all be empty */
    RWASSERT(RWRESOURCESGLOBAL(res.currentSize) == 0);

    /* Done */
    RWRETURN(TRUE);
}
