aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java')
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java b/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
new file mode 100644
index 0000000..0c9b8a3
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/image/ImageRendererManager.java
@@ -0,0 +1,120 @@
+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 factory) {
+ System.out.println(PRELOADED_IMAGE_CACHE.get(id));
+
+ if (IMAGE_CACHE.containsKey(id)) {
+ return (CompletableFuture<T>) IMAGE_CACHE.get(id);
+ }
+
+ var future = new CompletableFuture<ImageRenderer>();
+ IMAGE_CACHE.put(id, future);
+
+ SINGLE_THREAD_EXECUTOR.submit(() -> {
+ Supplier<Optional<ImageRendererFactory.ImageSupplier>> supplier =
+ factory.requiresOffThreadPreparation()
+ ? new CompletedSupplier<>(safelyPrepareFactory(id, factory))
+ : () -> safelyPrepareFactory(id, factory);
+
+ Minecraft.getInstance().execute(() -> completeImageFactory(id, supplier, future));
+ });
+
+ return (CompletableFuture<T>) future;
+ }
+
+ private static <T extends ImageRenderer> void completeImageFactory(ResourceLocation id, Supplier<Optional<ImageRendererFactory.ImageSupplier>> supplier, CompletableFuture<ImageRenderer> future) {
+ RenderSystem.assertOnRenderThread();
+
+ ImageRendererFactory.ImageSupplier completableImage = supplier.get().orElse(null);
+ if (completableImage == null) {
+ return;
+ }
+
+ // sanity check - this should never happen
+ if (future.isDone()) {
+ YACLConstants.LOGGER.error("Image '{}' was already completed", id);
+ return;
+ }
+
+ ImageRenderer image;
+ try {
+ image = completableImage.completeImage();
+ } catch (Exception e) {
+ YACLConstants.LOGGER.error("Failed to create image '{}'", id, e);
+ return;
+ }
+
+ future.complete(image);
+ }
+
+ public static void closeAll() {
+ SINGLE_THREAD_EXECUTOR.shutdownNow();
+ IMAGE_CACHE.values().removeIf(future -> {
+ if (future.isDone()) {
+ future.join().close();
+ }
+ return true;
+ });
+ }
+
+ static Optional<ImageRendererFactory.ImageSupplier> safelyPrepareFactory(ResourceLocation id, ImageRendererFactory factory) {
+ try {
+ return Optional.of(factory.prepareImage());
+ } catch (Exception e) {
+ YACLConstants.LOGGER.error("Failed to prepare image '{}'", id, e);
+ IMAGE_CACHE.remove(id);
+ return Optional.empty();
+ }
+ }
+
+ public record PreloadedImageFactory(Predicate<ResourceLocation> predicate, Function<ResourceLocation, ImageRendererFactory> factory) {
+ }
+
+ private record CompletedSupplier<T>(T get) implements Supplier<T> {
+ }
+
+}