Make slide reel better (#903)

Will explode if the cache is wrong.

Now the black screen loading time is like 5 seconds longer but the slide
reels are then done loading instead of popping in over the course of 2
minutes. (EOTP)
This commit is contained in:
xen-42 2024-06-17 18:09:31 -04:00 committed by GitHub
commit c84dbefcab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 149 additions and 120 deletions

View File

@ -149,24 +149,53 @@ namespace NewHorizons.Builder.Props
// We can fit 16 slides max into an atlas // We can fit 16 slides max into an atlas
var textures = new Texture2D[slidesCount > 16 ? 16 : slidesCount]; var textures = new Texture2D[slidesCount > 16 ? 16 : slidesCount];
var imageLoader = StartAsyncLoader(mod, info.slides, ref slideCollection); var (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true);
var key = GetUniqueSlideReelID(mod, info.slides); var key = GetUniqueSlideReelID(mod, info.slides);
// this variable just lets us track how many of the first 15 slides have been loaded. if (invImageLoader != null && atlasImageLoader != null)
// this way as soon as the last one is loaded (due to async loading, this may be {
// slide 7, or slide 3, or whatever), we can build the slide reel texture. This allows us // Loading directly from cache
// to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading invImageLoader.imageLoadedEvent.AddListener(
int displaySlidesLoaded = 0; (Texture2D tex, int index, string originalPath) =>
imageLoader.imageLoadedEvent.AddListener( {
(Texture2D tex, int index, string originalPath) => slideCollection.slides[index]._image = tex;
}
);
atlasImageLoader.imageLoadedEvent.AddListener(
(Texture2D tex, int _, string originalPath) =>
{
// all textures required to build the reel's textures have been loaded
var slidesBack = slideReelObj.GetComponentInChildren<TransformAnimator>().transform.Find("Slides_Back").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
var reelTexture = tex;
slidesBack.material.mainTexture = reelTexture;
slidesBack.material.SetTexture(EmissionMap, reelTexture);
slidesBack.material.name = reelTexture.name;
slidesFront.material.mainTexture = reelTexture;
slidesFront.material.SetTexture(EmissionMap, reelTexture);
slidesFront.material.name = reelTexture.name;
}
);
}
else
{
// 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
// slide 7, or slide 3, or whatever), we can build the slide reel texture. This allows us
// to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading
int displaySlidesLoaded = 0;
imageLoader.imageLoadedEvent.AddListener(
(Texture2D tex, int index, string originalPath) =>
{ {
var time = DateTime.Now; var time = DateTime.Now;
slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath); slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath);
NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms"); NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms");
// Track the first 16 to put on the slide reel object // 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;
displaySlidesLoaded++; displaySlidesLoaded++;
@ -188,8 +217,9 @@ namespace NewHorizons.Builder.Props
} }
NHLogger.LogVerbose($"Slide reel make reel texture {(DateTime.Now - time).TotalMilliseconds}ms"); NHLogger.LogVerbose($"Slide reel make reel texture {(DateTime.Now - time).TotalMilliseconds}ms");
} });
); }
// 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;
@ -210,58 +240,58 @@ namespace NewHorizons.Builder.Props
switch (model) switch (model)
{ {
case ProjectionInfo.SlideReelType.SixSlides: case ProjectionInfo.SlideReelType.SixSlides:
{
switch (condition)
{ {
case ProjectionInfo.SlideReelCondition.Antique: switch (condition)
default: {
return SlideReel6Prefab; case ProjectionInfo.SlideReelCondition.Antique:
case ProjectionInfo.SlideReelCondition.Pristine: default:
return SlideReel6PristinePrefab; return SlideReel6Prefab;
case ProjectionInfo.SlideReelCondition.Rusted: case ProjectionInfo.SlideReelCondition.Pristine:
return SlideReel6RustedPrefab; return SlideReel6PristinePrefab;
case ProjectionInfo.SlideReelCondition.Rusted:
return SlideReel6RustedPrefab;
}
} }
}
case ProjectionInfo.SlideReelType.SevenSlides: case ProjectionInfo.SlideReelType.SevenSlides:
default: default:
{
switch (condition)
{ {
case ProjectionInfo.SlideReelCondition.Antique: switch (condition)
default: {
return SlideReel7Prefab; case ProjectionInfo.SlideReelCondition.Antique:
case ProjectionInfo.SlideReelCondition.Pristine: default:
return SlideReel7PristinePrefab; return SlideReel7Prefab;
case ProjectionInfo.SlideReelCondition.Rusted: case ProjectionInfo.SlideReelCondition.Pristine:
return SlideReel7RustedPrefab; return SlideReel7PristinePrefab;
case ProjectionInfo.SlideReelCondition.Rusted:
return SlideReel7RustedPrefab;
}
} }
}
case ProjectionInfo.SlideReelType.EightSlides: case ProjectionInfo.SlideReelType.EightSlides:
{
switch (condition)
{ {
case ProjectionInfo.SlideReelCondition.Antique: switch (condition)
default: {
return SlideReel8Prefab; case ProjectionInfo.SlideReelCondition.Antique:
case ProjectionInfo.SlideReelCondition.Pristine: default:
return SlideReel8PristinePrefab; return SlideReel8Prefab;
case ProjectionInfo.SlideReelCondition.Rusted: case ProjectionInfo.SlideReelCondition.Pristine:
return SlideReel8RustedPrefab; return SlideReel8PristinePrefab;
case ProjectionInfo.SlideReelCondition.Rusted:
return SlideReel8RustedPrefab;
}
} }
}
case ProjectionInfo.SlideReelType.Whole: case ProjectionInfo.SlideReelType.Whole:
{
switch (condition)
{ {
case ProjectionInfo.SlideReelCondition.Antique: switch (condition)
default: {
return SlideReelWholePrefab; case ProjectionInfo.SlideReelCondition.Antique:
case ProjectionInfo.SlideReelCondition.Pristine: default:
return SlideReelWholePristinePrefab; return SlideReelWholePrefab;
case ProjectionInfo.SlideReelCondition.Rusted: case ProjectionInfo.SlideReelCondition.Pristine:
return SlideReelWholeRustedPrefab; return SlideReelWholePristinePrefab;
case ProjectionInfo.SlideReelCondition.Rusted:
return SlideReelWholeRustedPrefab;
}
} }
}
} }
} }
@ -314,13 +344,25 @@ 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 = StartAsyncLoader(mod, info.slides, ref slideCollection); var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true);
imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) => if (invImageLoader != null)
{ {
var time = DateTime.Now; // Loaded directly from cache
slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath); invImageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms"); {
}); slideCollection.slides[index]._image = tex;
});
}
else
{
// Create the inverted cache from existing images
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;
@ -358,9 +400,9 @@ 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 = StartAsyncLoader(mod, info.slides, ref slideCollection); var (_, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false);
imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) => imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
{ {
var time = DateTime.Now; var time = DateTime.Now;
slideCollection.slides[index]._image = tex; slideCollection.slides[index]._image = tex;
NHLogger.LogVerbose($"Slide reel set time {(DateTime.Now - time).TotalMilliseconds}ms"); NHLogger.LogVerbose($"Slide reel set time {(DateTime.Now - time).TotalMilliseconds}ms");
@ -398,9 +440,9 @@ namespace NewHorizons.Builder.Props
// Set some required properties on the torch // Set some required properties on the torch
var mindSlideProjector = standingTorch.GetComponent<MindSlideProjector>(); var mindSlideProjector = standingTorch.GetComponent<MindSlideProjector>();
mindSlideProjector._mindProjectorImageEffect = SearchUtilities.Find("Player_Body/PlayerCamera").GetComponent<MindProjectorImageEffect>(); mindSlideProjector._mindProjectorImageEffect = SearchUtilities.Find("Player_Body/PlayerCamera").GetComponent<MindProjectorImageEffect>();
// Setup for visually supporting async texture loading // Setup for visually supporting async texture loading
mindSlideProjector.enabled = false; mindSlideProjector.enabled = false;
var visionBeamEffect = standingTorch.FindChild("VisionBeam"); var visionBeamEffect = standingTorch.FindChild("VisionBeam");
visionBeamEffect.SetActive(false); visionBeamEffect.SetActive(false);
@ -411,7 +453,7 @@ 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 = StartAsyncLoader(mod, slides, ref slideCollection); var (_, _, imageLoader) = StartAsyncLoader(mod, slides, ref slideCollection, false);
// 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
@ -419,7 +461,7 @@ 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, string originalPath) => (Texture2D tex, int index, string originalPath) =>
{ {
var time = DateTime.Now; var time = DateTime.Now;
slideCollection.slides[index]._image = tex; slideCollection.slides[index]._image = tex;
@ -454,7 +496,8 @@ namespace NewHorizons.Builder.Props
return standingTorch; return standingTorch;
} }
private static SlideReelAsyncImageLoader StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection) private static (SlideReelAsyncImageLoader inverted, SlideReelAsyncImageLoader atlas, SlideReelAsyncImageLoader slides)
StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useCache)
{ {
var invertedImageLoader = new SlideReelAsyncImageLoader(); var invertedImageLoader = new SlideReelAsyncImageLoader();
var atlasImageLoader = new SlideReelAsyncImageLoader(); var atlasImageLoader = new SlideReelAsyncImageLoader();
@ -462,19 +505,15 @@ namespace NewHorizons.Builder.Props
var atlasKey = GetUniqueSlideReelID(mod, slides); var atlasKey = GetUniqueSlideReelID(mod, slides);
// attempt to load atlas guy from disk and precache so theres a cache hit when using ImageUtilities var cacheExists = Directory.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER));
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($"Does cache exist for slide reels? {cacheExists}");
if (useCache && cacheExists)
{ {
NHLogger.Log($"SLIDE REEL ATLAS from {slides.First().imagePath}: {s}"); // Load the atlas texture used to draw onto the physical slide reel object
ImageUtilities.TrackCachedTexture(atlasKey, t); atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png")));
}); }
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++)
{ {
@ -488,8 +527,11 @@ namespace NewHorizons.Builder.Props
} }
else else
{ {
// attempt to load inverted guy from disk and precache so theres a cache hit when using ImageUtilities if (useCache && cacheExists)
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath))); {
// 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)));
}
imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath))); imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
} }
@ -497,13 +539,25 @@ 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; if (useCache && cacheExists)
{
// This code will execute in order to create the cache
// 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 (invertedImageLoader, atlasImageLoader, imageLoader);
}
else
{
// Will be slow and create the cache if needed
imageLoader.Start(true);
return (null, null, imageLoader);
}
} }
private static void AddModules(SlideInfo slideInfo, ref Slide slide, IModBehaviour mod) private static void AddModules(SlideInfo slideInfo, ref Slide slide, IModBehaviour mod)
@ -569,7 +623,7 @@ namespace NewHorizons.Builder.Props
Slide.WriteModules(modules, ref slide._modulesList, ref slide._modulesData, ref slide.lengths); Slide.WriteModules(modules, ref slide._modulesList, ref slide._modulesData, ref slide.lengths);
} }
private static void LinkShipLogFacts(ProjectionInfo info, SlideCollectionContainer slideCollectionContainer) private static void LinkShipLogFacts(ProjectionInfo info, SlideCollectionContainer slideCollectionContainer)
{ {
// Idk why but it wants reveals to be comma delimited not a list // Idk why but it wants reveals to be comma delimited not a list

View File

@ -63,22 +63,12 @@ public class SlideReelAsyncImageLoader
} }
} }
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) private IEnumerator DownloadTexture(string url, int index)
{ {
var key = ImageUtilities.GetKey(url); var key = ImageUtilities.GetKey(url);
if (ImageUtilities.CheckCachedTexture(key, out var existingTexture)) if (ImageUtilities.CheckCachedTexture(key, out var existingTexture))
{ {
NHLogger.LogVerbose($"Already loaded image {index}:{url}"); NHLogger.LogVerbose($"Already loaded image {index}:{url} with key {key}");
imageLoadedEvent?.Invoke((Texture2D)existingTexture, index, url); imageLoadedEvent?.Invoke((Texture2D)existingTexture, index, url);
yield break; yield break;
} }
@ -124,10 +114,6 @@ 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;
@ -137,31 +123,20 @@ 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)
{ {
_loaders.Enqueue(loader); // Delay at least one frame to let things subscribe to the event before it fires
if (!_isLoading) Delay.FireOnNextUpdate(() =>
{ {
StartCoroutine(Run()); foreach (var (index, path) in loader.PathsToLoad)
} {
} NHLogger.LogVerbose($"Loaded slide reel {index} of {loader.PathsToLoad.Count}");
private IEnumerator Run() StartCoroutine(loader.DownloadTexture(path, index));
{ }
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");
} }
} }
} }