/*  mempool.c
**
**  A simple fast memory allocation package.
**
**  By Steve Hill in Graphics Gems III, David Kirk (ed.),
**    Academic Press, Boston, MA, 1992
**
**  Modified by Lew Rossman, 8/13/94.
**
**  AllocInit()     - create an alloc pool, returns the old pool handle
**  Alloc()         - allocate memory
**  AllocReset()    - reset the current pool
**  AllocSetPool()  - set the current pool
**  AllocFree()     - free the memory used by the current pool.
**
*/

#include <stdlib.h>
#ifndef __APPLE__
#include <malloc.h>
#endif
#include "mempool.h"

/*
**  ALLOC_BLOCK_SIZE - adjust this size to suit your installation - it
**  should be reasonably large otherwise you will be mallocing a lot.
*/

#define ALLOC_BLOCK_SIZE   64000       /*(62*1024)*/

/*
**  alloc_hdr_t - Header for each block of memory.
*/

typedef struct alloc_hdr_s
{
    struct alloc_hdr_s *next;   /* Next Block          */
    char               *block,  /* Start of block      */
                       *free,   /* Next free in block  */
                       *end;    /* block + block size  */
}  alloc_hdr_t;

/*
**  alloc_root_t - Header for the whole pool.
*/

typedef struct alloc_root_s
{
    alloc_hdr_t *first,    /* First header in pool */
                *current;  /* Current header       */
}  alloc_root_t;

/*
**  root - Pointer to the current pool.
*/

static alloc_root_t *root;


/*
**  AllocHdr()
**
**  Private routine to allocate a header and memory block.
*/

static alloc_hdr_t *AllocHdr(void);
                
static alloc_hdr_t * AllocHdr()
{
    alloc_hdr_t     *hdr;
    char            *block;

    block = (char *) malloc(ALLOC_BLOCK_SIZE);
    hdr   = (alloc_hdr_t *) malloc(sizeof(alloc_hdr_t));

    if (hdr == NULL || block == NULL) return(NULL);
    hdr->block = block;
    hdr->free  = block;
    hdr->next  = NULL;
    hdr->end   = block + ALLOC_BLOCK_SIZE;

    return(hdr);
}


/*
**  AllocInit()
**
**  Create a new memory pool with one block.
**  Returns pointer to the new pool.
*/

DLLEXPORT alloc_handle_t * AllocInit()
{
    alloc_handle_t *newpool;
    root = (alloc_root_t *) malloc(sizeof(alloc_root_t));
    if (root == NULL) return(NULL);
    if ( (root->first = AllocHdr()) == NULL) return(NULL);
    root->current = root->first;
    newpool = (alloc_handle_t *) root;
    return(newpool);
}


/*
**  Alloc()
**
**  Use as a direct replacement for malloc().  Allocates
**  memory from the current pool.
*/

DLLEXPORT char *Alloc(long size)
{
    alloc_hdr_t  *hdr = root->current;
    char         *ptr;

    /*
    **  Align to 4 byte boundary - should be ok for most machines.
    **  Change this if your machine has weird alignment requirements.
    */
    size = (size + 3) & 0xfffffffc;

    ptr = hdr->free;
    hdr->free += size;

    /* Check if the current block is exhausted. */

    if (hdr->free >= hdr->end)
    {
        /* Is the next block already allocated? */

        if (hdr->next != NULL)
        {
            /* re-use block */
            hdr->next->free = hdr->next->block;
            root->current = hdr->next;
        }
        else
        {
            /* extend the pool with a new block */
            if ( (hdr->next = AllocHdr()) == NULL) return(NULL);
            root->current = hdr->next;
        }

        /* set ptr to the first location in the next block */
        ptr = root->current->free;
        root->current->free += size;
    }

    /* Return pointer to allocated memory. */

    return(ptr);
}


/*
**  AllocSetPool()
**
**  Change the current pool.  Return the old pool.
*/

DLLEXPORT alloc_handle_t * AllocSetPool(alloc_handle_t *newpool)
{
    alloc_handle_t *old = (alloc_handle_t *) root;
    root = (alloc_root_t *) newpool;
    return(old);
}


/*
**  AllocReset()
**
**  Reset the current pool for re-use.  No memory is freed,
**  so this is very fast.
*/

DLLEXPORT void AllocReset()
{
    root->current = root->first;
    root->current->free = root->current->block;
}


/*
**  AllocFreePool()
**
**  Free the memory used by the current pool.
**  Don't use where AllocReset() could be used.
*/

DLLEXPORT void AllocFreePool()
{
    alloc_hdr_t  *tmp,
                 *hdr = root->first;

    while (hdr != NULL)
    {
        tmp = hdr->next;
        free((char *) hdr->block);
        free((char *) hdr);
        hdr = tmp;
    }
    free((char *) root);
    root = NULL;
}
