From 3809da762c5ad0aa20e4e08ac2eeb5b51133dea2 Mon Sep 17 00:00:00 2001
From: ALEXTANG <574809918@qq.com>
Date: Fri, 7 Apr 2023 18:02:33 +0800
Subject: [PATCH] =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9B=BF=E6=8D=A2=E7=AD=96?=
=?UTF-8?q?=E7=95=A5=EF=BC=88Cache=20Replacement=20Policy=EF=BC=89-=20ARC?=
=?UTF-8?q?=EF=BC=88Adaptive=20Replacement=20Cache=EF=BC=89=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
缓存替换策略(Cache Replacement Policy)- ARC(Adaptive Replacement Cache)。
---
.../GameFramework/Resource/ArcCache.meta | 3 +
.../Resource/ArcCache/ArcCacheTable.cs | 273 ++++++++++++++++++
.../Resource/ArcCache/ArcCacheTable.cs.meta | 3 +
.../Resource/ArcCache/QueueNode.cs | 82 ++++++
.../Resource/ArcCache/QueueNode.cs.meta | 3 +
.../GameFramework/Resource/LruCache.meta | 3 +
.../Resource/LruCache/AssetLruCacheTable.cs | 20 ++
.../LruCache/AssetLruCacheTable.cs.meta | 3 +
.../Resource/LruCache/LruCacheTable.cs | 198 +++++++++++++
.../Resource/LruCache/LruCacheTable.cs.meta | 11 +
.../Resource/LruCache/LruGroupAttribute.cs | 14 +
.../LruCache/LruGroupAttribute.cs.meta | 3 +
.../Resource/LruCache/LruGroupType.cs | 12 +
.../Resource/LruCache/LruGroupType.cs.meta | 3 +
.../Resource/LruCache/Resource.cs | 88 ++++++
.../Resource/LruCache/Resource.cs.meta | 3 +
16 files changed, 722 insertions(+)
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/ArcCache.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs.meta
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs
create mode 100644 Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs.meta
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache.meta b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache.meta
new file mode 100644
index 00000000..30ebd612
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3e380d86c4544d77a6560f769520ad62
+timeCreated: 1680855013
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs
new file mode 100644
index 00000000..580f0427
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs
@@ -0,0 +1,273 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TEngine.ArcCache
+{
+ ///
+ /// Adaptive Replacement Cache缓存表。
+ ///
+ /// Adaptive Replacement CacheKey。
+ /// Adaptive Replacement CacheValue
+ /// 缓存替换策略(Cache Replacement Policy)- ARC(Adaptive Replacement Cache)。
+ public class ArcCacheTable
+ {
+ ///
+ /// ARC缓存表哈希表。
+ /// Dictionary 中存储 key 和 QueueNode 中节点的映射关系。
+ /// 算法优化的关键在于如何降低链表的删除操作的时间复杂度。不管是在插入、删除、查找缓存的时候,都可以通过这种联系来将时间复杂度降低到 O(1)。
+ ///
+ protected readonly Dictionary> CacheStorageMap;
+
+ private readonly QueueNode _t1Head;
+ private readonly QueueNode _t2Head;
+ private readonly QueueNode _b1Head;
+ private readonly QueueNode _b2Head;
+
+ private int _adaptiveParameter;
+ private int _t1Size;
+ private int _t2Size;
+ private int _b1Size;
+ private int _b2Size;
+ protected readonly int Capacity;
+
+ public delegate void OnAdd(TValue data);
+
+ public delegate void OnRemove(TValue data);
+
+ ///
+ /// ARC缓存表添加成功回调。
+ ///
+ public OnAdd OnAddCallback;
+
+ ///
+ /// ARC缓存表移除回调。
+ ///
+ public OnRemove OnRemoveCallback;
+
+ ///
+ /// ARC缓存表哈希表构造。
+ ///
+ /// 容量。
+ /// LRU缓存表添加成功回调。
+ /// LRU缓存表移除回调。
+ public ArcCacheTable(int capacity, OnAdd onAdd = null, OnRemove onRemove = null)
+ {
+ this.Capacity = capacity;
+ OnAddCallback = onAdd;
+ OnRemoveCallback = onRemove;
+ this.CacheStorageMap = new Dictionary>();
+ this._t1Head = new QueueNode();
+ this._t2Head = new QueueNode();
+ this._b1Head = new QueueNode();
+ this._b2Head = new QueueNode();
+ }
+
+ ///
+ /// 对象推入ARC缓存表。
+ ///
+ /// 键。
+ /// 值。
+ public void PutCache(TKey key, TValue value)
+ {
+ QueueNode queueNode = CacheStorageMap[key];
+
+ if (queueNode == null)
+ {
+ OnMissOnAllQueue(key, value);
+ }
+ else if (queueNode.QueueType == QueueType.B1)
+ {
+ queueNode.Set(value);
+ OnHitOnB1(queueNode);
+ }
+ else if (queueNode.QueueType == QueueType.B2)
+ {
+ queueNode.Set(value);
+ OnHitOnB2(queueNode);
+ }
+ else
+ {
+ queueNode.Set(value);
+ OnHitOnT1orT2(queueNode);
+ }
+ }
+
+ ///
+ /// 从ARC缓存表取出对象。
+ ///
+ /// 键。
+ /// TValue from cache if exists or null。
+ public TValue GetCache(TKey key)
+ {
+ QueueNode queueNode = CacheStorageMap[key];
+
+ if (queueNode == null)
+ {
+ return default;
+ }
+ else
+ {
+ return queueNode.Get();
+ }
+ }
+
+ ///
+ /// 在所有队列中未命中时执行任务(Case:Key不在(T1 u B1 u T2 u B2))。
+ ///
+ /// key cache key。
+ /// value to inset in cache。
+ private void OnMissOnAllQueue(TKey key, TValue value)
+ {
+ QueueNode queueNode = new QueueNode(key, value);
+ queueNode.QueueType = QueueType.T1;
+
+ int sizeL1 = (_t1Size + _b1Size);
+ int sizeL2 = (_t2Size + _b2Size);
+ if (sizeL1 == Capacity)
+ {
+ if (_t1Size < Capacity)
+ {
+ QueueNode queueNodeToBeRemoved = _b1Head.Next;
+ RemoveFromQueue(queueNodeToBeRemoved);
+ queueNodeToBeRemoved.Remove();
+ _b1Size--;
+
+ Replace(queueNode);
+ }
+ else
+ {
+ QueueNode queueNodeToBeRemoved = _t1Head.Next;
+ RemoveFromQueue(queueNodeToBeRemoved);
+ queueNodeToBeRemoved.Remove();
+ _t1Size--;
+ }
+ }
+ else if ((sizeL1 < Capacity) && ((sizeL1 + sizeL2) >= Capacity))
+ {
+ if ((sizeL1 + sizeL2) >= (2 * Capacity))
+ {
+ QueueNode queueNodeToBeRemoved = _b2Head.Next;
+ RemoveFromQueue(queueNodeToBeRemoved);
+ queueNodeToBeRemoved.Remove();
+ _b2Size--;
+ }
+
+ Replace(queueNode);
+ }
+
+ _t1Size++;
+ CacheStorageMap.Add(key, queueNode);
+ queueNode.AddToLast(_t1Head);
+ }
+
+ ///
+ /// 执行任务命中B1 (Case:Key在B1中)。
+ ///
+ /// queueNode queue node。
+ private void OnHitOnB1(QueueNode queueNode)
+ {
+ _adaptiveParameter = Math.Min(Capacity, _adaptiveParameter + Math.Max(_b2Size / _b1Size, 1));
+ Replace(queueNode);
+
+ _t2Size++;
+ _b1Size--;
+ queueNode.Remove();
+ queueNode.QueueType = QueueType.T2;
+ queueNode.AddToLast(_t2Head);
+ }
+
+ ///
+ /// 执行任务命中B2 (Case:Key在B2中)。
+ ///
+ /// queueNode queue node。
+ private void OnHitOnB2(QueueNode queueNode)
+ {
+ _adaptiveParameter = Math.Max(0, _adaptiveParameter - Math.Max(_b1Size / _b2Size, 1));
+ Replace(queueNode);
+
+ _t2Size++;
+ _b2Size--;
+ queueNode.Remove();
+ queueNode.QueueType = QueueType.T2;
+ queueNode.AddToLast(_t2Head);
+ }
+
+ ///
+ /// 执行任务命中T1或者T2 (Case:Key在T1或者T2中)。
+ ///
+ /// queueNode queue node。
+ private void OnHitOnT1orT2(QueueNode queueNode)
+ {
+ if (queueNode.QueueType == QueueType.T1)
+ {
+ _t1Size--;
+ _t2Size++;
+ }
+
+ queueNode.Remove();
+ queueNode.QueueType = QueueType.T2;
+ queueNode.AddToLast(_t2Head);
+ }
+
+ ///
+ /// 替换队列节点(情况:L1(T1 u B1)少于c个)
+ ///
+ /// queueNode queue node。
+ private void Replace(QueueNode queueNode)
+ {
+ if ((_t1Size >= 1) && (((queueNode.QueueType == QueueType.B2) && (_t1Size == _adaptiveParameter)) || (_t1Size > _adaptiveParameter)))
+ {
+ QueueNode queueNodeToBeRemoved = _t1Head.Next;
+ queueNodeToBeRemoved.Remove();
+ queueNodeToBeRemoved.QueueType = QueueType.B1;
+ queueNodeToBeRemoved.AddToLast(_b1Head);
+ _t1Size--;
+ _b1Size++;
+ }
+ else
+ {
+ QueueNode queueNodeToBeRemoved = _t2Head.Next;
+ queueNodeToBeRemoved.Remove();
+ queueNodeToBeRemoved.QueueType = QueueType.B2;
+ queueNodeToBeRemoved.AddToLast(_b2Head);
+ _t2Size--;
+ _b2Size++;
+ }
+ }
+
+
+ ///
+ /// Remove TValue data from queue and dispose it
+ ///
+ /// queueNodeToBeRemoved queue node to be remove from queue
+ public void RemoveFromQueue(QueueNode queueNodeToBeRemoved)
+ {
+ CacheStorageMap.Remove(queueNodeToBeRemoved.Key);
+ TValue value = queueNodeToBeRemoved.Get();
+ try
+ {
+ //Dispose(value);
+ }
+ catch (System.Exception e)
+ {
+ Debug.Log(e);
+ }
+ }
+
+ public void PrintCacheIdsFromQueue()
+ {
+ String keys = "";
+ foreach (var queueNode in CacheStorageMap)
+ {
+ var key = queueNode.Key;
+ if (keys == "")
+ keys += key;
+ else
+ keys += ", " + key;
+ }
+
+ Debug.Log("All Existing Keys in Cache are : " + keys);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs.meta
new file mode 100644
index 00000000..fe2e6e2c
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/ArcCacheTable.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ef5afbe17b2748638f6fcd341958a4f2
+timeCreated: 1680857519
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs
new file mode 100644
index 00000000..9e0dfe50
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs
@@ -0,0 +1,82 @@
+namespace TEngine.ArcCache
+{
+ ///
+ /// 队列类型。
+ /// T1, recent cache entries.
+ /// T2, ghost entries recently evicted from the T1 cache.
+ /// B1, frequent entries.
+ /// B2, ghost entries recently evicted from the T2 cache.
+ ///
+ ///
+ public enum QueueType
+ {
+ None,
+ ///
+ /// T1, recent cache entries.
+ ///
+ T1,
+ ///
+ /// B1, frequent entries.
+ ///
+ B1,
+ ///
+ /// T2, ghost entries recently evicted from the T1 cache.
+ ///
+ T2,
+ ///
+ /// B2, ghost entries recently evicted from the T2 cache.
+ ///
+ B2
+ }
+
+ public class QueueNode
+ {
+ public readonly TKey Key;
+ public QueueNode Prev;
+ public QueueNode Next;
+ public QueueType QueueType;
+ public TValue Value;
+
+ public QueueNode()
+ {
+ this.Prev = this;
+ this.Next = this;
+ }
+
+ public QueueNode(TKey key, TValue data)
+ {
+ this.Key = key;
+ this.Value = data;
+ }
+
+ public TValue Get()
+ {
+ return Value;
+ }
+
+ public void Set(TValue value)
+ {
+ this.Value = value;
+ }
+
+ public void AddToLast(QueueNode head)
+ {
+ QueueNode tail = head.Prev;
+ head.Prev = this;
+ tail.Next = this;
+ Next = head;
+ Prev = tail;
+ }
+
+ public void Remove()
+ {
+ if (Prev != null && Next != null)
+ {
+ Prev.Next = Next;
+ Next.Prev = Prev;
+ Prev = Next = null;
+ QueueType = QueueType.None;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs.meta
new file mode 100644
index 00000000..2bcf47e1
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/ArcCache/QueueNode.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 908fe3016e154893908fb7f3b4050a74
+timeCreated: 1680857337
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache.meta
new file mode 100644
index 00000000..be67c5e7
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 02b781ce209c45098d54e0e0f11dc856
+timeCreated: 1680833959
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs
new file mode 100644
index 00000000..8b64cacd
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs
@@ -0,0 +1,20 @@
+using YooAsset;
+
+namespace TEngine
+{
+ ///
+ /// 资源Lru缓存表。
+ ///
+ public sealed class AssetLruCacheTable:LruCacheTable
+ {
+ public AssetLruCacheTable(int capacity) : base(capacity)
+ {
+ OnRemoveCallback += OnRelease;
+ }
+
+ private void OnRelease(OperationHandleBase handleBase)
+ {
+ handleBase.ReleaseInternal();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs.meta
new file mode 100644
index 00000000..527b4d4a
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/AssetLruCacheTable.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c371cc78061c4ed78a4df5b315314e43
+timeCreated: 1680835698
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs
new file mode 100644
index 00000000..94941bba
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+
+namespace TEngine
+{
+ ///
+ /// LRU缓存表。
+ ///
+ /// LRUKey。
+ /// LRUValue
+ /// 缓存替换策略(Cache Replacement Policy)- LRU(Least Recently Used)。
+ public class LruCacheTable
+ {
+ ///
+ /// LRU缓存表头节点。
+ ///
+ protected readonly DoubleLinkedListNode Head;
+
+ ///
+ /// LRU缓存表尾节点。
+ ///
+ protected readonly DoubleLinkedListNode Tail;
+
+ ///
+ /// 哈希表来存储键值对。
+ /// Dictionary 中存储 key 和 LinkedList 中节点的映射关系。
+ /// 算法优化的关键在于如何降低链表的删除操作的时间复杂度。不管是在插入、删除、查找缓存的时候,都可以通过这种联系来将时间复杂度降低到 O(1)。
+ ///
+ protected readonly Dictionary> LinkedListNodesMap;
+
+ protected readonly int Capacity;
+
+ public delegate void OnAdd(TValue data);
+ public delegate void OnRemove(TValue data);
+
+ ///
+ /// LRU缓存表添加成功回调。
+ ///
+ public OnAdd OnAddCallback;
+
+ ///
+ /// LRU缓存表移除回调。
+ ///
+ public OnRemove OnRemoveCallback;
+
+ ///
+ /// LRU缓存表构造。
+ ///
+ /// 容量。
+ /// LRU缓存表添加成功回调。
+ /// LRU缓存表移除回调。
+ public LruCacheTable(int capacity,OnAdd onAdd = null,OnRemove onRemove = null)
+ {
+ Capacity = capacity;
+ OnAddCallback = onAdd;
+ OnRemoveCallback = onRemove;
+ Head = new DoubleLinkedListNode();
+ Tail = new DoubleLinkedListNode();
+ Head.Next = Tail;
+ Tail.Previous = Head;
+ LinkedListNodesMap = new Dictionary>(Capacity);
+ }
+
+ ///
+ /// 从LRU缓存表中获取。
+ ///
+ /// 键。
+ /// 值。
+ /// 在链表中删除 key,然后将 key 添加到链表的尾部,这样就可以保证链表的尾部就是最近访问的数据,链表的头部就是最久没有被访问的数据。
+ public virtual TValue Get(TKey key)
+ {
+ if (LinkedListNodesMap.TryGetValue(key, out var node))
+ {
+ RemoveNode(node);
+ AddLastNode(node);
+ return node.Value;
+ }
+
+ return default;
+ }
+
+ ///
+ /// 放入LRU缓存表。
+ ///
+ /// 键。
+ /// 值。
+ public virtual void Put(TKey key, TValue value)
+ {
+ if (LinkedListNodesMap.TryGetValue(key, out var node))
+ {
+ // 如果插入的 key 已经存在,将 key 对应的值更新,然后将 key 移动到链表的尾部。
+ RemoveNode(node);
+ OnRemoveCallback?.Invoke(node.Value);
+ AddLastNode(node);
+ node.Value = value;
+ OnAddCallback?.Invoke(value);
+ }
+ else
+ {
+ if (LinkedListNodesMap.Count == Capacity)
+ {
+ // 缓存满了,删除链表的头部,也就是最久没有被访问的数据。
+ var firstNode = RemoveFirstNode();
+ LinkedListNodesMap.Remove(firstNode.Key);
+ OnRemoveCallback?.Invoke(firstNode.Value);
+ }
+
+ var newNode = new DoubleLinkedListNode(key, value);
+ AddLastNode(newNode);
+ LinkedListNodesMap.Add(key, newNode);
+ OnAddCallback?.Invoke(value);
+ }
+ }
+
+ ///
+ /// 从LRU缓存表中移除。
+ ///
+ /// 键。
+ public virtual void Remove(TKey key)
+ {
+ if (LinkedListNodesMap.TryGetValue(key, out var node))
+ {
+ LinkedListNodesMap.Remove(key);
+ RemoveNode(node);
+ OnRemoveCallback?.Invoke(node.Value);
+ }
+ }
+
+ ///
+ /// 清理LRU缓存表中所有数据。
+ ///
+ public virtual void Clear()
+ {
+ int protectedIndex = Capacity;
+ while (Head.Next != null)
+ {
+ var firstNode = RemoveFirstNode();
+ LinkedListNodesMap.Remove(firstNode.Key);
+ OnRemoveCallback?.Invoke(firstNode.Value);
+ protectedIndex--;
+ if (protectedIndex <0)
+ {
+ break;
+ }
+ }
+ }
+
+ protected void AddLastNode(DoubleLinkedListNode node)
+ {
+ node.Previous = Tail.Previous;
+ node.Next = Tail;
+ Tail.Previous.Next = node;
+ Tail.Previous = node;
+ }
+
+ protected DoubleLinkedListNode RemoveFirstNode()
+ {
+ var firstNode = Head.Next;
+ if (firstNode == null)
+ {
+ return firstNode;
+ }
+ Head.Next = firstNode.Next;
+ firstNode.Next.Previous = Head;
+ firstNode.Next = null;
+ firstNode.Previous = null;
+ return firstNode;
+ }
+
+ protected void RemoveNode(DoubleLinkedListNode node)
+ {
+ node.Previous.Next = node.Next;
+ node.Next.Previous = node.Previous;
+ node.Next = null;
+ node.Previous = null;
+ }
+
+ protected class DoubleLinkedListNode
+ {
+ public DoubleLinkedListNode()
+ {
+ }
+
+ public DoubleLinkedListNode(TNodeKey key, TNodeValue value)
+ {
+ Key = key;
+ Value = value;
+ }
+
+ public TNodeKey Key { get; set; }
+
+ public TNodeValue Value { get; set; }
+
+ public DoubleLinkedListNode Previous { get; set; }
+
+ public DoubleLinkedListNode Next { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs.meta
new file mode 100644
index 00000000..10761cf1
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruCacheTable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3f37f202a857ede4dbe45f6645eb9765
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs
new file mode 100644
index 00000000..855085ae
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace TEngine
+{
+ public class LruGroupAttribute:Attribute
+ {
+ public int Capacity;
+
+ public LruGroupAttribute(int capacity)
+ {
+ Capacity = capacity;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs.meta
new file mode 100644
index 00000000..fdc3d040
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupAttribute.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b20916852da04186917626247af54dae
+timeCreated: 1680850559
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs
new file mode 100644
index 00000000..37110c93
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs
@@ -0,0 +1,12 @@
+namespace TEngine
+{
+ public enum LruGroupType : byte
+ {
+ [LruGroup(20)]
+ GameActor,
+ [LruGroup(50)]
+ GameObject,
+ [LruGroup(100)]
+ Sprite,
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs.meta
new file mode 100644
index 00000000..2c18fdc0
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/LruGroupType.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 04c756fa6c5b4d52b421e31ce051ba43
+timeCreated: 1680850512
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs
new file mode 100644
index 00000000..86840ae7
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs
@@ -0,0 +1,88 @@
+using System;
+using YooAsset;
+using UnityEngine;
+using Cysharp.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace TEngine
+{
+ public static class Resource
+ {
+ private static readonly Dictionary ObjectHandlesMap = new Dictionary();
+
+ private static readonly Dictionary GameObjectsMap = new Dictionary();
+
+ public static async UniTask InstantiateAsync(string location, Transform parent = null, bool stayWorldSpace = false)
+ {
+ var handle = YooAssets.LoadAssetAsync(location);
+
+ await handle.ToUniTask();
+
+ if (!handle.IsValid)
+ {
+ throw new Exception($"[Resource] InstantiateAsync Failed to load asset: {location}");
+ }
+
+ ObjectHandlesMap.Add(handle.AssetObject, handle);
+
+ GameObject go = UnityEngine.Object.Instantiate(handle.AssetObject, parent, stayWorldSpace) as GameObject;
+ if (go == null)
+ {
+ Release(handle.AssetObject);
+ throw new Exception($"[Resource] InstantiateAsync Failed to instantiate asset: {location}");
+ }
+
+ GameObjectsMap.Add(go, handle.AssetObject);
+
+ return go;
+ }
+
+ public static async UniTask LoadAssetAsync(string location) where T : UnityEngine.Object
+ {
+ var handle = YooAssets.LoadAssetAsync(location);
+
+ await handle.ToUniTask();
+
+ if (!handle.IsValid)
+ {
+ throw new Exception($"[Resource] LoadAssetAsync Failed to load asset: {location}");
+ }
+
+ ObjectHandlesMap.Add(handle.AssetObject, handle);
+
+ return handle.AssetObject as T;
+ }
+
+ public static void ReleaseInstance(GameObject gameObject)
+ {
+ if (gameObject is null)
+ {
+ return;
+ }
+
+ UnityEngine.Object.Destroy(gameObject);
+
+ if (GameObjectsMap.TryGetValue(gameObject, out UnityEngine.Object obj))
+ {
+ GameObjectsMap.Remove(gameObject);
+
+ Release(obj);
+ }
+ }
+
+ public static void Release(UnityEngine.Object unityObject)
+ {
+ if (unityObject is null)
+ {
+ return;
+ }
+
+ if (ObjectHandlesMap.TryGetValue(unityObject, out OperationHandleBase handle))
+ {
+ ObjectHandlesMap.Remove(unityObject);
+
+ handle?.ReleaseInternal();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs.meta b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs.meta
new file mode 100644
index 00000000..ecefc0a9
--- /dev/null
+++ b/Assets/TEngine/Runtime/GameFramework/Resource/LruCache/Resource.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 115780f0702a4a4a824b789e0ec1fe7b
+timeCreated: 1680838058
\ No newline at end of file