/*******************************************************************
 *
 *  ttcache.c                                                   1.1
 *
 *    Generic object cache
 *
 *  Copyright 1996-1998 by
 *  David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 * Changes between 1.1 and 1.0:
 *
 *  - introduced the refresher and finalizer in the cache class
 *    definition/implementation.
 *
 ******************************************************************/

#include "ttengine.h"
#include "ttmemory.h"
#include "ttcache.h"
#include "ttobjs.h"
#include "ttdebug.h"

/* required by the tracing mode */
#undef  TT_COMPONENT
#define TT_COMPONENT  trace_cache

#define ZERO_List( list )  list = NULL

/* The macro FREE_Elements aliases the current engine instance's */
/* free list_elements recycle list.                              */
#define FREE_Elements  ( engine->list_free_elements )

/* Redefinition of LOCK and UNLOCK macros for New_Element and Done_Element */
/* Note: The macros are redefined below for the cache functions            */

#undef  LOCK
#define LOCK()    MUTEX_Lock   ( engine->lock )

#undef  UNLOCK
#define UNLOCK()  MUTEX_Release( engine->lock )

/*******************************************************************
 *
 *  Function    :  Element_New
 *
 *  Description :  Gets a new (either fresh or recycled) list
 *                 element.  The element is unlisted.
 *
 *  Input  :  None
 *
 *  Output :  List element address.  NULL if out of memory.
 *
 ******************************************************************/

  static
  PList_Element  Element_New( PEngine_Instance  engine )
  {
    PList_Element  element;


    LOCK();
    if ( FREE_Elements )
    {
      element       = (PList_Element)FREE_Elements;
      FREE_Elements = element->next;
    }
    else
    {
      if ( !MEM_Alloc( element, sizeof ( TList_Element ) ) )
      {
        element->next = NULL;
        element->data = NULL;
      }
    }

    /* Note: in case of failure, Alloc sets the pointer to NULL */
    UNLOCK();

    return element;
  }


/*******************************************************************
 *
 *  Function    :  Element_Done
 *
 *  Description :  Recycles an unlinked list element.
 *
 *  Input  :  The list element to recycle.  It _must_ be unlisted.
 *
 *  Output :  none.
 *
 *  Note   :  This function doesn't check the element.
 *
 ******************************************************************/

  static
  void  Element_Done( PEngine_Instance  engine,
                      PList_Element     element )
  {
    LOCK();
    /* Simply add the list element to the recycle list */
    element->next = (PList_Element)FREE_Elements;
    FREE_Elements = element;
    UNLOCK();
  }


/* Redefinition of LOCK and UNLOCK macros for the cache functions          */
/* Note: The macros are defined above for the list element functions       */

#undef  LOCK
#define LOCK()    MUTEX_Lock( *cache->lock )

#undef  UNLOCK
#define UNLOCK()  MUTEX_Release( *cache->lock )


