29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Diagnostics;
33 namespace OpenSim.Framework
52 public class CnmMemoryCache<TKey, TValue> : ICnmCache<TKey, TValue>
58 public const int DefaultMaxCount = 4096;
69 public const long DefaultMaxSize = 134217728;
74 private const int DefaultOperationsBetweenTimeChecks = 40;
84 public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes(30.0);
94 public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds(10.0);
108 private TimeSpan m_expirationTime = DefaultExpirationTime;
113 private int m_generationBucketCount;
118 private int m_generationElementCount;
123 private long m_generationMaxSize;
128 private int m_maxCount;
133 private long m_maxElementSize;
138 private long m_maxSize;
143 private IGeneration m_newGeneration;
148 private IGeneration m_oldGeneration;
153 private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
158 private readonly
object m_syncRoot =
new object();
168 private int m_version;
174 : this(DefaultMaxSize)
185 : this(maximalSize, DefaultMaxCount)
199 : this(maximalSize, maximalCount, DefaultExpirationTime)
215 public CnmMemoryCache(
long maximalSize,
int maximalCount, TimeSpan expirationTime)
216 : this(maximalSize, maximalCount, expirationTime, EqualityComparer<TKey>.Default)
240 TimeSpan expirationTime,
241 IEqualityComparer<TKey> comparer)
243 if (comparer == null)
244 throw new ArgumentNullException(
"comparer");
246 if (expirationTime < MinExpirationTime)
247 expirationTime = MinExpirationTime;
248 if (maximalCount < 8)
252 if (maximalCount > maximalSize)
253 maximalCount = (int) maximalSize;
256 m_expirationTime = expirationTime;
257 m_maxSize = maximalSize;
258 m_maxCount = maximalCount;
281 if (!m_newGeneration.Set(bucketIndex, key, value, size))
284 RecycleGenerations();
285 m_newGeneration.Set(bucketIndex,
key, value, size);
319 return (Comparer.GetHashCode(key) & 0x7FFFFFFF) % m_generationBucketCount;
337 private void CheckExpired()
341 m_operationsBetweenTimeChecks--;
342 if (m_operationsBetweenTimeChecks <= 0)
349 private void Initialize()
353 m_generationMaxSize = MaxSize / 2;
354 MaxElementSize = MaxSize / 8;
355 m_generationElementCount = MaxCount / 2;
358 m_generationBucketCount = PrimeNumberHelper.GetPrime(m_generationElementCount);
360 m_newGeneration =
new HashGeneration(
this);
361 m_oldGeneration =
new HashGeneration(
this);
362 m_oldGeneration.MakeOld();
368 private void RecycleGenerations()
371 IGeneration temp = m_newGeneration;
372 m_newGeneration = m_oldGeneration;
373 m_newGeneration.Clear();
374 m_oldGeneration = temp;
375 m_oldGeneration.MakeOld();
378 #region Nested type: Enumerator
383 private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
388 private int m_currentEnumerator = -1;
393 private readonly IEnumerator<KeyValuePair<TKey, TValue>>[] m_generationEnumerators =
394 new IEnumerator<KeyValuePair<TKey, TValue>>[2];
402 public Enumerator(CnmMemoryCache<TKey, TValue> cache)
404 m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator();
405 m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator();
408 #region IEnumerator<KeyValuePair<TKey,TValue>> Members
419 public KeyValuePair<TKey, TValue> Current
423 if (m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length)
424 throw new InvalidOperationException();
426 return m_generationEnumerators[ m_currentEnumerator ].Current;
439 object IEnumerator.Current
441 get {
return Current; }
448 public void Dispose()
462 public bool MoveNext()
464 if (m_currentEnumerator == -1)
465 m_currentEnumerator = 0;
467 while (m_currentEnumerator < m_generationEnumerators.Length)
469 if (m_generationEnumerators[ m_currentEnumerator ].MoveNext())
472 m_currentEnumerator++;
487 foreach (IEnumerator<KeyValuePair<TKey, TValue>> enumerator
in m_generationEnumerators)
492 m_currentEnumerator = -1;
500 #region Nested type: HashGeneration
515 private class HashGeneration : IGeneration
520 private bool m_accessedSinceLastTimeCheck;
532 private readonly
int[] m_buckets;
537 private readonly CnmMemoryCache<TKey, TValue> m_cache;
543 private readonly Element[] m_elements;
548 private DateTime m_expirationTime1;
553 private int m_firstFreeElement;
562 private int m_freeCount;
567 private bool m_newGeneration;
572 private int m_nextUnusedElement;
585 public HashGeneration(CnmMemoryCache<TKey, TValue> cache)
588 m_elements =
new Element[m_cache.m_generationElementCount];
589 m_buckets =
new int[m_cache.m_generationBucketCount];
611 private int FindElementIndex(
int bucketIndex, TKey
key,
bool moveToFront, out
int previousIndex)
614 int elementIndex = m_buckets[ bucketIndex ];
615 while (elementIndex >= 0)
617 if (m_cache.Comparer.Equals(key, m_elements[ elementIndex ].Key))
620 if (moveToFront && previousIndex >= 0)
623 m_elements[ previousIndex ].Next = m_elements[ elementIndex ].Next;
624 m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
625 m_buckets[ bucketIndex ] = elementIndex;
632 previousIndex = elementIndex;
633 elementIndex = m_elements[ elementIndex ].Next;
651 private void RemoveElement(
int bucketIndex,
int entryIndex,
int previousIndex)
653 if (previousIndex >= 0)
654 m_elements[ previousIndex ].Next = m_elements[ entryIndex ].Next;
656 m_buckets[ bucketIndex ] = m_elements[ entryIndex ].Next;
658 Size -= m_elements[ entryIndex ].Size;
659 m_elements[ entryIndex ].Value =
default(TValue);
660 m_elements[ entryIndex ].Key =
default(TKey);
663 m_elements[ entryIndex ].Next = m_firstFreeElement;
664 m_firstFreeElement = entryIndex;
668 #region Nested type: Element
673 private struct Element
715 get {
return Size == 0; }
721 #region Nested type: Enumerator
726 private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
731 private KeyValuePair<TKey, TValue> m_current;
736 private int m_currentIndex;
741 private readonly HashGeneration m_generation;
750 private readonly
int m_version;
758 public Enumerator(HashGeneration generation)
760 m_generation = generation;
761 m_version = m_generation.m_cache.m_version;
764 #region IEnumerator<KeyValuePair<TKey,TValue>> Members
775 public KeyValuePair<TKey, TValue> Current
779 if (m_currentIndex == 0 || m_currentIndex >= m_generation.Count)
780 throw new InvalidOperationException();
795 object IEnumerator.Current
797 get {
return Current; }
804 public void Dispose()
817 public bool MoveNext()
819 if (m_version != m_generation.m_cache.m_version)
820 throw new InvalidOperationException();
822 while (m_currentIndex < m_generation.Count)
824 if (m_generation.m_elements[ m_currentIndex ].IsFree)
830 m_current =
new KeyValuePair<TKey, TValue>(m_generation.m_elements[ m_currentIndex ].Key,
831 m_generation.m_elements[ m_currentIndex ].Value);
836 m_current =
new KeyValuePair<TKey, TValue>();
849 if (m_version != m_generation.m_cache.m_version)
850 throw new InvalidOperationException();
860 #region IGeneration Members
865 public bool AccessedSinceLastTimeCheck
867 get {
return m_accessedSinceLastTimeCheck; }
869 set { m_accessedSinceLastTimeCheck = value; }
877 get {
return m_nextUnusedElement - m_freeCount; }
883 public DateTime ExpirationTime
885 get {
return m_expirationTime1; }
887 set { m_expirationTime1 = value; }
895 get {
return m_size; }
897 private set { m_size = value; }
910 for (
int i = m_buckets.Length - 1 ; i >= 0 ; i--)
915 Array.Clear(m_elements, 0, m_elements.Length);
917 m_firstFreeElement = -1;
919 m_nextUnusedElement = 0;
920 m_newGeneration =
true;
921 ExpirationTime = DateTime.MaxValue;
937 public bool Contains(
int bucketIndex, TKey key)
940 if (FindElementIndex(bucketIndex, key,
true, out previousIndex) == -1)
943 AccessedSinceLastTimeCheck =
true;
954 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
956 return new Enumerator(
this);
966 public void MakeOld()
968 m_newGeneration =
false;
983 public bool Remove(
int bucketIndex, TKey key)
986 int entryIndex = FindElementIndex(bucketIndex, key,
false, out previousIndex);
987 if (entryIndex != -1)
989 RemoveElement(bucketIndex, entryIndex, previousIndex);
990 AccessedSinceLastTimeCheck =
true;
1023 public bool Set(
int bucketIndex, TKey key, TValue value,
long size)
1025 Debug.Assert(m_newGeneration,
"It is possible to insert new elements only to newest generation.");
1026 Debug.Assert(size > 0,
"New element size should be more than 0.");
1029 int elementIndex = FindElementIndex(bucketIndex, key,
true, out previousIndex);
1030 if (elementIndex == -1)
1033 if (Size + size > m_cache.m_generationMaxSize ||
1034 (m_nextUnusedElement == m_cache.m_generationElementCount && m_freeCount == 0))
1044 if (m_firstFreeElement != -1)
1047 elementIndex = m_firstFreeElement;
1048 m_firstFreeElement = m_elements[ elementIndex ].Next;
1054 elementIndex = m_nextUnusedElement;
1055 m_nextUnusedElement++;
1058 Debug.Assert(m_elements[ elementIndex ].IsFree,
"Allocated element is not free.");
1061 m_elements[ elementIndex ].Next = m_buckets[ bucketIndex ];
1062 m_buckets[ bucketIndex ] = elementIndex;
1065 m_elements[ elementIndex ].Key =
key;
1070 if (Size - m_elements[ elementIndex ].Size + size > m_cache.m_generationMaxSize)
1075 RemoveElement(bucketIndex, elementIndex, previousIndex);
1080 Size =
Size - m_elements[ elementIndex ].Size + size;
1084 m_elements[ elementIndex ].Value = value;
1085 m_elements[ elementIndex ].Size = size;
1088 AccessedSinceLastTimeCheck =
true;
1116 public bool TryGetValue(
int bucketIndex, TKey key, out TValue value, out
long size)
1120 int elementIndex = FindElementIndex(bucketIndex, key, m_newGeneration, out previousIndex);
1121 if (elementIndex == -1)
1123 value =
default(TValue);
1128 value = m_elements[ elementIndex ].Value;
1129 size = m_elements[ elementIndex ].Size;
1131 if (!m_newGeneration)
1134 RemoveElement(bucketIndex, elementIndex, previousIndex);
1137 AccessedSinceLastTimeCheck =
true;
1148 IEnumerator IEnumerable.GetEnumerator()
1150 return GetEnumerator();
1158 #region Nested type: IGeneration
1172 protected interface IGeneration :
IEnumerable<KeyValuePair<TKey, TValue>>
1177 bool AccessedSinceLastTimeCheck {
get; set; }
1187 DateTime ExpirationTime {
get; set; }
1217 bool Contains(
int bucketIndex, TKey key);
1240 bool Remove(
int bucketIndex, TKey key);
1268 bool Set(
int bucketIndex, TKey key, TValue value,
long size);
1294 bool TryGetValue(
int bucketIndex, TKey key, out TValue value, out
long size);
1299 #region ICnmCache<TKey,TValue> Members
1316 get {
return m_newGeneration.Count + m_oldGeneration.Count; }
1354 public TimeSpan ExpirationTime
1356 get {
return m_expirationTime; }
1360 if (value < MinExpirationTime)
1361 value = MinExpirationTime;
1363 if (m_expirationTime == value)
1366 m_newGeneration.ExpirationTime = (m_newGeneration.ExpirationTime - m_expirationTime) + value;
1367 m_oldGeneration.ExpirationTime = (m_oldGeneration.ExpirationTime - m_expirationTime) + value;
1368 m_expirationTime = value;
1391 public bool IsCountLimited
1393 get {
return true; }
1414 public bool IsSizeLimited
1416 get {
return true; }
1435 public bool IsSynchronized
1437 get {
return false; }
1457 public bool IsTimeLimited
1459 get {
return ExpirationTime != TimeSpan.MaxValue; }
1477 get {
return m_maxCount; }
1483 if (m_maxCount == value)
1507 public long MaxElementSize
1509 get {
return m_maxElementSize; }
1511 private set { m_maxElementSize = value; }
1534 get {
return m_maxSize; }
1540 if (m_maxSize == value)
1573 get {
return m_newGeneration.Size + m_oldGeneration.Size; }
1591 public object SyncRoot
1593 get {
return m_syncRoot; }
1606 m_newGeneration.Clear();
1607 m_oldGeneration.Clear();
1608 m_oldGeneration.MakeOld();
1621 return new Enumerator(
this);
1645 m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks;
1650 DateTime now = DateTime.Now;
1651 if (m_newGeneration.AccessedSinceLastTimeCheck)
1655 m_newGeneration.ExpirationTime = now + ExpirationTime;
1656 m_newGeneration.AccessedSinceLastTimeCheck =
false;
1658 else if (m_newGeneration.ExpirationTime < now)
1662 PurgeGeneration(m_newGeneration);
1663 PurgeGeneration(m_oldGeneration);
1667 if (m_oldGeneration.ExpirationTime < now)
1668 PurgeGeneration(m_oldGeneration);
1688 throw new ArgumentNullException(
"key");
1690 int bucketIndex = GetBucketIndex(key);
1691 if (!m_newGeneration.Remove(bucketIndex, key))
1693 if (!m_oldGeneration.Remove(bucketIndex, key))
1721 throw new ArgumentNullException(
"keys");
1723 foreach (TKey key
in keys)
1728 int bucketIndex = GetBucketIndex(key);
1729 if (!m_newGeneration.Remove(bucketIndex, key))
1730 m_oldGeneration.Remove(bucketIndex, key);
1782 public bool Set(TKey key, TValue value,
long size)
1785 throw new ArgumentNullException(
"key");
1788 throw new ArgumentOutOfRangeException(
"size", size,
"Value's size can't be less than 0.");
1790 if (size > MaxElementSize)
1800 int bucketIndex = GetBucketIndex(key);
1801 m_oldGeneration.Remove(bucketIndex,
key);
1802 AddToNewGeneration(bucketIndex, key, value, size);
1834 throw new ArgumentNullException(
"key");
1836 int bucketIndex = GetBucketIndex(key);
1838 if (m_newGeneration.TryGetValue(bucketIndex, key, out value, out size))
1844 if (m_oldGeneration.TryGetValue(bucketIndex, key, out value, out size))
1847 AddToNewGeneration(bucketIndex, key, value, size);
1863 IEnumerator IEnumerable.GetEnumerator()
1865 return GetEnumerator();
virtual int GetBucketIndex(TKey key)
bool Set(TKey key, TValue value, long size)
Add or replace an element with the provided key , value and size to ICnmCache{TKey,TValue}.
void PurgeExpired()
Purge expired elements from the ICnmCache{TKey,TValue}.
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
void Remove(TKey key)
Removes element associated with key from the ICnmCache{TKey,TValue}.
virtual void PurgeGeneration(IGeneration generation)
Purge generation from the cache.
CnmMemoryCache(long maximalSize, int maximalCount, TimeSpan expirationTime, IEqualityComparer< TKey > comparer)
Initializes a new instance of the CnmMemoryCache{TKey,TValue} class.
IEnumerator< KeyValuePair< TKey, TValue > > GetEnumerator()
Returns an enumerator that iterates through the elements stored to CnmMemoryCache{TKey,TValue}.
void Clear()
Removes all elements from the ICnmCache{TKey,TValue}.
CnmMemoryCache()
Initializes a new instance of the CnmMemoryCache{TKey,TValue} class.
CnmMemoryCache(long maximalSize, int maximalCount, TimeSpan expirationTime)
Initializes a new instance of the CnmMemoryCache{TKey,TValue} class.
readonly IEqualityComparer< TKey > Comparer
Comparer used to compare element keys.
void RemoveRange(IEnumerable< TKey > keys)
Removes elements that are associated with one of keys from the ICnmCache{TKey,TValue}.
System.Collections.IEnumerable IEnumerable
CnmMemoryCache(long maximalSize, int maximalCount)
Initializes a new instance of the CnmMemoryCache{TKey,TValue} class.
bool TryGetValue(TKey key, out TValue value)
Gets the value associated with the specified key .
CnmMemoryCache(long maximalSize)
Initializes a new instance of the CnmMemoryCache{TKey,TValue} class.
virtual void AddToNewGeneration(int bucketIndex, TKey key, TValue value, long size)
Add element to new generation.