diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java index 61c1e39..f7fdbaa 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java @@ -8,14 +8,11 @@ import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.core.HolderLookup; import net.minecraft.resources.RegistryOps; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -import org.apache.commons.io.IOUtils; import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackSerializer; import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStream; import java.nio.file.Path; import java.util.Objects; @@ -31,26 +28,17 @@ public class MinecraftPackSerializer implements PackSerializer { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { DynamicOps ops = RegistryOps.create(JsonOps.INSTANCE, registries); - return CompletableFuture.runAsync(() -> { - try { - CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops); - } catch (IOException exception) { - // TODO log - } - }, Util.backgroundExecutor().forName("PackSerializer-saveJson")); + return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops)), + Util.backgroundExecutor().forName("PackSerializer-saveJson")); } @Override public CompletableFuture saveTexture(byte[] texture, Path path) { - return CompletableFuture.runAsync(() -> { - try { - CodecUtil.ensureDirectoryExists(path.getParent()); - try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { - outputTexture.write(texture); - } - } catch (IOException exception) { - // TODO log + return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> { + CodecUtil.ensureDirectoryExists(path.getParent()); + try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { + outputTexture.write(texture); } - }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); + }), Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 4cba01b..172e576 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -8,6 +8,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.util.StringUtil; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.client.mixin.SplashRendererAccessor; import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer; import org.geysermc.rainbow.pack.BedrockItem; @@ -24,7 +25,7 @@ import java.util.function.Consumer; public final class PackManager { private static final List PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY", - "use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", + "use !!plshelp", "*message was deleted*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", "eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually", "you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.", "meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!", @@ -69,11 +70,7 @@ public final class PackManager { public boolean finish() { currentPack.map(pack -> { - try { - Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack)); - } catch (IOException exception) { - // TODO log - } + RainbowIO.safeIO(() -> Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack))); return pack.save(); }).ifPresent(CompletableFuture::join); boolean wasPresent = currentPack.isPresent(); diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index c3c70f3..b009858 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -23,6 +23,7 @@ import net.minecraft.util.ProblemReporter; import net.minecraft.world.item.Item; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockPack; @@ -125,7 +126,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { try { output.writeIfNeeded(path, texture, HashCode.fromBytes(texture)); } catch (IOException exception) { - LOGGER.error("Failed to save file to {}", path, exception); + LOGGER.error("Failed to save texture to {}", path, exception); } }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } @@ -153,12 +154,11 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public Optional getResolvedModel(ResourceLocation location) { return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location)) .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) - .or(() -> { + .or(() -> RainbowIO.safeIO(() -> { try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) { - return Optional.of(BlockModel.fromStream(reader)); - } catch (IOException ignored) {} - return Optional.empty(); - }) + return BlockModel.fromStream(reader); + } + })) .map(model -> new ResolvedModel() { @Override public @NotNull UnbakedModel wrapped() { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index aa3352d..f2e80b1 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -14,8 +14,7 @@ public class Rainbow { return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } - // TODO rename remove file - public static String fileSafeResourceLocation(ResourceLocation location) { + public static String safeResourceLocation(ResourceLocation location) { return location.toString().replace(':', '.').replace('/', '_'); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java new file mode 100644 index 0000000..e2f7cbe --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java @@ -0,0 +1,51 @@ +package org.geysermc.rainbow; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Optional; + +public final class RainbowIO { + private static final Logger LOGGER = LogUtils.getLogger(); + + private RainbowIO() {} + + public static T safeIO(IOSupplier supplier, T defaultValue) { + try { + return supplier.get(); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + return defaultValue; + } + } + + public static Optional safeIO(IOSupplier supplier) { + try { + return Optional.ofNullable(supplier.get()); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + return Optional.empty(); + } + } + + public static void safeIO(IORunnable runnable) { + try { + runnable.run(); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + } + } + + @FunctionalInterface + public interface IOSupplier { + + T get() throws IOException; + } + + @FunctionalInterface + public interface IORunnable { + + void run() throws IOException; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java index 304c011..66f3f04 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java @@ -61,7 +61,7 @@ public record GeyserBaseDefinition(ResourceLocation bedrockIdentifier, Optional< } public String textureName() { - return bedrockOptions.icon.orElse(Rainbow.fileSafeResourceLocation(bedrockIdentifier)); + return bedrockOptions.icon.orElse(Rainbow.safeResourceLocation(bedrockIdentifier)); } public record BedrockOptions(Optional icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, List tags) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 86ddd9a..c911d19 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -71,9 +71,9 @@ public class BedrockItemMapper { } public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { - // TODO Improve this, use resouce log in problemreporter - ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); - ProblemReporter childReporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " "); + ResourceLocation itemModel = stack.get(DataComponents.ITEM_MODEL); + ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(itemModel).map(ClientItem::model).orElseThrow(); + ProblemReporter childReporter = reporter.forChild(() -> "item model " + itemModel + " with custom model data " + customModelData + " "); if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List entries, Optional fallback)) { // WHY, Mojang? if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index a86d618..3d306b1 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -44,7 +44,7 @@ public record BedrockGeometryContext(Optional> geomet // This check should probably be done differently (actually check if the model is 2D or 3D) ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); - String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier); + String safeIdentifier = Rainbow.safeResourceLocation(bedrockIdentifier); geometry = Optional.of(Suppliers.memoize(() -> { StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java index eff4435..1ce8e85 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java @@ -2,7 +2,6 @@ package org.geysermc.rainbow.mapping.geometry; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.Util; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.SpriteLoader; @@ -12,12 +11,12 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.data.AtlasIds; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mixin.SpriteContentsAccessor; import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; import org.geysermc.rainbow.mixin.TextureSlotsAccessor; -import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; @@ -50,20 +49,20 @@ public record StitchedTextures(Map sprites, Supplier // Atlas ID doesn't matter much here, but BLOCKS is the most appropriate // Not sure if 1024 should be the max supported texture size, but it seems to work SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); - List sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList(); + List sprites = textures.distinct() + .map(texture -> readSpriteContents(texture, context)) + .mapMulti(Optional::ifPresent) + .toList(); return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); } - private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) { - // TODO decorate path util - // TODO don't use ResourceManager - // TODO IO is on main thread here? - try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { - NativeImage texture = NativeImage.read(textureStream); - return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); - } catch (IOException exception) { - throw new RuntimeException(exception); - } + private static Optional readSpriteContents(ResourceLocation location, PackContext context) { + return RainbowIO.safeIO(() -> { + try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { + NativeImage texture = NativeImage.read(textureStream); + return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); + } + }); } private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index b84a3bb..5481ac0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -32,7 +32,7 @@ public record BedrockItem(ResourceLocation identifier, String textureName, Bedro CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), stitchedGeometry.map(StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), - geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) + geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.safeResourceLocation(identifier))).orElse(noop()) ); }) ); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index c5a187b..5959868 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -13,6 +13,7 @@ import net.minecraft.world.item.component.CustomModelData; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; @@ -131,22 +132,12 @@ public class BedrockPack { Function> textureSaver = texture -> { ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); return texture.supplier() - .flatMap(image -> { - try { - return Optional.of(NativeImageUtil.writeToByteArray(image.get())); - } catch (IOException exception) { - // TODO log - return Optional.empty(); - } - }) - .or(() -> { + .flatMap(image -> RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(image.get()))) + .or(() -> RainbowIO.safeIO(() -> { try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) { - return Optional.of(textureStream.readAllBytes()); - } catch (IOException exception) { - // TODO log - return Optional.empty(); + return textureStream.readAllBytes(); } - }) + })) .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) .orElse(CompletableFuture.completedFuture(null)); }; @@ -156,11 +147,7 @@ public class BedrockPack { } if (paths.zipOutput().isPresent()) { - try { - CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get()); - } catch (IOException exception) { - // TODO log - } + RainbowIO.safeIO(() -> CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get())); } if (reporter instanceof AutoCloseable closeable) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java index 32b95d0..1178962 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java @@ -37,7 +37,7 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf public CompletableFuture save(PackSerializer serializer, Path attachablesDirectory) { // Get a safe attachable path by using Geyser's way of getting icons - return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier) + ".json")); + return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.safeResourceLocation(info.identifier) + ".json")); } public static Builder builder(ResourceLocation identifier) {