Preloading bundles and setting subtitle path (#772)

## Minor features
- Can now set `preloadBundles` in the addon config file. This will load
asset bundles on the main menu and keep them loaded. Will improve
initial load times at the cost of more memory usage.
- Can now set `subtitlePath` in the addon config file for displaying a
subtitle for your mod on the main menu.
This commit is contained in:
Will Corby 2024-02-03 15:42:29 -08:00 committed by GitHub
commit 4f0998a4ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 134 additions and 14 deletions

View File

@ -31,5 +31,17 @@ namespace NewHorizons.External.Configs
/// If popupMessage is set, should it repeat every time the game starts or only once /// If popupMessage is set, should it repeat every time the game starts or only once
/// </summary> /// </summary>
public bool repeatPopup; public bool repeatPopup;
/// <summary>
/// These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use.
/// The path is the relative directory of the asset bundle in the mod folder.
/// </summary>
public string[] preloadAssetBundles;
/// <summary>
/// The path to the addons subtitle for the main menu.
/// Defaults to "subtitle.png".
/// </summary>
public string subtitlePath = "subtitle.png";
} }
} }

View File

@ -61,9 +61,17 @@ namespace NewHorizons.Handlers
private void AddSubtitles() private void AddSubtitles()
{ {
foreach (var mod in Main.MountedAddons.Where(mod => File.Exists($"{mod.ModHelper.Manifest.ModFolderPath}subtitle.png"))) foreach (var mod in Main.MountedAddons)
{ {
AddSubtitle(mod, "subtitle.png"); if (Main.AddonConfigs.TryGetValue(mod, out var addonConfig) && File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, addonConfig.subtitlePath)))
{
AddSubtitle(mod, addonConfig.subtitlePath);
}
// Else default to subtitle.png
else if (File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "subtitle.png")))
{
AddSubtitle(mod, "subtitle.png");
}
} }
} }

View File

@ -50,9 +50,10 @@ namespace NewHorizons
private static bool _wasConfigured = false; private static bool _wasConfigured = false;
private static string _defaultSystemOverride; private static string _defaultSystemOverride;
public static Dictionary<string, NewHorizonsSystem> SystemDict = new Dictionary<string, NewHorizonsSystem>(); public static Dictionary<string, NewHorizonsSystem> SystemDict = new();
public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new Dictionary<string, List<NewHorizonsBody>>(); public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new();
public static List<IModBehaviour> MountedAddons = new List<IModBehaviour>(); public static List<IModBehaviour> MountedAddons = new();
public static Dictionary<IModBehaviour, AddonConfig> AddonConfigs = new();
public static float SecondsElapsedInLoop = -1; public static float SecondsElapsedInLoop = -1;
@ -747,6 +748,15 @@ namespace NewHorizons
{ {
MenuHandler.RegisterOneTimePopup(mod, TranslationHandler.GetTranslation(addonConfig.popupMessage, TranslationHandler.TextType.UI), addonConfig.repeatPopup); MenuHandler.RegisterOneTimePopup(mod, TranslationHandler.GetTranslation(addonConfig.popupMessage, TranslationHandler.TextType.UI), addonConfig.repeatPopup);
} }
if (addonConfig.preloadAssetBundles != null)
{
foreach (var bundle in addonConfig.preloadAssetBundles)
{
AssetBundleUtilities.PreloadBundle(bundle, mod);
}
}
AddonConfigs[mod] = addonConfig;
} }
private void LoadTranslations(string folder, IModBehaviour mod) private void LoadTranslations(string folder, IModBehaviour mod)

View File

