Cache inverted images

This commit is contained in:
Nick 2024-06-13 01:13:25 -04:00
parent 493365155f
commit 540a501b9a
2 changed files with 106 additions and 19 deletions

View File

@ -150,10 +150,12 @@ namespace NewHorizons.Builder.Props
// to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading // to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading
int displaySlidesLoaded = 0; int displaySlidesLoaded = 0;
imageLoader.imageLoadedEvent.AddListener( imageLoader.imageLoadedEvent.AddListener(
(Texture2D tex, int index) => (Texture2D tex, int index, string originalPath) =>
{ {
slideCollection.slides[index]._image = ImageUtilities.Invert(tex); var time = DateTime.Now;
slideCollection.slides[index]._image = ImageUtilities.Invert(mod, tex, originalPath);
NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms");
// Track the first 15 to put on the slide reel object // Track the first 15 to put on the slide reel object
if (index < textures.Length) if (index < textures.Length)
{ {
@ -174,6 +176,8 @@ namespace NewHorizons.Builder.Props
slidesFront.material.name = reelTexture.name; slidesFront.material.name = reelTexture.name;
} }
} }
NHLogger.LogVerbose($"Slide reel make reel texture {(DateTime.Now - time).TotalMilliseconds}ms");
} }
); );
@ -301,7 +305,12 @@ namespace NewHorizons.Builder.Props
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
var imageLoader = AddAsyncLoader(projectorObj, mod, info.slides, ref slideCollection); var imageLoader = AddAsyncLoader(projectorObj, mod, info.slides, ref slideCollection);
imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index) => { slideCollection.slides[index]._image = ImageUtilities.Invert(tex); }); imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
{
var time = DateTime.Now;
slideCollection.slides[index]._image = ImageUtilities.Invert(mod, tex, originalPath);
NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms");
});
slideCollectionContainer.slideCollection = slideCollection; slideCollectionContainer.slideCollection = slideCollection;
@ -340,7 +349,12 @@ namespace NewHorizons.Builder.Props
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
var imageLoader = AddAsyncLoader(g, mod, info.slides, ref slideCollection); var imageLoader = AddAsyncLoader(g, mod, info.slides, ref slideCollection);
imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index) => { slideCollection.slides[index]._image = tex; }); imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
{
var time = DateTime.Now;
slideCollection.slides[index]._image = tex;
NHLogger.LogVerbose($"Slide reel set time {(DateTime.Now - time).TotalMilliseconds}ms");
});
// attach a component to store all the data for the slides that play when a vision torch scans this target // attach a component to store all the data for the slides that play when a vision torch scans this target
var target = g.AddComponent<VisionTorchTarget>(); var target = g.AddComponent<VisionTorchTarget>();
@ -395,8 +409,9 @@ namespace NewHorizons.Builder.Props
// to avoid doing a "is every element in the array `slideCollection.slides` not null" check every time a texture finishes loading // to avoid doing a "is every element in the array `slideCollection.slides` not null" check every time a texture finishes loading
int displaySlidesLoaded = 0; int displaySlidesLoaded = 0;
imageLoader.imageLoadedEvent.AddListener( imageLoader.imageLoadedEvent.AddListener(
(Texture2D tex, int index) => (Texture2D tex, int index, string originalPath) =>
{ {
var time = DateTime.Now;
slideCollection.slides[index]._image = tex; slideCollection.slides[index]._image = tex;
if (Interlocked.Increment(ref displaySlidesLoaded) == slides.Length) if (Interlocked.Increment(ref displaySlidesLoaded) == slides.Length)
@ -404,6 +419,7 @@ namespace NewHorizons.Builder.Props
mindSlideProjector.enabled = true; mindSlideProjector.enabled = true;
visionBeamEffect.SetActive(true); visionBeamEffect.SetActive(true);
} }
NHLogger.LogVerbose($"Slide reel another set time {(DateTime.Now - time).TotalMilliseconds}ms");
} }
); );
@ -429,6 +445,7 @@ namespace NewHorizons.Builder.Props
private static ImageUtilities.AsyncImageLoader AddAsyncLoader(GameObject gameObject, IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection) private static ImageUtilities.AsyncImageLoader AddAsyncLoader(GameObject gameObject, IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection)
{ {
var invertedImageLoader = gameObject.AddComponent<ImageUtilities.AsyncImageLoader>();
var imageLoader = gameObject.AddComponent<ImageUtilities.AsyncImageLoader>(); var imageLoader = gameObject.AddComponent<ImageUtilities.AsyncImageLoader>();
for (int i = 0; i < slides.Length; i++) for (int i = 0; i < slides.Length; i++)
{ {
@ -438,10 +455,16 @@ namespace NewHorizons.Builder.Props
if (string.IsNullOrEmpty(slideInfo.imagePath)) if (string.IsNullOrEmpty(slideInfo.imagePath))
{ {
imageLoader.imageLoadedEvent?.Invoke(Texture2D.blackTexture, i); imageLoader.imageLoadedEvent?.Invoke(Texture2D.blackTexture, i, null);
} }
else else
{ {
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "invertedSlideReelCache", slideInfo.imagePath)));
invertedImageLoader.imageLoadedEvent.AddListener((Texture2D t, int i, string s) =>
{
var path = "\\" + Path.Combine(Path.GetFileName(Path.GetDirectoryName(mod.ModHelper.Manifest.ModFolderPath)), slides[i].imagePath) + " > invert";
ImageUtilities.TrackCachedTexture(path, t);
});
imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath))); imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
} }
@ -449,6 +472,8 @@ namespace NewHorizons.Builder.Props
slideCollection.slides[i] = slide; slideCollection.slides[i] = slide;
} }
invertedImageLoader.Start();
imageLoader.Start();
return imageLoader; return imageLoader;
} }

