Files
Nicolas Musset 41381dedd5 refactor: Nullable and Modernization for Core projects (#2643)
* [Core.Mathematics] Modernize code

* [Core.Mathematics] Add tests to Stride.Tests.Simple

* [Core] Cleanup some code

* [Core.CompilerServices] Cleanup some code

* [Core.CompilerServices] Add tests to Stride.Tests.Simple

* [Core.IO] Modernize code

* [Core.MicroThreading] Modernize code

* [Core.Serialization] Modernize code

* [Core.AssemblyProcessor.Tests] Modernize code

* [Core.CompilerServices.Tests] Modernize code

* [Core.Mathematics.Tests] Modernize code

* [Core.Tests] Modernize code

* [Core.Translation] Modernize code

* [Core.Reflection] Modernize code

* Fixes build of Stride.VisualStudio.sln

* [Core.Yaml.Tests] Modernize code

* [Core.Design] Modernize code

* [Core.Design.Tests] Modernize code

* [Core.Mathematics] More annotations

* Fix build of projects that import code from Core.Design

Especially those targeting .NET Framework
2025-02-22 16:50:47 +01:00

132 lines
4.9 KiB
C#

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
namespace Stride.Core;
/// <summary>
/// A thread-safe cache of indexed objects that will keep the most recently accessed instance around in a fixed size buffer.
/// </summary>
/// <typeparam name="TKey">The type of keys used to index objects.</typeparam>
/// <typeparam name="TValue">The type of objects contained in the cache.</typeparam>
public class ObjectCache<TKey, TValue> where TKey : IEquatable<TKey> where TValue : class
{
/// <summary>
/// The default size of the cache when an instance of <see cref="ObjectCache{TKey, TValue}"/> is created with the parameterless constructor.
/// </summary>
public const int DefaultCacheSize = 256;
private readonly SortedList<long, TKey> accessHistory;
private readonly Dictionary<TKey, TValue> cache;
private readonly object objectLock = new();
private int size;
private long currentAccessCount;
/// <summary>
/// Initializes a new instance of the <see cref="ObjectCache{TKey, TValue}"/> class with a custom comparer for the keys.
/// </summary>
/// <param name="size">The size of this cache.</param>
/// <param name="comparer">The comparer to use to compare keys.</param>
public ObjectCache(int size, IEqualityComparer<TKey>? comparer)
{
#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size);
#else
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size))
#endif
this.size = size;
cache = new Dictionary<TKey, TValue>(size, comparer);
accessHistory = new SortedList<long, TKey>(size);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObjectCache{TKey, TValue}"/> class.
/// </summary>
/// <param name="size">The size of this cache.</param>
public ObjectCache(int size)
: this(size, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ObjectCache{TKey, TValue}"/> class with a default size equal to <see cref="DefaultCacheSize"/>.
/// </summary>
public ObjectCache()
: this(256)
{
}
/// <summary>
/// Gets or sets the size of the cache.
/// </summary>
/// <remarks>If the new value is smaller than the current size, oldest accessed items will be removed from the cache immediately.</remarks>
public int Size { get { return size; } set { Resize(value); } }
/// <summary>
/// Tries to retrieve the object corresponding to the given key in the cache.
/// </summary>
/// <param name="key">The key of the item to look for.</param>
/// <returns>The object corresponding to the given key if it's available in the cache, <c>Null</c> otherwise.</returns>
public TValue? TryGet(TKey key)
{
lock (objectLock)
{
if (cache.TryGetValue(key, out var result))
{
// If we were able to find the object, we must update the access history.
var historyEntry = accessHistory.FirstOrDefault(x => x.Value.Equals(key)).Key;
if (historyEntry != default)
{
// Remove the key if it was already in the history.
accessHistory.Remove(historyEntry);
}
// Insert the key at the end of the access history
accessHistory.Add(++currentAccessCount, key);
}
return result;
}
}
/// <summary>
/// Adds the given indexed object in the cache.
/// </summary>
/// <param name="key">The key of the object.</param>
/// <param name="value">The object to cache.</param>
public void Cache(TKey key, TValue value)
{
lock (objectLock)
{
// If the cache is full, remove the oldest accessed items, and reserve space for the item we want to add.
ShrinkCache(1);
// Add the object to the cache
cache.Add(key, value);
// Make an access on it to set it as the most recently accessed object.
TryGet(key);
}
}
private void Resize(int newSize)
{
lock (objectLock)
{
#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(newSize);
#else
if (newSize <= 0) throw new ArgumentOutOfRangeException(nameof(newSize))
#endif
size = newSize;
// Remove any object that is beyond our new size.
ShrinkCache();
}
}
private void ShrinkCache(int emptySlotToKeep = 0)
{
while (cache.Count + emptySlotToKeep > Size)
{
var keyToRemove = accessHistory.First();
accessHistory.RemoveAt(0);
cache.Remove(keyToRemove.Value);
}
}
}