[FREETYPE]
[reactos.git] / reactos / lib / 3rdparty / freetype / 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 */