From 007016ecd2fabf0bbe789ac6fc0ab6f827ff90b7 Mon Sep 17 00:00:00 2001 From: Jeff Ames Date: Thu, 4 Jun 2009 00:51:02 +0000 Subject: Update svn properties. --- OpenSim/Framework/CnmMemoryCache.cs | 3704 ++++++++++---------- OpenSim/Framework/CnmSynchronizedCache.cs | 1492 ++++---- OpenSim/Framework/ICnmCache.cs | 882 ++--- OpenSim/Framework/PrimeNumberHelper.cs | 196 +- .../Region/CoreModules/Asset/CenomeAssetCache.cs | 764 ++-- 5 files changed, 3519 insertions(+), 3519 deletions(-) diff --git a/OpenSim/Framework/CnmMemoryCache.cs b/OpenSim/Framework/CnmMemoryCache.cs index 6ca71ec..8c25da0 100644 --- a/OpenSim/Framework/CnmMemoryCache.cs +++ b/OpenSim/Framework/CnmMemoryCache.cs @@ -1,1852 +1,1852 @@ -// -------------------------------------------------------------------------------------------------------------------- -// <copyright company="" file="CnmMemoryCache.cs"> -// -// </copyright> -// <summary> -// -// </summary> -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace OpenSim.Framework -{ - /// <summary> - /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size. - /// </summary> - /// <typeparam name="TKey"> - /// The type of keys in the cache. - /// </typeparam> - /// <typeparam name="TValue"> - /// The type of values in the dictionary. - /// </typeparam> - /// <remarks> - /// <para> - /// 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 <see cref="TryGetValue"/> method, they are moved to newest generation. - /// </para> - /// </remarks> - public class CnmMemoryCache<TKey, TValue> : ICnmCache<TKey, TValue> - { - /// <summary> - /// Default maximal count. - /// </summary> - /// <seealso cref="MaxCount"/> - public const int DefaultMaxCount = 4096; - - /// <summary> - /// Default maximal size. - /// </summary> - /// <remarks> - /// <para> - /// 128MB = 128 * 1024^2 = 134 217 728 bytes. - /// </para> - /// </remarks> - /// <seealso cref="MaxSize"/> - public const long DefaultMaxSize = 134217728; - - /// <summary> - /// How many operations between time checks. - /// </summary> - private const int DefaultOperationsBetweenTimeChecks = 40; - - /// <summary> - /// Default expiration time. - /// </summary> - /// <remarks> - /// <para> - /// 30 minutes. - /// </para> - /// </remarks> - public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 ); - - /// <summary> - /// Minimal allowed expiration time. - /// </summary> - /// <remarks> - /// <para> - /// 5 minutes. - /// </para> - /// </remarks> - public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds( 10.0 ); - - /// <summary> - /// Comparer used to compare element keys. - /// </summary> - /// <remarks> - /// Comparer is initialized by constructor. - /// </remarks> - /// <seealso cref="CnmMemoryCache{TKey,TValue}"/> - public readonly IEqualityComparer<TKey> Comparer; - - /// <summary> - /// Expiration time. - /// </summary> - private TimeSpan m_expirationTime = DefaultExpirationTime; - - /// <summary> - /// Generation bucket count. - /// </summary> - private int m_generationBucketCount; - - /// <summary> - /// Generation entry count. - /// </summary> - private int m_generationElementCount; - - /// <summary> - /// Generation max size. - /// </summary> - private long m_generationMaxSize; - - /// <summary> - /// Maximal allowed count of elements. - /// </summary> - private int m_maxCount; - - /// <summary> - /// Maximal allowed total size of elements. - /// </summary> - private long m_maxElementSize; - - /// <summary> - /// Maximal size. - /// </summary> - private long m_maxSize; - - /// <summary> - /// New generation. - /// </summary> - private IGeneration m_newGeneration; - - /// <summary> - /// Old generation. - /// </summary> - private IGeneration m_oldGeneration; - - /// <summary> - /// Operations between time check. - /// </summary> - private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks; - - /// <summary> - /// Synchronization root object, should always be private and exists always - /// </summary> - private readonly object m_syncRoot = new object(); - - /// <summary> - /// Version of cache. - /// </summary> - /// <remarks> - /// <para> - /// Updated every time when cache has been changed (element removed, expired, added, replaced). - /// </para> - /// </remarks> - private int m_version; - - /// <summary> - /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. - /// </summary> - public CnmMemoryCache() - : this( DefaultMaxSize ) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. - /// </summary> - /// <param name="maximalSize"> - /// Maximal cache size. - /// </param> - public CnmMemoryCache( long maximalSize ) - : this( maximalSize, DefaultMaxCount ) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. - /// </summary> - /// <param name="maximalSize"> - /// Maximal cache size. - /// </param> - /// <param name="maximalCount"> - /// Maximal element count. - /// </param> - public CnmMemoryCache( long maximalSize, int maximalCount ) - : this( maximalSize, maximalCount, DefaultExpirationTime ) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. - /// </summary> - /// <param name="maximalSize"> - /// Maximal cache size. - /// </param> - /// <param name="maximalCount"> - /// Maximal element count. - /// </param> - /// <param name="expirationTime"> - /// Elements expiration time. - /// </param> - public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime ) - : this( maximalSize, maximalCount, expirationTime, EqualityComparer<TKey>.Default ) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. - /// </summary> - /// <param name="maximalSize"> - /// Maximal cache size. - /// </param> - /// <param name="maximalCount"> - /// Maximal element count. - /// </param> - /// <param name="expirationTime"> - /// Elements expiration time. - /// </param> - /// <param name="comparer"> - /// Comparer used for comparing elements. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <see cref="comparer"/>is <see langword="null"/> reference. - /// </exception> - public CnmMemoryCache( long maximalSize, - int maximalCount, - TimeSpan expirationTime, - IEqualityComparer<TKey> 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(); - } - - /// <summary> - /// Add element to new generation. - /// </summary> - /// <param name="bucketIndex"> - /// The bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="value"> - /// The element's value. - /// </param> - /// <param name="size"> - /// The element's size. - /// </param> - 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++; - } - - /// <summary> - /// <para> - /// Get keys bucket index. - /// </para> - /// </summary> - /// <param name="key"> - /// <para> - /// Key which bucket index is being retrieved. - /// </para> - /// </param> - /// <returns> - /// <para> - /// Bucket index. - /// </para> - /// </returns> - /// <remarks> - /// <para> - /// Method uses <see cref="Comparer"/> to calculate <see cref="key"/> hash code. - /// </para> - /// <para> - /// Bucket index is remainder when element key's hash value is divided by bucket count. - /// </para> - /// <para> - /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2. - /// </para> - /// </remarks> - protected virtual int GetBucketIndex( TKey key ) - { - return (Comparer.GetHashCode( key ) & 0x7FFFFFFF) % m_generationBucketCount; - } - - /// <summary> - /// Purge generation from the cache. - /// </summary> - /// <param name="generation"> - /// The generation that is purged. - /// </param> - protected virtual void PurgeGeneration( IGeneration generation ) - { - generation.Clear(); - m_version++; - } - - /// <summary> - /// check expired. - /// </summary> - 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(); - } - - /// <summary> - /// Initialize cache. - /// </summary> - 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(); - } - - /// <summary> - /// Recycle generations. - /// </summary> - 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 - - /// <summary> - /// Key and value pair enumerator. - /// </summary> - private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> - { - /// <summary> - /// Current enumerator. - /// </summary> - private int m_currentEnumerator = -1; - - /// <summary> - /// Enumerators to different generations. - /// </summary> - private readonly IEnumerator<KeyValuePair<TKey, TValue>>[] m_generationEnumerators = - new IEnumerator<KeyValuePair<TKey, TValue>>[2]; - - /// <summary> - /// Initializes a new instance of the <see cref="Enumerator"/> class. - /// </summary> - /// <param name="cache"> - /// The cache. - /// </param> - public Enumerator( CnmMemoryCache<TKey, TValue> cache ) - { - m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator(); - m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator(); - } - - #region IEnumerator<KeyValuePair<TKey,TValue>> Members - - /// <summary> - /// Gets the element in the collection at the current position of the enumerator. - /// </summary> - /// <returns> - /// The element in the collection at the current position of the enumerator. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. - /// </exception> - public KeyValuePair<TKey, TValue> Current - { - get - { - if( m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length ) - throw new InvalidOperationException(); - - return m_generationEnumerators[ m_currentEnumerator ].Current; - } - } - - /// <summary> - /// Gets the current element in the collection. - /// </summary> - /// <returns> - /// The current element in the collection. - /// </returns> - /// <exception cref="T:System.InvalidOperationException"> - /// The enumerator is positioned before the first element of the collection or after the last element. - /// </exception><filterpriority>2</filterpriority> - object IEnumerator.Current - { - get { return Current; } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - /// <filterpriority>2</filterpriority> - public void Dispose() - { - } - - /// <summary> - /// Advances the enumerator to the next element of the collection. - /// </summary> - /// <returns> - /// <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. - /// </returns> - /// <exception cref="T:System.InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - /// <filterpriority>2</filterpriority> - 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; - } - - /// <summary> - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// </summary> - /// <exception cref="T:System.InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - /// <filterpriority>2</filterpriority> - public void Reset() - { - foreach( IEnumerator<KeyValuePair<TKey, TValue>> enumerator in m_generationEnumerators ) - { - enumerator.Reset(); - } - - m_currentEnumerator = -1; - } - - #endregion - } - - #endregion - - #region Nested type: HashGeneration - - /// <summary> - /// Hash generation class - /// </summary> - /// <remarks> - /// <para> - /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed - /// amount of buckets and it is never rehashed. - /// </para> - /// <para> - /// Read more about hash tables from <a href="http://en.wikipedia.org/wiki/Hash_table">Wiki article</a>. - /// </para> - /// </remarks> - /// <seealso href="http://en.wikipedia.org/wiki/Hash_table"/> - private class HashGeneration : IGeneration - { - /// <summary> - /// Value indicating whether generation was accessed since last time check. - /// </summary> - private bool m_accessedSinceLastTimeCheck; - - /// <summary> - /// Index of first element's in element chain. - /// </summary> - /// <value> - /// -1 if there is no element in bucket; otherwise first element's index in the element chain. - /// </value> - /// <remarks> - /// 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. - /// </remarks> - private readonly int[] m_buckets; - - /// <summary> - /// Cache object. - /// </summary> - private readonly CnmMemoryCache<TKey, TValue> m_cache; - - /// <summary> - /// Generation's element array. - /// </summary> - /// <seealso cref="Element"/> - private readonly Element[] m_elements; - - /// <summary> - /// Generation's expiration time. - /// </summary> - private DateTime m_expirationTime1; - - /// <summary> - /// Index to first free element. - /// </summary> - private int m_firstFreeElement; - - /// <summary> - /// Free element count. - /// </summary> - /// <remarks> - /// 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. - /// </remarks> - private int m_freeCount; - - /// <summary> - /// Is this generation "new generation". - /// </summary> - private bool m_newGeneration; - - /// <summary> - /// Next unused entry. - /// </summary> - private int m_nextUnusedElement; - - /// <summary> - /// Size of data stored to generation. - /// </summary> - private long m_size; - - /// <summary> - /// Initializes a new instance of the <see cref="HashGeneration"/> class. - /// </summary> - /// <param name="cache"> - /// The cache. - /// </param> - public HashGeneration( CnmMemoryCache<TKey, TValue> cache ) - { - m_cache = cache; - m_elements = new Element[m_cache.m_generationElementCount]; - m_buckets = new int[m_cache.m_generationBucketCount]; - Clear(); - } - - /// <summary> - /// Find element's index - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="moveToFront"> - /// Move element to front of elements. - /// </param> - /// <param name="previousIndex"> - /// The previous element's index. - /// </param> - /// <returns> - /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation). - /// </returns> - 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; - } - - /// <summary> - /// Remove element front the generation. - /// </summary> - /// <param name="bucketIndex"> - /// The bucket index. - /// </param> - /// <param name="entryIndex"> - /// The element index. - /// </param> - /// <param name="previousIndex"> - /// The element's previous index. - /// </param> - 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 - - /// <summary> - /// Element that stores key, next element in chain, size and value. - /// </summary> - private struct Element - { - /// <summary> - /// Element's key. - /// </summary> - public TKey Key; - - /// <summary> - /// Next element in chain. - /// </summary> - /// <remarks> - /// 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. - /// </remarks> - public int Next; - - /// <summary> - /// Size of element. - /// </summary> - /// <value> - /// 0 if element is free; otherwise larger than 0. - /// </value> - public long Size; - - /// <summary> - /// Element's value. - /// </summary> - /// <remarks> - /// It is possible that this value is <see langword="null"/> even when element - /// have value - element's value is then <see langword="null"/> reference. - /// </remarks> - public TValue Value; - - /// <summary> - /// Gets a value indicating whether element is free or have value. - /// </summary> - /// <value> - /// <see langword="true"/> when element is free; otherwise <see langword="false"/>. - /// </value> - public bool IsFree - { - get { return Size == 0; } - } - } - - #endregion - - #region Nested type: Enumerator - - /// <summary> - /// Key value pair enumerator for <see cref="HashGeneration"/> object. - /// </summary> - private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> - { - /// <summary> - /// Current element. - /// </summary> - private KeyValuePair<TKey, TValue> m_current; - - /// <summary> - /// Current index. - /// </summary> - private int m_currentIndex; - - /// <summary> - /// Generation that is being enumerated. - /// </summary> - private readonly HashGeneration m_generation; - - /// <summary> - /// Cache version. - /// </summary> - /// <remarks> - /// When cache is change, version number is changed. - /// </remarks> - /// <seealso cref="CnmMemoryCache{TKey,TValue}.m_version"/> - private readonly int m_version; - - /// <summary> - /// Initializes a new instance of the <see cref="Enumerator"/> class. - /// </summary> - /// <param name="generation"> - /// The generation. - /// </param> - public Enumerator( HashGeneration generation ) - { - m_generation = generation; - m_version = m_generation.m_cache.m_version; - } - - #region IEnumerator<KeyValuePair<TKey,TValue>> Members - - /// <summary> - /// Gets the element in the collection at the current position of the enumerator. - /// </summary> - /// <returns> - /// The element in the collection at the current position of the enumerator. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. - /// </exception> - public KeyValuePair<TKey, TValue> Current - { - get - { - if( m_currentIndex == 0 || m_currentIndex >= m_generation.Count ) - throw new InvalidOperationException(); - - return m_current; - } - } - - /// <summary> - /// Gets the current element in the collection. - /// </summary> - /// <returns> - /// The current element in the collection. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The enumerator is positioned before the first element of the collection or after the last element. - /// </exception><filterpriority>2</filterpriority> - object IEnumerator.Current - { - get { return Current; } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - /// <filterpriority>2</filterpriority> - public void Dispose() - { - } - - /// <summary> - /// Advances the enumerator to the next element of the collection. - /// </summary> - /// <returns> - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - 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<TKey, TValue>( m_generation.m_elements[ m_currentIndex ].Key, - m_generation.m_elements[ m_currentIndex ].Value ); - m_currentIndex++; - return true; - } - - m_current = new KeyValuePair<TKey, TValue>(); - return false; - } - - /// <summary> - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - /// <filterpriority>2</filterpriority> - public void Reset() - { - if( m_version != m_generation.m_cache.m_version ) - throw new InvalidOperationException(); - - m_currentIndex = 0; - } - - #endregion - } - - #endregion - - #region IGeneration Members - - /// <summary> - /// Gets or sets a value indicating whether generation was accessed since last time check. - /// </summary> - public bool AccessedSinceLastTimeCheck - { - get { return m_accessedSinceLastTimeCheck; } - - set { m_accessedSinceLastTimeCheck = value; } - } - - /// <summary> - /// Gets element count in generation. - /// </summary> - public int Count - { - get { return m_nextUnusedElement - m_freeCount; } - } - - /// <summary> - /// Gets or sets generation's expiration time. - /// </summary> - public DateTime ExpirationTime - { - get { return m_expirationTime1; } - - set { m_expirationTime1 = value; } - } - - /// <summary> - /// Gets or sets size of data stored to generation. - /// </summary> - public long Size - { - get { return m_size; } - - private set { m_size = value; } - } - - /// <summary> - /// Clear all elements from the generation and make generation new again. - /// </summary> - /// <remarks> - /// When generation is new, it is allowed to add new elements to it and - /// <see cref="IGeneration.TryGetValue"/>doesn't remove elements from it. - /// </remarks> - /// <seealso cref="IGeneration.MakeOld"/> - 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; - } - - /// <summary> - /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key. - /// </summary> - /// <param name="bucketIndex"> - /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>. - /// </param> - /// <param name="key"> - /// The key to locate in the <see cref="IGeneration"/>. - /// </param> - /// <returns> - /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>; - /// otherwise <see langword="false"/>. - /// </returns> - public bool Contains( int bucketIndex, TKey key ) - { - int previousIndex; - if( FindElementIndex( bucketIndex, key, true, out previousIndex ) == -1 ) - return false; - - AccessedSinceLastTimeCheck = true; - return true; - } - - /// <summary> - /// Returns an enumerator that iterates through the elements stored <see cref="HashGeneration"/>. - /// </summary> - /// <returns> - /// A <see cref="IEnumerator"/> that can be used to iterate through the <see cref="HashGeneration"/>. - /// </returns> - /// <filterpriority>1</filterpriority> - public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() - { - return new Enumerator( this ); - } - - /// <summary> - /// Make from generation old generation. - /// </summary> - /// <remarks> - /// When generation is old, <see cref="IGeneration.TryGetValue"/> hit removes element from the generation. - /// </remarks> - /// <seealso cref="IGeneration.Clear"/> - public void MakeOld() - { - m_newGeneration = false; - } - - /// <summary> - /// Remove element associated with the key from the generation. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <returns> - /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>. - /// </returns> - 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; - } - - /// <summary> - /// Set or add element to generation. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="value"> - /// The element's value. - /// </param> - /// <param name="size"> - /// The element's size. - /// </param> - /// <returns> - /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>. - /// </returns> - /// <remarks> - /// <para> - /// 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. - /// </para> - /// </remarks> - 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; - } - - /// <summary> - /// Try to get element associated with key. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="value"> - /// The element's value. - /// </param> - /// <param name="size"> - /// The element's size. - /// </param> - /// <returns> - /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>. - /// </returns> - /// <remarks> - /// <para> - /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/> - /// are set to default value (default(TValue) and 0). - /// </para> - /// </remarks> - 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; - } - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - /// <filterpriority>2</filterpriority> - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } - - #endregion - - #region Nested type: IGeneration - - /// <summary> - /// Cache element generation interface - /// </summary> - /// <remarks> - /// <para> - /// Generation can hold limited count of elements and limited size of data. - /// </para> - /// <para> - /// There are two kind generations: "new generation" and "old generation(s)". All new elements - /// are added to "new generation". - /// </para> - /// </remarks> - protected interface IGeneration : IEnumerable<KeyValuePair<TKey, TValue>> - { - /// <summary> - /// Gets or sets a value indicating whether generation was accessed since last time check. - /// </summary> - bool AccessedSinceLastTimeCheck { get; set; } - - /// <summary> - /// Gets element count in generation. - /// </summary> - int Count { get; } - - /// <summary> - /// Gets or sets generation's expiration time. - /// </summary> - DateTime ExpirationTime { get; set; } - - /// <summary> - /// Gets size of data stored to generation. - /// </summary> - long Size { get; } - - /// <summary> - /// Clear all elements from the generation and make generation new again. - /// </summary> - /// <remarks> - /// When generation is new, it is allowed to add new elements to it and - /// <see cref="TryGetValue"/>doesn't remove elements from it. - /// </remarks> - /// <seealso cref="MakeOld"/> - void Clear(); - - /// <summary> - /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key. - /// </summary> - /// <param name="bucketIndex"> - /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>. - /// </param> - /// <param name="key"> - /// The key to locate in the <see cref="IGeneration"/>. - /// </param> - /// <returns> - /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>; - /// otherwise <see langword="false"/>. - /// </returns> - bool Contains( int bucketIndex, TKey key ); - - /// <summary> - /// Make from generation old generation. - /// </summary> - /// <remarks> - /// When generation is old, <see cref="TryGetValue"/> hit removes element from the generation. - /// </remarks> - /// <seealso cref="Clear"/> - void MakeOld(); - - /// <summary> - /// Remove element associated with the key from the generation. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <returns> - /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>. - /// </returns> - bool Remove( int bucketIndex, TKey key ); - - /// <summary> - /// Set or add element to generation. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="value"> - /// The element's value. - /// </param> - /// <param name="size"> - /// The element's size. - /// </param> - /// <returns> - /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>. - /// </returns> - /// <remarks> - /// <para> - /// 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. - /// </para> - /// </remarks> - bool Set( int bucketIndex, TKey key, TValue value, long size ); - - /// <summary> - /// Try to get element associated with key. - /// </summary> - /// <param name="bucketIndex"> - /// The element's bucket index. - /// </param> - /// <param name="key"> - /// The element's key. - /// </param> - /// <param name="value"> - /// The element's value. - /// </param> - /// <param name="size"> - /// The element's size. - /// </param> - /// <returns> - /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>. - /// </returns> - /// <remarks> - /// <para> - /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/> - /// are set to default value (default(TValue) and 0). - /// </para> - /// </remarks> - bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size ); - } - - #endregion - - #region ICnmCache<TKey,TValue> Members - - /// <summary> - /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public int Count - { - get { return m_newGeneration.Count + m_oldGeneration.Count; } - } - - /// <summary> - /// Gets or sets elements expiration time. - /// </summary> - /// <value> - /// Elements expiration time. - /// </value> - /// <remarks> - /// <para> - /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is - /// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, - /// 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"/>. - /// </para> - /// <para> - /// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. - /// </para> - /// <para> - /// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired. - /// </para> - /// <para> - /// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - 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(); - } - } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public bool IsCountLimited - { - get { return true; } - } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public bool IsSizeLimited - { - get { return true; } - } - - /// <summary> - /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). - /// </summary> - /// <value> - /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use - /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class - /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - public bool IsSynchronized - { - get { return false; } - } - - /// <summary> - /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/> - /// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from - /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may - /// stay longer in cache. - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - public bool IsTimeLimited - { - get { return ExpirationTime != TimeSpan.MaxValue; } - } - - /// <summary> - /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; - /// otherwise maximal allowed count of elements. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - public int MaxCount - { - get { return m_maxCount; } - - set - { - if( value < 8 ) - value = 8; - if( m_maxCount == value ) - return; - - m_maxCount = value; - Initialize(); - } - } - - /// <summary> - /// <para>Gets maximal allowed element size.</para> - /// </summary> - /// <value> - /// Maximal allowed element size. - /// </value> - /// <remarks> - /// <para> - /// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - public long MaxElementSize - { - get { return m_maxElementSize; } - - private set { m_maxElementSize = value; } - } - - /// <summary> - /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - public long MaxSize - { - get { return m_maxSize; } - - set - { - if( value < 8 ) - value = 8; - if( m_maxSize == value ) - return; - - m_maxSize = value; - Initialize(); - } - } - - /// <summary> - /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally bytes, but can be any suitable unit of measure. - /// </para> - /// <para> - /// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - public long Size - { - get { return m_newGeneration.Size + m_oldGeneration.Size; } - } - - /// <summary> - /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> - /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - public object SyncRoot - { - get { return m_syncRoot; } - } - - /// <summary> - /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public void Clear() - { - m_newGeneration.Clear(); - m_oldGeneration.Clear(); - m_oldGeneration.MakeOld(); - m_version++; - } - - /// <summary> - /// Returns an enumerator that iterates through the elements stored to <see cref="CnmMemoryCache{TKey,TValue}"/>. - /// </summary> - /// <returns> - /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection. - /// </returns> - /// <filterpriority>1</filterpriority> - public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() - { - return new Enumerator( this ); - } - - /// <summary> - /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. - /// </para> - /// <para> - /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements - /// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - 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 ); - } - - /// <summary> - /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - 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++; - } - - /// <summary> - /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="keys"> - /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="keys"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public void RemoveRange( IEnumerable<TKey> 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++; - } - - /// <summary> - /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The object used as the key of the element. Can't be <see langword="null"/> reference. - /// </param> - /// <param name="value"> - /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. - /// </param> - /// <param name="size"> - /// The element's size. Normally bytes, but can be any suitable unit of measure. - /// </param> - /// <returns> - /// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; - /// otherwise <see langword="false"/>. - /// </returns> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <exception cref="ArgumentOutOfRangeException"> - /// The element's <paramref name="size"/> is less than 0. - /// </exception> - /// <remarks> - /// <para> - /// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is - /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - 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; - } - - /// <summary> - /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. - /// </summary> - /// <returns> - /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with - /// the specified key; otherwise, <see langword="false"/>. - /// </returns> - /// <param name="key"> - /// The key whose <paramref name="value"/> to get. - /// </param> - /// <param name="value"> - /// When this method returns, the value associated with the specified <paramref name="key"/>, - /// if the <paramref name="key"/> is found; otherwise, the - /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - 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; - } - - /// <summary> - /// Returns an enumerator that iterates through a collection. - /// </summary> - /// <returns> - /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. - /// </returns> - /// <filterpriority>2</filterpriority> - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} +// -------------------------------------------------------------------------------------------------------------------- +// <copyright company="" file="CnmMemoryCache.cs"> +// +// </copyright> +// <summary> +// +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenSim.Framework +{ + /// <summary> + /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size. + /// </summary> + /// <typeparam name="TKey"> + /// The type of keys in the cache. + /// </typeparam> + /// <typeparam name="TValue"> + /// The type of values in the dictionary. + /// </typeparam> + /// <remarks> + /// <para> + /// 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 <see cref="TryGetValue"/> method, they are moved to newest generation. + /// </para> + /// </remarks> + public class CnmMemoryCache<TKey, TValue> : ICnmCache<TKey, TValue> + { + /// <summary> + /// Default maximal count. + /// </summary> + /// <seealso cref="MaxCount"/> + public const int DefaultMaxCount = 4096; + + /// <summary> + /// Default maximal size. + /// </summary> + /// <remarks> + /// <para> + /// 128MB = 128 * 1024^2 = 134 217 728 bytes. + /// </para> + /// </remarks> + /// <seealso cref="MaxSize"/> + public const long DefaultMaxSize = 134217728; + + /// <summary> + /// How many operations between time checks. + /// </summary> + private const int DefaultOperationsBetweenTimeChecks = 40; + + /// <summary> + /// Default expiration time. + /// </summary> + /// <remarks> + /// <para> + /// 30 minutes. + /// </para> + /// </remarks> + public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 ); + + /// <summary> + /// Minimal allowed expiration time. + /// </summary> + /// <remarks> + /// <para> + /// 5 minutes. + /// </para> + /// </remarks> + public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds( 10.0 ); + + /// <summary> + /// Comparer used to compare element keys. + /// </summary> + /// <remarks> + /// Comparer is initialized by constructor. + /// </remarks> + /// <seealso cref="CnmMemoryCache{TKey,TValue}"/> + public readonly IEqualityComparer<TKey> Comparer; + + /// <summary> + /// Expiration time. + /// </summary> + private TimeSpan m_expirationTime = DefaultExpirationTime; + + /// <summary> + /// Generation bucket count. + /// </summary> + private int m_generationBucketCount; + + /// <summary> + /// Generation entry count. + /// </summary> + private int m_generationElementCount; + + /// <summary> + /// Generation max size. + /// </summary> + private long m_generationMaxSize; + + /// <summary> + /// Maximal allowed count of elements. + /// </summary> + private int m_maxCount; + + /// <summary> + /// Maximal allowed total size of elements. + /// </summary> + private long m_maxElementSize; + + /// <summary> + /// Maximal size. + /// </summary> + private long m_maxSize; + + /// <summary> + /// New generation. + /// </summary> + private IGeneration m_newGeneration; + + /// <summary> + /// Old generation. + /// </summary> + private IGeneration m_oldGeneration; + + /// <summary> + /// Operations between time check. + /// </summary> + private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks; + + /// <summary> + /// Synchronization root object, should always be private and exists always + /// </summary> + private readonly object m_syncRoot = new object(); + + /// <summary> + /// Version of cache. + /// </summary> + /// <remarks> + /// <para> + /// Updated every time when cache has been changed (element removed, expired, added, replaced). + /// </para> + /// </remarks> + private int m_version; + + /// <summary> + /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. + /// </summary> + public CnmMemoryCache() + : this( DefaultMaxSize ) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. + /// </summary> + /// <param name="maximalSize"> + /// Maximal cache size. + /// </param> + public CnmMemoryCache( long maximalSize ) + : this( maximalSize, DefaultMaxCount ) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. + /// </summary> + /// <param name="maximalSize"> + /// Maximal cache size. + /// </param> + /// <param name="maximalCount"> + /// Maximal element count. + /// </param> + public CnmMemoryCache( long maximalSize, int maximalCount ) + : this( maximalSize, maximalCount, DefaultExpirationTime ) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. + /// </summary> + /// <param name="maximalSize"> + /// Maximal cache size. + /// </param> + /// <param name="maximalCount"> + /// Maximal element count. + /// </param> + /// <param name="expirationTime"> + /// Elements expiration time. + /// </param> + public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime ) + : this( maximalSize, maximalCount, expirationTime, EqualityComparer<TKey>.Default ) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class. + /// </summary> + /// <param name="maximalSize"> + /// Maximal cache size. + /// </param> + /// <param name="maximalCount"> + /// Maximal element count. + /// </param> + /// <param name="expirationTime"> + /// Elements expiration time. + /// </param> + /// <param name="comparer"> + /// Comparer used for comparing elements. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <see cref="comparer"/>is <see langword="null"/> reference. + /// </exception> + public CnmMemoryCache( long maximalSize, + int maximalCount, + TimeSpan expirationTime, + IEqualityComparer<TKey> 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(); + } + + /// <summary> + /// Add element to new generation. + /// </summary> + /// <param name="bucketIndex"> + /// The bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="value"> + /// The element's value. + /// </param> + /// <param name="size"> + /// The element's size. + /// </param> + 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++; + } + + /// <summary> + /// <para> + /// Get keys bucket index. + /// </para> + /// </summary> + /// <param name="key"> + /// <para> + /// Key which bucket index is being retrieved. + /// </para> + /// </param> + /// <returns> + /// <para> + /// Bucket index. + /// </para> + /// </returns> + /// <remarks> + /// <para> + /// Method uses <see cref="Comparer"/> to calculate <see cref="key"/> hash code. + /// </para> + /// <para> + /// Bucket index is remainder when element key's hash value is divided by bucket count. + /// </para> + /// <para> + /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2. + /// </para> + /// </remarks> + protected virtual int GetBucketIndex( TKey key ) + { + return (Comparer.GetHashCode( key ) & 0x7FFFFFFF) % m_generationBucketCount; + } + + /// <summary> + /// Purge generation from the cache. + /// </summary> + /// <param name="generation"> + /// The generation that is purged. + /// </param> + protected virtual void PurgeGeneration( IGeneration generation ) + { + generation.Clear(); + m_version++; + } + + /// <summary> + /// check expired. + /// </summary> + 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(); + } + + /// <summary> + /// Initialize cache. + /// </summary> + 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(); + } + + /// <summary> + /// Recycle generations. + /// </summary> + 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 + + /// <summary> + /// Key and value pair enumerator. + /// </summary> + private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> + { + /// <summary> + /// Current enumerator. + /// </summary> + private int m_currentEnumerator = -1; + + /// <summary> + /// Enumerators to different generations. + /// </summary> + private readonly IEnumerator<KeyValuePair<TKey, TValue>>[] m_generationEnumerators = + new IEnumerator<KeyValuePair<TKey, TValue>>[2]; + + /// <summary> + /// Initializes a new instance of the <see cref="Enumerator"/> class. + /// </summary> + /// <param name="cache"> + /// The cache. + /// </param> + public Enumerator( CnmMemoryCache<TKey, TValue> cache ) + { + m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator(); + m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator(); + } + + #region IEnumerator<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Gets the element in the collection at the current position of the enumerator. + /// </summary> + /// <returns> + /// The element in the collection at the current position of the enumerator. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. + /// </exception> + public KeyValuePair<TKey, TValue> Current + { + get + { + if( m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length ) + throw new InvalidOperationException(); + + return m_generationEnumerators[ m_currentEnumerator ].Current; + } + } + + /// <summary> + /// Gets the current element in the collection. + /// </summary> + /// <returns> + /// The current element in the collection. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The enumerator is positioned before the first element of the collection or after the last element. + /// </exception><filterpriority>2</filterpriority> + object IEnumerator.Current + { + get { return Current; } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + /// <filterpriority>2</filterpriority> + public void Dispose() + { + } + + /// <summary> + /// Advances the enumerator to the next element of the collection. + /// </summary> + /// <returns> + /// <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. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + /// <filterpriority>2</filterpriority> + 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; + } + + /// <summary> + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + /// <filterpriority>2</filterpriority> + public void Reset() + { + foreach( IEnumerator<KeyValuePair<TKey, TValue>> enumerator in m_generationEnumerators ) + { + enumerator.Reset(); + } + + m_currentEnumerator = -1; + } + + #endregion + } + + #endregion + + #region Nested type: HashGeneration + + /// <summary> + /// Hash generation class + /// </summary> + /// <remarks> + /// <para> + /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed + /// amount of buckets and it is never rehashed. + /// </para> + /// <para> + /// Read more about hash tables from <a href="http://en.wikipedia.org/wiki/Hash_table">Wiki article</a>. + /// </para> + /// </remarks> + /// <seealso href="http://en.wikipedia.org/wiki/Hash_table"/> + private class HashGeneration : IGeneration + { + /// <summary> + /// Value indicating whether generation was accessed since last time check. + /// </summary> + private bool m_accessedSinceLastTimeCheck; + + /// <summary> + /// Index of first element's in element chain. + /// </summary> + /// <value> + /// -1 if there is no element in bucket; otherwise first element's index in the element chain. + /// </value> + /// <remarks> + /// 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. + /// </remarks> + private readonly int[] m_buckets; + + /// <summary> + /// Cache object. + /// </summary> + private readonly CnmMemoryCache<TKey, TValue> m_cache; + + /// <summary> + /// Generation's element array. + /// </summary> + /// <seealso cref="Element"/> + private readonly Element[] m_elements; + + /// <summary> + /// Generation's expiration time. + /// </summary> + private DateTime m_expirationTime1; + + /// <summary> + /// Index to first free element. + /// </summary> + private int m_firstFreeElement; + + /// <summary> + /// Free element count. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + private int m_freeCount; + + /// <summary> + /// Is this generation "new generation". + /// </summary> + private bool m_newGeneration; + + /// <summary> + /// Next unused entry. + /// </summary> + private int m_nextUnusedElement; + + /// <summary> + /// Size of data stored to generation. + /// </summary> + private long m_size; + + /// <summary> + /// Initializes a new instance of the <see cref="HashGeneration"/> class. + /// </summary> + /// <param name="cache"> + /// The cache. + /// </param> + public HashGeneration( CnmMemoryCache<TKey, TValue> cache ) + { + m_cache = cache; + m_elements = new Element[m_cache.m_generationElementCount]; + m_buckets = new int[m_cache.m_generationBucketCount]; + Clear(); + } + + /// <summary> + /// Find element's index + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="moveToFront"> + /// Move element to front of elements. + /// </param> + /// <param name="previousIndex"> + /// The previous element's index. + /// </param> + /// <returns> + /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation). + /// </returns> + 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; + } + + /// <summary> + /// Remove element front the generation. + /// </summary> + /// <param name="bucketIndex"> + /// The bucket index. + /// </param> + /// <param name="entryIndex"> + /// The element index. + /// </param> + /// <param name="previousIndex"> + /// The element's previous index. + /// </param> + 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 + + /// <summary> + /// Element that stores key, next element in chain, size and value. + /// </summary> + private struct Element + { + /// <summary> + /// Element's key. + /// </summary> + public TKey Key; + + /// <summary> + /// Next element in chain. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + public int Next; + + /// <summary> + /// Size of element. + /// </summary> + /// <value> + /// 0 if element is free; otherwise larger than 0. + /// </value> + public long Size; + + /// <summary> + /// Element's value. + /// </summary> + /// <remarks> + /// It is possible that this value is <see langword="null"/> even when element + /// have value - element's value is then <see langword="null"/> reference. + /// </remarks> + public TValue Value; + + /// <summary> + /// Gets a value indicating whether element is free or have value. + /// </summary> + /// <value> + /// <see langword="true"/> when element is free; otherwise <see langword="false"/>. + /// </value> + public bool IsFree + { + get { return Size == 0; } + } + } + + #endregion + + #region Nested type: Enumerator + + /// <summary> + /// Key value pair enumerator for <see cref="HashGeneration"/> object. + /// </summary> + private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> + { + /// <summary> + /// Current element. + /// </summary> + private KeyValuePair<TKey, TValue> m_current; + + /// <summary> + /// Current index. + /// </summary> + private int m_currentIndex; + + /// <summary> + /// Generation that is being enumerated. + /// </summary> + private readonly HashGeneration m_generation; + + /// <summary> + /// Cache version. + /// </summary> + /// <remarks> + /// When cache is change, version number is changed. + /// </remarks> + /// <seealso cref="CnmMemoryCache{TKey,TValue}.m_version"/> + private readonly int m_version; + + /// <summary> + /// Initializes a new instance of the <see cref="Enumerator"/> class. + /// </summary> + /// <param name="generation"> + /// The generation. + /// </param> + public Enumerator( HashGeneration generation ) + { + m_generation = generation; + m_version = m_generation.m_cache.m_version; + } + + #region IEnumerator<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Gets the element in the collection at the current position of the enumerator. + /// </summary> + /// <returns> + /// The element in the collection at the current position of the enumerator. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. + /// </exception> + public KeyValuePair<TKey, TValue> Current + { + get + { + if( m_currentIndex == 0 || m_currentIndex >= m_generation.Count ) + throw new InvalidOperationException(); + + return m_current; + } + } + + /// <summary> + /// Gets the current element in the collection. + /// </summary> + /// <returns> + /// The current element in the collection. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The enumerator is positioned before the first element of the collection or after the last element. + /// </exception><filterpriority>2</filterpriority> + object IEnumerator.Current + { + get { return Current; } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + /// <filterpriority>2</filterpriority> + public void Dispose() + { + } + + /// <summary> + /// Advances the enumerator to the next element of the collection. + /// </summary> + /// <returns> + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + 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<TKey, TValue>( m_generation.m_elements[ m_currentIndex ].Key, + m_generation.m_elements[ m_currentIndex ].Value ); + m_currentIndex++; + return true; + } + + m_current = new KeyValuePair<TKey, TValue>(); + return false; + } + + /// <summary> + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + /// <filterpriority>2</filterpriority> + public void Reset() + { + if( m_version != m_generation.m_cache.m_version ) + throw new InvalidOperationException(); + + m_currentIndex = 0; + } + + #endregion + } + + #endregion + + #region IGeneration Members + + /// <summary> + /// Gets or sets a value indicating whether generation was accessed since last time check. + /// </summary> + public bool AccessedSinceLastTimeCheck + { + get { return m_accessedSinceLastTimeCheck; } + + set { m_accessedSinceLastTimeCheck = value; } + } + + /// <summary> + /// Gets element count in generation. + /// </summary> + public int Count + { + get { return m_nextUnusedElement - m_freeCount; } + } + + /// <summary> + /// Gets or sets generation's expiration time. + /// </summary> + public DateTime ExpirationTime + { + get { return m_expirationTime1; } + + set { m_expirationTime1 = value; } + } + + /// <summary> + /// Gets or sets size of data stored to generation. + /// </summary> + public long Size + { + get { return m_size; } + + private set { m_size = value; } + } + + /// <summary> + /// Clear all elements from the generation and make generation new again. + /// </summary> + /// <remarks> + /// When generation is new, it is allowed to add new elements to it and + /// <see cref="IGeneration.TryGetValue"/>doesn't remove elements from it. + /// </remarks> + /// <seealso cref="IGeneration.MakeOld"/> + 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; + } + + /// <summary> + /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key. + /// </summary> + /// <param name="bucketIndex"> + /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>. + /// </param> + /// <param name="key"> + /// The key to locate in the <see cref="IGeneration"/>. + /// </param> + /// <returns> + /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>; + /// otherwise <see langword="false"/>. + /// </returns> + public bool Contains( int bucketIndex, TKey key ) + { + int previousIndex; + if( FindElementIndex( bucketIndex, key, true, out previousIndex ) == -1 ) + return false; + + AccessedSinceLastTimeCheck = true; + return true; + } + + /// <summary> + /// Returns an enumerator that iterates through the elements stored <see cref="HashGeneration"/>. + /// </summary> + /// <returns> + /// A <see cref="IEnumerator"/> that can be used to iterate through the <see cref="HashGeneration"/>. + /// </returns> + /// <filterpriority>1</filterpriority> + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return new Enumerator( this ); + } + + /// <summary> + /// Make from generation old generation. + /// </summary> + /// <remarks> + /// When generation is old, <see cref="IGeneration.TryGetValue"/> hit removes element from the generation. + /// </remarks> + /// <seealso cref="IGeneration.Clear"/> + public void MakeOld() + { + m_newGeneration = false; + } + + /// <summary> + /// Remove element associated with the key from the generation. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <returns> + /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>. + /// </returns> + 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; + } + + /// <summary> + /// Set or add element to generation. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="value"> + /// The element's value. + /// </param> + /// <param name="size"> + /// The element's size. + /// </param> + /// <returns> + /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>. + /// </returns> + /// <remarks> + /// <para> + /// 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. + /// </para> + /// </remarks> + 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; + } + + /// <summary> + /// Try to get element associated with key. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="value"> + /// The element's value. + /// </param> + /// <param name="size"> + /// The element's size. + /// </param> + /// <returns> + /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>. + /// </returns> + /// <remarks> + /// <para> + /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/> + /// are set to default value (default(TValue) and 0). + /// </para> + /// </remarks> + 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; + } + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + /// <filterpriority>2</filterpriority> + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } + + #endregion + + #region Nested type: IGeneration + + /// <summary> + /// Cache element generation interface + /// </summary> + /// <remarks> + /// <para> + /// Generation can hold limited count of elements and limited size of data. + /// </para> + /// <para> + /// There are two kind generations: "new generation" and "old generation(s)". All new elements + /// are added to "new generation". + /// </para> + /// </remarks> + protected interface IGeneration : IEnumerable<KeyValuePair<TKey, TValue>> + { + /// <summary> + /// Gets or sets a value indicating whether generation was accessed since last time check. + /// </summary> + bool AccessedSinceLastTimeCheck { get; set; } + + /// <summary> + /// Gets element count in generation. + /// </summary> + int Count { get; } + + /// <summary> + /// Gets or sets generation's expiration time. + /// </summary> + DateTime ExpirationTime { get; set; } + + /// <summary> + /// Gets size of data stored to generation. + /// </summary> + long Size { get; } + + /// <summary> + /// Clear all elements from the generation and make generation new again. + /// </summary> + /// <remarks> + /// When generation is new, it is allowed to add new elements to it and + /// <see cref="TryGetValue"/>doesn't remove elements from it. + /// </remarks> + /// <seealso cref="MakeOld"/> + void Clear(); + + /// <summary> + /// Determines whether the <see cref="IGeneration"/> contains an element with the specific key. + /// </summary> + /// <param name="bucketIndex"> + /// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>. + /// </param> + /// <param name="key"> + /// The key to locate in the <see cref="IGeneration"/>. + /// </param> + /// <returns> + /// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>; + /// otherwise <see langword="false"/>. + /// </returns> + bool Contains( int bucketIndex, TKey key ); + + /// <summary> + /// Make from generation old generation. + /// </summary> + /// <remarks> + /// When generation is old, <see cref="TryGetValue"/> hit removes element from the generation. + /// </remarks> + /// <seealso cref="Clear"/> + void MakeOld(); + + /// <summary> + /// Remove element associated with the key from the generation. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <returns> + /// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>. + /// </returns> + bool Remove( int bucketIndex, TKey key ); + + /// <summary> + /// Set or add element to generation. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="value"> + /// The element's value. + /// </param> + /// <param name="size"> + /// The element's size. + /// </param> + /// <returns> + /// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>. + /// </returns> + /// <remarks> + /// <para> + /// 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. + /// </para> + /// </remarks> + bool Set( int bucketIndex, TKey key, TValue value, long size ); + + /// <summary> + /// Try to get element associated with key. + /// </summary> + /// <param name="bucketIndex"> + /// The element's bucket index. + /// </param> + /// <param name="key"> + /// The element's key. + /// </param> + /// <param name="value"> + /// The element's value. + /// </param> + /// <param name="size"> + /// The element's size. + /// </param> + /// <returns> + /// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>. + /// </returns> + /// <remarks> + /// <para> + /// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/> + /// are set to default value (default(TValue) and 0). + /// </para> + /// </remarks> + bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size ); + } + + #endregion + + #region ICnmCache<TKey,TValue> Members + + /// <summary> + /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public int Count + { + get { return m_newGeneration.Count + m_oldGeneration.Count; } + } + + /// <summary> + /// Gets or sets elements expiration time. + /// </summary> + /// <value> + /// Elements expiration time. + /// </value> + /// <remarks> + /// <para> + /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is + /// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, + /// 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"/>. + /// </para> + /// <para> + /// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. + /// </para> + /// <para> + /// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired. + /// </para> + /// <para> + /// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + 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(); + } + } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public bool IsCountLimited + { + get { return true; } + } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public bool IsSizeLimited + { + get { return true; } + } + + /// <summary> + /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). + /// </summary> + /// <value> + /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use + /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class + /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + public bool IsSynchronized + { + get { return false; } + } + + /// <summary> + /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/> + /// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from + /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may + /// stay longer in cache. + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + public bool IsTimeLimited + { + get { return ExpirationTime != TimeSpan.MaxValue; } + } + + /// <summary> + /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; + /// otherwise maximal allowed count of elements. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + public int MaxCount + { + get { return m_maxCount; } + + set + { + if( value < 8 ) + value = 8; + if( m_maxCount == value ) + return; + + m_maxCount = value; + Initialize(); + } + } + + /// <summary> + /// <para>Gets maximal allowed element size.</para> + /// </summary> + /// <value> + /// Maximal allowed element size. + /// </value> + /// <remarks> + /// <para> + /// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + public long MaxElementSize + { + get { return m_maxElementSize; } + + private set { m_maxElementSize = value; } + } + + /// <summary> + /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + public long MaxSize + { + get { return m_maxSize; } + + set + { + if( value < 8 ) + value = 8; + if( m_maxSize == value ) + return; + + m_maxSize = value; + Initialize(); + } + } + + /// <summary> + /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally bytes, but can be any suitable unit of measure. + /// </para> + /// <para> + /// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + public long Size + { + get { return m_newGeneration.Size + m_oldGeneration.Size; } + } + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> + /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + public object SyncRoot + { + get { return m_syncRoot; } + } + + /// <summary> + /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public void Clear() + { + m_newGeneration.Clear(); + m_oldGeneration.Clear(); + m_oldGeneration.MakeOld(); + m_version++; + } + + /// <summary> + /// Returns an enumerator that iterates through the elements stored to <see cref="CnmMemoryCache{TKey,TValue}"/>. + /// </summary> + /// <returns> + /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection. + /// </returns> + /// <filterpriority>1</filterpriority> + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return new Enumerator( this ); + } + + /// <summary> + /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. + /// </para> + /// <para> + /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements + /// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + 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 ); + } + + /// <summary> + /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + 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++; + } + + /// <summary> + /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="keys"> + /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="keys"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public void RemoveRange( IEnumerable<TKey> 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++; + } + + /// <summary> + /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The object used as the key of the element. Can't be <see langword="null"/> reference. + /// </param> + /// <param name="value"> + /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. + /// </param> + /// <param name="size"> + /// The element's size. Normally bytes, but can be any suitable unit of measure. + /// </param> + /// <returns> + /// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; + /// otherwise <see langword="false"/>. + /// </returns> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// The element's <paramref name="size"/> is less than 0. + /// </exception> + /// <remarks> + /// <para> + /// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is + /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + 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; + } + + /// <summary> + /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. + /// </summary> + /// <returns> + /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with + /// the specified key; otherwise, <see langword="false"/>. + /// </returns> + /// <param name="key"> + /// The key whose <paramref name="value"/> to get. + /// </param> + /// <param name="value"> + /// When this method returns, the value associated with the specified <paramref name="key"/>, + /// if the <paramref name="key"/> is found; otherwise, the + /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + 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; + } + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + /// <filterpriority>2</filterpriority> + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/OpenSim/Framework/CnmSynchronizedCache.cs b/OpenSim/Framework/CnmSynchronizedCache.cs index 418a095..bf588ce 100644 --- a/OpenSim/Framework/CnmSynchronizedCache.cs +++ b/OpenSim/Framework/CnmSynchronizedCache.cs @@ -1,746 +1,746 @@ -/* - * 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.Threading; - -namespace OpenSim.Framework -{ - /// <summary> - /// Synchronized Cenome cache wrapper. - /// </summary> - /// <typeparam name="TKey"> - /// The type of keys in the cache. - /// </typeparam> - /// <typeparam name="TValue"> - /// The type of values in the cache. - /// </typeparam> - /// <remarks> - /// <para> - /// Enumerator will block other threads, until enumerator's <see cref="IDisposable.Dispose"/> method is called. - /// "foreach" statement is automatically calling it. - /// </para> - /// </remarks> - public class CnmSynchronizedCache<TKey, TValue> : ICnmCache<TKey, TValue> - { - /// <summary> - /// The cache object. - /// </summary> - private readonly ICnmCache<TKey, TValue> m_cache; - - /// <summary> - /// Synchronization root. - /// </summary> - private readonly object m_syncRoot; - - /// <summary> - /// Initializes a new instance of the <see cref="CnmSynchronizedCache{TKey,TValue}"/> class. - /// Initializes a new instance of the <see cref="CnmSynchronizedCache{TKey,TValue}"/> class. - /// </summary> - /// <param name="cache"> - /// The cache. - /// </param> - private CnmSynchronizedCache( ICnmCache<TKey, TValue> cache ) - { - m_cache = cache; - m_syncRoot = m_cache.SyncRoot; - } - - /// <summary> - /// Returns a <see cref="ICnmCache{TKey,TValue}"/> wrapper that is synchronized (thread safe). - /// </summary> - /// <param name="cache"> - /// The <see cref="ICnmCache{TKey,TValue}"/> to synchronize. - /// </param> - /// <returns> - /// A <see cref="ICnmCache{TKey,TValue}"/> wrapper that is synchronized (thread safe). - /// </returns> - /// <exception cref="ArgumentNullException"> - /// <paramref name="cache"/>is null. - /// </exception> - public static ICnmCache<TKey, TValue> Synchronized( ICnmCache<TKey, TValue> cache ) - { - if( cache == null ) - throw new ArgumentNullException( "cache" ); - return cache.IsSynchronized ? cache : new CnmSynchronizedCache<TKey, TValue>( cache ); - } - - #region Nested type: SynchronizedEnumerator - - /// <summary> - /// Synchronized enumerator. - /// </summary> - private class SynchronizedEnumerator : IEnumerator<KeyValuePair<TKey, TValue>> - { - /// <summary> - /// Enumerator that is being synchronized. - /// </summary> - private readonly IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; - - /// <summary> - /// Synchronization root. - /// </summary> - private object m_syncRoot; - - /// <summary> - /// Initializes a new instance of the <see cref="SynchronizedEnumerator"/> class. - /// </summary> - /// <param name="enumerator"> - /// The enumerator that is being synchronized. - /// </param> - /// <param name="syncRoot"> - /// The sync root. - /// </param> - public SynchronizedEnumerator( IEnumerator<KeyValuePair<TKey, TValue>> enumerator, object syncRoot ) - { - m_syncRoot = syncRoot; - m_enumerator = enumerator; - Monitor.Enter( m_syncRoot ); - } - - /// <summary> - /// Finalizes an instance of the <see cref="SynchronizedEnumerator"/> class. - /// </summary> - ~SynchronizedEnumerator() - { - Dispose(); - } - - #region IEnumerator<KeyValuePair<TKey,TValue>> Members - - /// <summary> - /// Gets the element in the collection at the current position of the enumerator. - /// </summary> - /// <returns> - /// The element in the collection at the current position of the enumerator. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. - /// </exception> - public KeyValuePair<TKey, TValue> Current - { - get { return m_enumerator.Current; } - } - - /// <summary> - /// Gets the current element in the collection. - /// </summary> - /// <returns> - /// The current element in the collection. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The enumerator is positioned before the first element of the collection or after the last element. - /// </exception><filterpriority>2</filterpriority> - object IEnumerator.Current - { - get { return Current; } - } - - /// <summary> - /// Releases synchronization lock. - /// </summary> - public void Dispose() - { - if( m_syncRoot != null ) - { - Monitor.Exit( m_syncRoot ); - m_syncRoot = null; - } - - m_enumerator.Dispose(); - GC.SuppressFinalize( this ); - } - - /// <summary> - /// Advances the enumerator to the next element of the collection. - /// </summary> - /// <returns> - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// </returns> - /// <exception cref="InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - public bool MoveNext() - { - return m_enumerator.MoveNext(); - } - - /// <summary> - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// The collection was modified after the enumerator was created. - /// </exception> - public void Reset() - { - m_enumerator.Reset(); - } - - #endregion - } - - #endregion - - #region ICnmCache<TKey,TValue> Members - - /// <summary> - /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public int Count - { - get - { - lock( m_syncRoot ) - { - return m_cache.Count; - } - } - } - - /// <summary> - /// Gets or sets elements expiration time. - /// </summary> - /// <value> - /// Elements expiration time. - /// </value> - /// <remarks> - /// <para> - /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is - /// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, - /// 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"/>. - /// </para> - /// <para> - /// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. - /// </para> - /// <para> - /// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired. - /// </para> - /// <para> - /// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - public TimeSpan ExpirationTime - { - get - { - lock( m_syncRoot ) - { - return m_cache.ExpirationTime; - } - } - - set - { - lock( m_syncRoot ) - { - m_cache.ExpirationTime = value; - } - } - } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public bool IsCountLimited - { - get - { - lock( m_syncRoot ) - { - return m_cache.IsCountLimited; - } - } - } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - public bool IsSizeLimited - { - get - { - lock( m_syncRoot ) - { - return m_cache.IsSizeLimited; - } - } - } - - /// <summary> - /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). - /// </summary> - /// <value> - /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use - /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class - /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - public bool IsSynchronized - { - get { return true; } - } - - /// <summary> - /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/> - /// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from - /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may - /// stay longer in cache. - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - public bool IsTimeLimited - { - get - { - lock( m_syncRoot ) - { - return m_cache.IsTimeLimited; - } - } - } - - /// <summary> - /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; - /// otherwise maximal allowed count of elements. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - public int MaxCount - { - get - { - lock( m_syncRoot ) - { - return m_cache.MaxCount; - } - } - - set - { - lock( m_syncRoot ) - { - m_cache.MaxCount = value; - } - } - } - - /// <summary> - /// <para>Gets maximal allowed element size.</para> - /// </summary> - /// <value> - /// Maximal allowed element size. - /// </value> - /// <remarks> - /// <para> - /// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - public long MaxElementSize - { - get - { - lock( m_syncRoot ) - { - return m_cache.MaxElementSize; - } - } - } - - /// <summary> - /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <exception cref="ArgumentOutOfRangeException">value is less than 0.</exception> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> - public long MaxSize - { - get - { - lock( m_syncRoot ) - { - return m_cache.MaxSize; - } - } - - set - { - lock( m_syncRoot ) - { - m_cache.MaxSize = value; - } - } - } - - /// <summary> - /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally bytes, but can be any suitable unit of measure. - /// </para> - /// <para> - /// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - public long Size - { - get - { - lock( m_syncRoot ) - { - return m_cache.Size; - } - } - } - - /// <summary> - /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> - /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - public object SyncRoot - { - get { return m_syncRoot; } - } - - /// <summary> - /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public void Clear() - { - lock( m_syncRoot ) - { - m_cache.Clear(); - } - } - - /// <summary> - /// Returns an enumerator that iterates through the elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <returns> - /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection. - /// </returns> - /// <filterpriority>1</filterpriority> - public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() - { - lock( m_syncRoot ) - { - return new SynchronizedEnumerator( m_cache.GetEnumerator(), m_syncRoot ); - } - } - - /// <summary> - /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. - /// </para> - /// <para> - /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements - /// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - public void PurgeExpired() - { - lock( m_syncRoot ) - { - m_cache.PurgeExpired(); - } - } - - /// <summary> - /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public void Remove( TKey key ) - { - lock( m_syncRoot ) - { - m_cache.Remove( key ); - } - } - - /// <summary> - /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="keys"> - /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="keys"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public void RemoveRange( IEnumerable<TKey> keys ) - { - lock( m_syncRoot ) - { - m_cache.RemoveRange( keys ); - } - } - - /// <summary> - /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The object used as the key of the element. Can't be <see langword="null"/> reference. - /// </param> - /// <param name="value"> - /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. - /// </param> - /// <param name="size"> - /// The element's size. Normally bytes, but can be any suitable unit of measure. - /// </param> - /// <returns> - /// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; - /// otherwise <see langword="false"/>. - /// </returns> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <exception cref="ArgumentOutOfRangeException"> - /// The element's <paramref name="size"/> is less than 0. - /// </exception> - /// <remarks> - /// <para> - /// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is - /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public bool Set( TKey key, TValue value, long size ) - { - lock( m_syncRoot ) - { - return m_cache.Set( key, value, size ); - } - } - - /// <summary> - /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. - /// </summary> - /// <returns> - /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with - /// the specified key; otherwise, <see langword="false"/>. - /// </returns> - /// <param name="key"> - /// The key whose <paramref name="value"/> to get. - /// </param> - /// <param name="value"> - /// When this method returns, the value associated with the specified <paramref name="key"/>, - /// if the <paramref name="key"/> is found; otherwise, the - /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> - /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> - /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> - /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> - public bool TryGetValue( TKey key, out TValue value ) - { - lock( m_syncRoot ) - { - return m_cache.TryGetValue( key, out value ); - } - } - - /// <summary> - /// Returns an enumerator that iterates through the elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <returns> - /// A <see cref="IEnumerator"/> that can be used to iterate through the collection. - /// </returns> - /// <filterpriority>1</filterpriority> - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} +/* + * 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.Threading; + +namespace OpenSim.Framework +{ + /// <summary> + /// Synchronized Cenome cache wrapper. + /// </summary> + /// <typeparam name="TKey"> + /// The type of keys in the cache. + /// </typeparam> + /// <typeparam name="TValue"> + /// The type of values in the cache. + /// </typeparam> + /// <remarks> + /// <para> + /// Enumerator will block other threads, until enumerator's <see cref="IDisposable.Dispose"/> method is called. + /// "foreach" statement is automatically calling it. + /// </para> + /// </remarks> + public class CnmSynchronizedCache<TKey, TValue> : ICnmCache<TKey, TValue> + { + /// <summary> + /// The cache object. + /// </summary> + private readonly ICnmCache<TKey, TValue> m_cache; + + /// <summary> + /// Synchronization root. + /// </summary> + private readonly object m_syncRoot; + + /// <summary> + /// Initializes a new instance of the <see cref="CnmSynchronizedCache{TKey,TValue}"/> class. + /// Initializes a new instance of the <see cref="CnmSynchronizedCache{TKey,TValue}"/> class. + /// </summary> + /// <param name="cache"> + /// The cache. + /// </param> + private CnmSynchronizedCache( ICnmCache<TKey, TValue> cache ) + { + m_cache = cache; + m_syncRoot = m_cache.SyncRoot; + } + + /// <summary> + /// Returns a <see cref="ICnmCache{TKey,TValue}"/> wrapper that is synchronized (thread safe). + /// </summary> + /// <param name="cache"> + /// The <see cref="ICnmCache{TKey,TValue}"/> to synchronize. + /// </param> + /// <returns> + /// A <see cref="ICnmCache{TKey,TValue}"/> wrapper that is synchronized (thread safe). + /// </returns> + /// <exception cref="ArgumentNullException"> + /// <paramref name="cache"/>is null. + /// </exception> + public static ICnmCache<TKey, TValue> Synchronized( ICnmCache<TKey, TValue> cache ) + { + if( cache == null ) + throw new ArgumentNullException( "cache" ); + return cache.IsSynchronized ? cache : new CnmSynchronizedCache<TKey, TValue>( cache ); + } + + #region Nested type: SynchronizedEnumerator + + /// <summary> + /// Synchronized enumerator. + /// </summary> + private class SynchronizedEnumerator : IEnumerator<KeyValuePair<TKey, TValue>> + { + /// <summary> + /// Enumerator that is being synchronized. + /// </summary> + private readonly IEnumerator<KeyValuePair<TKey, TValue>> m_enumerator; + + /// <summary> + /// Synchronization root. + /// </summary> + private object m_syncRoot; + + /// <summary> + /// Initializes a new instance of the <see cref="SynchronizedEnumerator"/> class. + /// </summary> + /// <param name="enumerator"> + /// The enumerator that is being synchronized. + /// </param> + /// <param name="syncRoot"> + /// The sync root. + /// </param> + public SynchronizedEnumerator( IEnumerator<KeyValuePair<TKey, TValue>> enumerator, object syncRoot ) + { + m_syncRoot = syncRoot; + m_enumerator = enumerator; + Monitor.Enter( m_syncRoot ); + } + + /// <summary> + /// Finalizes an instance of the <see cref="SynchronizedEnumerator"/> class. + /// </summary> + ~SynchronizedEnumerator() + { + Dispose(); + } + + #region IEnumerator<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Gets the element in the collection at the current position of the enumerator. + /// </summary> + /// <returns> + /// The element in the collection at the current position of the enumerator. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called. + /// </exception> + public KeyValuePair<TKey, TValue> Current + { + get { return m_enumerator.Current; } + } + + /// <summary> + /// Gets the current element in the collection. + /// </summary> + /// <returns> + /// The current element in the collection. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The enumerator is positioned before the first element of the collection or after the last element. + /// </exception><filterpriority>2</filterpriority> + object IEnumerator.Current + { + get { return Current; } + } + + /// <summary> + /// Releases synchronization lock. + /// </summary> + public void Dispose() + { + if( m_syncRoot != null ) + { + Monitor.Exit( m_syncRoot ); + m_syncRoot = null; + } + + m_enumerator.Dispose(); + GC.SuppressFinalize( this ); + } + + /// <summary> + /// Advances the enumerator to the next element of the collection. + /// </summary> + /// <returns> + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// </returns> + /// <exception cref="InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public bool MoveNext() + { + return m_enumerator.MoveNext(); + } + + /// <summary> + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public void Reset() + { + m_enumerator.Reset(); + } + + #endregion + } + + #endregion + + #region ICnmCache<TKey,TValue> Members + + /// <summary> + /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public int Count + { + get + { + lock( m_syncRoot ) + { + return m_cache.Count; + } + } + } + + /// <summary> + /// Gets or sets elements expiration time. + /// </summary> + /// <value> + /// Elements expiration time. + /// </value> + /// <remarks> + /// <para> + /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is + /// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, + /// 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"/>. + /// </para> + /// <para> + /// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. + /// </para> + /// <para> + /// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired. + /// </para> + /// <para> + /// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + public TimeSpan ExpirationTime + { + get + { + lock( m_syncRoot ) + { + return m_cache.ExpirationTime; + } + } + + set + { + lock( m_syncRoot ) + { + m_cache.ExpirationTime = value; + } + } + } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.Count"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public bool IsCountLimited + { + get + { + lock( m_syncRoot ) + { + return m_cache.IsCountLimited; + } + } + } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + public bool IsSizeLimited + { + get + { + lock( m_syncRoot ) + { + return m_cache.IsSizeLimited; + } + } + } + + /// <summary> + /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). + /// </summary> + /// <value> + /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use + /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class + /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + public bool IsSynchronized + { + get { return true; } + } + + /// <summary> + /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/> + /// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from + /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may + /// stay longer in cache. + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + public bool IsTimeLimited + { + get + { + lock( m_syncRoot ) + { + return m_cache.IsTimeLimited; + } + } + } + + /// <summary> + /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; + /// otherwise maximal allowed count of elements. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + public int MaxCount + { + get + { + lock( m_syncRoot ) + { + return m_cache.MaxCount; + } + } + + set + { + lock( m_syncRoot ) + { + m_cache.MaxCount = value; + } + } + } + + /// <summary> + /// <para>Gets maximal allowed element size.</para> + /// </summary> + /// <value> + /// Maximal allowed element size. + /// </value> + /// <remarks> + /// <para> + /// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + public long MaxElementSize + { + get + { + lock( m_syncRoot ) + { + return m_cache.MaxElementSize; + } + } + } + + /// <summary> + /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <exception cref="ArgumentOutOfRangeException">value is less than 0.</exception> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Size"/> + public long MaxSize + { + get + { + lock( m_syncRoot ) + { + return m_cache.MaxSize; + } + } + + set + { + lock( m_syncRoot ) + { + m_cache.MaxSize = value; + } + } + } + + /// <summary> + /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally bytes, but can be any suitable unit of measure. + /// </para> + /// <para> + /// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + public long Size + { + get + { + lock( m_syncRoot ) + { + return m_cache.Size; + } + } + } + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> + /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + public object SyncRoot + { + get { return m_syncRoot; } + } + + /// <summary> + /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public void Clear() + { + lock( m_syncRoot ) + { + m_cache.Clear(); + } + } + + /// <summary> + /// Returns an enumerator that iterates through the elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <returns> + /// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection. + /// </returns> + /// <filterpriority>1</filterpriority> + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + lock( m_syncRoot ) + { + return new SynchronizedEnumerator( m_cache.GetEnumerator(), m_syncRoot ); + } + } + + /// <summary> + /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>. + /// </para> + /// <para> + /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements + /// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + public void PurgeExpired() + { + lock( m_syncRoot ) + { + m_cache.PurgeExpired(); + } + } + + /// <summary> + /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public void Remove( TKey key ) + { + lock( m_syncRoot ) + { + m_cache.Remove( key ); + } + } + + /// <summary> + /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="keys"> + /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="keys"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public void RemoveRange( IEnumerable<TKey> keys ) + { + lock( m_syncRoot ) + { + m_cache.RemoveRange( keys ); + } + } + + /// <summary> + /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The object used as the key of the element. Can't be <see langword="null"/> reference. + /// </param> + /// <param name="value"> + /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. + /// </param> + /// <param name="size"> + /// The element's size. Normally bytes, but can be any suitable unit of measure. + /// </param> + /// <returns> + /// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; + /// otherwise <see langword="false"/>. + /// </returns> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// The element's <paramref name="size"/> is less than 0. + /// </exception> + /// <remarks> + /// <para> + /// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is + /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public bool Set( TKey key, TValue value, long size ) + { + lock( m_syncRoot ) + { + return m_cache.Set( key, value, size ); + } + } + + /// <summary> + /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. + /// </summary> + /// <returns> + /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with + /// the specified key; otherwise, <see langword="false"/>. + /// </returns> + /// <param name="key"> + /// The key whose <paramref name="value"/> to get. + /// </param> + /// <param name="value"> + /// When this method returns, the value associated with the specified <paramref name="key"/>, + /// if the <paramref name="key"/> is found; otherwise, the + /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="ICnmCache{TKey,TValue}.Set"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Remove"/> + /// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/> + /// <seealso cref="ICnmCache{TKey,TValue}.Clear"/> + /// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/> + public bool TryGetValue( TKey key, out TValue value ) + { + lock( m_syncRoot ) + { + return m_cache.TryGetValue( key, out value ); + } + } + + /// <summary> + /// Returns an enumerator that iterates through the elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <returns> + /// A <see cref="IEnumerator"/> that can be used to iterate through the collection. + /// </returns> + /// <filterpriority>1</filterpriority> + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/OpenSim/Framework/ICnmCache.cs b/OpenSim/Framework/ICnmCache.cs index cba8a7f..2f62189 100644 --- a/OpenSim/Framework/ICnmCache.cs +++ b/OpenSim/Framework/ICnmCache.cs @@ -1,441 +1,441 @@ -/* - * 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.Generic; - -namespace OpenSim.Framework -{ - /// <summary> - /// Represent generic cache to store key/value pairs (elements) limited by time, size and count of elements. - /// </summary> - /// <typeparam name="TKey"> - /// The type of keys in the cache. - /// </typeparam> - /// <typeparam name="TValue"> - /// The type of values in the cache. - /// </typeparam> - /// <remarks> - /// <para> - /// Cache store limitations: - /// </para> - /// <list type="table"> - /// <listheader> - /// <term>Limitation</term> - /// <description>Description</description> - /// </listheader> - /// <item> - /// <term>Time</term> - /// <description> - /// Element that is not accessed through <see cref="TryGetValue"/> or <see cref="Set"/> in last <see cref="ExpirationTime"/> are - /// removed from the cache automatically. Depending on implementation of the cache some of elements may stay longer in cache. - /// <see cref="IsTimeLimited"/> returns <see langword="true"/>, if cache is limited by time. - /// </description> - /// </item> - /// <item> - /// <term>Count</term> - /// <description> - /// When adding an new element to cache that already have <see cref="MaxCount"/> of elements, cache will remove less recently - /// used element(s) from the cache, until element fits to cache. - /// <see cref="IsCountLimited"/> returns <see langword="true"/>, if cache is limiting element count. - /// </description> - /// </item> - /// <item> - /// <term>Size</term> - /// <description> - /// <description> - /// When adding an new element to cache that already have <see cref="MaxSize"/> of elements, cache will remove less recently - /// used element(s) from the cache, until element fits to cache. - /// <see cref="IsSizeLimited"/> returns <see langword="true"/>, if cache is limiting total size of elements. - /// Normally size is bytes used by element in the cache. But it can be any other suitable unit of measure. - /// </description> - /// </description> - /// </item> - /// </list> - /// </remarks> - public interface ICnmCache<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> - { - /// <summary> - /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="MaxCount"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="IsTimeLimited"/> - int Count { get; } - - /// <summary> - /// Gets or sets elements expiration time. - /// </summary> - /// <value> - /// Elements expiration time. - /// </value> - /// <remarks> - /// <para> - /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ExpirationTime"/> - /// and it is not accessed through <see cref="TryGetValue"/> method or element's value is - /// not replaced by <see cref="Set"/> method, then it is automatically removed from the - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, - /// because total size or count of elements stored to cache is larger than <see cref="MaxSize"/> or <see cref="MaxCount"/>. - /// </para> - /// <para> - /// It is also possible that element stays in cache longer than <see cref="ExpirationTime"/>. - /// </para> - /// <para> - /// Calling <see cref="PurgeExpired"/> try to remove all elements that are expired. - /// </para> - /// <para> - /// To disable time limit in cache, set <see cref="ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. - /// </para> - /// </remarks> - /// <seealso cref="IsTimeLimited"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="PurgeExpired"/> - /// <seealso cref="Count"/> - /// <seealso cref="MaxCount"/> - /// <seealso cref="MaxSize"/> - /// <seealso cref="Size"/> - TimeSpan ExpirationTime { get; set; } - - /// <summary> - /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). - /// </summary> - /// <value> - /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use - /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class - /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. - /// </para> - /// </remarks> - /// <seealso cref="SyncRoot"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - bool IsSynchronized { get; } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="Count"/> - /// <seealso cref="MaxCount"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="IsTimeLimited"/> - bool IsCountLimited { get; } - - /// <summary> - /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="MaxElementSize"/> - /// <seealso cref="Size"/> - /// <seealso cref="MaxSize"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="IsTimeLimited"/> - bool IsSizeLimited { get; } - - /// <summary> - /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. - /// </summary> - /// <value> - /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; - /// otherwise, <see langword="false"/>. - /// </value> - /// <remarks> - /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="Set"/> - /// or <see cref="TryGetValue"/> methods in <see cref="ExpirationTime"/> , then element is automatically removed from - /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may - /// stay longer in cache. - /// </remarks> - /// <seealso cref="ExpirationTime"/> - /// <seealso cref="PurgeExpired"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="IsSizeLimited"/> - bool IsTimeLimited { get; } - - /// <summary> - /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; - /// otherwise maximal allowed count of elements. - /// </value> - /// <remarks> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - int MaxCount { get; set; } - - /// <summary> - /// <para>Gets maximal allowed element size.</para> - /// </summary> - /// <value> - /// Maximal allowed element size. - /// </value> - /// <remarks> - /// <para> - /// If element's size is larger than <see cref="MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="Set"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="Size"/> - /// <seealso cref="MaxSize"/> - long MaxElementSize { get; } - - /// <summary> - /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <exception cref="ArgumentOutOfRangeException">value is less than 0.</exception> - /// <seealso cref="MaxElementSize"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="Size"/> - long MaxSize { get; set; } - - /// <summary> - /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// Normally bytes, but can be any suitable unit of measure. - /// </para> - /// <para> - /// Element's size is given when element is added or replaced by <see cref="Set"/> method. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="MaxElementSize"/> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="MaxSize"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="ExpirationTime"/> - long Size { get; } - - /// <summary> - /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <value> - /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. - /// </value> - /// <remarks> - /// <para> - /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> - /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// </remarks> - /// <seealso cref="IsSynchronized"/> - /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> - object SyncRoot { get; } - - /// <summary> - /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <seealso cref="Set"/> - /// <seealso cref="Remove"/> - /// <seealso cref="RemoveRange"/> - /// <seealso cref="TryGetValue"/> - /// <seealso cref="PurgeExpired"/> - void Clear(); - - /// <summary> - /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <remarks> - /// <para> - /// Element becomes expired when last access time to it has been longer time than <see cref="ExpirationTime"/>. - /// </para> - /// <para> - /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements - /// may stay longer than <see cref="ExpirationTime"/> in the cache. - /// </para> - /// </remarks> - /// <seealso cref="IsTimeLimited"/> - /// <seealso cref="ExpirationTime"/> - /// <seealso cref="Set"/> - /// <seealso cref="Remove"/> - /// <seealso cref="RemoveRange"/> - /// <seealso cref="TryGetValue"/> - /// <seealso cref="Clear"/> - void PurgeExpired(); - - /// <summary> - /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/> is <see langword="null"/>. - /// </exception> - /// <seealso cref="Set"/> - /// <seealso cref="RemoveRange"/> - /// <seealso cref="TryGetValue"/> - /// <seealso cref="Clear"/> - /// <seealso cref="PurgeExpired"/> - void Remove( TKey key ); - - /// <summary> - /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="keys"> - /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="keys"/> is <see langword="null"/>. - /// </exception> - /// <seealso cref="Set"/> - /// <seealso cref="Remove"/> - /// <seealso cref="TryGetValue"/> - /// <seealso cref="Clear"/> - /// <seealso cref="PurgeExpired"/> - void RemoveRange( IEnumerable<TKey> keys ); - - /// <summary> - /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to - /// <see cref="ICnmCache{TKey,TValue}"/>. - /// </summary> - /// <param name="key"> - /// The object used as the key of the element. Can't be <see langword="null"/> reference. - /// </param> - /// <param name="value"> - /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. - /// </param> - /// <param name="size"> - /// The element's size. Normally bytes, but can be any suitable unit of measure. - /// </param> - /// <returns> - /// <see langword="true"/> if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; - /// otherwise <see langword="false"/>. - /// </returns> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <exception cref="ArgumentOutOfRangeException"> - /// The element's <paramref name="size"/> is less than 0. - /// </exception> - /// <remarks> - /// <para> - /// If element's <paramref name="size"/> is larger than <see cref="MaxElementSize"/>, then element is - /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is - /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// <para> - /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, - /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. - /// </para> - /// </remarks> - /// <seealso cref="IsSizeLimited"/> - /// <seealso cref="IsCountLimited"/> - /// <seealso cref="Remove"/> - /// <seealso cref="RemoveRange"/> - /// <seealso cref="TryGetValue"/> - /// <seealso cref="Clear"/> - /// <seealso cref="PurgeExpired"/> - bool Set( TKey key, TValue value, long size ); - - /// <summary> - /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. - /// </summary> - /// <returns> - /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with - /// the specified key; otherwise, <see langword="false"/>. - /// </returns> - /// <param name="key"> - /// The key whose <paramref name="value"/> to get. - /// </param> - /// <param name="value"> - /// When this method returns, the value associated with the specified <paramref name="key"/>, - /// if the <paramref name="key"/> is found; otherwise, the - /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. - /// </param> - /// <exception cref="ArgumentNullException"> - /// <paramref name="key"/>is <see langword="null"/>. - /// </exception> - /// <seealso cref="Set"/> - /// <seealso cref="Remove"/> - /// <seealso cref="RemoveRange"/> - /// <seealso cref="Clear"/> - /// <seealso cref="PurgeExpired"/> - bool TryGetValue( TKey key, out TValue value ); - } -} +/* + * 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.Generic; + +namespace OpenSim.Framework +{ + /// <summary> + /// Represent generic cache to store key/value pairs (elements) limited by time, size and count of elements. + /// </summary> + /// <typeparam name="TKey"> + /// The type of keys in the cache. + /// </typeparam> + /// <typeparam name="TValue"> + /// The type of values in the cache. + /// </typeparam> + /// <remarks> + /// <para> + /// Cache store limitations: + /// </para> + /// <list type="table"> + /// <listheader> + /// <term>Limitation</term> + /// <description>Description</description> + /// </listheader> + /// <item> + /// <term>Time</term> + /// <description> + /// Element that is not accessed through <see cref="TryGetValue"/> or <see cref="Set"/> in last <see cref="ExpirationTime"/> are + /// removed from the cache automatically. Depending on implementation of the cache some of elements may stay longer in cache. + /// <see cref="IsTimeLimited"/> returns <see langword="true"/>, if cache is limited by time. + /// </description> + /// </item> + /// <item> + /// <term>Count</term> + /// <description> + /// When adding an new element to cache that already have <see cref="MaxCount"/> of elements, cache will remove less recently + /// used element(s) from the cache, until element fits to cache. + /// <see cref="IsCountLimited"/> returns <see langword="true"/>, if cache is limiting element count. + /// </description> + /// </item> + /// <item> + /// <term>Size</term> + /// <description> + /// <description> + /// When adding an new element to cache that already have <see cref="MaxSize"/> of elements, cache will remove less recently + /// used element(s) from the cache, until element fits to cache. + /// <see cref="IsSizeLimited"/> returns <see langword="true"/>, if cache is limiting total size of elements. + /// Normally size is bytes used by element in the cache. But it can be any other suitable unit of measure. + /// </description> + /// </description> + /// </item> + /// </list> + /// </remarks> + public interface ICnmCache<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> + { + /// <summary> + /// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="MaxCount"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="IsTimeLimited"/> + int Count { get; } + + /// <summary> + /// Gets or sets elements expiration time. + /// </summary> + /// <value> + /// Elements expiration time. + /// </value> + /// <remarks> + /// <para> + /// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ExpirationTime"/> + /// and it is not accessed through <see cref="TryGetValue"/> method or element's value is + /// not replaced by <see cref="Set"/> method, then it is automatically removed from the + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time, + /// because total size or count of elements stored to cache is larger than <see cref="MaxSize"/> or <see cref="MaxCount"/>. + /// </para> + /// <para> + /// It is also possible that element stays in cache longer than <see cref="ExpirationTime"/>. + /// </para> + /// <para> + /// Calling <see cref="PurgeExpired"/> try to remove all elements that are expired. + /// </para> + /// <para> + /// To disable time limit in cache, set <see cref="ExpirationTime"/> to <see cref="DateTime.MaxValue"/>. + /// </para> + /// </remarks> + /// <seealso cref="IsTimeLimited"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="PurgeExpired"/> + /// <seealso cref="Count"/> + /// <seealso cref="MaxCount"/> + /// <seealso cref="MaxSize"/> + /// <seealso cref="Size"/> + TimeSpan ExpirationTime { get; set; } + + /// <summary> + /// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe). + /// </summary> + /// <value> + /// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe); + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use + /// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class + /// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object. + /// </para> + /// </remarks> + /// <seealso cref="SyncRoot"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + bool IsSynchronized { get; } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="Count"/> + /// <seealso cref="MaxCount"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="IsTimeLimited"/> + bool IsCountLimited { get; } + + /// <summary> + /// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="MaxElementSize"/> + /// <seealso cref="Size"/> + /// <seealso cref="MaxSize"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="IsTimeLimited"/> + bool IsSizeLimited { get; } + + /// <summary> + /// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time. + /// </summary> + /// <value> + /// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements; + /// otherwise, <see langword="false"/>. + /// </value> + /// <remarks> + /// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="Set"/> + /// or <see cref="TryGetValue"/> methods in <see cref="ExpirationTime"/> , then element is automatically removed from + /// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may + /// stay longer in cache. + /// </remarks> + /// <seealso cref="ExpirationTime"/> + /// <seealso cref="PurgeExpired"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="IsSizeLimited"/> + bool IsTimeLimited { get; } + + /// <summary> + /// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements; + /// otherwise maximal allowed count of elements. + /// </value> + /// <remarks> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + int MaxCount { get; set; } + + /// <summary> + /// <para>Gets maximal allowed element size.</para> + /// </summary> + /// <value> + /// Maximal allowed element size. + /// </value> + /// <remarks> + /// <para> + /// If element's size is larger than <see cref="MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="Set"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="Size"/> + /// <seealso cref="MaxSize"/> + long MaxElementSize { get; } + + /// <summary> + /// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <exception cref="ArgumentOutOfRangeException">value is less than 0.</exception> + /// <seealso cref="MaxElementSize"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="Size"/> + long MaxSize { get; set; } + + /// <summary> + /// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// Normally bytes, but can be any suitable unit of measure. + /// </para> + /// <para> + /// Element's size is given when element is added or replaced by <see cref="Set"/> method. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="MaxElementSize"/> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="MaxSize"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="ExpirationTime"/> + long Size { get; } + + /// <summary> + /// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <value> + /// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>. + /// </value> + /// <remarks> + /// <para> + /// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/> + /// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// </remarks> + /// <seealso cref="IsSynchronized"/> + /// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/> + object SyncRoot { get; } + + /// <summary> + /// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <seealso cref="Set"/> + /// <seealso cref="Remove"/> + /// <seealso cref="RemoveRange"/> + /// <seealso cref="TryGetValue"/> + /// <seealso cref="PurgeExpired"/> + void Clear(); + + /// <summary> + /// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <remarks> + /// <para> + /// Element becomes expired when last access time to it has been longer time than <see cref="ExpirationTime"/>. + /// </para> + /// <para> + /// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements + /// may stay longer than <see cref="ExpirationTime"/> in the cache. + /// </para> + /// </remarks> + /// <seealso cref="IsTimeLimited"/> + /// <seealso cref="ExpirationTime"/> + /// <seealso cref="Set"/> + /// <seealso cref="Remove"/> + /// <seealso cref="RemoveRange"/> + /// <seealso cref="TryGetValue"/> + /// <seealso cref="Clear"/> + void PurgeExpired(); + + /// <summary> + /// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/> is <see langword="null"/>. + /// </exception> + /// <seealso cref="Set"/> + /// <seealso cref="RemoveRange"/> + /// <seealso cref="TryGetValue"/> + /// <seealso cref="Clear"/> + /// <seealso cref="PurgeExpired"/> + void Remove( TKey key ); + + /// <summary> + /// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="keys"> + /// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="keys"/> is <see langword="null"/>. + /// </exception> + /// <seealso cref="Set"/> + /// <seealso cref="Remove"/> + /// <seealso cref="TryGetValue"/> + /// <seealso cref="Clear"/> + /// <seealso cref="PurgeExpired"/> + void RemoveRange( IEnumerable<TKey> keys ); + + /// <summary> + /// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to + /// <see cref="ICnmCache{TKey,TValue}"/>. + /// </summary> + /// <param name="key"> + /// The object used as the key of the element. Can't be <see langword="null"/> reference. + /// </param> + /// <param name="value"> + /// The object used as the value of the element to add or replace. <see langword="null"/> is allowed. + /// </param> + /// <param name="size"> + /// The element's size. Normally bytes, but can be any suitable unit of measure. + /// </param> + /// <returns> + /// <see langword="true"/> if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>; + /// otherwise <see langword="false"/>. + /// </returns> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// The element's <paramref name="size"/> is less than 0. + /// </exception> + /// <remarks> + /// <para> + /// If element's <paramref name="size"/> is larger than <see cref="MaxElementSize"/>, then element is + /// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is + /// removed from the <see cref="ICnmCache{TKey,TValue}"/>. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// <para> + /// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count, + /// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element. + /// </para> + /// </remarks> + /// <seealso cref="IsSizeLimited"/> + /// <seealso cref="IsCountLimited"/> + /// <seealso cref="Remove"/> + /// <seealso cref="RemoveRange"/> + /// <seealso cref="TryGetValue"/> + /// <seealso cref="Clear"/> + /// <seealso cref="PurgeExpired"/> + bool Set( TKey key, TValue value, long size ); + + /// <summary> + /// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>. + /// </summary> + /// <returns> + /// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with + /// the specified key; otherwise, <see langword="false"/>. + /// </returns> + /// <param name="key"> + /// The key whose <paramref name="value"/> to get. + /// </param> + /// <param name="value"> + /// When this method returns, the value associated with the specified <paramref name="key"/>, + /// if the <paramref name="key"/> is found; otherwise, the + /// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized. + /// </param> + /// <exception cref="ArgumentNullException"> + /// <paramref name="key"/>is <see langword="null"/>. + /// </exception> + /// <seealso cref="Set"/> + /// <seealso cref="Remove"/> + /// <seealso cref="RemoveRange"/> + /// <seealso cref="Clear"/> + /// <seealso cref="PurgeExpired"/> + bool TryGetValue( TKey key, out TValue value ); + } +} diff --git a/OpenSim/Framework/PrimeNumberHelper.cs b/OpenSim/Framework/PrimeNumberHelper.cs index e4bf615..f533f4a 100644 --- a/OpenSim/Framework/PrimeNumberHelper.cs +++ b/OpenSim/Framework/PrimeNumberHelper.cs @@ -1,98 +1,98 @@ -// -------------------------------------------------------------------------------------------------------------------- -// <copyright company="" file="PrimeNumberHelper.cs"> -// -// </copyright> -// <summary> -// -// </summary> -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; - -namespace OpenSim.Framework -{ - /// <summary> - /// Utility class that is used to find small prime numbers and test is number prime number. - /// </summary> - public static class PrimeNumberHelper - { - /// <summary> - /// Precalculated prime numbers. - /// </summary> - private static readonly int[] Primes = new int[] - { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, - 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, - 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, - 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, - 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, - 5999471, 7199369 - }; - - /// <summary> - /// Get prime number that is equal or larger than <see cref="min"/>. - /// </summary> - /// <param name="min"> - /// Minimal returned prime number. - /// </param> - /// <returns> - /// Primer number that is equal or larger than <see cref="min"/>. If <see cref="min"/> is too large, return -1. - /// </returns> - public static int GetPrime( int min ) - { - if( min <= 2 ) - return 2; - - if( Primes[ Primes.Length - 1 ] < min ) - { - for( int i = min | 1 ; i < 0x7FFFFFFF ; i += 2 ) - { - if( IsPrime( i ) ) - return i; - } - - return -1; - } - - for( int i = Primes.Length - 2 ; i >= 0 ; i-- ) - { - if( min == Primes[ i ] ) - return min; - - if( min > Primes[ i ] ) - return Primes[ i + 1 ]; - } - - return 2; - } - - /// <summary> - /// Just basic Sieve of Eratosthenes prime number test. - /// </summary> - /// <param name="candinate"> - /// Number that is tested. - /// </param> - /// <returns> - /// true, if <see cref="candinate"/> is prime number; otherwise false. - /// </returns> - public static bool IsPrime( int candinate ) - { - if( (candinate & 1) == 0 ) - - // Even number - only prime if 2 - return candinate == 2; - - int upperBound = (int) Math.Sqrt( candinate ); - for( int i = 3 ; i < upperBound ; i += 2 ) - { - if( candinate % i == 0 ) - return false; - } - - return true; - } - } -} +// -------------------------------------------------------------------------------------------------------------------- +// <copyright company="" file="PrimeNumberHelper.cs"> +// +// </copyright> +// <summary> +// +// </summary> +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; + +namespace OpenSim.Framework +{ + /// <summary> + /// Utility class that is used to find small prime numbers and test is number prime number. + /// </summary> + public static class PrimeNumberHelper + { + /// <summary> + /// Precalculated prime numbers. + /// </summary> + private static readonly int[] Primes = new int[] + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, + 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, + 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, + 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, + 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, + 5999471, 7199369 + }; + + /// <summary> + /// Get prime number that is equal or larger than <see cref="min"/>. + /// </summary> + /// <param name="min"> + /// Minimal returned prime number. + /// </param> + /// <returns> + /// Primer number that is equal or larger than <see cref="min"/>. If <see cref="min"/> is too large, return -1. + /// </returns> + public static int GetPrime( int min ) + { + if( min <= 2 ) + return 2; + + if( Primes[ Primes.Length - 1 ] < min ) + { + for( int i = min | 1 ; i < 0x7FFFFFFF ; i += 2 ) + { + if( IsPrime( i ) ) + return i; + } + + return -1; + } + + for( int i = Primes.Length - 2 ; i >= 0 ; i-- ) + { + if( min == Primes[ i ] ) + return min; + + if( min > Primes[ i ] ) + return Primes[ i + 1 ]; + } + + return 2; + } + + /// <summary> + /// Just basic Sieve of Eratosthenes prime number test. + /// </summary> + /// <param name="candinate"> + /// Number that is tested. + /// </param> + /// <returns> + /// true, if <see cref="candinate"/> is prime number; otherwise false. + /// </returns> + public static bool IsPrime( int candinate ) + { + if( (candinate & 1) == 0 ) + + // Even number - only prime if 2 + return candinate == 2; + + int upperBound = (int) Math.Sqrt( candinate ); + for( int i = 3 ; i < upperBound ; i += 2 ) + { + if( candinate % i == 0 ) + return false; + } + + return true; + } + } +} diff --git a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs index 00a8143..bbc9c9b 100644 --- a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs @@ -1,382 +1,382 @@ -// -------------------------------------------------------------------------------------------------------------------- -// <copyright company="" file="CenomeAssetCache.cs"> -// -// </copyright> -// <summary> -// -// </summary> -// -// -------------------------------------------------------------------------------------------------------------------- -using System; -using System.Reflection; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.Region.CoreModules.Asset -{ - /// <summary> - /// Cenome memory asset cache. - /// </summary> - /// <remarks> - /// <para> - /// Cache is enabled by setting "AssetCaching" configuration to value "CenomeMemoryAssetCache". - /// When cache is successfully enable log should have message - /// "[ASSET CACHE]: Cenome asset cache enabled (MaxSize = XXX bytes, MaxCount = XXX, ExpirationTime = XXX)". - /// </para> - /// <para> - /// Cache's size is limited by two parameters: - /// maximal allowed size in bytes and maximal allowed asset count. When new asset - /// is added to cache that have achieved either size or count limitation, cache - /// will automatically remove less recently used assets from cache. Additionally - /// asset's lifetime is controlled by expiration time. - /// </para> - /// <para> - /// <list type="table"> - /// <listheader> - /// <term>Configuration</term> - /// <description>Description</description> - /// </listheader> - /// <item> - /// <term>MaxSize</term> - /// <description>Maximal size of the cache in bytes. Default value: 128MB (134 217 728 bytes).</description> - /// </item> - /// <item> - /// <term>MaxCount</term> - /// <description>Maximal count of assets stored to cache. Default value: 4096 assets.</description> - /// </item> - /// <item> - /// <term>ExpirationTime</term> - /// <description>Asset's expiration time in minutes. Default value: 30 minutes.</description> - /// </item> - /// </list> - /// </para> - /// </remarks> - /// <example> - /// Enabling Cenome Asset Cache: - /// <code> - /// [Modules] - /// AssetCaching = "CenomeMemoryAssetCache" - /// </code> - /// Setting size and expiration time limitations: - /// <code> - /// [AssetService] - /// ; 256 MB (default: 134217728) - /// MaxSize = 268435456 - /// ; How many assets it is possible to store cache (default: 4096) - /// MaxCount = 16384 - /// ; Expiration time - 1 hour (default: 30 minutes) - /// ExpirationTime = 60 - /// </code> - /// </example> - public class CenomeMemoryAssetCache : IImprovedAssetCache, ISharedRegionModule - { - /// <summary> - /// Cache's default maximal asset count. - /// </summary> - /// <remarks> - /// <para> - /// Assuming that average asset size is about 32768 bytes. - /// </para> - /// </remarks> - public const int DefaultMaxCount = 4096; - - /// <summary> - /// Default maximal size of the cache in bytes - /// </summary> - /// <remarks> - /// <para> - /// 128MB = 128 * 1024^2 = 134 217 728 bytes. - /// </para> - /// </remarks> - public const long DefaultMaxSize = 134217728; - - /// <summary> - /// Asset's default expiration time in the cache. - /// </summary> - public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 ); - - /// <summary> - /// Log manager instance. - /// </summary> - private static readonly ILog Log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType ); - - /// <summary> - /// Cache object. - /// </summary> - private ICnmCache<string, AssetBase> m_cache; - - /// <summary> - /// Count of cache commands - /// </summary> - private int m_cachedCount; - - /// <summary> - /// How many gets before dumping statistics - /// </summary> - /// <remarks> - /// If 0 or less, then disabled. - /// </remarks> - private int m_debugEpoch; - - /// <summary> - /// Is Cenome asset cache enabled. - /// </summary> - private bool m_enabled; - - /// <summary> - /// Count of get requests - /// </summary> - private int m_getCount; - - /// <summary> - /// How many hits - /// </summary> - private int m_hitCount; - - /// <summary> - /// Initialize asset cache module with default parameters. - /// </summary> - public void Initialize() - { - Initialize( DefaultMaxSize, DefaultMaxCount, DefaultExpirationTime ); - } - - /// <summary> - /// Initialize asset cache module, with custom parameters. - /// </summary> - /// <param name="maximalSize"> - /// Cache's maximal size in bytes. - /// </param> - /// <param name="maximalCount"> - /// Cache's maximal count of assets. - /// </param> - /// <param name="expirationTime"> - /// Asset's expiration time. - /// </param> - public void Initialize( long maximalSize, int maximalCount, TimeSpan expirationTime ) - { - if( maximalSize <= 0 || maximalCount <= 0 ) - { - Log.Info( "[ASSET CACHE]: Cenome asset cache is not enabled." ); - m_enabled = false; - return; - } - - if( expirationTime <= TimeSpan.Zero ) - { - // Disable expiration time - expirationTime = TimeSpan.MaxValue; - } - - // Create cache and add synchronization wrapper over it - m_cache = - CnmSynchronizedCache<string, AssetBase>.Synchronized( new CnmMemoryCache<string, AssetBase>( - maximalSize, maximalCount, expirationTime ) ); - m_enabled = true; - Log.InfoFormat( - "[ASSET CACHE]: Cenome asset cache enabled (MaxSize = {0} bytes, MaxCount = {1}, ExpirationTime = {2})", - maximalSize, - maximalCount, - expirationTime ); - } - - #region IImprovedAssetCache Members - - /// <summary> - /// Cache asset. - /// </summary> - /// <param name="asset"> - /// The asset that is being cached. - /// </param> - public void Cache( AssetBase asset ) - { - long size = asset.Data != null ? asset.Data.Length : 1; - m_cache.Set( asset.ID, asset, size ); - m_cachedCount++; - } - - /// <summary> - /// Clear asset cache. - /// </summary> - public void Clear() - { - m_cache.Clear(); - } - - /// <summary> - /// Expire (remove) asset stored to cache. - /// </summary> - /// <param name="id"> - /// The expired asset's id. - /// </param> - public void Expire( string id ) - { - m_cache.Remove( id ); - } - - /// <summary> - /// Get asset stored - /// </summary> - /// <param name="id"> - /// The asset's id. - /// </param> - /// <returns> - /// Asset if it is found from cache; otherwise <see langword="null"/>. - /// </returns> - /// <remarks> - /// <para> - /// Caller should always check that is return value <see langword="null"/>. - /// Cache doesn't guarantee in any situation that asset is stored to it. - /// </para> - /// </remarks> - public AssetBase Get( string id ) - { - m_getCount++; - AssetBase assetBase; - if( m_cache.TryGetValue( id, out assetBase ) ) - m_hitCount++; - - if( m_getCount == m_debugEpoch ) - { - Log.InfoFormat( - "[ASSET CACHE]: Cached = {0}, Get = {1}, Hits = {2}%, Size = {3} bytes, Avg. A. Size = {4} bytes", - m_cachedCount, - m_getCount, - ((double) m_hitCount / m_getCount) * 100.0, - m_cache.Size, - m_cache.Size / m_cache.Count ); - m_getCount = 0; - m_hitCount = 0; - m_cachedCount = 0; - } - - return assetBase; - } - - #endregion - - #region ISharedRegionModule Members - - /// <summary> - /// Gets region module's name. - /// </summary> - public string Name - { - get { return "CenomeMemoryAssetCache"; } - } - - /// <summary> - /// New region is being added to server. - /// </summary> - /// <param name="scene"> - /// Region's scene. - /// </param> - public void AddRegion( Scene scene ) - { - if( m_enabled ) - scene.RegisterModuleInterface<IImprovedAssetCache>( this ); - } - - /// <summary> - /// Close region module. - /// </summary> - public void Close() - { - m_enabled = false; - m_cache.Clear(); - m_cache = null; - } - - /// <summary> - /// Initialize region module. - /// </summary> - /// <param name="source"> - /// Configuration source. - /// </param> - public void Initialise( IConfigSource source ) - { - m_cache = null; - m_enabled = false; - - IConfig moduleConfig = source.Configs[ "Modules" ]; - if( moduleConfig == null ) - return; - - string name = moduleConfig.GetString( "AssetCaching" ); - Log.DebugFormat( "[XXX] name = {0} (this module's name: {1}", name, Name ); - - if( name != Name ) - return; - - // This module is used - long maxSize = DefaultMaxSize; - int maxCount = DefaultMaxCount; - TimeSpan expirationTime = DefaultExpirationTime; - - IConfig assetConfig = source.Configs[ "AssetCache" ]; - if( assetConfig != null ) - { - // Get optional configurations - maxSize = assetConfig.GetLong( "MaxSize", DefaultMaxSize ); - maxCount = assetConfig.GetInt( "MaxCount", DefaultMaxCount ); - expirationTime = - TimeSpan.FromMinutes( assetConfig.GetInt( "ExpirationTime", (int) DefaultExpirationTime.TotalMinutes ) ); - - // Debugging purposes only - m_debugEpoch = assetConfig.GetInt( "DebugEpoch", 0 ); - } - - Initialize( maxSize, maxCount, expirationTime ); - } - - /// <summary> - /// Initialization post handling. - /// </summary> - /// <remarks> - /// <para> - /// Modules can use this to initialize connection with other modules. - /// </para> - /// </remarks> - public void PostInitialise() - { - } - - /// <summary> - /// Region has been loaded. - /// </summary> - /// <param name="scene"> - /// Region's scene. - /// </param> - /// <remarks> - /// <para> - /// This is needed for all module types. Modules will register - /// Interfaces with scene in AddScene, and will also need a means - /// to access interfaces registered by other modules. Without - /// this extra method, a module attempting to use another modules' - /// interface would be successful only depending on load order, - /// which can't be depended upon, or modules would need to resort - /// to ugly kludges to attempt to request interfaces when needed - /// and unnecessary caching logic repeated in all modules. - /// The extra function stub is just that much cleaner. - /// </para> - /// </remarks> - public void RegionLoaded( Scene scene ) - { - } - - /// <summary> - /// Region is being removed. - /// </summary> - /// <param name="scene"> - /// Region scene that is being removed. - /// </param> - public void RemoveRegion( Scene scene ) - { - } - - #endregion - } -} +// -------------------------------------------------------------------------------------------------------------------- +// <copyright company="" file="CenomeAssetCache.cs"> +// +// </copyright> +// <summary> +// +// </summary> +// +// -------------------------------------------------------------------------------------------------------------------- +using System; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Asset +{ + /// <summary> + /// Cenome memory asset cache. + /// </summary> + /// <remarks> + /// <para> + /// Cache is enabled by setting "AssetCaching" configuration to value "CenomeMemoryAssetCache". + /// When cache is successfully enable log should have message + /// "[ASSET CACHE]: Cenome asset cache enabled (MaxSize = XXX bytes, MaxCount = XXX, ExpirationTime = XXX)". + /// </para> + /// <para> + /// Cache's size is limited by two parameters: + /// maximal allowed size in bytes and maximal allowed asset count. When new asset + /// is added to cache that have achieved either size or count limitation, cache + /// will automatically remove less recently used assets from cache. Additionally + /// asset's lifetime is controlled by expiration time. + /// </para> + /// <para> + /// <list type="table"> + /// <listheader> + /// <term>Configuration</term> + /// <description>Description</description> + /// </listheader> + /// <item> + /// <term>MaxSize</term> + /// <description>Maximal size of the cache in bytes. Default value: 128MB (134 217 728 bytes).</description> + /// </item> + /// <item> + /// <term>MaxCount</term> + /// <description>Maximal count of assets stored to cache. Default value: 4096 assets.</description> + /// </item> + /// <item> + /// <term>ExpirationTime</term> + /// <description>Asset's expiration time in minutes. Default value: 30 minutes.</description> + /// </item> + /// </list> + /// </para> + /// </remarks> + /// <example> + /// Enabling Cenome Asset Cache: + /// <code> + /// [Modules] + /// AssetCaching = "CenomeMemoryAssetCache" + /// </code> + /// Setting size and expiration time limitations: + /// <code> + /// [AssetService] + /// ; 256 MB (default: 134217728) + /// MaxSize = 268435456 + /// ; How many assets it is possible to store cache (default: 4096) + /// MaxCount = 16384 + /// ; Expiration time - 1 hour (default: 30 minutes) + /// ExpirationTime = 60 + /// </code> + /// </example> + public class CenomeMemoryAssetCache : IImprovedAssetCache, ISharedRegionModule + { + /// <summary> + /// Cache's default maximal asset count. + /// </summary> + /// <remarks> + /// <para> + /// Assuming that average asset size is about 32768 bytes. + /// </para> + /// </remarks> + public const int DefaultMaxCount = 4096; + + /// <summary> + /// Default maximal size of the cache in bytes + /// </summary> + /// <remarks> + /// <para> + /// 128MB = 128 * 1024^2 = 134 217 728 bytes. + /// </para> + /// </remarks> + public const long DefaultMaxSize = 134217728; + + /// <summary> + /// Asset's default expiration time in the cache. + /// </summary> + public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 ); + + /// <summary> + /// Log manager instance. + /// </summary> + private static readonly ILog Log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType ); + + /// <summary> + /// Cache object. + /// </summary> + private ICnmCache<string, AssetBase> m_cache; + + /// <summary> + /// Count of cache commands + /// </summary> + private int m_cachedCount; + + /// <summary> + /// How many gets before dumping statistics + /// </summary> + /// <remarks> + /// If 0 or less, then disabled. + /// </remarks> + private int m_debugEpoch; + + /// <summary> + /// Is Cenome asset cache enabled. + /// </summary> + private bool m_enabled; + + /// <summary> + /// Count of get requests + /// </summary> + private int m_getCount; + + /// <summary> + /// How many hits + /// </summary> + private int m_hitCount; + + /// <summary> + /// Initialize asset cache module with default parameters. + /// </summary> + public void Initialize() + { + Initialize( DefaultMaxSize, DefaultMaxCount, DefaultExpirationTime ); + } + + /// <summary> + /// Initialize asset cache module, with custom parameters. + /// </summary> + /// <param name="maximalSize"> + /// Cache's maximal size in bytes. + /// </param> + /// <param name="maximalCount"> + /// Cache's maximal count of assets. + /// </param> + /// <param name="expirationTime"> + /// Asset's expiration time. + /// </param> + public void Initialize( long maximalSize, int maximalCount, TimeSpan expirationTime ) + { + if( maximalSize <= 0 || maximalCount <= 0 ) + { + Log.Info( "[ASSET CACHE]: Cenome asset cache is not enabled." ); + m_enabled = false; + return; + } + + if( expirationTime <= TimeSpan.Zero ) + { + // Disable expiration time + expirationTime = TimeSpan.MaxValue; + } + + // Create cache and add synchronization wrapper over it + m_cache = + CnmSynchronizedCache<string, AssetBase>.Synchronized( new CnmMemoryCache<string, AssetBase>( + maximalSize, maximalCount, expirationTime ) ); + m_enabled = true; + Log.InfoFormat( + "[ASSET CACHE]: Cenome asset cache enabled (MaxSize = {0} bytes, MaxCount = {1}, ExpirationTime = {2})", + maximalSize, + maximalCount, + expirationTime ); + } + + #region IImprovedAssetCache Members + + /// <summary> + /// Cache asset. + /// </summary> + /// <param name="asset"> + /// The asset that is being cached. + /// </param> + public void Cache( AssetBase asset ) + { + long size = asset.Data != null ? asset.Data.Length : 1; + m_cache.Set( asset.ID, asset, size ); + m_cachedCount++; + } + + /// <summary> + /// Clear asset cache. + /// </summary> + public void Clear() + { + m_cache.Clear(); + } + + /// <summary> + /// Expire (remove) asset stored to cache. + /// </summary> + /// <param name="id"> + /// The expired asset's id. + /// </param> + public void Expire( string id ) + { + m_cache.Remove( id ); + } + + /// <summary> + /// Get asset stored + /// </summary> + /// <param name="id"> + /// The asset's id. + /// </param> + /// <returns> + /// Asset if it is found from cache; otherwise <see langword="null"/>. + /// </returns> + /// <remarks> + /// <para> + /// Caller should always check that is return value <see langword="null"/>. + /// Cache doesn't guarantee in any situation that asset is stored to it. + /// </para> + /// </remarks> + public AssetBase Get( string id ) + { + m_getCount++; + AssetBase assetBase; + if( m_cache.TryGetValue( id, out assetBase ) ) + m_hitCount++; + + if( m_getCount == m_debugEpoch ) + { + Log.InfoFormat( + "[ASSET CACHE]: Cached = {0}, Get = {1}, Hits = {2}%, Size = {3} bytes, Avg. A. Size = {4} bytes", + m_cachedCount, + m_getCount, + ((double) m_hitCount / m_getCount) * 100.0, + m_cache.Size, + m_cache.Size / m_cache.Count ); + m_getCount = 0; + m_hitCount = 0; + m_cachedCount = 0; + } + + return assetBase; + } + + #endregion + + #region ISharedRegionModule Members + + /// <summary> + /// Gets region module's name. + /// </summary> + public string Name + { + get { return "CenomeMemoryAssetCache"; } + } + + /// <summary> + /// New region is being added to server. + /// </summary> + /// <param name="scene"> + /// Region's scene. + /// </param> + public void AddRegion( Scene scene ) + { + if( m_enabled ) + scene.RegisterModuleInterface<IImprovedAssetCache>( this ); + } + + /// <summary> + /// Close region module. + /// </summary> + public void Close() + { + m_enabled = false; + m_cache.Clear(); + m_cache = null; + } + + /// <summary> + /// Initialize region module. + /// </summary> + /// <param name="source"> + /// Configuration source. + /// </param> + public void Initialise( IConfigSource source ) + { + m_cache = null; + m_enabled = false; + + IConfig moduleConfig = source.Configs[ "Modules" ]; + if( moduleConfig == null ) + return; + + string name = moduleConfig.GetString( "AssetCaching" ); + Log.DebugFormat( "[XXX] name = {0} (this module's name: {1}", name, Name ); + + if( name != Name ) + return; + + // This module is used + long maxSize = DefaultMaxSize; + int maxCount = DefaultMaxCount; + TimeSpan expirationTime = DefaultExpirationTime; + + IConfig assetConfig = source.Configs[ "AssetCache" ]; + if( assetConfig != null ) + { + // Get optional configurations + maxSize = assetConfig.GetLong( "MaxSize", DefaultMaxSize ); + maxCount = assetConfig.GetInt( "MaxCount", DefaultMaxCount ); + expirationTime = + TimeSpan.FromMinutes( assetConfig.GetInt( "ExpirationTime", (int) DefaultExpirationTime.TotalMinutes ) ); + + // Debugging purposes only + m_debugEpoch = assetConfig.GetInt( "DebugEpoch", 0 ); + } + + Initialize( maxSize, maxCount, expirationTime ); + } + + /// <summary> + /// Initialization post handling. + /// </summary> + /// <remarks> + /// <para> + /// Modules can use this to initialize connection with other modules. + /// </para> + /// </remarks> + public void PostInitialise() + { + } + + /// <summary> + /// Region has been loaded. + /// </summary> + /// <param name="scene"> + /// Region's scene. + /// </param> + /// <remarks> + /// <para> + /// This is needed for all module types. Modules will register + /// Interfaces with scene in AddScene, and will also need a means + /// to access interfaces registered by other modules. Without + /// this extra method, a module attempting to use another modules' + /// interface would be successful only depending on load order, + /// which can't be depended upon, or modules would need to resort + /// to ugly kludges to attempt to request interfaces when needed + /// and unnecessary caching logic repeated in all modules. + /// The extra function stub is just that much cleaner. + /// </para> + /// </remarks> + public void RegionLoaded( Scene scene ) + { + } + + /// <summary> + /// Region is being removed. + /// </summary> + /// <param name="scene"> + /// Region scene that is being removed. + /// </param> + public void RemoveRegion( Scene scene ) + { + } + + #endregion + } +} -- cgit v1.1