aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/CnmMemoryCache.cs
diff options
context:
space:
mode:
authorDahlia Trimble2009-06-03 08:41:08 +0000
committerDahlia Trimble2009-06-03 08:41:08 +0000
commit9e3b592fa3636cf3d50460e303173de55bcb3920 (patch)
treeb2c39f6540fd52cc6382958f5604af1305210902 /OpenSim/Framework/CnmMemoryCache.cs
parentThanks aduffy70 for Mantis #3762: A patch to fix llGround, llGroundNormal, an... (diff)
downloadopensim-SC_OLD-9e3b592fa3636cf3d50460e303173de55bcb3920.zip
opensim-SC_OLD-9e3b592fa3636cf3d50460e303173de55bcb3920.tar.gz
opensim-SC_OLD-9e3b592fa3636cf3d50460e303173de55bcb3920.tar.bz2
opensim-SC_OLD-9e3b592fa3636cf3d50460e303173de55bcb3920.tar.xz
Thank you Imaze Rhiano for a patch that implements Cenome Memory Asset Cache (Mantis #3759)
See the files: bin/config-include/GridCommon.ini.example and bin/config-include/StandaloneCommon.ini.example to configure and enable this caching method.
Diffstat (limited to 'OpenSim/Framework/CnmMemoryCache.cs')
-rw-r--r--OpenSim/Framework/CnmMemoryCache.cs1852
1 files changed, 1852 insertions, 0 deletions
diff --git a/OpenSim/Framework/CnmMemoryCache.cs b/OpenSim/Framework/CnmMemoryCache.cs
new file mode 100644
index 0000000..6ca71ec
--- /dev/null
+++ b/OpenSim/Framework/CnmMemoryCache.cs
@@ -0,0 +1,1852 @@
1// --------------------------------------------------------------------------------------------------------------------
2// <copyright company="" file="CnmMemoryCache.cs">
3//
4// </copyright>
5// <summary>
6//
7// </summary>
8// --------------------------------------------------------------------------------------------------------------------
9
10using System;
11using System.Collections;
12using System.Collections.Generic;
13using System.Diagnostics;
14
15namespace OpenSim.Framework
16{
17 /// <summary>
18 /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size.
19 /// </summary>
20 /// <typeparam name="TKey">
21 /// The type of keys in the cache.
22 /// </typeparam>
23 /// <typeparam name="TValue">
24 /// The type of values in the dictionary.
25 /// </typeparam>
26 /// <remarks>
27 /// <para>
28 /// Cenome memory cache stores elements to hash table generations. When new element is being added to cache, and new size would exceed
29 /// maximal allowed size or maximal amount of allowed element count, then elements in oldest generation are deleted. Last access time
30 /// is also tracked in generation level - thus it is possible that some elements are staying in cache far beyond their expiration time.
31 /// If elements in older generations are accessed through <see cref="TryGetValue"/> method, they are moved to newest generation.
32 /// </para>
33 /// </remarks>
34 public class CnmMemoryCache<TKey, TValue> : ICnmCache<TKey, TValue>
35 {
36 /// <summary>
37 /// Default maximal count.
38 /// </summary>
39 /// <seealso cref="MaxCount"/>
40 public const int DefaultMaxCount = 4096;
41
42 /// <summary>
43 /// Default maximal size.
44 /// </summary>
45 /// <remarks>
46 /// <para>
47 /// 128MB = 128 * 1024^2 = 134 217 728 bytes.
48 /// </para>
49 /// </remarks>
50 /// <seealso cref="MaxSize"/>
51 public const long DefaultMaxSize = 134217728;
52
53 /// <summary>
54 /// How many operations between time checks.
55 /// </summary>
56 private const int DefaultOperationsBetweenTimeChecks = 40;
57
58 /// <summary>
59 /// Default expiration time.
60 /// </summary>
61 /// <remarks>
62 /// <para>
63 /// 30 minutes.
64 /// </para>
65 /// </remarks>
66 public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 );
67
68 /// <summary>
69 /// Minimal allowed expiration time.
70 /// </summary>
71 /// <remarks>
72 /// <para>
73 /// 5 minutes.
74 /// </para>
75 /// </remarks>
76 public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds( 10.0 );
77
78 /// <summary>
79 /// Comparer used to compare element keys.
80 /// </summary>
81 /// <remarks>
82 /// Comparer is initialized by constructor.
83 /// </remarks>
84 /// <seealso cref="CnmMemoryCache{TKey,TValue}"/>
85 public readonly IEqualityComparer<TKey> Comparer;
86
87 /// <summary>
88 /// Expiration time.
89 /// </summary>
90 private TimeSpan m_expirationTime = DefaultExpirationTime;
91
92 /// <summary>
93 /// Generation bucket count.
94 /// </summary>
95 private int m_generationBucketCount;
96
97 /// <summary>
98 /// Generation entry count.
99 /// </summary>
100 private int m_generationElementCount;
101
102 /// <summary>
103 /// Generation max size.
104 /// </summary>
105 private long m_generationMaxSize;
106
107 /// <summary>
108 /// Maximal allowed count of elements.
109 /// </summary>
110 private int m_maxCount;
111
112 /// <summary>
113 /// Maximal allowed total size of elements.
114 /// </summary>
115 private long m_maxElementSize;
116
117 /// <summary>
118 /// Maximal size.
119 /// </summary>
120 private long m_maxSize;
121
122 /// <summary>
123 /// New generation.
124 /// </summary>
125 private IGeneration m_newGeneration;
126
127 /// <summary>
128 /// Old generation.
129 /// </summary>
130 private IGeneration m_oldGeneration;
131
132 /// <summary>
133 /// Operations between time check.
134 /// </summary>
135 private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
136
137 /// <summary>
138 /// Synchronization root object, should always be private and exists always
139 /// </summary>
140 private readonly object m_syncRoot = new object();
141
142 /// <summary>
143 /// Version of cache.
144 /// </summary>
145 /// <remarks>
146 /// <para>
147 /// Updated every time when cache has been changed (element removed, expired, added, replaced).
148 /// </para>
149 /// </remarks>
150 private int m_version;
151
152 /// <summary>
153 /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
154 /// </summary>
155 public CnmMemoryCache()
156 : this( DefaultMaxSize )
157 {
158 }
159
160 /// <summary>
161 /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
162 /// </summary>
163 /// <param name="maximalSize">
164 /// Maximal cache size.
165 /// </param>
166 public CnmMemoryCache( long maximalSize )
167 : this( maximalSize, DefaultMaxCount )
168 {
169 }
170
171 /// <summary>
172 /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
173 /// </summary>
174 /// <param name="maximalSize">
175 /// Maximal cache size.
176 /// </param>
177 /// <param name="maximalCount">
178 /// Maximal element count.
179 /// </param>
180 public CnmMemoryCache( long maximalSize, int maximalCount )
181 : this( maximalSize, maximalCount, DefaultExpirationTime )
182 {
183 }
184
185 /// <summary>
186 /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
187 /// </summary>
188 /// <param name="maximalSize">
189 /// Maximal cache size.
190 /// </param>
191 /// <param name="maximalCount">
192 /// Maximal element count.
193 /// </param>
194 /// <param name="expirationTime">
195 /// Elements expiration time.
196 /// </param>
197 public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime )
198 : this( maximalSize, maximalCount, expirationTime, EqualityComparer<TKey>.Default )
199 {
200 }
201
202 /// <summary>
203 /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
204 /// </summary>
205 /// <param name="maximalSize">
206 /// Maximal cache size.
207 /// </param>
208 /// <param name="maximalCount">
209 /// Maximal element count.
210 /// </param>
211 /// <param name="expirationTime">
212 /// Elements expiration time.
213 /// </param>
214 /// <param name="comparer">
215 /// Comparer used for comparing elements.
216 /// </param>
217 /// <exception cref="ArgumentNullException">
218 /// <see cref="comparer"/>is <see langword="null"/> reference.
219 /// </exception>
220 public CnmMemoryCache( long maximalSize,
221 int maximalCount,
222 TimeSpan expirationTime,
223 IEqualityComparer<TKey> comparer )
224 {
225 if( comparer == null )
226 throw new ArgumentNullException( "comparer" );
227
228 if( expirationTime < MinExpirationTime )
229 expirationTime = MinExpirationTime;
230 if( maximalCount < 8 )
231 maximalCount = 8;
232 if( maximalSize < 8 )
233 maximalSize = 8;
234 if( maximalCount > maximalSize )
235 maximalCount = (int) maximalSize;
236
237 Comparer = comparer;
238 m_expirationTime = expirationTime;
239 m_maxSize = maximalSize;
240 m_maxCount = maximalCount;
241
242 Initialize();
243 }
244
245 /// <summary>
246 /// Add element to new generation.
247 /// </summary>
248 /// <param name="bucketIndex">
249 /// The bucket index.
250 /// </param>
251 /// <param name="key">
252 /// The element's key.
253 /// </param>
254 /// <param name="value">
255 /// The element's value.
256 /// </param>
257 /// <param name="size">
258 /// The element's size.
259 /// </param>
260 protected virtual void AddToNewGeneration( int bucketIndex, TKey key, TValue value, long size )
261 {
262 // Add to newest generation
263 if( !m_newGeneration.Set( bucketIndex, key, value, size ) )
264 {
265 // Failed to add new generation
266 RecycleGenerations();
267 m_newGeneration.Set( bucketIndex, key, value, size );
268 }
269
270 m_version++;
271 }
272
273 /// <summary>
274 /// <para>
275 /// Get keys bucket index.
276 /// </para>
277 /// </summary>
278 /// <param name="key">
279 /// <para>
280 /// Key which bucket index is being retrieved.
281 /// </para>
282 /// </param>
283 /// <returns>
284 /// <para>
285 /// Bucket index.
286 /// </para>
287 /// </returns>
288 /// <remarks>
289 /// <para>
290 /// Method uses <see cref="Comparer"/> to calculate <see cref="key"/> hash code.
291 /// </para>
292 /// <para>
293 /// Bucket index is remainder when element key's hash value is divided by bucket count.
294 /// </para>
295 /// <para>
296 /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
297 /// </para>
298 /// </remarks>
299 protected virtual int GetBucketIndex( TKey key )
300 {
301 return (Comparer.GetHashCode( key ) & 0x7FFFFFFF) % m_generationBucketCount;
302 }
303
304 /// <summary>
305 /// Purge generation from the cache.
306 /// </summary>
307 /// <param name="generation">
308 /// The generation that is purged.
309 /// </param>
310 protected virtual void PurgeGeneration( IGeneration generation )
311 {
312 generation.Clear();
313 m_version++;
314 }
315
316 /// <summary>
317 /// check expired.
318 /// </summary>
319 private void CheckExpired()
320 {
321 // Do this only one in every m_operationsBetweenTimeChecks
322 // Fetching time is using several millisecons - it is better not to do all time.
323 m_operationsBetweenTimeChecks--;
324 if( m_operationsBetweenTimeChecks <= 0 )
325 PurgeExpired();
326 }
327
328 /// <summary>
329 /// Initialize cache.
330 /// </summary>
331 private void Initialize()
332 {
333 m_version++;
334
335 m_generationMaxSize = MaxSize / 2;
336 MaxElementSize = MaxSize / 8;
337 m_generationElementCount = MaxCount / 2;
338
339 // Buckets need to be prime number to get better spread of hash values
340 m_generationBucketCount = PrimeNumberHelper.GetPrime( m_generationElementCount );
341
342 m_newGeneration = new HashGeneration( this );
343 m_oldGeneration = new HashGeneration( this );
344 m_oldGeneration.MakeOld();
345 }
346
347 /// <summary>
348 /// Recycle generations.
349 /// </summary>
350 private void RecycleGenerations()
351 {
352 // Rotate old generation to new generation, new generation to old generation
353 IGeneration temp = m_newGeneration;
354 m_newGeneration = m_oldGeneration;
355 m_newGeneration.Clear();
356 m_oldGeneration = temp;
357 m_oldGeneration.MakeOld();
358 }
359
360 #region Nested type: Enumerator
361
362 /// <summary>
363 /// Key and value pair enumerator.
364 /// </summary>
365 private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
366 {
367 /// <summary>
368 /// Current enumerator.
369 /// </summary>
370 private int m_currentEnumerator = -1;
371
372 /// <summary>
373 /// Enumerators to different generations.
374 /// </summary>
375 private readonly IEnumerator<KeyValuePair<TKey, TValue>>[] m_generationEnumerators =
376 new IEnumerator<KeyValuePair<TKey, TValue>>[2];
377
378 /// <summary>
379 /// Initializes a new instance of the <see cref="Enumerator"/> class.
380 /// </summary>
381 /// <param name="cache">
382 /// The cache.
383 /// </param>
384 public Enumerator( CnmMemoryCache<TKey, TValue> cache )
385 {
386 m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator();
387 m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator();
388 }
389
390 #region IEnumerator<KeyValuePair<TKey,TValue>> Members
391
392 /// <summary>
393 /// Gets the element in the collection at the current position of the enumerator.
394 /// </summary>
395 /// <returns>
396 /// The element in the collection at the current position of the enumerator.
397 /// </returns>
398 /// <exception cref="InvalidOperationException">
399 /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called.
400 /// </exception>
401 public KeyValuePair<TKey, TValue> Current
402 {
403 get
404 {
405 if( m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length )
406 throw new InvalidOperationException();
407
408 return m_generationEnumerators[ m_currentEnumerator ].Current;
409 }
410 }
411
412 /// <summary>
413 /// Gets the current element in the collection.
414 /// </summary>
415 /// <returns>
416 /// The current element in the collection.
417 /// </returns>
418 /// <exception cref="T:System.InvalidOperationException">
419 /// The enumerator is positioned before the first element of the collection or after the last element.
420 /// </exception><filterpriority>2</filterpriority>
421 object IEnumerator.Current
422 {
423 get { return Current; }
424 }
425
426 /// <summary>
427 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
428 /// </summary>
429 /// <filterpriority>2</filterpriority>
430 public void Dispose()
431 {
432 }
433
434 /// <summary>
435 /// Advances the enumerator to the next element of the collection.
436 /// </summary>
437 /// <returns>
438 /// <see langword="true"/>if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the collection.
439 /// </returns>
440 /// <exception cref="T:System.InvalidOperationException">
441 /// The collection was modified after the enumerator was created.
442 /// </exception>
443 /// <filterpriority>2</filterpriority>
444 public bool MoveNext()
445 {
446 if( m_currentEnumerator == -1 )
447 m_currentEnumerator = 0;
448
449 while( m_currentEnumerator < m_generationEnumerators.Length )
450 {
451 if( m_generationEnumerators[ m_currentEnumerator ].MoveNext() )
452 return true;
453
454 m_currentEnumerator++;
455 }
456
457 return false;
458 }
459
460 /// <summary>
461 /// Sets the enumerator to its initial position, which is before the first element in the collection.
462 /// </summary>
463 /// <exception cref="T:System.InvalidOperationException">
464 /// The collection was modified after the enumerator was created.
465 /// </exception>
466 /// <filterpriority>2</filterpriority>
467 public void Reset()
468 {
469 foreach( IEnumerator<KeyValuePair<TKey, TValue>> enumerator in m_generationEnumerators )
470 {
471 enumerator.Reset();
472 }
473
474 m_currentEnumerator = -1;
475 }
476
477 #endregion
478 }
479
480 #endregion
481
482 #region Nested type: HashGeneration
483
484 /// <summary>
485 /// Hash generation class
486 /// </summary>
487 /// <remarks>
488 /// <para>
489 /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed
490 /// amount of buckets and it is never rehashed.
491 /// </para>
492 /// <para>
493 /// Read more about hash tables from <a href="http://en.wikipedia.org/wiki/Hash_table">Wiki article</a>.
494 /// </para>
495 /// </remarks>
496 /// <seealso href="http://en.wikipedia.org/wiki/Hash_table"/>
497 private class HashGeneration : IGeneration
498 {
499 /// <summary>
500 /// Value indicating whether generation was accessed since last time check.
501 /// </summary>
502 private bool m_accessedSinceLastTimeCheck;
503
504 /// <summary>
505 /// Index of first element's in element chain.
506 /// </summary>
507 /// <value>
508 /// -1 if there is no element in bucket; otherwise first element's index in the element chain.
509 /// </value>
510 /// <remarks>
511 /// Bucket index is remainder when element key's hash value is divided by bucket count.
512 /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
513 /// </remarks>
514 private readonly int[] m_buckets;
515
516 /// <summary>
517 /// Cache object.
518 /// </summary>
519 private readonly CnmMemoryCache<TKey, TValue> m_cache;
520
521 /// <summary>
522 /// Generation's element array.
523 /// </summary>
524 /// <seealso cref="Element"/>
525 private readonly Element[] m_elements;
526
527 /// <summary>
528 /// Generation's expiration time.
529 /// </summary>
530 private DateTime m_expirationTime1;
531
532 /// <summary>
533 /// Index to first free element.
534 /// </summary>
535 private int m_firstFreeElement;
536
537 /// <summary>
538 /// Free element count.
539 /// </summary>
540 /// <remarks>
541 /// When generation is cleared or constructed, this is NOT set to element count.
542 /// This is only tracking elements that are removed and are currently free.
543 /// </remarks>
544 private int m_freeCount;
545
546 /// <summary>
547 /// Is this generation "new generation".
548 /// </summary>
549 private bool m_newGeneration;
550
551 /// <summary>
552 /// Next unused entry.
553 /// </summary>
554 private int m_nextUnusedElement;
555
556 /// <summary>
557 /// Size of data stored to generation.
558 /// </summary>
559 private long m_size;
560
561 /// <summary>
562 /// Initializes a new instance of the <see cref="HashGeneration"/> class.
563 /// </summary>
564 /// <param name="cache">
565 /// The cache.
566 /// </param>
567 public HashGeneration( CnmMemoryCache<TKey, TValue> cache )
568 {
569 m_cache = cache;
570 m_elements = new Element[m_cache.m_generationElementCount];
571 m_buckets = new int[m_cache.m_generationBucketCount];
572 Clear();
573 }
574
575 /// <summary>
576 /// Find element's index
577 /// </summary>
578 /// <param name="bucketIndex">
579 /// The element's bucket index.
580 /// </param>
581 /// <param name="key">
582 /// The element's key.
583 /// </param>
584 /// <param name="moveToFront">
585 /// Move element to front of elements.
586 /// </param>
587 /// <param name="previousIndex">
588 /// The previous element's index.
589 /// </param>
590 /// <returns>
591 /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation).
592 /// </returns>
593 private int FindElementIndex( int bucketIndex, TKey key, bool moveToFront, out int previousIndex )
594 {
595 previousIndex = -1;
596 int elementIndex = m_buckets[ bucketIndex ];
597 while( elementIndex >= 0 )
598 {
599 if( m_cache.Comparer.Equals( key, m_elements[ elementIndex ].Key ) )
600 {
601 // Found match
602 if( moveToFront && previousIndex >= 0 )
603 {
604 // Move entry to front
605 m_elements[ previousIndex ].Next = m_elements[ elementIndex ].Next;
606 m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
607 m_buckets[ bucketIndex ] = elementIndex;
608 previousIndex = 0;
609 }
610
611 return elementIndex;
612 }
613
614 previousIndex = elementIndex;
615 elementIndex = m_elements[ elementIndex ].Next;
616 }
617
618 return -1;
619 }
620
621 /// <summary>
622 /// Remove element front the generation.
623 /// </summary>
624 /// <param name="bucketIndex">
625 /// The bucket index.
626 /// </param>
627 /// <param name="entryIndex">
628 /// The element index.
629 /// </param>
630 /// <param name="previousIndex">
631 /// The element's previous index.
632 /// </param>
633 private void RemoveElement( int bucketIndex, int entryIndex, int previousIndex )
634 {
635 if( previousIndex >= 0 )
636 m_elements[ previousIndex ].Next = m_elements[ entryIndex ].Next;
637 else
638 m_buckets[ bucketIndex ] = m_elements[ entryIndex ].Next;
639
640 Size -= m_elements[ entryIndex ].Size;
641 m_elements[ entryIndex ].Value = default(TValue);
642 m_elements[ entryIndex ].Key = default(TKey);
643
644 // Add element to free elements list
645 m_elements[ entryIndex ].Next = m_firstFreeElement;
646 m_firstFreeElement = entryIndex;
647 m_freeCount++;
648 }
649
650 #region Nested type: Element
651
652 /// <summary>
653 /// Element that stores key, next element in chain, size and value.
654 /// </summary>
655 private struct Element
656 {
657 /// <summary>
658 /// Element's key.
659 /// </summary>
660 public TKey Key;
661
662 /// <summary>
663 /// Next element in chain.
664 /// </summary>
665 /// <remarks>
666 /// When element have value (something is stored to it), this is index of
667 /// next element with same bucket index. When element is free, this
668 /// is index of next element in free element's list.
669 /// </remarks>
670 public int Next;
671
672 /// <summary>
673 /// Size of element.
674 /// </summary>
675 /// <value>
676 /// 0 if element is free; otherwise larger than 0.
677 /// </value>
678 public long Size;
679
680 /// <summary>
681 /// Element's value.
682 /// </summary>
683 /// <remarks>
684 /// It is possible that this value is <see langword="null"/> even when element
685 /// have value - element's value is then <see langword="null"/> reference.
686 /// </remarks>
687 public TValue Value;
688
689 /// <summary>
690 /// Gets a value indicating whether element is free or have value.
691 /// </summary>
692 /// <value>
693 /// <see langword="true"/> when element is free; otherwise <see langword="false"/>.
694 /// </value>
695 public bool IsFree
696 {
697 get { return Size == 0; }
698 }
699 }
700
701 #endregion
702
703 #region Nested type: Enumerator
704
705 /// <summary>
706 /// Key value pair enumerator for <see cref="HashGeneration"/> object.
707 /// </summary>
708 private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
709 {
710 /// <summary>
711 /// Current element.
712 /// </summary>
713 private KeyValuePair<TKey, TValue> m_current;
714
715 /// <summary>
716 /// Current index.
717 /// </summary>
718 private int m_currentIndex;
719
720 /// <summary>
721 /// Generation that is being enumerated.
722 /// </summary>
723 private readonly HashGeneration m_generation;
724
725 /// <summary>
726 /// Cache version.
727 /// </summary>
728 /// <remarks>
729 /// When cache is change, version number is changed.
730 /// </remarks>
731 /// <seealso cref="CnmMemoryCache{TKey,TValue}.m_version"/>
732 private readonly int m_version;
733
734 /// <summary>
735 /// Initializes a new instance of the <see cref="Enumerator"/> class.
736 /// </summary>
737 /// <param name="generation">
738 /// The generation.
739 /// </param>
740 public Enumerator( HashGeneration generation )
741 {
742 m_generation = generation;
743 m_version = m_generation.m_cache.m_version;
744 }
745
746 #region IEnumerator<KeyValuePair<TKey,TValue>> Members
747
748 /// <summary>
749 /// Gets the element in the collection at the current position of the enumerator.
750 /// </summary>
751 /// <returns>
752 /// The element in the collection at the current position of the enumerator.
753 /// </returns>
754 /// <exception cref="InvalidOperationException">
755 /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called.
756 /// </exception>
757 public KeyValuePair<TKey, TValue> Current
758 {
759 get
760 {
761 if( m_currentIndex == 0 || m_currentIndex >= m_generation.Count )
762 throw new InvalidOperationException();
763
764 return m_current;
765 }
766 }
767
768 /// <summary>
769 /// Gets the current element in the collection.
770 /// </summary>
771 /// <returns>
772 /// The current element in the collection.
773 /// </returns>
774 /// <exception cref="InvalidOperationException">
775 /// The enumerator is positioned before the first element of the collection or after the last element.
776 /// </exception><filterpriority>2</filterpriority>
777 object IEnumerator.Current
778 {
779 get { return Current; }
780 }
781
782 /// <summary>
783 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
784 /// </summary>
785 /// <filterpriority>2</filterpriority>
786 public void Dispose()
787 {
788 }
789
790 /// <summary>
791 /// Advances the enumerator to the next element of the collection.
792 /// </summary>
793 /// <returns>
794 /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
795 /// </returns>
796 /// <exception cref="InvalidOperationException">
797 /// The collection was modified after the enumerator was created.
798 /// </exception>
799 public bool MoveNext()
800 {
801 if( m_version != m_generation.m_cache.m_version )
802 throw new InvalidOperationException();
803
804 while( m_currentIndex < m_generation.Count )
805 {
806 if( m_generation.m_elements[ m_currentIndex ].IsFree )
807 {
808 m_currentIndex++;
809 continue;
810 }
811
812 m_current = new KeyValuePair<TKey, TValue>( m_generation.m_elements[ m_currentIndex ].Key,
813 m_generation.m_elements[ m_currentIndex ].Value );
814 m_currentIndex++;
815 return true;
816 }
817
818 m_current = new KeyValuePair<TKey, TValue>();
819 return false;
820 }
821
822 /// <summary>
823 /// Sets the enumerator to its initial position, which is before the first element in the collection.
824 /// </summary>
825 /// <exception cref="InvalidOperationException">
826 /// The collection was modified after the enumerator was created.
827 /// </exception>
828 /// <filterpriority>2</filterpriority>
829 public void Reset()
830 {
831 if( m_version != m_generation.m_cache.m_version )
832 throw new InvalidOperationException();
833
834 m_currentIndex = 0;
835 }
836
837 #endregion
838 }
839
840 #endregion
841
842 #region IGeneration Members
843
844 /// <summary>
845 /// Gets or sets a value indicating whether generation was accessed since last time check.
846 /// </summary>
847 public bool AccessedSinceLastTimeCheck
848 {
849 get { return m_accessedSinceLastTimeCheck; }
850
851 set { m_accessedSinceLastTimeCheck = value; }
852 }
853
854 /// <summary>
855 /// Gets element count in generation.
856 /// </summary>
857 public int Count
858 {
859 get { return m_nextUnusedElement - m_freeCount; }
860 }
861
862 /// <summary>
863 /// Gets or sets generation's expiration time.
864 /// </summary>
865 public DateTime ExpirationTime
866 {
867 get { return m_expirationTime1; }
868
869 set { m_expirationTime1 = value; }
870 }
871
872 /// <summary>
873 /// Gets or sets size of data stored to generation.
874 /// </summary>
875 public long Size
876 {
877 get { return m_size; }
878
879 private set { m_size = value; }
880 }
881
882 /// <summary>
883 /// Clear all elements from the generation and make generation new again.
884 /// </summary>
885 /// <remarks>
886 /// When generation is new, it is allowed to add new elements to it and
887 /// <see cref="IGeneration.TryGetValue"/>doesn't remove elements from it.
888 /// </remarks>
889 /// <seealso cref="IGeneration.MakeOld"/>
890 public void Clear()
891 {
892 for( int i = m_buckets.Length - 1 ; i >= 0 ; i-- )
893 {
894 m_buckets[ i ] = -1;
895 }
896
897 Array.Clear( m_elements, 0, m_elements.Length );
898 Size = 0;
899 m_firstFreeElement = -1;
900 m_freeCount = 0;
901 m_nextUnusedElement = 0;
902 m_newGeneration = true;
903 ExpirationTime = DateTime.MaxValue;
904 }
905
906 /// <summary>
907 /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key.
908 /// </summary>
909 /// <param name="bucketIndex">
910 /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>.
911 /// </param>
912 /// <param name="key">
913 /// The key to locate in the <see cref="IGeneration"/>.
914 /// </param>
915 /// <returns>
916 /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>;
917 /// otherwise <see langword="false"/>.
918 /// </returns>
919 public bool Contains( int bucketIndex, TKey key )
920 {
921 int previousIndex;
922 if( FindElementIndex( bucketIndex, key, true, out previousIndex ) == -1 )
923 return false;
924
925 AccessedSinceLastTimeCheck = true;
926 return true;
927 }
928
929 /// <summary>
930 /// Returns an enumerator that iterates through the elements stored <see cref="HashGeneration"/>.
931 /// </summary>
932 /// <returns>
933 /// A <see cref="IEnumerator"/> that can be used to iterate through the <see cref="HashGeneration"/>.
934 /// </returns>
935 /// <filterpriority>1</filterpriority>
936 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
937 {
938 return new Enumerator( this );
939 }
940
941 /// <summary>
942 /// Make from generation old generation.
943 /// </summary>
944 /// <remarks>
945 /// When generation is old, <see cref="IGeneration.TryGetValue"/> hit removes element from the generation.
946 /// </remarks>
947 /// <seealso cref="IGeneration.Clear"/>
948 public void MakeOld()
949 {
950 m_newGeneration = false;
951 }
952
953 /// <summary>
954 /// Remove element associated with the key from the generation.
955 /// </summary>
956 /// <param name="bucketIndex">
957 /// The element's bucket index.
958 /// </param>
959 /// <param name="key">
960 /// The element's key.
961 /// </param>
962 /// <returns>
963 /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>.
964 /// </returns>
965 public bool Remove( int bucketIndex, TKey key )
966 {
967 int previousIndex;
968 int entryIndex = FindElementIndex( bucketIndex, key, false, out previousIndex );
969 if( entryIndex != -1 )
970 {
971 RemoveElement( bucketIndex, entryIndex, previousIndex );
972 AccessedSinceLastTimeCheck = true;
973 return true;
974 }
975
976 return false;
977 }
978
979 /// <summary>
980 /// Set or add element to generation.
981 /// </summary>
982 /// <param name="bucketIndex">
983 /// The element's bucket index.
984 /// </param>
985 /// <param name="key">
986 /// The element's key.
987 /// </param>
988 /// <param name="value">
989 /// The element's value.
990 /// </param>
991 /// <param name="size">
992 /// The element's size.
993 /// </param>
994 /// <returns>
995 /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>.
996 /// </returns>
997 /// <remarks>
998 /// <para>
999 /// If element was already existing in generation and new element size fits to collection limits,
1000 /// then it's value is replaced with new one and size information is updated. If element didn't
1001 /// exists in generation before, then generation must have empty space for a new element and
1002 /// size must fit generation's limits, before element is added to generation.
1003 /// </para>
1004 /// </remarks>
1005 public bool Set( int bucketIndex, TKey key, TValue value, long size )
1006 {
1007 Debug.Assert( m_newGeneration, "It is possible to insert new elements only to newest generation." );
1008 Debug.Assert( size > 0, "New element size should be more than 0." );
1009
1010 int previousIndex;
1011 int elementIndex = FindElementIndex( bucketIndex, key, true, out previousIndex );
1012 if( elementIndex == -1 )
1013 {
1014 // New key
1015 if( Size + size > m_cache.m_generationMaxSize ||
1016 (m_nextUnusedElement == m_cache.m_generationElementCount && m_freeCount == 0) )
1017 {
1018 // Generation is full
1019 return false;
1020 }
1021
1022 // Increase size of generation
1023 Size += size;
1024
1025 // Get first free entry and update free entry list
1026 if( m_firstFreeElement != -1 )
1027 {
1028 // There was entry that was removed
1029 elementIndex = m_firstFreeElement;
1030 m_firstFreeElement = m_elements[ elementIndex ].Next;
1031 m_freeCount--;
1032 }
1033 else
1034 {
1035 // No entries removed so far - just take a last one
1036 elementIndex = m_nextUnusedElement;
1037 m_nextUnusedElement++;
1038 }
1039
1040 Debug.Assert( m_elements[ elementIndex ].IsFree, "Allocated element is not free." );
1041
1042 // Move new entry to front
1043 m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
1044 m_buckets[ bucketIndex ] = elementIndex;
1045
1046 // Set key and update count
1047 m_elements[ elementIndex ].Key = key;
1048 }
1049 else
1050 {
1051 // Existing key
1052 if( Size - m_elements[ elementIndex ].Size + size > m_cache.m_generationMaxSize )
1053 {
1054 // Generation is full
1055 // Remove existing element, because generation is going to be recycled to
1056 // old generation and element is stored to new generation
1057 RemoveElement( bucketIndex, elementIndex, previousIndex );
1058 return false;
1059 }
1060
1061 // Update generation's size
1062 Size = Size - m_elements[ elementIndex ].Size + size;
1063 }
1064
1065 // Finally set value and size
1066 m_elements[ elementIndex ].Value = value;
1067 m_elements[ elementIndex ].Size = size;
1068
1069 // Success - key was inserterted to generation
1070 AccessedSinceLastTimeCheck = true;
1071 return true;
1072 }
1073
1074 /// <summary>
1075 /// Try to get element associated with key.
1076 /// </summary>
1077 /// <param name="bucketIndex">
1078 /// The element's bucket index.
1079 /// </param>
1080 /// <param name="key">
1081 /// The element's key.
1082 /// </param>
1083 /// <param name="value">
1084 /// The element's value.
1085 /// </param>
1086 /// <param name="size">
1087 /// The element's size.
1088 /// </param>
1089 /// <returns>
1090 /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>.
1091 /// </returns>
1092 /// <remarks>
1093 /// <para>
1094 /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/>
1095 /// are set to default value (default(TValue) and 0).
1096 /// </para>
1097 /// </remarks>
1098 public bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size )
1099 {
1100 // Find entry index,
1101 int previousIndex;
1102 int elementIndex = FindElementIndex( bucketIndex, key, m_newGeneration, out previousIndex );
1103 if( elementIndex == -1 )
1104 {
1105 value = default(TValue);
1106 size = 0;
1107 return false;
1108 }
1109
1110 value = m_elements[ elementIndex ].Value;
1111 size = m_elements[ elementIndex ].Size;
1112
1113 if( !m_newGeneration )
1114 {
1115 // Old generation - remove element, because it is moved to new generation
1116 RemoveElement( bucketIndex, elementIndex, previousIndex );
1117 }
1118
1119 AccessedSinceLastTimeCheck = true;
1120 return true;
1121 }
1122
1123 /// <summary>
1124 /// Returns an enumerator that iterates through a collection.
1125 /// </summary>
1126 /// <returns>
1127 /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
1128 /// </returns>
1129 /// <filterpriority>2</filterpriority>
1130 IEnumerator IEnumerable.GetEnumerator()
1131 {
1132 return GetEnumerator();
1133 }
1134
1135 #endregion
1136 }
1137
1138 #endregion
1139
1140 #region Nested type: IGeneration
1141
1142 /// <summary>
1143 /// Cache element generation interface
1144 /// </summary>
1145 /// <remarks>
1146 /// <para>
1147 /// Generation can hold limited count of elements and limited size of data.
1148 /// </para>
1149 /// <para>
1150 /// There are two kind generations: "new generation" and "old generation(s)". All new elements
1151 /// are added to "new generation".
1152 /// </para>
1153 /// </remarks>
1154 protected interface IGeneration : IEnumerable<KeyValuePair<TKey, TValue>>
1155 {
1156 /// <summary>
1157 /// Gets or sets a value indicating whether generation was accessed since last time check.
1158 /// </summary>
1159 bool AccessedSinceLastTimeCheck { get; set; }
1160
1161 /// <summary>
1162 /// Gets element count in generation.
1163 /// </summary>
1164 int Count { get; }
1165
1166 /// <summary>
1167 /// Gets or sets generation's expiration time.
1168 /// </summary>
1169 DateTime ExpirationTime { get; set; }
1170
1171 /// <summary>
1172 /// Gets size of data stored to generation.
1173 /// </summary>
1174 long Size { get; }
1175
1176 /// <summary>
1177 /// Clear all elements from the generation and make generation new again.
1178 /// </summary>
1179 /// <remarks>
1180 /// When generation is new, it is allowed to add new elements to it and
1181 /// <see cref="TryGetValue"/>doesn't remove elements from it.
1182 /// </remarks>
1183 /// <seealso cref="MakeOld"/>
1184 void Clear();
1185
1186 /// <summary>
1187 /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key.
1188 /// </summary>
1189 /// <param name="bucketIndex">
1190 /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>.
1191 /// </param>
1192 /// <param name="key">
1193 /// The key to locate in the <see cref="IGeneration"/>.
1194 /// </param>
1195 /// <returns>
1196 /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>;
1197 /// otherwise <see langword="false"/>.
1198 /// </returns>
1199 bool Contains( int bucketIndex, TKey key );
1200
1201 /// <summary>
1202 /// Make from generation old generation.
1203 /// </summary>
1204 /// <remarks>
1205 /// When generation is old, <see cref="TryGetValue"/> hit removes element from the generation.
1206 /// </remarks>
1207 /// <seealso cref="Clear"/>
1208 void MakeOld();
1209
1210 /// <summary>
1211 /// Remove element associated with the key from the generation.
1212 /// </summary>
1213 /// <param name="bucketIndex">
1214 /// The element's bucket index.
1215 /// </param>
1216 /// <param name="key">
1217 /// The element's key.
1218 /// </param>
1219 /// <returns>
1220 /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>.
1221 /// </returns>
1222 bool Remove( int bucketIndex, TKey key );
1223
1224 /// <summary>
1225 /// Set or add element to generation.
1226 /// </summary>
1227 /// <param name="bucketIndex">
1228 /// The element's bucket index.
1229 /// </param>
1230 /// <param name="key">
1231 /// The element's key.
1232 /// </param>
1233 /// <param name="value">
1234 /// The element's value.
1235 /// </param>
1236 /// <param name="size">
1237 /// The element's size.
1238 /// </param>
1239 /// <returns>
1240 /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>.
1241 /// </returns>
1242 /// <remarks>
1243 /// <para>
1244 /// If element was already existing in generation and new element size fits to collection limits,
1245 /// then it's value is replaced with new one and size information is updated. If element didn't
1246 /// exists in generation before, then generation must have empty space for a new element and
1247 /// size must fit generation's limits, before element is added to generation.
1248 /// </para>
1249 /// </remarks>
1250 bool Set( int bucketIndex, TKey key, TValue value, long size );
1251
1252 /// <summary>
1253 /// Try to get element associated with key.
1254 /// </summary>
1255 /// <param name="bucketIndex">
1256 /// The element's bucket index.
1257 /// </param>
1258 /// <param name="key">
1259 /// The element's key.
1260 /// </param>
1261 /// <param name="value">
1262 /// The element's value.
1263 /// </param>
1264 /// <param name="size">
1265 /// The element's size.
1266 /// </param>
1267 /// <returns>
1268 /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>.
1269 /// </returns>
1270 /// <remarks>
1271 /// <para>
1272 /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/>
1273 /// are set to default value (default(TValue) and 0).
1274 /// </para>
1275 /// </remarks>
1276 bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size );
1277 }
1278
1279 #endregion
1280
1281 #region ICnmCache<TKey,TValue> Members
1282
1283 /// <summary>
1284 /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
1285 /// </summary>
1286 /// <remarks>
1287 /// <para>
1288 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
1289 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1290 /// </para>
1291 /// </remarks>
1292 /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
1293 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1294 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1295 /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
1296 public int Count
1297 {
1298 get { return m_newGeneration.Count + m_oldGeneration.Count; }
1299 }
1300
1301 /// <summary>
1302 /// Gets or sets elements expiration time.
1303 /// </summary>
1304 /// <value>
1305 /// Elements expiration time.
1306 /// </value>
1307 /// <remarks>
1308 /// <para>
1309 /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
1310 /// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is
1311 /// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the
1312 /// <see cref="ICnmCache{TKey,TValue}"/>.
1313 /// </para>
1314 /// <para>
1315 /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time,
1316 /// because total size or count of elements stored to cache is larger than <see cref="ICnmCache{TKey,TValue}.MaxSize"/> or <see cref="ICnmCache{TKey,TValue}.MaxCount"/>.
1317 /// </para>
1318 /// <para>
1319 /// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>.
1320 /// </para>
1321 /// <para>
1322 /// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired.
1323 /// </para>
1324 /// <para>
1325 /// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>.
1326 /// </para>
1327 /// </remarks>
1328 /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
1329 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1330 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1331 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1332 /// <seealso cref="ICnmCache{TKey,TValue}.Count"/>
1333 /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
1334 /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
1335 /// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
1336 public TimeSpan ExpirationTime
1337 {
1338 get { return m_expirationTime; }
1339
1340 set
1341 {
1342 if( value < MinExpirationTime )
1343 value = MinExpirationTime;
1344
1345 if( m_expirationTime == value )
1346 return;
1347
1348 m_newGeneration.ExpirationTime = (m_newGeneration.ExpirationTime - m_expirationTime) + value;
1349 m_oldGeneration.ExpirationTime = (m_oldGeneration.ExpirationTime - m_expirationTime) + value;
1350 m_expirationTime = value;
1351
1352 PurgeExpired();
1353 }
1354 }
1355
1356 /// <summary>
1357 /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements.
1358 /// </summary>
1359 /// <value>
1360 /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited;
1361 /// otherwise, <see langword="false"/>.
1362 /// </value>
1363 /// <remarks>
1364 /// <para>
1365 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
1366 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1367 /// </para>
1368 /// </remarks>
1369 /// <seealso cref="ICnmCache{TKey,TValue}.Count"/>
1370 /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
1371 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1372 /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
1373 public bool IsCountLimited
1374 {
1375 get { return true; }
1376 }
1377
1378 /// <summary>
1379 /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements.
1380 /// </summary>
1381 /// <value>
1382 /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited;
1383 /// otherwise, <see langword="false"/>.
1384 /// </value>
1385 /// <remarks>
1386 /// <para>
1387 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
1388 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1389 /// </para>
1390 /// </remarks>
1391 /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
1392 /// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
1393 /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
1394 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1395 /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
1396 public bool IsSizeLimited
1397 {
1398 get { return true; }
1399 }
1400
1401 /// <summary>
1402 /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe).
1403 /// </summary>
1404 /// <value>
1405 /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe);
1406 /// otherwise, <see langword="false"/>.
1407 /// </value>
1408 /// <remarks>
1409 /// <para>
1410 /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use
1411 /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class
1412 /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object.
1413 /// </para>
1414 /// </remarks>
1415 /// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/>
1416 /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/>
1417 public bool IsSynchronized
1418 {
1419 get { return false; }
1420 }
1421
1422 /// <summary>
1423 /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time.
1424 /// </summary>
1425 /// <value>
1426 /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements;
1427 /// otherwise, <see langword="false"/>.
1428 /// </value>
1429 /// <remarks>
1430 /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/>
1431 /// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from
1432 /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may
1433 /// stay longer in cache.
1434 /// </remarks>
1435 /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
1436 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1437 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1438 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1439 public bool IsTimeLimited
1440 {
1441 get { return ExpirationTime != TimeSpan.MaxValue; }
1442 }
1443
1444 /// <summary>
1445 /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>.
1446 /// </summary>
1447 /// <value>
1448 /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements;
1449 /// otherwise maximal allowed count of elements.
1450 /// </value>
1451 /// <remarks>
1452 /// <para>
1453 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
1454 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1455 /// </para>
1456 /// </remarks>
1457 public int MaxCount
1458 {
1459 get { return m_maxCount; }
1460
1461 set
1462 {
1463 if( value < 8 )
1464 value = 8;
1465 if( m_maxCount == value )
1466 return;
1467
1468 m_maxCount = value;
1469 Initialize();
1470 }
1471 }
1472
1473 /// <summary>
1474 /// <para>Gets maximal allowed element size.</para>
1475 /// </summary>
1476 /// <value>
1477 /// Maximal allowed element size.
1478 /// </value>
1479 /// <remarks>
1480 /// <para>
1481 /// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is
1482 /// not added to the <see cref="ICnmCache{TKey,TValue}"/>.
1483 /// </para>
1484 /// </remarks>
1485 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1486 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1487 /// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
1488 /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
1489 public long MaxElementSize
1490 {
1491 get { return m_maxElementSize; }
1492
1493 private set { m_maxElementSize = value; }
1494 }
1495
1496 /// <summary>
1497 /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
1498 /// </summary>
1499 /// <value>
1500 /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
1501 /// </value>
1502 /// <remarks>
1503 /// <para>
1504 /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure.
1505 /// </para>
1506 /// <para>
1507 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
1508 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1509 /// </para>
1510 /// </remarks>
1511 /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
1512 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1513 /// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
1514 public long MaxSize
1515 {
1516 get { return m_maxSize; }
1517
1518 set
1519 {
1520 if( value < 8 )
1521 value = 8;
1522 if( m_maxSize == value )
1523 return;
1524
1525 m_maxSize = value;
1526 Initialize();
1527 }
1528 }
1529
1530 /// <summary>
1531 /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
1532 /// </summary>
1533 /// <value>
1534 /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
1535 /// </value>
1536 /// <remarks>
1537 /// <para>
1538 /// Normally bytes, but can be any suitable unit of measure.
1539 /// </para>
1540 /// <para>
1541 /// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method.
1542 /// </para>
1543 /// <para>
1544 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
1545 /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
1546 /// </para>
1547 /// </remarks>
1548 /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
1549 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1550 /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
1551 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1552 /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
1553 public long Size
1554 {
1555 get { return m_newGeneration.Size + m_oldGeneration.Size; }
1556 }
1557
1558 /// <summary>
1559 /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>.
1560 /// </summary>
1561 /// <value>
1562 /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>.
1563 /// </value>
1564 /// <remarks>
1565 /// <para>
1566 /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/>
1567 /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to
1568 /// <see cref="ICnmCache{TKey,TValue}"/>.
1569 /// </para>
1570 /// </remarks>
1571 /// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/>
1572 /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/>
1573 public object SyncRoot
1574 {
1575 get { return m_syncRoot; }
1576 }
1577
1578 /// <summary>
1579 /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>.
1580 /// </summary>
1581 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1582 /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
1583 /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
1584 /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
1585 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1586 public void Clear()
1587 {
1588 m_newGeneration.Clear();
1589 m_oldGeneration.Clear();
1590 m_oldGeneration.MakeOld();
1591 m_version++;
1592 }
1593
1594 /// <summary>
1595 /// Returns an enumerator that iterates through the elements stored to <see cref="CnmMemoryCache{TKey,TValue}"/>.
1596 /// </summary>
1597 /// <returns>
1598 /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.
1599 /// </returns>
1600 /// <filterpriority>1</filterpriority>
1601 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
1602 {
1603 return new Enumerator( this );
1604 }
1605
1606 /// <summary>
1607 /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>.
1608 /// </summary>
1609 /// <remarks>
1610 /// <para>
1611 /// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>.
1612 /// </para>
1613 /// <para>
1614 /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements
1615 /// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache.
1616 /// </para>
1617 /// </remarks>
1618 /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
1619 /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
1620 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1621 /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
1622 /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
1623 /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
1624 /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
1625 public void PurgeExpired()
1626 {
1627 m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
1628
1629 if( !IsTimeLimited )
1630 return;
1631
1632 DateTime now = DateTime.Now;
1633 if( m_newGeneration.AccessedSinceLastTimeCheck )
1634 {
1635 // New generation has been accessed since last check
1636 // Update it's expiration time.
1637 m_newGeneration.ExpirationTime = now + ExpirationTime;
1638 m_newGeneration.AccessedSinceLastTimeCheck = false;
1639 }
1640 else if( m_newGeneration.ExpirationTime < now )
1641 {
1642 // New generation has been expired.
1643 // --> also old generation must be expired.
1644 PurgeGeneration( m_newGeneration );
1645 PurgeGeneration( m_oldGeneration );
1646 return;
1647 }
1648
1649 if( m_oldGeneration.ExpirationTime < now )
1650 PurgeGeneration( m_oldGeneration );
1651 }
1652
1653 /// <summary>
1654 /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>.
1655 /// </summary>
1656 /// <param name="key">
1657 /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>.
1658 /// </param>
1659 /// <exception cref="ArgumentNullException">
1660 /// <paramref name="key"/>is <see langword="null"/>.
1661 /// </exception>
1662 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1663 /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
1664 /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
1665 /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
1666 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1667 public void Remove( TKey key )
1668 {
1669 if( key == null )
1670 throw new ArgumentNullException( "key" );
1671
1672 int bucketIndex = GetBucketIndex( key );
1673 if( !m_newGeneration.Remove( bucketIndex, key ) )
1674 {
1675 if( !m_oldGeneration.Remove( bucketIndex, key ) )
1676 {
1677 CheckExpired();
1678 return;
1679 }
1680 }
1681
1682 CheckExpired();
1683 m_version++;
1684 }
1685
1686 /// <summary>
1687 /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>.
1688 /// </summary>
1689 /// <param name="keys">
1690 /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>.
1691 /// </param>
1692 /// <exception cref="ArgumentNullException">
1693 /// <paramref name="keys"/>is <see langword="null"/>.
1694 /// </exception>
1695 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1696 /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
1697 /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
1698 /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
1699 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1700 public void RemoveRange( IEnumerable<TKey> keys )
1701 {
1702 if( keys == null )
1703 throw new ArgumentNullException( "keys" );
1704
1705 foreach( TKey key in keys )
1706 {
1707 if( key == null )
1708 continue;
1709
1710 int bucketIndex = GetBucketIndex( key );
1711 if( !m_newGeneration.Remove( bucketIndex, key ) )
1712 m_oldGeneration.Remove( bucketIndex, key );
1713 }
1714
1715 CheckExpired();
1716 m_version++;
1717 }
1718
1719 /// <summary>
1720 /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to
1721 /// <see cref="ICnmCache{TKey,TValue}"/>.
1722 /// </summary>
1723 /// <param name="key">
1724 /// The object used as the key of the element. Can't be <see langword="null"/> reference.
1725 /// </param>
1726 /// <param name="value">
1727 /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed.
1728 /// </param>
1729 /// <param name="size">
1730 /// The element's size. Normally bytes, but can be any suitable unit of measure.
1731 /// </param>
1732 /// <returns>
1733 /// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>;
1734 /// otherwise <see langword="false"/>.
1735 /// </returns>
1736 /// <exception cref="ArgumentNullException">
1737 /// <paramref name="key"/>is <see langword="null"/>.
1738 /// </exception>
1739 /// <exception cref="ArgumentOutOfRangeException">
1740 /// The element's <paramref name="size"/> is less than 0.
1741 /// </exception>
1742 /// <remarks>
1743 /// <para>
1744 /// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is
1745 /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is
1746 /// removed from the <see cref="ICnmCache{TKey,TValue}"/>.
1747 /// </para>
1748 /// <para>
1749 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
1750 /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element.
1751 /// </para>
1752 /// <para>
1753 /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
1754 /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element.
1755 /// </para>
1756 /// </remarks>
1757 /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
1758 /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
1759 /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
1760 /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
1761 /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
1762 /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
1763 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1764 public bool Set( TKey key, TValue value, long size )
1765 {
1766 if( key == null )
1767 throw new ArgumentNullException( "key" );
1768
1769 if( size < 0 )
1770 throw new ArgumentOutOfRangeException( "size", size, "Value's size can't be less than 0." );
1771
1772 if( size > MaxElementSize )
1773 {
1774 // Entry size is too big to fit cache - ignore it
1775 Remove( key );
1776 return false;
1777 }
1778
1779 if( size == 0 )
1780 size = 1;
1781
1782 int bucketIndex = GetBucketIndex( key );
1783 m_oldGeneration.Remove( bucketIndex, key );
1784 AddToNewGeneration( bucketIndex, key, value, size );
1785 CheckExpired();
1786
1787 return true;
1788 }
1789
1790 /// <summary>
1791 /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>.
1792 /// </summary>
1793 /// <returns>
1794 /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with
1795 /// the specified key; otherwise, <see langword="false"/>.
1796 /// </returns>
1797 /// <param name="key">
1798 /// The key whose <paramref name="value"/> to get.
1799 /// </param>
1800 /// <param name="value">
1801 /// When this method returns, the value associated with the specified <paramref name="key"/>,
1802 /// if the <paramref name="key"/> is found; otherwise, the
1803 /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.
1804 /// </param>
1805 /// <exception cref="ArgumentNullException">
1806 /// <paramref name="key"/>is <see langword="null"/>.
1807 /// </exception>
1808 /// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
1809 /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
1810 /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
1811 /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
1812 /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
1813 public bool TryGetValue( TKey key, out TValue value )
1814 {
1815 if( key == null )
1816 throw new ArgumentNullException( "key" );
1817
1818 int bucketIndex = GetBucketIndex( key );
1819 long size;
1820 if( m_newGeneration.TryGetValue( bucketIndex, key, out value, out size ) )
1821 {
1822 CheckExpired();
1823 return true;
1824 }
1825
1826 if( m_oldGeneration.TryGetValue( bucketIndex, key, out value, out size ) )
1827 {
1828 // Move element to new generation
1829 AddToNewGeneration( bucketIndex, key, value, size );
1830 CheckExpired();
1831 return true;
1832 }
1833
1834 CheckExpired();
1835 return false;
1836 }
1837
1838 /// <summary>
1839 /// Returns an enumerator that iterates through a collection.
1840 /// </summary>
1841 /// <returns>
1842 /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
1843 /// </returns>
1844 /// <filterpriority>2</filterpriority>
1845 IEnumerator IEnumerable.GetEnumerator()
1846 {
1847 return GetEnumerator();
1848 }
1849
1850 #endregion
1851 }
1852}