/******************************************************************* * * 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 */