From 784b1adb60fbe23ccf40288e8e38d5d93933709f Mon Sep 17 00:00:00 2001 From: xen-42 Date: Thu, 11 Jul 2024 21:12:07 -0400 Subject: [PATCH 1/4] it doesnt work --- .../Builder/Props/ProjectionBuilder.cs | 20 +++--- NewHorizons/Utility/Files/ImageUtilities.cs | 5 ++ .../Files/SlideReelAsyncImageLoader.cs | 64 ++++++++++++++++--- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index a8e42c0f..2d25948d 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -513,7 +513,7 @@ namespace NewHorizons.Builder.Props { NHLogger.LogVerbose($"The atlas cache for slide reel containing [{slides.FirstOrDefault(x => !string.IsNullOrEmpty(x.imagePath))?.imagePath}] is {atlasKey}"); // Load the atlas texture used to draw onto the physical slide reel object - atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png"))); + atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png"), false)); } for (int i = 0; i < slides.Length; i++) @@ -527,11 +527,12 @@ namespace NewHorizons.Builder.Props if (useInvertedCache && cacheExists) { // Load the inverted images used when displaying slide reels to a screen - invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png"))); + invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png"), false)); } else { - imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"))); + // Used to then make cached stuff + imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"), useInvertedCache)); } } else @@ -539,11 +540,11 @@ namespace NewHorizons.Builder.Props if (useInvertedCache && cacheExists) { // Load the inverted images used when displaying slide reels to a screen - invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath))); + invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath), false)); } else { - imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath))); + imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath), useInvertedCache)); } } @@ -556,16 +557,16 @@ namespace NewHorizons.Builder.Props { if (useAtlasCache) { - atlasImageLoader.Start(false); + atlasImageLoader.Start(false, false); } // When using the inverted cache we never need the regular images if (useInvertedCache) { - invertedImageLoader.Start(true); + invertedImageLoader.Start(true, false); } else { - imageLoader.Start(true); + imageLoader.Start(true, false); } return (invertedImageLoader, atlasImageLoader, imageLoader); @@ -573,7 +574,8 @@ namespace NewHorizons.Builder.Props else { // Will be slow and create the cache if needed - imageLoader.Start(true); + // Will run sequentially to ensure we don't run out of memory + imageLoader.Start(true, true); return (null, null, imageLoader); } diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs index c6ef49da..5d0c4d2f 100644 --- a/NewHorizons/Utility/Files/ImageUtilities.cs +++ b/NewHorizons/Utility/Files/ImageUtilities.cs @@ -72,6 +72,11 @@ namespace NewHorizons.Utility.Files { var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename); var key = GetKey(path); + DeleteTexture(key, texture); + } + + public static void DeleteTexture(string key, Texture2D texture) + { if (_textureCache.ContainsKey(key)) { if (_textureCache[key] == texture) diff --git a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs index 58b9db92..8897c927 100644 --- a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs +++ b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs @@ -15,7 +15,7 @@ namespace NewHorizons.Utility.Files; /// public class SlideReelAsyncImageLoader { - public List<(int index, string path)> PathsToLoad { get; private set; } = new(); + public List<(int index, string path, bool deleteImmediately)> PathsToLoad { get; private set; } = new(); public class ImageLoadedEvent : UnityEvent { } public ImageLoadedEvent imageLoadedEvent = new(); @@ -31,7 +31,7 @@ public class SlideReelAsyncImageLoader private bool _started; private bool _clamp; - public void Start(bool clamp) + public void Start(bool clamp, bool sequential) { if (_started) return; @@ -46,7 +46,7 @@ public class SlideReelAsyncImageLoader NHLogger.LogVerbose("Loading new slide reel"); imageLoadedEvent.AddListener(OnImageLoaded); - SingletonSlideReelAsyncImageLoader.Instance.Load(this); + SingletonSlideReelAsyncImageLoader.Instance.Load(this, sequential); } private void OnImageLoaded(Texture texture, int index, string originalPath) @@ -60,7 +60,17 @@ public class SlideReelAsyncImageLoader } } - private IEnumerator DownloadTexture(string url, int index) + private IEnumerator DownloadTextures() + { + foreach (var (index, path, deleteImmediately) in PathsToLoad) + { + NHLogger.LogVerbose($"Loaded slide reel {index} of {PathsToLoad.Count}"); + + yield return DownloadTexture(path, index, deleteImmediately); + } + } + + private IEnumerator DownloadTexture(string url, int index, bool deleteImmediately) { var key = ImageUtilities.GetKey(url); if (ImageUtilities.CheckCachedTexture(key, out var existingTexture)) @@ -107,6 +117,12 @@ public class SlideReelAsyncImageLoader var time = DateTime.Now; imageLoadedEvent?.Invoke(texture, index, url); + + // For when its an image loaded only to create a new cacheable image + if (deleteImmediately) + { + ImageUtilities.DeleteTexture(key, texture); + } NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms"); } } @@ -115,6 +131,10 @@ public class SlideReelAsyncImageLoader { public static SingletonSlideReelAsyncImageLoader Instance { get; private set; } + private Queue _loaders = new(); + + private bool _isLoading; + public void Awake() { Instance = this; @@ -124,20 +144,48 @@ public class SlideReelAsyncImageLoader private void OnSceneUnloaded(Scene _) { StopAllCoroutines(); + _loaders.Clear(); + _isLoading = false; } - public void Load(SlideReelAsyncImageLoader loader) + public void Load(SlideReelAsyncImageLoader loader, bool sequential) { // Delay at least one frame to let things subscribe to the event before it fires Delay.FireOnNextUpdate(() => { - foreach (var (index, path) in loader.PathsToLoad) + if (sequential) { - NHLogger.LogVerbose($"Loaded slide reel {index} of {loader.PathsToLoad.Count}"); + // Sequential + _loaders.Enqueue(loader); + if (!_isLoading) + { + StartCoroutine(LoadAllSequential()); + } + } + else + { + foreach (var (index, path, deleteImmediately) in loader.PathsToLoad) + { + NHLogger.LogVerbose($"Loaded slide reel {index} of {loader.PathsToLoad.Count}"); - StartCoroutine(loader.DownloadTexture(path, index)); + StartCoroutine(loader.DownloadTexture(path, index, deleteImmediately)); + } } }); } + + private IEnumerator LoadAllSequential() + { + NHLogger.Log("Loading slide reels"); + _isLoading = true; + while (_loaders.Count > 0) + { + var loader = _loaders.Dequeue(); + yield return loader.DownloadTextures(); + NHLogger.Log($"Finished a slide reel, {_loaders.Count} left"); + } + _isLoading = false; + NHLogger.Log("Done loading slide reels"); + } } } \ No newline at end of file From 18f35df554c4eabad5f5ccef93ba577bf3855312 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sun, 14 Jul 2024 18:47:50 -0400 Subject: [PATCH 2/4] Sequential loading works, is under new option, also redid default config to have tooltips and stuff --- .../Builder/Props/ProjectionBuilder.cs | 25 ++++++++--- NewHorizons/Main.cs | 40 +++++++++-------- .../Files/SlideReelAsyncImageLoader.cs | 40 +++++++++++------ NewHorizons/default-config.json | 43 ++++++++++++++++--- 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 2d25948d..597ac9a8 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -45,6 +45,8 @@ namespace NewHorizons.Builder.Props private static bool _isInit; + public static bool CacheExists(IModBehaviour mod) => Directory.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER)); + internal static void InitPrefabs() { if (_isInit) return; @@ -151,6 +153,9 @@ namespace NewHorizons.Builder.Props var (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, true); + // If the cache doesn't exist it will be created here, slide reels only use the base image loader for cache creation so delete the images after to free memory + imageLoader.deleteTexturesWhenDone = !CacheExists(mod); + var key = GetUniqueSlideReelID(mod, info.slides); if (invImageLoader != null && atlasImageLoader != null) @@ -220,7 +225,6 @@ namespace NewHorizons.Builder.Props }); } - // Else when you put them down you can't pick them back up slideReelObj.GetComponent()._physicsRemoved = false; @@ -361,6 +365,9 @@ namespace NewHorizons.Builder.Props var time = DateTime.Now; slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath); NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms"); + + // Autoprojector only uses the inverted images so the original can be destroyed now + ImageUtilities.DeleteTexture(ImageUtilities.GetKey(originalPath), tex); }); } @@ -505,7 +512,7 @@ namespace NewHorizons.Builder.Props var atlasKey = GetUniqueSlideReelID(mod, slides); - var cacheExists = Directory.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER)); + var cacheExists = CacheExists(mod); NHLogger.Log($"Does cache exist for slide reels? {cacheExists}"); @@ -513,7 +520,7 @@ namespace NewHorizons.Builder.Props { NHLogger.LogVerbose($"The atlas cache for slide reel containing [{slides.FirstOrDefault(x => !string.IsNullOrEmpty(x.imagePath))?.imagePath}] is {atlasKey}"); // Load the atlas texture used to draw onto the physical slide reel object - atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png"), false)); + atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png"))); } for (int i = 0; i < slides.Length; i++) @@ -527,12 +534,12 @@ namespace NewHorizons.Builder.Props if (useInvertedCache && cacheExists) { // Load the inverted images used when displaying slide reels to a screen - invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png"), false)); + invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png"))); } else { // Used to then make cached stuff - imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"), useInvertedCache)); + imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"))); } } else @@ -540,11 +547,11 @@ namespace NewHorizons.Builder.Props if (useInvertedCache && cacheExists) { // Load the inverted images used when displaying slide reels to a screen - invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath), false)); + invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath))); } else { - imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath), useInvertedCache)); + imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath))); } } @@ -555,6 +562,8 @@ namespace NewHorizons.Builder.Props if (cacheExists) { + NHLogger.Log("Loading slide reels from cache"); + if (useAtlasCache) { atlasImageLoader.Start(false, false); @@ -573,6 +582,8 @@ namespace NewHorizons.Builder.Props } else { + NHLogger.Log("Generating slide reel cache"); + // Will be slow and create the cache if needed // Will run sequentially to ensure we don't run out of memory imageLoader.Start(true, true); diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 4df680c9..85c26823 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -46,9 +46,10 @@ namespace NewHorizons // Settings public static bool Debug { get; private set; } public static bool VerboseLogs { get; private set; } - private static bool _useCustomTitleScreen; + public static bool SequentialPreCaching { get; private set; } + public static bool CustomTitleScreen { get; private set; } + public static string DefaultSystemOverride { get; private set; } private static bool _wasConfigured = false; - private static string _defaultSystemOverride; public static Dictionary SystemDict = new(); public static Dictionary> BodyDict = new(); @@ -59,7 +60,7 @@ namespace NewHorizons public static bool IsSystemReady { get; private set; } - public string DefaultStarSystem => SystemDict.ContainsKey(_defaultSystemOverride) ? _defaultSystemOverride : _defaultStarSystem; + public string DefaultStarSystem => SystemDict.ContainsKey(DefaultSystemOverride) ? DefaultSystemOverride : _defaultStarSystem; public string CurrentStarSystem { get @@ -130,8 +131,9 @@ namespace NewHorizons var currentScene = SceneManager.GetActiveScene().name; - Debug = config.GetSettingsValue("Debug"); - VerboseLogs = config.GetSettingsValue("Verbose Logs"); + Debug = config.GetSettingsValue(nameof(Debug)); + VerboseLogs = config.GetSettingsValue(nameof(VerboseLogs)); + SequentialPreCaching = config.GetSettingsValue(nameof(SequentialPreCaching)); if (currentScene == "SolarSystem") { @@ -143,19 +145,19 @@ namespace NewHorizons else if (Debug) NHLogger.UpdateLogLevel(NHLogger.LogType.Log); else NHLogger.UpdateLogLevel(NHLogger.LogType.Error); - var oldDefaultSystemOverride = _defaultSystemOverride; - _defaultSystemOverride = config.GetSettingsValue("Default System Override"); - if (oldDefaultSystemOverride != _defaultSystemOverride) + var oldDefaultSystemOverride = DefaultSystemOverride; + DefaultSystemOverride = config.GetSettingsValue(nameof(DefaultSystemOverride)); + if (oldDefaultSystemOverride != DefaultSystemOverride) { ResetCurrentStarSystem(); - NHLogger.Log($"Changed default star system override to {_defaultSystemOverride}"); + NHLogger.Log($"Changed default star system override to {DefaultSystemOverride}"); } - var wasUsingCustomTitleScreen = _useCustomTitleScreen; - _useCustomTitleScreen = config.GetSettingsValue("Custom title screen"); + var wasUsingCustomTitleScreen = CustomTitleScreen; + CustomTitleScreen = config.GetSettingsValue(nameof(CustomTitleScreen)); // Reload the title screen if this was updated on it // Don't reload if we haven't configured yet (called on game start) - if (wasUsingCustomTitleScreen != _useCustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured) + if (wasUsingCustomTitleScreen != CustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured) { NHLogger.LogVerbose("Reloading"); SceneManager.LoadScene("TitleScreen", LoadSceneMode.Single); @@ -428,7 +430,7 @@ namespace NewHorizons IsChangingStarSystem = false; - if (isTitleScreen && _useCustomTitleScreen) + if (isTitleScreen && CustomTitleScreen) { try { @@ -1037,16 +1039,16 @@ namespace NewHorizons private void ResetCurrentStarSystem() { - if (SystemDict.ContainsKey(_defaultSystemOverride)) + if (SystemDict.ContainsKey(DefaultSystemOverride)) { - CurrentStarSystem = _defaultSystemOverride; + CurrentStarSystem = DefaultSystemOverride; // #738 - Sometimes the override will not support spawning regularly, so always warp in if possible - if (SystemDict[_defaultSystemOverride].Config.Vessel?.spawnOnVessel == true) + if (SystemDict[DefaultSystemOverride].Config.Vessel?.spawnOnVessel == true) { IsWarpingFromVessel = true; } - else if (BodyDict.TryGetValue(_defaultSystemOverride, out var bodies) && bodies.Any(x => x.Config?.Spawn?.shipSpawn != null)) + else if (BodyDict.TryGetValue(DefaultSystemOverride, out var bodies) && bodies.Any(x => x.Config?.Spawn?.shipSpawn != null)) { IsWarpingFromShip = true; } @@ -1058,9 +1060,9 @@ namespace NewHorizons else { // Ignore first load because it doesn't even know what systems we have - if (!_firstLoad && !string.IsNullOrEmpty(_defaultSystemOverride)) + if (!_firstLoad && !string.IsNullOrEmpty(DefaultSystemOverride)) { - NHLogger.LogError($"The given default system override {_defaultSystemOverride} is invalid - no system exists with that name"); + NHLogger.LogError($"The given default system override {DefaultSystemOverride} is invalid - no system exists with that name"); } CurrentStarSystem = _defaultStarSystem; diff --git a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs index 8897c927..46b15ec9 100644 --- a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs +++ b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Security.Policy; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; @@ -15,7 +16,9 @@ namespace NewHorizons.Utility.Files; /// public class SlideReelAsyncImageLoader { - public List<(int index, string path, bool deleteImmediately)> PathsToLoad { get; private set; } = new(); + public List<(int index, string path)> PathsToLoad { get; private set; } = new(); + + private Dictionary _loadedTextures = new(); public class ImageLoadedEvent : UnityEvent { } public ImageLoadedEvent imageLoadedEvent = new(); @@ -23,6 +26,8 @@ public class SlideReelAsyncImageLoader public bool FinishedLoading { get; private set; } private int _loadedCount = 0; + public bool deleteTexturesWhenDone; + // TODO: set up an optional “StartLoading” and “StartUnloading” condition on AsyncTextureLoader, // and make use of that for at least for projector stuff (require player to be in the same sector as the slides // for them to start loading, and unload when the player leaves) @@ -53,24 +58,40 @@ public class SlideReelAsyncImageLoader { _loadedCount++; + var key = ImageUtilities.GetKey(originalPath); + _loadedTextures[key] = texture as Texture2D; + if (_loadedCount >= PathsToLoad.Count) { NHLogger.LogVerbose($"Finished loading all textures for a slide reel (one was {PathsToLoad.FirstOrDefault()}"); FinishedLoading = true; + + if (deleteTexturesWhenDone) + { + DeleteLoadedImages(); + } + } + } + + private void DeleteLoadedImages() + { + foreach (var (key, texture) in _loadedTextures) + { + ImageUtilities.DeleteTexture(key, texture); } } private IEnumerator DownloadTextures() { - foreach (var (index, path, deleteImmediately) in PathsToLoad) + foreach (var (index, path) in PathsToLoad) { NHLogger.LogVerbose($"Loaded slide reel {index} of {PathsToLoad.Count}"); - yield return DownloadTexture(path, index, deleteImmediately); + yield return DownloadTexture(path, index); } } - private IEnumerator DownloadTexture(string url, int index, bool deleteImmediately) + private IEnumerator DownloadTexture(string url, int index) { var key = ImageUtilities.GetKey(url); if (ImageUtilities.CheckCachedTexture(key, out var existingTexture)) @@ -118,11 +139,6 @@ public class SlideReelAsyncImageLoader var time = DateTime.Now; imageLoadedEvent?.Invoke(texture, index, url); - // For when its an image loaded only to create a new cacheable image - if (deleteImmediately) - { - ImageUtilities.DeleteTexture(key, texture); - } NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms"); } } @@ -153,7 +169,7 @@ public class SlideReelAsyncImageLoader // Delay at least one frame to let things subscribe to the event before it fires Delay.FireOnNextUpdate(() => { - if (sequential) + if (sequential && Main.SequentialPreCaching) { // Sequential _loaders.Enqueue(loader); @@ -164,11 +180,11 @@ public class SlideReelAsyncImageLoader } else { - foreach (var (index, path, deleteImmediately) in loader.PathsToLoad) + foreach (var (index, path) in loader.PathsToLoad) { NHLogger.LogVerbose($"Loaded slide reel {index} of {loader.PathsToLoad.Count}"); - StartCoroutine(loader.DownloadTexture(path, index, deleteImmediately)); + StartCoroutine(loader.DownloadTexture(path, index)); } } }); diff --git a/NewHorizons/default-config.json b/NewHorizons/default-config.json index 146eb9fe..e4b87a61 100644 --- a/NewHorizons/default-config.json +++ b/NewHorizons/default-config.json @@ -1,9 +1,38 @@ { - "enabled": true, - "settings": { - "Debug": false, - "Custom title screen": true, - "Default System Override": "", - "Verbose Logs": false - } + "enabled": true, + "settings": { + "CustomTitleScreen": { + "title": "Custom Title Screen", + "type": "toggle", + "value": true, + "tooltip": "Displays planets from installed mods on the title screen." + }, + "DebugSeparator": { + "type": "separator" + }, + "Debug": { + "title": "Debug", + "type": "toggle", + "value": false, + "tooltip": "Enables the debug raycast, visible quantum object colliders, and debug options menu." + }, + "VerboseLogs": { + "title": "Verbose Logs", + "type": "toggle", + "value": false, + "tooltip": "Makes logs much more detailed. Useful when debugging." + }, + "SequentialPreCaching": { + "title": "Sequential Pre-caching", + "type": "toggle", + "value": false, + "tooltip": "This is a debug option intended for mod creators. Prevents running out of memory when computing slide reel caches, but is slower and will cause in-game lag." + }, + "DefaultSystemOverride": { + "title": "Default System Override", + "type": "text", + "value": "", + "tooltip": "Forces the player to spawn in this system after death. Must match the unique name of a system, eg, xen.NewHorizonsExamples" + } + } } \ No newline at end of file From 63cb8e6f5d88eafff2cd1a08e688b48c619b7799 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sun, 14 Jul 2024 20:48:56 -0400 Subject: [PATCH 3/4] Use deleteTexturesWhenDone on autoprojectors, add comments to make it clearer what this does --- NewHorizons/Builder/Props/ProjectionBuilder.cs | 7 ++++--- NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 597ac9a8..7a55d891 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -349,6 +349,10 @@ namespace NewHorizons.Builder.Props slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, false); + + // Autoprojector only uses the inverted images so the original can be destroyed if they are loaded (when creating the cached inverted images) + imageLoader.deleteTexturesWhenDone = true; + if (invImageLoader != null) { // Loaded directly from cache @@ -365,9 +369,6 @@ namespace NewHorizons.Builder.Props var time = DateTime.Now; slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath); NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms"); - - // Autoprojector only uses the inverted images so the original can be destroyed now - ImageUtilities.DeleteTexture(ImageUtilities.GetKey(originalPath), tex); }); } diff --git a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs index 46b15ec9..51123183 100644 --- a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs +++ b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs @@ -26,7 +26,13 @@ public class SlideReelAsyncImageLoader public bool FinishedLoading { get; private set; } private int _loadedCount = 0; - public bool deleteTexturesWhenDone; + /// + /// If we are loading images where: + /// 1) The slide reel cache does not exist + /// 2) The loader would only use the images to make the cache + /// Then we want to delete them immediately after loading, in order to save memory + /// + public bool deleteTexturesWhenDone = false; // TODO: set up an optional “StartLoading” and “StartUnloading” condition on AsyncTextureLoader, // and make use of that for at least for projector stuff (require player to be in the same sector as the slides From a564afcf0c5a31e80460223dd1f8d694b858f8c1 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sun, 14 Jul 2024 21:04:31 -0400 Subject: [PATCH 4/4] Ensure inverted slide reels have the same key regardless of if they're from the cache folder or not --- NewHorizons/Utility/Files/ImageUtilities.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs index 5d0c4d2f..3f9d8497 100644 --- a/NewHorizons/Utility/Files/ImageUtilities.cs +++ b/NewHorizons/Utility/Files/ImageUtilities.cs @@ -108,6 +108,16 @@ namespace NewHorizons.Utility.Files public static Texture2D InvertSlideReel(IModBehaviour mod, Texture2D texture, string originalPath) { var key = $"{texture.name} > invert"; + var cachedPath = ""; + + // If we're going to end up caching the texture we must make sure it will end up using the same key + // Not sure why we check if the originalPath is null but it did that before so + if (!string.IsNullOrEmpty(originalPath)) + { + cachedPath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.INVERTED_SLIDE_CACHE_FOLDER, originalPath.Replace(mod.ModHelper.Manifest.ModFolderPath, "")); + key = GetKey(cachedPath); + } + if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture; var pixels = texture.GetPixels(); @@ -143,12 +153,11 @@ namespace NewHorizons.Utility.Files // Since doing this is expensive we cache the results to the disk // Preloading cached values is done in ProjectionBuilder - if (!string.IsNullOrEmpty(originalPath)) + if (!string.IsNullOrEmpty(cachedPath)) { - var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.INVERTED_SLIDE_CACHE_FOLDER, originalPath.Replace(mod.ModHelper.Manifest.ModFolderPath, "")); - NHLogger.LogVerbose($"Caching inverted image to {path}"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, newTexture.EncodeToPNG()); + NHLogger.LogVerbose($"Caching inverted image to {cachedPath}"); + Directory.CreateDirectory(Path.GetDirectoryName(cachedPath)); + File.WriteAllBytes(cachedPath, newTexture.EncodeToPNG()); } return newTexture;