View File

@ -1,6 +1,7 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.Utility.OWML; using NewHorizons.Utility.OWML;
using OWML.Common; using OWML.Common;
using OWML.ModHelper;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,14 +20,14 @@ namespace NewHorizons.Utility.Files
// key is path + applied effects // key is path + applied effects
private static readonly Dictionary<string, Texture> _textureCache = new(); private static readonly Dictionary<string, Texture> _textureCache = new();
public static bool CheckCachedTexture(string key, out Texture existingTexture) => _textureCache.TryGetValue(key, out existingTexture); public static bool CheckCachedTexture(string key, out Texture existingTexture) => _textureCache.TryGetValue(key, out existingTexture);
public static void TrackCachedTexture(string key, Texture texture) => _textureCache.Add(key, texture); public static void TrackCachedTexture(string key, Texture texture) => _textureCache[key] = texture;
private static string GetKey(string path) => path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length); public static string GetKey(string path) => path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length);
public static bool IsTextureLoaded(IModBehaviour mod, string filename) public static bool IsTextureLoaded(IModBehaviour mod, string filename)
{ {
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).Replace('\\', '/');
return _textureCache.ContainsKey(key); return _textureCache.ContainsKey(key);
} }
@ -101,7 +102,7 @@ namespace NewHorizons.Utility.Files
/// used specifically for projected slides. /// used specifically for projected slides.
/// also adds a border (to prevent weird visual bug) and makes the texture linear (otherwise the projected image is too bright). /// also adds a border (to prevent weird visual bug) and makes the texture linear (otherwise the projected image is too bright).
/// </summary> /// </summary>
public static Texture2D Invert(Texture2D texture) public static Texture2D Invert(IModBehaviour mod, Texture2D texture, string originalPath)
{ {
var key = $"{texture.name} > invert"; var key = $"{texture.name} > invert";
if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture; if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
@ -137,6 +138,14 @@ namespace NewHorizons.Utility.Files
_textureCache.Add(key, newTexture); _textureCache.Add(key, newTexture);
if (!string.IsNullOrEmpty(originalPath))
{
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "invertedSlideReelCache", originalPath.Replace(mod.ModHelper.Manifest.ModFolderPath, ""));
NHLogger.LogVerbose($"Caching inverted image to {path}");
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, newTexture.EncodeToPNG());
}
return newTexture; return newTexture;
} }
@ -432,12 +441,49 @@ namespace NewHorizons.Utility.Files
return sprite; return sprite;
} }
public class SingletonAsyncImageLoader : MonoBehaviour
{
public static SingletonAsyncImageLoader Instance { get; private set; }
private Queue<AsyncImageLoader> loaders = new();
private bool _isLoading;
public void Awake()
{
Instance = this;
}
public void Load(AsyncImageLoader loader)
{
loaders.Enqueue(loader);
if (!_isLoading)
{
StartCoroutine(Run());
}
}
private IEnumerator Run()
{
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");
}
}
// Modified from https://stackoverflow.com/a/69141085/9643841 // Modified from https://stackoverflow.com/a/69141085/9643841
public class AsyncImageLoader : MonoBehaviour public class AsyncImageLoader : MonoBehaviour
{ {
public List<(int index, string path)> PathsToLoad { get; private set; } = new(); public List<(int index, string path)> PathsToLoad { get; private set; } = new();
public class ImageLoadedEvent : UnityEvent<Texture2D, int> { } public class ImageLoadedEvent : UnityEvent<Texture2D, int, string> { }
public ImageLoadedEvent imageLoadedEvent = new(); public ImageLoadedEvent imageLoadedEvent = new();
private readonly object _lockObj = new(); private readonly object _lockObj = new();
@ -449,13 +495,25 @@ namespace NewHorizons.Utility.Files
// 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)
void Start() private bool _started;
public void Start()
{ {
imageLoadedEvent.AddListener(OnImageLoaded); if (_started) return;
StartCoroutine(DownloadTextures());
_started = true;
if (SingletonAsyncImageLoader.Instance == null)
{
Main.Instance.gameObject.AddComponent<SingletonAsyncImageLoader>();
} }
private void OnImageLoaded(Texture texture, int index) NHLogger.LogVerbose("Loading new slide reel");
imageLoadedEvent.AddListener(OnImageLoaded);
SingletonAsyncImageLoader.Instance.Load(this);
}
private void OnImageLoaded(Texture texture, int index, string originalPath)
{ {
lock (_lockObj) lock (_lockObj)
{ {
@ -469,11 +527,13 @@ namespace NewHorizons.Utility.Files
} }
} }
IEnumerator DownloadTextures() internal IEnumerator DownloadTextures()
{ {
foreach (var (index, path) in PathsToLoad) foreach (var (index, path) in PathsToLoad)
{ {
yield return StartCoroutine(DownloadTexture(path, index)); NHLogger.Log($"Loaded slide reel {index} of {PathsToLoad.Count}");
yield return DownloadTexture(path, index);
} }
} }
@ -483,7 +543,7 @@ namespace NewHorizons.Utility.Files
if (_textureCache.TryGetValue(key, out var existingTexture)) if (_textureCache.TryGetValue(key, out var existingTexture))
{ {
NHLogger.LogVerbose($"Already loaded image {index}:{url}"); NHLogger.LogVerbose($"Already loaded image {index}:{url}");
imageLoadedEvent?.Invoke((Texture2D)existingTexture, index); imageLoadedEvent?.Invoke((Texture2D)existingTexture, index, url);
yield break; yield break;
} }
@ -517,7 +577,9 @@ namespace NewHorizons.Utility.Files
} }
yield return null; yield return null;
imageLoadedEvent?.Invoke(texture, index); var time = DateTime.Now;
imageLoadedEvent?.Invoke(texture, index, url);
NHLogger.LogVerbose($"Slide reel event? {(DateTime.Now - time).TotalMilliseconds}ms");
} }
} }
} }