fix errors caused by content managers finalizing asynchronously

This commit is contained in:
Jesse Plamondon-Willard 2017-10-01 14:08:28 -04:00
parent 29232ffd45
commit bd4ed43829
1 changed files with 87 additions and 72 deletions

View File

@ -128,8 +128,11 @@ namespace StardewModdingAPI.Framework
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
public bool IsLoaded(string assetName) public bool IsLoaded(string assetName)
{ {
assetName = this.NormaliseAssetName(assetName); lock (this.Cache)
return this.IsNormalisedKeyLoaded(assetName); {
assetName = this.NormaliseAssetName(assetName);
return this.IsNormalisedKeyLoaded(assetName);
}
} }
/// <summary>Load an asset that has been processed by the content pipeline.</summary> /// <summary>Load an asset that has been processed by the content pipeline.</summary>
@ -146,38 +149,41 @@ namespace StardewModdingAPI.Framework
/// <param name="instance">The content manager instance for which to load the asset.</param> /// <param name="instance">The content manager instance for which to load the asset.</param>
public T LoadFor<T>(string assetName, ContentManager instance) public T LoadFor<T>(string assetName, ContentManager instance)
{ {
assetName = this.NormaliseAssetName(assetName); lock (this.Cache)
{
assetName = this.NormaliseAssetName(assetName);
// skip if already loaded // skip if already loaded
if (this.IsNormalisedKeyLoaded(assetName)) if (this.IsNormalisedKeyLoaded(assetName))
{
this.TrackAssetLoader(assetName, instance);
return base.Load<T>(assetName);
}
// load asset
T data;
if (this.AssetsBeingLoaded.Contains(assetName))
{
this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn);
this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace);
data = base.Load<T>(assetName);
}
else
{
data = this.AssetsBeingLoaded.Track(assetName, () =>
{ {
IAssetInfo info = new AssetInfo(this.GetLocale(), assetName, typeof(T), this.NormaliseAssetName); this.TrackAssetLoader(assetName, instance);
IAssetData asset = this.ApplyLoader<T>(info) ?? new AssetDataForObject(info, base.Load<T>(assetName), this.NormaliseAssetName); return base.Load<T>(assetName);
asset = this.ApplyEditors<T>(info, asset); }
return (T)asset.Data;
});
}
// update cache & return data // load asset
this.Cache[assetName] = data; T data;
this.TrackAssetLoader(assetName, instance); if (this.AssetsBeingLoaded.Contains(assetName))
return data; {
this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn);
this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace);
data = base.Load<T>(assetName);
}
else
{
data = this.AssetsBeingLoaded.Track(assetName, () =>
{
IAssetInfo info = new AssetInfo(this.GetLocale(), assetName, typeof(T), this.NormaliseAssetName);
IAssetData asset = this.ApplyLoader<T>(info) ?? new AssetDataForObject(info, base.Load<T>(assetName), this.NormaliseAssetName);
asset = this.ApplyEditors<T>(info, asset);
return (T)asset.Data;
});
}
// update cache & return data
this.Cache[assetName] = data;
this.TrackAssetLoader(assetName, instance);
return data;
}
} }
/// <summary>Inject an asset into the cache.</summary> /// <summary>Inject an asset into the cache.</summary>
@ -186,9 +192,12 @@ namespace StardewModdingAPI.Framework
/// <param name="value">The asset value.</param> /// <param name="value">The asset value.</param>
public void Inject<T>(string assetName, T value) public void Inject<T>(string assetName, T value)
{ {
assetName = this.NormaliseAssetName(assetName); lock (this.Cache)
this.Cache[assetName] = value; {
this.TrackAssetLoader(assetName, this); assetName = this.NormaliseAssetName(assetName);
this.Cache[assetName] = value;
this.TrackAssetLoader(assetName, this);
}
} }
/// <summary>Get the current content locale.</summary> /// <summary>Get the current content locale.</summary>
@ -200,16 +209,19 @@ namespace StardewModdingAPI.Framework
/// <summary>Get the cached asset keys.</summary> /// <summary>Get the cached asset keys.</summary>
public IEnumerable<string> GetAssetKeys() public IEnumerable<string> GetAssetKeys()
{ {
IEnumerable<string> GetAllAssetKeys() lock (this.Cache)
{ {
foreach (string cacheKey in this.Cache.Keys) IEnumerable<string> GetAllAssetKeys()
{ {
this.ParseCacheKey(cacheKey, out string assetKey, out string _); foreach (string cacheKey in this.Cache.Keys)
yield return assetKey; {
this.ParseCacheKey(cacheKey, out string assetKey, out string _);
yield return assetKey;
}
} }
}
return GetAllAssetKeys().Distinct(); return GetAllAssetKeys().Distinct();
}
} }
/// <summary>Purge assets from the cache that match one of the interceptors.</summary> /// <summary>Purge assets from the cache that match one of the interceptors.</summary>
@ -248,45 +260,48 @@ namespace StardewModdingAPI.Framework
/// <returns>Returns whether any cache entries were invalidated.</returns> /// <returns>Returns whether any cache entries were invalidated.</returns>
public bool InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) public bool InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{ {
// find matching asset keys lock (this.Cache)
HashSet<string> purgeCacheKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
HashSet<string> purgeAssetKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
foreach (string cacheKey in this.Cache.Keys)
{ {
this.ParseCacheKey(cacheKey, out string assetKey, out _); // find matching asset keys
Type type = this.Cache[cacheKey].GetType(); HashSet<string> purgeCacheKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
if (predicate(assetKey, type)) HashSet<string> purgeAssetKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
foreach (string cacheKey in this.Cache.Keys)
{ {
purgeAssetKeys.Add(assetKey); this.ParseCacheKey(cacheKey, out string assetKey, out _);
purgeCacheKeys.Add(cacheKey); Type type = this.Cache[cacheKey].GetType();
if (predicate(assetKey, type))
{
purgeAssetKeys.Add(assetKey);
purgeCacheKeys.Add(cacheKey);
}
} }
}
// purge assets // purge assets
foreach (string key in purgeCacheKeys) foreach (string key in purgeCacheKeys)
{ {
if (dispose && this.Cache[key] is IDisposable disposable) if (dispose && this.Cache[key] is IDisposable disposable)
disposable.Dispose(); disposable.Dispose();
this.Cache.Remove(key); this.Cache.Remove(key);
this.AssetLoaders.Remove(key); this.AssetLoaders.Remove(key);
} }
// reload core game assets // reload core game assets
int reloaded = 0; int reloaded = 0;
foreach (string key in purgeAssetKeys) foreach (string key in purgeAssetKeys)
{ {
if (this.CoreAssets.ReloadForKey(this, key)) if (this.CoreAssets.ReloadForKey(this, key))
reloaded++; reloaded++;
} }
// report result // report result
if (purgeCacheKeys.Any()) if (purgeCacheKeys.Any())
{ {
this.Monitor.Log($"Invalidated {purgeCacheKeys.Count} cache entries for {purgeAssetKeys.Count} asset keys: {string.Join(", ", purgeCacheKeys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace); this.Monitor.Log($"Invalidated {purgeCacheKeys.Count} cache entries for {purgeAssetKeys.Count} asset keys: {string.Join(", ", purgeCacheKeys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
return true; return true;
}
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
return false;
} }
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
return false;
} }
/// <summary>Dispose assets for the given content manager shim.</summary> /// <summary>Dispose assets for the given content manager shim.</summary>