diff --git a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs new file mode 100644 index 0000000..32cd4dd --- /dev/null +++ b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace SlatedGameToolkit.Framework.Graphics.Text +{ + public class DynamicFont { + private readonly Dictionary glyphLocations = new Dictionary(); + + public DynamicFont() { + + } + } +} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs new file mode 100644 index 0000000..a203699 --- /dev/null +++ b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs @@ -0,0 +1,14 @@ +using System.Numerics; + +namespace SlatedGameToolkit.Framework.Graphics.Text +{ + public struct DynamicGlyph { + public readonly char character; + public readonly (Vector2, Vector2)[] vertices; + + public DynamicGlyph(char character, (Vector2, Vector2)[] vertices) { + this.character = character; + this.vertices = vertices; + } + } +} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Utilities/Collections/Caching/LRUCache.cs b/src/SlatedGameToolkit.Framework/Utilities/Collections/Caching/LRUCache.cs new file mode 100644 index 0000000..2f6da0c --- /dev/null +++ b/src/SlatedGameToolkit.Framework/Utilities/Collections/Caching/LRUCache.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SlatedGameToolkit.Framework.Utilities.Collections +{ + public class LRUCache : ICollection + { + public V this[K key] + { + get + { + return Get(key); + } + + set + { + Set(key, value); + } + } + private Node first, last; + private int maxLength; + public int MaxLength { + get { + return maxLength; + } + + set { + maxLength = value; + } + } + Dictionary hashmap; + + public int Count => hashmap.Count; + + public bool IsSynchronized => false; + + public object SyncRoot => this; + + public LRUCache(int initialSize = 1024) { + this.maxLength = initialSize; + if (initialSize < 1) throw new ArgumentException("size cannot be less than 1."); + this.hashmap = new Dictionary(maxLength + 1); + } + + /// + /// Sets the value given the key. If the key already registered with a value, the previous value is replaced. + /// If the key did not exist, it is registered with the value. + /// + /// The key to associate the value with. + /// The value to cache. + public void Set(K key, V value) { + if (!hashmap.ContainsKey(key)) { + Node node = new Node(key, value); + hashmap.Add(key, node); + AddToStack(node); + if (hashmap.Count > maxLength) { + hashmap.Remove(RemoveLastFromStack().key); + } + } else { + Node node = hashmap[key]; + node.value = value; + MoveToFrontOfStack(node); + } + hashmap[key].value = value; + } + + /// + /// Retrieves the key, and increases the pair's usage ranking. + /// If the key doesn't exist, a KeyNotFoundException is thrown. + /// + /// The key to retrieve the value for. + /// The value. + public V Get(K key) { + if (ContainsKey(key)) { + Node node = hashmap[key]; + MoveToFrontOfStack(node); + return node.value; + } + throw new KeyNotFoundException(); + } + + /// + /// Checks if the key exists and returns it if it does, otherwise, + /// uses the function to calculate the value and cache it. + /// + /// The key to retrieve the value for. + /// The function to calculate the value in the case the key doesn't have a associated with it. + /// The value. + public V ComputeIfNonExistent(K key, Func func) { + if (ContainsKey(key)) { + return hashmap[key].value; + } + V val = func(key); + Set(key, val); + return val; + } + + /// + /// If the cache currently holds a value for the key. + /// The value for the key can be null. + /// + /// The key to verify for a value. + /// True if there does exist a value for the key in the cache. + public bool ContainsKey(K key) { + return hashmap.ContainsKey(key); + } + + private void AddToStack(Node node) { + node.left = null; + if (first != null) { + node.right = first; + first.left = node; + } else { + last = node; + } + first = node; + } + + private Node RemoveLastFromStack() { + if (last != null) { + Node last = this.last; + RemoveFromStack(last); + return last; + } + return null; + } + + private void MoveToFrontOfStack(Node node) { + RemoveFromStack(node); + AddToStack(node); + } + + private void RemoveFromStack(Node node) { + if (node.left == null) { + first = node.right; + } else { + node.left.right = node.right; + } + if (node.right == null) { + last = node.left; + } else { + node.right.left = node.left; + } + node.left = null; + node.right = null; + } + + public void CopyTo(Array array, int index) + { + Node currentNode = first; + for (int i = 0; i < array.Length && i < Count; i++) { + array.SetValue(currentNode.value, i + index); + currentNode = currentNode.right; + } + } + + public IEnumerator GetEnumerator() + { + return new LinkedHashmapEnumerable(first); + } + + private class LinkedHashmapEnumerable : IEnumerator + { + Node start, current; + public LinkedHashmapEnumerable(Node start) { + this.start = start; + this.current = start; + } + public V Current => current.value; + + object IEnumerator.Current => current.value; + + public void Dispose() + { + } + + public bool MoveNext() + { + current = current.right; + return current != null; + } + + public void Reset() + { + current = start; + } + } + + private class Node { + public Node left, right; + public readonly K key; + public V value; + + public Node(K key, V value) { + this.key = key; + this.value = value; + } + + public override int GetHashCode() { + return value.GetHashCode(); + } + + public override bool Equals(object obj) + { + if ((obj == null && value == null) || GetType() != obj.GetType()) + { + return false; + } + + return value.Equals(obj); + } + + } + } + +} \ No newline at end of file diff --git a/tests/SlatedGameToolkit.Framework.Tests/SlatedGameToolkit.Framework.Tests.csproj b/tests/SlatedGameToolkit.Framework.Tests/SlatedGameToolkit.Framework.Tests.csproj new file mode 100644 index 0000000..05ee3bb --- /dev/null +++ b/tests/SlatedGameToolkit.Framework.Tests/SlatedGameToolkit.Framework.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + diff --git a/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs b/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs new file mode 100644 index 0000000..d76de4d --- /dev/null +++ b/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using SlatedGameToolkit.Framework.Utilities.Collections; + +namespace SlatedGameToolkit.Framework.Tests.Utilities.Collections.Caching +{ + public class LRUCacheTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void SizeLimitTest() + { + LRUCache cache = new LRUCache(16); + for (int i = 0; i < 32; i++) { + cache.Set(i, i); + } + + Assert.AreEqual(16, cache.Count); + } + + [Test] + public void ResizeLimitTest() { + LRUCache cache = new LRUCache(16); + for (int i = 0; i < 32; i++) { + cache.Set(i, i); + } + cache.MaxLength = 64; + for (int i = 32; i < 64; i++) { + cache.Set(i, i); + } + + Assert.AreEqual(48, cache.Count); + Assert.AreEqual(63, cache[63]); + } + + [Test] + public void LeastUsedTest() { + LRUCache cache = new LRUCache(4); + for (int i = 0; i < cache.MaxLength; i++) { + cache[i] = i; + } + int used = cache[0]; + Assert.AreEqual(0, used); + cache[4] = 4; + Assert.IsFalse(cache.ContainsKey(1)); + cache[2] = 2; + cache[5] = 5; + Assert.IsTrue(cache.ContainsKey(2)); + } + } +} \ No newline at end of file