mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Actual async slide loading (#897)
## Bug fixes - Slides are now loaded in the background instead of freezing your game. They were supposed to do this before, but clay wrote the code wrong and none of us caught it until now. (Fixes #812)
This commit is contained in:
commit
28038c8999
@ -1,3 +1,4 @@
|
|||||||
|
using HarmonyLib;
|
||||||
using NewHorizons.External.Modules.Props;
|
using NewHorizons.External.Modules.Props;
|
||||||
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
|
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
|
||||||
using NewHorizons.Handlers;
|
using NewHorizons.Handlers;
|
||||||
@ -8,14 +9,18 @@ using OWML.Common;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
using static NewHorizons.Main;
|
using static NewHorizons.Main;
|
||||||
|
|
||||||
namespace NewHorizons.Builder.Props
|
namespace NewHorizons.Builder.Props
|
||||||
{
|
{
|
||||||
public static class ProjectionBuilder
|
public static class ProjectionBuilder
|
||||||
{
|
{
|
||||||
|
public const string INVERTED_SLIDE_CACHE_FOLDER = "SlideReelCache/Inverted";
|
||||||
|
public const string ATLAS_SLIDE_CACHE_FOLDER = "SlideReelCache/Atlas";
|
||||||
|
|
||||||
public static GameObject SlideReelWholePrefab { get; private set; }
|
public static GameObject SlideReelWholePrefab { get; private set; }
|
||||||
public static GameObject SlideReelWholePristinePrefab { get; private set; }
|
public static GameObject SlideReelWholePristinePrefab { get; private set; }
|
||||||
public static GameObject SlideReelWholeRustedPrefab { get; private set; }
|
public static GameObject SlideReelWholeRustedPrefab { get; private set; }
|
||||||
@ -113,6 +118,8 @@ namespace NewHorizons.Builder.Props
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetUniqueSlideReelID(IModBehaviour mod, SlideInfo[] slides) => $"{mod.ModHelper.Manifest.UniqueName}{slides.Join(x => x.imagePath)}".GetHashCode().ToString();
|
||||||
|
|
||||||
private static GameObject MakeSlideReel(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
|
private static GameObject MakeSlideReel(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
|
||||||
{
|
{
|
||||||
InitPrefabs();
|
InitPrefabs();
|
||||||
@ -139,10 +146,12 @@ namespace NewHorizons.Builder.Props
|
|||||||
var slideCollection = new SlideCollection(slidesCount);
|
var slideCollection = new SlideCollection(slidesCount);
|
||||||
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
||||||
|
|
||||||
// The base game ones only have 15 slides max
|
// We can fit 16 slides max into an atlas
|
||||||
var textures = new Texture2D[slidesCount >= 15 ? 15 : slidesCount];
|
var textures = new Texture2D[slidesCount > 16 ? 16 : slidesCount];
|
||||||
|
|
||||||
var imageLoader = AddAsyncLoader(slideReelObj, mod, info.slides, ref slideCollection);
|
var imageLoader = StartAsyncLoader(mod, info.slides, ref slideCollection);
|
||||||
|
|
||||||
|
var key = GetUniqueSlideReelID(mod, info.slides);
|
||||||
|
|
||||||
// this variable just lets us track how many of the first 15 slides have been loaded.
|
// this variable just lets us track how many of the first 15 slides have been loaded.
|
||||||
// this way as soon as the last one is loaded (due to async loading, this may be
|
// this way as soon as the last one is loaded (due to async loading, this may be
|
||||||
@ -150,22 +159,25 @@ 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;
|
||||||
|
|
||||||
// Track the first 15 to put on the slide reel object
|
slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath);
|
||||||
|
NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms");
|
||||||
|
// Track the first 16 to put on the slide reel object
|
||||||
if (index < textures.Length)
|
if (index < textures.Length)
|
||||||
{
|
{
|
||||||
textures[index] = tex;
|
textures[index] = tex;
|
||||||
if (Interlocked.Increment(ref displaySlidesLoaded) >= textures.Length)
|
displaySlidesLoaded++;
|
||||||
|
if (displaySlidesLoaded == textures.Length)
|
||||||
{
|
{
|
||||||
// all textures required to build the reel's textures have been loaded
|
// all textures required to build the reel's textures have been loaded
|
||||||
var slidesBack = slideReelObj.GetComponentInChildren<TransformAnimator>().transform.Find("Slides_Back").GetComponent<MeshRenderer>();
|
var slidesBack = slideReelObj.GetComponentInChildren<TransformAnimator>().transform.Find("Slides_Back").GetComponent<MeshRenderer>();
|
||||||
var slidesFront = slideReelObj.GetComponentInChildren<TransformAnimator>().transform.Find("Slides_Front").GetComponent<MeshRenderer>();
|
var slidesFront = slideReelObj.GetComponentInChildren<TransformAnimator>().transform.Find("Slides_Front").GetComponent<MeshRenderer>();
|
||||||
|
|
||||||
// Now put together the textures into a 4x4 thing for the materials
|
// Now put together the textures into a 4x4 thing for the materials
|
||||||
var reelTexture = ImageUtilities.MakeReelTexture(textures);
|
var reelTexture = ImageUtilities.MakeReelTexture(mod, textures, key);
|
||||||
slidesBack.material.mainTexture = reelTexture;
|
slidesBack.material.mainTexture = reelTexture;
|
||||||
slidesBack.material.SetTexture(EmissionMap, reelTexture);
|
slidesBack.material.SetTexture(EmissionMap, reelTexture);
|
||||||
slidesBack.material.name = reelTexture.name;
|
slidesBack.material.name = reelTexture.name;
|
||||||
@ -174,6 +186,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");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -300,8 +314,13 @@ namespace NewHorizons.Builder.Props
|
|||||||
var slideCollection = new SlideCollection(slidesCount);
|
var slideCollection = new SlideCollection(slidesCount);
|
||||||
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 = StartAsyncLoader(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.InvertSlideReel(mod, tex, originalPath);
|
||||||
|
NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms");
|
||||||
|
});
|
||||||
|
|
||||||
slideCollectionContainer.slideCollection = slideCollection;
|
slideCollectionContainer.slideCollection = slideCollection;
|
||||||
|
|
||||||
@ -339,8 +358,13 @@ namespace NewHorizons.Builder.Props
|
|||||||
var slideCollection = new SlideCollection(slidesCount); // TODO: uh I think that info.slides[i].playTimeDuration is not being read here... note to self for when I implement support for that: 0.7 is what to default to if playTimeDuration turns out to be 0
|
var slideCollection = new SlideCollection(slidesCount); // TODO: uh I think that info.slides[i].playTimeDuration is not being read here... note to self for when I implement support for that: 0.7 is what to default to if playTimeDuration turns out to be 0
|
||||||
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 = StartAsyncLoader(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>();
|
||||||
@ -381,13 +405,13 @@ namespace NewHorizons.Builder.Props
|
|||||||
visionBeamEffect.SetActive(false);
|
visionBeamEffect.SetActive(false);
|
||||||
|
|
||||||
// Set up slides
|
// Set up slides
|
||||||
// The number of slides is unlimited, 15 is only for texturing the actual slide reel item. This is not a slide reel item
|
// The number of slides is unlimited, 16 is only for texturing the actual slide reel item. This is not a slide reel item
|
||||||
var slides = info.slides;
|
var slides = info.slides;
|
||||||
var slidesCount = slides.Length;
|
var slidesCount = slides.Length;
|
||||||
var slideCollection = new SlideCollection(slidesCount);
|
var slideCollection = new SlideCollection(slidesCount);
|
||||||
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
||||||
|
|
||||||
var imageLoader = AddAsyncLoader(standingTorch, mod, slides, ref slideCollection);
|
var imageLoader = StartAsyncLoader(mod, slides, ref slideCollection);
|
||||||
|
|
||||||
// This variable just lets us track how many of the slides have been loaded.
|
// This variable just lets us track how many of the slides have been loaded.
|
||||||
// This way as soon as the last one is loaded (due to async loading, this may be
|
// This way as soon as the last one is loaded (due to async loading, this may be
|
||||||
@ -395,15 +419,18 @@ 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)
|
displaySlidesLoaded++;
|
||||||
|
if (displaySlidesLoaded == slides.Length)
|
||||||
{
|
{
|
||||||
mindSlideProjector.enabled = true;
|
mindSlideProjector.enabled = true;
|
||||||
visionBeamEffect.SetActive(true);
|
visionBeamEffect.SetActive(true);
|
||||||
}
|
}
|
||||||
|
NHLogger.LogVerbose($"Slide reel another set time {(DateTime.Now - time).TotalMilliseconds}ms");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -427,9 +454,28 @@ namespace NewHorizons.Builder.Props
|
|||||||
return standingTorch;
|
return standingTorch;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageUtilities.AsyncImageLoader AddAsyncLoader(GameObject gameObject, IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection)
|
private static SlideReelAsyncImageLoader StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection)
|
||||||
{
|
{
|
||||||
var imageLoader = gameObject.AddComponent<ImageUtilities.AsyncImageLoader>();
|
var invertedImageLoader = new SlideReelAsyncImageLoader();
|
||||||
|
var atlasImageLoader = new SlideReelAsyncImageLoader();
|
||||||
|
var imageLoader = new SlideReelAsyncImageLoader();
|
||||||
|
|
||||||
|
var atlasKey = GetUniqueSlideReelID(mod, slides);
|
||||||
|
|
||||||
|
// attempt to load atlas guy from disk and precache so theres a cache hit when using ImageUtilities
|
||||||
|
atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png")));
|
||||||
|
atlasImageLoader.imageLoadedEvent.AddListener((Texture2D t, int i, string s) =>
|
||||||
|
{
|
||||||
|
NHLogger.Log($"SLIDE REEL ATLAS from {slides.First().imagePath}: {s}");
|
||||||
|
ImageUtilities.TrackCachedTexture(atlasKey, t);
|
||||||
|
});
|
||||||
|
invertedImageLoader.imageLoadedEvent.AddListener((Texture2D t, int i, string s) =>
|
||||||
|
{
|
||||||
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slides[i].imagePath);
|
||||||
|
var key = $"{ImageUtilities.GetKey(path)} > invert";
|
||||||
|
ImageUtilities.TrackCachedTexture(key, t);
|
||||||
|
});
|
||||||
|
|
||||||
for (int i = 0; i < slides.Length; i++)
|
for (int i = 0; i < slides.Length; i++)
|
||||||
{
|
{
|
||||||
var slide = new Slide();
|
var slide = new Slide();
|
||||||
@ -438,10 +484,12 @@ 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
|
||||||
{
|
{
|
||||||
|
// attempt to load inverted guy from disk and precache so theres a cache hit when using ImageUtilities
|
||||||
|
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath)));
|
||||||
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 +497,11 @@ namespace NewHorizons.Builder.Props
|
|||||||
|
|
||||||
slideCollection.slides[i] = slide;
|
slideCollection.slides[i] = slide;
|
||||||
}
|
}
|
||||||
|
// Loaders go sequentually - Load the inverted textures to the cache so that ImageUtilities will reuse them later
|
||||||
|
invertedImageLoader.Start(true);
|
||||||
|
// Atlas texture next so that the normal iamgeLoader knows not to regenerate them unless they were missing
|
||||||
|
atlasImageLoader.Start(false);
|
||||||
|
imageLoader.Start(true);
|
||||||
|
|
||||||
return imageLoader;
|
return imageLoader;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
using HarmonyLib;
|
using NewHorizons.Builder.Props;
|
||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
using OWML.Common;
|
using OWML.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
|
|
||||||
namespace NewHorizons.Utility.Files
|
namespace NewHorizons.Utility.Files
|
||||||
{
|
{
|
||||||
@ -17,9 +13,10 @@ 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 + 1).Replace('\\', '/');
|
||||||
|
|
||||||
public static bool IsTextureLoaded(IModBehaviour mod, string filename)
|
public static bool IsTextureLoaded(IModBehaviour mod, string filename)
|
||||||
{
|
{
|
||||||
@ -99,7 +96,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 InvertSlideReel(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;
|
||||||
@ -135,18 +132,27 @@ namespace NewHorizons.Utility.Files
|
|||||||
|
|
||||||
_textureCache.Add(key, newTexture);
|
_textureCache.Add(key, newTexture);
|
||||||
|
|
||||||
|
// Since doing this is expensive we cache the results to the disk
|
||||||
|
// Preloading cached values is done in ProjectionBuilder
|
||||||
|
if (!string.IsNullOrEmpty(originalPath))
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
return newTexture;
|
return newTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Texture2D MakeReelTexture(Texture2D[] textures)
|
public static Texture2D MakeReelTexture(IModBehaviour mod, Texture2D[] textures, string uniqueSlideReelID)
|
||||||
{
|
{
|
||||||
var key = $"SlideReelAtlas of {textures.Join(x => x.name)}";
|
if (_textureCache.TryGetValue(uniqueSlideReelID, out var existingTexture)) return (Texture2D)existingTexture;
|
||||||
if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
|
|
||||||
|
|
||||||
var size = 256;
|
var size = 256;
|
||||||
|
|
||||||
var texture = new Texture2D(size * 4, size * 4, TextureFormat.ARGB32, false);
|
var texture = new Texture2D(size * 4, size * 4, TextureFormat.ARGB32, false);
|
||||||
texture.name = key;
|
texture.name = uniqueSlideReelID;
|
||||||
|
|
||||||
var fillPixels = new Color[size * size * 4 * 4];
|
var fillPixels = new Color[size * size * 4 * 4];
|
||||||
for (int xIndex = 0; xIndex < 4; xIndex++)
|
for (int xIndex = 0; xIndex < 4; xIndex++)
|
||||||
@ -188,7 +194,14 @@ namespace NewHorizons.Utility.Files
|
|||||||
texture.SetPixels(fillPixels);
|
texture.SetPixels(fillPixels);
|
||||||
texture.Apply();
|
texture.Apply();
|
||||||
|
|
||||||
_textureCache.Add(key, texture);
|
_textureCache.Add(uniqueSlideReelID, texture);
|
||||||
|
|
||||||
|
// Since doing this is expensive we cache the results to the disk
|
||||||
|
// Preloading cached values is done in ProjectionBuilder
|
||||||
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.ATLAS_SLIDE_CACHE_FOLDER, $"{uniqueSlideReelID}.png");
|
||||||
|
NHLogger.LogVerbose($"Caching atlas image to {path}");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
File.WriteAllBytes(path, texture.EncodeToPNG());
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
@ -429,96 +442,5 @@ namespace NewHorizons.Utility.Files
|
|||||||
sprite.name = texture.name;
|
sprite.name = texture.name;
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified from https://stackoverflow.com/a/69141085/9643841
|
|
||||||
public class AsyncImageLoader : MonoBehaviour
|
|
||||||
{
|
|
||||||
public List<(int index, string path)> PathsToLoad { get; private set; } = new();
|
|
||||||
|
|
||||||
public class ImageLoadedEvent : UnityEvent<Texture2D, int> { }
|
|
||||||
public ImageLoadedEvent imageLoadedEvent = new();
|
|
||||||
|
|
||||||
private readonly object _lockObj = new();
|
|
||||||
|
|
||||||
public bool FinishedLoading { get; private set; }
|
|
||||||
private int _loadedCount = 0;
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
void Start()
|
|
||||||
{
|
|
||||||
imageLoadedEvent.AddListener(OnImageLoaded);
|
|
||||||
foreach (var (index, path) in PathsToLoad)
|
|
||||||
{
|
|
||||||
StartCoroutine(DownloadTexture(path, index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnImageLoaded(Texture texture, int index)
|
|
||||||
{
|
|
||||||
lock (_lockObj)
|
|
||||||
{
|
|
||||||
_loadedCount++;
|
|
||||||
|
|
||||||
if (_loadedCount >= PathsToLoad.Count)
|
|
||||||
{
|
|
||||||
NHLogger.LogVerbose($"Finished loading all textures for {gameObject.name} (one was {PathsToLoad.FirstOrDefault()}");
|
|
||||||
FinishedLoading = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator DownloadTexture(string url, int index)
|
|
||||||
{
|
|
||||||
var key = GetKey(url);
|
|
||||||
lock (_textureCache)
|
|
||||||
{
|
|
||||||
if (_textureCache.TryGetValue(key, out var existingTexture))
|
|
||||||
{
|
|
||||||
NHLogger.LogVerbose($"Already loaded image {index}:{url}");
|
|
||||||
imageLoadedEvent?.Invoke((Texture2D)existingTexture, index);
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(url);
|
|
||||||
|
|
||||||
yield return uwr.SendWebRequest();
|
|
||||||
|
|
||||||
var hasError = uwr.error != null && uwr.error != "";
|
|
||||||
|
|
||||||
if (hasError)
|
|
||||||
{
|
|
||||||
NHLogger.LogError($"Failed to load {index}:{url} - {uwr.error}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
|
||||||
texture.name = key;
|
|
||||||
texture.wrapMode = TextureWrapMode.Clamp;
|
|
||||||
|
|
||||||
var handler = (DownloadHandlerTexture)uwr.downloadHandler;
|
|
||||||
texture.LoadImage(handler.data);
|
|
||||||
|
|
||||||
lock (_textureCache)
|
|
||||||
{
|
|
||||||
if (_textureCache.TryGetValue(key, out var existingTexture))
|
|
||||||
{
|
|
||||||
NHLogger.LogVerbose($"Already loaded image {index}:{url}");
|
|
||||||
Destroy(texture);
|
|
||||||
texture = (Texture2D)existingTexture;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_textureCache.Add(key, texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
imageLoadedEvent?.Invoke(texture, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
167
NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs
Normal file
167
NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
|
namespace NewHorizons.Utility.Files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modified from https://stackoverflow.com/a/69141085/9643841
|
||||||
|
/// Having more than one SlideReelAsyncImageLoader running at the same time is LAGGY. While loading the image data from the disk is async, nothing else is!
|
||||||
|
/// It will load the images async and then do tens to hundreds of callbacks to imageLoadedEvent all running at around the same time and lagging out the game
|
||||||
|
/// This is why we do it sequentially using SingletonAsyncImageLoader
|
||||||
|
/// </summary>
|
||||||
|
public class SlideReelAsyncImageLoader
|
||||||
|
{
|
||||||
|
public List<(int index, string path)> PathsToLoad { get; private set; } = new();
|
||||||
|
|
||||||
|
public class ImageLoadedEvent : UnityEvent<Texture2D, int, string> { }
|
||||||
|
public ImageLoadedEvent imageLoadedEvent = new();
|
||||||
|
|
||||||
|
public bool FinishedLoading { get; private set; }
|
||||||
|
private int _loadedCount = 0;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// also remember this for ship logs!!! lol
|
||||||
|
|
||||||
|
private bool _started;
|
||||||
|
private bool _clamp;
|
||||||
|
|
||||||
|
public void Start(bool clamp)
|
||||||
|
{
|
||||||
|
if (_started) return;
|
||||||
|
|
||||||
|
_clamp = clamp;
|
||||||
|
|
||||||
|
_started = true;
|
||||||
|
|
||||||
|
if (SingletonSlideReelAsyncImageLoader.Instance == null)
|
||||||
|
{
|
||||||
|
Main.Instance.gameObject.AddComponent<SingletonSlideReelAsyncImageLoader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
NHLogger.LogVerbose("Loading new slide reel");
|
||||||
|
imageLoadedEvent.AddListener(OnImageLoaded);
|
||||||
|
SingletonSlideReelAsyncImageLoader.Instance.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnImageLoaded(Texture texture, int index, string originalPath)
|
||||||
|
{
|
||||||
|
_loadedCount++;
|
||||||
|
|
||||||
|
if (_loadedCount >= PathsToLoad.Count)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Finished loading all textures for a slide reel (one was {PathsToLoad.FirstOrDefault()}");
|
||||||
|
FinishedLoading = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator DownloadTextures()
|
||||||
|
{
|
||||||
|
foreach (var (index, path) in PathsToLoad)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Loaded slide reel {index} of {PathsToLoad.Count}");
|
||||||
|
|
||||||
|
yield return DownloadTexture(path, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator DownloadTexture(string url, int index)
|
||||||
|
{
|
||||||
|
var key = ImageUtilities.GetKey(url);
|
||||||
|
if (ImageUtilities.CheckCachedTexture(key, out var existingTexture))
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Already loaded image {index}:{url}");
|
||||||
|
imageLoadedEvent?.Invoke((Texture2D)existingTexture, index, url);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
using UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(url);
|
||||||
|
|
||||||
|
yield return uwr.SendWebRequest();
|
||||||
|
|
||||||
|
var hasError = uwr.error != null && uwr.error != "";
|
||||||
|
|
||||||
|
if (hasError)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Failed to load {index}:{url} - {uwr.error}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var texture = DownloadHandlerTexture.GetContent(uwr);
|
||||||
|
texture.name = key;
|
||||||
|
if (_clamp)
|
||||||
|
{
|
||||||
|
texture.wrapMode = TextureWrapMode.Clamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImageUtilities.CheckCachedTexture(key, out existingTexture))
|
||||||
|
{
|
||||||
|
// the image could be loaded by something else by the time we're done doing async stuff
|
||||||
|
NHLogger.LogVerbose($"Already loaded image {index}:{url}");
|
||||||
|
GameObject.Destroy(texture);
|
||||||
|
texture = (Texture2D)existingTexture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImageUtilities.TrackCachedTexture(key, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = DateTime.Now;
|
||||||
|
imageLoadedEvent?.Invoke(texture, index, url);
|
||||||
|
NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SingletonSlideReelAsyncImageLoader : MonoBehaviour
|
||||||
|
{
|
||||||
|
public static SingletonSlideReelAsyncImageLoader Instance { get; private set; }
|
||||||
|
|
||||||
|
private Queue<SlideReelAsyncImageLoader> _loaders = new();
|
||||||
|
|
||||||
|
private bool _isLoading;
|
||||||
|
|
||||||
|
public void Awake()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneUnloaded(Scene _)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
_loaders.Clear();
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(SlideReelAsyncImageLoader 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Clay, Trifid, and friends",
|
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Clay, Trifid, and friends",
|
||||||
"name": "New Horizons",
|
"name": "New Horizons",
|
||||||
"uniqueName": "xen.NewHorizons",
|
"uniqueName": "xen.NewHorizons",
|
||||||
"version": "1.21.2",
|
"version": "1.21.3",
|
||||||
"owmlVersion": "2.12.1",
|
"owmlVersion": "2.12.1",
|
||||||
"dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
|
"dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
|
||||||
"conflicts": [ "PacificEngine.OW_CommonResources" ],
|
"conflicts": [ "PacificEngine.OW_CommonResources" ],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user