This commit is contained in:
xen-42 2024-08-05 15:17:53 -04:00
commit b9c7c2b280
5 changed files with 172 additions and 43 deletions

View File

@ -45,6 +45,8 @@ namespace NewHorizons.Builder.Props
private static bool _isInit; 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() internal static void InitPrefabs()
{ {
if (_isInit) return; if (_isInit) return;
@ -151,6 +153,9 @@ namespace NewHorizons.Builder.Props
var (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, true); 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); var key = GetUniqueSlideReelID(mod, info.slides);
if (invImageLoader != null && atlasImageLoader != null) 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 // Else when you put them down you can't pick them back up
slideReelObj.GetComponent<OWCollider>()._physicsRemoved = false; slideReelObj.GetComponent<OWCollider>()._physicsRemoved = false;
@ -345,6 +349,10 @@ namespace NewHorizons.Builder.Props
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, false); 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) if (invImageLoader != null)
{ {
// Loaded directly from cache // Loaded directly from cache
@ -505,7 +513,7 @@ namespace NewHorizons.Builder.Props
var atlasKey = GetUniqueSlideReelID(mod, slides); 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}"); NHLogger.Log($"Does cache exist for slide reels? {cacheExists}");
@ -531,6 +539,7 @@ namespace NewHorizons.Builder.Props
} }
else else
{ {
// Used to then make cached stuff
imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"))); imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png")));
} }
} }
@ -554,26 +563,31 @@ namespace NewHorizons.Builder.Props
if (cacheExists) if (cacheExists)
{ {
NHLogger.Log("Loading slide reels from cache");
if (useAtlasCache) if (useAtlasCache)
{ {
atlasImageLoader.Start(false); atlasImageLoader.Start(false, false);
} }
// When using the inverted cache we never need the regular images // When using the inverted cache we never need the regular images
if (useInvertedCache) if (useInvertedCache)
{ {
invertedImageLoader.Start(true); invertedImageLoader.Start(true, false);
} }
else else
{ {
imageLoader.Start(true); imageLoader.Start(true, false);
} }
return (invertedImageLoader, atlasImageLoader, imageLoader); return (invertedImageLoader, atlasImageLoader, imageLoader);
} }
else else
{ {
NHLogger.Log("Generating slide reel cache");
// Will be slow and create the cache if needed // 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); return (null, null, imageLoader);
} }

View File

