/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace OpenSim.Framework { /// /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size. /// /// /// The type of keys in the cache. /// /// /// The type of values in the dictionary. /// /// /// /// Cenome memory cache stores elements to hash table generations. When new element is being added to cache, and new size would exceed /// maximal allowed size or maximal amount of allowed element count, then elements in oldest generation are deleted. Last access time /// is also tracked in generation level - thus it is possible that some elements are staying in cache far beyond their expiration time. /// If elements in older generations are accessed through method, they are moved to newest generation. /// /// public class CnmMemoryCache : ICnmCache { /// /// Default maximal count. /// /// public const int DefaultMaxCount = 4096; /// /// Default maximal size. /// /// /// /// 128MB = 128 * 1024^2 = 134 217 728 bytes. /// /// /// public const long DefaultMaxSize = 134217728; /// /// How many operations between time checks. /// private const int DefaultOperationsBetweenTimeChecks = 40; /// /// Default expiration time. /// /// /// /// 30 minutes. /// /// public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 ); /// /// Minimal allowed expiration time. /// /// /// /// 5 minutes. /// /// public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds( 10.0 ); /// /// Comparer used to compare element keys. /// /// /// Comparer is initialized by constructor. /// /// public readonly IEqualityComparer Comparer; /// /// Expiration time. /// private TimeSpan m_expirationTime = DefaultExpirationTime; /// /// Generation bucket count. /// private int m_generationBucketCount; /// /// Generation entry count. /// private int m_generationElementCount; /// /// Generation max size. /// private long m_generationMaxSize; /// /// Maximal allowed count of elements. /// private int m_maxCount; /// /// Maximal allowed total size of elements. /// private long m_maxElementSize; /// /// Maximal size. /// private long m_maxSize; /// /// New generation. /// private IGeneration m_newGeneration; /// /// Old generation. /// private IGeneration m_oldGeneration; /// /// Operations between time check. /// private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks; /// /// Synchronization root object, should always be private and exists always /// private readonly object m_syncRoot = new object(); /// /// Version of cache. /// /// /// /// Updated every time when cache has been changed (element removed, expired, added, replaced). /// /// private int m_version; /// /// Initializes a new instance of the class. /// public CnmMemoryCache() : this( DefaultMaxSize ) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// public CnmMemoryCache( long maximalSize ) : this( maximalSize, DefaultMaxCount ) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// public CnmMemoryCache( long maximalSize, int maximalCount ) : this( maximalSize, maximalCount, DefaultExpirationTime ) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// /// /// Elements expiration time. /// public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime ) : this( maximalSize, maximalCount, expirationTime, EqualityComparer.Default ) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// /// /// Elements expiration time. /// /// /// Comparer used for comparing elements. /// /// /// is reference. /// public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime, IEqualityComparer comparer ) { if( comparer == null ) throw new ArgumentNullException( "comparer" ); if( expirationTime < MinExpirationTime ) expirationTime = MinExpirationTime; if( maximalCount < 8 ) maximalCount = 8; if( maximalSize < 8 ) maximalSize = 8; if( maximalCount > maximalSize ) maximalCount = (int) maximalSize; Comparer = comparer; m_expirationTime = expirationTime; m_maxSize = maximalSize; m_maxCount = maximalCount; Initialize(); } /// /// Add element to new generation. /// /// /// The bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// protected virtual void AddToNewGeneration( int bucketIndex, TKey key, TValue value, long size ) { // Add to newest generation if( !m_newGeneration.Set( bucketIndex, key, value, size ) ) { // Failed to add new generation RecycleGenerations(); m_newGeneration.Set( bucketIndex, key, value, size ); } m_version++; } /// /// /// Get keys bucket index. /// /// /// /// /// Key which bucket index is being retrieved. /// /// /// /// /// Bucket index. /// /// /// /// /// Method uses to calculate hash code. /// /// /// Bucket index is remainder when element key's hash value is divided by bucket count. /// /// /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2. /// /// protected virtual int GetBucketIndex( TKey key ) { return (Comparer.GetHashCode( key ) & 0x7FFFFFFF) % m_generationBucketCount; } /// /// Purge generation from the cache. /// /// /// The generation that is purged. /// protected virtual void PurgeGeneration( IGeneration generation ) { generation.Clear(); m_version++; } /// /// check expired. /// private void CheckExpired() { // Do this only one in every m_operationsBetweenTimeChecks // Fetching time is using several millisecons - it is better not to do all time. m_operationsBetweenTimeChecks--; if( m_operationsBetweenTimeChecks <= 0 ) PurgeExpired(); } /// /// Initialize cache. /// private void Initialize() { m_version++; m_generationMaxSize = MaxSize / 2; MaxElementSize = MaxSize / 8; m_generationElementCount = MaxCount / 2; // Buckets need to be prime number to get better spread of hash values m_generationBucketCount = PrimeNumberHelper.GetPrime( m_generationElementCount ); m_newGeneration = new HashGeneration( this ); m_oldGeneration = new HashGeneration( this ); m_oldGeneration.MakeOld(); } /// /// Recycle generations. /// private void RecycleGenerations() { // Rotate old generation to new generation, new generation to old generation IGeneration temp = m_newGeneration; m_newGeneration = m_oldGeneration; m_newGeneration.Clear(); m_oldGeneration = temp; m_oldGeneration.MakeOld(); } #region Nested type: Enumerator /// /// Key and value pair enumerator. /// private class Enumerator : IEnumerator> { /// /// Current enumerator. /// private int m_currentEnumerator = -1; /// /// Enumerators to different generations. /// private readonly IEnumerator>[] m_generationEnumerators = new IEnumerator>[2]; /// /// Initializes a new instance of the class. /// /// /// The cache. /// public Enumerator( CnmMemoryCache cache ) { m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator(); m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator(); } #region IEnumerator> Members /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// /// /// The enumerator has reach end of collection or is not called. /// public KeyValuePair Current { get { if( m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length ) throw new InvalidOperationException(); return m_generationEnumerators[ m_currentEnumerator ].Current; } } /// /// Gets the current element in the collection. /// /// /// The current element in the collection. /// /// /// The enumerator is positioned before the first element of the collection or after the last element. /// 2 object IEnumerator.Current { get { return Current; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { } /// /// Advances the enumerator to the next element of the collection. /// /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 public bool MoveNext() { if( m_currentEnumerator == -1 ) m_currentEnumerator = 0; while( m_currentEnumerator < m_generationEnumerators.Length ) { if( m_generationEnumerators[ m_currentEnumerator ].MoveNext() ) return true; m_currentEnumerator++; } return false; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 public void Reset() { foreach( IEnumerator> enumerator in m_generationEnumerators ) { enumerator.Reset(); } m_currentEnumerator = -1; } #endregion } #endregion #region Nested type: HashGeneration /// /// Hash generation class /// /// /// /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed /// amount of buckets and it is never rehashed. /// /// /// Read more about hash tables from Wiki article. /// /// /// private class HashGeneration : IGeneration { /// /// Value indicating whether generation was accessed since last time check. /// private bool m_accessedSinceLastTimeCheck; /// /// Index of first element's in element chain. /// /// /// -1 if there is no element in bucket; otherwise first element's index in the element chain. /// /// /// Bucket index is remainder when element key's hash value is divided by bucket count. /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2. /// private readonly int[] m_buckets; /// /// Cache object. /// private readonly CnmMemoryCache m_cache; /// /// Generation's element array. /// /// private readonly Element[] m_elements; /// /// Generation's expiration time. /// private DateTime m_expirationTime1; /// /// Index to first free element. /// private int m_firstFreeElement; /// /// Free element count. /// /// /// When generation is cleared or constructed, this is NOT set to element count. /// This is only tracking elements that are removed and are currently free. /// private int m_freeCount; /// /// Is this generation "new generation". /// private bool m_newGeneration; /// /// Next unused entry. /// private int m_nextUnusedElement; /// /// Size of data stored to generation. /// private long m_size; /// /// Initializes a new instance of the class. /// /// /// The cache. /// public HashGeneration( CnmMemoryCache cache ) { m_cache = cache; m_elements = new Element[m_cache.m_generationElementCount]; m_buckets = new int[m_cache.m_generationBucketCount]; Clear(); } /// /// Find element's index /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// Move element to front of elements. /// /// /// The previous element's index. /// /// /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation). /// private int FindElementIndex( int bucketIndex, TKey key, bool moveToFront, out int previousIndex ) { previousIndex = -1; int elementIndex = m_buckets[ bucketIndex ]; while( elementIndex >= 0 ) { if( m_cache.Comparer.Equals( key, m_elements[ elementIndex ].Key ) ) { // Found match if( moveToFront && previousIndex >= 0 ) { // Move entry to front m_elements[ previousIndex ].Next = m_elements[ elementIndex ].Next; m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ]; m_buckets[ bucketIndex ] = elementIndex; previousIndex = 0; } return elementIndex; } previousIndex = elementIndex; elementIndex = m_elements[ elementIndex ].Next; } return -1; } /// /// Remove element front the generation. /// /// /// The bucket index. /// /// /// The element index. /// /// /// The element's previous index. /// private void RemoveElement( int bucketIndex, int entryIndex, int previousIndex ) { if( previousIndex >= 0 ) m_elements[ previousIndex ].Next = m_elements[ entryIndex ].Next; else m_buckets[ bucketIndex ] = m_elements[ entryIndex ].Next; Size -= m_elements[ entryIndex ].Size; m_elements[ entryIndex ].Value = default(TValue); m_elements[ entryIndex ].Key = default(TKey); // Add element to free elements list m_elements[ entryIndex ].Next = m_firstFreeElement; m_firstFreeElement = entryIndex; m_freeCount++; } #region Nested type: Element /// /// Element that stores key, next element in chain, size and value. /// private struct Element { /// /// Element's key. /// public TKey Key; /// /// Next element in chain. /// /// /// When element have value (something is stored to it), this is index of /// next element with same bucket index. When element is free, this /// is index of next element in free element's list. /// public int Next; /// /// Size of element. /// /// /// 0 if element is free; otherwise larger than 0. /// public long Size; /// /// Element's value. /// /// /// It is possible that this value is even when element /// have value - element's value is then reference. /// public TValue Value; /// /// Gets a value indicating whether element is free or have value. /// /// /// when element is free; otherwise . /// public bool IsFree { get { return Size == 0; } } } #endregion #region Nested type: Enumerator /// /// Key value pair enumerator for object. /// private class Enumerator : IEnumerator> { /// /// Current element. /// private KeyValuePair m_current; /// /// Current index. /// private int m_currentIndex; /// /// Generation that is being enumerated. /// private readonly HashGeneration m_generation; /// /// Cache version. /// /// /// When cache is change, version number is changed. /// /// private readonly int m_version; /// /// Initializes a new instance of the class. /// /// /// The generation. /// public Enumerator( HashGeneration generation ) { m_generation = generation; m_version = m_generation.m_cache.m_version; } #region IEnumerator> Members /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// /// /// The enumerator has reach end of collection or is not called. /// public KeyValuePair Current { get { if( m_currentIndex == 0 || m_currentIndex >= m_generation.Count ) throw new InvalidOperationException(); return m_current; } } /// /// Gets the current element in the collection. /// /// /// The current element in the collection. /// /// /// The enumerator is positioned before the first element of the collection or after the last element. /// 2 object IEnumerator.Current { get { return Current; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { } /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// /// The collection was modified after the enumerator was created. /// public bool MoveNext() { if( m_version != m_generation.m_cache.m_version ) throw new InvalidOperationException(); while( m_currentIndex < m_generation.Count ) { if( m_generation.m_elements[ m_currentIndex ].IsFree ) { m_currentIndex++; continue; } m_current = new KeyValuePair( m_generation.m_elements[ m_currentIndex ].Key, m_generation.m_elements[ m_currentIndex ].Value ); m_currentIndex++; return true; } m_current = new KeyValuePair(); return false; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 public void Reset() { if( m_version != m_generation.m_cache.m_version ) throw new InvalidOperationException(); m_currentIndex = 0; } #endregion } #endregion #region IGeneration Members /// /// Gets or sets a value indicating whether generation was accessed since last time check. /// public bool AccessedSinceLastTimeCheck { get { return m_accessedSinceLastTimeCheck; } set { m_accessedSinceLastTimeCheck = value; } } /// /// Gets element count in generation. /// public int Count { get { return m_nextUnusedElement - m_freeCount; } } /// /// Gets or sets generation's expiration time. /// public DateTime ExpirationTime { get { return m_expirationTime1; } set { m_expirationTime1 = value; } } /// /// Gets or sets size of data stored to generation. /// public long Size { get { return m_size; } private set { m_size = value; } } /// /// Clear all elements from the generation and make generation new again. /// /// /// When generation is new, it is allowed to add new elements to it and /// doesn't remove elements from it. /// /// public void Clear() { for( int i = m_buckets.Length - 1 ; i >= 0 ; i-- ) { m_buckets[ i ] = -1; } Array.Clear( m_elements, 0, m_elements.Length ); Size = 0; m_firstFreeElement = -1; m_freeCount = 0; m_nextUnusedElement = 0; m_newGeneration = true; ExpirationTime = DateTime.MaxValue; } /// /// Determines whether the contains an element with the specific key. /// /// /// The bucket index for the to locate in . /// /// /// The key to locate in the . /// /// /// if the contains an element with the ; /// otherwise . /// public bool Contains( int bucketIndex, TKey key ) { int previousIndex; if( FindElementIndex( bucketIndex, key, true, out previousIndex ) == -1 ) return false; AccessedSinceLastTimeCheck = true; return true; } /// /// Returns an enumerator that iterates through the elements stored . /// /// /// A that can be used to iterate through the . /// /// 1 public IEnumerator> GetEnumerator() { return new Enumerator( this ); } /// /// Make from generation old generation. /// /// /// When generation is old, hit removes element from the generation. /// /// public void MakeOld() { m_newGeneration = false; } /// /// Remove element associated with the key from the generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// , if remove was successful; otherwise . /// public bool Remove( int bucketIndex, TKey key ) { int previousIndex; int entryIndex = FindElementIndex( bucketIndex, key, false, out previousIndex ); if( entryIndex != -1 ) { RemoveElement( bucketIndex, entryIndex, previousIndex ); AccessedSinceLastTimeCheck = true; return true; } return false; } /// /// Set or add element to generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if setting or adding was successful; otherwise . /// /// /// /// If element was already existing in generation and new element size fits to collection limits, /// then it's value is replaced with new one and size information is updated. If element didn't /// exists in generation before, then generation must have empty space for a new element and /// size must fit generation's limits, before element is added to generation. /// /// public bool Set( int bucketIndex, TKey key, TValue value, long size ) { Debug.Assert( m_newGeneration, "It is possible to insert new elements only to newest generation." ); Debug.Assert( size > 0, "New element size should be more than 0." ); int previousIndex; int elementIndex = FindElementIndex( bucketIndex, key, true, out previousIndex ); if( elementIndex == -1 ) { // New key if( Size + size > m_cache.m_generationMaxSize || (m_nextUnusedElement == m_cache.m_generationElementCount && m_freeCount == 0) ) { // Generation is full return false; } // Increase size of generation Size += size; // Get first free entry and update free entry list if( m_firstFreeElement != -1 ) { // There was entry that was removed elementIndex = m_firstFreeElement; m_firstFreeElement = m_elements[ elementIndex ].Next; m_freeCount--; } else { // No entries removed so far - just take a last one elementIndex = m_nextUnusedElement; m_nextUnusedElement++; } Debug.Assert( m_elements[ elementIndex ].IsFree, "Allocated element is not free." ); // Move new entry to front m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ]; m_buckets[ bucketIndex ] = elementIndex; // Set key and update count m_elements[ elementIndex ].Key = key; } else { // Existing key if( Size - m_elements[ elementIndex ].Size + size > m_cache.m_generationMaxSize ) { // Generation is full // Remove existing element, because generation is going to be recycled to // old generation and element is stored to new generation RemoveElement( bucketIndex, elementIndex, previousIndex ); return false; } // Update generation's size Size = Size - m_elements[ elementIndex ].Size + size; } // Finally set value and size m_elements[ elementIndex ].Value = value; m_elements[ elementIndex ].Size = size; // Success - key was inserterted to generation AccessedSinceLastTimeCheck = true; return true; } /// /// Try to get element associated with key. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if element was successful retrieved; otherwise . /// /// /// /// If element is not found from generation then and /// are set to default value (default(TValue) and 0). /// /// public bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size ) { // Find entry index, int previousIndex; int elementIndex = FindElementIndex( bucketIndex, key, m_newGeneration, out previousIndex ); if( elementIndex == -1 ) { value = default(TValue); size = 0; return false; } value = m_elements[ elementIndex ].Value; size = m_elements[ elementIndex ].Size; if( !m_newGeneration ) { // Old generation - remove element, because it is moved to new generation RemoveElement( bucketIndex, elementIndex, previousIndex ); } AccessedSinceLastTimeCheck = true; return true; } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } #endregion #region Nested type: IGeneration /// /// Cache element generation interface /// /// /// /// Generation can hold limited count of elements and limited size of data. /// /// /// There are two kind generations: "new generation" and "old generation(s)". All new elements /// are added to "new generation". /// /// protected interface IGeneration : IEnumerable> { /// /// Gets or sets a value indicating whether generation was accessed since last time check. /// bool AccessedSinceLastTimeCheck { get; set; } /// /// Gets element count in generation. /// int Count { get; } /// /// Gets or sets generation's expiration time. /// DateTime ExpirationTime { get; set; } /// /// Gets size of data stored to generation. /// long Size { get; } /// /// Clear all elements from the generation and make generation new again. /// /// /// When generation is new, it is allowed to add new elements to it and /// doesn't remove elements from it. /// /// void Clear(); /// /// Determines whether the contains an element with the specific key. /// /// /// The bucket index for the to locate in . /// /// /// The key to locate in the . /// /// /// if the contains an element with the ; /// otherwise . /// bool Contains( int bucketIndex, TKey key ); /// /// Make from generation old generation. /// /// /// When generation is old, hit removes element from the generation. /// /// void MakeOld(); /// /// Remove element associated with the key from the generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// , if remove was successful; otherwise . /// bool Remove( int bucketIndex, TKey key ); /// /// Set or add element to generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if setting or adding was successful; otherwise . /// /// /// /// If element was already existing in generation and new element size fits to collection limits, /// then it's value is replaced with new one and size information is updated. If element didn't /// exists in generation before, then generation must have empty space for a new element and /// size must fit generation's limits, before element is added to generation. /// /// bool Set( int bucketIndex, TKey key, TValue value, long size ); /// /// Try to get element associated with key. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if element was successful retrieved; otherwise . /// /// /// /// If element is not found from generation then and /// are set to default value (default(TValue) and 0). /// /// bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size ); } #endregion #region ICnmCache Members /// /// Gets current count of elements stored to . /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// public int Count { get { return m_newGeneration.Count + m_oldGeneration.Count; } } /// /// Gets or sets elements expiration time. /// /// /// Elements expiration time. /// /// /// /// When element has been stored in longer than /// and it is not accessed through method or element's value is /// not replaced by method, then it is automatically removed from the /// . /// /// /// It is possible that implementation removes element before it's expiration time, /// because total size or count of elements stored to cache is larger than or . /// /// /// It is also possible that element stays in cache longer than . /// /// /// Calling try to remove all elements that are expired. /// /// /// To disable time limit in cache, set to . /// /// /// /// /// /// /// /// /// /// public TimeSpan ExpirationTime { get { return m_expirationTime; } set { if( value < MinExpirationTime ) value = MinExpirationTime; if( m_expirationTime == value ) return; m_newGeneration.ExpirationTime = (m_newGeneration.ExpirationTime - m_expirationTime) + value; m_oldGeneration.ExpirationTime = (m_oldGeneration.ExpirationTime - m_expirationTime) + value; m_expirationTime = value; PurgeExpired(); } } /// /// Gets a value indicating whether is limiting count of elements. /// /// /// if the count of elements is limited; /// otherwise, . /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// public bool IsCountLimited { get { return true; } } /// /// Gets a value indicating whether is limiting size of elements. /// /// /// if the total size of elements is limited; /// otherwise, . /// /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// public bool IsSizeLimited { get { return true; } } /// /// Gets a value indicating whether or not access to the is synchronized (thread safe). /// /// /// if access to the is synchronized (thread safe); /// otherwise, . /// /// /// /// To get synchronized (thread safe) access to object, use /// in class /// to retrieve synchronized wrapper for object. /// /// /// /// public bool IsSynchronized { get { return false; } } /// /// Gets a value indicating whether elements stored to have limited inactivity time. /// /// /// if the has a fixed total size of elements; /// otherwise, . /// /// /// If have limited inactivity time and element is not accessed through /// or methods in , then element is automatically removed from /// the cache. Depending on implementation of the , some of the elements may /// stay longer in cache. /// /// /// /// /// public bool IsTimeLimited { get { return ExpirationTime != TimeSpan.MaxValue; } } /// /// Gets or sets maximal allowed count of elements that can be stored to . /// /// /// , if is not limited by count of elements; /// otherwise maximal allowed count of elements. /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// public int MaxCount { get { return m_maxCount; } set { if( value < 8 ) value = 8; if( m_maxCount == value ) return; m_maxCount = value; Initialize(); } } /// /// Gets maximal allowed element size. /// /// /// Maximal allowed element size. /// /// /// /// If element's size is larger than , then element is /// not added to the . /// /// /// /// /// /// public long MaxElementSize { get { return m_maxElementSize; } private set { m_maxElementSize = value; } } /// /// Gets or sets maximal allowed total size for elements stored to . /// /// /// Maximal allowed total size for elements stored to . /// /// /// /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// public long MaxSize { get { return m_maxSize; } set { if( value < 8 ) value = 8; if( m_maxSize == value ) return; m_maxSize = value; Initialize(); } } /// /// Gets total size of elements stored to . /// /// /// Total size of elements stored to . /// /// /// /// Normally bytes, but can be any suitable unit of measure. /// /// /// Element's size is given when element is added or replaced by method. /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// public long Size { get { return m_newGeneration.Size + m_oldGeneration.Size; } } /// /// Gets an object that can be used to synchronize access to the . /// /// /// An object that can be used to synchronize access to the . /// /// /// /// To get synchronized (thread safe) access to , use /// method to retrieve synchronized wrapper interface to /// . /// /// /// /// public object SyncRoot { get { return m_syncRoot; } } /// /// Removes all elements from the . /// /// /// /// /// /// public void Clear() { m_newGeneration.Clear(); m_oldGeneration.Clear(); m_oldGeneration.MakeOld(); m_version++; } /// /// Returns an enumerator that iterates through the elements stored to . /// /// /// A that can be used to iterate through the collection. /// /// 1 public IEnumerator> GetEnumerator() { return new Enumerator( this ); } /// /// Purge expired elements from the . /// /// /// /// Element becomes expired when last access time to it has been longer time than . /// /// /// Depending on implementation, some of expired elements /// may stay longer than in the cache. /// /// /// /// /// /// /// /// /// public void PurgeExpired() { m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks; if( !IsTimeLimited ) return; DateTime now = DateTime.Now; if( m_newGeneration.AccessedSinceLastTimeCheck ) { // New generation has been accessed since last check // Update it's expiration time. m_newGeneration.ExpirationTime = now + ExpirationTime; m_newGeneration.AccessedSinceLastTimeCheck = false; } else if( m_newGeneration.ExpirationTime < now ) { // New generation has been expired. // --> also old generation must be expired. PurgeGeneration( m_newGeneration ); PurgeGeneration( m_oldGeneration ); return; } if( m_oldGeneration.ExpirationTime < now ) PurgeGeneration( m_oldGeneration ); } /// /// Removes element associated with from the . /// /// /// The key that is associated with element to remove from the . /// /// /// is . /// /// /// /// /// /// public void Remove( TKey key ) { if( key == null ) throw new ArgumentNullException( "key" ); int bucketIndex = GetBucketIndex( key ); if( !m_newGeneration.Remove( bucketIndex, key ) ) { if( !m_oldGeneration.Remove( bucketIndex, key ) ) { CheckExpired(); return; } } CheckExpired(); m_version++; } /// /// Removes elements that are associated with one of from the . /// /// /// The keys that are associated with elements to remove from the . /// /// /// is . /// /// /// /// /// /// public void RemoveRange( IEnumerable keys ) { if( keys == null ) throw new ArgumentNullException( "keys" ); foreach( TKey key in keys ) { if( key == null ) continue; int bucketIndex = GetBucketIndex( key ); if( !m_newGeneration.Remove( bucketIndex, key ) ) m_oldGeneration.Remove( bucketIndex, key ); } CheckExpired(); m_version++; } /// /// Add or replace an element with the provided , and to /// . /// /// /// The object used as the key of the element. Can't be reference. /// /// /// The object used as the value of the element to add or replace. is allowed. /// /// /// The element's size. Normally bytes, but can be any suitable unit of measure. /// /// /// if element has been added successfully to the ; /// otherwise . /// /// /// is . /// /// /// The element's is less than 0. /// /// /// /// If element's is larger than , then element is /// not added to the , however - possible older element is /// removed from the . /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// /// /// public bool Set( TKey key, TValue value, long size ) { if( key == null ) throw new ArgumentNullException( "key" ); if( size < 0 ) throw new ArgumentOutOfRangeException( "size", size, "Value's size can't be less than 0." ); if( size > MaxElementSize ) { // Entry size is too big to fit cache - ignore it Remove( key ); return false; } if( size == 0 ) size = 1; int bucketIndex = GetBucketIndex( key ); m_oldGeneration.Remove( bucketIndex, key ); AddToNewGeneration( bucketIndex, key, value, size ); CheckExpired(); return true; } /// /// Gets the associated with the specified . /// /// /// if the contains an element with /// the specified key; otherwise, . /// /// /// The key whose to get. /// /// /// When this method returns, the value associated with the specified , /// if the is found; otherwise, the /// default value for the type of the parameter. This parameter is passed uninitialized. /// /// /// is . /// /// /// /// /// /// public bool TryGetValue( TKey key, out TValue value ) { if( key == null ) throw new ArgumentNullException( "key" ); int bucketIndex = GetBucketIndex( key ); long size; if( m_newGeneration.TryGetValue( bucketIndex, key, out value, out size ) ) { CheckExpired(); return true; } if( m_oldGeneration.TryGetValue( bucketIndex, key, out value, out size ) ) { // Move element to new generation AddToNewGeneration( bucketIndex, key, value, size ); CheckExpired(); return true; } CheckExpired(); return false; } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } }