| /***************************************************************************/ |
| /* */ |
| /* ftlru.c */ |
| /* */ |
| /* Simple LRU list-cache (body). */ |
| /* */ |
| /* Copyright 2000-2001, 2002, 2003 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. */ |
| /* */ |
| /***************************************************************************/ |
| |
| |
| #include <ft2build.h> |
| #include FT_CACHE_H |
| #include FT_CACHE_INTERNAL_LRU_H |
| #include FT_LIST_H |
| #include FT_INTERNAL_OBJECTS_H |
| #include FT_INTERNAL_DEBUG_H |
| |
| #include "ftcerror.h" |
| |
| |
| FT_EXPORT_DEF( FT_Error ) |
| FT_LruList_New( FT_LruList_Class clazz, |
| FT_UInt max_nodes, |
| FT_Pointer user_data, |
| FT_Memory memory, |
| FT_LruList *alist ) |
| { |
| FT_Error error; |
| FT_LruList list; |
| |
| |
| if ( !alist || !clazz ) |
| return FTC_Err_Invalid_Argument; |
| |
| *alist = NULL; |
| if ( !FT_ALLOC( list, clazz->list_size ) ) |
| { |
| /* initialize common fields */ |
| list->clazz = clazz; |
| list->memory = memory; |
| list->max_nodes = max_nodes; |
| list->data = user_data; |
| |
| if ( clazz->list_init ) |
| { |
| error = clazz->list_init( list ); |
| if ( error ) |
| { |
| if ( clazz->list_done ) |
| clazz->list_done( list ); |
| |
| FT_FREE( list ); |
| } |
| } |
| |
| *alist = list; |
| } |
| |
| return error; |
| } |
| |
| |
| FT_EXPORT_DEF( void ) |
| FT_LruList_Destroy( FT_LruList list ) |
| { |
| FT_Memory memory; |
| FT_LruList_Class clazz; |
| |
| |
| if ( !list ) |
| return; |
| |
| memory = list->memory; |
| clazz = list->clazz; |
| |
| FT_LruList_Reset( list ); |
| |
| if ( clazz->list_done ) |
| clazz->list_done( list ); |
| |
| FT_FREE( list ); |
| } |
| |
| |
| FT_EXPORT_DEF( void ) |
| FT_LruList_Reset( FT_LruList list ) |
| { |
| FT_LruNode node; |
| FT_LruList_Class clazz; |
| FT_Memory memory; |
| |
| |
| if ( !list ) |
| return; |
| |
| node = list->nodes; |
| clazz = list->clazz; |
| memory = list->memory; |
| |
| while ( node ) |
| { |
| FT_LruNode next = node->next; |
| |
| |
| if ( clazz->node_done ) |
| clazz->node_done( node, list->data ); |
| |
| FT_FREE( node ); |
| node = next; |
| } |
| |
| list->nodes = NULL; |
| list->num_nodes = 0; |
| } |
| |
| |
| FT_EXPORT_DEF( FT_Error ) |
| FT_LruList_Lookup( FT_LruList list, |
| FT_LruKey key, |
| FT_LruNode *anode ) |
| { |
| FT_Error error = 0; |
| FT_LruNode node, *pnode; |
| FT_LruList_Class clazz; |
| FT_LruNode result = NULL; |
| FT_Memory memory; |
| |
| |
| if ( !list || !key || !anode ) |
| return FTC_Err_Invalid_Argument; |
| |
| pnode = &list->nodes; |
| node = NULL; |
| clazz = list->clazz; |
| memory = list->memory; |
| |
| if ( clazz->node_compare ) |
| { |
| for (;;) |
| { |
| node = *pnode; |
| if ( node == NULL ) |
| break; |
| |
| if ( clazz->node_compare( node, key, list->data ) ) |
| break; |
| |
| pnode = &(*pnode)->next; |
| } |
| } |
| else |
| { |
| for (;;) |
| { |
| node = *pnode; |
| if ( node == NULL ) |
| break; |
| |
| if ( node->key == key ) |
| break; |
| |
| pnode = &(*pnode)->next; |
| } |
| } |
| |
| if ( node ) |
| { |
| /* move element to top of list */ |
| if ( list->nodes != node ) |
| { |
| *pnode = node->next; |
| node->next = list->nodes; |
| list->nodes = node; |
| } |
| result = node; |
| goto Exit; |
| } |
| |
| /* Since we haven't found the relevant element in our LRU list, |
| * we're going to "create" a new one. |
| * |
| * The following code is a bit special, because it tries to handle |
| * out-of-memory conditions (OOM) in an intelligent way. |
| * |
| * More precisely, if not enough memory is available to create a |
| * new node or "flush" an old one, we need to remove the oldest |
| * elements from our list, and try again. Since several tries may |
| * be necessary, a loop is needed. |
| * |
| * This loop will only exit when: |
| * |
| * - a new node was successfully created, or an old node flushed |
| * - an error other than FTC_Err_Out_Of_Memory is detected |
| * - the list of nodes is empty, and it isn't possible to create |
| * new nodes |
| * |
| * On each unsuccessful attempt, one node will be removed from the list. |
| * |
| */ |
| |
| { |
| FT_Int drop_last = ( list->max_nodes > 0 && |
| list->num_nodes >= list->max_nodes ); |
| |
| for (;;) |
| { |
| node = NULL; |
| |
| /* If "drop_last" is true, we should free the last node in |
| * the list to make room for a new one. Note that we reuse |
| * its memory block to save allocation calls. |
| */ |
| if ( drop_last ) |
| { |
| /* find the last node in the list |
| */ |
| pnode = &list->nodes; |
| node = *pnode; |
| |
| if ( node == NULL ) |
| { |
| FT_ASSERT( list->num_nodes == 0 ); |
| error = FTC_Err_Out_Of_Memory; |
| goto Exit; |
| } |
| |
| FT_ASSERT( list->num_nodes > 0 ); |
| |
| while ( node->next ) |
| { |
| pnode = &node->next; |
| node = *pnode; |
| } |
| |
| /* Remove it from the list, and try to "flush" it. Doing this will |
| * save a significant number of dynamic allocations compared to |
| * a classic destroy/create cycle. |
| */ |
| *pnode = NULL; |
| list->num_nodes--; |
| |
| if ( clazz->node_flush ) |
| { |
| error = clazz->node_flush( node, key, list->data ); |
| if ( !error ) |
| goto Success; |
| |
| /* Note that if an error occured during the flush, we need to |
| * finalize it since it is potentially in incomplete state. |
| */ |
| } |
| |
| /* We finalize, but do not destroy the last node, we |
| * simply reuse its memory block! |
| */ |
| if ( clazz->node_done ) |
| clazz->node_done( node, list->data ); |
| |
| FT_MEM_ZERO( node, clazz->node_size ); |
| } |
| else |
| { |
| /* Try to allocate a new node when "drop_last" is not TRUE. |
| * This usually happens on the first pass, when the LRU list |
| * is not already full. |
| */ |
| if ( FT_ALLOC( node, clazz->node_size ) ) |
| goto Fail; |
| } |
| |
| FT_ASSERT( node != NULL ); |
| |
| node->key = key; |
| error = clazz->node_init( node, key, list->data ); |
| if ( error ) |
| { |
| if ( clazz->node_done ) |
| clazz->node_done( node, list->data ); |
| |
| FT_FREE( node ); |
| goto Fail; |
| } |
| |
| Success: |
| result = node; |
| |
| node->next = list->nodes; |
| list->nodes = node; |
| list->num_nodes++; |
| goto Exit; |
| |
| Fail: |
| if ( error != FTC_Err_Out_Of_Memory ) |
| goto Exit; |
| |
| drop_last = 1; |
| continue; |
| } |
| } |
| |
| Exit: |
| *anode = result; |
| return error; |
| } |
| |
| |
| FT_EXPORT_DEF( void ) |
| FT_LruList_Remove( FT_LruList list, |
| FT_LruNode node ) |
| { |
| FT_LruNode *pnode; |
| |
| |
| if ( !list || !node ) |
| return; |
| |
| pnode = &list->nodes; |
| for (;;) |
| { |
| if ( *pnode == node ) |
| { |
| FT_Memory memory = list->memory; |
| FT_LruList_Class clazz = list->clazz; |
| |
| |
| *pnode = node->next; |
| node->next = NULL; |
| |
| if ( clazz->node_done ) |
| clazz->node_done( node, list->data ); |
| |
| FT_FREE( node ); |
| list->num_nodes--; |
| break; |
| } |
| |
| pnode = &(*pnode)->next; |
| } |
| } |
| |
| |
| FT_EXPORT_DEF( void ) |
| FT_LruList_Remove_Selection( FT_LruList list, |
| FT_LruNode_SelectFunc select_func, |
| FT_Pointer select_data ) |
| { |
| FT_LruNode *pnode, node; |
| FT_LruList_Class clazz; |
| FT_Memory memory; |
| |
| |
| if ( !list || !select_func ) |
| return; |
| |
| memory = list->memory; |
| clazz = list->clazz; |
| pnode = &list->nodes; |
| |
| for (;;) |
| { |
| node = *pnode; |
| if ( node == NULL ) |
| break; |
| |
| if ( select_func( node, select_data, list->data ) ) |
| { |
| *pnode = node->next; |
| node->next = NULL; |
| |
| if ( clazz->node_done ) |
| clazz->node_done( node, list ); |
| |
| FT_FREE( node ); |
| list->num_nodes--; |
| } |
| else |
| pnode = &(*pnode)->next; |
| } |
| } |
| |
| |
| /* END */ |