aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisxander <xander@isxander.dev>2023-12-08 19:59:09 +0000
committerisxander <xander@isxander.dev>2023-12-08 19:59:09 +0000
commit450dbad49b72b37cdeef9b1358a59da1d66076c9 (patch)
treef2d99e7740b96d9660c405a2f92c2f7342751971
parentf54989903c4697fe14bdc6f1998f4a88757cdd0c (diff)
downloadYetAnotherConfigLib-450dbad49b72b37cdeef9b1358a59da1d66076c9.tar.gz
YetAnotherConfigLib-450dbad49b72b37cdeef9b1358a59da1d66076c9.tar.bz2
YetAnotherConfigLib-450dbad49b72b37cdeef9b1358a59da1d66076c9.zip
extreme image preloading hackery
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererFactory.java10
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java43
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java99
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java14
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java4
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java4
-rw-r--r--common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java8
-rw-r--r--fabric/src/main/java/dev/isxander/yacl3/platform/fabric/YACLFabricEntrypoint.java13
-rw-r--r--fabric/src/main/java/dev/isxander/yacl3/platform/fabric/image/YACLImageReloadListenerFabric.java12
-rw-r--r--fabric/src/main/resources/fabric.mod.json5
-rw-r--r--neoforge/src/main/java/dev/isxander/yacl3/platform/neoforge/YACLForgeEntrypoint.java12
11 files changed, 200 insertions, 24 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererFactory.java b/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererFactory.java
index 480d8b1..d9d2e2d 100644
--- a/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererFactory.java
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererFactory.java
@@ -1,21 +1,21 @@
package dev.isxander.yacl3.gui.image;
-public interface ImageRendererFactory<T extends ImageRenderer> {
+public interface ImageRendererFactory {
/**
* Prepares the image. This can be run off-thread,
* and should NOT contain any GL calls whatsoever.
*/
- ImageSupplier<T> prepareImage() throws Exception;
+ ImageSupplier prepareImage() throws Exception;
default boolean requiresOffThreadPreparation() {
return true;
}
- interface ImageSupplier<T extends ImageRenderer> {
- T completeImage() throws Exception;
+ interface ImageSupplier {
+ ImageRenderer completeImage() throws Exception;
}
- interface OnThread<T extends ImageRenderer> extends ImageRendererFactory<T> {
+ interface OnThread extends ImageRendererFactory {
@Override
default boolean requiresOffThreadPreparation() {
return false;
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java b/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
index 680a6da..0c9b8a3 100644
--- a/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
@@ -1,22 +1,52 @@
package dev.isxander.yacl3.gui.image;
import com.mojang.blaze3d.systems.RenderSystem;
+import dev.isxander.yacl3.gui.image.impl.AnimatedDynamicTextureImage;
import dev.isxander.yacl3.impl.utils.YACLConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.function.Supplier;
public class ImageRendererManager {
private static final ExecutorService SINGLE_THREAD_EXECUTOR = Executors.newSingleThreadExecutor(task -> new Thread(task, "YACL Image Prep"));
private static final Map<ResourceLocation, CompletableFuture<ImageRenderer>> IMAGE_CACHE = new ConcurrentHashMap<>();
+ static final Map<ResourceLocation, ImageRenderer> PRELOADED_IMAGE_CACHE = new ConcurrentHashMap<>();
+
+ static final List<PreloadedImageFactory> PRELOADED_IMAGE_FACTORIES = List.of(
+ new PreloadedImageFactory(
+ location -> location.getPath().endsWith(".webp"),
+ AnimatedDynamicTextureImage::createWEBPFromTexture
+ ),
+ new PreloadedImageFactory(
+ location -> location.getPath().endsWith(".gif"),
+ AnimatedDynamicTextureImage::createGIFFromTexture
+ )
+ );
+
+ public static <T extends ImageRenderer> Optional<T> getImage(ResourceLocation id) {
+ if (PRELOADED_IMAGE_CACHE.containsKey(id)) {
+ return Optional.of((T) PRELOADED_IMAGE_CACHE.get(id));
+ }
+
+ if (IMAGE_CACHE.containsKey(id)) {
+ return Optional.ofNullable((T) IMAGE_CACHE.get(id).getNow(null));
+ }
+
+ return Optional.empty();
+ }
@SuppressWarnings("unchecked")
- public static <T extends ImageRenderer> CompletableFuture<T> registerImage(ResourceLocation id, ImageRendererFactory<T> factory) {
+ public static <T extends ImageRenderer> CompletableFuture<T> registerImage(ResourceLocation id, ImageRendererFactory factory) {
+ System.out.println(PRELOADED_IMAGE_CACHE.get(id));
+
if (IMAGE_CACHE.containsKey(id)) {
return (CompletableFuture<T>) IMAGE_CACHE.get(id);
}
@@ -25,7 +55,7 @@ public class ImageRendererManager {
IMAGE_CACHE.put(id, future);
SINGLE_THREAD_EXECUTOR.submit(() -> {
- Supplier<Optional<ImageRendererFactory.ImageSupplier<T>>> supplier =
+ Supplier<Optional<ImageRendererFactory.ImageSupplier>> supplier =
factory.requiresOffThreadPreparation()
? new CompletedSupplier<>(safelyPrepareFactory(id, factory))
: () -> safelyPrepareFactory(id, factory);
@@ -36,10 +66,10 @@ public class ImageRendererManager {
return (CompletableFuture<T>) future;
}
- private static <T extends ImageRenderer> void completeImageFactory(ResourceLocation id, Supplier<Optional<ImageRendererFactory.ImageSupplier<T>>> supplier, CompletableFuture<ImageRenderer> future) {
+ private static <T extends ImageRenderer> void completeImageFactory(ResourceLocation id, Supplier<Optional<ImageRendererFactory.ImageSupplier>> supplier, CompletableFuture<ImageRenderer> future) {
RenderSystem.assertOnRenderThread();
- ImageRendererFactory.ImageSupplier<T> completableImage = supplier.get().orElse(null);
+ ImageRendererFactory.ImageSupplier completableImage = supplier.get().orElse(null);
if (completableImage == null) {
return;
}
@@ -71,7 +101,7 @@ public class ImageRendererManager {
});
}
- private static <T extends ImageRenderer> Optional<ImageRendererFactory.ImageSupplier<T>> safelyPrepareFactory(ResourceLocation id, ImageRendererFactory<T> factory) {
+ static Optional<ImageRendererFactory.ImageSupplier> safelyPrepareFactory(ResourceLocation id, ImageRendererFactory factory) {
try {
return Optional.of(factory.prepareImage());
} catch (Exception e) {
@@ -81,6 +111,9 @@ public class ImageRendererManager {
}
}
+ public record PreloadedImageFactory(Predicate<ResourceLocation> predicate, Function<ResourceLocation, ImageRendererFactory> factory) {
+ }
+
private record CompletedSupplier<T>(T get) implements Supplier<T> {
}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java b/common/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java
new file mode 100644
index 0000000..cc259b0
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/YACLImageReloadListener.java
@@ -0,0 +1,99 @@
+package dev.isxander.yacl3.gui.image;
+
+import dev.isxander.yacl3.impl.utils.YACLConstants;
+import net.minecraft.CrashReport;
+import net.minecraft.CrashReportCategory;
+import net.minecraft.ReportedException;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.packs.resources.PreparableReloadListener;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.server.packs.resources.ResourceManager;
+import net.minecraft.util.profiling.ProfilerFiller;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+public class YACLImageReloadListener implements PreparableReloadListener {
+ @Override
+ public CompletableFuture<Void> reload(
+ PreparationBarrier preparationBarrier,
+ ResourceManager resourceManager,
+ ProfilerFiller preparationsProfiler,
+ ProfilerFiller reloadProfiler,
+ Executor backgroundExecutor,
+ Executor gameExecutor
+ ) {
+ Map<ResourceLocation, Resource> imageResources = resourceManager.listResources(
+ "",
+ location -> ImageRendererManager.PRELOADED_IMAGE_FACTORIES
+ .stream()
+ .anyMatch(factory -> factory.predicate().test(location))
+ );
+
+ // extreme mojang hackery.
+ // for some reason this wait method needs to be called for the reload
+ // instance to be marked as complete
+ if (imageResources.isEmpty()) {
+ preparationBarrier.wait(null);
+ }
+
+ List<CompletableFuture<?>> futures = new ArrayList<>(imageResources.size());
+
+ for (Map.Entry<ResourceLocation, Resource> entry : imageResources.entrySet()) {
+ ResourceLocation location = entry.getKey();
+ Resource resource = entry.getValue();
+
+ ImageRendererFactory imageFactory = ImageRendererManager.PRELOADED_IMAGE_FACTORIES
+ .stream()
+ .filter(factory -> factory.predicate().test(location))
+ .map(factory -> factory.factory().apply(location))
+ .findAny()
+ .orElseThrow();
+
+ CompletableFuture<Optional<ImageRenderer>> imageFuture =
+ CompletableFuture.supplyAsync(
+ () -> ImageRendererManager.safelyPrepareFactory(
+ location, imageFactory
+ ),
+ backgroundExecutor
+ )
+ .thenCompose(preparationBarrier::wait)
+ .thenApplyAsync(imageSupplierOpt -> {
+ if (imageSupplierOpt.isEmpty()) {
+ return Optional.empty();
+ }
+ ImageRendererFactory.ImageSupplier supplier = imageSupplierOpt.get();
+
+ ImageRenderer imageRenderer;
+ try {
+ imageRenderer = supplier.completeImage();
+ } catch (Exception e) {
+ YACLConstants.LOGGER.error("Failed to create image '{}'", location, e);
+ return Optional.empty();
+ }
+
+ ImageRendererManager.PRELOADED_IMAGE_CACHE.put(location, imageRenderer);
+
+ return Optional.of(imageRenderer);
+ }, gameExecutor);
+
+ futures.add(imageFuture);
+
+ imageFuture.whenComplete((result, throwable) -> {
+ if (throwable != null) {
+ CrashReport crashReport = CrashReport.forThrowable(throwable, "Failed to load image");
+ CrashReportCategory category = crashReport.addCategory("YACL Gui");
+ category.setDetail("Image identifier", location.toString());
+ throw new ReportedException(crashReport);
+ }
+ });
+ }
+
+ return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));
+ }
+}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java
index cc5a2c3..39ddb55 100644
--- a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/AnimatedDynamicTextureImage.java
@@ -94,7 +94,7 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage {
return targetHeight;
}
- public static ImageRendererFactory<AnimatedDynamicTextureImage> createGIFFromTexture(ResourceLocation textureLocation) {
+ public static ImageRendererFactory createGIFFromTexture(ResourceLocation textureLocation) {
return () -> {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
Resource resource = resourceManager.getResource(textureLocation).orElseThrow();
@@ -103,11 +103,11 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage {
};
}
- public static ImageRendererFactory<AnimatedDynamicTextureImage> createGIFFromPath(Path path, ResourceLocation uniqueLocation) {
+ public static ImageRendererFactory createGIFFromPath(Path path, ResourceLocation uniqueLocation) {
return () -> createGIFSupplier(new FileInputStream(path.toFile()), uniqueLocation);
}
- public static ImageRendererFactory<AnimatedDynamicTextureImage> createWEBPFromTexture(ResourceLocation textureLocation) {
+ public static ImageRendererFactory createWEBPFromTexture(ResourceLocation textureLocation) {
return () -> {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
Resource resource = resourceManager.getResource(textureLocation).orElseThrow();
@@ -116,11 +116,11 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage {
};
}
- public static ImageRendererFactory<AnimatedDynamicTextureImage> createWEBPFromPath(Path path, ResourceLocation uniqueLocation) {
+ public static ImageRendererFactory createWEBPFromPath(Path path, ResourceLocation uniqueLocation) {
return () -> createWEBPSupplier(new FileInputStream(path.toFile()), uniqueLocation);
}
- private static ImageRendererFactory.ImageSupplier<AnimatedDynamicTextureImage> createGIFSupplier(InputStream is, ResourceLocation uniqueLocation) {
+ private static ImageRendererFactory.ImageSupplier createGIFSupplier(InputStream is, ResourceLocation uniqueLocation) {
try (is) {
ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next();
reader.setInput(ImageIO.createImageInputStream(is));
@@ -144,7 +144,7 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage {
}
}
- private static ImageRendererFactory.ImageSupplier<AnimatedDynamicTextureImage> createWEBPSupplier(InputStream is, ResourceLocation uniqueLocation) {
+ private static ImageRendererFactory.ImageSupplier createWEBPSupplier(InputStream is, ResourceLocation uniqueLocation) {
try (is) {
ImageReader reader = new WebPImageReaderSpi().createReaderInstance();
reader.setInput(ImageIO.createImageInputStream(is));
@@ -180,7 +180,7 @@ public class AnimatedDynamicTextureImage extends DynamicTextureImage {
}
}
- private static ImageRendererFactory.ImageSupplier<AnimatedDynamicTextureImage> createFromImageReader(ImageReader reader, AnimFrameProvider animationProvider, ResourceLocation uniqueLocation) throws Exception {
+ private static ImageRendererFactory.ImageSupplier createFromImageReader(ImageReader reader, AnimFrameProvider animationProvider, ResourceLocation uniqueLocation) throws Exception {
if (reader.isSeekForwardOnly()) {
throw new RuntimeException("Image reader is not seekable");
}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java
index 86dc833..2d2abb9 100644
--- a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/DynamicTextureImage.java
@@ -66,7 +66,7 @@ public class DynamicTextureImage implements ImageRenderer {
textureManager.release(uniqueLocation);
}
- public static ImageRendererFactory<DynamicTextureImage> fromPath(Path imagePath, ResourceLocation location) {
- return (ImageRendererFactory.OnThread<DynamicTextureImage>) () -> () -> new DynamicTextureImage(NativeImage.read(new FileInputStream(imagePath.toFile())), location);
+ public static ImageRendererFactory fromPath(Path imagePath, ResourceLocation location) {
+ return (ImageRendererFactory.OnThread) () -> () -> new DynamicTextureImage(NativeImage.read(new FileInputStream(imagePath.toFile())), location);
}
}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java
index 6805611..abbeec7 100644
--- a/common/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java
+++ b/common/src/main/java/dev/isxander/yacl3/gui/image/impl/ResourceTextureImage.java
@@ -50,7 +50,7 @@ public class ResourceTextureImage implements ImageRenderer {
}
- public static ImageRendererFactory<ResourceTextureImage> createFactory(ResourceLocation location, float u, float v, int width, int height, int textureWidth, int textureHeight) {
- return (ImageRendererFactory.OnThread<ResourceTextureImage>) () -> () -> new ResourceTextureImage(location, u, v, width, height, textureWidth, textureHeight);
+ public static ImageRendererFactory createFactory(ResourceLocation location, float u, float v, int width, int height, int textureWidth, int textureHeight) {
+ return (ImageRendererFactory.OnThread) () -> () -> new ResourceTextureImage(location, u, v, width, height, textureWidth, textureHeight);
}
}
diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java
index 3a3008c..67fa6a6 100644
--- a/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java
+++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionDescriptionImpl.java
@@ -88,7 +88,13 @@ public record OptionDescriptionImpl(Component text, CompletableFuture<Optional<I
public Builder webpImage(ResourceLocation image) {
Validate.isTrue(imageUnset, "Image already set!");
- this.image = ImageRendererManager.registerImage(image, AnimatedDynamicTextureImage.createWEBPFromTexture(image)).thenApply(Optional::of);
+ Optional<ImageRenderer> completedImage = ImageRendererManager.getImage(image);
+ if (completedImage.isPresent()) {
+ this.image = CompletableFuture.completedFuture(completedImage);
+ } else {
+ this.image = ImageRendererManager.registerImage(image, AnimatedDynamicTextureImage.createWEBPFromTexture(image)).thenApply(Optional::of);
+ }
+
imageUnset = false;
return this;
}
diff --git a/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/YACLFabricEntrypoint.java b/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/YACLFabricEntrypoint.java
new file mode 100644
index 0000000..032f46b
--- /dev/null
+++ b/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/YACLFabricEntrypoint.java
@@ -0,0 +1,13 @@
+package dev.isxander.yacl3.platform.fabric;
+
+import dev.isxander.yacl3.platform.fabric.image.YACLImageReloadListenerFabric;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
+import net.minecraft.server.packs.PackType;
+
+public class YACLFabricEntrypoint implements ClientModInitializer {
+ @Override
+ public void onInitializeClient() {
+ ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new YACLImageReloadListenerFabric());
+ }
+}
diff --git a/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/image/YACLImageReloadListenerFabric.java b/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/image/YACLImageReloadListenerFabric.java
new file mode 100644
index 0000000..9eed7fe
--- /dev/null
+++ b/fabric/src/main/java/dev/isxander/yacl3/platform/fabric/image/YACLImageReloadListenerFabric.java
@@ -0,0 +1,12 @@
+package dev.isxander.yacl3.platform.fabric.image;
+
+import dev.isxander.yacl3.gui.image.YACLImageReloadListener;
+import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
+import net.minecraft.resources.ResourceLocation;
+
+public class YACLImageReloadListenerFabric extends YACLImageReloadListener implements IdentifiableResourceReloadListener {
+ @Override
+ public ResourceLocation getFabricId() {
+ return new ResourceLocation("yet_another_config_lib_v3", "image_reload_listener");
+ }
+}
diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json
index f2979ff..7f74e56 100644
--- a/fabric/src/main/resources/fabric.mod.json
+++ b/fabric/src/main/resources/fabric.mod.json
@@ -25,6 +25,11 @@
"yacl.mixins.json",
"yacl-fabric.mixins.json"
],
+ "entrypoints": {
+ "client": [
+ "dev.isxander.yacl3.platform.fabric.YACLFabricEntrypoint"
+ ]
+ },
"custom": {
"modmenu": {
"badges": ["library"]
diff --git a/neoforge/src/main/java/dev/isxander/yacl3/platform/neoforge/YACLForgeEntrypoint.java b/neoforge/src/main/java/dev/isxander/yacl3/platform/neoforge/YACLForgeEntrypoint.java
index 343635e..4dfe2dd 100644
--- a/neoforge/src/main/java/dev/isxander/yacl3/platform/neoforge/YACLForgeEntrypoint.java
+++ b/neoforge/src/main/java/dev/isxander/yacl3/platform/neoforge/YACLForgeEntrypoint.java
@@ -1,11 +1,19 @@
package dev.isxander.yacl3.platform.neoforge;
+import dev.isxander.yacl3.gui.image.YACLImageReloadListener;
+import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
+import net.neoforged.fml.javafmlmod.FMLJavaModLoadingContext;
+import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
+import net.neoforged.neoforge.common.NeoForge;
@Mod("yet_another_config_lib_v3")
public class YACLForgeEntrypoint {
- public YACLForgeEntrypoint() {
-
+ public YACLForgeEntrypoint(IEventBus modEventBus) {
+ modEventBus.addListener(RegisterClientReloadListenersEvent.class, event -> {
+ System.out.println("image reload event");
+ event.registerReloadListener(new YACLImageReloadListener());
+ });
}
}