@ -46,9 +46,10 @@ namespace NewHorizons
// Settings // Settings
public static bool Debug { get; private set; } public static bool Debug { get; private set; }
public static bool VerboseLogs { 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 bool _wasConfigured = false;
private static string _defaultSystemOverride;
public static Dictionary<string, NewHorizonsSystem> SystemDict = new(); public static Dictionary<string, NewHorizonsSystem> SystemDict = new();
public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new(); public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new();
@ -59,7 +60,7 @@ namespace NewHorizons
public static bool IsSystemReady { get; private set; } 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 public string CurrentStarSystem
{ {
get get
@ -130,8 +131,9 @@ namespace NewHorizons
var currentScene = SceneManager.GetActiveScene().name; var currentScene = SceneManager.GetActiveScene().name;
Debug = config.GetSettingsValue<bool>("Debug"); Debug = config.GetSettingsValue<bool>(nameof(Debug));
VerboseLogs = config.GetSettingsValue<bool>("Verbose Logs"); VerboseLogs = config.GetSettingsValue<bool>(nameof(VerboseLogs));
SequentialPreCaching = config.GetSettingsValue<bool>(nameof(SequentialPreCaching));
if (currentScene == "SolarSystem") if (currentScene == "SolarSystem")
{ {
@ -143,19 +145,19 @@ namespace NewHorizons
else if (Debug) NHLogger.UpdateLogLevel(NHLogger.LogType.Log); else if (Debug) NHLogger.UpdateLogLevel(NHLogger.LogType.Log);
else NHLogger.UpdateLogLevel(NHLogger.LogType.Error); else NHLogger.UpdateLogLevel(NHLogger.LogType.Error);
var oldDefaultSystemOverride = _defaultSystemOverride; var oldDefaultSystemOverride = DefaultSystemOverride;
_defaultSystemOverride = config.GetSettingsValue<string>("Default System Override"); DefaultSystemOverride = config.GetSettingsValue<string>(nameof(DefaultSystemOverride));
if (oldDefaultSystemOverride != _defaultSystemOverride) if (oldDefaultSystemOverride != DefaultSystemOverride)
{ {
ResetCurrentStarSystem(); ResetCurrentStarSystem();
NHLogger.Log($"Changed default star system override to {_defaultSystemOverride}"); NHLogger.Log($"Changed default star system override to {DefaultSystemOverride}");
} }
var wasUsingCustomTitleScreen = _useCustomTitleScreen; var wasUsingCustomTitleScreen = CustomTitleScreen;
_useCustomTitleScreen = config.GetSettingsValue<bool>("Custom title screen"); CustomTitleScreen = config.GetSettingsValue<bool>(nameof(CustomTitleScreen));
// Reload the title screen if this was updated on it // Reload the title screen if this was updated on it
// Don't reload if we haven't configured yet (called on game start) // 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"); NHLogger.LogVerbose("Reloading");
SceneManager.LoadScene("TitleScreen", LoadSceneMode.Single); SceneManager.LoadScene("TitleScreen", LoadSceneMode.Single);
@ -428,7 +430,7 @@ namespace NewHorizons
IsChangingStarSystem = false; IsChangingStarSystem = false;
if (isTitleScreen && _useCustomTitleScreen) if (isTitleScreen && CustomTitleScreen)
{ {
try try
{ {
@ -1037,16 +1039,16 @@ namespace NewHorizons
private void ResetCurrentStarSystem() 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 // #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; 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; IsWarpingFromShip = true;
} }
@ -1058,9 +1060,9 @@ namespace NewHorizons
else else
{ {
// Ignore first load because it doesn't even know what systems we have // 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; CurrentStarSystem = _defaultStarSystem;

View File

@ -72,6 +72,11 @@ namespace NewHorizons.Utility.Files
{ {
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename); var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename);
var key = GetKey(path); var key = GetKey(path);
DeleteTexture(key, texture);
}
public static void DeleteTexture(string key, Texture2D texture)
{
if (_textureCache.ContainsKey(key)) if (_textureCache.ContainsKey(key))
{ {
if (_textureCache[key] == texture) if (_textureCache[key] == texture)
@ -103,6 +108,16 @@ namespace NewHorizons.Utility.Files
public static Texture2D InvertSlideReel(IModBehaviour mod, Texture2D texture, string originalPath) public static Texture2D InvertSlideReel(IModBehaviour mod, Texture2D texture, string originalPath)
{ {
var key = $"{texture.name} > invert"; 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; if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
var pixels = texture.GetPixels(); var pixels = texture.GetPixels();
@ -138,12 +153,11 @@ namespace NewHorizons.Utility.Files
// Since doing this is expensive we cache the results to the disk // Since doing this is expensive we cache the results to the disk
// Preloading cached values is done in ProjectionBuilder // 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 {cachedPath}");
NHLogger.LogVerbose($"Caching inverted image to {path}"); Directory.CreateDirectory(Path.GetDirectoryName(cachedPath));
Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(cachedPath, newTexture.EncodeToPNG());
File.WriteAllBytes(path, newTexture.EncodeToPNG());
} }
return newTexture; return newTexture;

View File

@ -3,6 +3,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Policy;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.Networking; using UnityEngine.Networking;
@ -17,12 +18,22 @@ public class SlideReelAsyncImageLoader
{ {
public List<(int index, string path)> PathsToLoad { get; private set; } = new(); public List<(int index, string path)> PathsToLoad { get; private set; } = new();
private Dictionary<string, Texture2D> _loadedTextures = new();
public class ImageLoadedEvent : UnityEvent<Texture2D, int, string> { } public class ImageLoadedEvent : UnityEvent<Texture2D, int, string> { }
public ImageLoadedEvent imageLoadedEvent = new(); public ImageLoadedEvent imageLoadedEvent = new();
public bool FinishedLoading { get; private set; } public bool FinishedLoading { get; private set; }
private int _loadedCount = 0; private int _loadedCount = 0;
/// <summary>
/// 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
/// </summary>
public bool deleteTexturesWhenDone = false;
// TODO: set up an optional “StartLoading” and “StartUnloading” condition on AsyncTextureLoader, // 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 // 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) // for them to start loading, and unload when the player leaves)
@ -31,7 +42,7 @@ public class SlideReelAsyncImageLoader
private bool _started; private bool _started;
private bool _clamp; private bool _clamp;
public void Start(bool clamp) public void Start(bool clamp, bool sequential)
{ {
if (_started) return; if (_started) return;
@ -46,17 +57,43 @@ public class SlideReelAsyncImageLoader
NHLogger.LogVerbose("Loading new slide reel"); NHLogger.LogVerbose("Loading new slide reel");
imageLoadedEvent.AddListener(OnImageLoaded); imageLoadedEvent.AddListener(OnImageLoaded);
SingletonSlideReelAsyncImageLoader.Instance.Load(this); SingletonSlideReelAsyncImageLoader.Instance.Load(this, sequential);
} }
private void OnImageLoaded(Texture texture, int index, string originalPath) private void OnImageLoaded(Texture texture, int index, string originalPath)
{ {
_loadedCount++; _loadedCount++;
var key = ImageUtilities.GetKey(originalPath);
_loadedTextures[key] = texture as Texture2D;
if (_loadedCount >= PathsToLoad.Count) if (_loadedCount >= PathsToLoad.Count)
{ {
NHLogger.LogVerbose($"Finished loading all textures for a slide reel (one was {PathsToLoad.FirstOrDefault()}"); NHLogger.LogVerbose($"Finished loading all textures for a slide reel (one was {PathsToLoad.FirstOrDefault()}");
FinishedLoading = true; 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) in PathsToLoad)
{
NHLogger.LogVerbose($"Loaded slide reel {index} of {PathsToLoad.Count}");
yield return DownloadTexture(path, index);
} }
} }
@ -107,6 +144,7 @@ public class SlideReelAsyncImageLoader
var time = DateTime.Now; var time = DateTime.Now;
imageLoadedEvent?.Invoke(texture, index, url); imageLoadedEvent?.Invoke(texture, index, url);
NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms"); NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms");
} }
} }
@ -115,6 +153,10 @@ public class SlideReelAsyncImageLoader
{ {
public static SingletonSlideReelAsyncImageLoader Instance { get; private set; } public static SingletonSlideReelAsyncImageLoader Instance { get; private set; }
private Queue<SlideReelAsyncImageLoader> _loaders = new();
private bool _isLoading;
public void Awake() public void Awake()
{ {
Instance = this; Instance = this;
@ -124,12 +166,25 @@ public class SlideReelAsyncImageLoader
private void OnSceneUnloaded(Scene _) private void OnSceneUnloaded(Scene _)
{ {
StopAllCoroutines(); 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 at least one frame to let things subscribe to the event before it fires
Delay.FireOnNextUpdate(() => Delay.FireOnNextUpdate(() =>
{
if (sequential && Main.SequentialPreCaching)
{
// Sequential
_loaders.Enqueue(loader);
if (!_isLoading)
{
StartCoroutine(LoadAllSequential());
}
}
else
{ {
foreach (var (index, path) in loader.PathsToLoad) foreach (var (index, path) in loader.PathsToLoad)
{ {
@ -137,7 +192,22 @@ public class SlideReelAsyncImageLoader
StartCoroutine(loader.DownloadTexture(path, index)); StartCoroutine(loader.DownloadTexture(path, index));
} }
}
}); });
} }
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");
}
} }
} }

View File

@ -1,9 +1,38 @@
{ {
"enabled": true, "enabled": true,
"settings": { "settings": {
"Debug": false, "CustomTitleScreen": {
"Custom title screen": true, "title": "Custom Title Screen",
"Default System Override": "", "type": "toggle",
"Verbose Logs": false "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"
}
} }
} }