From bd04d46dd1d66b30d4f21575bbbd2e541eabcef3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 22 May 2018 22:53:44 -0400 Subject: refactor content API to fix load errors with decentralised cache (#524) --- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 66 ++++++++++++++----------- 1 file changed, 38 insertions(+), 28 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 4a71f7e7..ce26c980 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Utilities; using StardewValley; @@ -25,8 +26,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// SMAPI's core content logic. private readonly ContentCoordinator ContentCore; - /// The content manager for this mod. - private readonly SContentManager ContentManager; + /// A content manager for this mod which manages files from the game's Content folder. + private readonly IContentManager GameContentManager; + + /// A content manager for this mod which manages files from the mod's folder. + private readonly IContentManager ModContentManager; /// The absolute path to the mod folder. private readonly string ModFolderPath; @@ -42,10 +46,10 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Accessors *********/ /// The game's current locale code (like pt-BR). - public string CurrentLocale => this.ContentManager.GetLocale(); + public string CurrentLocale => this.GameContentManager.GetLocale(); /// The game's current locale as an enum value. - public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.ContentManager.Language; + public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; /// The observable implementation of . internal ObservableCollection ObservableAssetEditors { get; } = new ObservableCollection(); @@ -65,16 +69,16 @@ namespace StardewModdingAPI.Framework.ModHelpers *********/ /// Construct an instance. /// SMAPI's core content logic. - /// The content manager for this mod. /// The absolute path to the mod folder. /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. - public ContentHelper(ContentCoordinator contentCore, SContentManager contentManager, string modFolderPath, string modID, string modName, IMonitor monitor) + public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor) : base(modID) { this.ContentCore = contentCore; - this.ContentManager = contentManager; + this.GameContentManager = contentCore.CreateGameContentManager(this.ContentCore.GetManagedAssetPrefix(modID) + ".content"); + this.ModContentManager = contentCore.CreateModContentManager(this.ContentCore.GetManagedAssetPrefix(modID), rootDirectory: modFolderPath); this.ModFolderPath = modFolderPath; this.ModName = modName; this.Monitor = monitor; @@ -92,24 +96,22 @@ namespace StardewModdingAPI.Framework.ModHelpers try { - this.AssertValidAssetKeyFormat(key); + this.AssertAndNormaliseAssetName(key); switch (source) { case ContentSource.GameContent: - return this.ContentCore.MainContentManager.Load(key); + return this.GameContentManager.Load(key); case ContentSource.ModFolder: // get file FileInfo file = this.GetModFile(key); if (!file.Exists) throw GetContentError($"there's no matching file at path '{file.FullName}'."); - - // get asset path - string assetName = this.ContentManager.GetAssetNameFromFilePath(file.FullName, ContentSource.ModFolder); + string internalKey = this.GetInternalModAssetKey(file); // try cache - if (this.ContentManager.IsLoaded(assetName)) - return this.ContentManager.Load(assetName); + if (this.ModContentManager.IsLoaded(internalKey)) + return this.ModContentManager.Load(internalKey); // fix map tilesheets if (file.Extension.ToLower() == ".tbin") @@ -121,15 +123,15 @@ namespace StardewModdingAPI.Framework.ModHelpers // fetch & cache FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); - this.FixCustomTilesheetPaths(map, key); + this.FixCustomTilesheetPaths(map, relativeMapPath: key); // inject map - this.ContentManager.Inject(assetName, map); + this.ModContentManager.Inject(internalKey, map); return (T)(object)map; } // load through content manager - return this.ContentManager.Load(assetName); + return this.ModContentManager.Load(internalKey); default: throw GetContentError($"unknown content source '{source}'."); @@ -146,7 +148,7 @@ namespace StardewModdingAPI.Framework.ModHelpers [Pure] public string NormaliseAssetName(string assetName) { - return this.ContentManager.NormaliseAssetName(assetName); + return this.ModContentManager.AssertAndNormaliseAssetName(assetName); } /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. @@ -158,11 +160,11 @@ namespace StardewModdingAPI.Framework.ModHelpers switch (source) { case ContentSource.GameContent: - return this.ContentManager.NormaliseAssetName(key); + return this.GameContentManager.AssertAndNormaliseAssetName(key); case ContentSource.ModFolder: FileInfo file = this.GetModFile(key); - return this.ContentManager.NormaliseAssetName(this.ContentManager.GetAssetNameFromFilePath(file.FullName, ContentSource.GameContent)); + return this.GetInternalModAssetKey(file); default: throw new NotSupportedException($"Unknown content source '{source}'."); @@ -205,16 +207,24 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The asset key to check. /// The asset key is empty or contains invalid characters. [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] - private void AssertValidAssetKeyFormat(string key) + private void AssertAndNormaliseAssetName(string key) { - this.ContentManager.AssertValidAssetKeyFormat(key); + this.ModContentManager.AssertAndNormaliseAssetName(key); if (Path.IsPathRooted(key)) throw new ArgumentException("The asset key must not be an absolute path."); } + /// Get the internal key in the content cache for a mod asset. + /// The asset file. + private string GetInternalModAssetKey(FileInfo modFile) + { + string relativePath = PathUtilities.GetRelativePath(this.ModFolderPath, modFile.FullName); + return Path.Combine(this.ModContentManager.Name, relativePath); + } + /// Fix custom map tilesheet paths so they can be found by the content manager. /// The map whose tilesheets to fix. - /// The map asset key within the mod folder. + /// The relative map path within the mod folder. /// A map tilesheet couldn't be resolved. /// /// The game's logic for tilesheets in is a bit specialised. It boils @@ -230,13 +240,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// /// While that doesn't exactly match the game logic, it's close enough that it's unlikely to make a difference. /// - private void FixCustomTilesheetPaths(Map map, string mapKey) + private void FixCustomTilesheetPaths(Map map, string relativeMapPath) { // get map info if (!map.TileSheets.Any()) return; - mapKey = this.ContentManager.NormaliseAssetName(mapKey); // Mono's Path.GetDirectoryName doesn't handle Windows dir separators - string relativeMapFolder = Path.GetDirectoryName(mapKey) ?? ""; // folder path containing the map, relative to the mod folder + relativeMapPath = this.ModContentManager.AssertAndNormaliseAssetName(relativeMapPath); // Mono's Path.GetDirectoryName doesn't handle Windows dir separators + string relativeMapFolder = Path.GetDirectoryName(relativeMapPath) ?? ""; // folder path containing the map, relative to the mod folder // fix tilesheets foreach (TileSheet tilesheet in map.TileSheets) @@ -341,7 +351,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private FileInfo GetModFile(string path) { // try exact match - path = Path.Combine(this.ModFolderPath, this.ContentManager.NormalisePathSeparators(path)); + path = Path.Combine(this.ModFolderPath, this.ModContentManager.NormalisePathSeparators(path)); FileInfo file = new FileInfo(path); // try with default extension @@ -360,7 +370,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private FileInfo GetContentFolderFile(string key) { // get file path - string path = Path.Combine(this.ContentManager.FullRootDirectory, key); + string path = Path.Combine(this.GameContentManager.FullRootDirectory, key); if (!path.EndsWith(".xnb")) path += ".xnb"; -- cgit