recrownedgtk/RecrownedGTK/AssetsSystem/AssetManager.cs

222 lines
7.4 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using RecrownedGTK.AssetsSystem.Information;
namespace RecrownedGTK.AssetsSystem
{
/// <summary>
/// Wrapper for the content manager that helps with controlling it by adding automated multithreaded content loading.
/// </summary>
public class AssetManager
{
public readonly AssetLoader assetLoader;
readonly Queue<AssetOutline> queue;
ConcurrentDictionary<string, IDisposable> assets;
private readonly Dictionary<Type, IAssetPathResolver> contentPathModifier;
public IAssetPathResolver normalPathModifier = new NormalAssetPathResolver();
volatile float progress;
volatile bool running;
/// <summary>
/// Whether or not the queue is empty and all content is loaded.
/// </summary>
public bool Done { get { return !running && queue.Count == 0; } }
/// <summary>
/// The progress of the loading. 1 is complete while 0 is incomplete.
/// </summary>
public float Progress { get { return progress; } }
/// <summary>
/// Creates an asset manager with a default asset loader.
/// </summary>
public AssetManager() : this(new AssetLoader())
{
}
/// <summary>
/// A manager for the given asset loader.
/// The manager will asynchronously load assets and store them.
/// </summary>
/// <param name="assetLoader">The asset loader to use for loading.</param>
public AssetManager(AssetLoader assetLoader) {
this.assetLoader = assetLoader;
assets = new ConcurrentDictionary<string, IDisposable>();
queue = new Queue<AssetOutline>();
contentPathModifier = new Dictionary<Type, IAssetPathResolver>();
}
/// <summary>
/// Adds a <see cref="IContentPathResolver"/> to this handler.
/// </summary>
/// <param name="assetType"></param>
/// <param name="contentResolver"></param>
public void AddContentPathResolver(Type assetType, IAssetPathResolver contentResolver) {
contentPathModifier.Add(assetType, contentResolver);
}
/// <summary>
/// Removes the <see cref="IContentPathResolver"/> for the key.
/// </summary>
/// <param name="assetType"></param>
public void RemoveContentResolver(Type assetType) {
contentPathModifier.Remove(assetType);
}
private void Load(string assetName, Type type, bool usePathModifier)
{
Debug.WriteLine("Loading asset: " + assetName);
string path = assetName;
if (usePathModifier)
{
IAssetPathResolver handler;
if (contentPathModifier.ContainsKey(type))
{
handler = contentPathModifier[type];
}
else
{
handler = normalPathModifier;
}
path = handler.Modify(assetName);
}
assets.TryAdd(assetName, assetLoader.Load(path, type).CreateUseable());
}
/// <summary>
/// Gets the requested asset.
/// </summary>
/// <typeparam name="T">The type of the asset for an alternative way to cast.</typeparam>
/// <param name="assetName">The name of the asset.</param>
/// <returns>The asset casted to the type given with T.</returns>
public T Get<T>(string assetName)
{
lock (queue)
{
return (T)assets[assetName];
}
}
/// <summary>
/// Queues an asset to be loaded.
/// </summary>
/// <typeparam name="T">The type of the asset to be queued.</typeparam>
/// <param name="assetName">Name of asset to look for.</param>
/// <param name="usePathModifier">Whether or not to use the path modifiers.</param>
public void Queue<T>(string assetName, bool usePathModifier = true)
{
lock (queue)
{
if (!assets.ContainsKey(assetName))
{
queue.Enqueue(new AssetOutline(assetName, typeof(T), usePathModifier));
Debug.WriteLine("Queued asset: " + assetName);
Update();
}
else
{
throw new InvalidOperationException("Did not queue asset due to asset with same name being loaded: " + assetName);
}
}
}
/// <summary>
/// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame.
/// </summary>
public void Update()
{
if (queue.Count > 0 && !running)
{
ThreadPool.QueueUserWorkItem(LoadBatchCallback);
}
}
private void LoadBatchCallback(object info) {
LoadBatch();
}
private void LoadBatch()
{
running = true;
int totalTasks = queue.Count;
int tasksCompleted = 0;
while (queue.Count != 0)
{
lock (queue)
{
AssetOutline content = queue.Dequeue();
Load(content.assetName, content.type, content.usePathModifier);
tasksCompleted++;
progress = (float)tasksCompleted / totalTasks;
}
}
running = false;
}
/// <summary>
/// Removes the asset from the list of assets in the system.
/// Cannot remove from queue.
/// </summary>
/// <param name="name">the string name used to load the asset</param>
public void Remove(string name)
{
lock (queue)
{
if (assets.ContainsKey(name))
{
if (assets[name] is IDisposable)
{
((IDisposable)assets[name]).Dispose();
}
IDisposable disposable = null;
assets.TryRemove(name, out disposable);
}
}
}
/// <summary>
/// Clears the queue.
/// </summary>
public void ClearQueue()
{
lock (queue)
{
queue.Clear();
}
}
/// <summary>
/// Unloads everything from both queue and loaded list while properly disposing of the assets loaded.
/// </summary>
public void UnloadAll()
{
lock (queue)
{
foreach (KeyValuePair<string, IDisposable> asset in assets)
{
asset.Value.Dispose();
}
assets.Clear();
ClearQueue();
Debug.WriteLine("Unloaded all assets.");
}
}
private struct AssetOutline
{
internal Type type;
internal string assetName;
internal bool usePathModifier;
public AssetOutline(string assetName, Type type, bool usePathModifier)
{
this.type = type;
this.assetName = assetName;
this.usePathModifier = usePathModifier;
}
}
}
}