/*******************************************************************
 *
 *  Function    :  Cache_Create
 *
 *  Description :  Creates a new cache that will be used to list
 *                 and recycle several objects of the same class.
 *
 *  Input  :  clazz       a pointer to the cache's class.  This is
 *                        a simple structure that describes the
 *                        the cache's object types and recycling
 *                        limits.
 *
 *            cache       address of cache to create
 *
 *            lock        address of the mutex to use for this
 *                        cache.  The mutex will be used to protect
 *                        the cache's lists.  Use NULL for unprotected
 *                        cache.
 *
 *  Output :  Error code.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Cache_Create( PEngine_Instance  engine,
                          PCache_Class      clazz,
                          TCache*           cache,
                          TMutex*           lock )
  {
    cache->engine     = engine;
    cache->clazz      = clazz;
    cache->lock       = lock;
    cache->idle_count = 0;

    ZERO_List( cache->active );
    ZERO_List( cache->idle );

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Cache_Destroy
 *
 *  Description :  Destroys a cache and all its idle and active
 *                 objects.  This will call each object's destructor
 *                 before freeing it.
 *
 *  Input  :  cache   address of cache to destroy
 *
 *  Output :  error code.
 *
 *  Note: This function is not MT-Safe, as we assume that a client
 *        isn't stupid enough to use an object while destroying it.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Cache_Destroy( TCache*  cache )
  {
    PDestructor    destroy;
    PList_Element  current;
    PList_Element  next;


    /* now destroy all active and idle listed objects */

    /* get the destructor function */
    destroy = cache->clazz->done;

    /* destroy all elements in active list */
    current = cache->active;
    while ( current )
    {
      next = current->next;
      destroy( current->data );
      FREE( current->data );

      Element_Done( cache->engine, current );
      current = next;
    }
    ZERO_List(cache->active);

    /* destroy all elements in idle list */
    current = cache->idle;
    while ( current )
    {
      next = current->next;
      destroy( current->data );
      FREE( current->data );

      Element_Done( cache->engine, current );
      current = next;
    }
    ZERO_List(cache->idle);

    cache->clazz      = NULL;
    cache->idle_count = 0;

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  Cache_New
 *
 *  Description :  Extracts a new object from a cache.  This will
 *                 try to recycle an idle object, if any is found.
 *                 Otherwise, a new object will be allocated and
 *                 built (by calling its constructor).
 *
 *  Input  :   cache          address of cache to use
 *             new_object     address of target pointer to the 'new'
 *                            object
 *             parent_object  this pointer is passed to a new object
 *                            constructor (unused if object is
 *                            recycled)
 *
 *  Output :   Error code.
 *
 *  Note:  This function is thread-safe, each cache list is protected
 *         through the cache's mutex, if there is one...
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Cache_New( TCache*  cache,
                       void**   new_object,
                       void*    parent_object )
  {
    TT_Error       error;
    PList_Element  current;
    PConstructor   build;
    PRefresher     reset;
    void*          object;


    LOCK();
    current = cache->idle;
    if ( current )
    {
      cache->idle = current->next;
      cache->idle_count--;
    }
    UNLOCK();

    if ( current )
    {
      object = current->data;
      reset  = cache->clazz->reset;
      if ( reset )
      {
        error = reset( object, parent_object );
        if ( error )
        {
          LOCK();
          current->next = cache->idle;
          cache->idle   = current;
          cache->idle_count++;
          UNLOCK();
          goto Exit;
        }
      }
    }
    else
    {
      /* if no object was found in the cache, create a new one */
      build  = cache->clazz->init;

      if ( MEM_Alloc( object, cache->clazz->object_size ) )
        goto Memory_Fail;

      current = Element_New( cache->engine );
      if ( !current )
        goto Memory_Fail;

      current->data = object;

      error = build( object, parent_object );
      if ( error )
      {
        Element_Done( cache->engine, current );
        goto Fail;
      }
    }

    LOCK();
    current->next = cache->active;
    cache->active = current;
    UNLOCK();

    *new_object = current->data;
    error = TT_Err_Ok;

  Exit:
    return  error;

  Memory_Fail:
    error = TT_Err_Out_Of_Memory;

  Fail:
    FREE( object );
    goto Exit;
  }


/*******************************************************************
 *
 *  Function    :  Cache_Done
 *
 *  Description :  Releases an object to the cache.  This will either
 *                 recycle or destroy the object, based on the cache's
 *                 class and state.
 *
 *  Input  :  cache   the cache to use
 *            data    the object to recycle/discard
 *
 *  Output :  error code.
 *
 *  Notes  :  The object's destructor is called only when
 *            the objectwill be effectively destroyed by this
 *            function.  This will not happen during recycling.
 *
 ******************************************************************/

  LOCAL_FUNC
  TT_Error  Cache_Done( TCache*  cache, void*  data )
  {
    TT_Error       error;
    PList_Element  element;
    PList_Element  prev;
    PFinaliser     finalise;
    Long           limit;
    Bool           destroy;


    /* Look for object in active list */
    LOCK();

    element = cache->active;
    prev = NULL;
    while ( element )
    {
      if ( element->data == data )
      {
        if ( prev )
          prev->next = element->next;
        else
          cache->active = element->next;
        goto Suite;
      }
      prev = element;
      element = element->next;
    }

    UNLOCK();
    return TT_Err_Unlisted_Object;

  Suite:

    limit   = cache->clazz->idle_limit;
    destroy = (cache->idle_count >= limit);
    UNLOCK();

    if ( destroy )
    {
      /* destroy the object when the cache is full */
      cache->clazz->done( element->data );
      FREE( element->data );
      Element_Done( cache->engine, element );
    }
    else
    {
      /* Finalise the object before adding it to the   */
      /* idle list.  Return the error if any is found. */

      finalise = cache->clazz->finalise;
      if ( finalise )
      {
        error = finalise( element->data );
        if ( error )
          goto Exit;

        /* Note: a failure at finalise time is a severe bug in     */
        /*       the engine, which is why we allow ourselves to    */
        /*       lose the object in this case.  A finaliser should */
        /*       have its own error codes to spot this kind of     */
        /*       problems easily.                                  */
      }

      LOCK();
      element->next = cache->idle;
      cache->idle   = element;
      cache->idle_count++;
      UNLOCK();
    }

    error = TT_Err_Ok;

  Exit:
    return error;
  }


  LOCAL_FUNC
  TT_Error  TTCache_Init( PEngine_Instance  engine )
  {
    /* Create list elements mutex */
    FREE_Elements = NULL;
    return TT_Err_Ok;
  }


  LOCAL_FUNC
  TT_Error  TTCache_Done( PEngine_Instance  engine )
  {
    /* We don't protect this function, as this is the end of the engine's */
    /* execution..                                                        */
    PList_Element  element, next;


    /* frees the recycled list elements */
    element = FREE_Elements;
    while ( element )
    {
      next = element->next;
      FREE( element );
      element = next;
    }
    return TT_Err_Ok;
  }


/* END */
