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