using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; using Object = UnityEngine.Object; namespace Coffee.UIParticleInternal { internal class ObjectRepository<T> where T : Object { private readonly Dictionary<Hash128, Entry> _cache = new Dictionary<Hash128, Entry>(8); private readonly Dictionary<int, Hash128> _objectKey = new Dictionary<int, Hash128>(8); private readonly string _name; private readonly Action<T> _onRelease; private readonly Stack<Entry> _pool = new Stack<Entry>(8); public ObjectRepository(Action<T> onRelease = null) { _name = $"{typeof(T).Name}Repository"; if (onRelease == null) { _onRelease = x => { #if UNITY_EDITOR if (!Application.isPlaying) { Object.DestroyImmediate(x, false); } else #endif { Object.Destroy(x); } }; } else { _onRelease = onRelease; } for (var i = 0; i < 8; i++) { _pool.Push(new Entry()); } } public int count => _cache.Count; public void Clear() { foreach (var kv in _cache) { var entry = kv.Value; if (entry == null) continue; entry.Release(_onRelease); _pool.Push(entry); } _cache.Clear(); _objectKey.Clear(); } public bool Valid(Hash128 hash, T obj) { return _cache.TryGetValue(hash, out var entry) && entry.storedObject == obj; } /// <summary> /// Adds or retrieves a cached object based on the hash. /// </summary> public void Get(Hash128 hash, ref T obj, Func<T> onCreate) { if (GetFromCache(hash, ref obj)) return; Add(hash, ref obj, onCreate()); } /// <summary> /// Adds or retrieves a cached object based on the hash. /// </summary> public void Get<TS>(Hash128 hash, ref T obj, Func<TS, T> onCreate, TS source) { if (GetFromCache(hash, ref obj)) return; Add(hash, ref obj, onCreate(source)); } private bool GetFromCache(Hash128 hash, ref T obj) { // Find existing entry. Profiler.BeginSample("(COF)[ObjectRepository] GetFromCache"); if (_cache.TryGetValue(hash, out var entry)) { if (!entry.storedObject) { Release(ref entry.storedObject); Profiler.EndSample(); return false; } if (entry.storedObject != obj) { // if the object is different, release the old one. Release(ref obj); ++entry.reference; obj = entry.storedObject; Logging.Log(_name, $"Get(total#{count}): {entry}"); } Profiler.EndSample(); return true; } Profiler.EndSample(); return false; } private void Add(Hash128 hash, ref T obj, T newObject) { if (!newObject) { Release(ref obj); obj = newObject; return; } // Create and add a new entry. Profiler.BeginSample("(COF)[ObjectRepository] Add"); var newEntry = 0 < _pool.Count ? _pool.Pop() : new Entry(); newEntry.storedObject = newObject; newEntry.hash = hash; newEntry.reference = 1; _cache[hash] = newEntry; _objectKey[newObject.GetInstanceID()] = hash; Logging.Log(_name, $"<color=#03c700>Add</color>(total#{count}): {newEntry}"); Release(ref obj); obj = newObject; Profiler.EndSample(); } /// <summary> /// Release a object. /// </summary> public void Release(ref T obj) { if (ReferenceEquals(obj, null)) return; // Find and release the entry. Profiler.BeginSample("(COF)[ObjectRepository] Release"); var id = obj.GetInstanceID(); if (_objectKey.TryGetValue(id, out var hash) && _cache.TryGetValue(hash, out var entry)) { entry.reference--; if (entry.reference <= 0 || !entry.storedObject) { Remove(entry); } else { Logging.Log(_name, $"Release(total#{_cache.Count}): {entry}"); } } else { Logging.Log(_name, $"Release(total#{_cache.Count}): <color=red>Already released: {obj}</color>"); } obj = null; Profiler.EndSample(); } private void Remove(Entry entry) { if (ReferenceEquals(entry, null)) return; Profiler.BeginSample("(COF)[ObjectRepository] Remove"); _cache.Remove(entry.hash); _objectKey.Remove(entry.storedObject.GetInstanceID()); _pool.Push(entry); entry.reference = 0; Logging.Log(_name, $"<color=#f29e03>Remove</color>(total#{_cache.Count}): {entry}"); entry.Release(_onRelease); Profiler.EndSample(); } private class Entry { public Hash128 hash; public int reference; public T storedObject; public void Release(Action<T> onRelease) { reference = 0; if (storedObject) { onRelease?.Invoke(storedObject); } storedObject = null; } public override string ToString() { return $"h{(uint)hash.GetHashCode()} (refs#{reference}), {storedObject}"; } } } }