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