tizen 2.3.1 release
[framework/graphics/freetype.git] / src / cache / ftccache.c
1 /***************************************************************************/
2 /*                                                                         */
3 /*  ftccache.c                                                             */
4 /*                                                                         */
5 /*    The FreeType internal cache interface (body).                        */
6 /*                                                                         */
7 /*  Copyright 2000-2007, 2009-2011, 2013 by                                */
8 /*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
9 /*                                                                         */
10 /*  This file is part of the FreeType project, and may only be used,       */
11 /*  modified, and distributed under the terms of the FreeType project      */
12 /*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
13 /*  this file you indicate that you have read the license and              */
14 /*  understand and accept it fully.                                        */
15 /*                                                                         */
16 /***************************************************************************/
17
18
19 #include <ft2build.h>
20 #include "ftcmanag.h"
21 #include FT_INTERNAL_OBJECTS_H
22 #include FT_INTERNAL_DEBUG_H
23
24 #include "ftccback.h"
25 #include "ftcerror.h"
26
27 #undef  FT_COMPONENT
28 #define FT_COMPONENT  trace_cache
29
30
31 #define FTC_HASH_MAX_LOAD  2
32 #define FTC_HASH_MIN_LOAD  1
33 #define FTC_HASH_SUB_LOAD  ( FTC_HASH_MAX_LOAD - FTC_HASH_MIN_LOAD )
34
35   /* this one _must_ be a power of 2! */
36 #define FTC_HASH_INITIAL_SIZE  8
37
38
39   /*************************************************************************/
40   /*************************************************************************/
41   /*****                                                               *****/
42   /*****                   CACHE NODE DEFINITIONS                      *****/
43   /*****                                                               *****/
44   /*************************************************************************/
45   /*************************************************************************/
46
47   /* add a new node to the head of the manager's circular MRU list */
48   static void
49   ftc_node_mru_link( FTC_Node     node,
50                      FTC_Manager  manager )
51   {
52     void  *nl = &manager->nodes_list;
53
54
55     FTC_MruNode_Prepend( (FTC_MruNode*)nl,
56                          (FTC_MruNode)node );
57     manager->num_nodes++;
58   }
59
60
61   /* remove a node from the manager's MRU list */
62   static void
63   ftc_node_mru_unlink( FTC_Node     node,
64                        FTC_Manager  manager )
65   {
66     void  *nl = &manager->nodes_list;
67
68
69     FTC_MruNode_Remove( (FTC_MruNode*)nl,
70                         (FTC_MruNode)node );
71     manager->num_nodes--;
72   }
73
74
75 #ifndef FTC_INLINE
76
77   /* move a node to the head of the manager's MRU list */
78   static void
79   ftc_node_mru_up( FTC_Node     node,
80                    FTC_Manager  manager )
81   {
82     FTC_MruNode_Up( (FTC_MruNode*)&manager->nodes_list,
83                     (FTC_MruNode)node );
84   }
85
86
87   /* get a top bucket for specified hash from cache,
88    * body for FTC_NODE__TOP_FOR_HASH( cache, hash )
89    */
90   FT_LOCAL_DEF( FTC_Node* )
91   ftc_get_top_node_for_hash( FTC_Cache   cache,
92                              FT_PtrDist  hash )
93   {
94     FTC_Node*  pnode;
95     FT_UInt    idx;
96
97
98     idx = (FT_UInt)( hash & cache->mask );
99     if ( idx < cache->p )
100       idx = (FT_UInt)( hash & ( 2 * cache->mask + 1 ) );
101     pnode = cache->buckets + idx;
102     return pnode;
103   }
104
105 #endif /* !FTC_INLINE */
106
107
108   /* Note that this function cannot fail.  If we cannot re-size the
109    * buckets array appropriately, we simply degrade the hash table's
110    * performance!
111    */
112   static void
113   ftc_cache_resize( FTC_Cache  cache )
114   {
115     for (;;)
116     {
117       FTC_Node  node, *pnode;
118       FT_UFast  p     = cache->p;
119       FT_UFast  mask  = cache->mask;
120       FT_UFast  count = mask + p + 1;    /* number of buckets */
121
122
123       /* do we need to shrink the buckets array? */
124       if ( cache->slack < 0 )
125       {
126         FTC_Node  new_list = NULL;
127
128
129         /* try to expand the buckets array _before_ splitting
130          * the bucket lists
131          */
132         if ( p >= mask )
133         {
134           FT_Memory  memory = cache->memory;
135           FT_Error   error;
136
137
138           /* if we can't expand the array, leave immediately */
139           if ( FT_RENEW_ARRAY( cache->buckets,
140                                ( mask + 1 ) * 2, ( mask + 1 ) * 4 ) )
141             break;
142         }
143
144         /* split a single bucket */
145         pnode = cache->buckets + p;
146
147         for (;;)
148         {
149           node = *pnode;
150           if ( node == NULL )
151             break;
152
153           if ( node->hash & ( mask + 1 ) )
154           {
155             *pnode     = node->link;
156             node->link = new_list;
157             new_list   = node;
158           }
159           else
160             pnode = &node->link;
161         }
162
163         cache->buckets[p + mask + 1] = new_list;
164
165         cache->slack += FTC_HASH_MAX_LOAD;
166
167         if ( p >= mask )
168         {
169           cache->mask = 2 * mask + 1;
170           cache->p    = 0;
171         }
172         else
173           cache->p = p + 1;
174       }
175
176       /* do we need to expand the buckets array? */
177       else if ( cache->slack > (FT_Long)count * FTC_HASH_SUB_LOAD )
178       {
179         FT_UFast   old_index = p + mask;
180         FTC_Node*  pold;
181
182
183         if ( old_index + 1 <= FTC_HASH_INITIAL_SIZE )
184           break;
185
186         if ( p == 0 )
187         {
188           FT_Memory  memory = cache->memory;
189           FT_Error   error;
190
191
192           /* if we can't shrink the array, leave immediately */
193           if ( FT_RENEW_ARRAY( cache->buckets,
194                                ( mask + 1 ) * 2, mask + 1 ) )
195             break;
196
197           cache->mask >>= 1;
198           p             = cache->mask;
199         }
200         else
201           p--;
202
203         pnode = cache->buckets + p;
204         while ( *pnode )
205           pnode = &(*pnode)->link;
206
207         pold   = cache->buckets + old_index;
208         *pnode = *pold;
209         *pold  = NULL;
210
211         cache->slack -= FTC_HASH_MAX_LOAD;
212         cache->p      = p;
213       }
214
215       /* otherwise, the hash table is balanced */
216       else
217         break;
218     }
219   }
220
221
222   /* remove a node from its cache's hash table */
223   static void
224   ftc_node_hash_unlink( FTC_Node   node0,
225                         FTC_Cache  cache )
226   {
227     FTC_Node  *pnode = FTC_NODE__TOP_FOR_HASH( cache, node0->hash );
228
229
230     for (;;)
231     {
232       FTC_Node  node = *pnode;
233
234
235       if ( node == NULL )
236       {
237         FT_TRACE0(( "ftc_node_hash_unlink: unknown node\n" ));
238         return;
239       }
240
241       if ( node == node0 )
242         break;
243
244       pnode = &(*pnode)->link;
245     }
246
247     *pnode      = node0->link;
248     node0->link = NULL;
249
250     cache->slack++;
251     ftc_cache_resize( cache );
252   }
253
254
255   /* add a node to the `top' of its cache's hash table */
256   static void
257   ftc_node_hash_link( FTC_Node   node,
258                       FTC_Cache  cache )
259   {
260     FTC_Node  *pnode = FTC_NODE__TOP_FOR_HASH( cache, node->hash );
261
262
263     node->link = *pnode;
264     *pnode     = node;
265
266     cache->slack--;
267     ftc_cache_resize( cache );
268   }
269
270
271   /* remove a node from the cache manager */
272   FT_LOCAL_DEF( void )
273   ftc_node_destroy( FTC_Node     node,
274                     FTC_Manager  manager )
275   {
276     FTC_Cache  cache;
277
278
279 #ifdef FT_DEBUG_ERROR
280     /* find node's cache */
281     if ( node->cache_index >= manager->num_caches )
282     {
283       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
284       return;
285     }
286 #endif
287
288     cache = manager->caches[node->cache_index];
289
290 #ifdef FT_DEBUG_ERROR
291     if ( cache == NULL )
292     {
293       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
294       return;
295     }
296 #endif
297
298     manager->cur_weight -= cache->clazz.node_weight( node, cache );
299
300     /* remove node from mru list */
301     ftc_node_mru_unlink( node, manager );
302
303     /* remove node from cache's hash table */
304     ftc_node_hash_unlink( node, cache );
305
306     /* now finalize it */
307     cache->clazz.node_free( node, cache );
308
309 #if 0
310     /* check, just in case of general corruption :-) */
311     if ( manager->num_nodes == 0 )
312       FT_TRACE0(( "ftc_node_destroy: invalid cache node count (%d)\n",
313                   manager->num_nodes ));
314 #endif
315   }
316
317
318   /*************************************************************************/
319   /*************************************************************************/
320   /*****                                                               *****/
321   /*****                    ABSTRACT CACHE CLASS                       *****/
322   /*****                                                               *****/
323   /*************************************************************************/
324   /*************************************************************************/
325
326
327   FT_LOCAL_DEF( FT_Error )
328   FTC_Cache_Init( FTC_Cache  cache )
329   {
330     return ftc_cache_init( cache );
331   }
332
333
334   FT_LOCAL_DEF( FT_Error )
335   ftc_cache_init( FTC_Cache  cache )
336   {
337     FT_Memory  memory = cache->memory;
338     FT_Error   error;
339
340
341     cache->p     = 0;
342     cache->mask  = FTC_HASH_INITIAL_SIZE - 1;
343     cache->slack = FTC_HASH_INITIAL_SIZE * FTC_HASH_MAX_LOAD;
344
345     (void)FT_NEW_ARRAY( cache->buckets, FTC_HASH_INITIAL_SIZE * 2 );
346     return error;
347   }
348
349
350   static void
351   FTC_Cache_Clear( FTC_Cache  cache )
352   {
353     if ( cache && cache->buckets )
354     {
355       FTC_Manager  manager = cache->manager;
356       FT_UFast     i;
357       FT_UFast     count;
358
359
360       count = cache->p + cache->mask + 1;
361
362       for ( i = 0; i < count; i++ )
363       {
364         FTC_Node  *pnode = cache->buckets + i, next, node = *pnode;
365
366
367         while ( node )
368         {
369           next        = node->link;
370           node->link  = NULL;
371
372           /* remove node from mru list */
373           ftc_node_mru_unlink( node, manager );
374
375           /* now finalize it */
376           manager->cur_weight -= cache->clazz.node_weight( node, cache );
377
378           cache->clazz.node_free( node, cache );
379           node = next;
380         }
381         cache->buckets[i] = NULL;
382       }
383       ftc_cache_resize( cache );
384     }
385   }
386
387
388   FT_LOCAL_DEF( void )
389   ftc_cache_done( FTC_Cache  cache )
390   {
391     if ( cache->memory )
392     {
393       FT_Memory  memory = cache->memory;
394
395
396       FTC_Cache_Clear( cache );
397
398       FT_FREE( cache->buckets );
399       cache->mask  = 0;
400       cache->p     = 0;
401       cache->slack = 0;
402
403       cache->memory = NULL;
404     }
405   }
406
407
408   FT_LOCAL_DEF( void )
409   FTC_Cache_Done( FTC_Cache  cache )
410   {
411     ftc_cache_done( cache );
412   }
413
414
415   static void
416   ftc_cache_add( FTC_Cache  cache,
417                  FT_PtrDist hash,
418                  FTC_Node   node )
419   {
420     node->hash        = hash;
421     node->cache_index = (FT_UInt16)cache->index;
422     node->ref_count   = 0;
423
424     ftc_node_hash_link( node, cache );
425     ftc_node_mru_link( node, cache->manager );
426
427     {
428       FTC_Manager  manager = cache->manager;
429
430
431       manager->cur_weight += cache->clazz.node_weight( node, cache );
432
433       if ( manager->cur_weight >= manager->max_weight )
434       {
435         node->ref_count++;
436         FTC_Manager_Compress( manager );
437         node->ref_count--;
438       }
439     }
440   }
441
442
443   FT_LOCAL_DEF( FT_Error )
444   FTC_Cache_NewNode( FTC_Cache   cache,
445                      FT_PtrDist  hash,
446                      FT_Pointer  query,
447                      FTC_Node   *anode )
448   {
449     FT_Error  error;
450     FTC_Node  node;
451
452
453     /*
454      * We use the FTC_CACHE_TRYLOOP macros to support out-of-memory
455      * errors (OOM) correctly, i.e., by flushing the cache progressively
456      * in order to make more room.
457      */
458
459     FTC_CACHE_TRYLOOP( cache )
460     {
461       error = cache->clazz.node_new( &node, query, cache );
462     }
463     FTC_CACHE_TRYLOOP_END( NULL );
464
465     if ( error )
466       node = NULL;
467     else
468     {
469      /* don't assume that the cache has the same number of buckets, since
470       * our allocation request might have triggered global cache flushing
471       */
472       ftc_cache_add( cache, hash, node );
473     }
474
475     *anode = node;
476     return error;
477   }
478
479
480 #ifndef FTC_INLINE
481
482   FT_LOCAL_DEF( FT_Error )
483   FTC_Cache_Lookup( FTC_Cache   cache,
484                     FT_PtrDist  hash,
485                     FT_Pointer  query,
486                     FTC_Node   *anode )
487   {
488     FTC_Node*  bucket;
489     FTC_Node*  pnode;
490     FTC_Node   node;
491     FT_Error   error        = FT_Err_Ok;
492     FT_Bool    list_changed = FALSE;
493
494     FTC_Node_CompareFunc  compare = cache->clazz.node_compare;
495
496
497     if ( cache == NULL || anode == NULL )
498       return FT_THROW( Invalid_Argument );
499
500     /* Go to the `top' node of the list sharing same masked hash */
501     bucket = pnode = FTC_NODE__TOP_FOR_HASH( cache, hash );
502
503     /* Lookup a node with exactly same hash and queried properties.  */
504     /* NOTE: _nodcomp() may change the linked list to reduce memory. */
505     for (;;)
506     {
507       node = *pnode;
508       if ( node == NULL )
509         goto NewNode;
510
511       if ( node->hash == hash                           &&
512            compare( node, query, cache, &list_changed ) )
513         break;
514
515       pnode = &node->link;
516     }
517
518     if ( list_changed )
519     {
520       /* Update bucket by modified linked list */
521       bucket = pnode = FTC_NODE__TOP_FOR_HASH( cache, hash );
522
523       /* Update pnode by modified linked list */
524       while ( *pnode != node )
525       {
526         if ( *pnode == NULL )
527         {
528           FT_ERROR(( "FTC_Cache_Lookup: oops!!!  node missing\n" ));
529           goto NewNode;
530         }
531         else
532           pnode = &((*pnode)->link);
533       }
534     }
535
536     /* Reorder the list to move the found node to the `top' */
537     if ( node != *bucket )
538     {
539       *pnode     = node->link;
540       node->link = *bucket;
541       *bucket    = node;
542     }
543
544     /* move to head of MRU list */
545     {
546       FTC_Manager  manager = cache->manager;
547
548
549       if ( node != manager->nodes_list )
550         ftc_node_mru_up( node, manager );
551     }
552     *anode = node;
553
554     return error;
555
556   NewNode:
557     return FTC_Cache_NewNode( cache, hash, query, anode );
558   }
559
560 #endif /* !FTC_INLINE */
561
562
563   FT_LOCAL_DEF( void )
564   FTC_Cache_RemoveFaceID( FTC_Cache   cache,
565                           FTC_FaceID  face_id )
566   {
567     FT_UFast     i, count;
568     FTC_Manager  manager = cache->manager;
569     FTC_Node     frees   = NULL;
570
571
572     count = cache->p + cache->mask + 1;
573     for ( i = 0; i < count; i++ )
574     {
575       FTC_Node*  bucket = cache->buckets + i;
576       FTC_Node*  pnode  = bucket;
577
578
579       for ( ;; )
580       {
581         FTC_Node  node = *pnode;
582         FT_Bool   list_changed = FALSE;
583
584
585         if ( node == NULL )
586           break;
587
588         if ( cache->clazz.node_remove_faceid( node, face_id,
589                                               cache, &list_changed ) )
590         {
591           *pnode     = node->link;
592           node->link = frees;
593           frees      = node;
594         }
595         else
596           pnode = &node->link;
597       }
598     }
599
600     /* remove all nodes in the free list */
601     while ( frees )
602     {
603       FTC_Node  node;
604
605
606       node  = frees;
607       frees = node->link;
608
609       manager->cur_weight -= cache->clazz.node_weight( node, cache );
610       ftc_node_mru_unlink( node, manager );
611
612       cache->clazz.node_free( node, cache );
613
614       cache->slack++;
615     }
616
617     ftc_cache_resize( cache );
618   }
619
620
621 /* END */