using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; using Object = UnityEngine.Object; namespace Coffee.UIParticleInternal { internal class ObjectRepository where T : Object { private readonly Dictionary _cache = new Dictionary(8); private readonly Dictionary _objectKey = new Dictionary(8); private readonly string _name; private readonly Action _onRelease; private readonly Stack _pool = new Stack(8); public ObjectRepository(Action 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; } /// /// Adds or retrieves a cached object based on the hash. /// public void Get(Hash128 hash, ref T obj, Func onCreate) { if (GetFromCache(hash, ref obj)) return; Add(hash, ref obj, onCreate()); } /// /// Adds or retrieves a cached object based on the hash. /// public void Get(Hash128 hash, ref T obj, Func 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, $"Add(total#{count}): {newEntry}"); Release(ref obj); obj = newObject; Profiler.EndSample(); } /// /// Release a object. /// 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}): Already released: {obj}"); } 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, $"Remove(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 onRelease) { reference = 0; if (storedObject) { onRelease?.Invoke(storedObject); } storedObject = null; } public override string ToString() { return $"h{(uint)hash.GetHashCode()} (refs#{reference}), {storedObject}"; } } } }