using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#if NET6_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
namespace MLEM.Data.Content {
///
/// Represents a version of that doesn't load content binary xnb files, but rather as their regular formats.
///
public class RawContentManager : ContentManager, IGameComponent {
///
/// The graphics device that this content manager uses
///
public readonly GraphicsDevice GraphicsDevice;
private readonly List disposableAssets = new List();
private readonly List readers;
#if FNA
private Dictionary LoadedAssets { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
#endif
///
/// Creates a new content manager with an optionally specified root directory.
/// Each required for asset loading is gathered and instantiated automatically from the loaded assemblies.
///
/// The service provider of your game
/// The root directory. Defaults to "Content"
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Automatically gathered RawContentReader types might be removed, use other constructor to add readers manually")]
#endif
public RawContentManager(IServiceProvider serviceProvider, string rootDirectory = "Content") :
this(serviceProvider, RawContentManager.CollectContentReaders(), rootDirectory) {}
///
/// Creates a new content manager with an optionally specified root directory.
/// Each required for asset loading has to be passed using .
///
/// The service provider of your game
/// The raw content readers to use, which can be modified externally afterwards to add additional readers if desired.
/// The root directory. Defaults to "Content"
public RawContentManager(IServiceProvider serviceProvider, List readers, string rootDirectory) :
base(serviceProvider, rootDirectory) {
if (serviceProvider.GetService(typeof(IGraphicsDeviceService)) is IGraphicsDeviceService s)
this.GraphicsDevice = s.GraphicsDevice;
this.readers = readers;
}
///
/// Loads a raw asset with the given name, based on the .
/// If the asset was previously loaded using this method, the cached asset is merely returned.
///
/// The path and name of the asset to load, without extension.
/// The type of asset to load
/// The asset, either loaded from the cache, or from disk.
public override T Load(string assetName) {
if (this.LoadedAssets.TryGetValue(assetName, out var ret) && ret is T t)
return t;
return this.Read(assetName, default);
}
///
/// Reloads the asset of the given type, with the given original name.
///
/// The original name of the asset.
/// The current asset instance.
/// The asset's type.
protected
#if !FNA
override
#endif
void ReloadAsset(string originalAssetName, T currentAsset) {
this.Read(originalAssetName, currentAsset);
}
private T Read(string assetName, T existing) {
var triedFiles = new List();
foreach (var reader in this.readers) {
if (!reader.CanRead(typeof(T)))
continue;
foreach (var ext in reader.GetFileExtensions()) {
var file = Path.Combine(this.RootDirectory, $"{assetName}.{ext}");
triedFiles.Add(file);
try {
using (var stream = Path.IsPathRooted(file) ? File.OpenRead(file) : TitleContainer.OpenStream(file)) {
var read = reader.Read(this, assetName, stream, typeof(T), existing);
if (!(read is T t))
throw new ContentLoadException($"{reader} returned non-{typeof(T)} for asset {assetName}");
this.LoadedAssets[assetName] = t;
if (t is IDisposable d && !this.disposableAssets.Contains(d))
this.disposableAssets.Add(d);
if (t is GraphicsResource r)
r.Name = assetName;
return t;
}
} catch (IOException) {}
}
}
throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}");
}
///
/// Unloads this content manager, disposing all of the assets that it loaded.
///
public override void Unload() {
foreach (var d in this.disposableAssets)
d.Dispose();
this.disposableAssets.Clear();
base.Unload();
}
///
/// Initializes the component. Used to load non-graphical resources.
///
public void Initialize() {}
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Automatically gathered RawContentReader types might be removed, use other constructor to add readers manually")]
#endif
private static List CollectContentReaders() {
var ret = new List();
var assemblyExceptions = new List();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
try {
if (assembly.IsDynamic)
continue;
foreach (var type in assembly.GetExportedTypes()) {
try {
if (type.IsAbstract)
continue;
if (!type.IsSubclassOf(typeof(RawContentReader)))
continue;
ret.Add((RawContentReader) Activator.CreateInstance(type));
} catch (Exception e) {
throw new NotSupportedException($"The type {type} cannot be constructed by a RawContentManager. Does it have a visible parameterless constructor?", e);
}
}
} catch (Exception e) {
assemblyExceptions.Add(e);
}
}
if (ret.Count <= 0)
throw new AggregateException("Failed to construct any RawContentReader instances", assemblyExceptions);
return ret;
}
}
}