From 9e3b592fa3636cf3d50460e303173de55bcb3920 Mon Sep 17 00:00:00 2001
From: Dahlia Trimble
Date: Wed, 3 Jun 2009 08:41:08 +0000
Subject: Thank you Imaze Rhiano for a patch that implements Cenome Memory
Asset Cache (Mantis #3759) See the files:
bin/config-include/GridCommon.ini.example and
bin/config-include/StandaloneCommon.ini.example to configure and enable this
caching method.
---
OpenSim/Framework/CnmMemoryCache.cs | 1852 ++++++++++++++++++++
OpenSim/Framework/CnmSynchronizedCache.cs | 746 ++++++++
OpenSim/Framework/ICnmCache.cs | 441 +++++
OpenSim/Framework/PrimeNumberHelper.cs | 98 ++
.../Region/CoreModules/Asset/CenomeAssetCache.cs | 382 ++++
.../Resources/CoreModulePlugin.addin.xml | 1 +
6 files changed, 3520 insertions(+)
create mode 100644 OpenSim/Framework/CnmMemoryCache.cs
create mode 100644 OpenSim/Framework/CnmSynchronizedCache.cs
create mode 100644 OpenSim/Framework/ICnmCache.cs
create mode 100644 OpenSim/Framework/PrimeNumberHelper.cs
create mode 100644 OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs
(limited to 'OpenSim')
diff --git a/OpenSim/Framework/CnmMemoryCache.cs b/OpenSim/Framework/CnmMemoryCache.cs
new file mode 100644
index 0000000..6ca71ec
--- /dev/null
+++ b/OpenSim/Framework/CnmMemoryCache.cs
@@ -0,0 +1,1852 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+//
+//
+//
+//
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace OpenSim.Framework
+{
+ ///
+ /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size.
+ ///
+ ///
+ /// The type of keys in the cache.
+ ///
+ ///
+ /// The type of values in the dictionary.
+ ///
+ ///
+ ///
+ /// Cenome memory cache stores elements to hash table generations. When new element is being added to cache, and new size would exceed
+ /// maximal allowed size or maximal amount of allowed element count, then elements in oldest generation are deleted. Last access time
+ /// is also tracked in generation level - thus it is possible that some elements are staying in cache far beyond their expiration time.
+ /// If elements in older generations are accessed through method, they are moved to newest generation.
+ ///
+ ///
+ public class CnmMemoryCache : ICnmCache
+ {
+ ///
+ /// Default maximal count.
+ ///
+ ///
+ public const int DefaultMaxCount = 4096;
+
+ ///
+ /// Default maximal size.
+ ///
+ ///
+ ///
+ /// 128MB = 128 * 1024^2 = 134 217 728 bytes.
+ ///
+ ///
+ ///
+ public const long DefaultMaxSize = 134217728;
+
+ ///
+ /// How many operations between time checks.
+ ///
+ private const int DefaultOperationsBetweenTimeChecks = 40;
+
+ ///
+ /// Default expiration time.
+ ///
+ ///
+ ///
+ /// 30 minutes.
+ ///
+ ///
+ public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 );
+
+ ///
+ /// Minimal allowed expiration time.
+ ///
+ ///
+ ///
+ /// 5 minutes.
+ ///
+ ///
+ public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds( 10.0 );
+
+ ///
+ /// Comparer used to compare element keys.
+ ///
+ ///
+ /// Comparer is initialized by constructor.
+ ///
+ ///
+ public readonly IEqualityComparer Comparer;
+
+ ///
+ /// Expiration time.
+ ///
+ private TimeSpan m_expirationTime = DefaultExpirationTime;
+
+ ///
+ /// Generation bucket count.
+ ///
+ private int m_generationBucketCount;
+
+ ///
+ /// Generation entry count.
+ ///
+ private int m_generationElementCount;
+
+ ///
+ /// Generation max size.
+ ///
+ private long m_generationMaxSize;
+
+ ///
+ /// Maximal allowed count of elements.
+ ///
+ private int m_maxCount;
+
+ ///
+ /// Maximal allowed total size of elements.
+ ///
+ private long m_maxElementSize;
+
+ ///
+ /// Maximal size.
+ ///
+ private long m_maxSize;
+
+ ///
+ /// New generation.
+ ///
+ private IGeneration m_newGeneration;
+
+ ///
+ /// Old generation.
+ ///
+ private IGeneration m_oldGeneration;
+
+ ///
+ /// Operations between time check.
+ ///
+ private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
+
+ ///
+ /// Synchronization root object, should always be private and exists always
+ ///
+ private readonly object m_syncRoot = new object();
+
+ ///
+ /// Version of cache.
+ ///
+ ///
+ ///
+ /// Updated every time when cache has been changed (element removed, expired, added, replaced).
+ ///
+ ///
+ private int m_version;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CnmMemoryCache()
+ : this( DefaultMaxSize )
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Maximal cache size.
+ ///
+ public CnmMemoryCache( long maximalSize )
+ : this( maximalSize, DefaultMaxCount )
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Maximal cache size.
+ ///
+ ///
+ /// Maximal element count.
+ ///
+ public CnmMemoryCache( long maximalSize, int maximalCount )
+ : this( maximalSize, maximalCount, DefaultExpirationTime )
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Maximal cache size.
+ ///
+ ///
+ /// Maximal element count.
+ ///
+ ///
+ /// Elements expiration time.
+ ///
+ public CnmMemoryCache( long maximalSize, int maximalCount, TimeSpan expirationTime )
+ : this( maximalSize, maximalCount, expirationTime, EqualityComparer.Default )
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Maximal cache size.
+ ///
+ ///
+ /// Maximal element count.
+ ///
+ ///
+ /// Elements expiration time.
+ ///
+ ///
+ /// Comparer used for comparing elements.
+ ///
+ ///
+ /// is reference.
+ ///
+ public CnmMemoryCache( long maximalSize,
+ int maximalCount,
+ TimeSpan expirationTime,
+ IEqualityComparer comparer )
+ {
+ if( comparer == null )
+ throw new ArgumentNullException( "comparer" );
+
+ if( expirationTime < MinExpirationTime )
+ expirationTime = MinExpirationTime;
+ if( maximalCount < 8 )
+ maximalCount = 8;
+ if( maximalSize < 8 )
+ maximalSize = 8;
+ if( maximalCount > maximalSize )
+ maximalCount = (int) maximalSize;
+
+ Comparer = comparer;
+ m_expirationTime = expirationTime;
+ m_maxSize = maximalSize;
+ m_maxCount = maximalCount;
+
+ Initialize();
+ }
+
+ ///
+ /// Add element to new generation.
+ ///
+ ///
+ /// The bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// The element's value.
+ ///
+ ///
+ /// The element's size.
+ ///
+ protected virtual void AddToNewGeneration( int bucketIndex, TKey key, TValue value, long size )
+ {
+ // Add to newest generation
+ if( !m_newGeneration.Set( bucketIndex, key, value, size ) )
+ {
+ // Failed to add new generation
+ RecycleGenerations();
+ m_newGeneration.Set( bucketIndex, key, value, size );
+ }
+
+ m_version++;
+ }
+
+ ///
+ ///
+ /// Get keys bucket index.
+ ///
+ ///
+ ///
+ ///
+ /// Key which bucket index is being retrieved.
+ ///
+ ///
+ ///
+ ///
+ /// Bucket index.
+ ///
+ ///
+ ///
+ ///
+ /// Method uses to calculate hash code.
+ ///
+ ///
+ /// Bucket index is remainder when element key's hash value is divided by bucket count.
+ ///
+ ///
+ /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
+ ///
+ ///
+ protected virtual int GetBucketIndex( TKey key )
+ {
+ return (Comparer.GetHashCode( key ) & 0x7FFFFFFF) % m_generationBucketCount;
+ }
+
+ ///
+ /// Purge generation from the cache.
+ ///
+ ///
+ /// The generation that is purged.
+ ///
+ protected virtual void PurgeGeneration( IGeneration generation )
+ {
+ generation.Clear();
+ m_version++;
+ }
+
+ ///
+ /// check expired.
+ ///
+ private void CheckExpired()
+ {
+ // Do this only one in every m_operationsBetweenTimeChecks
+ // Fetching time is using several millisecons - it is better not to do all time.
+ m_operationsBetweenTimeChecks--;
+ if( m_operationsBetweenTimeChecks <= 0 )
+ PurgeExpired();
+ }
+
+ ///
+ /// Initialize cache.
+ ///
+ private void Initialize()
+ {
+ m_version++;
+
+ m_generationMaxSize = MaxSize / 2;
+ MaxElementSize = MaxSize / 8;
+ m_generationElementCount = MaxCount / 2;
+
+ // Buckets need to be prime number to get better spread of hash values
+ m_generationBucketCount = PrimeNumberHelper.GetPrime( m_generationElementCount );
+
+ m_newGeneration = new HashGeneration( this );
+ m_oldGeneration = new HashGeneration( this );
+ m_oldGeneration.MakeOld();
+ }
+
+ ///
+ /// Recycle generations.
+ ///
+ private void RecycleGenerations()
+ {
+ // Rotate old generation to new generation, new generation to old generation
+ IGeneration temp = m_newGeneration;
+ m_newGeneration = m_oldGeneration;
+ m_newGeneration.Clear();
+ m_oldGeneration = temp;
+ m_oldGeneration.MakeOld();
+ }
+
+ #region Nested type: Enumerator
+
+ ///
+ /// Key and value pair enumerator.
+ ///
+ private class Enumerator : IEnumerator>
+ {
+ ///
+ /// Current enumerator.
+ ///
+ private int m_currentEnumerator = -1;
+
+ ///
+ /// Enumerators to different generations.
+ ///
+ private readonly IEnumerator>[] m_generationEnumerators =
+ new IEnumerator>[2];
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The cache.
+ ///
+ public Enumerator( CnmMemoryCache cache )
+ {
+ m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator();
+ m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator();
+ }
+
+ #region IEnumerator> Members
+
+ ///
+ /// Gets the element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The enumerator has reach end of collection or is not called.
+ ///
+ public KeyValuePair Current
+ {
+ get
+ {
+ if( m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length )
+ throw new InvalidOperationException();
+
+ return m_generationEnumerators[ m_currentEnumerator ].Current;
+ }
+ }
+
+ ///
+ /// Gets the current element in the collection.
+ ///
+ ///
+ /// The current element in the collection.
+ ///
+ ///
+ /// The enumerator is positioned before the first element of the collection or after the last element.
+ /// 2
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ /// 2
+ public void Dispose()
+ {
+ }
+
+ ///
+ /// Advances the enumerator to the next element of the collection.
+ ///
+ ///
+ /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ /// 2
+ public bool MoveNext()
+ {
+ if( m_currentEnumerator == -1 )
+ m_currentEnumerator = 0;
+
+ while( m_currentEnumerator < m_generationEnumerators.Length )
+ {
+ if( m_generationEnumerators[ m_currentEnumerator ].MoveNext() )
+ return true;
+
+ m_currentEnumerator++;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Sets the enumerator to its initial position, which is before the first element in the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ /// 2
+ public void Reset()
+ {
+ foreach( IEnumerator> enumerator in m_generationEnumerators )
+ {
+ enumerator.Reset();
+ }
+
+ m_currentEnumerator = -1;
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ #region Nested type: HashGeneration
+
+ ///
+ /// Hash generation class
+ ///
+ ///
+ ///
+ /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed
+ /// amount of buckets and it is never rehashed.
+ ///
+ ///
+ /// Read more about hash tables from Wiki article.
+ ///
+ ///
+ ///
+ private class HashGeneration : IGeneration
+ {
+ ///
+ /// Value indicating whether generation was accessed since last time check.
+ ///
+ private bool m_accessedSinceLastTimeCheck;
+
+ ///
+ /// Index of first element's in element chain.
+ ///
+ ///
+ /// -1 if there is no element in bucket; otherwise first element's index in the element chain.
+ ///
+ ///
+ /// Bucket index is remainder when element key's hash value is divided by bucket count.
+ /// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
+ ///
+ private readonly int[] m_buckets;
+
+ ///
+ /// Cache object.
+ ///
+ private readonly CnmMemoryCache m_cache;
+
+ ///
+ /// Generation's element array.
+ ///
+ ///
+ private readonly Element[] m_elements;
+
+ ///
+ /// Generation's expiration time.
+ ///
+ private DateTime m_expirationTime1;
+
+ ///
+ /// Index to first free element.
+ ///
+ private int m_firstFreeElement;
+
+ ///
+ /// Free element count.
+ ///
+ ///
+ /// When generation is cleared or constructed, this is NOT set to element count.
+ /// This is only tracking elements that are removed and are currently free.
+ ///
+ private int m_freeCount;
+
+ ///
+ /// Is this generation "new generation".
+ ///
+ private bool m_newGeneration;
+
+ ///
+ /// Next unused entry.
+ ///
+ private int m_nextUnusedElement;
+
+ ///
+ /// Size of data stored to generation.
+ ///
+ private long m_size;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The cache.
+ ///
+ public HashGeneration( CnmMemoryCache cache )
+ {
+ m_cache = cache;
+ m_elements = new Element[m_cache.m_generationElementCount];
+ m_buckets = new int[m_cache.m_generationBucketCount];
+ Clear();
+ }
+
+ ///
+ /// Find element's index
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// Move element to front of elements.
+ ///
+ ///
+ /// The previous element's index.
+ ///
+ ///
+ /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation).
+ ///
+ private int FindElementIndex( int bucketIndex, TKey key, bool moveToFront, out int previousIndex )
+ {
+ previousIndex = -1;
+ int elementIndex = m_buckets[ bucketIndex ];
+ while( elementIndex >= 0 )
+ {
+ if( m_cache.Comparer.Equals( key, m_elements[ elementIndex ].Key ) )
+ {
+ // Found match
+ if( moveToFront && previousIndex >= 0 )
+ {
+ // Move entry to front
+ m_elements[ previousIndex ].Next = m_elements[ elementIndex ].Next;
+ m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
+ m_buckets[ bucketIndex ] = elementIndex;
+ previousIndex = 0;
+ }
+
+ return elementIndex;
+ }
+
+ previousIndex = elementIndex;
+ elementIndex = m_elements[ elementIndex ].Next;
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Remove element front the generation.
+ ///
+ ///
+ /// The bucket index.
+ ///
+ ///
+ /// The element index.
+ ///
+ ///
+ /// The element's previous index.
+ ///
+ private void RemoveElement( int bucketIndex, int entryIndex, int previousIndex )
+ {
+ if( previousIndex >= 0 )
+ m_elements[ previousIndex ].Next = m_elements[ entryIndex ].Next;
+ else
+ m_buckets[ bucketIndex ] = m_elements[ entryIndex ].Next;
+
+ Size -= m_elements[ entryIndex ].Size;
+ m_elements[ entryIndex ].Value = default(TValue);
+ m_elements[ entryIndex ].Key = default(TKey);
+
+ // Add element to free elements list
+ m_elements[ entryIndex ].Next = m_firstFreeElement;
+ m_firstFreeElement = entryIndex;
+ m_freeCount++;
+ }
+
+ #region Nested type: Element
+
+ ///
+ /// Element that stores key, next element in chain, size and value.
+ ///
+ private struct Element
+ {
+ ///
+ /// Element's key.
+ ///
+ public TKey Key;
+
+ ///
+ /// Next element in chain.
+ ///
+ ///
+ /// When element have value (something is stored to it), this is index of
+ /// next element with same bucket index. When element is free, this
+ /// is index of next element in free element's list.
+ ///
+ public int Next;
+
+ ///
+ /// Size of element.
+ ///
+ ///
+ /// 0 if element is free; otherwise larger than 0.
+ ///
+ public long Size;
+
+ ///
+ /// Element's value.
+ ///
+ ///
+ /// It is possible that this value is even when element
+ /// have value - element's value is then reference.
+ ///
+ public TValue Value;
+
+ ///
+ /// Gets a value indicating whether element is free or have value.
+ ///
+ ///
+ /// when element is free; otherwise .
+ ///
+ public bool IsFree
+ {
+ get { return Size == 0; }
+ }
+ }
+
+ #endregion
+
+ #region Nested type: Enumerator
+
+ ///
+ /// Key value pair enumerator for object.
+ ///
+ private class Enumerator : IEnumerator>
+ {
+ ///
+ /// Current element.
+ ///
+ private KeyValuePair m_current;
+
+ ///
+ /// Current index.
+ ///
+ private int m_currentIndex;
+
+ ///
+ /// Generation that is being enumerated.
+ ///
+ private readonly HashGeneration m_generation;
+
+ ///
+ /// Cache version.
+ ///
+ ///
+ /// When cache is change, version number is changed.
+ ///
+ ///
+ private readonly int m_version;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The generation.
+ ///
+ public Enumerator( HashGeneration generation )
+ {
+ m_generation = generation;
+ m_version = m_generation.m_cache.m_version;
+ }
+
+ #region IEnumerator> Members
+
+ ///
+ /// Gets the element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The enumerator has reach end of collection or is not called.
+ ///
+ public KeyValuePair Current
+ {
+ get
+ {
+ if( m_currentIndex == 0 || m_currentIndex >= m_generation.Count )
+ throw new InvalidOperationException();
+
+ return m_current;
+ }
+ }
+
+ ///
+ /// Gets the current element in the collection.
+ ///
+ ///
+ /// The current element in the collection.
+ ///
+ ///
+ /// The enumerator is positioned before the first element of the collection or after the last element.
+ /// 2
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ /// 2
+ public void Dispose()
+ {
+ }
+
+ ///
+ /// Advances the enumerator to the next element of the collection.
+ ///
+ ///
+ /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ public bool MoveNext()
+ {
+ if( m_version != m_generation.m_cache.m_version )
+ throw new InvalidOperationException();
+
+ while( m_currentIndex < m_generation.Count )
+ {
+ if( m_generation.m_elements[ m_currentIndex ].IsFree )
+ {
+ m_currentIndex++;
+ continue;
+ }
+
+ m_current = new KeyValuePair( m_generation.m_elements[ m_currentIndex ].Key,
+ m_generation.m_elements[ m_currentIndex ].Value );
+ m_currentIndex++;
+ return true;
+ }
+
+ m_current = new KeyValuePair();
+ return false;
+ }
+
+ ///
+ /// Sets the enumerator to its initial position, which is before the first element in the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ /// 2
+ public void Reset()
+ {
+ if( m_version != m_generation.m_cache.m_version )
+ throw new InvalidOperationException();
+
+ m_currentIndex = 0;
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ #region IGeneration Members
+
+ ///
+ /// Gets or sets a value indicating whether generation was accessed since last time check.
+ ///
+ public bool AccessedSinceLastTimeCheck
+ {
+ get { return m_accessedSinceLastTimeCheck; }
+
+ set { m_accessedSinceLastTimeCheck = value; }
+ }
+
+ ///
+ /// Gets element count in generation.
+ ///
+ public int Count
+ {
+ get { return m_nextUnusedElement - m_freeCount; }
+ }
+
+ ///
+ /// Gets or sets generation's expiration time.
+ ///
+ public DateTime ExpirationTime
+ {
+ get { return m_expirationTime1; }
+
+ set { m_expirationTime1 = value; }
+ }
+
+ ///
+ /// Gets or sets size of data stored to generation.
+ ///
+ public long Size
+ {
+ get { return m_size; }
+
+ private set { m_size = value; }
+ }
+
+ ///
+ /// Clear all elements from the generation and make generation new again.
+ ///
+ ///
+ /// When generation is new, it is allowed to add new elements to it and
+ /// doesn't remove elements from it.
+ ///
+ ///
+ public void Clear()
+ {
+ for( int i = m_buckets.Length - 1 ; i >= 0 ; i-- )
+ {
+ m_buckets[ i ] = -1;
+ }
+
+ Array.Clear( m_elements, 0, m_elements.Length );
+ Size = 0;
+ m_firstFreeElement = -1;
+ m_freeCount = 0;
+ m_nextUnusedElement = 0;
+ m_newGeneration = true;
+ ExpirationTime = DateTime.MaxValue;
+ }
+
+ ///
+ /// Determines whether the contains an element with the specific key.
+ ///
+ ///
+ /// The bucket index for the to locate in .
+ ///
+ ///
+ /// The key to locate in the .
+ ///
+ ///
+ /// if the contains an element with the ;
+ /// otherwise .
+ ///
+ public bool Contains( int bucketIndex, TKey key )
+ {
+ int previousIndex;
+ if( FindElementIndex( bucketIndex, key, true, out previousIndex ) == -1 )
+ return false;
+
+ AccessedSinceLastTimeCheck = true;
+ return true;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the elements stored .
+ ///
+ ///
+ /// A that can be used to iterate through the .
+ ///
+ /// 1
+ public IEnumerator> GetEnumerator()
+ {
+ return new Enumerator( this );
+ }
+
+ ///
+ /// Make from generation old generation.
+ ///
+ ///
+ /// When generation is old, hit removes element from the generation.
+ ///
+ ///
+ public void MakeOld()
+ {
+ m_newGeneration = false;
+ }
+
+ ///
+ /// Remove element associated with the key from the generation.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// , if remove was successful; otherwise .
+ ///
+ public bool Remove( int bucketIndex, TKey key )
+ {
+ int previousIndex;
+ int entryIndex = FindElementIndex( bucketIndex, key, false, out previousIndex );
+ if( entryIndex != -1 )
+ {
+ RemoveElement( bucketIndex, entryIndex, previousIndex );
+ AccessedSinceLastTimeCheck = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Set or add element to generation.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// The element's value.
+ ///
+ ///
+ /// The element's size.
+ ///
+ ///
+ /// , if setting or adding was successful; otherwise .
+ ///
+ ///
+ ///
+ /// If element was already existing in generation and new element size fits to collection limits,
+ /// then it's value is replaced with new one and size information is updated. If element didn't
+ /// exists in generation before, then generation must have empty space for a new element and
+ /// size must fit generation's limits, before element is added to generation.
+ ///
+ ///
+ public bool Set( int bucketIndex, TKey key, TValue value, long size )
+ {
+ Debug.Assert( m_newGeneration, "It is possible to insert new elements only to newest generation." );
+ Debug.Assert( size > 0, "New element size should be more than 0." );
+
+ int previousIndex;
+ int elementIndex = FindElementIndex( bucketIndex, key, true, out previousIndex );
+ if( elementIndex == -1 )
+ {
+ // New key
+ if( Size + size > m_cache.m_generationMaxSize ||
+ (m_nextUnusedElement == m_cache.m_generationElementCount && m_freeCount == 0) )
+ {
+ // Generation is full
+ return false;
+ }
+
+ // Increase size of generation
+ Size += size;
+
+ // Get first free entry and update free entry list
+ if( m_firstFreeElement != -1 )
+ {
+ // There was entry that was removed
+ elementIndex = m_firstFreeElement;
+ m_firstFreeElement = m_elements[ elementIndex ].Next;
+ m_freeCount--;
+ }
+ else
+ {
+ // No entries removed so far - just take a last one
+ elementIndex = m_nextUnusedElement;
+ m_nextUnusedElement++;
+ }
+
+ Debug.Assert( m_elements[ elementIndex ].IsFree, "Allocated element is not free." );
+
+ // Move new entry to front
+ m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
+ m_buckets[ bucketIndex ] = elementIndex;
+
+ // Set key and update count
+ m_elements[ elementIndex ].Key = key;
+ }
+ else
+ {
+ // Existing key
+ if( Size - m_elements[ elementIndex ].Size + size > m_cache.m_generationMaxSize )
+ {
+ // Generation is full
+ // Remove existing element, because generation is going to be recycled to
+ // old generation and element is stored to new generation
+ RemoveElement( bucketIndex, elementIndex, previousIndex );
+ return false;
+ }
+
+ // Update generation's size
+ Size = Size - m_elements[ elementIndex ].Size + size;
+ }
+
+ // Finally set value and size
+ m_elements[ elementIndex ].Value = value;
+ m_elements[ elementIndex ].Size = size;
+
+ // Success - key was inserterted to generation
+ AccessedSinceLastTimeCheck = true;
+ return true;
+ }
+
+ ///
+ /// Try to get element associated with key.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// The element's value.
+ ///
+ ///
+ /// The element's size.
+ ///
+ ///
+ /// , if element was successful retrieved; otherwise .
+ ///
+ ///
+ ///
+ /// If element is not found from generation then and
+ /// are set to default value (default(TValue) and 0).
+ ///
+ ///
+ public bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size )
+ {
+ // Find entry index,
+ int previousIndex;
+ int elementIndex = FindElementIndex( bucketIndex, key, m_newGeneration, out previousIndex );
+ if( elementIndex == -1 )
+ {
+ value = default(TValue);
+ size = 0;
+ return false;
+ }
+
+ value = m_elements[ elementIndex ].Value;
+ size = m_elements[ elementIndex ].Size;
+
+ if( !m_newGeneration )
+ {
+ // Old generation - remove element, because it is moved to new generation
+ RemoveElement( bucketIndex, elementIndex, previousIndex );
+ }
+
+ AccessedSinceLastTimeCheck = true;
+ return true;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ /// 2
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ #region Nested type: IGeneration
+
+ ///
+ /// Cache element generation interface
+ ///
+ ///
+ ///
+ /// Generation can hold limited count of elements and limited size of data.
+ ///
+ ///
+ /// There are two kind generations: "new generation" and "old generation(s)". All new elements
+ /// are added to "new generation".
+ ///
+ ///
+ protected interface IGeneration : IEnumerable>
+ {
+ ///
+ /// Gets or sets a value indicating whether generation was accessed since last time check.
+ ///
+ bool AccessedSinceLastTimeCheck { get; set; }
+
+ ///
+ /// Gets element count in generation.
+ ///
+ int Count { get; }
+
+ ///
+ /// Gets or sets generation's expiration time.
+ ///
+ DateTime ExpirationTime { get; set; }
+
+ ///
+ /// Gets size of data stored to generation.
+ ///
+ long Size { get; }
+
+ ///
+ /// Clear all elements from the generation and make generation new again.
+ ///
+ ///
+ /// When generation is new, it is allowed to add new elements to it and
+ /// doesn't remove elements from it.
+ ///
+ ///
+ void Clear();
+
+ ///
+ /// Determines whether the contains an element with the specific key.
+ ///
+ ///
+ /// The bucket index for the to locate in .
+ ///
+ ///
+ /// The key to locate in the .
+ ///
+ ///
+ /// if the contains an element with the ;
+ /// otherwise .
+ ///
+ bool Contains( int bucketIndex, TKey key );
+
+ ///
+ /// Make from generation old generation.
+ ///
+ ///
+ /// When generation is old, hit removes element from the generation.
+ ///
+ ///
+ void MakeOld();
+
+ ///
+ /// Remove element associated with the key from the generation.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// , if remove was successful; otherwise .
+ ///
+ bool Remove( int bucketIndex, TKey key );
+
+ ///
+ /// Set or add element to generation.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// The element's value.
+ ///
+ ///
+ /// The element's size.
+ ///
+ ///
+ /// , if setting or adding was successful; otherwise .
+ ///
+ ///
+ ///
+ /// If element was already existing in generation and new element size fits to collection limits,
+ /// then it's value is replaced with new one and size information is updated. If element didn't
+ /// exists in generation before, then generation must have empty space for a new element and
+ /// size must fit generation's limits, before element is added to generation.
+ ///
+ ///
+ bool Set( int bucketIndex, TKey key, TValue value, long size );
+
+ ///
+ /// Try to get element associated with key.
+ ///
+ ///
+ /// The element's bucket index.
+ ///
+ ///
+ /// The element's key.
+ ///
+ ///
+ /// The element's value.
+ ///
+ ///
+ /// The element's size.
+ ///
+ ///
+ /// , if element was successful retrieved; otherwise .
+ ///
+ ///
+ ///
+ /// If element is not found from generation then and
+ /// are set to default value (default(TValue) and 0).
+ ///
+ ///
+ bool TryGetValue( int bucketIndex, TKey key, out TValue value, out long size );
+ }
+
+ #endregion
+
+ #region ICnmCache Members
+
+ ///
+ /// Gets current count of elements stored to .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int Count
+ {
+ get { return m_newGeneration.Count + m_oldGeneration.Count; }
+ }
+
+ ///
+ /// Gets or sets elements expiration time.
+ ///
+ ///
+ /// Elements expiration time.
+ ///
+ ///
+ ///
+ /// When element has been stored in longer than
+ /// and it is not accessed through method or element's value is
+ /// not replaced by method, then it is automatically removed from the
+ /// .
+ ///
+ ///
+ /// It is possible that implementation removes element before it's expiration time,
+ /// because total size or count of elements stored to cache is larger than or .
+ ///
+ ///
+ /// It is also possible that element stays in cache longer than .
+ ///
+ ///
+ /// Calling try to remove all elements that are expired.
+ ///
+ ///
+ /// To disable time limit in cache, set to .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public TimeSpan ExpirationTime
+ {
+ get { return m_expirationTime; }
+
+ set
+ {
+ if( value < MinExpirationTime )
+ value = MinExpirationTime;
+
+ if( m_expirationTime == value )
+ return;
+
+ m_newGeneration.ExpirationTime = (m_newGeneration.ExpirationTime - m_expirationTime) + value;
+ m_oldGeneration.ExpirationTime = (m_oldGeneration.ExpirationTime - m_expirationTime) + value;
+ m_expirationTime = value;
+
+ PurgeExpired();
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether is limiting count of elements.
+ ///
+ ///
+ /// if the count of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsCountLimited
+ {
+ get { return true; }
+ }
+
+ ///
+ /// Gets a value indicating whether is limiting size of elements.
+ ///
+ ///
+ /// if the total size of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsSizeLimited
+ {
+ get { return true; }
+ }
+
+ ///
+ /// Gets a value indicating whether or not access to the is synchronized (thread safe).
+ ///
+ ///
+ /// if access to the is synchronized (thread safe);
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to object, use
+ /// in class
+ /// to retrieve synchronized wrapper for object.
+ ///
+ ///
+ ///
+ ///
+ public bool IsSynchronized
+ {
+ get { return false; }
+ }
+
+ ///
+ /// Gets a value indicating whether elements stored to have limited inactivity time.
+ ///
+ ///
+ /// if the has a fixed total size of elements;
+ /// otherwise, .
+ ///
+ ///
+ /// If have limited inactivity time and element is not accessed through
+ /// or methods in , then element is automatically removed from
+ /// the cache. Depending on implementation of the , some of the elements may
+ /// stay longer in cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsTimeLimited
+ {
+ get { return ExpirationTime != TimeSpan.MaxValue; }
+ }
+
+ ///
+ /// Gets or sets maximal allowed count of elements that can be stored to .
+ ///
+ ///
+ /// , if is not limited by count of elements;
+ /// otherwise maximal allowed count of elements.
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ public int MaxCount
+ {
+ get { return m_maxCount; }
+
+ set
+ {
+ if( value < 8 )
+ value = 8;
+ if( m_maxCount == value )
+ return;
+
+ m_maxCount = value;
+ Initialize();
+ }
+ }
+
+ ///
+ /// Gets maximal allowed element size.
+ ///
+ ///
+ /// Maximal allowed element size.
+ ///
+ ///
+ ///
+ /// If element's size is larger than , then element is
+ /// not added to the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public long MaxElementSize
+ {
+ get { return m_maxElementSize; }
+
+ private set { m_maxElementSize = value; }
+ }
+
+ ///
+ /// Gets or sets maximal allowed total size for elements stored to .
+ ///
+ ///
+ /// Maximal allowed total size for elements stored to .
+ ///
+ ///
+ ///
+ /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public long MaxSize
+ {
+ get { return m_maxSize; }
+
+ set
+ {
+ if( value < 8 )
+ value = 8;
+ if( m_maxSize == value )
+ return;
+
+ m_maxSize = value;
+ Initialize();
+ }
+ }
+
+ ///
+ /// Gets total size of elements stored to .
+ ///
+ ///
+ /// Total size of elements stored to .
+ ///
+ ///
+ ///
+ /// Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// Element's size is given when element is added or replaced by method.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public long Size
+ {
+ get { return m_newGeneration.Size + m_oldGeneration.Size; }
+ }
+
+ ///
+ /// Gets an object that can be used to synchronize access to the .
+ ///
+ ///
+ /// An object that can be used to synchronize access to the .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to , use
+ /// method to retrieve synchronized wrapper interface to
+ /// .
+ ///
+ ///
+ ///
+ ///
+ public object SyncRoot
+ {
+ get { return m_syncRoot; }
+ }
+
+ ///
+ /// Removes all elements from the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Clear()
+ {
+ m_newGeneration.Clear();
+ m_oldGeneration.Clear();
+ m_oldGeneration.MakeOld();
+ m_version++;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the elements stored to .
+ ///
+ ///
+ /// A that can be used to iterate through the collection.
+ ///
+ /// 1
+ public IEnumerator> GetEnumerator()
+ {
+ return new Enumerator( this );
+ }
+
+ ///
+ /// Purge expired elements from the .
+ ///
+ ///
+ ///
+ /// Element becomes expired when last access time to it has been longer time than .
+ ///
+ ///
+ /// Depending on implementation, some of expired elements
+ /// may stay longer than in the cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void PurgeExpired()
+ {
+ m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
+
+ if( !IsTimeLimited )
+ return;
+
+ DateTime now = DateTime.Now;
+ if( m_newGeneration.AccessedSinceLastTimeCheck )
+ {
+ // New generation has been accessed since last check
+ // Update it's expiration time.
+ m_newGeneration.ExpirationTime = now + ExpirationTime;
+ m_newGeneration.AccessedSinceLastTimeCheck = false;
+ }
+ else if( m_newGeneration.ExpirationTime < now )
+ {
+ // New generation has been expired.
+ // --> also old generation must be expired.
+ PurgeGeneration( m_newGeneration );
+ PurgeGeneration( m_oldGeneration );
+ return;
+ }
+
+ if( m_oldGeneration.ExpirationTime < now )
+ PurgeGeneration( m_oldGeneration );
+ }
+
+ ///
+ /// Removes element associated with from the .
+ ///
+ ///
+ /// The key that is associated with element to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Remove( TKey key )
+ {
+ if( key == null )
+ throw new ArgumentNullException( "key" );
+
+ int bucketIndex = GetBucketIndex( key );
+ if( !m_newGeneration.Remove( bucketIndex, key ) )
+ {
+ if( !m_oldGeneration.Remove( bucketIndex, key ) )
+ {
+ CheckExpired();
+ return;
+ }
+ }
+
+ CheckExpired();
+ m_version++;
+ }
+
+ ///
+ /// Removes elements that are associated with one of from the .
+ ///
+ ///
+ /// The keys that are associated with elements to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RemoveRange( IEnumerable keys )
+ {
+ if( keys == null )
+ throw new ArgumentNullException( "keys" );
+
+ foreach( TKey key in keys )
+ {
+ if( key == null )
+ continue;
+
+ int bucketIndex = GetBucketIndex( key );
+ if( !m_newGeneration.Remove( bucketIndex, key ) )
+ m_oldGeneration.Remove( bucketIndex, key );
+ }
+
+ CheckExpired();
+ m_version++;
+ }
+
+ ///
+ /// Add or replace an element with the provided , and to
+ /// .
+ ///
+ ///
+ /// The object used as the key of the element. Can't be reference.
+ ///
+ ///
+ /// The object used as the value of the element to add or replace. is allowed.
+ ///
+ ///
+ /// The element's size. Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// if element has been added successfully to the ;
+ /// otherwise .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The element's is less than 0.
+ ///
+ ///
+ ///
+ /// If element's is larger than , then element is
+ /// not added to the , however - possible older element is
+ /// removed from the .
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Set( TKey key, TValue value, long size )
+ {
+ if( key == null )
+ throw new ArgumentNullException( "key" );
+
+ if( size < 0 )
+ throw new ArgumentOutOfRangeException( "size", size, "Value's size can't be less than 0." );
+
+ if( size > MaxElementSize )
+ {
+ // Entry size is too big to fit cache - ignore it
+ Remove( key );
+ return false;
+ }
+
+ if( size == 0 )
+ size = 1;
+
+ int bucketIndex = GetBucketIndex( key );
+ m_oldGeneration.Remove( bucketIndex, key );
+ AddToNewGeneration( bucketIndex, key, value, size );
+ CheckExpired();
+
+ return true;
+ }
+
+ ///
+ /// Gets the associated with the specified .
+ ///
+ ///
+ /// if the contains an element with
+ /// the specified key; otherwise, .
+ ///
+ ///
+ /// The key whose to get.
+ ///
+ ///
+ /// When this method returns, the value associated with the specified ,
+ /// if the is found; otherwise, the
+ /// default value for the type of the parameter. This parameter is passed uninitialized.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetValue( TKey key, out TValue value )
+ {
+ if( key == null )
+ throw new ArgumentNullException( "key" );
+
+ int bucketIndex = GetBucketIndex( key );
+ long size;
+ if( m_newGeneration.TryGetValue( bucketIndex, key, out value, out size ) )
+ {
+ CheckExpired();
+ return true;
+ }
+
+ if( m_oldGeneration.TryGetValue( bucketIndex, key, out value, out size ) )
+ {
+ // Move element to new generation
+ AddToNewGeneration( bucketIndex, key, value, size );
+ CheckExpired();
+ return true;
+ }
+
+ CheckExpired();
+ return false;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ /// 2
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/OpenSim/Framework/CnmSynchronizedCache.cs b/OpenSim/Framework/CnmSynchronizedCache.cs
new file mode 100644
index 0000000..418a095
--- /dev/null
+++ b/OpenSim/Framework/CnmSynchronizedCache.cs
@@ -0,0 +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
+{
+ ///
+ /// Synchronized Cenome cache wrapper.
+ ///
+ ///
+ /// The type of keys in the cache.
+ ///
+ ///
+ /// The type of values in the cache.
+ ///
+ ///
+ ///
+ /// Enumerator will block other threads, until enumerator's method is called.
+ /// "foreach" statement is automatically calling it.
+ ///
+ ///
+ public class CnmSynchronizedCache : ICnmCache
+ {
+ ///
+ /// The cache object.
+ ///
+ private readonly ICnmCache m_cache;
+
+ ///
+ /// Synchronization root.
+ ///
+ private readonly object m_syncRoot;
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The cache.
+ ///
+ private CnmSynchronizedCache( ICnmCache cache )
+ {
+ m_cache = cache;
+ m_syncRoot = m_cache.SyncRoot;
+ }
+
+ ///
+ /// Returns a wrapper that is synchronized (thread safe).
+ ///
+ ///
+ /// The to synchronize.
+ ///
+ ///
+ /// A wrapper that is synchronized (thread safe).
+ ///
+ ///
+ /// is null.
+ ///
+ public static ICnmCache Synchronized( ICnmCache cache )
+ {
+ if( cache == null )
+ throw new ArgumentNullException( "cache" );
+ return cache.IsSynchronized ? cache : new CnmSynchronizedCache( cache );
+ }
+
+ #region Nested type: SynchronizedEnumerator
+
+ ///
+ /// Synchronized enumerator.
+ ///
+ private class SynchronizedEnumerator : IEnumerator>
+ {
+ ///
+ /// Enumerator that is being synchronized.
+ ///
+ private readonly IEnumerator> m_enumerator;
+
+ ///
+ /// Synchronization root.
+ ///
+ private object m_syncRoot;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The enumerator that is being synchronized.
+ ///
+ ///
+ /// The sync root.
+ ///
+ public SynchronizedEnumerator( IEnumerator> enumerator, object syncRoot )
+ {
+ m_syncRoot = syncRoot;
+ m_enumerator = enumerator;
+ Monitor.Enter( m_syncRoot );
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~SynchronizedEnumerator()
+ {
+ Dispose();
+ }
+
+ #region IEnumerator> Members
+
+ ///
+ /// Gets the element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The element in the collection at the current position of the enumerator.
+ ///
+ ///
+ /// The enumerator has reach end of collection or is not called.
+ ///
+ public KeyValuePair Current
+ {
+ get { return m_enumerator.Current; }
+ }
+
+ ///
+ /// Gets the current element in the collection.
+ ///
+ ///
+ /// The current element in the collection.
+ ///
+ ///
+ /// The enumerator is positioned before the first element of the collection or after the last element.
+ /// 2
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ ///
+ /// Releases synchronization lock.
+ ///
+ public void Dispose()
+ {
+ if( m_syncRoot != null )
+ {
+ Monitor.Exit( m_syncRoot );
+ m_syncRoot = null;
+ }
+
+ m_enumerator.Dispose();
+ GC.SuppressFinalize( this );
+ }
+
+ ///
+ /// Advances the enumerator to the next element of the collection.
+ ///
+ ///
+ /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ public bool MoveNext()
+ {
+ return m_enumerator.MoveNext();
+ }
+
+ ///
+ /// Sets the enumerator to its initial position, which is before the first element in the collection.
+ ///
+ ///
+ /// The collection was modified after the enumerator was created.
+ ///
+ public void Reset()
+ {
+ m_enumerator.Reset();
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ #region ICnmCache Members
+
+ ///
+ /// Gets current count of elements stored to .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int Count
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.Count;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets elements expiration time.
+ ///
+ ///
+ /// Elements expiration time.
+ ///
+ ///
+ ///
+ /// When element has been stored in longer than
+ /// and it is not accessed through method or element's value is
+ /// not replaced by method, then it is automatically removed from the
+ /// .
+ ///
+ ///
+ /// It is possible that implementation removes element before it's expiration time,
+ /// because total size or count of elements stored to cache is larger than or .
+ ///
+ ///
+ /// It is also possible that element stays in cache longer than .
+ ///
+ ///
+ /// Calling try to remove all elements that are expired.
+ ///
+ ///
+ /// To disable time limit in cache, set to .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public TimeSpan ExpirationTime
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.ExpirationTime;
+ }
+ }
+
+ set
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.ExpirationTime = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether is limiting count of elements.
+ ///
+ ///
+ /// if the count of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsCountLimited
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.IsCountLimited;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether is limiting size of elements.
+ ///
+ ///
+ /// if the total size of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsSizeLimited
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.IsSizeLimited;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether or not access to the is synchronized (thread safe).
+ ///
+ ///
+ /// if access to the is synchronized (thread safe);
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to object, use
+ /// in class
+ /// to retrieve synchronized wrapper for object.
+ ///
+ ///
+ ///
+ ///
+ public bool IsSynchronized
+ {
+ get { return true; }
+ }
+
+ ///
+ /// Gets a value indicating whether elements stored to have limited inactivity time.
+ ///
+ ///
+ /// if the has a fixed total size of elements;
+ /// otherwise, .
+ ///
+ ///
+ /// If have limited inactivity time and element is not accessed through
+ /// or methods in , then element is automatically removed from
+ /// the cache. Depending on implementation of the , some of the elements may
+ /// stay longer in cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool IsTimeLimited
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.IsTimeLimited;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets maximal allowed count of elements that can be stored to .
+ ///
+ ///
+ /// , if is not limited by count of elements;
+ /// otherwise maximal allowed count of elements.
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ public int MaxCount
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.MaxCount;
+ }
+ }
+
+ set
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.MaxCount = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets maximal allowed element size.
+ ///
+ ///
+ /// Maximal allowed element size.
+ ///
+ ///
+ ///
+ /// If element's size is larger than , then element is
+ /// not added to the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public long MaxElementSize
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.MaxElementSize;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets maximal allowed total size for elements stored to .
+ ///
+ ///
+ /// Maximal allowed total size for elements stored to .
+ ///
+ ///
+ ///
+ /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ /// value is less than 0.
+ ///
+ ///
+ ///
+ public long MaxSize
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.MaxSize;
+ }
+ }
+
+ set
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.MaxSize = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets total size of elements stored to .
+ ///
+ ///
+ /// Total size of elements stored to .
+ ///
+ ///
+ ///
+ /// Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// Element's size is given when element is added or replaced by method.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public long Size
+ {
+ get
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.Size;
+ }
+ }
+ }
+
+ ///
+ /// Gets an object that can be used to synchronize access to the .
+ ///
+ ///
+ /// An object that can be used to synchronize access to the .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to , use
+ /// method to retrieve synchronized wrapper interface to
+ /// .
+ ///
+ ///
+ ///
+ ///
+ public object SyncRoot
+ {
+ get { return m_syncRoot; }
+ }
+
+ ///
+ /// Removes all elements from the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Clear()
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.Clear();
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the elements stored to .
+ ///
+ ///
+ /// A that can be used to iterate through the collection.
+ ///
+ /// 1
+ public IEnumerator> GetEnumerator()
+ {
+ lock( m_syncRoot )
+ {
+ return new SynchronizedEnumerator( m_cache.GetEnumerator(), m_syncRoot );
+ }
+ }
+
+ ///
+ /// Purge expired elements from the .
+ ///
+ ///
+ ///
+ /// Element becomes expired when last access time to it has been longer time than .
+ ///
+ ///
+ /// Depending on implementation, some of expired elements
+ /// may stay longer than in the cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void PurgeExpired()
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.PurgeExpired();
+ }
+ }
+
+ ///
+ /// Removes element associated with from the .
+ ///
+ ///
+ /// The key that is associated with element to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Remove( TKey key )
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.Remove( key );
+ }
+ }
+
+ ///
+ /// Removes elements that are associated with one of from the .
+ ///
+ ///
+ /// The keys that are associated with elements to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void RemoveRange( IEnumerable keys )
+ {
+ lock( m_syncRoot )
+ {
+ m_cache.RemoveRange( keys );
+ }
+ }
+
+ ///
+ /// Add or replace an element with the provided , and to
+ /// .
+ ///
+ ///
+ /// The object used as the key of the element. Can't be reference.
+ ///
+ ///
+ /// The object used as the value of the element to add or replace. is allowed.
+ ///
+ ///
+ /// The element's size. Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// if element has been added successfully to the ;
+ /// otherwise .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The element's is less than 0.
+ ///
+ ///
+ ///
+ /// If element's is larger than , then element is
+ /// not added to the , however - possible older element is
+ /// removed from the .
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Set( TKey key, TValue value, long size )
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.Set( key, value, size );
+ }
+ }
+
+ ///
+ /// Gets the associated with the specified .
+ ///
+ ///
+ /// if the contains an element with
+ /// the specified key; otherwise, .
+ ///
+ ///
+ /// The key whose to get.
+ ///
+ ///
+ /// When this method returns, the value associated with the specified ,
+ /// if the is found; otherwise, the
+ /// default value for the type of the parameter. This parameter is passed uninitialized.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetValue( TKey key, out TValue value )
+ {
+ lock( m_syncRoot )
+ {
+ return m_cache.TryGetValue( key, out value );
+ }
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the elements stored to .
+ ///
+ ///
+ /// A that can be used to iterate through the collection.
+ ///
+ /// 1
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/OpenSim/Framework/ICnmCache.cs b/OpenSim/Framework/ICnmCache.cs
new file mode 100644
index 0000000..cba8a7f
--- /dev/null
+++ b/OpenSim/Framework/ICnmCache.cs
@@ -0,0 +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
+{
+ ///
+ /// Represent generic cache to store key/value pairs (elements) limited by time, size and count of elements.
+ ///
+ ///
+ /// The type of keys in the cache.
+ ///
+ ///
+ /// The type of values in the cache.
+ ///
+ ///
+ ///
+ /// Cache store limitations:
+ ///
+ ///
+ ///
+ /// Limitation
+ /// Description
+ ///
+ /// -
+ /// Time
+ ///
+ /// Element that is not accessed through or in last are
+ /// removed from the cache automatically. Depending on implementation of the cache some of elements may stay longer in cache.
+ /// returns , if cache is limited by time.
+ ///
+ ///
+ /// -
+ /// Count
+ ///
+ /// When adding an new element to cache that already have of elements, cache will remove less recently
+ /// used element(s) from the cache, until element fits to cache.
+ /// returns , if cache is limiting element count.
+ ///
+ ///
+ /// -
+ /// Size
+ ///
+ ///
+ /// When adding an new element to cache that already have of elements, cache will remove less recently
+ /// used element(s) from the cache, until element fits to cache.
+ /// returns , 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.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public interface ICnmCache : IEnumerable>
+ {
+ ///
+ /// Gets current count of elements stored to .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ int Count { get; }
+
+ ///
+ /// Gets or sets elements expiration time.
+ ///
+ ///
+ /// Elements expiration time.
+ ///
+ ///
+ ///
+ /// When element has been stored in longer than
+ /// and it is not accessed through method or element's value is
+ /// not replaced by method, then it is automatically removed from the
+ /// .
+ ///
+ ///
+ /// It is possible that implementation removes element before it's expiration time,
+ /// because total size or count of elements stored to cache is larger than or .
+ ///
+ ///
+ /// It is also possible that element stays in cache longer than .
+ ///
+ ///
+ /// Calling try to remove all elements that are expired.
+ ///
+ ///
+ /// To disable time limit in cache, set to .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ TimeSpan ExpirationTime { get; set; }
+
+ ///
+ /// Gets a value indicating whether or not access to the is synchronized (thread safe).
+ ///
+ ///
+ /// if access to the is synchronized (thread safe);
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to object, use
+ /// in class
+ /// to retrieve synchronized wrapper for object.
+ ///
+ ///
+ ///
+ ///
+ bool IsSynchronized { get; }
+
+ ///
+ /// Gets a value indicating whether is limiting count of elements.
+ ///
+ ///
+ /// if the count of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool IsCountLimited { get; }
+
+ ///
+ /// Gets a value indicating whether is limiting size of elements.
+ ///
+ ///
+ /// if the total size of elements is limited;
+ /// otherwise, .
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool IsSizeLimited { get; }
+
+ ///
+ /// Gets a value indicating whether elements stored to have limited inactivity time.
+ ///
+ ///
+ /// if the has a fixed total size of elements;
+ /// otherwise, .
+ ///
+ ///
+ /// If have limited inactivity time and element is not accessed through
+ /// or methods in , then element is automatically removed from
+ /// the cache. Depending on implementation of the , some of the elements may
+ /// stay longer in cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool IsTimeLimited { get; }
+
+ ///
+ /// Gets or sets maximal allowed count of elements that can be stored to .
+ ///
+ ///
+ /// , if is not limited by count of elements;
+ /// otherwise maximal allowed count of elements.
+ ///
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ int MaxCount { get; set; }
+
+ ///
+ /// Gets maximal allowed element size.
+ ///
+ ///
+ /// Maximal allowed element size.
+ ///
+ ///
+ ///
+ /// If element's size is larger than , then element is
+ /// not added to the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ long MaxElementSize { get; }
+
+ ///
+ /// Gets or sets maximal allowed total size for elements stored to .
+ ///
+ ///
+ /// Maximal allowed total size for elements stored to .
+ ///
+ ///
+ ///
+ /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ /// value is less than 0.
+ ///
+ ///
+ ///
+ long MaxSize { get; set; }
+
+ ///
+ /// Gets total size of elements stored to .
+ ///
+ ///
+ /// Total size of elements stored to .
+ ///
+ ///
+ ///
+ /// Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// Element's size is given when element is added or replaced by method.
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ long Size { get; }
+
+ ///
+ /// Gets an object that can be used to synchronize access to the .
+ ///
+ ///
+ /// An object that can be used to synchronize access to the .
+ ///
+ ///
+ ///
+ /// To get synchronized (thread safe) access to , use
+ /// method to retrieve synchronized wrapper interface to
+ /// .
+ ///
+ ///
+ ///
+ ///
+ object SyncRoot { get; }
+
+ ///
+ /// Removes all elements from the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void Clear();
+
+ ///
+ /// Purge expired elements from the .
+ ///
+ ///
+ ///
+ /// Element becomes expired when last access time to it has been longer time than .
+ ///
+ ///
+ /// Depending on implementation, some of expired elements
+ /// may stay longer than in the cache.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void PurgeExpired();
+
+ ///
+ /// Removes element associated with from the .
+ ///
+ ///
+ /// The key that is associated with element to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void Remove( TKey key );
+
+ ///
+ /// Removes elements that are associated with one of from the .
+ ///
+ ///
+ /// The keys that are associated with elements to remove from the .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void RemoveRange( IEnumerable keys );
+
+ ///
+ /// Add or replace an element with the provided , and to
+ /// .
+ ///
+ ///
+ /// The object used as the key of the element. Can't be reference.
+ ///
+ ///
+ /// The object used as the value of the element to add or replace. is allowed.
+ ///
+ ///
+ /// The element's size. Normally bytes, but can be any suitable unit of measure.
+ ///
+ ///
+ /// if element has been added successfully to the ;
+ /// otherwise .
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The element's is less than 0.
+ ///
+ ///
+ ///
+ /// If element's is larger than , then element is
+ /// not added to the , however - possible older element is
+ /// removed from the .
+ ///
+ ///
+ /// When adding an new element to that is limiting total size of elements,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ /// When adding an new element to that is limiting element count,
+ /// will remove less recently used elements until it can fit an new element.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool Set( TKey key, TValue value, long size );
+
+ ///
+ /// Gets the associated with the specified .
+ ///
+ ///
+ /// if the contains an element with
+ /// the specified key; otherwise, .
+ ///
+ ///
+ /// The key whose to get.
+ ///
+ ///
+ /// When this method returns, the value associated with the specified ,
+ /// if the is found; otherwise, the
+ /// default value for the type of the parameter. This parameter is passed uninitialized.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool TryGetValue( TKey key, out TValue value );
+ }
+}
diff --git a/OpenSim/Framework/PrimeNumberHelper.cs b/OpenSim/Framework/PrimeNumberHelper.cs
new file mode 100644
index 0000000..e4bf615
--- /dev/null
+++ b/OpenSim/Framework/PrimeNumberHelper.cs
@@ -0,0 +1,98 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+//
+//
+//
+//
+//
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+using System;
+
+namespace OpenSim.Framework
+{
+ ///
+ /// Utility class that is used to find small prime numbers and test is number prime number.
+ ///
+ public static class PrimeNumberHelper
+ {
+ ///
+ /// Precalculated prime numbers.
+ ///
+ 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
+ };
+
+ ///
+ /// Get prime number that is equal or larger than .
+ ///
+ ///
+ /// Minimal returned prime number.
+ ///
+ ///
+ /// Primer number that is equal or larger than . If is too large, return -1.
+ ///
+ 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;
+ }
+
+ ///
+ /// Just basic Sieve of Eratosthenes prime number test.
+ ///
+ ///
+ /// Number that is tested.
+ ///
+ ///
+ /// true, if is prime number; otherwise false.
+ ///
+ 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
new file mode 100644
index 0000000..00a8143
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs
@@ -0,0 +1,382 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+//
+//
+//
+//
+//
+//
+// --------------------------------------------------------------------------------------------------------------------
+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
+{
+ ///
+ /// Cenome memory asset cache.
+ ///
+ ///
+ ///
+ /// 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)".
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ ///
+ /// Configuration
+ /// Description
+ ///
+ /// -
+ /// MaxSize
+ /// Maximal size of the cache in bytes. Default value: 128MB (134 217 728 bytes).
+ ///
+ /// -
+ /// MaxCount
+ /// Maximal count of assets stored to cache. Default value: 4096 assets.
+ ///
+ /// -
+ /// ExpirationTime
+ /// Asset's expiration time in minutes. Default value: 30 minutes.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Enabling Cenome Asset Cache:
+ ///
+ /// [Modules]
+ /// AssetCaching = "CenomeMemoryAssetCache"
+ ///
+ /// Setting size and expiration time limitations:
+ ///
+ /// [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
+ ///
+ ///
+ public class CenomeMemoryAssetCache : IImprovedAssetCache, ISharedRegionModule
+ {
+ ///
+ /// Cache's default maximal asset count.
+ ///
+ ///
+ ///
+ /// Assuming that average asset size is about 32768 bytes.
+ ///
+ ///
+ public const int DefaultMaxCount = 4096;
+
+ ///
+ /// Default maximal size of the cache in bytes
+ ///
+ ///
+ ///
+ /// 128MB = 128 * 1024^2 = 134 217 728 bytes.
+ ///
+ ///
+ public const long DefaultMaxSize = 134217728;
+
+ ///
+ /// Asset's default expiration time in the cache.
+ ///
+ public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes( 30.0 );
+
+ ///
+ /// Log manager instance.
+ ///
+ private static readonly ILog Log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType );
+
+ ///
+ /// Cache object.
+ ///
+ private ICnmCache m_cache;
+
+ ///
+ /// Count of cache commands
+ ///
+ private int m_cachedCount;
+
+ ///
+ /// How many gets before dumping statistics
+ ///
+ ///
+ /// If 0 or less, then disabled.
+ ///
+ private int m_debugEpoch;
+
+ ///
+ /// Is Cenome asset cache enabled.
+ ///
+ private bool m_enabled;
+
+ ///
+ /// Count of get requests
+ ///
+ private int m_getCount;
+
+ ///
+ /// How many hits
+ ///
+ private int m_hitCount;
+
+ ///
+ /// Initialize asset cache module with default parameters.
+ ///
+ public void Initialize()
+ {
+ Initialize( DefaultMaxSize, DefaultMaxCount, DefaultExpirationTime );
+ }
+
+ ///
+ /// Initialize asset cache module, with custom parameters.
+ ///
+ ///
+ /// Cache's maximal size in bytes.
+ ///
+ ///
+ /// Cache's maximal count of assets.
+ ///
+ ///
+ /// Asset's expiration time.
+ ///
+ 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.Synchronized( new CnmMemoryCache(
+ 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
+
+ ///
+ /// Cache asset.
+ ///
+ ///
+ /// The asset that is being cached.
+ ///
+ public void Cache( AssetBase asset )
+ {
+ long size = asset.Data != null ? asset.Data.Length : 1;
+ m_cache.Set( asset.ID, asset, size );
+ m_cachedCount++;
+ }
+
+ ///
+ /// Clear asset cache.
+ ///
+ public void Clear()
+ {
+ m_cache.Clear();
+ }
+
+ ///
+ /// Expire (remove) asset stored to cache.
+ ///
+ ///
+ /// The expired asset's id.
+ ///
+ public void Expire( string id )
+ {
+ m_cache.Remove( id );
+ }
+
+ ///
+ /// Get asset stored
+ ///
+ ///
+ /// The asset's id.
+ ///
+ ///
+ /// Asset if it is found from cache; otherwise .
+ ///
+ ///
+ ///
+ /// Caller should always check that is return value .
+ /// Cache doesn't guarantee in any situation that asset is stored to it.
+ ///
+ ///
+ 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
+
+ ///
+ /// Gets region module's name.
+ ///
+ public string Name
+ {
+ get { return "CenomeMemoryAssetCache"; }
+ }
+
+ ///
+ /// New region is being added to server.
+ ///
+ ///
+ /// Region's scene.
+ ///
+ public void AddRegion( Scene scene )
+ {
+ if( m_enabled )
+ scene.RegisterModuleInterface( this );
+ }
+
+ ///
+ /// Close region module.
+ ///
+ public void Close()
+ {
+ m_enabled = false;
+ m_cache.Clear();
+ m_cache = null;
+ }
+
+ ///
+ /// Initialize region module.
+ ///
+ ///
+ /// Configuration source.
+ ///
+ 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 );
+ }
+
+ ///
+ /// Initialization post handling.
+ ///
+ ///
+ ///
+ /// Modules can use this to initialize connection with other modules.
+ ///
+ ///
+ public void PostInitialise()
+ {
+ }
+
+ ///
+ /// Region has been loaded.
+ ///
+ ///
+ /// Region's scene.
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ public void RegionLoaded( Scene scene )
+ {
+ }
+
+ ///
+ /// Region is being removed.
+ ///
+ ///
+ /// Region scene that is being removed.
+ ///
+ public void RemoveRegion( Scene scene )
+ {
+ }
+
+ #endregion
+ }
+}
diff --git a/OpenSim/Region/CoreModules/Resources/CoreModulePlugin.addin.xml b/OpenSim/Region/CoreModules/Resources/CoreModulePlugin.addin.xml
index 393f340..9969ebe 100644
--- a/OpenSim/Region/CoreModules/Resources/CoreModulePlugin.addin.xml
+++ b/OpenSim/Region/CoreModules/Resources/CoreModulePlugin.addin.xml
@@ -19,6 +19,7 @@
+
--
cgit v1.1