Oolite
Loading...
Searching...
No Matches
OOCache.m
Go to the documentation of this file.
1/*
2
3OOCache.m
4By Jens Ayton
5
6Oolite
7Copyright (C) 2004-2013 Giles C Williams and contributors
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22MA 02110-1301, USA.
23
24*/
25
26/* IMPLEMENTATION NOTES
27 A cache needs to be able to implement three types of operation
28 efficiently:
29 * Retrieving: looking up an element by key.
30 * Inserting: setting the element associated with a key.
31 * Deleting: removing a single element.
32 * Pruning: removing one or more least-recently used elements.
33
34 An NSMutableDictionary performs the first three operations efficiently but
35 has no support for pruning - specifically no support for finding the
36 least-recently-accessed element. Using standard Foundation containers, it
37 would be necessary to use several dictionaries and arrays, which would be
38 quite inefficient since small NSArrays aren’t very good at head insertion
39 or deletion. Alternatively, a standard dictionary whose value objects
40 maintain an age-sorted list could be used.
41
42 I chose instead to implement a custom scheme from scratch. It uses two
43 parallel data structures: a doubly-linked list sorted by age, and a splay
44 tree to implement insertion/deletion. The implementation is largely
45 procedural C. Deserialization, pruning and modification tracking is done
46 in the ObjC class; everything else is done in C functions.
47
48 A SPLAY TREE is a type of semi-balanced binary search tree with certain
49 useful properties:
50 * Simplicity. All look-up and restructuring operations are based on a
51 single operation, splaying, which brings the node with the desired key
52 (or the node whose key is "left" of the desired key, if there is no
53 exact match) to the root, while maintaining the binary search tree
54 invariant. Splaying itself is sufficient for look-up; insertion and
55 deletion work by splaying and then manipulating at the root.
56 * Self-optimization. Because each look-up brings the sought element to
57 the root, often-used elements tend to stay near the top. (Oolite often
58 performs sequences of identical look-ups, for instance when creating
59 an asteroid field, or the racing ring set-up which uses lots of
60 identical ring segments; during combat, missiles, canisters and hull
61 plates will be commonly used.) Also, this means that for a retrieve-
62 attempt/insert sequence, the retrieve attempt will optimize the tree
63 for the insertion.
64 * Efficiency. In addition to the self-optimization, splay trees have a
65 small code size and no storage overhead for flags.
66 The amortized worst-case cost of splaying (cost averaged over a
67 worst-case sequence of operations) is O(log n); a single worst-case
68 splay is O(n), but that worst-case also improves the balance of the
69 tree, so you can't have two worst cases in a row. Insertion and
70 deletion are also O(log n), consisting of a splay plus an O(1)
71 operation.
72 References for splay trees:
73 * http://www.cs.cmu.edu/~sleator/papers/self-adjusting.pdf
74 Original research paper.
75 * http://www.ibr.cs.tu-bs.de/courses/ss98/audii/applets/BST/SplayTree-Example.html
76 Java applet demonstrating splaying.
77 * http://www.link.cs.cmu.edu/link/ftp-site/splaying/top-down-splay.c
78 Sample implementation by one of the inventors. The TreeSplay(),
79 TreeInsert() and CacheRemove() functions are based on this.
80
81 The AGE LIST is a doubly-linked list, ordered from oldest to youngest.
82 Whenever an element is retrieved or inserted, it is promoted to the
83 youngest end of the age list. Pruning proceeds from the oldest end of the
84 age list.
85
86 if (autoPrune)
87 {
88 PRUNING is batched, handling 20% of the cache at once. This is primarily
89 because deletion somewhat pessimizes the tree (see "Self-optimization"
90 below). It also provides a bit of code coherency. To reduce pruning
91 batches while in flight, pruning is also performed before serialization
92 (which in turn is done, if the cache has changed, whenever the user
93 docks). This has the effect that the number of items in the cache on disk
94 never exceeds 80% of the prune threshold. This is probably not actually
95 poinful, since pruning should be a very small portion of the per-frame run
96 time in any case. Premature optimization and all that jazz.
97 Pruning performs at most 0.2n deletions, and is thus O(n log n).
98 }
99 else
100 {
101 PRUNING is performed manually by calling -prune.
102 }
103
104 If the macro OOCACHE_PERFORM_INTEGRITY_CHECKS is set to a non-zero value,
105 the integrity of the tree and the age list will be checked before and
106 after each high-level operation. This is an inherently O(n) operation.
107*/
108
109
110#import "OOCache.h"
111#import "OOStringParsing.h"
112
113
114#ifndef OOCACHE_PERFORM_INTEGRITY_CHECKS
115#define OOCACHE_PERFORM_INTEGRITY_CHECKS 0
116#endif
117
118
119// Protocol used internally to squash idiotic warnings in gnu-gcc.
120@protocol OOCacheComparable <NSObject, NSCopying>
121- (NSComparisonResult) compare:(id<OOCacheComparable>)other;
122- (id) copy;
123@end
124
125
128
130{
131 // Splay tree root
133
134 // Ends of age list
136
137 unsigned count;
138 NSString *name;
139};
140
141
143{
144 // Payload
145 id<OOCacheComparable> key;
147
148 // Splay tree
150
151 // Age list
153};
154
155
156
157enum { kCountUnknown = -1U };
158
159
160static NSString * const kSerializedEntryKeyKey = @"key";
161static NSString * const kSerializedEntryKeyValue = @"value";
162
163
164static OOCacheImpl *CacheAllocate(void);
165static void CacheFree(OOCacheImpl *cache);
166
167static BOOL CacheInsert(OOCacheImpl *cache, id key, id value);
168static BOOL CacheRemove(OOCacheImpl *cache, id key);
169static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey);
170static id CacheRetrieve(OOCacheImpl *cache, id key);
171static unsigned CacheGetCount(OOCacheImpl *cache);
172static NSArray *CacheArrayOfContentsByAge(OOCacheImpl *cache);
173static NSArray *CacheArrayOfNodesByAge(OOCacheImpl *cache);
174static NSString *CacheGetName(OOCacheImpl *cache);
175static void CacheSetName(OOCacheImpl *cache, NSString *name);
176
177#if OOCACHE_PERFORM_INTEGRITY_CHECKS
178static NSString * const kOOLogCacheIntegrityCheck = @"dataCache.integrityCheck";
179static void CacheCheckIntegrity(OOCacheImpl *cache, NSString *context);
180
181#define CHECK_INTEGRITY(context) CacheCheckIntegrity(cache, (context))
182#else
183#define CHECK_INTEGRITY(context) do {} while (0)
184#endif
185
186
187@interface OOCache (Private)
188
189- (void)loadFromArray:(NSArray *)inArray;
190
191@end
192
193
194@implementation OOCache
195
196
197- (void)dealloc
198{
199 CHECK_INTEGRITY(@"dealloc");
200 CacheFree(cache);
201
202 [super dealloc];
203}
204
205
206- (NSString *)description
207{
208 return [NSString stringWithFormat:@"<%@ %p>{\"%@\", %u elements, prune threshold=%u, auto-prune=%s dirty=%s}", [self class], self, [self name], CacheGetCount(cache), pruneThreshold, autoPrune ? "yes" : "no", dirty ? "yes" : "no"];
209}
210
211
212- (id)init
213{
214 return [self initWithPList:nil];
215}
216
217
218- (id)initWithPList:(id)pList
219{
220 BOOL OK = YES;
221
222 self = [super init];
223 OK = self != nil;
224
225 if (OK)
226 {
227 cache = CacheAllocate();
228 if (cache == NULL) OK = NO;
229 }
230
231 if (pList != nil)
232 {
233 if (OK) OK = [pList isKindOfClass:[NSArray class]];
234 if (OK) [self loadFromArray:pList];
235 }
236 if (OK)
237 {
238 pruneThreshold = kOOCacheDefaultPruneThreshold;
239 autoPrune = YES;
240 }
241
242 if (!OK)
243 {
244 [self release];
245 self = nil;
246 }
247
248 return self;
249}
250
251
252- (id)pListRepresentation
253{
254 return CacheArrayOfNodesByAge(cache);
255
256 return nil;
257}
258
259
260- (id)objectForKey:(id)key
261{
262 id result = nil;
263
264 CHECK_INTEGRITY(@"objectForKey: before");
265
266 result = CacheRetrieve(cache, key);
267 // Note: while reordering the age list technically makes the cache dirty, it's not worth rewriting it just for that, so we don't flag it.
268
269 CHECK_INTEGRITY(@"objectForKey: after");
270
271 return [[result retain] autorelease];
272}
273
274
275- (void)setObject:inObject forKey:(id)key
276{
277 CHECK_INTEGRITY(@"setObject:forKey: before");
278
279 if (CacheInsert(cache, key, inObject))
280 {
281 dirty = YES;
282 if (autoPrune) [self prune];
283 }
284
285 CHECK_INTEGRITY(@"setObject:forKey: after");
286}
287
288
289- (void)removeObjectForKey:(id)key
290{
291 CHECK_INTEGRITY(@"removeObjectForKey: before");
292
293 if (CacheRemove(cache, key)) dirty = YES;
294
295 CHECK_INTEGRITY(@"removeObjectForKey: after");
296}
297
298
299- (void)setPruneThreshold:(unsigned)threshold
300{
301 threshold = MAX(threshold, (unsigned)kOOCacheMinimumPruneThreshold);
302 if (threshold != pruneThreshold)
303 {
304 pruneThreshold = threshold;
305 if (autoPrune) [self prune];
306 }
307}
308
309
310- (unsigned)pruneThreshold
311{
312 return pruneThreshold;
313}
314
315
316- (void)setAutoPrune:(BOOL)flag
317{
318 BOOL prune = (flag != NO);
319 if (prune != autoPrune)
320 {
321 autoPrune = prune;
322 [self prune];
323 }
324}
325
326
327- (BOOL)autoPrune
328{
329 return autoPrune;
330}
331
332
333- (void)prune
334{
335 unsigned pruneCount;
336 unsigned desiredCount;
337 unsigned count;
338
339 // Order of operations is to ensure rounding down.
340 if (autoPrune) desiredCount = (pruneThreshold * 4) / 5;
341 else desiredCount = pruneThreshold;
342
343 if (pruneThreshold == kOOCacheNoPrune || (count = CacheGetCount(cache)) <= pruneThreshold) return;
344
345 pruneCount = count - desiredCount;
346
347 NSString *logKey = [NSString stringWithFormat:@"dataCache.prune.%@", CacheGetName(cache)];
348 OOLog(logKey, @"Pruning cache \"%@\" - removing %u entries", CacheGetName(cache), pruneCount);
349 OOLogIndentIf(logKey);
350
351 while (pruneCount--) CacheRemoveOldest(cache, logKey);
352
353 OOLogOutdentIf(logKey);
354}
355
356
357- (BOOL)dirty
358{
359 return dirty;
360}
361
362
363- (void)markClean
364{
365 dirty = NO;
366}
367
368
369- (NSString *)name
370{
371 return CacheGetName(cache);
372}
373
374
375- (void)setName:(NSString *)name
376{
377 CacheSetName(cache, name);
378}
379
380
381- (NSArray *) objectsByAge
382{
383 return CacheArrayOfContentsByAge(cache);
384}
385
386@end
387
388
389@implementation OOCache (Private)
390
391- (void)loadFromArray:(NSArray *)array
392{
393 NSDictionary *entry = nil;
394 NSString *key = nil;
395 id value = nil;
396
397 if (array == nil) return;
398
399 foreach (entry, array)
400 {
401 if ([entry isKindOfClass:[NSDictionary class]])
402 {
403 key = [entry objectForKey:kSerializedEntryKeyKey];
404 value = [entry objectForKey:kSerializedEntryKeyValue];
405 if ([key isKindOfClass:[NSString class]] && value != nil)
406 {
407 [self setObject:value forKey:key];
408 }
409 }
410 }
411}
412
413@end
414
415
416/***** Most of the implementation. In C. Because I'm inconsistent and slightly m. *****/
417
418static OOCacheNode *CacheNodeAllocate(id<OOCacheComparable> key, id value);
419static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node);
420static id CacheNodeGetValue(OOCacheNode *node);
421static void CacheNodeSetValue(OOCacheNode *node, id value);
422
423#if OOCACHE_PERFORM_INTEGRITY_CHECKS
424static NSString *CacheNodeGetDescription(OOCacheNode *node);
425#endif
426
427static OOCacheNode *TreeSplay(OOCacheNode **root, id<OOCacheComparable> key);
428static OOCacheNode *TreeInsert(OOCacheImpl *cache, id<OOCacheComparable> key, id value);
429
430#if OOCACHE_PERFORM_INTEGRITY_CHECKS
431static unsigned TreeCountNodes(OOCacheNode *node);
432static OOCacheNode *TreeCheckIntegrity(OOCacheImpl *cache, OOCacheNode *node, OOCacheNode *expectedParent, NSString *context);
433#endif
434
435static void AgeListMakeYoungest(OOCacheImpl *cache, OOCacheNode *node);
436static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node);
437
438#if OOCACHE_PERFORM_INTEGRITY_CHECKS
439static void AgeListCheckIntegrity(OOCacheImpl *cache, NSString *context);
440#endif
441
442
443/***** CacheImpl functions *****/
444
446{
447 return calloc(sizeof (OOCacheImpl), 1);
448}
449
450
451static void CacheFree(OOCacheImpl *cache)
452{
453 if (cache == NULL) return;
454
455 CacheNodeFree(cache, cache->root);
456 [cache->name autorelease];
457 free(cache);
458}
459
460
461static BOOL CacheInsert(OOCacheImpl *cache, id key, id value)
462{
463 OOCacheNode *node = NULL;
464
465 if (cache == NULL || key == nil || value == nil) return NO;
466
467 node = TreeInsert(cache, key, value);
468 if (node != NULL)
469 {
470 AgeListMakeYoungest(cache, node);
471 return YES;
472 }
473 else return NO;
474}
475
476
477static BOOL CacheRemove(OOCacheImpl *cache, id key)
478{
479 OOCacheNode *node = NULL, *newRoot = NULL;
480
481 node = TreeSplay(&cache->root, key);
482 if (node != NULL)
483 {
484 if (node->leftChild == NULL) newRoot = node->rightChild;
485 else
486 {
487 newRoot = node->leftChild;
488 TreeSplay(&newRoot, key);
489 newRoot->rightChild = node->rightChild;
490 }
491 node->leftChild = NULL;
492 node->rightChild = NULL;
493
494 cache->root = newRoot;
495 --cache->count;
496
497 AgeListRemove(cache, node);
498 CacheNodeFree(cache, node);
499
500 return YES;
501 }
502 else return NO;
503}
504
505
506static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey)
507{
508 // This could be more efficient, but does it need to be?
509 if (cache == NULL || cache->oldest == NULL) return NO;
510
511 OOLog(logKey, @"Pruning cache \"%@\": removing %@", cache->name, cache->oldest->key);
512 return CacheRemove(cache, cache->oldest->key);
513}
514
515
516static id CacheRetrieve(OOCacheImpl *cache, id key)
517{
518 OOCacheNode *node = NULL;
519 id result = nil;
520
521 if (cache == NULL || key == NULL) return nil;
522
523 node = TreeSplay(&cache->root, key);
524 if (node != NULL)
525 {
526 result = CacheNodeGetValue(node);
527 AgeListMakeYoungest(cache, node);
528 }
529 return result;
530}
531
532
534{
535 OOCacheNode *node = NULL;
536 NSMutableArray *result = nil;
537
538 if (cache == NULL || cache->count == 0) return nil;
539
540 result = [NSMutableArray arrayWithCapacity:cache->count];
541
542 for (node = cache->youngest; node != NULL; node = node->older)
543 {
544 [result addObject:node->value];
545 }
546 return result;
547}
548
549
550static NSArray *CacheArrayOfNodesByAge(OOCacheImpl *cache)
551{
552 OOCacheNode *node = NULL;
553 NSMutableArray *result = nil;
554
555 if (cache == NULL || cache->count == 0) return nil;
556
557 result = [NSMutableArray arrayWithCapacity:cache->count];
558
559 for (node = cache->oldest; node != NULL; node = node->younger)
560 {
561 [result addObject:[NSDictionary dictionaryWithObjectsAndKeys:node->key, kSerializedEntryKeyKey, node->value, kSerializedEntryKeyValue, nil]];
562 }
563 return result;
564}
565
566
567static NSString *CacheGetName(OOCacheImpl *cache)
568{
569 return cache->name;
570}
571
572
573static void CacheSetName(OOCacheImpl *cache, NSString *name)
574{
575 [cache->name autorelease];
576 cache->name = [name copy];
577}
578
579
580static unsigned CacheGetCount(OOCacheImpl *cache)
581{
582 return cache->count;
583}
584
585#if OOCACHE_PERFORM_INTEGRITY_CHECKS
586
587static void CacheCheckIntegrity(OOCacheImpl *cache, NSString *context)
588{
589 unsigned trueCount;
590
591 cache->root = TreeCheckIntegrity(cache, cache->root, NULL, context);
592
593 trueCount = TreeCountNodes(cache->root);
594 if (kCountUnknown == cache->count) cache->count = trueCount;
595 else if (cache->count != trueCount)
596 {
597 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): count is %u, but should be %u.", context, cache->name, cache->count, trueCount);
598 cache->count = trueCount;
599 }
600
601 AgeListCheckIntegrity(cache, context);
602}
603
604#endif // OOCACHE_PERFORM_INTEGRITY_CHECKS
605
606
607/***** CacheNode functions *****/
608
609// CacheNodeAllocate(): create a cache node for a key, value pair, without inserting it in the structures.
610static OOCacheNode *CacheNodeAllocate(id<OOCacheComparable> key, id value)
611{
612 OOCacheNode *result = NULL;
613
614 if (key == nil || value == nil) return NULL;
615
616 result = calloc(sizeof *result, 1);
617 if (result != NULL)
618 {
619 result->key = [key copy];
620 result->value = [value retain];
621 }
622
623 return result;
624}
625
626
627// CacheNodeFree(): recursively delete a cache node and its children in the splay tree. To delete an individual node, first clear its child pointers.
628static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node)
629{
630 id key, value;
631
632 if (node == NULL) return;
633
634 AgeListRemove(cache, node);
635
636 key = node->key;
637 node->key = nil;
638 [key release];
639
640 value = node->value;
641 node->value = nil;
642 [value release];
643
644 CacheNodeFree(cache, node->leftChild);
645 CacheNodeFree(cache, node->rightChild);
646
647 free(node);
648}
649
650
651// CacheNodeGetValue(): retrieve the value of a cache node
653{
654 if (node == NULL) return nil;
655
656 return node->value;
657}
658
659
660// CacheNodeSetValue(): change the value of a cache node (as when setObject:forKey: is called for an existing key).
661static void CacheNodeSetValue(OOCacheNode *node, id value)
662{
663 if (node == NULL) return;
664
665 id tmp = node->value;
666 node->value = [value retain];
667 [tmp release];
668}
669
670
671#if OOCACHE_PERFORM_INTEGRITY_CHECKS
672// CacheNodeGetDescription(): get a description of a cache node for debugging purposes.
673static NSString *CacheNodeGetDescription(OOCacheNode *node)
674{
675 if (node == NULL) return @"0[null]";
676
677 return [NSString stringWithFormat:@"%p[\"%@\"]", node, node->key];
678}
679#endif // OOCACHE_PERFORM_INTEGRITY_CHECKS
680
681
682/***** Tree functions *****/
683
684/* TreeSplay()
685 This is the fundamental operation of a splay tree. It searches for a node
686 with a given key, and rebalances the tree so that the found node becomes
687 the root. If no match is found, the node moved to the root is the one that
688 would have been found before the target, and will thus be a neighbour of
689 the target if the key is subsequently inserted.
690*/
691static OOCacheNode *TreeSplay(OOCacheNode **root, id<OOCacheComparable> key)
692{
693 NSComparisonResult order;
694 OOCacheNode N = { .leftChild = NULL, .rightChild = NULL };
695 OOCacheNode *node = NULL, *temp = NULL, *l = &N, *r = &N;
696 BOOL exact = NO;
697
698 if (root == NULL || *root == NULL || key == nil) return NULL;
699
700 node = *root;
701
702 for (;;)
703 {
704#ifndef NDEBUG
705 if (node == NULL)
706 {
707 OOLog(@"node.error", @"%@", @"node is NULL");
708 }
709 else if (node->key == NULL)
710 {
711 OOLog(@"node.error", @"%@", @"node->key is NULL");
712 }
713#endif
714 order = [key compare:node->key];
715 if (order == NSOrderedAscending)
716 {
717 // Closest match is in left subtree
718 if (node->leftChild == NULL) break;
719 if ([key compare:node->leftChild->key] == NSOrderedAscending)
720 {
721 // Rotate right
722 temp = node->leftChild;
723 node->leftChild = temp->rightChild;
724 temp->rightChild = node;
725 node = temp;
726 if (node->leftChild == NULL) break;
727 }
728 // Link right
729 r->leftChild = node;
730 r = node;
731 node = node->leftChild;
732 }
733 else if (order == NSOrderedDescending)
734 {
735 // Closest match is in right subtree
736 if (node->rightChild == NULL) break;
737 if ([key compare:node->rightChild->key] == NSOrderedDescending)
738 {
739 // Rotate left
740 temp = node->rightChild;
741 node->rightChild = temp->leftChild;
742 temp->leftChild = node;
743 node = temp;
744 if (node->rightChild == NULL) break;
745 }
746 // Link left
747 l->rightChild = node;
748 l = node;
749 node = node->rightChild;
750 }
751 else
752 {
753 // Found exact match
754 exact = YES;
755 break;
756 }
757 }
758
759 // Assemble
760 l->rightChild = node->leftChild;
761 r->leftChild = node->rightChild;
762 node->leftChild = N.rightChild;
763 node->rightChild = N.leftChild;
764
765 *root = node;
766 return exact ? node : NULL;
767}
768
769
770static OOCacheNode *TreeInsert(OOCacheImpl *cache, id<OOCacheComparable> key, id value)
771{
772 OOCacheNode *closest = NULL,
773 *node = NULL;
774 NSComparisonResult order;
775
776 if (cache == NULL || key == nil || value == nil) return NULL;
777
778 if (cache->root == NULL)
779 {
780 node = CacheNodeAllocate(key, value);
781 cache->root = node;
782 cache->count = 1;
783 }
784 else
785 {
786 node = TreeSplay(&cache->root, key);
787 if (node != NULL)
788 {
789 // Exact match: key already exists, reuse its node
790 CacheNodeSetValue(node, value);
791 }
792 else
793 {
794 closest = cache->root;
795 node = CacheNodeAllocate(key, value);
796 if (EXPECT_NOT(node == NULL)) return NULL;
797
798 order = [key compare:closest->key];
799
800 if (order == NSOrderedAscending)
801 {
802 // Insert to left
803 node->leftChild = closest->leftChild;
804 node->rightChild = closest;
805 closest->leftChild = NULL;
806 cache->root = node;
807 ++cache->count;
808 }
809 else if (order == NSOrderedDescending)
810 {
811 // Insert to right
812 node->rightChild = closest->rightChild;
813 node->leftChild = closest;
814 closest->rightChild = NULL;
815 cache->root = node;
816 ++cache->count;
817 }
818 else
819 {
820 // Key already exists, which we should have caught above
821 OOLog(@"dataCache.inconsistency", @"%s() internal inconsistency for cache \"%@\", insertion failed.", __PRETTY_FUNCTION__, cache->name);
822 CacheNodeFree(cache, node);
823 return NULL;
824 }
825 }
826 }
827
828 return node;
829}
830
831
832#if OOCACHE_PERFORM_INTEGRITY_CHECKS
833static unsigned TreeCountNodes(OOCacheNode *node)
834{
835 if (node == NULL) return 0;
836 return 1 + TreeCountNodes(node->leftChild) + TreeCountNodes(node->rightChild);
837}
838
839
840// TreeCheckIntegrity(): verify the links and contents of a (sub-)tree. If successful, returns the root of the subtree (which could theoretically be changed), otherwise returns NULL.
841static OOCacheNode *TreeCheckIntegrity(OOCacheImpl *cache, OOCacheNode *node, OOCacheNode *expectedParent, NSString *context)
842{
843 NSComparisonResult order;
844 BOOL OK = YES;
845
846 if (node == NULL) return NULL;
847
848 if (OK && node->key == nil)
849 {
850 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has nil key; deleting subtree.", context, cache->name, CacheNodeGetDescription(node));
851 OK = NO;
852 }
853
854 if (OK && node->value == nil)
855 {
856 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has nil value, deleting.", context, cache->name, CacheNodeGetDescription(node));
857 OK = NO;
858 }
859 if (OK && node->leftChild != NULL)
860 {
861 order = [node->key compare:node->leftChild->key];
862 if (order != NSOrderedDescending)
863 {
864 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node %@'s left child %@ is not correctly ordered. Deleting subtree.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->leftChild));
865 CacheNodeFree(cache, node->leftChild);
866 node->leftChild = NULL;
867 cache->count = kCountUnknown;
868 }
869 else
870 {
871 node->leftChild = TreeCheckIntegrity(cache, node->leftChild, node, context);
872 }
873 }
874 if (node->rightChild != NULL)
875 {
876 order = [node->key compare:node->rightChild->key];
877 if (order != NSOrderedAscending)
878 {
879 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\"'s right child \"%@\" is not correctly ordered. Deleting subtree.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(node->rightChild));
880 CacheNodeFree(cache, node->rightChild);
881 node->rightChild = NULL;
882 cache->count = kCountUnknown;
883 }
884 else
885 {
886 node->rightChild = TreeCheckIntegrity(cache, node->rightChild, node, context);
887 }
888 }
889
890 if (OK) return node;
891 else
892 {
893 cache->count = kCountUnknown;
894 CacheNodeFree(cache, node);
895 return NULL;
896 }
897}
898#endif // OOCACHE_PERFORM_INTEGRITY_CHECKS
899
900
901/***** Age list functions *****/
902
903// AgeListMakeYoungest(): place a given cache node at the youngest end of the age list.
905{
906 if (cache == NULL || node == NULL) return;
907
908 AgeListRemove(cache, node);
909 node->older = cache->youngest;
910 if (NULL != cache->youngest) cache->youngest->younger = node;
911 cache->youngest = node;
912 if (cache->oldest == NULL) cache->oldest = node;
913}
914
915
916// AgeListRemove(): remove a cache node from the age-sorted tree. Does not affect its position in the splay tree.
917static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node)
918{
919 OOCacheNode *younger = NULL;
920 OOCacheNode *older = NULL;
921
922 if (node == NULL) return;
923
924 younger = node->younger;
925 older = node->older;
926
927 if (cache->youngest == node) cache->youngest = older;
928 if (cache->oldest == node) cache->oldest = younger;
929
930 node->younger = NULL;
931 node->older = NULL;
932
933 if (younger != NULL) younger->older = older;
934 if (older != NULL) older->younger = younger;
935}
936
937
938#if OOCACHE_PERFORM_INTEGRITY_CHECKS
939
940static void AgeListCheckIntegrity(OOCacheImpl *cache, NSString *context)
941{
942 OOCacheNode *node = NULL, *next = NULL;
943 unsigned seenCount = 0;
944
945 if (cache == NULL || context == NULL) return;
946
947 node = cache->youngest;
948
949 if (node) for (;;)
950 {
951 next = node->older;
952 ++seenCount;
953 if (next == NULL) break;
954
955 if (next->younger != node)
956 {
957 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): node \"%@\" has invalid older link (should be \"%@\", is \"%@\"); repairing.", context, cache->name, CacheNodeGetDescription(next), CacheNodeGetDescription(node), CacheNodeGetDescription(next->older));
958 next->older = node;
959 }
960 node = next;
961 }
962
963 if (seenCount != cache->count)
964 {
965 // This is especially bad since this function is called just after verifying that the count field reflects the number of objects in the tree.
966 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): expected %u nodes, found %u. Cannot repair; clearing cache.", context, cache->name, cache->count, seenCount);
967
968 /* Start of temporary extra logging */
969 node = cache->youngest;
970
971 if (node)
972 {
973 for (;;)
974 {
975 next = node->older;
976 ++seenCount;
977 if (next == NULL) break;
978
979 if (node->key != NULL)
980 {
981 OOLog(kOOLogCacheIntegrityCheck,@"Key is: %@",node->key);
982 }
983 else
984 {
985 OOLog(kOOLogCacheIntegrityCheck,@"Key is: NULL");
986 }
987
988 if (node->value != NULL)
989 {
990 OOLog(kOOLogCacheIntegrityCheck,@"Value is: %@",node->value);
991 }
992 else
993 {
994 OOLog(kOOLogCacheIntegrityCheck,@"Value is: NULL");
995 }
996
997 node = next;
998 }
999 }
1000 /* End of temporary extra logging */
1001
1002 cache->count = 0;
1003 CacheNodeFree(cache, cache->root);
1004 cache->root = NULL;
1005 cache->youngest = NULL;
1006 cache->oldest = NULL;
1007 return;
1008 }
1009
1010 if (node != cache->oldest)
1011 {
1012 OOLog(kOOLogCacheIntegrityCheck, @"Integrity check (%@ for \"%@\"): oldest pointer in cache is wrong (should be \"%@\", is \"%@\"); repairing.", context, cache->name, CacheNodeGetDescription(node), CacheNodeGetDescription(cache->oldest));
1013 cache->oldest = node;
1014 }
1015}
1016
1017#endif // OOCACHE_PERFORM_INTEGRITY_CHECKS
1018
1019
1020#if DEBUG_GRAPHVIZ
1021
1022/* NOTE: enabling AGE_LIST can result in graph rendering times of many hours,
1023 because determining paths for non-constraint arcs is NP-hard. In particular,
1024 I gave up on rendering a dump of a fairly minimal cache manager after
1025 three and a half hours. Individual caches were fine.
1026*/
1027#define AGE_LIST 0
1028
1029@implementation OOCache (DebugGraphViz)
1030
1031- (void) appendNodesFromSubTree:(OOCacheNode *)subTree toString:(NSMutableString *)ioString
1032{
1033 [ioString appendFormat:@"\tn%p [label=\"<f0> | <f1> %@ | <f2>\"];\n", subTree, EscapedGraphVizString([subTree->key description])];
1034
1035 if (subTree->leftChild != NULL)
1036 {
1037 [self appendNodesFromSubTree:subTree->leftChild toString:ioString];
1038 [ioString appendFormat:@"\tn%p:f0 -> n%p:f1;\n", subTree, subTree->leftChild];
1039 }
1040 if (subTree->rightChild != NULL)
1041 {
1042 [self appendNodesFromSubTree:subTree->rightChild toString:ioString];
1043 [ioString appendFormat:@"\tn%p:f2 -> n%p:f1;\n", subTree, subTree->rightChild];
1044 }
1045}
1046
1047
1048- (NSString *) generateGraphVizBodyWithRootNamed:(NSString *)rootName
1049{
1050 NSMutableString *result = nil;
1051
1052 result = [NSMutableString string];
1053
1054 // Root node representing cache
1055 [result appendFormat:@"\t%@ [label=\"Cache \\\"%@\\\"\" shape=box];\n"
1056 "\tnode [shape=record];\n\t\n", rootName, EscapedGraphVizString([self name])];
1057
1058 if (cache == NULL) return result;
1059
1060 // Cache
1061 [self appendNodesFromSubTree:cache->root toString:result];
1062
1063 // Arc from cache object to root node
1064 [result appendString:@"\tedge [color=black constraint=true];\n"];
1065 [result appendFormat:@"\t%@ -> n%p:f1;\n", rootName, cache->root];
1066
1067#if AGE_LIST
1068 OOCacheNode *node = NULL;
1069 // Arcs representing age list
1070 [result appendString:@"\t\n\t// Age-sorted list in blue\n\tedge [color=blue constraint=false];\n"];
1071 node = cache->oldest;
1072 while (node->younger != NULL)
1073 {
1074 [result appendFormat:@"\tn%p:f2 -> n%p:f0;\n", node, node->younger];
1075 node = node->younger;
1076 }
1077#endif
1078
1079 return result;
1080}
1081
1082
1083- (NSString *) generateGraphViz
1084{
1085 NSMutableString *result = nil;
1086
1087 result = [NSMutableString string];
1088
1089 // Header
1090 [result appendFormat:
1091 @"// OOCache dump\n\n"
1092 "digraph cache\n"
1093 "{\n"
1094 "\tgraph [charset=\"UTF-8\", label=\"OOCache \"%@\" debug dump\", labelloc=t, labeljust=l];\n\t\n", [self name]];
1095
1096 [result appendString:[self generateGraphVizBodyWithRootNamed:@"cache"]];
1097
1098 [result appendString:@"}\n"];
1099
1100 return result;
1101}
1102
1103
1104- (void) writeGraphVizToURL:(NSURL *)url
1105{
1106 NSString *graphViz = nil;
1107 NSData *data = nil;
1108
1109 graphViz = [self generateGraphViz];
1110 data = [graphViz dataUsingEncoding:NSUTF8StringEncoding];
1111
1112 if (data != nil)
1113 {
1114 [data writeToURL:url atomically:YES];
1115 }
1116}
1117
1118
1119- (void) writeGraphVizToPath:(NSString *)path
1120{
1121 [self writeGraphVizToURL:[NSURL fileURLWithPath:path]];
1122}
1123
1124@end
1125#endif
1126
@ kOOCacheDefaultPruneThreshold
Definition OOCache.h:52
@ kOOCacheNoPrune
Definition OOCache.h:53
@ kOOCacheMinimumPruneThreshold
Definition OOCache.h:51
static void AgeListMakeYoungest(OOCacheImpl *cache, OOCacheNode *node)
Definition OOCache.m:904
static NSString *const kSerializedEntryKeyKey
Definition OOCache.m:160
static NSString * CacheGetName(OOCacheImpl *cache)
Definition OOCache.m:567
static void CacheNodeSetValue(OOCacheNode *node, id value)
Definition OOCache.m:661
static OOCacheNode * CacheNodeAllocate(id< OOCacheComparable > key, id value)
Definition OOCache.m:610
static void CacheFree(OOCacheImpl *cache)
Definition OOCache.m:451
static NSArray * CacheArrayOfContentsByAge(OOCacheImpl *cache)
Definition OOCache.m:533
static unsigned CacheGetCount(OOCacheImpl *cache)
Definition OOCache.m:580
#define CHECK_INTEGRITY(context)
Definition OOCache.m:183
@ kCountUnknown
Definition OOCache.m:157
static OOCacheNode * TreeInsert(OOCacheImpl *cache, id< OOCacheComparable > key, id value)
Definition OOCache.m:770
static NSArray * CacheArrayOfNodesByAge(OOCacheImpl *cache)
Definition OOCache.m:550
static BOOL CacheRemoveOldest(OOCacheImpl *cache, NSString *logKey)
Definition OOCache.m:506
static OOCacheImpl * CacheAllocate(void)
Definition OOCache.m:445
static id CacheNodeGetValue(OOCacheNode *node)
Definition OOCache.m:652
static id CacheRetrieve(OOCacheImpl *cache, id key)
Definition OOCache.m:516
static void AgeListRemove(OOCacheImpl *cache, OOCacheNode *node)
Definition OOCache.m:917
static void CacheNodeFree(OOCacheImpl *cache, OOCacheNode *node)
Definition OOCache.m:628
static BOOL CacheRemove(OOCacheImpl *cache, id key)
Definition OOCache.m:477
static NSString *const kSerializedEntryKeyValue
Definition OOCache.m:161
static void CacheSetName(OOCacheImpl *cache, NSString *name)
Definition OOCache.m:573
static BOOL CacheInsert(OOCacheImpl *cache, id key, id value)
Definition OOCache.m:461
static OOCacheNode * TreeSplay(OOCacheNode **root, id< OOCacheComparable > key)
Definition OOCache.m:691
#define EXPECT_NOT(x)
#define OOLogOutdentIf(class)
Definition OOLogging.h:102
#define OOLog(class, format,...)
Definition OOLogging.h:88
#define OOLogIndentIf(class)
Definition OOLogging.h:101
#define MAX(A, B)
Definition OOMaths.h:114
unsigned count
return nil
unsigned count
Definition OOCache.m:137
OOCacheNode * root
Definition OOCache.m:132
OOCacheNode * youngest
Definition OOCache.m:135
NSString * name
Definition OOCache.m:138
OOCacheNode * oldest
Definition OOCache.m:135
OOCacheNode * rightChild
Definition OOCache.m:149
OOCacheNode * older
Definition OOCache.m:152
OOCacheNode * younger
Definition OOCache.m:152
id< OOCacheComparable > key
Definition OOCache.m:145
OOCacheNode * leftChild
Definition OOCache.m:149