diff --git a/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/AbstractComponentContainer.java b/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/AbstractComponentContainer.java index 2f219df6..688fb499 100644 --- a/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/AbstractComponentContainer.java +++ b/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/AbstractComponentContainer.java @@ -22,8 +22,8 @@ */ package org.ladysnake.cca.internal.base; -import net.fabricmc.fabric.api.util.NbtType; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; import net.minecraft.registry.RegistryWrapper; import net.minecraft.util.Identifier; @@ -74,8 +74,8 @@ public void copyFrom(ComponentContainer other, RegistryWrapper.WrapperLookup reg */ @Override public void fromTag(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { - if(tag.contains(NBT_KEY, NbtType.LIST)) { - NbtList componentList = tag.getList(NBT_KEY, NbtType.COMPOUND); + if(tag.contains(NBT_KEY, NbtElement.LIST_TYPE)) { + NbtList componentList = tag.getList(NBT_KEY, NbtElement.COMPOUND_TYPE); for (int i = 0; i < componentList.size(); i++) { NbtCompound nbt = componentList.getCompound(i); ComponentKey type = ComponentRegistry.get(new Identifier(nbt.getString("componentId"))); @@ -86,13 +86,13 @@ public void fromTag(NbtCompound tag, RegistryWrapper.WrapperLookup registryLooku } } } - } else if (tag.contains("cardinal_components", NbtType.COMPOUND)) { + } else if (tag.contains(NBT_KEY, NbtElement.COMPOUND_TYPE)) { NbtCompound componentMap = tag.getCompound(NBT_KEY); for (ComponentKey key : this.keys()) { String keyId = key.getId().toString(); - if (componentMap.contains(keyId, NbtType.COMPOUND)) { + if (componentMap.contains(keyId, NbtElement.COMPOUND_TYPE)) { Component component = key.getInternal(this); assert component != null; component.readFromNbt(componentMap.getCompound(keyId), registryLookup); diff --git a/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/ComponentsInternals.java b/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/ComponentsInternals.java index 59150741..52acfb07 100644 --- a/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/ComponentsInternals.java +++ b/cardinal-components-base/src/main/java/org/ladysnake/cca/internal/base/ComponentsInternals.java @@ -22,6 +22,8 @@ */ package org.ladysnake.cca.internal.base; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.util.Identifier; import org.apache.logging.log4j.LogManager; @@ -42,27 +44,44 @@ public final class ComponentsInternals { public static final Logger LOGGER = LogManager.getLogger("Cardinal Components API"); - private static boolean logDeserializationWarnings = true; + private static final int DEFAULT_MAX_WARNINGS_PER_COMPONENT = 5; + private static final boolean DEFAULT_LOG_DESERIALIZATION_WARNINGS = true; + private static final int configVersion = 2; + private static boolean logDeserializationWarnings = DEFAULT_LOG_DESERIALIZATION_WARNINGS; + private static int maxWarningsPerComponent = DEFAULT_MAX_WARNINGS_PER_COMPONENT; + private static final Object2IntMap warningCounts = new Object2IntOpenHashMap<>(); public static void init() { Path path = FabricLoader.getInstance().getConfigDir().resolve("cardinal-components-api.properties"); try(Reader reader = Files.newBufferedReader(path)) { Properties cfg = new Properties(); cfg.load(reader); - logDeserializationWarnings = Boolean.parseBoolean(cfg.getProperty("log-deserialization-warnings", "true")); + if (Integer.parseInt(cfg.getProperty("config-version")) < configVersion) { + writeConfigFile(path); + cfg.clear(); + } + logDeserializationWarnings = Boolean.parseBoolean(cfg.getProperty("log-deserialization-warnings", String.valueOf(DEFAULT_LOG_DESERIALIZATION_WARNINGS))); + maxWarningsPerComponent = Integer.parseInt(cfg.getProperty("max-deserialization-warnings", String.valueOf(DEFAULT_MAX_WARNINGS_PER_COMPONENT))); } catch (IOException e) { - try { - Files.writeString(path, """ - # If set to false, warnings will not get logged when a component fails to be resolved (typically due to mods being removed) - # Default value: true - log-deserialization-warnings = true + writeConfigFile(path); + } + } - # Internal value, do not edit or your changes may be arbitrarily reset - config-version = 1 - """); - } catch (IOException ex) { - LOGGER.error("Failed to write config file at {}", path); - } + private static void writeConfigFile(Path path) { + try { + Files.writeString(path, """ + # If set to false, warnings will not get logged when a component fails to be resolved (typically due to mods being removed) + # Default value: %1$s + log-deserialization-warnings = %1$s + # If log-deserialization-warnings is enabled, warnings will be printed at most *this number of times* for every component type + # Default value: %2$d + max-deserialization-warnings = %2$d + + # Internal value, do not edit or your changes may be arbitrarily reset + config-version = %3$d + """.formatted(DEFAULT_LOG_DESERIALIZATION_WARNINGS, DEFAULT_MAX_WARNINGS_PER_COMPONENT, configVersion)); + } catch (IOException ex) { + LOGGER.error("Failed to write config file at {}", path); } } @@ -78,12 +97,16 @@ public static R createFactory(Class factoryClass) { public static void logDeserializationWarnings(Collection missedKeyIds) { if (logDeserializationWarnings) { for (String missedKeyId : missedKeyIds) { - Identifier id = Identifier.tryParse(missedKeyId); - String cause; - if (id == null) cause = "invalid identifier"; - else if (ComponentRegistry.get(id) == null) cause = "unregistered key"; - else cause = "provider does not have "; - LOGGER.warn("Failed to deserialize component: {} {}", cause, missedKeyId); + int warningCount = warningCounts.getInt(missedKeyId); + if (warningCount < maxWarningsPerComponent) { + Identifier id = Identifier.tryParse(missedKeyId); + String cause; + if (id == null) cause = "invalid identifier"; + else if (ComponentRegistry.get(id) == null) cause = "unregistered key"; + else cause = "provider does not have "; + LOGGER.warn("Failed to deserialize component: {} {}{}", cause, missedKeyId, warningCount + 1 >= maxWarningsPerComponent ? " (last warning for this component)" : ""); + warningCounts.put(missedKeyId, warningCount + 1); + } } } } diff --git a/cardinal-components-chunk/src/main/java/org/ladysnake/cca/mixin/chunk/common/MixinChunkSerializer.java b/cardinal-components-chunk/src/main/java/org/ladysnake/cca/mixin/chunk/common/MixinChunkSerializer.java index e00f0db9..73ee3d9d 100644 --- a/cardinal-components-chunk/src/main/java/org/ladysnake/cca/mixin/chunk/common/MixinChunkSerializer.java +++ b/cardinal-components-chunk/src/main/java/org/ladysnake/cca/mixin/chunk/common/MixinChunkSerializer.java @@ -23,6 +23,7 @@ package org.ladysnake.cca.mixin.chunk.common; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.ChunkSerializer; @@ -30,6 +31,7 @@ import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.WrapperProtoChunk; import net.minecraft.world.poi.PointOfInterestStorage; +import org.ladysnake.cca.internal.base.AbstractComponentContainer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -42,6 +44,11 @@ private static void deserialize(ServerWorld world, PointOfInterestStorage pointO ProtoChunk ret = cir.getReturnValue(); Chunk chunk = ret instanceof WrapperProtoChunk ? ((WrapperProtoChunk) ret).getWrappedChunk() : ret; chunk.asComponentProvider().getComponentContainer().fromTag(tag, world.getRegistryManager()); + // If components have been removed, we need to make the chunk save again + if (tag.contains(AbstractComponentContainer.NBT_KEY, NbtElement.COMPOUND_TYPE)) { + int remainingComponentCount = tag.getCompound(AbstractComponentContainer.NBT_KEY).getSize(); + chunk.setNeedsSaving(remainingComponentCount > 0); + } } @Inject(method = "serialize", at = @At("RETURN")) diff --git a/changelog.md b/changelog.md index ba464e15..d7061835 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,11 @@ Updated to 1.20.5/1.20.6 This update introduces multiple breaking changes - a migration guide is available on [the Ladysnake website](https://ladysnake.org/wiki/cardinal-components-api/upgrade-instructions/CCA-6-changes). +### Pre-Release 3 +**Fixes** +- Missing components (usually caused by removed mods) will no longer trigger a wall of warnings + - The number of warnings logged for each missing component type is configurable + ### Pre-Release 2 **Fixes** - The library no longer requires Loom 1.7-alpha to be used in dev workspaces diff --git a/gradle.properties b/gradle.properties index 7543d020..3cc8c10d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ fabric_api_version=0.98.0+1.20.6 elmendorf_version=0.12.0-SNAPSHOT #Publishing -mod_version = 6.0.0-pre2 +mod_version = 6.0.0-pre3 curseforge_id = 318449 modrinth_id = K01OU20C curseforge_versions = 1.20.5; 1.20.6