Files
freetype/lib/ttcache.c
2023-08-27 18:03:45 +02:00

464 lines
12 KiB
C

/*******************************************************************
*
* ttcache.c 1.1
*
* Generic object cache
*
* Copyright 1996-1999 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.
*
* Notes: This function is thread-safe, each cache list is protected
* through the cache's mutex, if there is one...
*
* *new_object will be set to NULL in case of failure.
*
******************************************************************/
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;
return TT_Err_Ok;
Exit:
*new_object = NULL;
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;
PFinalizer finalize;
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
{
/* Finalize the object before adding it to the */
/* idle list. Return the error if any is found. */
finalize = cache->clazz->finalize;
if ( finalize )
{
error = finalize( element->data );
if ( error )
goto Exit;
/* Note: a failure at finalize time is a severe bug in */
/* the engine, which is why we allow ourselves to */
/* lose the object in this case. A finalizer 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 */