mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Slide reel streaming (#1040)
## Minor features - Can set `displaySlides` on a slide reel now to define which slide indices should be displayed on the physical reel model. Fixes #888. ## Improvements - Slide reels are now streamed (Fixes #898). Other projectors (auto, torch) are not streamed yet. - Empty slide reel slots are now transparent on the slide reel model. Requires existing slide reel caches to be cleared. ## Bug fixes - Fixed a 3 frame hitch when changing tools So the strategy is: If the cache does not exist, do nothing different. It will take like 5 minutes and all your memory but that doesn't matter that's on the dev to make sure that they pre-gen the caches (the sequential pre-caching option should stop you running out of memory when making the cache probably). Users won't experience any of that Then we just do not ever load the inverted cached images when loading slides from the cache. Only do it right as the player is about to see a slide, by patching any base game method that tries to get a streamed slide. We currently do not change how auto-projectors and vision torches work. TODO: - [x] Track who is requesting to load what image so that an unsocketed slide reel doesnt unload all slides - [x] Investigate why load times are longer - [x] Make loading the images async (on an SSD doing it sync is unnoticeable but might be on older hardware) - [x] I need somebody to test this on an HDD and see that the slide reels are actually loading async without hitching - [x] When slotting slide reels in on EOTP you get a ~3 frame drop. Does not affect NH Examples (smaller images). Need to figure out why (since this is meant to be async it shouldnt matter the image size) In EOTP I save 6 seconds of load time and 3.5gb of memory (6.5gb vs 10gb)
This commit is contained in:
commit
d12ef2ac97
@ -150,28 +150,22 @@ namespace NewHorizons.Builder.Props
|
|||||||
|
|
||||||
// Now we replace the slides
|
// Now we replace the slides
|
||||||
int slidesCount = info.slides.Length;
|
int slidesCount = info.slides.Length;
|
||||||
var slideCollection = new SlideCollection(slidesCount);
|
SlideCollection slideCollection = new NHSlideCollection(slidesCount, mod, info.slides.Select(x => x.imagePath).ToArray());
|
||||||
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
|
||||||
|
|
||||||
// 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 (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, true);
|
// Slide reels dynamically load the inverted cached images when needed. We only need to load raw images to generate the cache or atlases
|
||||||
|
var (_, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, true, false);
|
||||||
|
|
||||||
// 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
|
// 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);
|
imageLoader.deleteTexturesWhenDone = true;
|
||||||
|
|
||||||
var key = GetUniqueSlideReelID(mod, info.slides);
|
var key = GetUniqueSlideReelID(mod, info.slides);
|
||||||
|
|
||||||
if (invImageLoader != null && atlasImageLoader != null)
|
if (atlasImageLoader != null)
|
||||||
{
|
{
|
||||||
// Loading directly from cache
|
|
||||||
invImageLoader.imageLoadedEvent.AddListener(
|
|
||||||
(Texture2D tex, int index, string originalPath) =>
|
|
||||||
{
|
|
||||||
slideCollection.slides[index]._image = tex;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
atlasImageLoader.imageLoadedEvent.AddListener(
|
atlasImageLoader.imageLoadedEvent.AddListener(
|
||||||
(Texture2D tex, int _, string originalPath) =>
|
(Texture2D tex, int _, string originalPath) =>
|
||||||
{
|
{
|
||||||
@ -202,6 +196,7 @@ namespace NewHorizons.Builder.Props
|
|||||||
{
|
{
|
||||||
var time = DateTime.Now;
|
var time = DateTime.Now;
|
||||||
|
|
||||||
|
// inverted slides will be loaded for the whole loop but its fine since this is only when generating cache
|
||||||
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
|
||||||
@ -215,8 +210,14 @@ namespace NewHorizons.Builder.Props
|
|||||||
var slidesBack = slideReelObj.GetComponentInChildren<TransformAnimator>(true).transform.Find("Slides_Back").GetComponent<MeshRenderer>();
|
var slidesBack = slideReelObj.GetComponentInChildren<TransformAnimator>(true).transform.Find("Slides_Back").GetComponent<MeshRenderer>();
|
||||||
var slidesFront = slideReelObj.GetComponentInChildren<TransformAnimator>(true).transform.Find("Slides_Front").GetComponent<MeshRenderer>();
|
var slidesFront = slideReelObj.GetComponentInChildren<TransformAnimator>(true).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 #888
|
||||||
var reelTexture = ImageUtilities.MakeReelTexture(mod, textures, key);
|
var displayTextures = textures;
|
||||||
|
if (info.displaySlides != null && info.displaySlides.Length > 0)
|
||||||
|
{
|
||||||
|
displayTextures = info.displaySlides.Select(x => textures[x]).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var reelTexture = ImageUtilities.MakeReelTexture(mod, displayTextures, 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;
|
||||||
@ -348,15 +349,16 @@ namespace NewHorizons.Builder.Props
|
|||||||
|
|
||||||
var toDestroy = autoProjector.GetComponent<SlideCollectionContainer>();
|
var toDestroy = autoProjector.GetComponent<SlideCollectionContainer>();
|
||||||
var slideCollectionContainer = autoProjector.gameObject.AddComponent<NHSlideCollectionContainer>();
|
var slideCollectionContainer = autoProjector.gameObject.AddComponent<NHSlideCollectionContainer>();
|
||||||
|
slideCollectionContainer.doAsyncLoading = false;
|
||||||
autoProjector._slideCollectionItem = slideCollectionContainer;
|
autoProjector._slideCollectionItem = slideCollectionContainer;
|
||||||
Component.DestroyImmediate(toDestroy);
|
Component.DestroyImmediate(toDestroy);
|
||||||
|
|
||||||
// Now we replace the slides
|
// Now we replace the slides
|
||||||
int slidesCount = info.slides.Length;
|
int slidesCount = info.slides.Length;
|
||||||
var slideCollection = new SlideCollection(slidesCount);
|
SlideCollection slideCollection = new NHSlideCollection(slidesCount, mod, info.slides.Select(x => x.imagePath).ToArray());
|
||||||
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, false);
|
||||||
|
|
||||||
// Autoprojector only uses the inverted images so the original can be destroyed if they are loaded (when creating the cached inverted images)
|
// 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;
|
imageLoader.deleteTexturesWhenDone = true;
|
||||||
@ -381,6 +383,7 @@ namespace NewHorizons.Builder.Props
|
|||||||
}
|
}
|
||||||
|
|
||||||
slideCollectionContainer.slideCollection = slideCollection;
|
slideCollectionContainer.slideCollection = slideCollection;
|
||||||
|
slideCollectionContainer._playWithShipLogFacts = Array.Empty<string>(); // else it NREs in container initialize
|
||||||
|
|
||||||
StreamingHandler.SetUpStreaming(projectorObj, sector);
|
StreamingHandler.SetUpStreaming(projectorObj, sector);
|
||||||
|
|
||||||
@ -402,9 +405,9 @@ namespace NewHorizons.Builder.Props
|
|||||||
if (_visionTorchDetectorPrefab == null) return null;
|
if (_visionTorchDetectorPrefab == null) return null;
|
||||||
|
|
||||||
// spawn a trigger for the vision torch
|
// spawn a trigger for the vision torch
|
||||||
var g = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
|
var visionTorchTargetGO = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
|
||||||
|
|
||||||
if (g == null)
|
if (visionTorchTargetGO == null)
|
||||||
{
|
{
|
||||||
NHLogger.LogWarning($"Tried to make a vision torch target but couldn't. Do you have the DLC installed?");
|
NHLogger.LogWarning($"Tried to make a vision torch target but couldn't. Do you have the DLC installed?");
|
||||||
return null;
|
return null;
|
||||||
@ -416,7 +419,7 @@ 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, false, false);
|
var (_, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, false, true);
|
||||||
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;
|
||||||
@ -425,17 +428,18 @@ namespace NewHorizons.Builder.Props
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 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 = visionTorchTargetGO.AddComponent<VisionTorchTarget>();
|
||||||
var slideCollectionContainer = g.AddComponent<NHSlideCollectionContainer>();
|
var slideCollectionContainer = visionTorchTargetGO.AddComponent<NHSlideCollectionContainer>();
|
||||||
|
slideCollectionContainer.doAsyncLoading = false;
|
||||||
slideCollectionContainer.slideCollection = slideCollection;
|
slideCollectionContainer.slideCollection = slideCollection;
|
||||||
target.slideCollection = g.AddComponent<MindSlideCollection>();
|
target.slideCollection = visionTorchTargetGO.AddComponent<MindSlideCollection>();
|
||||||
target.slideCollection._slideCollectionContainer = slideCollectionContainer;
|
target.slideCollection._slideCollectionContainer = slideCollectionContainer;
|
||||||
|
|
||||||
LinkShipLogFacts(info, slideCollectionContainer);
|
LinkShipLogFacts(info, slideCollectionContainer);
|
||||||
|
|
||||||
g.SetActive(true);
|
visionTorchTargetGO.SetActive(true);
|
||||||
|
|
||||||
return g;
|
return visionTorchTargetGO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameObject MakeStandingVisionTorch(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
|
public static GameObject MakeStandingVisionTorch(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
|
||||||
@ -469,7 +473,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, false, false);
|
var (_, _, imageLoader) = StartAsyncLoader(mod, slides, ref slideCollection, false, false, true);
|
||||||
|
|
||||||
// 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
|
||||||
@ -494,6 +498,7 @@ namespace NewHorizons.Builder.Props
|
|||||||
|
|
||||||
// Set up the containers for the slides
|
// Set up the containers for the slides
|
||||||
var slideCollectionContainer = standingTorch.AddComponent<NHSlideCollectionContainer>();
|
var slideCollectionContainer = standingTorch.AddComponent<NHSlideCollectionContainer>();
|
||||||
|
slideCollectionContainer.doAsyncLoading = false;
|
||||||
slideCollectionContainer.slideCollection = slideCollection;
|
slideCollectionContainer.slideCollection = slideCollection;
|
||||||
|
|
||||||
var mindSlideCollection = standingTorch.AddComponent<MindSlideCollection>();
|
var mindSlideCollection = standingTorch.AddComponent<MindSlideCollection>();
|
||||||
@ -512,8 +517,18 @@ namespace NewHorizons.Builder.Props
|
|||||||
return standingTorch;
|
return standingTorch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// start loading all the slide stuff we need async.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod">the mod to load slides from</param>
|
||||||
|
/// <param name="slides">slides to load</param>
|
||||||
|
/// <param name="slideCollection">where to assign the slide objects</param>
|
||||||
|
/// <param name="useInvertedCache">should we load cached inverted images?</param>
|
||||||
|
/// <param name="useAtlasCache">should we load cached atlas images?</param>
|
||||||
|
/// <param name="loadRawImages">should we load the original images? happens anyway if cache doesnt exist since atlas or inverted will need it</param>
|
||||||
|
/// <returns>the 3 loaders (inverted, atlas, original). inverted and atlas will be null if cache doesnt exist, so check those to find out if cache exists</returns>
|
||||||
private static (SlideReelAsyncImageLoader inverted, SlideReelAsyncImageLoader atlas, SlideReelAsyncImageLoader slides)
|
private static (SlideReelAsyncImageLoader inverted, SlideReelAsyncImageLoader atlas, SlideReelAsyncImageLoader slides)
|
||||||
StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useInvertedCache, bool useAtlasCache)
|
StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useInvertedCache, bool useAtlasCache, bool loadRawImages)
|
||||||
{
|
{
|
||||||
var invertedImageLoader = new SlideReelAsyncImageLoader();
|
var invertedImageLoader = new SlideReelAsyncImageLoader();
|
||||||
var atlasImageLoader = new SlideReelAsyncImageLoader();
|
var atlasImageLoader = new SlideReelAsyncImageLoader();
|
||||||
@ -545,7 +560,7 @@ namespace NewHorizons.Builder.Props
|
|||||||
// Load the inverted images used when displaying slide reels to a screen
|
// Load the inverted images used when displaying slide reels to a screen
|
||||||
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png")));
|
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png")));
|
||||||
}
|
}
|
||||||
else
|
else if (!cacheExists || loadRawImages)
|
||||||
{
|
{
|
||||||
// Used to then make cached stuff
|
// Used to then make cached stuff
|
||||||
imageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png")));
|
imageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png")));
|
||||||
@ -553,12 +568,12 @@ namespace NewHorizons.Builder.Props
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (useInvertedCache && cacheExists)
|
if (cacheExists && useInvertedCache)
|
||||||
{
|
{
|
||||||
// Load the inverted images used when displaying slide reels to a screen
|
// Load the inverted images used when displaying slide reels to a screen
|
||||||
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, InvertedSlideReelCacheFolder, slideInfo.imagePath)));
|
invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, InvertedSlideReelCacheFolder, slideInfo.imagePath)));
|
||||||
}
|
}
|
||||||
else
|
if (!cacheExists || loadRawImages)
|
||||||
{
|
{
|
||||||
imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
|
imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
|
||||||
}
|
}
|
||||||
@ -577,12 +592,11 @@ namespace NewHorizons.Builder.Props
|
|||||||
{
|
{
|
||||||
atlasImageLoader.Start(false, false);
|
atlasImageLoader.Start(false, false);
|
||||||
}
|
}
|
||||||
// When using the inverted cache we never need the regular images
|
|
||||||
if (useInvertedCache)
|
if (useInvertedCache)
|
||||||
{
|
{
|
||||||
invertedImageLoader.Start(true, false);
|
invertedImageLoader.Start(true, false);
|
||||||
}
|
}
|
||||||
else
|
if (loadRawImages)
|
||||||
{
|
{
|
||||||
imageLoader.Start(true, false);
|
imageLoader.Start(true, false);
|
||||||
}
|
}
|
||||||
|
|||||||
215
NewHorizons/Components/EOTE/NHSlideCollection.cs
Normal file
215
NewHorizons/Components/EOTE/NHSlideCollection.cs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Builder.Props;
|
||||||
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.Files;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using OWML.Common;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
|
namespace NewHorizons.Components.EOTE;
|
||||||
|
|
||||||
|
[HarmonyPatch]
|
||||||
|
public class NHSlideCollection : SlideCollection
|
||||||
|
{
|
||||||
|
public string[] slidePaths;
|
||||||
|
public IModBehaviour mod;
|
||||||
|
private HashSet<string> _pathsBeingLoaded = new();
|
||||||
|
/// <summary>
|
||||||
|
/// map of slide path to collections that have this path loaded. used to only unload slide when nothing else is using it
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, HashSet<NHSlideCollection>> _slidesRequiringPath = new();
|
||||||
|
|
||||||
|
private static ShipLogSlideProjector _shipLogSlideProjector;
|
||||||
|
|
||||||
|
static NHSlideCollection()
|
||||||
|
{
|
||||||
|
SceneManager.sceneUnloaded += (_) =>
|
||||||
|
{
|
||||||
|
foreach (var (slide, collections) in _slidesRequiringPath)
|
||||||
|
{
|
||||||
|
// If it has null, that means some other permanent thing loaded this texture and it will get cleared elsewhere
|
||||||
|
// Otherwise it was loaded by an NHSlideCollection and should be deleted
|
||||||
|
if (collections.Any() && !collections.Contains(null))
|
||||||
|
{
|
||||||
|
ImageUtilities.DeleteTexture(slide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_slidesRequiringPath.Clear();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public NHSlideCollection(int startArrSize, IModBehaviour mod, string[] slidePaths) : base(startArrSize)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
this.slidePaths = slidePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.RequestStreamSlides))]
|
||||||
|
public static bool SlideCollection_RequestStreamSlides(SlideCollection __instance, int[] slideIndices)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollection collection)
|
||||||
|
{
|
||||||
|
foreach (var id in slideIndices)
|
||||||
|
{
|
||||||
|
collection.LoadSlide(id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.RequestRelease))]
|
||||||
|
public static bool SlideCollection_RequestRelease(SlideCollection __instance, int[] slideIndices)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollection collection)
|
||||||
|
{
|
||||||
|
foreach (var id in slideIndices)
|
||||||
|
{
|
||||||
|
collection.UnloadSlide(id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.IsStreamedTextureIndexLoaded))]
|
||||||
|
public static bool SlideCollection_IsStreamedTextureIndexLoaded(SlideCollection __instance, int streamIdx, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollection collection)
|
||||||
|
{
|
||||||
|
__result = collection.IsSlideLoaded(streamIdx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.GetStreamingTexture))]
|
||||||
|
public static bool SlideCollection_GetStreamingTexture(SlideCollection __instance, int id, ref Texture __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollection collection)
|
||||||
|
{
|
||||||
|
__result = collection.LoadSlide(id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture LoadSlide(int index)
|
||||||
|
{
|
||||||
|
Texture LoadSlideInt(int index)
|
||||||
|
{
|
||||||
|
var wrappedIndex = (index + slides.Length) % slides.Length;
|
||||||
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]);
|
||||||
|
|
||||||
|
// We are the first slide collection container to try and load this image
|
||||||
|
var key = ImageUtilities.GetKey(mod, path);
|
||||||
|
if (!_slidesRequiringPath.ContainsKey(key))
|
||||||
|
{
|
||||||
|
// Something else has loaded this image i.e., AutoProjector or Vision torch. We want to ensure we do not delete it
|
||||||
|
if (ImageUtilities.IsTextureLoaded(mod, path))
|
||||||
|
{
|
||||||
|
// null is dummy value to ensure its never empty (so its not deleted)
|
||||||
|
_slidesRequiringPath[key] = new() { null };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_slidesRequiringPath[key] = new();
|
||||||
|
}
|
||||||
|
_slidesRequiringPath[key].Add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImageUtilities.IsTextureLoaded(mod, path))
|
||||||
|
{
|
||||||
|
// already loaded
|
||||||
|
var texture = ImageUtilities.GetTexture(mod, path);
|
||||||
|
slides[wrappedIndex]._image = texture;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
else if (!_pathsBeingLoaded.Contains(path))
|
||||||
|
{
|
||||||
|
// not loaded yet, we need to load it
|
||||||
|
var loader = new SlideReelAsyncImageLoader();
|
||||||
|
loader.PathsToLoad.Add((wrappedIndex, path));
|
||||||
|
loader.Start(true, false);
|
||||||
|
loader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
|
||||||
|
{
|
||||||
|
// weird: sometimes we set image, sometimes we return from GetStreamingTexture. oh well
|
||||||
|
slides[wrappedIndex]._image = tex;
|
||||||
|
_pathsBeingLoaded.Remove(path);
|
||||||
|
if (_shipLogSlideProjector == null)
|
||||||
|
{
|
||||||
|
// Object.FindObjectOfType doesnt work with inactive
|
||||||
|
_shipLogSlideProjector = Resources.FindObjectsOfTypeAll<ShipLogSlideProjector>().FirstOrDefault();
|
||||||
|
}
|
||||||
|
if (_shipLogSlideProjector != null)
|
||||||
|
{
|
||||||
|
// gotta tell ship log we updated the image
|
||||||
|
_shipLogSlideProjector._slideDirty = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose("No ship log slide reel projector exists");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_pathsBeingLoaded.Add(path);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// It is being loaded so we just wait
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var texture = LoadSlideInt(index);
|
||||||
|
LoadSlideInt(index - 1);
|
||||||
|
LoadSlideInt(index + 1);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSlideLoaded(int index)
|
||||||
|
{
|
||||||
|
var wrappedIndex = (index + slides.Length) % slides.Length;
|
||||||
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]);
|
||||||
|
return ImageUtilities.IsTextureLoaded(mod, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnloadSlide(int index)
|
||||||
|
{
|
||||||
|
var wrappedIndex = (index + slides.Length) % slides.Length;
|
||||||
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]);
|
||||||
|
|
||||||
|
// Only unload textures that we were the ones to load in
|
||||||
|
if (ImageUtilities.IsTextureLoaded(mod, path))
|
||||||
|
{
|
||||||
|
var key = ImageUtilities.GetKey(mod, path);
|
||||||
|
_slidesRequiringPath[key].Remove(this);
|
||||||
|
if (!_slidesRequiringPath[key].Any())
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Slide reel deleting {key} since nobody is using it anymore");
|
||||||
|
ImageUtilities.DeleteTexture(mod, path, ImageUtilities.GetTexture(mod, path));
|
||||||
|
slides[wrappedIndex]._image = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NewHorizons.Components.EOTE;
|
namespace NewHorizons.Components.EOTE;
|
||||||
|
|
||||||
@ -7,12 +10,14 @@ public class NHSlideCollectionContainer : SlideCollectionContainer
|
|||||||
{
|
{
|
||||||
public string[] conditionsToSet;
|
public string[] conditionsToSet;
|
||||||
public string[] persistentConditionsToSet;
|
public string[] persistentConditionsToSet;
|
||||||
|
// at some point we'll do streaming on all slides. until then just have an off switch
|
||||||
|
public bool doAsyncLoading = true;
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.Initialize))]
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.Initialize))]
|
||||||
public static bool SlideCollectionContainer_Initialize(SlideCollectionContainer __instance)
|
public static bool SlideCollectionContainer_Initialize(SlideCollectionContainer __instance)
|
||||||
{
|
{
|
||||||
if (__instance is NHSlideCollectionContainer)
|
if (__instance is NHSlideCollectionContainer container)
|
||||||
{
|
{
|
||||||
if (__instance._initialized)
|
if (__instance._initialized)
|
||||||
return false;
|
return false;
|
||||||
@ -28,6 +33,7 @@ public class NHSlideCollectionContainer : SlideCollectionContainer
|
|||||||
{
|
{
|
||||||
var fact = Locator.GetShipLogManager().GetFact(factID);
|
var fact = Locator.GetShipLogManager().GetFact(factID);
|
||||||
fact?.RegisterSlideCollection(__instance._slideCollection);
|
fact?.RegisterSlideCollection(__instance._slideCollection);
|
||||||
|
// in original it logs. we dont want that here ig
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -59,4 +65,98 @@ public class NHSlideCollectionContainer : SlideCollectionContainer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.NextSlideAvailable))]
|
||||||
|
public static bool SlideCollectionContainer_NextSlideAvailable(SlideCollectionContainer __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
__result = ((NHSlideCollection)container.slideCollection).IsSlideLoaded(container.slideIndex + 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.PrevSlideAvailable))]
|
||||||
|
public static bool SlideCollectionContainer_PrevSlideAvailable(SlideCollectionContainer __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
__result = ((NHSlideCollection)container.slideCollection).IsSlideLoaded(container.slideIndex - 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.UnloadStreamingTextures))]
|
||||||
|
public static bool SlideCollectionContainer_UnloadStreamingTextures(SlideCollectionContainer __instance)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ((NHSlideCollection)container.slideCollection).slidePaths.Length; i++)
|
||||||
|
{
|
||||||
|
((NHSlideCollection)container.slideCollection).UnloadSlide(i);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.GetStreamingTexture))]
|
||||||
|
public static bool SlideCollectionContainer_GetStreamingTexture(SlideCollectionContainer __instance, int id, ref Texture __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
__result = ((NHSlideCollection)container.slideCollection).LoadSlide(id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.RequestManualStreamSlides))]
|
||||||
|
public static bool SlideCollectionContainer_RequestManualStreamSlides(SlideCollectionContainer __instance)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
((NHSlideCollection)container.slideCollection).LoadSlide(__instance._currentSlideIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.streamingTexturesAvailable), MethodType.Getter)]
|
||||||
|
public static bool SlideCollectionContainer_streamingTexturesAvailable(SlideCollectionContainer __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading)
|
||||||
|
{
|
||||||
|
__result = ((NHSlideCollection)container.slideCollection).slidePaths != null && ((NHSlideCollection)container.slideCollection).slidePaths.Any();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,12 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
|
|||||||
/// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.
|
/// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique;
|
[DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique;
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set which slides appear on the slide reel model. Leave empty to default to the first few slides.
|
||||||
|
/// Takes a list of indices, i.e., to show the first 5 slides in reverse you would put [4, 3, 2, 1, 0].
|
||||||
|
/// Index starts at 0.
|
||||||
|
/// </summary>
|
||||||
|
public int[] displaySlides;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Components.EOTE;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.ShipLogPatches;
|
||||||
|
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class ShipLogSlideReelPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(ShipLogSlideProjector), nameof(ShipLogSlideProjector.CheckStreamingTexturesAvailable))]
|
||||||
|
public static bool ShipLogSlideProjector_CheckStreamingTexturesAvailable(ShipLogSlideProjector __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__instance._collectionIndex >= 0 && __instance._collectionIndex < __instance._slideCollections.Count &&
|
||||||
|
__instance._slideCollections[__instance._collectionIndex] is NHSlideCollection)
|
||||||
|
{
|
||||||
|
__result = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(ShipLogSlideProjector), nameof(ShipLogSlideProjector.UnloadCurrentStreamingTextures))]
|
||||||
|
public static bool ShipLogSlideProjector_UnloadCurrentStreamingTextures(ShipLogSlideProjector __instance)
|
||||||
|
{
|
||||||
|
if (__instance._collectionIndex >= 0 && __instance._collectionIndex < __instance._slideCollections.Count &&
|
||||||
|
__instance._slideCollections[__instance._collectionIndex] is NHSlideCollection collection)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < collection.slides.Length; i++)
|
||||||
|
{
|
||||||
|
collection.UnloadSlide(i);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ namespace NewHorizons.Patches.ToolPatches
|
|||||||
[HarmonyPatch(typeof(ToolModeSwapper))]
|
[HarmonyPatch(typeof(ToolModeSwapper))]
|
||||||
public static class ToolModeSwapperPatches
|
public static class ToolModeSwapperPatches
|
||||||
{
|
{
|
||||||
|
private static ShipCockpitController _shipCockpitController;
|
||||||
|
|
||||||
// Patches ToolModeSwapper.EquipToolMode(ToolMode mode) to deny swaps if you're holding a vision torch.
|
// Patches ToolModeSwapper.EquipToolMode(ToolMode mode) to deny swaps if you're holding a vision torch.
|
||||||
// This is critical for preventing swapping to the scout launcher (causes memory slides to fail) but it
|
// This is critical for preventing swapping to the scout launcher (causes memory slides to fail) but it
|
||||||
// just doesn't look right when you switch to other stuff (eg the signalscope), so I'm disabling swapping tools entirely
|
// just doesn't look right when you switch to other stuff (eg the signalscope), so I'm disabling swapping tools entirely
|
||||||
@ -21,7 +23,9 @@ namespace NewHorizons.Patches.ToolPatches
|
|||||||
mode == ToolMode.Probe ||
|
mode == ToolMode.Probe ||
|
||||||
mode == ToolMode.SignalScope ||
|
mode == ToolMode.SignalScope ||
|
||||||
mode == ToolMode.Translator;
|
mode == ToolMode.Translator;
|
||||||
var isInShip = UnityEngine.Object.FindObjectOfType<ShipCockpitController>()?._playerAtFlightConsole ?? false;
|
if (_shipCockpitController == null)
|
||||||
|
_shipCockpitController = UnityEngine.Object.FindObjectOfType<ShipCockpitController>();
|
||||||
|
var isInShip = _shipCockpitController != null ? _shipCockpitController._playerAtFlightConsole : false;
|
||||||
|
|
||||||
if (!isInShip && isHoldingVisionTorch && swappingToRestrictedTool) return false;
|
if (!isInShip && isHoldingVisionTorch && swappingToRestrictedTool) return false;
|
||||||
|
|
||||||
|
|||||||
@ -2863,6 +2863,14 @@
|
|||||||
"description": "Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.",
|
"description": "Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.",
|
||||||
"default": "antique",
|
"default": "antique",
|
||||||
"$ref": "#/definitions/SlideReelCondition"
|
"$ref": "#/definitions/SlideReelCondition"
|
||||||
|
},
|
||||||
|
"displaySlides": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Set which slides appear on the slide reel model. Leave empty to default to the first few slides.\nTakes a list of indices, i.e., to show the first 5 slides in reverse you would put [4, 3, 2, 1, 0].\nIndex starts at 0.",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,9 @@ namespace NewHorizons.Utility.Files
|
|||||||
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); // dont reinsert cuz that causes memory leak!
|
public static void TrackCachedTexture(string key, Texture texture) => _textureCache.Add(key, texture); // dont reinsert cuz that causes memory leak!
|
||||||
|
|
||||||
|
public static string GetKey(IModBehaviour mod, string filename)
|
||||||
|
=> GetKey(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename));
|
||||||
|
|
||||||
public static string GetKey(string path) =>
|
public static string GetKey(string path) =>
|
||||||
path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length + 1).Replace('\\', '/');
|
path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length + 1).Replace('\\', '/');
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ namespace NewHorizons.Utility.Files
|
|||||||
var key = GetKey(path);
|
var key = GetKey(path);
|
||||||
if (_textureCache.TryGetValue(key, out var existingTexture))
|
if (_textureCache.TryGetValue(key, out var existingTexture))
|
||||||
{
|
{
|
||||||
NHLogger.LogVerbose($"Already loaded image at path: {path}");
|
//NHLogger.LogVerbose($"Already loaded image at path: {path}");
|
||||||
return (Texture2D)existingTexture;
|
return (Texture2D)existingTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +71,19 @@ namespace NewHorizons.Utility.Files
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not sure why the other method takes in the texture as well
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public static void DeleteTexture(string key)
|
||||||
|
{
|
||||||
|
if (_textureCache.ContainsKey(key))
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(_textureCache[key]);
|
||||||
|
_textureCache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void DeleteTexture(IModBehaviour mod, string filename, Texture2D texture)
|
public static void DeleteTexture(IModBehaviour mod, string filename, Texture2D texture)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename);
|
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename);
|
||||||
@ -191,9 +207,9 @@ namespace NewHorizons.Utility.Files
|
|||||||
{
|
{
|
||||||
for (int j = 0; j < size; j++)
|
for (int j = 0; j < size; j++)
|
||||||
{
|
{
|
||||||
var colour = Color.black;
|
var colour = Color.clear;
|
||||||
|
|
||||||
if (srcTexture)
|
if (srcTexture != null)
|
||||||
{
|
{
|
||||||
var srcX = i * srcTexture.width / (float)size;
|
var srcX = i * srcTexture.width / (float)size;
|
||||||
var srcY = j * srcTexture.height / (float)size;
|
var srcY = j * srcTexture.height / (float)size;
|
||||||
|
|||||||
@ -42,6 +42,11 @@ public class SlideReelAsyncImageLoader
|
|||||||
private bool _started;
|
private bool _started;
|
||||||
private bool _clamp;
|
private bool _clamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// start loading the images a frame later
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clamp">sets wrapMode</param>
|
||||||
|
/// <param name="sequential">load all slides one at a time vs at the same time</param>
|
||||||
public void Start(bool clamp, bool sequential)
|
public void Start(bool clamp, bool sequential)
|
||||||
{
|
{
|
||||||
if (_started) return;
|
if (_started) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user