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