@ -28,7 +28,6 @@ using static NewHorizons.External.Modules.ShipLogModule;
namespace NewHorizons namespace NewHorizons
{ {
public class NewHorizonsApi : INewHorizons public class NewHorizonsApi : INewHorizons
{ {
[Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] [Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]

View File

@ -1,4 +1,5 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.Utility.Files;
using NewHorizons.Utility.OWML; using NewHorizons.Utility.OWML;
namespace NewHorizons.Patches.EyeScenePatches namespace NewHorizons.Patches.EyeScenePatches
@ -6,10 +7,18 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(typeof(SubmitActionLoadScene))] [HarmonyPatch(typeof(SubmitActionLoadScene))]
public static class SubmitActionLoadScenePatches public static class SubmitActionLoadScenePatches
{ {
// To call the base method
[HarmonyReversePatch]
[HarmonyPatch(typeof(SubmitActionConfirm), nameof(SubmitActionConfirm.ConfirmSubmit))]
public static void SubmitActionConfirm_ConfirmSubmit(SubmitActionConfirm instance) { }
[HarmonyPrefix] [HarmonyPrefix]
[HarmonyPatch(nameof(SubmitActionLoadScene.ConfirmSubmit))] [HarmonyPatch(nameof(SubmitActionLoadScene.ConfirmSubmit))]
public static void SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance) public static bool SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance)
{ {
if (__instance._receivedSubmitAction) return false;
// Title screen can warp you to eye and cause problems. // Title screen can warp you to eye and cause problems.
if (__instance._sceneToLoad == SubmitActionLoadScene.LoadableScenes.EYE) if (__instance._sceneToLoad == SubmitActionLoadScene.LoadableScenes.EYE)
{ {
@ -17,6 +26,44 @@ namespace NewHorizons.Patches.EyeScenePatches
Main.Instance.IsWarpingBackToEye = true; Main.Instance.IsWarpingBackToEye = true;
__instance._sceneToLoad = SubmitActionLoadScene.LoadableScenes.GAME; __instance._sceneToLoad = SubmitActionLoadScene.LoadableScenes.GAME;
} }
// modified from patched function
SubmitActionConfirm_ConfirmSubmit(__instance);
__instance._receivedSubmitAction = true;
Locator.GetMenuInputModule().DisableInputs();
Delay.RunWhen(() =>
{
// update text. just use 0%
__instance.ResetStringBuilder();
__instance._nowLoadingSB.Append(UITextLibrary.GetString(UITextType.LoadingMessage));
__instance._nowLoadingSB.Append(0.ToString("P0"));
__instance._loadingText.text = __instance._nowLoadingSB.ToString();
return AssetBundleUtilities.AreRequiredAssetsLoaded();
}, () =>
{
switch (__instance._sceneToLoad)
{
case SubmitActionLoadScene.LoadableScenes.GAME:
LoadManager.LoadSceneAsync(OWScene.SolarSystem, false, LoadManager.FadeType.ToBlack, 1f, false);
__instance.ResetStringBuilder();
__instance._waitingOnStreaming = true;
break;
case SubmitActionLoadScene.LoadableScenes.EYE:
LoadManager.LoadSceneAsync(OWScene.EyeOfTheUniverse, true, LoadManager.FadeType.ToBlack, 1f, false);
__instance.ResetStringBuilder();
break;
case SubmitActionLoadScene.LoadableScenes.TITLE:
LoadManager.LoadScene(OWScene.TitleScreen, LoadManager.FadeType.ToBlack, 2f, true);
break;
case SubmitActionLoadScene.LoadableScenes.CREDITS:
LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack, 1f, false);
break;
}
});
return false;
} }
} }
} }

View File

@ -27,6 +27,17 @@
"type": "boolean", "type": "boolean",
"description": "If popupMessage is set, should it repeat every time the game starts or only once" "description": "If popupMessage is set, should it repeat every time the game starts or only once"
}, },
"preloadAssetBundles": {
"type": "array",
"description": "These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use.\nThe path is the relative directory of the asset bundle in the mod folder.",
"items": {
"type": "string"
}
},
"subtitlePath": {
"type": "string",
"description": "The path to the addons subtitle for the main menu.\nDefaults to \"subtitle.png\"."
},
"$schema": { "$schema": {
"type": "string", "type": "string",
"description": "The schema to validate with" "description": "The schema to validate with"

View File

@ -3,24 +3,57 @@ using OWML.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using UnityEngine; using UnityEngine;
namespace NewHorizons.Utility.Files namespace NewHorizons.Utility.Files
{ {
public static class AssetBundleUtilities public static class AssetBundleUtilities
{ {
public static Dictionary<string, AssetBundle> AssetBundles = new Dictionary<string, AssetBundle>(); public static Dictionary<string, (AssetBundle bundle, bool keepLoaded)> AssetBundles = new();
private static readonly List<AssetBundleCreateRequest> _loadingBundles = new();
public static void ClearCache() public static void ClearCache()
{ {
foreach (var pair in AssetBundles) foreach (var pair in AssetBundles)
{ {
if (pair.Value == null) NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload"); if (!pair.Value.keepLoaded)
else pair.Value.Unload(true); {
if (pair.Value.bundle == null)
{
NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload");
}
else
{
pair.Value.bundle.Unload(true);
}
}
} }
AssetBundles.Clear(); AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value);
} }
public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod)
{
string key = Path.GetFileName(assetBundleRelativeDir);
var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir);
var request = AssetBundle.LoadFromFileAsync(completePath);
_loadingBundles.Add(request);
NHLogger.Log($"Preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left");
request.completed += _ =>
{
_loadingBundles.Remove(request);
NHLogger.Log($"Finshed preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left");
AssetBundles[key] = (request.assetBundle, true);
};
}
/// <summary>
/// are preloaded bundles done loading?
/// </summary>
public static bool AreRequiredAssetsLoaded() => _loadingBundles.Count == 0;
public static T Load<T>(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object public static T Load<T>(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object
{ {
string key = Path.GetFileName(assetBundleRelativeDir); string key = Path.GetFileName(assetBundleRelativeDir);
@ -32,7 +65,7 @@ namespace NewHorizons.Utility.Files
if (AssetBundles.ContainsKey(key)) if (AssetBundles.ContainsKey(key))
{ {
bundle = AssetBundles[key]; bundle = AssetBundles[key].bundle;
} }
else else
{ {
@ -44,7 +77,7 @@ namespace NewHorizons.Utility.Files
return null; return null;
} }
AssetBundles[key] = bundle; AssetBundles[key] = (bundle, false);
} }
obj = bundle.LoadAsset<T>(pathInBundle); obj = bundle.LoadAsset<T>(pathInBundle);
@ -124,4 +157,4 @@ namespace NewHorizons.Utility.Files
} }
} }
} }
} }