1
0
mirror of https://github.com/GeyserMC/Rainbow.git synced 2025-12-19 14:59:16 +00:00

More final tweaks

This commit is contained in:
Eclipse
2025-10-16 14:07:22 +00:00
parent 2cc85d6c91
commit 3296b5a59e
12 changed files with 94 additions and 73 deletions

View File

@@ -8,14 +8,11 @@ import net.minecraft.Util;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderLookup;
import net.minecraft.resources.RegistryOps; 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.CodecUtil;
import org.geysermc.rainbow.RainbowIO;
import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.PackSerializer;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
@@ -31,26 +28,17 @@ public class MinecraftPackSerializer implements PackSerializer {
@Override @Override
public <T> CompletableFuture<?> saveJson(Codec<T> codec, T object, Path path) { public <T> CompletableFuture<?> saveJson(Codec<T> codec, T object, Path path) {
DynamicOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, registries); DynamicOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, registries);
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops)),
try { Util.backgroundExecutor().forName("PackSerializer-saveJson"));
CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops);
} catch (IOException exception) {
// TODO log
}
}, Util.backgroundExecutor().forName("PackSerializer-saveJson"));
} }
@Override @Override
public CompletableFuture<?> saveTexture(byte[] texture, Path path) { public CompletableFuture<?> saveTexture(byte[] texture, Path path) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> {
try { CodecUtil.ensureDirectoryExists(path.getParent());
CodecUtil.ensureDirectoryExists(path.getParent()); try (OutputStream outputTexture = new FileOutputStream(path.toFile())) {
try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { outputTexture.write(texture);
outputTexture.write(texture);
}
} catch (IOException exception) {
// TODO log
} }
}, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); }), Util.backgroundExecutor().forName("PackSerializer-saveTexture"));
} }
} }

View File

@@ -8,6 +8,7 @@ import net.minecraft.util.RandomSource;
import net.minecraft.util.StringUtil; import net.minecraft.util.StringUtil;
import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.CodecUtil;
import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.RainbowIO;
import org.geysermc.rainbow.client.mixin.SplashRendererAccessor; import org.geysermc.rainbow.client.mixin.SplashRendererAccessor;
import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer; import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer;
import org.geysermc.rainbow.pack.BedrockItem; import org.geysermc.rainbow.pack.BedrockItem;
@@ -24,7 +25,7 @@ import java.util.function.Consumer;
public final class PackManager { public final class PackManager {
private static final List<String> PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY", private static final List<String> 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", "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.", "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!", "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() { public boolean finish() {
currentPack.map(pack -> { currentPack.map(pack -> {
try { RainbowIO.safeIO(() -> Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack)));
Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack));
} catch (IOException exception) {
// TODO log
}
return pack.save(); return pack.save();
}).ifPresent(CompletableFuture::join); }).ifPresent(CompletableFuture::join);
boolean wasPresent = currentPack.isPresent(); boolean wasPresent = currentPack.isPresent();

View File

