1 /*******************************************************************************
2 
3     A priority cache which stores a limited amount of items defined at the
4     instantiation tine of the class. When the cache is full and a new object is
5     added the item with the least priority gets dropped.
6 
7 
8     To create a new cache class you have to specify the maximum of items that
9     can be stored:
10     ---
11 
12         const NUM_ITEM = 10;
13         auto cache = new PriorityCache!(char[])(NUM_ITEM);
14 
15     ---
16 
17     To store an item in the cache you should use 'getOrCreate()` method.
18     The method takes a key and a priority value, if the key already exists then
19     the item associated with the key is returned, if it didn't exist then the
20     class will attempt to create to create a new key with the given priority:
21     ---
22 
23         auto key = 1;
24         ulong priority = 20;
25         bool item_existed_before;
26         char[]* item = cache.getOrCreate(key, priority, item_existed_before);
27 
28         if (item)
29         {
30             *item = "ABC";
31         }
32         assert(item_existed_before is false);
33 
34     ---
35 
36     Notice that if the item already existed then the priority won't be used
37     (but you still can assign the item to a new value) .
38     ---
39 
40         ulong no_effect_priority = 70;
41         item = cache.getOrCreate(key, no_effect_priority, item_existed_before);
42 
43         if (item)
44         {
45             *item = "DEF";
46         }
47         assert(item_existed_before is true);
48 
49         ulong retrieved_priority;
50         item = cache.getPriority(key, retrieved_priority);
51         assert(item !is null);
52         assert(*item == "DEF");
53         assert(retrieved_priority == priority); // Not no_effect_priority
54 
55     ---
56 
57     Notice that in all the previous example we have always to check if item is
58     not null (even though we call `getOrCreate()`), if you are using this class
59     directory then there should be no need to check for null as always a new
60     item will be created. If you are using a class which inherits this class
61     then the subclass might override the `whenNewAndLeastPriority()` method.
62     This method decides which item to keep if the cache is full and the a newly
63     added item has a lower priority than the existing item in the cache with the
64     least priority. If the method decided to keep the current item and not t
65     add the new one then the `getOrCreate()` method will return null as no item
66     was found or created.
67 
68     A useful method to be used when the user wants to store an item with a given
69     priority regardless of whether the item is a new item or already existing
70     one but with a different priority is to use `getUpdateOrCreate()` method:
71     ---
72 
73         auto new_priority = 10;
74         item = cache.getUpdateOrCreate(key, new_priority, item_existed_before);
75 
76         cache.getPriority(key, retrieved_priority);
77         assert(item_existed_before is true);
78         assert(item !is null);
79         assert(retrieved_priority == new_priority);
80     ---
81 
82     Copyright:
83         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
84         All rights reserved.
85 
86     License:
87         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
88         Alternatively, this file may be distributed under the terms of the Tango
89         3-Clause BSD License (see LICENSE_BSD.txt for details).
90 
91 *******************************************************************************/
92 
93 module ocean.util.container.cache.PriorityCache;
94 
95 
96 import ocean.util.container.cache.model.ICacheInfo;
97 
98 import ocean.util.container.cache.model.containers.TimeToIndex;
99 import ocean.util.container.cache.model.containers.KeyToNode;
100 
101 import core.memory;
102 import core.stdc.time: time_t, time;
103 
104 /*******************************************************************************
105 
106     Stores a maximum number of items keeping only items with the highest
107     priority.
108 
109 *******************************************************************************/
110 
111 class PriorityCache(T) : ICacheInfo
112 {
113     import ocean.core.Verify;
114 
115     /***************************************************************************
116 
117         A wrapper around the stored item
118 
119     ***************************************************************************/
120 
121     protected struct CacheItem
122     {
123         /***********************************************************************
124 
125             The object itself to be stored in the cache
126 
127         ***********************************************************************/
128 
129         T value;
130 
131         /***********************************************************************
132 
133             The item's key.
134             Used to retrieve the nodes when two items are swapped to update the
135             nodes with the new indices.
136 
137         ***********************************************************************/
138 
139         hash_t key;
140     }
141 
142     /***************************************************************************
143 
144         Insert position into array of items.
145 
146     ***************************************************************************/
147 
148     private size_t insert;
149 
150     /***************************************************************************
151 
152         Mapping from access time to the index of an item in the items array. The
153         map is implemented with an EBTree, so that it is sorted in order of
154         access times.
155 
156         The time-to-index mapping records are stored in time_to_index as
157         so-called EBTree "nodes" of type TimeToIndex.Node. Each node contains a
158         so-called "key" of type TimeToIndex.Key which consists of two uint
159         values, "lo" and "hi".
160         The sort order is ascending by "hi"; records with the same "hi" value
161         are sorted by "lo". Therefore, since the time-to-index mapping records
162         should be sorted by access time, time and cache index are stored as
163 
164             TimeToIndex.Key.hi = access time,
165             TimeToIndex.Key.lo = cache index.
166 
167     ***************************************************************************/
168 
169     private TimeToIndex time_to_index;
170 
171     /***************************************************************************
172 
173         Mapping from key to TimeToIndex.Mapping struct (which contains a mapping
174         from an access time to the index of an elements in this.items).
175 
176     ***************************************************************************/
177 
178     private KeyToNode key_to_node;
179 
180     /***************************************************************************
181 
182         Array of cached items.
183 
184     ***************************************************************************/
185 
186     private CacheItem[] items;
187 
188     /***************************************************************************
189 
190         Maximum number of items in the cache.
191 
192     ***************************************************************************/
193 
194     private size_t max_items;
195 
196     /***************************************************************************
197 
198         Counters for the cache lookups and misses.
199 
200     ***************************************************************************/
201 
202     protected uint n_lookups = 0,
203                    n_misses  = 0;
204 
205 
206     /***************************************************************************
207 
208         Constructor.
209 
210         Params:
211             max_items = maximum number of items in the cache, set once, cannot
212                 be changed
213 
214     ***************************************************************************/
215 
216     public this ( size_t max_items)
217     {
218         this.insert = 0;
219 
220         this.max_items     = max_items;
221         this.time_to_index = new TimeToIndex(max_items);
222         this.key_to_node   = new KeyToNode(max_items);
223         this.items = new CacheItem[max_items];
224     }
225 
226     /***************************************************************************
227 
228         Obtains the item that corresponds a key. Returns null if the key doesn't
229         exist.
230 
231         Params:
232             key = the item key
233             track_misses = flags whether not finding the item should count as
234                 a cache miss
235 
236         Returns:
237             the corresponding cache item or null if the key didn't exist.
238 
239     ***************************************************************************/
240 
241     public T* get(hash_t key, bool track_misses = true)
242     {
243         if (TimeToIndex.Node** node = this.getNode(key, track_misses))
244         {
245             return &this.items[this.getNodeIndex(**node)].value;
246         }
247         else
248             return null;
249     }
250 
251 
252     /***************************************************************************
253 
254         Get an item with a given key if it already existed or creates a new item
255         with the given priority if it didn't exist.
256 
257         Beware that in case an item didn't exist it is still possible that a new
258         item will NOT be created if whenNewAndLeastPriority() implementation
259         prefers the already existing item over the new one. The default
260         implementation of whenNewAndLeastPriority() always creates a new one.
261 
262         Params:
263             key = item's key
264             priority = the priority to update to assign to the new item if no
265                 item already exists
266             existed = will be assigned to true if the item already existed and
267                 wasn't created
268             tracK_get_miss = flags whether not finding the item should count as
269                 a cache miss
270 
271         Returns:
272             The existing or created item or null if no item was found or
273             created.
274 
275     ***************************************************************************/
276 
277     public T* getOrCreate (hash_t key, lazy ulong priority, out bool existed, bool tracK_get_miss = true)
278     {
279         T* item = this.get(key, tracK_get_miss);
280         existed = item !is null;
281         return item ? item : this.create(key, priority);
282     }
283 
284     /***************************************************************************
285 
286         Updates the priority of an item if it already existed or creates a new
287         item with the given priority if it didn't exist.
288 
289         Beware that in case an item didn't exist it is still possible that a new
290         item will NOT be created if whenNewAndLeastPriority() implementation
291         prefers the already existing item over the new one. The default
292         implementation of whenNewAndLeastPriority() always creates a new one.
293 
294         Params:
295             key = item's key
296             priority = the priority to update for the existing item or to assign
297                 to the new item
298             existed = will be assigned to true if the item already existed and
299                 wasn't created
300             tracK_get_miss = flags whether not finding the item should count as
301                 a cache miss
302 
303         Returns:
304             The existing or created item or null if no item was found or
305             created.
306 
307         Out:
308             if the item existed then the pointer is not null
309 
310     ***************************************************************************/
311 
312     public T* getUpdateOrCreate (hash_t key, ulong priority, out bool existed, bool tracK_get_miss = true)
313     out (val)
314     {
315         if (existed)
316         {
317             assert(val !is null, "Null return value although item exists");
318         }
319     }
320     do
321     {
322         T* item = this.updatePriority(key, priority, tracK_get_miss);
323         existed = item !is null;
324         return item ? item : this.create(key, priority);
325     }
326 
327     /***************************************************************************
328 
329         Updates an existing item's priority.
330 
331         Params:
332             key = item's key
333             new_priority = node's new priority
334             tracK_get_miss = flags whether not finding the item should count as
335                 a cache miss
336 
337         Returns:
338             A pointer to the item that was updated or null if the key didn't
339             exist.
340 
341     ***************************************************************************/
342 
343     public T* updatePriority(hash_t key, lazy ulong new_priority, bool tracK_get_miss = true)
344     {
345         if (TimeToIndex.Node** node = this.getNode(key, tracK_get_miss))
346         {
347             auto new_index = this.updatePriority(**node, new_priority);
348             return &this.items[new_index].value;
349         }
350         else
351             return null;
352     }
353 
354     /***************************************************************************
355 
356         Retrieves an item's priority if it exists.
357 
358         Params:
359             key = the key to look up
360             priority = the variable that will be assigned the item's priority,
361                 the variable contain unknown value if the item doesn't exist
362 
363         Returns:
364             Returns an pointer to the updated item or null if it didn't exist
365 
366     ***************************************************************************/
367 
368     public T* getPriority (hash_t key, out ulong priority)
369     {
370         if (TimeToIndex.Node** node = this.getNode(key))
371         {
372             priority = this.getNodePriority(**node);
373             return &this.items[this.getNodeIndex(**node)].value;
374         }
375         else
376             return null;
377     }
378 
379     /***************************************************************************
380 
381         Checks whether an item exists in the cache.
382 
383         Params:
384             key = key to lookup
385 
386         Returns:
387             true if item exists in cache
388 
389     ***************************************************************************/
390 
391     public bool exists ( hash_t key )
392     {
393         return this.getNode(key) !is null;
394     }
395 
396     /***************************************************************************
397 
398         Removes an item from the cache.
399 
400         Params:
401             key = key of item to remove
402 
403         Returns:
404             returns true if removed, false if not in cache
405 
406     ***************************************************************************/
407 
408     public bool remove ( hash_t key )
409     {
410         TimeToIndex.Node** node = this.getNode(key);
411         if (node)
412         {
413             this.remove_(key, **node);
414             return true;
415         }
416         else
417         {
418             return false;
419         }
420     }
421 
422     /***************************************************************************
423 
424         Returns the item with highest priority.
425 
426         Params:
427             key = set to the key of highest priority item
428             priority = set to the priority of the highest priority item
429 
430         Returns:
431             returns a pointer to the highest priority item or null if the cache
432             is empty
433 
434     ***************************************************************************/
435 
436     public T* getHighestPriorityItem ( out hash_t key, out ulong priority )
437     {
438         if ( !this.length )
439             return null;
440 
441         auto highest_node = this.time_to_index.last;
442         priority = this.getNodePriority(*highest_node);
443         auto item = &this.items[this.getNodeIndex(*highest_node)];
444         key = item.key;
445         return &item.value;
446     }
447 
448     /***************************************************************************
449 
450         Returns the item with lowest priority.
451 
452         Params:
453             key = set to the key of lowest priority item
454             priority = set to the priority of the lowest priority item
455 
456         Returns:
457             returns a pointer to the lowest priority item or null if the cache
458             is empty
459 
460     ***************************************************************************/
461 
462     public T* getLowestPriorityItem ( out hash_t key, out ulong priority )
463     {
464         if ( !this.length )
465             return null;
466 
467         auto lowest_node = this.time_to_index.first;
468         priority = this.getNodePriority(*lowest_node);
469         auto item = &this.items[this.getNodeIndex(*lowest_node)];
470         key = item.key;
471         return &item.value;
472     }
473 
474     /***************************************************************************
475 
476         The signature for the delegate to be used in a foreach loop:
477 
478             foreach(hash_t key, ref T item, ulong item_priority; cache)
479             {
480                 // You can change the value of item if it was ref
481                 item = new_value;
482             }
483 
484         Params:
485             key = the item key, cannot be changed (even if passed by ref)
486             item = the stored item, can be changed if passed by ref
487             priority = the item's priority, cannot be changed (even if
488                 passed by ref)
489 
490         Returns:
491             the return value of a foreach delegate
492 
493     ***************************************************************************/
494 
495     public alias int delegate (ref hash_t key, ref T item, ref ulong priority) ForeachDg;
496 
497     /***************************************************************************
498 
499         A foreach-iterator for iterating over the items in the tree.
500 
501         The items are passed in a descending order of priority (highest priority
502         first followed by lower priority).
503 
504         Parmas:
505             dg = the foreach delegate
506 
507         Returns:
508             If dg returns a nonzero value then the method return that value,
509             returns zero otherwise
510 
511     ***************************************************************************/
512 
513     public int opApply ( scope ForeachDg dg )
514     {
515         int ret = 0;
516 
517         scope iterator = this.time_to_index..new Iterator;
518 
519         foreach_reverse (ref node; iterator)
520         {
521             auto node_item_index = this.getNodeIndex(node);
522             CacheItem* cache_item =  &this.items[node_item_index];
523 
524             auto key = cache_item.key; // Copy it so it can't be changed by ref
525             auto priority = this.getNodePriority(node);
526             ret = dg(key, cache_item.value, priority);
527             if (ret)
528                 break;
529         }
530 
531         return ret;
532     }
533 
534     /***************************************************************************
535 
536         A foreach-iterator for iterating over the items in the tree.
537 
538         The items are passed in a ascending order of priority (lowest priority
539         first followed by higher priority).
540 
541         Parmas:
542             dg = the foreach delegate
543 
544         Returns:
545             If dg returns a nonzero value then the method return that value,
546             returns zero otherwise
547 
548     ***************************************************************************/
549 
550     public int opApplyReverse ( scope ForeachDg dg )
551     {
552         int ret = 0;
553 
554         scope iterator = this.time_to_index..new Iterator;
555 
556         foreach (ref node; iterator)
557         {
558             auto node_item_index = this.getNodeIndex(node);
559             CacheItem* cache_item =  &this.items[node_item_index];
560 
561             auto key = cache_item.key; // Copy it so it can't be changed by ref
562             auto priority = this.getNodePriority(node);
563             ret = dg(key, cache_item.value, priority);
564             if (ret)
565                 break;
566         }
567 
568         return ret;
569     }
570 
571 
572     /***************************************************************************
573 
574         Removes all items from the cache.
575 
576     ***************************************************************************/
577 
578     public void clear ( )
579     {
580         this.time_to_index.clear();
581         this.key_to_node.clearErase();
582         this.insert = 0;
583         this.items[] = this.items[0].init;
584     }
585 
586     /***************************************************************************
587 
588         Returns:
589             the number of items currently in the cache.
590 
591     ***************************************************************************/
592 
593     public size_t length ( )
594     {
595         return this.insert;
596     }
597 
598     /***************************************************************************
599 
600         Returns:
601             the maximum number of items the cache can have.
602 
603     ***************************************************************************/
604 
605     public size_t max_length ( )
606     {
607         return this.max_items;
608     }
609 
610     /***************************************************************************
611 
612         Returns:
613             the number of cache lookups since instantiation or the last call of
614             resetStats().
615 
616     ***************************************************************************/
617 
618     public uint num_lookups ( )
619     {
620         return this.n_lookups;
621     }
622 
623     /***************************************************************************
624 
625         Returns:
626             the number of cache lookups since instantiation or the last call of
627             resetStats().
628 
629     ***************************************************************************/
630 
631     public uint num_misses ( )
632     {
633         return this.n_misses;
634     }
635 
636     /***************************************************************************
637 
638         Resets the statistics counter values.
639 
640     ***************************************************************************/
641 
642     public void resetStats ( )
643     {
644         this.n_lookups = this.n_misses = 0;
645     }
646 
647     /***************************************************************************
648 
649         A notifier which is fired and an item is removed from the cache.
650 
651         The notifier is called after the item has already been removed.
652         The default implementation of the notifier inits the value of the item
653         to remove any references to it.
654 
655         When overriding this method, make sure this cache is not manipulated
656         while this method is executing (i.e. don't add or remove items) so make
657         sure that:
658         - neither the overriding method nor a callee manipulates this cache and
659         - if using fibers which can be suspended while this method is running,
660         that this cache cannot be manipulated by another fiber in this case.
661 
662         Params:
663             key = the key of the dropped item
664             value = the dropped item
665 
666     ***************************************************************************/
667 
668     protected void itemDropped (hash_t key, ref T value)
669     {
670         value = value.init;
671     }
672 
673     /***************************************************************************
674 
675         Called by attemptCreateNode() when the cache is full and the new item
676         to be added has a lower priority than the already existing lowest
677         priority item. The method decides which of the two items should be
678         stored.
679 
680         This implementation favors the new element over the existing element.
681         The user can override this method to implement a different behavior.
682 
683         Params:
684             new_item_lowest_priority = the priority of new item to be added
685             current_lowest_priority = the priority of the lowest existing
686                 item
687 
688         Returns:
689             true if the new item with lower priority should replace the current
690             existing lowest priority item, false otherwise.
691 
692     ***************************************************************************/
693 
694     protected bool whenNewAndLeastPriority ( ulong new_item_lowest_priority,
695                                              ulong current_lowest_priority )
696     {
697         return true;
698     }
699 
700     /***************************************************************************
701 
702         Creates a new item with the given priority.
703         The key must be not existing in the cache or else an unexpected behavior
704         can occur.
705 
706         Params:
707             key = the key to create
708             priority = the priority to assign to the key
709 
710         Returns:
711             The item that was created or null if the item wasn't added.
712 
713     ***************************************************************************/
714 
715     private T* create ( hash_t key, ulong priority )
716     do
717     {
718         bool item_added;
719         auto index = this.attemptCreateNode(key, priority, item_added);
720 
721         if (item_added)
722         {
723             return &this.items[index].value;
724         }
725         else
726             return null;
727     }
728 
729     /***************************************************************************
730 
731         Return the priority of an item.
732 
733         Params:
734             node = node to lookup
735 
736         Returns:
737             item's priority
738 
739     ***************************************************************************/
740 
741     private ulong getNodePriority ( ref TimeToIndex.Node node )
742     {
743         return node.key.hi;
744     }
745 
746     /***************************************************************************
747 
748         Return the index of an item.
749 
750         Params:
751             node = node to lookup
752 
753         Returns:
754             item's priority
755 
756     ***************************************************************************/
757 
758     private size_t getNodeIndex ( TimeToIndex.Node node )
759     {
760         return node.key.lo;
761     }
762 
763     /***************************************************************************
764 
765         Updates an item's priority and returns the new index of the in the tree.
766 
767         Params:
768             node = time-to-index tree node
769             new_priority = node's new priority
770 
771         Returns:
772             the new index of the corresponding cache item.
773 
774         Out:
775             the returned index is less than length.
776 
777     ***************************************************************************/
778 
779     private size_t updatePriority(ref TimeToIndex.Node node, ulong new_priority)
780     out (index)
781     {
782         assert(&node, "ref argument is a dereferenced null pointer");
783         assert (index < this.insert, "cache index out of bounds");
784     }
785     do
786     {
787         TimeToIndex.Key node_key = node.key;
788 
789         // A call to update() cause a remove() then an add(), so skip it if no
790         // change in priority
791         if (node_key.hi != new_priority)
792         {
793             node_key.hi = new_priority;
794             this.time_to_index.update(node, node_key);
795         }
796 
797         return node_key.lo;
798     }
799 
800     /***************************************************************************
801 
802         Obtains the time-to-index node for key.
803 
804         Params:
805             key = key to lookup
806 
807         Returns:
808             pointer to the time-to-index node for key or null if not found.
809             track_misses = flags whether not finding the item should count as
810                 a cache miss
811 
812         Out:
813             If found, it is safe to dereference the pointer to which the
814             returned pointer points (*node is not null).
815 
816     ***************************************************************************/
817 
818     private TimeToIndex.Node** getNode (hash_t key, bool track_misses = true)
819     out (node)
820     {
821         if (node) assert (*node !is null, "null pointer value was stored in key_to_node");
822     }
823     do
824     {
825         TimeToIndex.Node** node = key in this.key_to_node;
826         if (track_misses)
827         {
828             this.n_lookups++;
829             this.n_misses += (node is null);
830         }
831         return node;
832     }
833 
834     /***************************************************************************
835 
836         Registers a new cache item and obtains the item's index in this.items
837         for it.
838 
839         If the cache is full and whenEarlierThanOldestItem() returns true, the
840         oldest cache element is replaced.
841 
842         Params:
843             key = item's key
844             priority = item's priority
845             item_added = set to true if the item was added, false otherwise
846 
847         Returns:
848             the index that should be used in this.items which corresponds to the
849             newly registered item.
850 
851         Out:
852             the returned index is below length.
853 
854     ***************************************************************************/
855 
856     private size_t attemptCreateNode (hash_t key, ulong priority, out bool item_added)
857     out (index)
858     {
859         assert (index < this.max_length);
860 
861         if (item_added)
862         {
863             assert(this.items[index].key == key, "keys mismatch");
864         }
865     }
866     do
867     {
868         size_t index;
869 
870         auto is_key_removed = false;
871         hash_t removed_key;
872 
873         if ( this.insert < this.max_length )
874         {
875             index = this.insert++;
876         }
877         else
878         {
879             // Find the item with lowest (ie oldest) update time.
880             TimeToIndex.Node* oldest_time_node = this.time_to_index.first;
881 
882             verify (oldest_time_node !is null);
883 
884             // Get the item index and check if the time of the last access is
885             // less than the current time. If not, notify the subclass because
886             // we are about to replace the oldest record with an even older one.
887 
888             with (oldest_time_node.key)
889             {
890                 index = lo;
891 
892                 if (priority < hi)
893                 {
894                     if ( !this.whenNewAndLeastPriority(priority, hi) )
895                     {
896                         item_added = false;
897                         return index;
898                     }
899                 }
900             }
901 
902             // Call the notifier at the end of the method so that the old key is
903             // already removed and the new key is added
904             is_key_removed = true;
905             removed_key = this.items[index].key;
906 
907             // Remove old item in tree map
908             this.time_to_index.remove(*oldest_time_node);
909             this.key_to_node.remove(removed_key);
910         }
911 
912         auto node_key = TimeToIndex.Key(index, priority);
913         *this.key_to_node.put(key) = this.time_to_index.add(node_key);
914         this.items[index].key = key;
915         item_added = true;
916 
917         if (is_key_removed)
918         {
919             this.itemDropped(removed_key, this.items[index].value);
920         }
921 
922         return index;
923     }
924 
925     /***************************************************************************
926 
927         Removes the cache item that corresponds to dst_key and dst_node.
928 
929         Params:
930             dst_key  = key of item to remove
931             dst_node = time-to-index tree node to remove
932 
933     ***************************************************************************/
934 
935     private void remove_ ( hash_t dst_key, ref TimeToIndex.Node dst_node )
936     {
937         /*
938          * If the caller passes a dereferenced pointer as dst_node the
939          * implementation of `ref` function arguments postpones dereferencing
940          * this pointer to the places where dst_node is used in this function:
941          * ---
942          *   hash_t dst_key;
943          *   TimeToIndex.Node* dst_node = null;
944          *   remove(dst_key, *dst_node); // null isn't deferenced here but when
945          *                               // actually used inside remove().
946          * ---
947          * If that happens &dst_node is `null` in this function.
948          */
949 
950         verify(&dst_node !is null, "ref argument is a dereferenced null pointer");
951 
952         /*
953          * Remove item in items list by copying the last item to the item to
954          * remove and decrementing the insert index which reflects the
955          * actual number of items.
956          */
957 
958         this.insert--;
959 
960         size_t index = this.getNodeIndex(dst_node);
961 
962         // Remove the tree map entry of the removed cache item.
963         this.time_to_index.remove(dst_node);
964 
965         // Remove key -> item mapping
966         this.key_to_node.remove(dst_key);
967 
968         if ( index != this.insert )
969         {
970             // Swap the content of the two array items
971             CacheItem tmp = this.items[this.insert];
972             this.items[this.insert] = this.items[index];
973             this.items[index] = tmp;
974 
975             hash_t src_key = tmp.key;
976 
977             /*
978              * Obtain the time-to-mapping entry for the copied cache item.
979              * Update it to the new index and update the key-to-mapping
980              * entry to the updated time-to-mapping entry.
981              */
982 
983             TimeToIndex.Node** src_node_in_map = src_key in this.key_to_node;
984 
985             verify (src_node_in_map !is null, "Null src_node_in_map found");
986 
987             TimeToIndex.Node* src_node = *src_node_in_map;
988 
989             verify (src_node !is null, "Null src_node found");
990 
991             TimeToIndex.Key src_node_key = src_node.key;
992 
993             src_node_key.lo = index;
994 
995             *src_node_in_map = this.time_to_index.update(*src_node, src_node_key);
996         }
997 
998         this.itemDropped(dst_key, this.items[this.insert].value);
999     }
1000 }
1001 
1002 version (unittest) import ocean.core.Test;
1003 
1004 // Test documentation example
1005 unittest
1006 {
1007     auto t = new NamedTest("Documentation example");
1008 
1009     static immutable NUM_ITEM = 10;
1010     auto cache = new PriorityCache!(char[])(NUM_ITEM);
1011 
1012     auto key = 1;
1013     ulong priority = 20;
1014     bool item_existed_before;
1015     char[]* item = cache.getOrCreate(key, priority, item_existed_before);
1016 
1017     if (item)
1018     {
1019         *item = "ABC".dup;
1020     }
1021     t.test!("==")(item_existed_before, false);
1022 
1023     ulong no_effect_priority = 70;
1024     item = cache.getOrCreate(key, no_effect_priority, item_existed_before);
1025 
1026     if (item)
1027     {
1028         *item = "DEF".dup;
1029     }
1030     t.test!("==")(item_existed_before, true);
1031 
1032     ulong retrieved_priority;
1033     item = cache.getPriority(key, retrieved_priority);
1034     t.test!("!is")(item, null);
1035     t.test!("==")(*item, "DEF");
1036     t.test!("==")(retrieved_priority, priority); // Not no_effect_priority
1037 
1038 
1039     auto new_priority = 10;
1040     item = cache.getUpdateOrCreate(key, new_priority, item_existed_before);
1041 
1042     cache.getPriority(key, retrieved_priority);
1043     t.test!("==")(item_existed_before, true);
1044     t.test!("!is")(item, null);
1045     t.test!("==")(retrieved_priority, new_priority);
1046 }
1047 
1048 // Test adding and removing
1049 unittest
1050 {
1051     auto t = new NamedTest("Adding and removing items to the cache");
1052 
1053     static immutable NUM_OF_ITEMS = 150;
1054 
1055     auto test_cache = new PriorityCache!(int)(NUM_OF_ITEMS);
1056 
1057     static immutable PRIORITY = 10;
1058     static immutable VALUE = 50;
1059 
1060     for (int i = 0; i < NUM_OF_ITEMS; i++)
1061     {
1062         bool existed;
1063         auto int_ptr = test_cache.getOrCreate(i, i + PRIORITY, existed);
1064         t.test!("!is")(int_ptr, null, "unexpectedly item was not created");
1065         t.test!("==")(existed, false, "item previously existed");
1066         *int_ptr = i + VALUE;
1067     }
1068 
1069 
1070     foreach(j, value; test_cache.items)
1071     {
1072         t.test(test_cache.remove(j), "Removing non-existing item");
1073     }
1074 }
1075 
1076 // Test getting highest and lowest items
1077 unittest
1078 {
1079     auto t = new NamedTest("Retrieving highest and lowest priority items");
1080 
1081     static immutable NUM_OF_ITEMS = 150;
1082 
1083     auto test_cache = new PriorityCache!(int)(NUM_OF_ITEMS);
1084 
1085     static immutable PRIORITY = 10;
1086     static immutable VALUE = 50;
1087 
1088     bool existed;
1089     hash_t key;
1090     ulong priority;
1091 
1092     // Test that nothing is returned when cache is empty
1093     t.test!("==")(test_cache.getLowestPriorityItem(key, priority), null);
1094     t.test!("==")(test_cache.getHighestPriorityItem(key, priority), null);
1095 
1096     // Populate the cache with some items
1097     for (int i = 0; i < NUM_OF_ITEMS; i++)
1098     {
1099         auto int_ptr = test_cache.getOrCreate(i, i + PRIORITY, existed);
1100         *int_ptr = i + VALUE;
1101     }
1102 
1103     // Test the cache after items has been added to it
1104     t.test!("==")(*test_cache.getLowestPriorityItem(key, priority), VALUE);
1105     t.test!("==")(key, 0);
1106     t.test!("==")(priority, PRIORITY);
1107 
1108     t.test!("==")(*test_cache.getHighestPriorityItem(key, priority),
1109                   NUM_OF_ITEMS - 1 + VALUE);
1110     t.test!("==")(key, NUM_OF_ITEMS - 1);
1111     t.test!("==")(priority, NUM_OF_ITEMS - 1 + PRIORITY);
1112 }
1113 
1114 // Test clearing
1115 unittest
1116 {
1117     auto t = new NamedTest("Clearing the cache");
1118 
1119     static immutable NUM_OF_ITEMS = 150;
1120 
1121     auto test_cache = new PriorityCache!(int)(NUM_OF_ITEMS);
1122 
1123     static immutable VALUE = 50;
1124     static immutable PRIORITY = 8;
1125     static immutable INDEX = 20;
1126 
1127     // Create some items
1128     for (int i = 0; i < NUM_OF_ITEMS; i++)
1129     {
1130         bool existed;
1131         auto int_ptr = test_cache.getOrCreate(i + INDEX, i + PRIORITY, existed);
1132         *int_ptr = i + VALUE;
1133     }
1134 
1135     // After clearing we shouldn't find anything
1136     test_cache.clear();
1137 
1138     for (int i = 0; i < NUM_OF_ITEMS; i++)
1139     {
1140         auto is_removed = test_cache.remove(i + INDEX);
1141         t.test(!is_removed, "Should fail removing non-existing item");
1142     }
1143 }
1144 
1145 // Test opApply
1146 unittest
1147 {
1148     auto t = new NamedTest("opApply foreach loops");
1149 
1150     static immutable NUM_OF_ITEMS = 150;
1151 
1152     auto test_cache = new PriorityCache!(int)(NUM_OF_ITEMS);
1153 
1154     static immutable PRIORITY = 10;
1155     static immutable ORIGINAL_VALUE = 50;
1156     static immutable NEW_VALUE = 80;
1157 
1158     for (int i = 0; i < NUM_OF_ITEMS; i++)
1159     {
1160         bool existed;
1161         auto int_ptr = test_cache.getOrCreate(i, i + PRIORITY, existed);
1162         *int_ptr = i + ORIGINAL_VALUE;
1163     }
1164 
1165     int counter = NUM_OF_ITEMS;
1166     foreach (key, ref item, ulong priority; test_cache)
1167     {
1168         counter--;
1169         t.test!("==")(key, counter, "Unexpected key");
1170         t.test!("==")(priority, counter + PRIORITY, "Unexpected item priority");
1171         item = counter + NEW_VALUE;
1172     }
1173 
1174     // Confirm that the new assigned values weren't lost
1175     for (int i = 0; i < NUM_OF_ITEMS; i++)
1176     {
1177         auto int_ptr = test_cache.get(i);
1178         t.test(int_ptr, "item unexpectedly null");
1179         t.test!("==")(*int_ptr, i + NEW_VALUE, "Unexpected item value");
1180     }
1181 
1182     foreach_reverse (key, ref item, ulong priority; test_cache)
1183     {
1184         t.test!("==")(key, counter, "Unexpected key");
1185         t.test!("==")(priority, counter + PRIORITY, "Unexpected item priority");
1186         item = counter - NEW_VALUE;
1187         counter++;
1188     }
1189 
1190     // Confirm that the new assigned values weren't lost
1191     for (int i = 0; i < NUM_OF_ITEMS; i++)
1192     {
1193         auto int_ptr = test_cache.get(i);
1194         t.test(int_ptr, "item unexpectedly null");
1195         t.test!("==")(*int_ptr, i - NEW_VALUE, "Unexpected item value");
1196     }
1197 }
1198 
1199 
1200 // Test dropped items are correctly reported
1201 unittest
1202 {
1203     auto t = new NamedTest("Dropped items are correctly reported");
1204 
1205     static immutable CACHE_SIZE = 10;
1206     static immutable ITEMS_INSERTED = 150;
1207 
1208     uint items_removed_count;
1209 
1210     class PriorityNotify : PriorityCache!(uint)
1211     {
1212         public this (size_t max_items)
1213         {
1214             super(max_items);
1215         }
1216 
1217         protected override void itemDropped (hash_t key, ref uint value)
1218         {
1219             t.test!("==")(key, value, "Wrong key/value are reported");
1220             items_removed_count++;
1221         }
1222     }
1223 
1224     auto test_cache = new PriorityNotify(CACHE_SIZE);
1225     for (uint i = 0; i < ITEMS_INSERTED; i++)
1226     {
1227         bool existed;
1228         auto int_ptr = test_cache.getOrCreate(i, i, existed);
1229         *int_ptr = i;
1230     }
1231 
1232     t.test!("==")(items_removed_count, ITEMS_INSERTED - CACHE_SIZE,
1233                   "Not all dropped items were reported");
1234 }
1235 
1236 
1237 // Test dropped items are passed by ref
1238 unittest
1239 {
1240     auto t = new NamedTest("Dropped items are passed by ref to notifier");
1241 
1242     static immutable CACHE_SIZE = 10;
1243     bool item_dropped = false;
1244 
1245     class PriorityNotify2 : PriorityCache!(uint)
1246     {
1247         public this (size_t max_items)
1248         {
1249             super(max_items);
1250         }
1251 
1252         protected override void itemDropped (hash_t key, ref uint value)
1253         {
1254             item_dropped = true;
1255             value = 10;
1256         }
1257     }
1258 
1259     auto test_cache = new PriorityNotify2(CACHE_SIZE);
1260     bool existed;
1261     auto new_value = test_cache.getOrCreate(20, 20, existed);
1262     *new_value = 50;
1263     test_cache.remove(20);
1264 
1265     t.test(item_dropped, "Item was not dropped");
1266     t.test!("==")(*new_value, 10, "Item was not dropped by ref");
1267 }