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 (FileNotFoundException) {} } } 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; } } }