using Robust.Client.Interfaces.ResourceManagement; using Robust.Shared.ContentPack; using Robust.Shared.Log; using Robust.Shared.Utility; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace Robust.Client.ResourceManagement { internal class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable { private readonly Dictionary> CachedResources = new Dictionary>(); private readonly Dictionary _fallbacks = new Dictionary(); public T GetResource(string path, bool useFallback = true) where T : BaseResource, new() { return GetResource(new ResourcePath(path), useFallback); } public T GetResource(ResourcePath path, bool useFallback = true) where T : BaseResource, new() { var cache = GetTypeDict(); if (cache.TryGetValue(path, out var cached)) { return (T) cached; } var _resource = new T(); try { _resource.Load(this, path); cache[path] = _resource; return _resource; } catch (Exception e) { if (useFallback && _resource.Fallback != null) { Logger.Error( $"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}"); return GetResource(_resource.Fallback, false); } else { Logger.Error( $"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}"); throw; } } } public bool TryGetResource(string path, out T resource) where T : BaseResource, new() { return TryGetResource(new ResourcePath(path), out resource); } public bool TryGetResource(ResourcePath path, out T resource) where T : BaseResource, new() { var cache = GetTypeDict(); if (cache.TryGetValue(path, out var cached)) { resource = (T) cached; return true; } var _resource = new T(); try { _resource.Load(this, path); resource = _resource; cache[path] = resource; return true; } catch { resource = null; return false; } } public void ReloadResource(string path) where T : BaseResource, new() { ReloadResource(new ResourcePath(path)); } public void ReloadResource(ResourcePath path) where T : BaseResource, new() { var cache = GetTypeDict(); if (!cache.TryGetValue(path, out var res)) { return; } try { res.Reload(this, path); } catch (Exception e) { Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}"); throw; } } public bool HasResource(string path) where T : BaseResource, new() { return HasResource(new ResourcePath(path)); } public bool HasResource(ResourcePath path) where T : BaseResource, new() { return TryGetResource(path, out var _); } public void CacheResource(string path, T resource) where T : BaseResource, new() { CacheResource(new ResourcePath(path), resource); } public void CacheResource(ResourcePath path, T resource) where T : BaseResource, new() { GetTypeDict()[path] = resource; } public T GetFallback() where T : BaseResource, new() { if (_fallbacks.TryGetValue(typeof(T), out var fallback)) { return (T) fallback; } var res = new T(); if (res.Fallback == null) { throw new InvalidOperationException($"Resource of type '{typeof(T)}' has no fallback."); } fallback = GetResource(res.Fallback, useFallback: false); _fallbacks.Add(typeof(T), fallback); return (T) fallback; } public IEnumerable> GetAllResources() where T : BaseResource, new() { return GetTypeDict().Select(p => new KeyValuePair(p.Key, (T) p.Value)); } #region IDisposable Members private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) { return; } if (disposing) { foreach (var res in CachedResources.Values.SelectMany(dict => dict.Values)) { res.Dispose(); } } disposed = true; } ~ResourceCache() { Dispose(false); } #endregion IDisposable Members [MethodImpl(MethodImplOptions.AggressiveInlining)] private Dictionary GetTypeDict() { if (!CachedResources.TryGetValue(typeof(T), out var ret)) { ret = new Dictionary(); CachedResources.Add(typeof(T), ret); } return ret; } } }