NBTExplorer/SubstrateCS/Source/Core/ChunkCache.cs
2011-12-17 01:43:41 -05:00

144 lines
5.4 KiB
C#

using System;
using System.Collections.Generic;
namespace Substrate.Core
{
/// <summary>
/// An LRU-based caching data structure for holding references to <see cref="ChunkRef"/> objects.
/// </summary>
/// <remarks>The <see cref="ChunkCache"/> class maintains a separate dictionary on the side for tracking
/// dirty chunks. References to dirty chunks will still be held even if the chunk has been evicted from
/// the normal LRU cache. The dirty list is reset when the world is saved, or manually cleared.</remarks>
public class ChunkCache
{
private LRUCache<ChunkKey, ChunkRef> _cache;
private Dictionary<ChunkKey, ChunkRef> _dirty;
/// <summary>
/// Creates a new <see cref="ChunkCache"/> with the default capacity of 256 chunks.
/// </summary>
public ChunkCache ()
: this(256)
{
}
/// <summary>
/// Creates a new <see cref="ChunkCache"/> with the given chunk capacity.
/// </summary>
/// <param name="cacheSize">The capacity of the LRU-portion of the cache.</param>
public ChunkCache (int cacheSize)
{
_cache = new LRUCache<ChunkKey, ChunkRef>(cacheSize);
_dirty = new Dictionary<ChunkKey, ChunkRef>();
_cache.RemoveCacheValue += EvictionHandler;
}
#region IChunkCache Members
/// <summary>
/// Inserts a new chunk into the cache.
/// </summary>
/// <param name="chunk">The <see cref="ChunkRef"/> to add to the cache.</param>
/// <returns><c>True</c> if the chunk did not already exist in the cache, <c>false</c> otherwise.</returns>
/// <remarks>If the chunk does not already exist and the list is full, then the least-recently used chunk in the cache will be evicted
/// to make room for the new chunk. If the chunk is present in the dirty list, it will be removed.</remarks>
public bool Insert (ChunkRef chunk)
{
ChunkKey key = new ChunkKey(chunk.X, chunk.Z);
_dirty.Remove(key);
ChunkRef c;
if (!_cache.TryGetValue(key, out c)) {
_cache[key] = chunk;
return true;
}
return false;
}
/// <summary>
/// Removes a chunk from the cache.
/// </summary>
/// <param name="key">The key identifying which <see cref="ChunkRef"/> to try and remove.</param>
/// <returns><c>True</c> if the chunk was in the LRU-portion of the cache and removed, <c>False</c> otherwise.</returns>
/// <remarks>The chunk will also be removed from the dirty list, if it is currently in it.</remarks>
public bool Remove (ChunkKey key)
{
_dirty.Remove(key);
return _cache.Remove(key);
}
/// <summary>
/// Attempts to get a chunk from the LRU- or dirty-portions of the cache.
/// </summary>
/// <param name="key">The key identifying which <see cref="ChunkRef"/> to find.</param>
/// <returns>The cached <see cref="ChunkRef"/> if it was found anywhere in the cache, or <c>null</c> if it was not found.</returns>
/// <remarks>If the <see cref="ChunkRef"/> is found in the LRU-portion of the cache, it will be moved to the front of the
/// LRU list, making future eviction less likely.</remarks>
public ChunkRef Fetch (ChunkKey key)
{
ChunkRef c;
if (_dirty.TryGetValue(key, out c)) {
return c;
}
if (_cache.TryGetValue(key, out c)) {
return c;
}
return null;
}
/// <summary>
/// Gets an enumerator to iterate over all of the <see cref="ChunkRef"/> objects currently in the dirty list.
/// </summary>
/// <returns>An enumerator over all of the dirty <see cref="ChunkRef"/> objects.</returns>
public IEnumerator<ChunkRef> GetDirtyEnumerator ()
{
return _dirty.Values.GetEnumerator();
}
/// <summary>
/// Clears all chunks from the LRU list.
/// </summary>
/// <remarks>This method will clear all chunks from the LRU-portion of the cache, including any chunks that are
/// dirty but have not yet been discovered and added to the dirty list. Chunks already in the dirty list will
/// not be affected. To clear dirty chunks as well, see <see cref="ClearDirty"/>.</remarks>
public void Clear ()
{
_cache.Clear();
}
/// <summary>
/// Clears all chunks from the dirty list.
/// </summary>
public void ClearDirty ()
{
_dirty.Clear();
}
/// <summary>
/// Scans the LRU list for any dirty chunks, and adds them to the dirty list.
/// </summary>
public void SyncDirty ()
{
foreach (KeyValuePair<ChunkKey, ChunkRef> e in _cache) {
if (e.Value.IsDirty) {
_dirty[e.Key] = e.Value;
}
}
}
private void EvictionHandler (object sender, LRUCache<ChunkKey, ChunkRef>.CacheValueArgs e)
{
if (e.Value.IsDirty) {
_dirty[e.Key] = e.Value;
}
}
#endregion
}
}