@@ -23,6 +23,7 @@ import net.minecraft.util.ProblemReporter;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.equipment.EquipmentAsset; import net.minecraft.world.item.equipment.EquipmentAsset;
import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.RainbowIO;
import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.AssetResolver;
import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.PackSerializer;
import org.geysermc.rainbow.pack.BedrockPack; import org.geysermc.rainbow.pack.BedrockPack;
@@ -125,7 +126,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
try { try {
output.writeIfNeeded(path, texture, HashCode.fromBytes(texture)); output.writeIfNeeded(path, texture, HashCode.fromBytes(texture));
} catch (IOException exception) { } 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")); }, Util.backgroundExecutor().forName("PackSerializer-saveTexture"));
} }
@@ -153,12 +154,11 @@ public abstract class RainbowModelProvider extends FabricModelProvider {
public Optional<ResolvedModel> getResolvedModel(ResourceLocation location) { public Optional<ResolvedModel> getResolvedModel(ResourceLocation location) {
return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location)) return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location))
.map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString())))
.or(() -> { .or(() -> RainbowIO.safeIO(() -> {
try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) { try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) {
return Optional.of(BlockModel.fromStream(reader)); return BlockModel.fromStream(reader);
} catch (IOException ignored) {} }
return Optional.empty(); }))
})
.map(model -> new ResolvedModel() { .map(model -> new ResolvedModel() {
@Override @Override
public @NotNull UnbakedModel wrapped() { public @NotNull UnbakedModel wrapped() {

View File

@@ -14,8 +14,7 @@ public class Rainbow {
return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); return ResourceLocation.fromNamespaceAndPath(MOD_ID, path);
} }
// TODO rename remove file public static String safeResourceLocation(ResourceLocation location) {
public static String fileSafeResourceLocation(ResourceLocation location) {
return location.toString().replace(':', '.').replace('/', '_'); return location.toString().replace(':', '.').replace('/', '_');
} }

View File

@@ -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> T safeIO(IOSupplier<T> supplier, T defaultValue) {
try {
return supplier.get();
} catch (IOException exception) {
LOGGER.error("Failed to perform IO operation!", exception);
return defaultValue;
}
}
public static <T> Optional<T> safeIO(IOSupplier<T> 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> {
T get() throws IOException;
}
@FunctionalInterface
public interface IORunnable {
void run() throws IOException;
}
}

View File

@@ -61,7 +61,7 @@ public record GeyserBaseDefinition(ResourceLocation bedrockIdentifier, Optional<
} }
public String textureName() { public String textureName() {
return bedrockOptions.icon.orElse(Rainbow.fileSafeResourceLocation(bedrockIdentifier)); return bedrockOptions.icon.orElse(Rainbow.safeResourceLocation(bedrockIdentifier));
} }
public record BedrockOptions(Optional<String> icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, List<ResourceLocation> tags) { public record BedrockOptions(Optional<String> icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, List<ResourceLocation> tags) {

View File

@@ -71,9 +71,9 @@ public class BedrockItemMapper {
} }
public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) {
// TODO Improve this, use resouce log in problemreporter ResourceLocation itemModel = stack.get(DataComponents.ITEM_MODEL);
ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(itemModel).map(ClientItem::model).orElseThrow();
ProblemReporter childReporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " "); ProblemReporter childReporter = reporter.forChild(() -> "item model " + itemModel + " with custom model data " + customModelData + " ");
if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List<RangeSelectItemModel.Entry> entries, Optional<ItemModel.Unbaked> fallback)) { if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List<RangeSelectItemModel.Entry> entries, Optional<ItemModel.Unbaked> fallback)) {
// WHY, Mojang? // WHY, Mojang?
if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) { if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) {

View File

@@ -44,7 +44,7 @@ public record BedrockGeometryContext(Optional<Supplier<StitchedGeometry>> geomet
// This check should probably be done differently (actually check if the model is 2D or 3D) // This check should probably be done differently (actually check if the model is 2D or 3D)
ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); ResourceLocation modelLocation = ResourceLocation.parse(model.debugName());
String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier); String safeIdentifier = Rainbow.safeResourceLocation(bedrockIdentifier);
geometry = Optional.of(Suppliers.memoize(() -> { geometry = Optional.of(Suppliers.memoize(() -> {
StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context);

View File

@@ -2,7 +2,6 @@ package org.geysermc.rainbow.mapping.geometry;
import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.renderer.texture.SpriteLoader; 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.data.AtlasIds;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.RainbowIO;
import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.PackContext;
import org.geysermc.rainbow.mixin.SpriteContentsAccessor; import org.geysermc.rainbow.mixin.SpriteContentsAccessor;
import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; import org.geysermc.rainbow.mixin.SpriteLoaderAccessor;
import org.geysermc.rainbow.mixin.TextureSlotsAccessor; import org.geysermc.rainbow.mixin.TextureSlotsAccessor;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -50,20 +49,20 @@ public record StitchedTextures(Map<String, TextureAtlasSprite> sprites, Supplier
// Atlas ID doesn't matter much here, but BLOCKS is the most appropriate // 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 // 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); SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16);
List<SpriteContents> sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList(); List<SpriteContents> sprites = textures.distinct()
.map(texture -> readSpriteContents(texture, context))
.<SpriteContents>mapMulti(Optional::ifPresent)
.toList();
return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor());
} }
private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) { private static Optional<SpriteContents> readSpriteContents(ResourceLocation location, PackContext context) {
// TODO decorate path util return RainbowIO.safeIO(() -> {
// TODO don't use ResourceManager try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) {
// TODO IO is on main thread here? NativeImage texture = NativeImage.read(textureStream);
try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture);
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 NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) {

View File

@@ -32,7 +32,7 @@ public record BedrockItem(ResourceLocation identifier, String textureName, Bedro
CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)),
stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()),
stitchedGeometry.map(StitchedGeometry::stitchedTextures).map(textureSaver).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())
); );
}) })
); );

View File

@@ -13,6 +13,7 @@ import net.minecraft.world.item.component.CustomModelData;
import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.CodecUtil;
import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.PackConstants;
import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.Rainbow;
import org.geysermc.rainbow.RainbowIO;
import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.image.NativeImageUtil;
import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.AssetResolver;
import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.BedrockItemMapper;
@@ -131,22 +132,12 @@ public class BedrockPack {
Function<TextureHolder, CompletableFuture<?>> textureSaver = texture -> { Function<TextureHolder, CompletableFuture<?>> textureSaver = texture -> {
ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location());
return texture.supplier() return texture.supplier()
.flatMap(image -> { .flatMap(image -> RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(image.get())))
try { .or(() -> RainbowIO.safeIO(() -> {
return Optional.of(NativeImageUtil.writeToByteArray(image.get()));
} catch (IOException exception) {
// TODO log
return Optional.empty();
}
})
.or(() -> {
try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) { try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) {
return Optional.of(textureStream.readAllBytes()); return textureStream.readAllBytes();
} catch (IOException exception) {
// TODO log
return Optional.empty();
} }
}) }))
.map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath())))
.orElse(CompletableFuture.completedFuture(null)); .orElse(CompletableFuture.completedFuture(null));
}; };
@@ -156,11 +147,7 @@ public class BedrockPack {
} }
if (paths.zipOutput().isPresent()) { if (paths.zipOutput().isPresent()) {
try { RainbowIO.safeIO(() -> CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get()));
CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get());
} catch (IOException exception) {
// TODO log
}
} }
if (reporter instanceof AutoCloseable closeable) { if (reporter instanceof AutoCloseable closeable) {

View File

@@ -37,7 +37,7 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf
public CompletableFuture<?> save(PackSerializer serializer, Path attachablesDirectory) { public CompletableFuture<?> save(PackSerializer serializer, Path attachablesDirectory) {
// Get a safe attachable path by using Geyser's way of getting icons // 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) { public static Builder builder(ResourceLocation identifier) {