diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-11-01 17:42:18 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-11-01 17:42:18 -0400 |
commit | e0b72374cd14298aacc6f71dc391fdc9814be37c (patch) | |
tree | 2e5d85937c34539c1a0df48423b5136508693ca8 /src/SMAPI/Framework/Content/ContentCache.cs | |
parent | 79118316065a01322d8ea12a14589ec016794c32 (diff) | |
parent | 089e6de749ae7cb109af00164d2597c6644c255e (diff) | |
download | SMAPI-e0b72374cd14298aacc6f71dc391fdc9814be37c.tar.gz SMAPI-e0b72374cd14298aacc6f71dc391fdc9814be37c.tar.bz2 SMAPI-e0b72374cd14298aacc6f71dc391fdc9814be37c.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/Content/ContentCache.cs')
-rw-r--r-- | src/SMAPI/Framework/Content/ContentCache.cs | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs new file mode 100644 index 00000000..10c41d08 --- /dev/null +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.Xna.Framework; +using StardewModdingAPI.Framework.ModLoading; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; + +namespace StardewModdingAPI.Framework.Content +{ + /// <summary>A low-level wrapper around the content cache which handles reading, writing, and invalidating entries in the cache. This doesn't handle any higher-level logic like localisation, loading content, etc. It assumes all keys passed in are already normalised.</summary> + internal class ContentCache + { + /********* + ** Properties + *********/ + /// <summary>The underlying asset cache.</summary> + private readonly IDictionary<string, object> Cache; + + /// <summary>The possible directory separator characters in an asset key.</summary> + private readonly char[] PossiblePathSeparators; + + /// <summary>The preferred directory separator chaeacter in an asset key.</summary> + private readonly string PreferredPathSeparator; + + /// <summary>Applies platform-specific asset key normalisation so it's consistent with the underlying cache.</summary> + private readonly Func<string, string> NormaliseAssetNameForPlatform; + + + /********* + ** Accessors + *********/ + /// <summary>Get or set the value of a raw cache entry.</summary> + /// <param name="key">The cache key.</param> + public object this[string key] + { + get => this.Cache[key]; + set => this.Cache[key] = value; + } + + /// <summary>The current cache keys.</summary> + public IEnumerable<string> Keys => this.Cache.Keys; + + + /********* + ** Public methods + *********/ + /**** + ** Constructor + ****/ + /// <summary>Construct an instance.</summary> + /// <param name="contentManager">The underlying content manager whose cache to manage.</param> + /// <param name="reflection">Simplifies access to private game code.</param> + /// <param name="possiblePathSeparators">The possible directory separator characters in an asset key.</param> + /// <param name="preferredPathSeparator">The preferred directory separator chaeacter in an asset key.</param> + public ContentCache(LocalizedContentManager contentManager, Reflector reflection, char[] possiblePathSeparators, string preferredPathSeparator) + { + // init + this.Cache = reflection.GetPrivateField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue(); + this.PossiblePathSeparators = possiblePathSeparators; + this.PreferredPathSeparator = preferredPathSeparator; + + // get key normalisation logic + if (Constants.TargetPlatform == Platform.Windows) + { + IPrivateMethod method = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); + this.NormaliseAssetNameForPlatform = path => method.Invoke<string>(path); + } + else + this.NormaliseAssetNameForPlatform = key => key.Replace('\\', '/'); // based on MonoGame's ContentManager.Load<T> logic + } + + /**** + ** Fetch + ****/ + /// <summary>Get whether the cache contains a given key.</summary> + /// <param name="key">The cache key.</param> + public bool ContainsKey(string key) + { + return this.Cache.ContainsKey(key); + } + + + /**** + ** Normalise + ****/ + /// <summary>Normalise path separators in a file path. For asset keys, see <see cref="NormaliseKey"/> instead.</summary> + /// <param name="path">The file path to normalise.</param> + [Pure] + public string NormalisePathSeparators(string path) + { + string[] parts = path.Split(this.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); + string normalised = string.Join(this.PreferredPathSeparator, parts); + if (path.StartsWith(this.PreferredPathSeparator)) + normalised = this.PreferredPathSeparator + normalised; // keep root slash + return normalised; + } + + /// <summary>Normalise a cache key so it's consistent with the underlying cache.</summary> + /// <param name="key">The asset key.</param> + [Pure] + public string NormaliseKey(string key) + { + key = this.NormalisePathSeparators(key); + return key.EndsWith(".xnb", StringComparison.InvariantCultureIgnoreCase) + ? key.Substring(0, key.Length - 4) + : this.NormaliseAssetNameForPlatform(key); + } + + /**** + ** Remove + ****/ + /// <summary>Remove an asset with the given key.</summary> + /// <param name="key">The cache key.</param> + /// <param name="dispose">Whether to dispose the entry value, if applicable.</param> + /// <returns>Returns the removed key (if any).</returns> + public bool Remove(string key, bool dispose) + { + // get entry + if (!this.Cache.TryGetValue(key, out object value)) + return false; + + // dispose & remove entry + if (dispose && value is IDisposable disposable) + disposable.Dispose(); + + return this.Cache.Remove(key); + } + + /// <summary>Purge matched assets from the cache.</summary> + /// <param name="predicate">Matches the asset keys to invalidate.</param> + /// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param> + /// <returns>Returns the removed keys (if any).</returns> + public IEnumerable<string> Remove(Func<string, Type, bool> predicate, bool dispose = false) + { + List<string> removed = new List<string>(); + foreach (string key in this.Cache.Keys.ToArray()) + { + Type type = this.Cache[key].GetType(); + if (predicate(key, type)) + { + this.Remove(key, dispose); + removed.Add(key); + } + } + return removed; + } + } +} |