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