1 /*******************************************************************************
2 3 Cache base class, implements the cache logic that is not related to values.
4 5 Copyright:
6 Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7 All rights reserved.
8 9 License:
10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11 Alternatively, this file may be distributed under the terms of the Tango
12 3-Clause BSD License (see LICENSE_BSD.txt for details).
13 14 *******************************************************************************/15 16 moduleocean.util.container.cache.model.ICache;
17 18 19 importocean.meta.types.Qualifiers;
20 21 importocean.util.container.cache.model.ICacheInfo;
22 23 importocean.util.container.cache.model.containers.TimeToIndex;
24 importocean.util.container.cache.model.containers.KeyToNode;
25 26 importcore.stdc.time: time_t, time;
27 28 /******************************************************************************/29 30 abstractclassICache : ICacheInfo31 {
32 importocean.core.Verify;
33 34 /***************************************************************************
35 36 Alias required by the subclasses.
37 38 ***************************************************************************/39 40 protectedalias .TimeToIndexTimeToIndex;
41 42 /***************************************************************************
43 44 Insert position into array of items.
45 46 ***************************************************************************/47 48 privatesize_tinsert;
49 50 51 /***************************************************************************
52 53 Mapping from access time to the index of an item in the items array. The
54 map is implemented with an EBTree, so that it is sorted in order of
55 access times.
56 57 The time-to-index mapping records are stored in time_to_index as
58 so-called EBTree "nodes" of type TimeToIndex.Node. Each node contains a
59 so-called "key" of type TimeToIndex.Key which consists of two uint
60 values, "lo" and "hi".
61 The sort order is ascending by "hi"; records with the same "hi" value
62 are sorted by "lo". Therefore, since the time-to-index mapping records
63 should be sorted by access time, time and cache index are stored as
64 65 TimeToIndex.Key.hi = access time,
66 TimeToIndex.Key.lo = cache index.
67 68 ***************************************************************************/69 70 protectedTimeToIndextime_to_index;
71 72 73 /***************************************************************************
74 75 Mapping from key to TimeToIndex.Mapping struct (which contains a mapping
76 from an access time to the index of an elements in this.items).
77 78 ***************************************************************************/79 80 protectedKeyToNodekey_to_node;
81 82 83 /***************************************************************************
84 85 Maximum number of items in the cache.
86 87 ***************************************************************************/88 89 privatesize_tmax_items;
90 91 /***************************************************************************
92 93 Counters for the cache lookups and misses.
94 95 ***************************************************************************/96 97 protecteduintn_lookups = 0,
98 n_misses = 0;
99 100 /***************************************************************************
101 102 Constructor.
103 104 Params:
105 max_items = maximum number of items in the cache, set once, cannot
106 be changed
107 108 ***************************************************************************/109 110 protectedthis ( size_tmax_items )
111 {
112 this.insert = 0;
113 114 this.max_items = max_items;
115 this.time_to_index = newTimeToIndex(max_items);
116 this.key_to_node = newKeyToNode(max_items);
117 }
118 119 /***************************************************************************
120 121 Removes all items from the cache.
122 123 ***************************************************************************/124 125 publicvoidclear ( )
126 {
127 this.time_to_index.clear();
128 this.key_to_node.clear();
129 this.insert = 0;
130 }
131 132 /***************************************************************************
133 134 Returns:
135 the number of items currently in the cache.
136 137 ***************************************************************************/138 139 publicsize_tlength ( )
140 {
141 returnthis.insert;
142 }
143 144 /***************************************************************************
145 146 Returns:
147 the maximum number of items the cache can have.
148 149 ***************************************************************************/150 151 publicsize_tmax_length ( )
152 {
153 returnthis.max_items;
154 }
155 156 /***************************************************************************
157 158 Returns:
159 the number of cache lookups since instantiation or the last call of
160 resetStats().
161 162 ***************************************************************************/163 164 publicuintnum_lookups ( )
165 {
166 returnthis.n_lookups;
167 }
168 169 /***************************************************************************
170 171 Returns:
172 the number of cache lookups since instantiation or the last call of
173 resetStats().
174 175 ***************************************************************************/176 177 publicuintnum_misses ( )
178 {
179 returnthis.n_misses;
180 }
181 182 /***************************************************************************
183 184 Resets the statistics counter values.
185 186 ***************************************************************************/187 188 publicvoidresetStats ( )
189 {
190 this.n_lookups = this.n_misses = 0;
191 }
192 193 /***************************************************************************
194 195 Checks whether an item exists in the cache.
196 197 Params:
198 key = key to lookup
199 200 Returns:
201 true if item exists in cache
202 203 ***************************************************************************/204 205 publicboolexists ( hash_tkey )
206 {
207 return (keyinthis) !isnull;
208 }
209 210 /***************************************************************************
211 212 Removes an item from the cache.
213 214 Params:
215 key = key of item to remove
216 217 Returns:
218 returns true if removed, false if not in cache
219 220 ***************************************************************************/221 222 publicboolremove ( hash_tkey )
223 {
224 TimeToIndex.Node** node = keyinthis;
225 226 if (node)
227 {
228 this.remove_(key, **node);
229 returntrue;
230 }
231 else232 {
233 returnfalse;
234 }
235 }
236 237 /***************************************************************************
238 239 Checks whether an item exists in the cache and returns the last time it
240 was accessed.
241 242 Params:
243 key = key to lookup
244 245 Returns:
246 item's last access time, or 0 if the item doesn't exist
247 248 ***************************************************************************/249 250 publictime_taccessTime ( hash_tkey )
251 {
252 TimeToIndex.Node** node = keyinthis;
253 254 returnnode? (*node).key.hi : 0;
255 }
256 257 /***************************************************************************
258 259 Obtains the index of the cache item that corresponds to node and updates
260 the access time.
261 If realtime is enabled, access_time is expected to be equal to or
262 greater than the time stored in node. If disabled and the access time is
263 less, the node will not be updated and a value of at least length
264 returned.
265 266 267 Params:
268 node = time-to-index tree node
269 access_time = access time
270 271 Returns:
272 the index of the corresponding cache item or a value of at least
273 length if realtime is disabled and the access time is less than the
274 access time in the node.
275 276 Out:
277 If realtime is enabled, the returned index is less than length.
278 279 ***************************************************************************/280 281 protectedsize_taccessIndex ( refTimeToIndex.Nodenode, outtime_taccess_time )
282 out (index)
283 {
284 assert (index < this.insert, "cache index out of bounds");
285 }
286 do287 {
288 TimeToIndex.Keykey = node.key;
289 290 access_time = key.hi = this.now;
291 292 this.time_to_index.update(node, key);
293 294 returnkey.lo;
295 }
296 297 /***************************************************************************
298 299 Obtains the current time. By default this is the wall clock time in
300 seconds.
301 This time value is used to find the least recently updated cache item
302 and stored as create time. A subclass may override this method to use a
303 different time unit or clock.
304 305 306 Returns:
307 the current time in seconds.
308 309 ***************************************************************************/310 311 protectedtime_tnow ( )
312 {
313 return .time(null);
314 }
315 316 /***************************************************************************
317 318 Obtains the index of the cache item that corresponds to key and updates
319 the access time.
320 If realtime is enabled and key could be found, access_time is expected
321 to be at least the time value stored in node. If disabled and
322 access_time is less, the result is the same as if key could not be
323 found.
324 325 326 Params:
327 key = time-to-index tree key
328 access_time = access time
329 330 Returns:
331 the index of the corresponding cache item or a value of at least
332 this.length if key could not be found.
333 334 ***************************************************************************/335 336 protectedsize_tget_ ( hash_tkey, outtime_taccess_time )
337 {
338 TimeToIndex.Node** node = keyinthis;
339 340 returnnode? this.accessIndex(**node, access_time) : size_t.max;
341 }
342 343 /***************************************************************************
344 345 Obtains the time-to-index node for key.
346 347 Params:
348 key = key to lookup
349 350 Returns:
351 pointer to the time-to-index node for key or null if not found.
352 353 Out:
354 If found, it is safe to dereference the pointer to which the
355 returned pointer points (*node is not null).
356 357 ***************************************************************************/358 359 protectedTimeToIndex.Node** opIn_r ( hash_tkey )
360 out (node)
361 {
362 if (node) assert (*node !isnull, "null pointer value was stored in key_to_node");
363 }
364 do365 {
366 TimeToIndex.Node** node = keyinthis.key_to_node;
367 368 this.n_lookups++;
369 this.n_misses += (nodeisnull);
370 371 returnnode;
372 }
373 374 375 /***************************************************************************
376 377 Support for the 'in' operator
378 379 Aliased to opIn_r, for backwards compatibility
380 381 ***************************************************************************/382 383 protectedaliasopBinaryRight ( istringop : "in" ) = opIn_r;
384 385 386 /***************************************************************************
387 388 Registers a new cache element and obtains the cache item index for it.
389 If the cache is full, the oldest cache element is replaced.
390 If realtime is enabled, time is expected to be at least the time value
391 of the most recent cache element.
392 If realtime is disabled and time is less than the time value of the most
393 recent cache element, nothing is done and a value of at least length is
394 returned.
395 396 Params:
397 key = cache element key
398 access_time = cache element creation time
399 400 Returns:
401 the index of the cache item that corresponds to the newly registered
402 cache element or a value of at least length if realtime is disabled
403 and time is less than the time value of the most recent cache
404 element.
405 406 In:
407 If realtime is enabled, time must bebe at least the time value of
408 the most recent cache element.
409 410 Out:
411 If realtime is enabled, the returned index is below length.
412 413 ***************************************************************************/414 415 protectedsize_tregister ( hash_tkey, outtime_taccess_time )
416 out (index)
417 {
418 assert (index < this.max_length);
419 }
420 do421 {
422 size_tindex;
423 424 access_time = this.now;
425 426 if ( this.insert < this.max_length )
427 {
428 index = this.insert++;
429 }
430 else431 {
432 // Find the item with lowest (ie oldest) update time.433 TimeToIndex.Node* oldest_time_node = this.time_to_index.first;
434 435 verify (oldest_time_node !isnull);
436 437 // Get the item index and check if the time of the last access is438 // less than the current time. If not, notify the subclass because439 // we are about to replace the oldest record with an even older one.440 441 with (oldest_time_node.key)
442 {
443 index = lo;
444 445 if (access_time < hi)
446 {
447 this.whenEarlierThanOldestItem(access_time, hi);
448 }
449 }
450 451 // Call the notifier method before actual removal452 this.whenCacheItemDropped(index);
453 454 // Remove old item in tree map455 this.time_to_index.remove(*oldest_time_node);
456 457 this.key_to_node.remove(this.keyByIndex(index));
458 }
459 460 461 *this.key_to_node.put(key) = this.time_to_index.add(TimeToIndex.Key(index, access_time));
462 463 464 returnindex;
465 }
466 467 /***************************************************************************
468 469 Called when the time value returned by now() is less than the time of
470 last access of the oldest record in the cache; may be overridden by a
471 subclass to be notified if this happens.
472 With the system time as external data source this can theoretically
473 happen and is at least not a program bug so in this class assert() would
474 be inappropriate.
475 476 Params:
477 now = time value reported by now()
478 oldest = last access time of the oldest record in the cache
479 480 ***************************************************************************/481 482 protectedvoidwhenEarlierThanOldestItem ( time_tnow, time_toldest ) { }
483 484 /***************************************************************************
485 486 Called when the oldest item is replaced in cache with a new one
487 because cache is full.
488 When the cache gets full, the oldest item will be replaced with the
489 new value. Before that happens, this method will be called, having
490 the item index passed as a argument.
491 492 Params:
493 index = index of the cache item that will be dropped.
494 495 ***************************************************************************/496 497 protectedvoidwhenCacheItemDropped ( size_tindex ) { }
498 499 /***************************************************************************
500 501 Obtains the key of the cache item corresponding to index.
502 503 Params:
504 index = cache item index, guaranteed to be below length
505 506 Returns:
507 cache item key
508 509 ***************************************************************************/510 511 abstractprotectedhash_tkeyByIndex ( size_tindex );
512 513 /***************************************************************************
514 515 Removes the cache item that corresponds to dst_key and dst_node.
516 517 Params:
518 dst_key = key of item to remove
519 dst_node = time-to-index tree node to remove
520 521 ***************************************************************************/522 523 protectedvoidremove_ ( hash_tdst_key, refTimeToIndex.Nodedst_node )
524 {
525 /*
526 * Remove item in items list by copying the last item to the item to
527 * remove and decrementing the insert index which reflects the
528 * actual number of items.
529 */530 531 size_tindex = dst_node.key.lo;
532 533 // Remove the tree map entry of the removed cache item.534 this.time_to_index.remove(dst_node);
535 536 // Remove key -> item mapping537 this.key_to_node.remove(dst_key);
538 539 if ( index != this.insert - 1)
540 {
541 hash_tsrc_key = this.replaceRemovedItem(index, this.insert - 1);
542 543 /*
544 * Obtain the time-to-mapping entry for the copied cache item.
545 * Update it to the new index and update the key-to-mapping
546 * entry to the updated time-to-mapping entry.
547 */548 549 TimeToIndex.Node** src_node_in_map = src_keyinthis.key_to_node;
550 551 verify (src_node_in_map !isnull, "Null src_node_in_map found");
552 553 TimeToIndex.Node* src_node = *src_node_in_map;
554 555 verify (src_node !isnull, "Null src_node found");
556 557 TimeToIndex.Keysrc_node_key = src_node.key;
558 559 src_node_key.lo = index;
560 561 *src_node_in_map = this.time_to_index.update(*src_node, src_node_key);
562 }
563 564 this.insert--;
565 }
566 567 /***************************************************************************
568 569 Called when a cache element is removed, replaces the cache items at
570 index "replaced"" with the one at index "replace".
571 572 The "replace" and "replaced" indices are guaranteed to be different and
573 valid cache item indices, i.e. less than this.length.
574 575 When this method has returned, the cache item at index "replace" won't
576 be used until a new cache element is added; a subclass is free to do
577 with it as it pleases but should be aware that it will be reused later
578 on.
579 580 Params:
581 replaced = index of the cache item that is to be replaced
582 replace = index of the cache item that will replace the replaced
583 item
584 585 Returns:
586 the key of the cache item that was at index "replace"" before and is
587 at index "replaced" now.
588 589 ***************************************************************************/590 591 protectedhash_treplaceRemovedItem ( size_treplaced, size_treplace );
592 }