using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; 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 { private static List readers; private readonly List disposableAssets = new List(); /// /// The graphics device that this content manager uses /// public readonly GraphicsDevice GraphicsDevice; /// /// Creates a new content manager with an optionally specified root directory. /// /// The service provider of your game /// The root directory. Defaults to "Content" public RawContentManager(IServiceProvider serviceProvider, string rootDirectory = "Content") : base(serviceProvider, rootDirectory) { if (serviceProvider.GetService(typeof(IGraphicsDeviceService)) is IGraphicsDeviceService s) this.GraphicsDevice = s.GraphicsDevice; } /// /// 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); } /// protected override void ReloadAsset(string originalAssetName, T currentAsset) { this.Read(originalAssetName, currentAsset); } private T Read(string assetName, T existing) { var triedFiles = new List(); if (readers == null) readers = CollectContentReaders(); foreach (var reader in 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); return t; } } catch (FileNotFoundException) { } } } throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}"); } /// public override void Unload() { foreach (var d in this.disposableAssets) d.Dispose(); this.disposableAssets.Clear(); base.Unload(); } /// public void Initialize() { } private static List CollectContentReaders() { var ret = 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; var inst = type.GetConstructor(Type.EmptyTypes).Invoke(null); ret.Add((RawContentReader) inst); } catch (Exception e) { throw new NotSupportedException($"The type {type} cannot be constructed by a RawContentManager. Does it have a visible parameterless constructor?", e); } } } catch { // ignored } } return ret; } } }