From 62d8340fe17dc0be515a0508059f254687cdedf0 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 4 May 2026 16:30:14 +1000 Subject: [PATCH 01/27] Initial prototype for holder components --- fabric-holder-component-api-v1/build.gradle | 9 + .../FabricDataComponentInitializer.java | 20 +++ .../FabricDataComponentInitializers.java | 12 ++ .../DataHolderComponentInitializer.java | 68 ++++++++ .../FabricDataComponentInitContextImpl.java | 21 +++ .../FabricDataComponentInitializersImpl.java | 14 ++ .../component/HolderComponentEntrypoint.java | 11 ++ .../DataComponentInitializersMixin.java | 58 +++++++ .../ReloadableServerResourcesMixin.java | 35 ++++ .../fabric-holder-component-api-v1/icon.png | Bin 0 -> 1555 bytes ...fabric-holder-component-api-v1.mixins.json | 15 ++ .../src/main/resources/fabric.mod.json | 33 ++++ .../component/HolderComponentCommand.java | 157 ++++++++++++++++++ .../HolderComponentTestModEntrypoint.java | 14 ++ .../components/block/acacia_slab.json | 3 + .../src/testmod/resources/fabric.mod.json | 13 ++ gradle.properties | 1 + settings.gradle | 1 + 18 files changed, 485 insertions(+) create mode 100644 fabric-holder-component-api-v1/build.gradle create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/resources/assets/fabric-holder-component-api-v1/icon.png create mode 100644 fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json create mode 100644 fabric-holder-component-api-v1/src/main/resources/fabric.mod.json create mode 100644 fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java create mode 100644 fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java create mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json create mode 100644 fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json diff --git a/fabric-holder-component-api-v1/build.gradle b/fabric-holder-component-api-v1/build.gradle new file mode 100644 index 00000000000..0bd9ff8a6da --- /dev/null +++ b/fabric-holder-component-api-v1/build.gradle @@ -0,0 +1,9 @@ +version = getSubprojectVersion(project) + +moduleDependencies(project, [ + 'fabric-api-base' +]) + +testDependencies(project, [ + 'fabric-command-api-v2' +]) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java new file mode 100644 index 00000000000..373d063aa8b --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java @@ -0,0 +1,20 @@ +package net.fabricmc.fabric.api.holder.component; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentInitializers; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceManager; + +/// Extended version of [DataComponentInitializers.Initializer] that allows adding components to arbitrary holders instead of having to define what holders are to be given components at registration. +@FunctionalInterface +public interface FabricDataComponentInitializer { + void run(Context context); + + interface Context { + HolderLookup.Provider lookupProvider(); + ResourceManager resourceManager(); + + DataComponentMap.Builder builder(ResourceKey key); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java new file mode 100644 index 00000000000..3a2c0ec3039 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java @@ -0,0 +1,12 @@ +package net.fabricmc.fabric.api.holder.component; + +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + +public final class FabricDataComponentInitializers { + private FabricDataComponentInitializers() { + } + + public static void register(FabricDataComponentInitializer initializer) { + FabricDataComponentInitializersImpl.INITIALIZERS.add(initializer); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java new file mode 100644 index 00000000000..d8ff0ad86f0 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java @@ -0,0 +1,68 @@ +package net.fabricmc.fabric.impl.holder.component; + +import com.google.gson.JsonElement; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.JsonOps; + +import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.util.StrictJsonParser; + +import org.slf4j.Logger; + +import java.io.Reader; +import java.util.List; +import java.util.Map; + +public class DataHolderComponentInitializer implements FabricDataComponentInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public void run(Context context) { + context.lookupProvider().listRegistryKeys().forEach(key -> parse(context, key)); + } + + // TODO: Less jank way of doing this? Not sure if its possible + @SuppressWarnings("unchecked") + private static ResourceKey createResourceKey(ResourceKey> registry, Identifier path) { + return ResourceKey.create((ResourceKey>) registry, path); + } + + private void parse( + Context context, + ResourceKey> key + ) { + + HolderLookup.RegistryLookup lookup = context.lookupProvider().lookupOrThrow(key); + FileToIdConverter lister = FileToIdConverter.json(Registries.componentsDirPath(lookup.key())); + RegistryOps ops = context.lookupProvider().createSerializationContext(JsonOps.INSTANCE); + + for (Map.Entry> entry : lister.listMatchingResourceStacks(context.resourceManager()).entrySet()) { + Identifier location = entry.getKey(); + Identifier id = lister.fileToId(location); + + lookup.get(createResourceKey(key, id)).ifPresent(holder -> { + DataComponentMap.Builder builder = context.builder(holder.key()); + + for (Resource resource : entry.getValue()) { + try (Reader reader = resource.openAsReader()) { + JsonElement element = StrictJsonParser.parse(reader); + DataComponentMap map = DataComponentMap.CODEC.parse(ops, element).getOrThrow(); + builder.addAll(map); + } catch (Exception e) { + LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); + } + } + }); + } + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java new file mode 100644 index 00000000000..2feb9862ffb --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java @@ -0,0 +1,21 @@ +package net.fabricmc.fabric.impl.holder.component; + +import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceManager; + +import java.util.Map; + +public record FabricDataComponentInitContextImpl( + HolderLookup.Provider lookupProvider, + ResourceManager resourceManager, + Map, DataComponentMap.Builder> builders +) implements FabricDataComponentInitializer.Context { + @Override + public DataComponentMap.Builder builder(ResourceKey key) { + return builders.computeIfAbsent(key, _ -> DataComponentMap.builder()); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java new file mode 100644 index 00000000000..18bc60bc822 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java @@ -0,0 +1,14 @@ +package net.fabricmc.fabric.impl.holder.component; + +import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; + +import net.minecraft.server.packs.resources.ResourceManager; + +import java.util.ArrayList; +import java.util.List; + +public class FabricDataComponentInitializersImpl { + public static final List INITIALIZERS = new ArrayList<>(); + + public static final ScopedValue RESOURCE_MANAGER = ScopedValue.newInstance(); +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java new file mode 100644 index 00000000000..b6bafdf43d0 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -0,0 +1,11 @@ +package net.fabricmc.fabric.impl.holder.component; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializers; + +public class HolderComponentEntrypoint implements ModInitializer { + @Override + public void onInitialize() { + FabricDataComponentInitializers.register(new DataHolderComponentInitializer()); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java new file mode 100644 index 00000000000..b70fe4d5d2a --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java @@ -0,0 +1,58 @@ +package net.fabricmc.fabric.mixin.holder.component; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.logging.LogUtils; + +import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitContextImpl; + +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentInitializers; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceManager; + +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.Map; + +@Mixin(DataComponentInitializers.class) +public class DataComponentInitializersMixin { + @Unique + private static final Logger LOGGER = LogUtils.getLogger(); + + @ModifyReturnValue(method = "runInitializers", at = @At("RETURN")) + private Map, DataComponentMap.Builder> runFabricInitializers( + Map, DataComponentMap.Builder> original, + @Local(name = "context", argsOnly = true) HolderLookup.Provider holders + ) { + if (!FabricDataComponentInitializersImpl.RESOURCE_MANAGER.isBound()) { + // we got called either by the report or by a mod + // if a mod called this, it is doing something cursed, and cant get our components + // if this is the component report, it didnt need our components + // TODO: More descriptive error + LOGGER.warn("DataComponentInitializers.runInitializers() was called, but RESOURCE_MANAGER is not bound!"); + return original; + } + + ResourceManager resourceManager = FabricDataComponentInitializersImpl.RESOURCE_MANAGER.get(); + + FabricDataComponentInitializer.Context context = new FabricDataComponentInitContextImpl( + holders, + resourceManager, + original + ); + + for (FabricDataComponentInitializer initializer : FabricDataComponentInitializersImpl.INITIALIZERS) { + initializer.run(context); + } + + return original; + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java new file mode 100644 index 00000000000..d9e79464810 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java @@ -0,0 +1,35 @@ +package net.fabricmc.fabric.mixin.holder.component; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.server.ReloadableServerResources; +import net.minecraft.server.packs.resources.ResourceManager; + +@Mixin(ReloadableServerResources.class) +public class ReloadableServerResourcesMixin { + @WrapOperation(method = "lambda$loadResources$0", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) + private static CompletableFuture wrapWithScopedValue( + Supplier supplier, + Executor executor, + Operation> original, + @Local(argsOnly = true, name = "resourceManager") ResourceManager resourceManager + ) { + Supplier wrappedSupplier = () -> ScopedValue.where( + FabricDataComponentInitializersImpl.RESOURCE_MANAGER, + resourceManager + ).call(supplier::get); + + return original.call(wrappedSupplier, executor); + } +} diff --git a/fabric-holder-component-api-v1/src/main/resources/assets/fabric-holder-component-api-v1/icon.png b/fabric-holder-component-api-v1/src/main/resources/assets/fabric-holder-component-api-v1/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..12c4531de92fe0544a0e15347f84381c132a7feb GIT binary patch literal 1555 zcmbVMONbmr817YeS0`Bvm<_8$sdPkC($if%&o0utJLwrS1L<{`3}loX(o5`x6Ep1Z4+am|^rTyU-RHPnPe)^ld+*!=$4&3I z>W!eGA48bhNyDT~k_>H^p*imGQs^3Zl?0$k+Loj8KVQ7W1ItwT6B%97U5#|C^1vg< z2P<_vSjCFTFD-(@Az}nJ2@DY0UB^eE$`5%FTSvzt4~CFnRpkqjLeS8wK%*W3nPgVL zFfD_el7v}Fk<*8OEWw;8R1=sseC60TqKJ9em~hy zC8^gIp`s|FB#W{vFofW*JAn}jj(>2%P$WL~EH|*I10qJFN!J3EXO@m!u-%x}@yB6e z0TV;R6=70}Tp9vR9OK+IuRBz3Vv%%-O`O1ISQum74h^W^p?^aii~pp6g;v*N9S^m| zwqq53Q0g%^#sPUK+OMy>M63~?u6dZ0dd$p&kvA^VJYodYt5e#YJXCdJGSIZ>Ve;Um z6P9DrzW?%$JEUj?MCBv70A&GY>oAKlX#{hElt+>@g6hlcrFNcIG_C5bC_#`dML=rKZ5X)F-wenaX=`<9S6l@7;C1a*r zFpYEBk-O}Ek>a%|3nur?|9Ss4&tg?*bRU@~s?8{UP}%a?!>*63=Qw$Dyy{wDm@&w} zQ6;E6j#7Y_{P@^<utq@Kv^7o)Nrxg!46%b{#X ziBI6HZ$!(uVX;lz@`%IwoW~m4((?!2X3g;ZO0iH6ziV#JEKKiv@r~aK;fD%m+`qeP zqj!gUd>JIZzIXQGa=BdwQ+vS+TaVp2d4HjC{TKZ1T^OEP^h*yupZ((7eYf^3ZRqE( zWln<`?)nV)~_M|=0.18.4" + }, + "description": "Allows conveniently attaching data to holders via datapack", + "mixins": [ + "fabric-holder-component-api-v1.mixins.json" + ], + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.holder.component.HolderComponentEntrypoint" + ] + }, + "custom": { + "fabric-api:module-lifecycle": "experimental" + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java new file mode 100644 index 00000000000..c871154540c --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java @@ -0,0 +1,157 @@ +package net.fabricmc.fabric.test.holder.component; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.serialization.Codec; + +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.IdentifierArgument; +import net.minecraft.commands.arguments.ResourceArgument; +import net.minecraft.commands.arguments.ResourceKeyArgument; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class HolderComponentCommand { + private static final DynamicCommandExceptionType INVALID_REGISTRY = new DynamicCommandExceptionType( + value -> Component.literal("Invalid Registry '" + value + "'") + ); + + private static final ResourceKey>> ROOT_KEY = ResourceKey.createRegistryKey(Registries.ROOT_REGISTRY_NAME); + + private static CompletableFuture suggestRegistries(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource( + context.getSource().registryAccess().registries().map(registryEntry -> registryEntry.key().identifier()), + builder + ); + } + + private static CompletableFuture suggestHolders(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + ResourceKey> key = ResourceKeyArgument.getRegistryKey( + context, + "registry", + ROOT_KEY, + INVALID_REGISTRY + ); + + return SharedSuggestionProvider.suggestResource( + context.getSource().registryAccess().lookupOrThrow(key) + .listElementIds() + .map(ResourceKey::identifier), + builder + ); + } + + public static void register(CommandDispatcher dispatcher, CommandBuildContext buildContext) { + dispatcher.register(literal("holder_component") + .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS)) + .then(literal("get_holder") + .then(argument("registry", ResourceKeyArgument.key(ROOT_KEY)) + .suggests(HolderComponentCommand::suggestRegistries) + .then(argument("holder", IdentifierArgument.id()) + .suggests(HolderComponentCommand::suggestHolders) + .executes(context -> get( + context, + ResourceKeyArgument.getRegistryKey( + context, + "registry", + ROOT_KEY, + INVALID_REGISTRY + ), + IdentifierArgument.getId(context, "holder") + ))))) + .then(literal("dump_registry") + .then(argument("component", ResourceArgument.resource(buildContext, Registries.DATA_COMPONENT_TYPE)) + .then(argument("registry", ResourceKeyArgument.key(ROOT_KEY)) + .suggests(HolderComponentCommand::suggestRegistries) + .executes(context -> dump( + context, + ResourceArgument.getResource( + context, + "component", + Registries.DATA_COMPONENT_TYPE + ).value(), + ResourceKeyArgument.getRegistryKey( + context, + "registry", + ROOT_KEY, + INVALID_REGISTRY + ) + ))))) + ); + } + + private static final Codec> CODEC = Codec.unboundedMap( + Identifier.CODEC, + DataComponentPatch.CODEC + ); + + private static int dump(CommandContext context, DataComponentType component, ResourceKey> registryKey) { + RegistryAccess registryManager = context.getSource().registryAccess(); + Registry registry = registryManager.lookupOrThrow(registryKey); + + Map builders = new HashMap<>(); + + for (Holder holder : registry.asHolderIdMap()) { + holder.unwrapKey().ifPresent( + key -> { + var val = holder.components().get(component); + if (val != null) { + var builder = builders.computeIfAbsent(key.identifier(), _ -> DataComponentPatch.builder()); + + builder.set(holder.components()); + } + } + ); + } + + Map patches = builders.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().build() + )); + + context.getSource().sendSuccess(() -> + NbtUtils.toPrettyComponent(CODEC.encodeStart(registryManager.createSerializationContext(NbtOps.INSTANCE), patches) + .getOrThrow()), true); + return Command.SINGLE_SUCCESS; + } + + + private static int get(CommandContext context, ResourceKey> registryKey, Identifier holderId) { + RegistryAccess registryManager = context.getSource().registryAccess(); + Registry registry = registryManager.lookupOrThrow(registryKey); + + Holder.Reference holder = registry.get(holderId).orElseThrow(); + + context.getSource().sendSuccess(() -> + NbtUtils.toPrettyComponent(DataComponentMap.CODEC.encodeStart(registryManager.createSerializationContext(NbtOps.INSTANCE), holder.components()) + .getOrThrow()), true); + return Command.SINGLE_SUCCESS; + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java new file mode 100644 index 00000000000..c5343ad2196 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -0,0 +1,14 @@ +package net.fabricmc.fabric.test.holder.component; + + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; + +public class HolderComponentTestModEntrypoint implements ModInitializer { + @Override + public void onInitialize() { + CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, selection) -> { + HolderComponentCommand.register(dispatcher, buildContext); + }); + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json new file mode 100644 index 00000000000..d1bcd3c9c51 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json @@ -0,0 +1,3 @@ +{ + "minecraft:custom_name": "Hello, world!" +} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json b/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 00000000000..5ed59b14441 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "id": "fabric-holder-component-api-v1-testmod", + "name": "Fabric Holder Component API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "entrypoints": { + "main" : [ + "net.fabricmc.fabric.test.holder.component.HolderComponentTestModEntrypoint" + ] + } +} diff --git a/gradle.properties b/gradle.properties index 5184f407fa7..cb6128f9cbb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,7 @@ fabric-entity-events-v1-version=5.0.2 fabric-events-interaction-v0-version=5.1.10 fabric-game-rule-api-v1-version=4.0.5 fabric-gametest-api-v1-version=4.0.13 +fabric-holder-component-api-v1-version=1.0.0 fabric-item-api-v1-version=14.1.0 fabric-creative-tab-api-v1-version=5.0.10 fabric-key-mapping-api-v1-version=2.0.4 diff --git a/settings.gradle b/settings.gradle index 8e249715bc4..50ea4befc47 100644 --- a/settings.gradle +++ b/settings.gradle @@ -43,6 +43,7 @@ include 'fabric-entity-events-v1' include 'fabric-events-interaction-v0' include 'fabric-game-rule-api-v1' include 'fabric-gametest-api-v1' +include 'fabric-holder-component-api-v1' include 'fabric-item-api-v1' include 'fabric-key-mapping-api-v1' include 'fabric-lifecycle-events-v1' From 43505e238dd889da75a3fc19a6d8f03edb743c62 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 4 May 2026 17:20:26 +1000 Subject: [PATCH 02/27] fix: redo data loading to support namespaces yes i forgot namespaces, i was copying the layout from the registrycomponentreport but it was flawed --- .../component/DataHolderComponentFile.java | 27 +++++++++ .../DataHolderComponentInitializer.java | 56 ++++++++++++------- .../components/block/acacia_slab.json | 3 - .../components/block/custom_name.json | 5 ++ 4 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java delete mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json create mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java new file mode 100644 index 00000000000..316e63581b8 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java @@ -0,0 +1,27 @@ +package net.fabricmc.fabric.impl.holder.component; + +import com.mojang.serialization.Codec; + +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.resources.ResourceKey; + +import java.util.Map; + +// TODO: Make public api and use for datagen +public record DataHolderComponentFile( + boolean replace, + Map, C> components +) { + public static Codec> codec(ResourceKey> registryKey, DataComponentType componentType) { + return RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.optionalFieldOf("replace", false).forGetter(DataHolderComponentFile::replace), + Codec.unboundedMap( + ResourceKey.codec(registryKey), + componentType.codecOrThrow() + ).fieldOf("components").forGetter(DataHolderComponentFile::components) + ).apply(instance, DataHolderComponentFile::new)); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java index d8ff0ad86f0..932ac33591b 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java @@ -2,13 +2,17 @@ import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; +import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.Identifier; @@ -22,47 +26,61 @@ import java.io.Reader; import java.util.List; import java.util.Map; +import java.util.Optional; public class DataHolderComponentInitializer implements FabricDataComponentInitializer { private static final Logger LOGGER = LogUtils.getLogger(); @Override public void run(Context context) { - context.lookupProvider().listRegistryKeys().forEach(key -> parse(context, key)); - } + RegistryOps ops = context.lookupProvider().createSerializationContext(JsonOps.INSTANCE); - // TODO: Less jank way of doing this? Not sure if its possible - @SuppressWarnings("unchecked") - private static ResourceKey createResourceKey(ResourceKey> registry, Identifier path) { - return ResourceKey.create((ResourceKey>) registry, path); + context.lookupProvider().listRegistryKeys().forEach(key -> parse(context, ops, key)); } private void parse( Context context, - ResourceKey> key + RegistryOps ops, + ResourceKey> key1 ) { + // TODO: less cursed way of doing this? + @SuppressWarnings("unchecked") ResourceKey> key = (ResourceKey>) key1; HolderLookup.RegistryLookup lookup = context.lookupProvider().lookupOrThrow(key); FileToIdConverter lister = FileToIdConverter.json(Registries.componentsDirPath(lookup.key())); - RegistryOps ops = context.lookupProvider().createSerializationContext(JsonOps.INSTANCE); for (Map.Entry> entry : lister.listMatchingResourceStacks(context.resourceManager()).entrySet()) { Identifier location = entry.getKey(); Identifier id = lister.fileToId(location); - lookup.get(createResourceKey(key, id)).ifPresent(holder -> { - DataComponentMap.Builder builder = context.builder(holder.key()); + BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(id).ifPresent(componentType -> { + parse(context, ops, entry.getValue(), key, componentType, location, id); + }); + } + } - for (Resource resource : entry.getValue()) { - try (Reader reader = resource.openAsReader()) { - JsonElement element = StrictJsonParser.parse(reader); - DataComponentMap map = DataComponentMap.CODEC.parse(ops, element).getOrThrow(); - builder.addAll(map); - } catch (Exception e) { - LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); - } + private void parse( + Context context, + RegistryOps ops, + List resources, + ResourceKey> registryKey, + DataComponentType componentType, + Identifier location, + Identifier id + ) { + + for (Resource resource : resources) { + try (Reader reader = resource.openAsReader()) { + JsonElement element = StrictJsonParser.parse(reader); + DataHolderComponentFile file = DataHolderComponentFile.codec(registryKey, componentType).parse(ops, element).getOrThrow(); + + for (Map.Entry, C> entry : file.components().entrySet()) { + var builder = context.builder(entry.getKey()); + builder.set(componentType, entry.getValue()); } - }); + } catch (Exception e) { + LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); + } } } } diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json deleted file mode 100644 index d1bcd3c9c51..00000000000 --- a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "minecraft:custom_name": "Hello, world!" -} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json new file mode 100644 index 00000000000..51146c97ced --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json @@ -0,0 +1,5 @@ +{ + "components": { + "minecraft:acacia_slab": "Hello, world!" + } +} From 654885c9da5f96af3ec5e081f40a0b005092aba3 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 4 May 2026 17:25:23 +1000 Subject: [PATCH 03/27] fix: add namespace to componentsdir --- .../fabric/mixin/registry/sync/RegistriesMixin.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistriesMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistriesMixin.java index 9bc5bf5c407..df8402a1d1d 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistriesMixin.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistriesMixin.java @@ -51,4 +51,15 @@ private static String prependTagDirectoryWithNamespace(String original, @Local(a return original; } + + @ModifyReturnValue(method = "componentsDirPath", at = @At("RETURN")) + private static String prependComponentDirectoryWithNamespace(String original, @Local(argsOnly = true) ResourceKey> registryRef) { + Identifier id = registryRef.identifier(); + + if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) { + return "components/" + id.getNamespace() + "/" + id.getPath(); + } + + return original; + } } From b479c207d5478b3c8e586aa73ff1abee9ecd795d Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 4 May 2026 17:56:56 +1000 Subject: [PATCH 04/27] chore: checkstyle and spotless --- .../FabricDataComponentInitializer.java | 20 ---------- .../FabricDataComponentInitializers.java | 12 ------ .../v1/FabricDataComponentInitializer.java | 35 +++++++++++++++++ .../v1/FabricDataComponentInitializers.java | 28 +++++++++++++ .../component/DataHolderComponentFile.java | 21 ++++++++-- .../DataHolderComponentInitializer.java | 37 ++++++++++++------ .../FabricDataComponentInitContextImpl.java | 20 +++++++++- .../FabricDataComponentInitializersImpl.java | 22 +++++++++-- .../component/HolderComponentEntrypoint.java | 18 ++++++++- .../DataComponentInitializersMixin.java | 38 ++++++++++++------ .../ReloadableServerResourcesMixin.java | 23 +++++++++-- .../component/HolderComponentCommand.java | 39 +++++++++++++------ .../HolderComponentTestModEntrypoint.java | 17 +++++++- 13 files changed, 248 insertions(+), 82 deletions(-) delete mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java delete mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java deleted file mode 100644 index 373d063aa8b..00000000000 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializer.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.fabricmc.fabric.api.holder.component; - -import net.minecraft.core.HolderLookup; -import net.minecraft.core.component.DataComponentInitializers; -import net.minecraft.core.component.DataComponentMap; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.packs.resources.ResourceManager; - -/// Extended version of [DataComponentInitializers.Initializer] that allows adding components to arbitrary holders instead of having to define what holders are to be given components at registration. -@FunctionalInterface -public interface FabricDataComponentInitializer { - void run(Context context); - - interface Context { - HolderLookup.Provider lookupProvider(); - ResourceManager resourceManager(); - - DataComponentMap.Builder builder(ResourceKey key); - } -} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java deleted file mode 100644 index 3a2c0ec3039..00000000000 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/FabricDataComponentInitializers.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.fabricmc.fabric.api.holder.component; - -import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; - -public final class FabricDataComponentInitializers { - private FabricDataComponentInitializers() { - } - - public static void register(FabricDataComponentInitializer initializer) { - FabricDataComponentInitializersImpl.INITIALIZERS.add(initializer); - } -} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java new file mode 100644 index 00000000000..ba6161c8928 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.holder.component.v1; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceManager; + +/// Extended version of [DataComponentInitializers.Initializer] that allows adding components to arbitrary holders instead of having to define what holders are to be given components at registration. +@FunctionalInterface +public interface FabricDataComponentInitializer { + void run(Context context); + + interface Context { + HolderLookup.Provider lookupProvider(); + ResourceManager resourceManager(); + + DataComponentMap.Builder builder(ResourceKey key); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java new file mode 100644 index 00000000000..ab0617a8780 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.holder.component.v1; + +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + +public final class FabricDataComponentInitializers { + private FabricDataComponentInitializers() { + } + + public static void register(FabricDataComponentInitializer initializer) { + FabricDataComponentInitializersImpl.INITIALIZERS.add(initializer); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java index 316e63581b8..a24716ea139 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java @@ -1,15 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.impl.holder.component; -import com.mojang.serialization.Codec; +import java.util.Map; +import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentType; import net.minecraft.resources.ResourceKey; -import java.util.Map; - // TODO: Make public api and use for datagen public record DataHolderComponentFile( boolean replace, diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java index 932ac33591b..0d9e808c024 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java @@ -1,13 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.impl.holder.component; +import java.io.Reader; +import java.util.List; +import java.util.Map; + import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; -import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; +import org.slf4j.Logger; -import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; - -import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentMap; @@ -21,12 +38,7 @@ import net.minecraft.server.packs.resources.Resource; import net.minecraft.util.StrictJsonParser; -import org.slf4j.Logger; - -import java.io.Reader; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; public class DataHolderComponentInitializer implements FabricDataComponentInitializer { private static final Logger LOGGER = LogUtils.getLogger(); @@ -68,14 +80,15 @@ private void parse( Identifier location, Identifier id ) { - for (Resource resource : resources) { try (Reader reader = resource.openAsReader()) { JsonElement element = StrictJsonParser.parse(reader); + + // TODO: Implement replace, also add required DataHolderComponentFile file = DataHolderComponentFile.codec(registryKey, componentType).parse(ops, element).getOrThrow(); for (Map.Entry, C> entry : file.components().entrySet()) { - var builder = context.builder(entry.getKey()); + DataComponentMap.Builder builder = context.builder(entry.getKey()); builder.set(componentType, entry.getValue()); } } catch (Exception e) { diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java index 2feb9862ffb..752e0616468 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.java @@ -1,13 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.impl.holder.component; -import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; +import java.util.Map; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentMap; import net.minecraft.resources.ResourceKey; import net.minecraft.server.packs.resources.ResourceManager; -import java.util.Map; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; public record FabricDataComponentInitContextImpl( HolderLookup.Provider lookupProvider, diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java index 18bc60bc822..953d146cc1c 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java @@ -1,11 +1,27 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.impl.holder.component; -import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; +import java.util.ArrayList; +import java.util.List; import net.minecraft.server.packs.resources.ResourceManager; -import java.util.ArrayList; -import java.util.List; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; public class FabricDataComponentInitializersImpl { public static final List INITIALIZERS = new ArrayList<>(); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index b6bafdf43d0..f0016c6e561 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -1,7 +1,23 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.impl.holder.component; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializers; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; public class HolderComponentEntrypoint implements ModInitializer { @Override diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java index b70fe4d5d2a..eb1cef03fe5 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java @@ -1,13 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component; +import java.util.Map; + import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.logging.LogUtils; - -import net.fabricmc.fabric.api.holder.component.FabricDataComponentInitializer; -import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitContextImpl; - -import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentInitializers; @@ -15,12 +32,9 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.packs.resources.ResourceManager; -import org.slf4j.Logger; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; - -import java.util.Map; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitContextImpl; +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; @Mixin(DataComponentInitializers.class) public class DataComponentInitializersMixin { @@ -30,7 +44,7 @@ public class DataComponentInitializersMixin { @ModifyReturnValue(method = "runInitializers", at = @At("RETURN")) private Map, DataComponentMap.Builder> runFabricInitializers( Map, DataComponentMap.Builder> original, - @Local(name = "context", argsOnly = true) HolderLookup.Provider holders + @Local(argsOnly = true) HolderLookup.Provider holders ) { if (!FabricDataComponentInitializersImpl.RESOURCE_MANAGER.isBound()) { // we got called either by the report or by a mod diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java index d9e79464810..e7f518090c2 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component; import java.util.concurrent.CompletableFuture; @@ -7,15 +23,14 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; - -import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; - import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import net.minecraft.server.ReloadableServerResources; import net.minecraft.server.packs.resources.ResourceManager; +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + @Mixin(ReloadableServerResources.class) public class ReloadableServerResourcesMixin { @WrapOperation(method = "lambda$loadResources$0", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) @@ -23,7 +38,7 @@ private static CompletableFuture wrapWithScopedValue( Supplier supplier, Executor executor, Operation> original, - @Local(argsOnly = true, name = "resourceManager") ResourceManager resourceManager + @Local(argsOnly = true) ResourceManager resourceManager ) { Supplier wrappedSupplier = () -> ScopedValue.where( FabricDataComponentInitializersImpl.RESOURCE_MANAGER, diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java index c871154540c..adde0ea3dd0 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java @@ -1,5 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.test.holder.component; +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.context.CommandContext; @@ -29,15 +53,6 @@ import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - public class HolderComponentCommand { private static final DynamicCommandExceptionType INVALID_REGISTRY = new DynamicCommandExceptionType( value -> Component.literal("Invalid Registry '" + value + "'") @@ -121,9 +136,10 @@ private static int dump(CommandContext context, DataComponen for (Holder holder : registry.asHolderIdMap()) { holder.unwrapKey().ifPresent( key -> { - var val = holder.components().get(component); + Object val = holder.components().get(component); + if (val != null) { - var builder = builders.computeIfAbsent(key.identifier(), _ -> DataComponentPatch.builder()); + DataComponentPatch.Builder builder = builders.computeIfAbsent(key.identifier(), _ -> DataComponentPatch.builder()); builder.set(holder.components()); } @@ -142,7 +158,6 @@ private static int dump(CommandContext context, DataComponen return Command.SINGLE_SUCCESS; } - private static int get(CommandContext context, ResourceKey> registryKey, Identifier holderId) { RegistryAccess registryManager = context.getSource().registryAccess(); Registry registry = registryManager.lookupOrThrow(registryKey); diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java index c5343ad2196..cd9dd8181eb 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -1,5 +1,20 @@ -package net.fabricmc.fabric.test.holder.component; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.fabricmc.fabric.test.holder.component; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; From 3baf6dca8dc30b5d54fbe56767d3efd6923dc864 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 11:13:21 +1000 Subject: [PATCH 05/27] feat: Initializer toposort --- .../v1/FabricDataComponentInitializers.java | 13 ++- .../FabricDataComponentInitializersImpl.java | 85 ++++++++++++++++++- .../component/HolderComponentEntrypoint.java | 5 +- .../DataComponentInitializersMixin.java | 2 +- .../HolderComponentTestModEntrypoint.java | 20 +++++ 5 files changed, 120 insertions(+), 5 deletions(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java index ab0617a8780..810c9fef745 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java @@ -18,11 +18,20 @@ import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; +import net.minecraft.resources.Identifier; + public final class FabricDataComponentInitializers { + /// @see FabricDataComponentInitializers#addInitializerOrdering(Identifier, Identifier) + public static final Identifier DATA_HOLDER_COMPONENTS = Identifier.fromNamespaceAndPath("fabric", "data_holder_components"); + private FabricDataComponentInitializers() { } - public static void register(FabricDataComponentInitializer initializer) { - FabricDataComponentInitializersImpl.INITIALIZERS.add(initializer); + public static void registerInitializer(Identifier id, FabricDataComponentInitializer initializer) { + FabricDataComponentInitializersImpl.registerInitializer(id, initializer); + } + + public static void addInitializerOrdering(Identifier first, Identifier second) { + FabricDataComponentInitializersImpl.addInitializerOrdering(first, second); } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java index 953d146cc1c..4c812f967ed 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java @@ -17,14 +17,97 @@ package net.fabricmc.fabric.impl.holder.component; import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import net.fabricmc.fabric.impl.base.toposort.NodeSorting; +import net.fabricmc.fabric.impl.base.toposort.SortableNode; + +import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.ResourceManager; import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; +import org.jspecify.annotations.Nullable; + public class FabricDataComponentInitializersImpl { - public static final List INITIALIZERS = new ArrayList<>(); + private static final Map initializers = new LinkedHashMap<>(); + private static final List sorted = new ArrayList<>(); + private static boolean dirty = false; + + public static void registerInitializer(Identifier id, FabricDataComponentInitializer initializer) { + Objects.requireNonNull(id, "The initializer identifier should not be null."); + Objects.requireNonNull(initializer, "The initializer should not be null."); + + if (initializers.containsKey(id)) { + throw new IllegalStateException( + "Tried to register initializer %s twice!".formatted(id) + ); + } + + for (Map.Entry entry : initializers.entrySet()) { + if (entry.getValue().initializer == initializer) { + throw new IllegalStateException( + "Initializer with ID %s already registered with ID %s!" + .formatted(id, entry.getKey()) + ); + } + } + + initializers.put(id, new PhaseData(id, initializer)); + + dirty = true; + } + + public static void addInitializerOrdering(Identifier first, Identifier second) { + Objects.requireNonNull(first, "The first initializer identifier should not be null."); + Objects.requireNonNull(second, "The second initializer identifier should not be null."); + + if (first.equals(second)) { + throw new IllegalArgumentException("Tried to add a phase that depends on itself."); + } + + PhaseData firstInitializer = initializers.get(first); + PhaseData secondInitializer = initializers.get(second); + + PhaseData.link(firstInitializer, secondInitializer); + + dirty = true; + } + + public static List sort() { + if (!dirty) return sorted; + + ArrayList phases = new ArrayList<>(initializers.values()); + NodeSorting.sort(phases, "data component initializer phases", Comparator.comparing(data -> data.id)); + + sorted.clear(); + + for (PhaseData phase : phases) { + sorted.add(phase.initializer); + } + + return sorted; + } + + private static class PhaseData extends SortableNode { + final Identifier id; + FabricDataComponentInitializer initializer; + + PhaseData(Identifier id, @Nullable FabricDataComponentInitializer initializer) { + super(); + this.id = id; + this.initializer = initializer; + } + + @Override + protected String getDescription() { + return this.id.toString(); + } + } public static final ScopedValue RESOURCE_MANAGER = ScopedValue.newInstance(); } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index f0016c6e561..cd24ff6fe88 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -22,6 +22,9 @@ public class HolderComponentEntrypoint implements ModInitializer { @Override public void onInitialize() { - FabricDataComponentInitializers.register(new DataHolderComponentInitializer()); + FabricDataComponentInitializers.registerInitializer( + FabricDataComponentInitializers.DATA_HOLDER_COMPONENTS, + new DataHolderComponentInitializer() + ); } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java index eb1cef03fe5..f3759403d90 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java @@ -63,7 +63,7 @@ private Map, DataComponentMap.Builder> runFabricInitializers( original ); - for (FabricDataComponentInitializer initializer : FabricDataComponentInitializersImpl.INITIALIZERS) { + for (FabricDataComponentInitializer initializer : FabricDataComponentInitializersImpl.sort()) { initializer.run(context); } diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java index cd9dd8181eb..5635cf579bb 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -18,12 +18,32 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; + +import net.minecraft.resources.Identifier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HolderComponentTestModEntrypoint implements ModInitializer { + public static final String MODID = "fabric-holder-component-api-v1-testmod"; + private static final Logger LOGGER = LoggerFactory.getLogger(MODID); + @Override public void onInitialize() { CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, selection) -> { HolderComponentCommand.register(dispatcher, buildContext); }); + + final Identifier first = Identifier.fromNamespaceAndPath("fabric", "first"); + final Identifier second = Identifier.fromNamespaceAndPath("fabric", "second"); + final Identifier third = Identifier.fromNamespaceAndPath("fabric", "third"); + + FabricDataComponentInitializers.registerInitializer(third, _ -> LOGGER.info("I should be third!")); + FabricDataComponentInitializers.registerInitializer(first, _ -> LOGGER.info("I should be first!")); + FabricDataComponentInitializers.registerInitializer(second, _ -> LOGGER.info("I should be second!")); + + FabricDataComponentInitializers.addInitializerOrdering(first, second); + FabricDataComponentInitializers.addInitializerOrdering(second, third); } } From 92858e45db06025949c671fddeee5fe0d8378877 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 11:20:56 +1000 Subject: [PATCH 06/27] chore: spotless --- .../component/v1/FabricDataComponentInitializers.java | 4 ++-- .../component/FabricDataComponentInitializersImpl.java | 7 +++---- .../holder/component/HolderComponentEntrypoint.java | 1 + .../component/{ => data}/DataHolderComponentFile.java | 2 +- .../{ => data}/DataHolderComponentInitializer.java | 2 +- .../component/HolderComponentTestModEntrypoint.java | 10 +++++----- 6 files changed, 13 insertions(+), 13 deletions(-) rename fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/{ => data}/DataHolderComponentFile.java (96%) rename fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/{ => data}/DataHolderComponentInitializer.java (98%) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java index 810c9fef745..a7ebddbaf5e 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java @@ -16,10 +16,10 @@ package net.fabricmc.fabric.api.holder.component.v1; -import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; - import net.minecraft.resources.Identifier; +import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; + public final class FabricDataComponentInitializers { /// @see FabricDataComponentInitializers#addInitializerOrdering(Identifier, Identifier) public static final Identifier DATA_HOLDER_COMPONENTS = Identifier.fromNamespaceAndPath("fabric", "data_holder_components"); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java index 4c812f967ed..7cad86e53b6 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java @@ -23,15 +23,14 @@ import java.util.Map; import java.util.Objects; -import net.fabricmc.fabric.impl.base.toposort.NodeSorting; -import net.fabricmc.fabric.impl.base.toposort.SortableNode; +import org.jspecify.annotations.Nullable; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.ResourceManager; import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; - -import org.jspecify.annotations.Nullable; +import net.fabricmc.fabric.impl.base.toposort.NodeSorting; +import net.fabricmc.fabric.impl.base.toposort.SortableNode; public class FabricDataComponentInitializersImpl { private static final Map initializers = new LinkedHashMap<>(); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index cd24ff6fe88..ab20707b1bb 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -18,6 +18,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; +import net.fabricmc.fabric.impl.holder.component.data.DataHolderComponentInitializer; public class HolderComponentEntrypoint implements ModInitializer { @Override diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java similarity index 96% rename from fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java rename to fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index a24716ea139..c36486a8b3d 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.holder.component; +package net.fabricmc.fabric.impl.holder.component.data; import java.util.Map; diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java similarity index 98% rename from fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java rename to fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index 0d9e808c024..d86aa19c336 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.fabricmc.fabric.impl.holder.component; +package net.fabricmc.fabric.impl.holder.component.data; import java.io.Reader; import java.util.List; diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java index 5635cf579bb..90c7a0483c6 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -16,14 +16,14 @@ package net.fabricmc.fabric.test.holder.component; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.minecraft.resources.Identifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; public class HolderComponentTestModEntrypoint implements ModInitializer { public static final String MODID = "fabric-holder-component-api-v1-testmod"; From eccc337384f5ad5df12a53340f9910a502a998fb Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 11:33:51 +1000 Subject: [PATCH 07/27] invert format --- .../data/DataHolderComponentFile.java | 18 ++++++--------- .../data/DataHolderComponentInitializer.java | 22 +++++++++---------- .../components/block/acacia_slab.json | 5 +++++ .../components/block/custom_name.json | 5 ----- 4 files changed, 22 insertions(+), 28 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json delete mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index c36486a8b3d..3e3bc3e4e51 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -22,21 +22,17 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentType; import net.minecraft.resources.ResourceKey; // TODO: Make public api and use for datagen -public record DataHolderComponentFile( +public record DataHolderComponentFile( boolean replace, - Map, C> components + DataComponentMap components ) { - public static Codec> codec(ResourceKey> registryKey, DataComponentType componentType) { - return RecordCodecBuilder.create(instance -> instance.group( - Codec.BOOL.optionalFieldOf("replace", false).forGetter(DataHolderComponentFile::replace), - Codec.unboundedMap( - ResourceKey.codec(registryKey), - componentType.codecOrThrow() - ).fieldOf("components").forGetter(DataHolderComponentFile::components) - ).apply(instance, DataHolderComponentFile::new)); - } + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.optionalFieldOf("replace", false).forGetter(DataHolderComponentFile::replace), + DataComponentMap.CODEC.fieldOf("components").forGetter(DataHolderComponentFile::components) + ).apply(instance, DataHolderComponentFile::new)); } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index d86aa19c336..a92957ca3c1 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -23,13 +23,14 @@ import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; import com.mojang.serialization.JsonOps; + +import net.minecraft.core.Holder; + import org.slf4j.Logger; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentMap; -import net.minecraft.core.component.DataComponentType; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.Identifier; @@ -65,18 +66,17 @@ private void parse( Identifier location = entry.getKey(); Identifier id = lister.fileToId(location); - BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(id).ifPresent(componentType -> { - parse(context, ops, entry.getValue(), key, componentType, location, id); + lookup.get(ResourceKey.create(key, id)).ifPresent(holder -> { + parse(context, ops, entry.getValue(), holder, location, id); }); } } - private void parse( + private void parse( Context context, RegistryOps ops, List resources, - ResourceKey> registryKey, - DataComponentType componentType, + Holder.Reference holder, Identifier location, Identifier id ) { @@ -85,12 +85,10 @@ private void parse( JsonElement element = StrictJsonParser.parse(reader); // TODO: Implement replace, also add required - DataHolderComponentFile file = DataHolderComponentFile.codec(registryKey, componentType).parse(ops, element).getOrThrow(); + DataHolderComponentFile file = DataHolderComponentFile.CODEC.parse(ops, element).getOrThrow(); - for (Map.Entry, C> entry : file.components().entrySet()) { - DataComponentMap.Builder builder = context.builder(entry.getKey()); - builder.set(componentType, entry.getValue()); - } + DataComponentMap.Builder builder = context.builder(holder.key()); + builder.addAll(file.components()); } catch (Exception e) { LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); } diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json new file mode 100644 index 00000000000..148661000f1 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json @@ -0,0 +1,5 @@ +{ + "components": { + "minecraft:custom_name": "Hello, world!" + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json deleted file mode 100644 index 51146c97ced..00000000000 --- a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/custom_name.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "components": { - "minecraft:acacia_slab": "Hello, world!" - } -} From 32273e1a6e2dc6b5ffe5016bb5a5f442a96d8f12 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 13:31:44 +1000 Subject: [PATCH 08/27] Fix import in javadoc for `FabricDataComponentInitializer` --- .../api/holder/component/v1/FabricDataComponentInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java index ba6161c8928..0f0fb04ecfd 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializer.java @@ -21,7 +21,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.packs.resources.ResourceManager; -/// Extended version of [DataComponentInitializers.Initializer] that allows adding components to arbitrary holders instead of having to define what holders are to be given components at registration. +/// Extended version of [net.minecraft.core.component.DataComponentInitializers.Initializer] that allows adding components to arbitrary holders instead of having to define what holders are to be given components at registration. @FunctionalInterface public interface FabricDataComponentInitializer { void run(Context context); From 41c46f56c827a0c5f7554c0b688b490575d19903 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 13:50:19 +1000 Subject: [PATCH 09/27] Extend some objects with `builtInRegistryHolder`s to support getting components --- fabric-holder-component-api-v1/build.gradle | 4 +++ .../extension/BlockEntityTypeMixin.java | 25 +++++++++++++++++++ .../component/extension/BlockMixin.java | 25 +++++++++++++++++++ .../component/extension/EntityTypeMixin.java | 25 +++++++++++++++++++ .../component/extension/FluidMixin.java | 25 +++++++++++++++++++ .../component/extension/HolderMixin.java | 21 ++++++++++++++++ .../holder/component/extension/ItemMixin.java | 25 +++++++++++++++++++ ...abric-holder-component-api-v1.classtweaker | 8 ++++++ ...fabric-holder-component-api-v1.mixins.json | 6 +++++ .../src/main/resources/fabric.mod.json | 1 + 10 files changed, 165 insertions(+) create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker diff --git a/fabric-holder-component-api-v1/build.gradle b/fabric-holder-component-api-v1/build.gradle index 0bd9ff8a6da..2113fa61707 100644 --- a/fabric-holder-component-api-v1/build.gradle +++ b/fabric-holder-component-api-v1/build.gradle @@ -1,5 +1,9 @@ version = getSubprojectVersion(project) +loom { + accessWidenerPath = file("src/main/resources/fabric-holder-component-api-v1.classtweaker") +} + moduleDependencies(project, [ 'fabric-api-base' ]) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java new file mode 100644 index 00000000000..785f153c353 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.world.level.block.entity.BlockEntityType; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(BlockEntityType.class) +public class BlockEntityTypeMixin implements DataComponentHolder { + @Shadow + @Final + private Holder.Reference> builtInRegistryHolder; + + @Override + public DataComponentMap getComponents() { + return builtInRegistryHolder.components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java new file mode 100644 index 00000000000..1038c166036 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.world.level.block.Block; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Block.class) +public class BlockMixin implements DataComponentHolder { + @Shadow + @Final + private Holder.Reference builtInRegistryHolder; + + @Override + public DataComponentMap getComponents() { + return builtInRegistryHolder.components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java new file mode 100644 index 00000000000..83b2060cbf6 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.world.entity.EntityType; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(EntityType.class) +public class EntityTypeMixin implements DataComponentHolder { + @Shadow + @Final + private Holder.Reference> builtInRegistryHolder; + + @Override + public DataComponentMap getComponents() { + return builtInRegistryHolder.components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java new file mode 100644 index 00000000000..679957318bb --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.world.level.material.Fluid; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Fluid.class) +public class FluidMixin implements DataComponentHolder { + @Shadow + @Final + private Holder.Reference builtInRegistryHolder; + + @Override + public DataComponentMap getComponents() { + return builtInRegistryHolder.components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java new file mode 100644 index 00000000000..59a3a805523 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java @@ -0,0 +1,21 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Holder.class) +public interface HolderMixin extends DataComponentHolder { + @Shadow + DataComponentMap components(); + + @Override + default DataComponentMap getComponents() { + return components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java new file mode 100644 index 00000000000..a8e2fce429b --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.minecraft.core.Holder; + +import net.minecraft.core.component.DataComponentHolder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.world.item.Item; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Item.class) +public class ItemMixin implements DataComponentHolder { + @Shadow + @Final + private Holder.Reference builtInRegistryHolder; + + @Override + public DataComponentMap getComponents() { + return builtInRegistryHolder.components(); + } +} diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker new file mode 100644 index 00000000000..1320641ed18 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker @@ -0,0 +1,8 @@ +classTweaker v1 official +transitive-inject-interface net/minecraft/world/level/block/entity/BlockEntityType net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/world/level/block/Block net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/world/entity/EntityType net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/world/level/material/Fluid net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/core/Holder net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/world/item/Item net/minecraft/core/component/DataComponentHolder + diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json index 4055ccb33b5..6f9035beaa1 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json @@ -3,7 +3,13 @@ "package": "net.fabricmc.fabric.mixin.holder.component", "compatibilityLevel": "JAVA_25", "mixins": [ + "extension.BlockEntityTypeMixin", + "extension.BlockMixin", "DataComponentInitializersMixin", + "extension.EntityTypeMixin", + "extension.FluidMixin", + "extension.HolderMixin", + "extension.ItemMixin", "ReloadableServerResourcesMixin" ], "injectors": { diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json index 15c6a89879a..0ed7dad88e7 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json @@ -27,6 +27,7 @@ "net.fabricmc.fabric.impl.holder.component.HolderComponentEntrypoint" ] }, + "accessWidener": "fabric-holder-component-api-v1.classtweaker", "custom": { "fabric-api:module-lifecycle": "experimental" } From 069c35004111162f1cb02e349ce545683e4dbae0 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 15:15:54 +1000 Subject: [PATCH 10/27] Init gametests --- .../data/DataHolderComponentFile.java | 4 -- .../data/DataHolderComponentInitializer.java | 4 +- .../extension/BlockEntityTypeMixin.java | 27 ++++++++++---- .../component/extension/BlockMixin.java | 27 ++++++++++---- .../component/extension/EntityTypeMixin.java | 27 ++++++++++---- .../component/extension/FluidMixin.java | 27 ++++++++++---- .../component/extension/HolderMixin.java | 24 +++++++++--- .../holder/component/extension/ItemMixin.java | 27 ++++++++++---- .../component/HolderComponentGameTest.java | 37 +++++++++++++++++++ .../src/testmod/resources/fabric.mod.json | 3 ++ 10 files changed, 160 insertions(+), 47 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentGameTest.java diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index 3e3bc3e4e51..0791916ca8e 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -16,15 +16,11 @@ package net.fabricmc.fabric.impl.holder.component.data; -import java.util.Map; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentMap; -import net.minecraft.core.component.DataComponentType; -import net.minecraft.resources.ResourceKey; // TODO: Make public api and use for datagen public record DataHolderComponentFile( diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index a92957ca3c1..804e19a016f 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -23,11 +23,9 @@ import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; import com.mojang.serialization.JsonOps; - -import net.minecraft.core.Holder; - import org.slf4j.Logger; +import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentMap; diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java index 785f153c353..39e20bef1f2 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java @@ -1,17 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; - import net.minecraft.world.level.block.entity.BlockEntityType; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(BlockEntityType.class) public class BlockEntityTypeMixin implements DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java index 1038c166036..56473f72947 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java @@ -1,17 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; - import net.minecraft.world.level.block.Block; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(Block.class) public class BlockMixin implements DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java index 83b2060cbf6..318c5b07713 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java @@ -1,17 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; - import net.minecraft.world.entity.EntityType; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(EntityType.class) public class EntityTypeMixin implements DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java index 679957318bb..0a47739377f 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java @@ -1,17 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; - import net.minecraft.world.level.material.Fluid; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(Fluid.class) public class FluidMixin implements DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java index 59a3a805523..3828242f7e6 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.java @@ -1,14 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(Holder.class) public interface HolderMixin extends DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java index a8e2fce429b..a14626f6292 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java @@ -1,17 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.Holder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentHolder; - import net.minecraft.core.component.DataComponentMap; - import net.minecraft.world.item.Item; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - @Mixin(Item.class) public class ItemMixin implements DataComponentHolder { @Shadow diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentGameTest.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentGameTest.java new file mode 100644 index 00000000000..aea3e48343c --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentGameTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.holder.component; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.Blocks; + +import net.fabricmc.fabric.api.gametest.v1.GameTest; + +public class HolderComponentGameTest { + @GameTest + public void testBasicUsage(GameTestHelper helper) { + helper.assertValueEqual( + Blocks.ACACIA_SLAB.get(DataComponents.CUSTOM_NAME), + Component.literal("Hello, world!"), + "custom_name_component" + ); + + helper.succeed(); + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json b/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json index 5ed59b14441..f1e754cdb44 100644 --- a/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json @@ -8,6 +8,9 @@ "entrypoints": { "main" : [ "net.fabricmc.fabric.test.holder.component.HolderComponentTestModEntrypoint" + ], + "fabric-gametest": [ + "net.fabricmc.fabric.test.holder.component.HolderComponentGameTest" ] } } From 5f42df0094baf8754452fbddd63f84304ae39561 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 5 May 2026 15:26:13 +1000 Subject: [PATCH 11/27] Add `@ApiStatus.Experimental` to api package --- .../api/holder/component/v1/package-info.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java new file mode 100644 index 00000000000..3a0617b3ee1 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * API code for fabric-holder-component-api-v1. + */ +@NullMarked +@ApiStatus.Experimental +package net.fabricmc.fabric.api.holder.component.v1; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; From 42f30ff8006357468d59909b7c649ad842383ef4 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Wed, 6 May 2026 13:35:00 +1000 Subject: [PATCH 12/27] checkstyle --- .../impl/holder/component/data/DataHolderComponentFile.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index 0791916ca8e..938aaf2177d 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -16,7 +16,6 @@ package net.fabricmc.fabric.impl.holder.component.data; - import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; From 113bc0bfc542e9015e59081eebc04130550d413a Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 10 May 2026 11:47:22 +1000 Subject: [PATCH 13/27] Synchronization (VERY wip) --- fabric-holder-component-api-v1/build.gradle | 3 +- .../FabricRegistryDataCollector.java | 25 ++++ .../HolderComponentClientEntrypoint.java | 60 ++++++++ ...nfigurationPacketListenerImplAccessor.java | 29 ++++ .../component/RegistryDataCollectorMixin.java | 81 ++++++++++ ...holder-component-api-v1.client.mixins.json | 17 +++ .../component/HolderComponentEntrypoint.java | 18 +++ .../data/DataHolderComponentFile.java | 1 + .../ClientboundUpdateComponentsPayload.java | 72 +++++++++ .../DataComponentNetworkSerialization.java | 138 ++++++++++++++++++ .../holder/component/PlayerListMixin.java | 51 +++++++ .../SynchronizeRegistriesTaskMixin.java | 59 ++++++++ ...fabric-holder-component-api-v1.mixins.json | 8 +- .../src/main/resources/fabric.mod.json | 9 +- 14 files changed, 566 insertions(+), 5 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/FabricRegistryDataCollector.java create mode 100644 fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java create mode 100644 fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/ClientConfigurationPacketListenerImplAccessor.java create mode 100644 fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java create mode 100644 fabric-holder-component-api-v1/src/client/resources/fabric-holder-component-api-v1.client.mixins.json create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java diff --git a/fabric-holder-component-api-v1/build.gradle b/fabric-holder-component-api-v1/build.gradle index 2113fa61707..cc4c88be687 100644 --- a/fabric-holder-component-api-v1/build.gradle +++ b/fabric-holder-component-api-v1/build.gradle @@ -5,7 +5,8 @@ loom { } moduleDependencies(project, [ - 'fabric-api-base' + 'fabric-api-base', + 'fabric-networking-api-v1' ]) testDependencies(project, [ diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/FabricRegistryDataCollector.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/FabricRegistryDataCollector.java new file mode 100644 index 00000000000..9b45f16d271 --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/FabricRegistryDataCollector.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.holder.component; + +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; + +public interface FabricRegistryDataCollector { + default void fabric$appendComponents(ClientboundUpdateComponentsPayload payload) { + throw new IllegalStateException("Implemented via mixin."); + } +} diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java new file mode 100644 index 00000000000..fd433938c4e --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.holder.component; + +import java.util.List; + +import net.minecraft.client.multiplayer.RegistryDataCollector; +import net.minecraft.core.component.DataComponentInitializers; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.context.PacketContext; +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; +import net.fabricmc.fabric.mixin.client.holder.component.ClientConfigurationPacketListenerImplAccessor; + +public class HolderComponentClientEntrypoint implements ClientModInitializer { + @Override + public void onInitializeClient() { + ClientConfigurationNetworking.registerGlobalReceiver( + ClientboundUpdateComponentsPayload.TYPE, + (payload, context) -> { + RegistryDataCollector collector = ((ClientConfigurationPacketListenerImplAccessor) context.packetListener()).getRegistryDataCollector(); + FabricRegistryDataCollector fabricCollector = ((FabricRegistryDataCollector) collector); + + fabricCollector.fabric$appendComponents(payload); + } + ); + + ClientPlayNetworking.registerGlobalReceiver( + ClientboundUpdateComponentsPayload.TYPE, + (payload, context) -> { + List> pending = DataComponentNetworkSerialization.deserialize( + payload.registryToComponents(), + context.packetContext().orElseThrow(PacketContext.REGISTRY_ACCESS) + ); + + // if we are not on an integrated server + if (!context.packetContext().orElseThrow(PacketContext.CONNECTION).isMemoryConnection()) { + pending.forEach(DataComponentInitializers.PendingComponents::apply); + } + } + ); + } +} diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/ClientConfigurationPacketListenerImplAccessor.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/ClientConfigurationPacketListenerImplAccessor.java new file mode 100644 index 00000000000..66204d8687c --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/ClientConfigurationPacketListenerImplAccessor.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.holder.component; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.multiplayer.ClientConfigurationPacketListenerImpl; +import net.minecraft.client.multiplayer.RegistryDataCollector; + +@Mixin(ClientConfigurationPacketListenerImpl.class) +public interface ClientConfigurationPacketListenerImplAccessor { + @Accessor + RegistryDataCollector getRegistryDataCollector(); +} diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java new file mode 100644 index 00000000000..1131a178cff --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.holder.component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.llamalad7.mixinextras.sugar.Local; + +import net.minecraft.core.RegistrySynchronization; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.multiplayer.RegistryDataCollector; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponentInitializers; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceProvider; + +import net.fabricmc.fabric.impl.client.holder.component.FabricRegistryDataCollector; +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; + +@Mixin(RegistryDataCollector.class) +public class RegistryDataCollectorMixin implements FabricRegistryDataCollector { + @Unique + private final Map>, Map>> components = new HashMap<>(); + + @Override + public void fabric$appendComponents(ClientboundUpdateComponentsPayload payload) { + for (var entry : payload.registryToComponents().entrySet()) { + Map> registryMaps = + components.computeIfAbsent(entry.getKey(), _ -> new HashMap<>(entry.getValue().size())); + + entry.getValue().forEach((holder, components) -> { + registryMaps.computeIfAbsent(holder, _ -> new HashMap<>()).putAll(components); + }); + } + } + + @Inject(method = "collectGameRegistries", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/RegistryDataCollector;updateComponents(Lnet/minecraft/core/RegistryAccess$Frozen;Z)V", shift = At.Shift.AFTER)) + private void updateSyncedComponents( + ResourceProvider knownDataSource, + RegistryAccess.Frozen originalRegistries, + boolean tagsAndComponentsForSynchronizedRegistriesOnly, + CallbackInfoReturnable cir, + @Local(name = "frozenRegistries") RegistryAccess.Frozen frozenRegistries + ) { + List> pending = DataComponentNetworkSerialization.deserialize(components, frozenRegistries); + + boolean includeSharedRegistries = !tagsAndComponentsForSynchronizedRegistriesOnly; + + for (DataComponentInitializers.PendingComponents prepared : pending) { + if (includeSharedRegistries || RegistrySynchronization.isNetworkable(prepared.key())) { + prepared.apply(); + } + } + } +} diff --git a/fabric-holder-component-api-v1/src/client/resources/fabric-holder-component-api-v1.client.mixins.json b/fabric-holder-component-api-v1/src/client/resources/fabric-holder-component-api-v1.client.mixins.json new file mode 100644 index 00000000000..1a2564d8a86 --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/resources/fabric-holder-component-api-v1.client.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.client.holder.component", + "compatibilityLevel": "JAVA_25", + "client": [ + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + }, + "mixins": [ + "ClientConfigurationPacketListenerImplAccessor", + "RegistryDataCollectorMixin" + ] +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index ab20707b1bb..aee6eebadcd 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -18,14 +18,32 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializers; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.impl.holder.component.data.DataHolderComponentInitializer; +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; public class HolderComponentEntrypoint implements ModInitializer { + // TODO: This size is enormous, I copied it straight out of FabricRegistryInit. This should be smaller but I don't know how to choose a good value. + private static final int MAX_PACKET_SIZE = Integer.getInteger("fabric.holder.component.sync.max_packet_size", 128 * 1024 * 1024); + + @Override public void onInitialize() { FabricDataComponentInitializers.registerInitializer( FabricDataComponentInitializers.DATA_HOLDER_COMPONENTS, new DataHolderComponentInitializer() ); + + PayloadTypeRegistry.clientboundPlay().registerLarge( + ClientboundUpdateComponentsPayload.TYPE, + ClientboundUpdateComponentsPayload.STREAM_CODEC, + MAX_PACKET_SIZE + ); + + PayloadTypeRegistry.clientboundConfiguration().registerLarge( + ClientboundUpdateComponentsPayload.TYPE, + ClientboundUpdateComponentsPayload.STREAM_CODEC, + MAX_PACKET_SIZE + ); } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index 938aaf2177d..789638e347e 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -22,6 +22,7 @@ import net.minecraft.core.component.DataComponentMap; // TODO: Make public api and use for datagen +// TODO: Switch to DataComponentPatch for removal syntax (would require a more advanced version of DataComponentMap.Builder) public record DataHolderComponentFile( boolean replace, DataComponentMap components diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java new file mode 100644 index 00000000000..91a1f97cb3e --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.holder.component.sync; + + +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + +import net.minecraft.core.Registry; +import net.minecraft.nbt.Tag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +public record ClientboundUpdateComponentsPayload( + // component values have to be sent as nbt tags as the StreamCodecs for them require RegistryFriendlyByteBuf which we don't have during configure + // this is hopefully fine since they aren't updated often + // TODO: optimize this format. im quite sure this could be smaller (ComponentTypeId could maybe be the int id?) + // Map>> + Map>, Map>> registryToComponents +) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(Identifier.fromNamespaceAndPath("fabric", "update_holder_components")); + + // (crime against humanity in codec form) + public static final StreamCodec STREAM_CODEC = + ByteBufCodecs.>, Map>, Map>, Map>>>map( + Object2ObjectOpenHashMap::new, + StreamCodec.ofMember( + (key, buf) -> buf.writeResourceKey(key), + FriendlyByteBuf::readRegistryKey + ), + ByteBufCodecs.map( + Object2ObjectOpenHashMap::new, + Identifier.STREAM_CODEC, + ByteBufCodecs.map( + size -> size < 8 // this is straight out of the DataComponentMap.CODEC + ? new Reference2ObjectArrayMap<>(size) + : new Reference2ObjectOpenHashMap<>(size), + Identifier.STREAM_CODEC, + ByteBufCodecs.TAG + ) + ) + ).map( + ClientboundUpdateComponentsPayload::new, + ClientboundUpdateComponentsPayload::registryToComponents + ); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java new file mode 100644 index 00000000000..bede3f20e2d --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.holder.component.sync; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; + +import net.minecraft.core.Holder; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.RegistrySynchronization; +import net.minecraft.core.component.DataComponentInitializers; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.RegistryLayer; + +public class DataComponentNetworkSerialization { + public static ClientboundUpdateComponentsPayload serialize(DynamicOps ops, LayeredRegistryAccess registries) { + return new ClientboundUpdateComponentsPayload( + RegistrySynchronization.networkSafeRegistries(registries) + .collect(Collectors.toMap( + RegistryAccess.RegistryEntry::key, + entry -> serialize(ops, entry.value()) + )) + ); + } + + private static Map> serialize(DynamicOps ops, Registry registry) { + Map> result = new HashMap<>(); + + registry.listElements().forEach(holder -> { + if (holder.components().isEmpty()) return; + + Map serialized = result.computeIfAbsent(holder.key().identifier(), _ -> new HashMap<>()); + holder.components().forEach(component -> { + serialized.put( + BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component.type()), + component.encodeValue(ops).getOrThrow() + ); + }); + }); + + return result; + } + + private record BakedEntry(Holder.Reference element, DataComponentMap components) { + public void apply() { + this.element.bindComponents(this.components); + } + } + + public static List> deserialize( + Map>, Map>> registryToComponents, + RegistryAccess registries + ) { + RegistryOps ops = registries.createSerializationContext(NbtOps.INSTANCE); + + List> result = new ArrayList<>(); + + registryToComponents.forEach((registryKey, holderComponents) -> { + result.add(deserialize(ops, registries.lookupOrThrow(registryKey), holderComponents)); + }); + + return result; + } + + private static DataComponentInitializers.PendingComponents deserialize(RegistryOps ops, Registry registry, Map> holderComponents) { + List> entries = new ArrayList<>(); + + holderComponents.forEach((id, components) -> { + DataComponentMap.Builder builder = DataComponentMap.builder(); + + components.forEach((componentId, encodedValue) -> { + DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(componentId).orElseThrow(); + parse(ops, type, encodedValue, builder); + }); + + entries.add(new BakedEntry<>(registry.get(id).orElseThrow(), builder.build())); + }); + + return new DataComponentInitializers.PendingComponents<>() { + @Override + public ResourceKey> key() { + return registry.key(); + } + + @Override + public void forEach(BiConsumer, DataComponentMap> output) { + for (BakedEntry entry : entries) { + output.accept(entry.element, entry.components); + } + } + + @Override + public void apply() { + for (BakedEntry entry : entries) { + entry.apply(); + } + } + }; + } + + private static void parse(RegistryOps ops, DataComponentType type, Tag tag, DataComponentMap.Builder builder) { + DataResult result = type.codecOrThrow().parse(ops, tag); + builder.set( + type, + result.getOrThrow() + ); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java new file mode 100644 index 00000000000..4bce726009b --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.holder.component; + +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.players.PlayerList; + +@Mixin(PlayerList.class) +public abstract class PlayerListMixin { + @Shadow + public abstract void broadcastAll(Packet packet); + + @Shadow + @Final + private LayeredRegistryAccess registries; + + @Inject(method = "reloadResources", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V", shift = At.Shift.AFTER)) + private void sendComponents(CallbackInfo ci) { + broadcastAll(new ClientboundCustomPayloadPacket(DataComponentNetworkSerialization.serialize( + registries.compositeAccess().createSerializationContext(NbtOps.INSTANCE), + registries + ))); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java new file mode 100644 index 00000000000..b4b82b9af38 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.holder.component; + +import java.util.Set; +import java.util.function.Consumer; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.serialization.DynamicOps; + +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.nbt.Tag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.network.config.SynchronizeRegistriesTask; +import net.minecraft.server.packs.repository.KnownPack; + +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; + +@Mixin(SynchronizeRegistriesTask.class) +public class SynchronizeRegistriesTaskMixin { + @Shadow + @Final + private LayeredRegistryAccess registries; + + @Inject(method = "sendRegistries", at = @At("TAIL")) + private void sendComponents( + Consumer> connection, + Set negotiatedPacks, + CallbackInfo ci, + @Local DynamicOps ops + ) { + connection.accept(new ClientboundCustomPayloadPacket(DataComponentNetworkSerialization.serialize(ops, registries))); + } +} diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json index 6f9035beaa1..0252a3ce549 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json @@ -3,14 +3,16 @@ "package": "net.fabricmc.fabric.mixin.holder.component", "compatibilityLevel": "JAVA_25", "mixins": [ + "DataComponentInitializersMixin", + "PlayerListMixin", + "ReloadableServerResourcesMixin", + "SynchronizeRegistriesTaskMixin", "extension.BlockEntityTypeMixin", "extension.BlockMixin", - "DataComponentInitializersMixin", "extension.EntityTypeMixin", "extension.FluidMixin", "extension.HolderMixin", - "extension.ItemMixin", - "ReloadableServerResourcesMixin" + "extension.ItemMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json index 0ed7dad88e7..27308d28e4e 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json @@ -20,11 +20,18 @@ }, "description": "Allows conveniently attaching data to holders via datapack", "mixins": [ - "fabric-holder-component-api-v1.mixins.json" + "fabric-holder-component-api-v1.mixins.json", + { + "config": "fabric-holder-component-api-v1.client.mixins.json", + "environment": "client" + } ], "entrypoints": { "main": [ "net.fabricmc.fabric.impl.holder.component.HolderComponentEntrypoint" + ], + "client": [ + "net.fabricmc.fabric.impl.client.holder.component.HolderComponentClientEntrypoint" ] }, "accessWidener": "fabric-holder-component-api-v1.classtweaker", From 61201ca1ebcb936a07db970b6c0cb29c16ac94a5 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 10 May 2026 14:24:54 +1000 Subject: [PATCH 14/27] chore: spotless --- .../holder/component/RegistryDataCollectorMixin.java | 7 +++---- .../impl/holder/component/HolderComponentEntrypoint.java | 1 - .../component/sync/ClientboundUpdateComponentsPayload.java | 1 - .../fabric/mixin/holder/component/PlayerListMixin.java | 4 ++-- .../holder/component/SynchronizeRegistriesTaskMixin.java | 5 +---- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java index 1131a178cff..46566c9bb6a 100644 --- a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java @@ -21,9 +21,6 @@ import java.util.Map; import com.llamalad7.mixinextras.sugar.Local; - -import net.minecraft.core.RegistrySynchronization; - import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -33,6 +30,7 @@ import net.minecraft.client.multiplayer.RegistryDataCollector; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.RegistrySynchronization; import net.minecraft.core.component.DataComponentInitializers; import net.minecraft.nbt.Tag; import net.minecraft.resources.Identifier; @@ -50,7 +48,8 @@ public class RegistryDataCollectorMixin implements FabricRegistryDataCollector { @Override public void fabric$appendComponents(ClientboundUpdateComponentsPayload payload) { - for (var entry : payload.registryToComponents().entrySet()) { + // please change checkstyle to let me use var here this is suffering + for (Map.Entry>, Map>> entry : payload.registryToComponents().entrySet()) { Map> registryMaps = components.computeIfAbsent(entry.getKey(), _ -> new HashMap<>(entry.getValue().size())); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index aee6eebadcd..e38dd881a17 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -26,7 +26,6 @@ public class HolderComponentEntrypoint implements ModInitializer { // TODO: This size is enormous, I copied it straight out of FabricRegistryInit. This should be smaller but I don't know how to choose a good value. private static final int MAX_PACKET_SIZE = Integer.getInteger("fabric.holder.component.sync.max_packet_size", 128 * 1024 * 1024); - @Override public void onInitialize() { FabricDataComponentInitializers.registerInitializer( diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java index 91a1f97cb3e..e5991c9b992 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -16,7 +16,6 @@ package net.fabricmc.fabric.impl.holder.component.sync; - import java.util.Map; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java index 4bce726009b..cc54f347075 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -16,8 +16,6 @@ package net.fabricmc.fabric.mixin.holder.component; -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; - import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -32,6 +30,8 @@ import net.minecraft.server.RegistryLayer; import net.minecraft.server.players.PlayerList; +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; + @Mixin(PlayerList.class) public abstract class PlayerListMixin { @Shadow diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java index b4b82b9af38..dbc5a7a30ae 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -21,9 +21,6 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.serialization.DynamicOps; - -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; - import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -39,7 +36,7 @@ import net.minecraft.server.network.config.SynchronizeRegistriesTask; import net.minecraft.server.packs.repository.KnownPack; -import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; +import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; @Mixin(SynchronizeRegistriesTask.class) public class SynchronizeRegistriesTaskMixin { From a165eb3eb70932ea7099405c687e1929895d3d3d Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 11 May 2026 11:02:37 +1000 Subject: [PATCH 15/27] Fix sync payload map types --- .../sync/ClientboundUpdateComponentsPayload.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java index e5991c9b992..d78c86687e0 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -18,8 +18,8 @@ import java.util.Map; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import net.minecraft.core.Registry; @@ -43,7 +43,7 @@ public record ClientboundUpdateComponentsPayload( // (crime against humanity in codec form) public static final StreamCodec STREAM_CODEC = ByteBufCodecs.>, Map>, Map>, Map>>>map( - Object2ObjectOpenHashMap::new, + Reference2ObjectOpenHashMap::new, StreamCodec.ofMember( (key, buf) -> buf.writeResourceKey(key), FriendlyByteBuf::readRegistryKey @@ -53,8 +53,8 @@ public record ClientboundUpdateComponentsPayload( Identifier.STREAM_CODEC, ByteBufCodecs.map( size -> size < 8 // this is straight out of the DataComponentMap.CODEC - ? new Reference2ObjectArrayMap<>(size) - : new Reference2ObjectOpenHashMap<>(size), + ? new Object2ObjectArrayMap<>(size) + : new Object2ObjectOpenHashMap<>(size), Identifier.STREAM_CODEC, ByteBufCodecs.TAG ) From 9560dac46b279c68592c7175ab0a36508f5d63ef Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 11 May 2026 17:18:48 +1000 Subject: [PATCH 16/27] fix: update interface injection to be defaulted --- .../v1/FabricDataComponentHolder.java | 27 +++++++++++++++++++ ...abric-holder-component-api-v1.classtweaker | 12 ++++----- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java new file mode 100644 index 00000000000..69cd2fd9956 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.holder.component.v1; + +import net.minecraft.core.component.DataComponentHolder; +import net.minecraft.core.component.DataComponentMap; + +public interface FabricDataComponentHolder extends DataComponentHolder { + @Override + default DataComponentMap getComponents() { + throw new UnsupportedOperationException("Implemented via mixin."); + } +} diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker index 1320641ed18..d6e1413e051 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker @@ -1,8 +1,8 @@ classTweaker v1 official -transitive-inject-interface net/minecraft/world/level/block/entity/BlockEntityType net/minecraft/core/component/DataComponentHolder -transitive-inject-interface net/minecraft/world/level/block/Block net/minecraft/core/component/DataComponentHolder -transitive-inject-interface net/minecraft/world/entity/EntityType net/minecraft/core/component/DataComponentHolder -transitive-inject-interface net/minecraft/world/level/material/Fluid net/minecraft/core/component/DataComponentHolder -transitive-inject-interface net/minecraft/core/Holder net/minecraft/core/component/DataComponentHolder -transitive-inject-interface net/minecraft/world/item/Item net/minecraft/core/component/DataComponentHolder +transitive-inject-interface net/minecraft/world/level/block/entity/BlockEntityType net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder +transitive-inject-interface net/minecraft/world/level/block/Block net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder +transitive-inject-interface net/minecraft/world/entity/EntityType net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder +transitive-inject-interface net/minecraft/world/level/material/Fluid net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder +transitive-inject-interface net/minecraft/core/Holder net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder +transitive-inject-interface net/minecraft/world/item/Item net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder From 854b916864a0d45e158ba30b31753c26f5687f94 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 11 May 2026 17:20:05 +1000 Subject: [PATCH 17/27] Add javadoc to `FabricDataComponentHolder` --- .../api/holder/component/v1/FabricDataComponentHolder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java index 69cd2fd9956..2c86ff2702b 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.java @@ -19,6 +19,8 @@ import net.minecraft.core.component.DataComponentHolder; import net.minecraft.core.component.DataComponentMap; +/// This interface is injected into several vanilla classes via mixin. Do not implement it yourself, implement [DataComponentHolder] instead. +/// @see DataComponentHolder public interface FabricDataComponentHolder extends DataComponentHolder { @Override default DataComponentMap getComponents() { From b558bd5601c0afc1a695105936e3ce5106428458 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 11 May 2026 17:28:52 +1000 Subject: [PATCH 18/27] Add deps to fmj --- .../fabricmc/fabric/api/holder/component/v1/package-info.java | 2 ++ .../src/main/resources/fabric.mod.json | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java index 3a0617b3ee1..4b3653e9ba1 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.java @@ -16,6 +16,8 @@ /** * API code for fabric-holder-component-api-v1. + * + *

Experimental feature, may be removed or changed without further notice. */ @NullMarked @ApiStatus.Experimental diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json index 27308d28e4e..3a24399e030 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json @@ -16,7 +16,9 @@ "FabricMC" ], "depends": { - "fabricloader": ">=0.18.4" + "fabricloader": ">=0.18.4", + "fabric-api-base": "*", + "fabric-networking-api-v1": "*" }, "description": "Allows conveniently attaching data to holders via datapack", "mixins": [ From b9c3bbdc9e50c1c2462cc57a80fb9342cdd94a8b Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 12 May 2026 14:22:21 +1000 Subject: [PATCH 19/27] Component removal (via `DataComponentPatch`) --- .../v1/FabricDataComponentMapBuilder.java | 11 ++++++ .../data/DataHolderComponentFile.java | 6 +-- .../data/DataHolderComponentInitializer.java | 3 +- .../DataComponentMap$BuilderMixin.java | 37 +++++++++++++++++++ ...abric-holder-component-api-v1.classtweaker | 2 + ...fabric-holder-component-api-v1.mixins.json | 1 + .../components/item/diamond_sword.json | 6 +++ 7 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java create mode 100644 fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java create mode 100644 fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java new file mode 100644 index 00000000000..fce9d57b212 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java @@ -0,0 +1,11 @@ +package net.fabricmc.fabric.api.holder.component.v1; + +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; + +/// Extensions for [DataComponentMap.Builder]. Implemented via interface injection, do not implement yourself! +public interface FabricDataComponentMapBuilder { + default DataComponentMap.Builder apply(DataComponentPatch patch) { + throw new UnsupportedOperationException("Implemented via mixin."); + } +} diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index 789638e347e..9db5b7e6d47 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -20,15 +20,15 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; // TODO: Make public api and use for datagen -// TODO: Switch to DataComponentPatch for removal syntax (would require a more advanced version of DataComponentMap.Builder) public record DataHolderComponentFile( boolean replace, - DataComponentMap components + DataComponentPatch components ) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.BOOL.optionalFieldOf("replace", false).forGetter(DataHolderComponentFile::replace), - DataComponentMap.CODEC.fieldOf("components").forGetter(DataHolderComponentFile::components) + DataComponentPatch.CODEC.fieldOf("components").forGetter(DataHolderComponentFile::components) ).apply(instance, DataHolderComponentFile::new)); } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index 804e19a016f..33f57b6666a 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -82,11 +82,10 @@ private void parse( try (Reader reader = resource.openAsReader()) { JsonElement element = StrictJsonParser.parse(reader); - // TODO: Implement replace, also add required DataHolderComponentFile file = DataHolderComponentFile.CODEC.parse(ops, element).getOrThrow(); DataComponentMap.Builder builder = context.builder(holder.key()); - builder.addAll(file.components()); + builder.apply(file.components()); } catch (Exception e) { LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java new file mode 100644 index 00000000000..603a798a4e3 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java @@ -0,0 +1,37 @@ +package net.fabricmc.fabric.mixin.holder.component.extension; + +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentMapBuilder; + +import net.minecraft.core.component.DataComponentMap; + +import net.minecraft.core.component.DataComponentPatch; + +import net.minecraft.core.component.DataComponentType; + +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; +import java.util.Optional; + +@Mixin(DataComponentMap.Builder.class) +public abstract class DataComponentMap$BuilderMixin implements FabricDataComponentMapBuilder { + @Shadow + public abstract DataComponentMap.Builder set(DataComponentType type, @Nullable T value); + + @Shadow + abstract void setUnchecked(DataComponentType type, @Nullable Object value); + + @Override + public DataComponentMap.Builder apply(DataComponentPatch patch) { + for (Map.Entry, Optional> entry : patch.entrySet()) { + entry.getValue().ifPresentOrElse( + value -> this.setUnchecked(entry.getKey(), value), + () -> this.set(entry.getKey(), null) + ); + } + + return (DataComponentMap.Builder) (Object) this; + } +} diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker index d6e1413e051..8e7cd47c7f5 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker @@ -1,4 +1,6 @@ classTweaker v1 official +transitive-inject-interface net/minecraft/core/component/DataComponentMap$Builder net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder + transitive-inject-interface net/minecraft/world/level/block/entity/BlockEntityType net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder transitive-inject-interface net/minecraft/world/level/block/Block net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder transitive-inject-interface net/minecraft/world/entity/EntityType net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder diff --git a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json index 0252a3ce549..0a58d510447 100644 --- a/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json @@ -9,6 +9,7 @@ "SynchronizeRegistriesTaskMixin", "extension.BlockEntityTypeMixin", "extension.BlockMixin", + "extension.DataComponentMap$BuilderMixin", "extension.EntityTypeMixin", "extension.FluidMixin", "extension.HolderMixin", diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json new file mode 100644 index 00000000000..6b686524ce0 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json @@ -0,0 +1,6 @@ +{ + "components": { + "minecraft:rarity": "rare", + "!minecraft:item_model": {} + } +} From 6dfe02f6e2e7e2e2c41f5b2072a374d1e073c49e Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 12 May 2026 14:25:40 +1000 Subject: [PATCH 20/27] Implement replace --- .../v1/FabricDataComponentMapBuilder.java | 4 ++++ .../data/DataHolderComponentInitializer.java | 2 ++ .../extension/DataComponentMap$BuilderMixin.java | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java index fce9d57b212..96cbed9c916 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java @@ -8,4 +8,8 @@ public interface FabricDataComponentMapBuilder { default DataComponentMap.Builder apply(DataComponentPatch patch) { throw new UnsupportedOperationException("Implemented via mixin."); } + + default DataComponentMap.Builder clear() { + throw new UnsupportedOperationException("Implemented via mixin."); + } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index 33f57b6666a..97677a67d64 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -85,6 +85,8 @@ private void parse( DataHolderComponentFile file = DataHolderComponentFile.CODEC.parse(ops, element).getOrThrow(); DataComponentMap.Builder builder = context.builder(holder.key()); + + if (file.replace()) builder.clear(); builder.apply(file.components()); } catch (Exception e) { LOGGER.error("Couldn't read component list {} from {} in data pack {}", id, location, resource.sourcePackId(), e); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java index 603a798a4e3..019d342e350 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java @@ -1,5 +1,7 @@ package net.fabricmc.fabric.mixin.holder.component.extension; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; + import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentMapBuilder; import net.minecraft.core.component.DataComponentMap; @@ -9,6 +11,7 @@ import net.minecraft.core.component.DataComponentType; import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -23,6 +26,10 @@ public abstract class DataComponentMap$BuilderMixin implements FabricDataCompone @Shadow abstract void setUnchecked(DataComponentType type, @Nullable Object value); + @Shadow + @Final + private Reference2ObjectMap, Object> map; + @Override public DataComponentMap.Builder apply(DataComponentPatch patch) { for (Map.Entry, Optional> entry : patch.entrySet()) { @@ -34,4 +41,11 @@ public DataComponentMap.Builder apply(DataComponentPatch patch) { return (DataComponentMap.Builder) (Object) this; } + + @Override + public DataComponentMap.Builder clear() { + map.clear(); + + return (DataComponentMap.Builder) (Object) this; + } } From 68724bf15215d1369ddc880f72202822b70e6533 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 12 May 2026 14:26:17 +1000 Subject: [PATCH 21/27] spotless --- .../v1/FabricDataComponentMapBuilder.java | 16 +++++++++ .../data/DataHolderComponentFile.java | 1 - .../DataComponentMap$BuilderMixin.java | 35 +++++++++++++------ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java index 96cbed9c916..78833bf5025 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.api.holder.component.v1; import net.minecraft.core.component.DataComponentMap; diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java index 9db5b7e6d47..dd36c95a39c 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -19,7 +19,6 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; // TODO: Make public api and use for datagen diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java index 019d342e350..b3d6011f8bb 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java @@ -1,22 +1,35 @@ -package net.fabricmc.fabric.mixin.holder.component.extension; - -import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; - -import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentMapBuilder; +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import net.minecraft.core.component.DataComponentMap; - -import net.minecraft.core.component.DataComponentPatch; +package net.fabricmc.fabric.mixin.holder.component.extension; -import net.minecraft.core.component.DataComponentType; +import java.util.Map; +import java.util.Optional; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import java.util.Map; -import java.util.Optional; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponentType; + +import net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentMapBuilder; @Mixin(DataComponentMap.Builder.class) public abstract class DataComponentMap$BuilderMixin implements FabricDataComponentMapBuilder { From 8da600e90a1b17032318efcc6551a614b2c551b5 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Wed, 13 May 2026 13:39:01 +1000 Subject: [PATCH 22/27] Start documentation --- .../component/v1/FabricDataComponentInitializers.java | 7 +++++++ .../component/v1/FabricDataComponentMapBuilder.java | 1 + .../holder/component/SynchronizeRegistriesTaskMixin.java | 5 ++++- .../component/HolderComponentTestModEntrypoint.java | 9 +++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java index a7ebddbaf5e..1c4ec6b34f3 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java @@ -16,10 +16,17 @@ package net.fabricmc.fabric.api.holder.component.v1; +import net.minecraft.core.component.DataComponentInitializers; import net.minecraft.resources.Identifier; import net.fabricmc.fabric.impl.holder.component.FabricDataComponentInitializersImpl; +/// Allows registration of custom [Data Component Initializers][DataComponentInitializers.Initializer]. +/// +/// [Data Component Initializers][DataComponentInitializers.Initializer] are used to add components to [Holders][net.minecraft.core.Holder]. They are run right before the end of a resource reload. +/// @see DataComponentInitializers +/// @see FabricDataComponentInitializer +// TODO: Finish docs public final class FabricDataComponentInitializers { /// @see FabricDataComponentInitializers#addInitializerOrdering(Identifier, Identifier) public static final Identifier DATA_HOLDER_COMPONENTS = Identifier.fromNamespaceAndPath("fabric", "data_holder_components"); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java index 78833bf5025..5eabac99c84 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java @@ -20,6 +20,7 @@ import net.minecraft.core.component.DataComponentPatch; /// Extensions for [DataComponentMap.Builder]. Implemented via interface injection, do not implement yourself! +/// @see DataComponentMap.Builder public interface FabricDataComponentMapBuilder { default DataComponentMap.Builder apply(DataComponentPatch patch) { throw new UnsupportedOperationException("Implemented via mixin."); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java index dbc5a7a30ae..155c9ed2b88 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -21,6 +21,9 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.serialization.DynamicOps; + +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; + import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -51,6 +54,6 @@ private void sendComponents( CallbackInfo ci, @Local DynamicOps ops ) { - connection.accept(new ClientboundCustomPayloadPacket(DataComponentNetworkSerialization.serialize(ops, registries))); + connection.accept(ServerConfigurationNetworking.createClientboundPacket(DataComponentNetworkSerialization.serialize(ops, registries))); } } diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java index 90c7a0483c6..f95c90b2758 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -16,6 +16,15 @@ package net.fabricmc.fabric.test.holder.component; +import net.minecraft.core.component.DataComponentHolder; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; +import net.minecraft.references.BlockIds; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.equipment.Equippable; +import net.minecraft.world.level.block.Blocks; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 39d0380f8dacb674f6f0c61efeffe0890f8d9d4c Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Thu, 14 May 2026 15:48:22 +1000 Subject: [PATCH 23/27] Slight de-janking of sync --- .../HolderComponentClientEntrypoint.java | 6 +- .../component/RegistryDataCollectorMixin.java | 27 ++++----- .../ClientboundUpdateComponentsPayload.java | 20 ++----- ...va => HolderComponentSynchronization.java} | 56 +++++++++++++------ .../holder/component/PlayerListMixin.java | 6 +- .../SynchronizeRegistriesTaskMixin.java | 9 +-- .../HolderComponentTestModEntrypoint.java | 8 --- 7 files changed, 67 insertions(+), 65 deletions(-) rename fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/{DataComponentNetworkSerialization.java => HolderComponentSynchronization.java} (69%) diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java index fd433938c4e..9a66b593a4b 100644 --- a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java @@ -26,7 +26,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.context.PacketContext; import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; +import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; import net.fabricmc.fabric.mixin.client.holder.component.ClientConfigurationPacketListenerImplAccessor; public class HolderComponentClientEntrypoint implements ClientModInitializer { @@ -45,8 +45,8 @@ public void onInitializeClient() { ClientPlayNetworking.registerGlobalReceiver( ClientboundUpdateComponentsPayload.TYPE, (payload, context) -> { - List> pending = DataComponentNetworkSerialization.deserialize( - payload.registryToComponents(), + List> pending = HolderComponentSynchronization.deserialize( + payload, context.packetContext().orElseThrow(PacketContext.REGISTRY_ACCESS) ); diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java index 46566c9bb6a..9be0f40680a 100644 --- a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/holder/component/RegistryDataCollectorMixin.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.mixin.client.holder.component; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,35 +29,32 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.client.multiplayer.RegistryDataCollector; -import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.RegistrySynchronization; import net.minecraft.core.component.DataComponentInitializers; -import net.minecraft.nbt.Tag; import net.minecraft.resources.Identifier; -import net.minecraft.resources.ResourceKey; import net.minecraft.server.packs.resources.ResourceProvider; import net.fabricmc.fabric.impl.client.holder.component.FabricRegistryDataCollector; import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; +import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; @Mixin(RegistryDataCollector.class) public class RegistryDataCollectorMixin implements FabricRegistryDataCollector { @Unique - private final Map>, Map>> components = new HashMap<>(); + private final ClientboundUpdateComponentsPayload components = new ClientboundUpdateComponentsPayload(new HashMap<>()); @Override public void fabric$appendComponents(ClientboundUpdateComponentsPayload payload) { - // please change checkstyle to let me use var here this is suffering - for (Map.Entry>, Map>> entry : payload.registryToComponents().entrySet()) { - Map> registryMaps = - components.computeIfAbsent(entry.getKey(), _ -> new HashMap<>(entry.getValue().size())); + payload.registryToComponents().forEach((key, value) -> { + Map> holderMaps = + components.registryToComponents() + .computeIfAbsent(key, _ -> new HashMap<>(value.size())); - entry.getValue().forEach((holder, components) -> { - registryMaps.computeIfAbsent(holder, _ -> new HashMap<>()).putAll(components); + value.forEach((holder, components) -> { + holderMaps.computeIfAbsent(holder, _ -> new ArrayList<>()).addAll(components); }); - } + }); } @Inject(method = "collectGameRegistries", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/RegistryDataCollector;updateComponents(Lnet/minecraft/core/RegistryAccess$Frozen;Z)V", shift = At.Shift.AFTER)) @@ -67,7 +65,10 @@ private void updateSyncedComponents( CallbackInfoReturnable cir, @Local(name = "frozenRegistries") RegistryAccess.Frozen frozenRegistries ) { - List> pending = DataComponentNetworkSerialization.deserialize(components, frozenRegistries); + List> pending = HolderComponentSynchronization.deserialize( + components, + frozenRegistries + ); boolean includeSharedRegistries = !tagsAndComponentsForSynchronizedRegistriesOnly; diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java index d78c86687e0..3b901994a93 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -16,14 +16,13 @@ package net.fabricmc.fabric.impl.holder.component.sync; +import java.util.List; import java.util.Map; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import net.minecraft.core.Registry; -import net.minecraft.nbt.Tag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; @@ -36,13 +35,13 @@ public record ClientboundUpdateComponentsPayload( // this is hopefully fine since they aren't updated often // TODO: optimize this format. im quite sure this could be smaller (ComponentTypeId could maybe be the int id?) // Map>> - Map>, Map>> registryToComponents + Map>, Map>> registryToComponents ) implements CustomPacketPayload { public static final Type TYPE = new Type<>(Identifier.fromNamespaceAndPath("fabric", "update_holder_components")); // (crime against humanity in codec form) public static final StreamCodec STREAM_CODEC = - ByteBufCodecs.>, Map>, Map>, Map>>>map( + ByteBufCodecs.>, Map>, Map>, Map>>>map( Reference2ObjectOpenHashMap::new, StreamCodec.ofMember( (key, buf) -> buf.writeResourceKey(key), @@ -51,18 +50,9 @@ public record ClientboundUpdateComponentsPayload( ByteBufCodecs.map( Object2ObjectOpenHashMap::new, Identifier.STREAM_CODEC, - ByteBufCodecs.map( - size -> size < 8 // this is straight out of the DataComponentMap.CODEC - ? new Object2ObjectArrayMap<>(size) - : new Object2ObjectOpenHashMap<>(size), - Identifier.STREAM_CODEC, - ByteBufCodecs.TAG - ) + HolderComponentSynchronization.PackedComponentMap.STREAM_CODEC.apply(ByteBufCodecs.list()) ) - ).map( - ClientboundUpdateComponentsPayload::new, - ClientboundUpdateComponentsPayload::registryToComponents - ); + ).map(ClientboundUpdateComponentsPayload::new, ClientboundUpdateComponentsPayload::registryToComponents); @Override public Type type() { diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java similarity index 69% rename from fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java rename to fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java index bede3f20e2d..797cdf2e86e 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/DataComponentNetworkSerialization.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java @@ -25,6 +25,7 @@ import com.mojang.serialization.DataResult; import com.mojang.serialization.DynamicOps; +import io.netty.buffer.ByteBuf; import net.minecraft.core.Holder; import net.minecraft.core.LayeredRegistryAccess; @@ -34,16 +35,22 @@ import net.minecraft.core.component.DataComponentInitializers; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.TypedDataComponent; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.Identifier; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.server.RegistryLayer; -public class DataComponentNetworkSerialization { - public static ClientboundUpdateComponentsPayload serialize(DynamicOps ops, LayeredRegistryAccess registries) { +public class HolderComponentSynchronization { + public static ClientboundUpdateComponentsPayload serialize( + DynamicOps ops, + LayeredRegistryAccess registries + ) { return new ClientboundUpdateComponentsPayload( RegistrySynchronization.networkSafeRegistries(registries) .collect(Collectors.toMap( @@ -53,19 +60,26 @@ public static ClientboundUpdateComponentsPayload serialize(DynamicOps ops, ); } - private static Map> serialize(DynamicOps ops, Registry registry) { - Map> result = new HashMap<>(); + private static Map> serialize( + DynamicOps ops, + Registry registry + ) { + Map> result = new HashMap<>(); registry.listElements().forEach(holder -> { if (holder.components().isEmpty()) return; - Map serialized = result.computeIfAbsent(holder.key().identifier(), _ -> new HashMap<>()); - holder.components().forEach(component -> { - serialized.put( - BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component.type()), - component.encodeValue(ops).getOrThrow() + List serialized = result.computeIfAbsent(holder.key().identifier(), _ -> new ArrayList<>()); + for (TypedDataComponent component : holder.components()) { + Identifier id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component.type()); + + serialized.add( + new PackedComponentMap( + id, + component.encodeValue(ops).getOrThrow(s -> new IllegalArgumentException("Failed to serialize " + id + ": " + s)) + ) ); - }); + } }); return result; @@ -78,30 +92,30 @@ public void apply() { } public static List> deserialize( - Map>, Map>> registryToComponents, + ClientboundUpdateComponentsPayload payload, RegistryAccess registries ) { RegistryOps ops = registries.createSerializationContext(NbtOps.INSTANCE); List> result = new ArrayList<>(); - registryToComponents.forEach((registryKey, holderComponents) -> { + payload.registryToComponents().forEach((registryKey, holderComponents) -> { result.add(deserialize(ops, registries.lookupOrThrow(registryKey), holderComponents)); }); return result; } - private static DataComponentInitializers.PendingComponents deserialize(RegistryOps ops, Registry registry, Map> holderComponents) { + private static DataComponentInitializers.PendingComponents deserialize(RegistryOps ops, Registry registry, Map> holderComponents) { List> entries = new ArrayList<>(); holderComponents.forEach((id, components) -> { DataComponentMap.Builder builder = DataComponentMap.builder(); - components.forEach((componentId, encodedValue) -> { - DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(componentId).orElseThrow(); - parse(ops, type, encodedValue, builder); - }); + for (PackedComponentMap map : components) { + DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(map.id).orElseThrow(); + parse(ops, type, map.data, builder); + } entries.add(new BakedEntry<>(registry.get(id).orElseThrow(), builder.build())); }); @@ -135,4 +149,12 @@ private static void parse(RegistryOps ops, DataComponentType type, T result.getOrThrow() ); } + + public record PackedComponentMap(Identifier id, Tag data) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Identifier.STREAM_CODEC, PackedComponentMap::id, + ByteBufCodecs.TAG, PackedComponentMap::data, + PackedComponentMap::new + ); + } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java index cc54f347075..bcdedab4f51 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -26,11 +26,11 @@ import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.nbt.NbtOps; import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.server.RegistryLayer; import net.minecraft.server.players.PlayerList; -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; @Mixin(PlayerList.class) public abstract class PlayerListMixin { @@ -43,7 +43,7 @@ public abstract class PlayerListMixin { @Inject(method = "reloadResources", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V", shift = At.Shift.AFTER)) private void sendComponents(CallbackInfo ci) { - broadcastAll(new ClientboundCustomPayloadPacket(DataComponentNetworkSerialization.serialize( + broadcastAll(ServerPlayNetworking.createClientboundPacket(HolderComponentSynchronization.serialize( registries.compositeAccess().createSerializationContext(NbtOps.INSTANCE), registries ))); diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java index 155c9ed2b88..b90462ade64 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -21,9 +21,6 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.serialization.DynamicOps; - -import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; - import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -34,12 +31,12 @@ import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.server.RegistryLayer; import net.minecraft.server.network.config.SynchronizeRegistriesTask; import net.minecraft.server.packs.repository.KnownPack; -import net.fabricmc.fabric.impl.holder.component.sync.DataComponentNetworkSerialization; +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; @Mixin(SynchronizeRegistriesTask.class) public class SynchronizeRegistriesTaskMixin { @@ -54,6 +51,6 @@ private void sendComponents( CallbackInfo ci, @Local DynamicOps ops ) { - connection.accept(ServerConfigurationNetworking.createClientboundPacket(DataComponentNetworkSerialization.serialize(ops, registries))); + connection.accept(ServerConfigurationNetworking.createClientboundPacket(HolderComponentSynchronization.serialize(ops, registries))); } } diff --git a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java index f95c90b2758..0ecf57f36c8 100644 --- a/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -16,14 +16,6 @@ package net.fabricmc.fabric.test.holder.component; -import net.minecraft.core.component.DataComponentHolder; -import net.minecraft.core.component.DataComponentType; -import net.minecraft.core.component.DataComponents; -import net.minecraft.references.BlockIds; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.item.DyeColor; -import net.minecraft.world.item.equipment.Equippable; -import net.minecraft.world.level.block.Blocks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From b8948ea4db2874a1841ff15f5953845bd91ea3b8 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Thu, 14 May 2026 15:54:04 +1000 Subject: [PATCH 24/27] Only sync components if client supports packet (play only) --- .../holder/component/PlayerListMixin.java | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java index bcdedab4f51..3d200e748e4 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -16,6 +16,11 @@ package net.fabricmc.fabric.mixin.holder.component; +import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; + +import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.server.level.ServerPlayer; + import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -32,6 +37,8 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; +import java.util.List; + @Mixin(PlayerList.class) public abstract class PlayerListMixin { @Shadow @@ -41,11 +48,23 @@ public abstract class PlayerListMixin { @Final private LayeredRegistryAccess registries; + @Shadow + @Final + private List players; + @Inject(method = "reloadResources", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V", shift = At.Shift.AFTER)) private void sendComponents(CallbackInfo ci) { - broadcastAll(ServerPlayNetworking.createClientboundPacket(HolderComponentSynchronization.serialize( - registries.compositeAccess().createSerializationContext(NbtOps.INSTANCE), - registries - ))); + Packet payload = ServerPlayNetworking.createClientboundPacket( + HolderComponentSynchronization.serialize( + registries.compositeAccess().createSerializationContext(NbtOps.INSTANCE), + registries + ) + ); + + for (ServerPlayer player : this.players) { + if (ServerPlayNetworking.canSend(player, ClientboundUpdateComponentsPayload.TYPE)) { + player.connection.send(payload); + } + } } } From 1fe02d6057ac98aade41da5661184be370026ab7 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Thu, 14 May 2026 16:20:45 +1000 Subject: [PATCH 25/27] Smol packets --- .../sync/HolderComponentSynchronization.java | 52 +++++++++++-------- .../holder/component/PlayerListMixin.java | 5 +- .../SynchronizeRegistriesTaskMixin.java | 5 +- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java index 797cdf2e86e..9ba54a3fab4 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java @@ -23,10 +23,10 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; -import com.mojang.serialization.DataResult; -import com.mojang.serialization.DynamicOps; import io.netty.buffer.ByteBuf; +import net.fabricmc.fabric.api.networking.v1.FriendlyByteBufs; + import net.minecraft.core.Holder; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.Registry; @@ -37,31 +37,25 @@ import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.TypedDataComponent; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.nbt.NbtOps; -import net.minecraft.nbt.Tag; -import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.Identifier; -import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.server.RegistryLayer; public class HolderComponentSynchronization { - public static ClientboundUpdateComponentsPayload serialize( - DynamicOps ops, - LayeredRegistryAccess registries - ) { + public static ClientboundUpdateComponentsPayload serialize(LayeredRegistryAccess registries) { return new ClientboundUpdateComponentsPayload( RegistrySynchronization.networkSafeRegistries(registries) .collect(Collectors.toMap( RegistryAccess.RegistryEntry::key, - entry -> serialize(ops, entry.value()) + entry -> serialize(registries.compositeAccess(), entry.value()) )) ); } private static Map> serialize( - DynamicOps ops, + RegistryAccess registries, Registry registry ) { Map> result = new HashMap<>(); @@ -72,11 +66,13 @@ private static Map> serialize( List serialized = result.computeIfAbsent(holder.key().identifier(), _ -> new ArrayList<>()); for (TypedDataComponent component : holder.components()) { Identifier id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component.type()); + RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(FriendlyByteBufs.create(), registries); + encode(component, buf); serialized.add( new PackedComponentMap( id, - component.encodeValue(ops).getOrThrow(s -> new IllegalArgumentException("Failed to serialize " + id + ": " + s)) + buf ) ); } @@ -85,6 +81,10 @@ private static Map> serialize( return result; } + private static void encode(TypedDataComponent component, RegistryFriendlyByteBuf buf) { + component.type().streamCodec().encode(buf, component.value()); + } + private record BakedEntry(Holder.Reference element, DataComponentMap components) { public void apply() { this.element.bindComponents(this.components); @@ -95,18 +95,16 @@ public static List> deserialize( ClientboundUpdateComponentsPayload payload, RegistryAccess registries ) { - RegistryOps ops = registries.createSerializationContext(NbtOps.INSTANCE); - List> result = new ArrayList<>(); payload.registryToComponents().forEach((registryKey, holderComponents) -> { - result.add(deserialize(ops, registries.lookupOrThrow(registryKey), holderComponents)); + result.add(deserialize(registries, registries.lookupOrThrow(registryKey), holderComponents)); }); return result; } - private static DataComponentInitializers.PendingComponents deserialize(RegistryOps ops, Registry registry, Map> holderComponents) { + private static DataComponentInitializers.PendingComponents deserialize(RegistryAccess registries, Registry registry, Map> holderComponents) { List> entries = new ArrayList<>(); holderComponents.forEach((id, components) -> { @@ -114,7 +112,9 @@ private static DataComponentInitializers.PendingComponents deserialize(Re for (PackedComponentMap map : components) { DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(map.id).orElseThrow(); - parse(ops, type, map.data, builder); + parse(registries, type, map.data, builder); + + map.data.release(); } entries.add(new BakedEntry<>(registry.get(id).orElseThrow(), builder.build())); @@ -142,18 +142,24 @@ public void apply() { }; } - private static void parse(RegistryOps ops, DataComponentType type, Tag tag, DataComponentMap.Builder builder) { - DataResult result = type.codecOrThrow().parse(ops, tag); + private static void parse(RegistryAccess registries, DataComponentType type, ByteBuf buf, DataComponentMap.Builder builder) { + T result = type.streamCodec().decode(new RegistryFriendlyByteBuf(buf, registries)); builder.set( type, - result.getOrThrow() + result ); } - public record PackedComponentMap(Identifier id, Tag data) { + public record PackedComponentMap(Identifier id, ByteBuf data) { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Identifier.STREAM_CODEC, PackedComponentMap::id, - ByteBufCodecs.TAG, PackedComponentMap::data, + StreamCodec.of( + (output, value) -> { + output.writeInt(value.readableBytes()); + output.writeBytes(value); + }, + buf -> buf.readRetainedSlice(buf.readInt()) + ), PackedComponentMap::data, PackedComponentMap::new ); } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java index 3d200e748e4..a7fcff2927e 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -55,10 +55,7 @@ public abstract class PlayerListMixin { @Inject(method = "reloadResources", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V", shift = At.Shift.AFTER)) private void sendComponents(CallbackInfo ci) { Packet payload = ServerPlayNetworking.createClientboundPacket( - HolderComponentSynchronization.serialize( - registries.compositeAccess().createSerializationContext(NbtOps.INSTANCE), - registries - ) + HolderComponentSynchronization.serialize(registries) ); for (ServerPlayer player : this.players) { diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java index b90462ade64..6d929add631 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -48,9 +48,8 @@ public class SynchronizeRegistriesTaskMixin { private void sendComponents( Consumer> connection, Set negotiatedPacks, - CallbackInfo ci, - @Local DynamicOps ops + CallbackInfo ci ) { - connection.accept(ServerConfigurationNetworking.createClientboundPacket(HolderComponentSynchronization.serialize(ops, registries))); + connection.accept(ServerConfigurationNetworking.createClientboundPacket(HolderComponentSynchronization.serialize(registries))); } } From 547db4e3919a378382ed14328e46dd9d85f9f39a Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 15 May 2026 12:56:48 +1000 Subject: [PATCH 26/27] Prefix components directory with `fabric/` --- .../holder/component/data/DataHolderComponentInitializer.java | 2 +- .../minecraft/{ => fabric}/components/block/acacia_slab.json | 0 .../minecraft/{ => fabric}/components/item/diamond_sword.json | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/{ => fabric}/components/block/acacia_slab.json (100%) rename fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/{ => fabric}/components/item/diamond_sword.json (100%) diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java index 97677a67d64..e2a27c12051 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -58,7 +58,7 @@ private void parse( @SuppressWarnings("unchecked") ResourceKey> key = (ResourceKey>) key1; HolderLookup.RegistryLookup lookup = context.lookupProvider().lookupOrThrow(key); - FileToIdConverter lister = FileToIdConverter.json(Registries.componentsDirPath(lookup.key())); + FileToIdConverter lister = FileToIdConverter.json("fabric/" + Registries.componentsDirPath(lookup.key())); for (Map.Entry> entry : lister.listMatchingResourceStacks(context.resourceManager()).entrySet()) { Identifier location = entry.getKey(); diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/components/block/acacia_slab.json similarity index 100% rename from fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/block/acacia_slab.json rename to fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/components/block/acacia_slab.json diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/components/item/diamond_sword.json similarity index 100% rename from fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/components/item/diamond_sword.json rename to fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/components/item/diamond_sword.json From 2dfbc0cee3540eb6692c0d003d9dd43af3f6ff2e Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Tue, 26 May 2026 14:27:53 +1000 Subject: [PATCH 27/27] More sync checks --- .../component/HolderComponentClientEntrypoint.java | 9 +++++++-- .../holder/component/HolderComponentEntrypoint.java | 6 ++++++ .../sync/ClientboundUpdateComponentsPayload.java | 10 ++++++++++ .../component/DataComponentInitializersMixin.java | 12 ++++-------- .../mixin/holder/component/PlayerListMixin.java | 5 +---- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java index 9a66b593a4b..419aeb186ab 100644 --- a/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java @@ -18,6 +18,8 @@ import java.util.List; +import net.fabricmc.fabric.impl.holder.component.HolderComponentEntrypoint; + import net.minecraft.client.multiplayer.RegistryDataCollector; import net.minecraft.core.component.DataComponentInitializers; @@ -50,8 +52,11 @@ public void onInitializeClient() { context.packetContext().orElseThrow(PacketContext.REGISTRY_ACCESS) ); - // if we are not on an integrated server - if (!context.packetContext().orElseThrow(PacketContext.CONNECTION).isMemoryConnection()) { + // we already check for integrated server when sending, but anything could send a packet and applying it will cause serious issues. + if (context.packetContext().orElseThrow(PacketContext.CONNECTION).isMemoryConnection()) { + // RFC: Should this be a crash? + HolderComponentEntrypoint.LOGGER.warn("A ClientboundUpdateComponentsPayload was sent to the integrated server host. This is a bug, whichever mod sent this packet should check if the receiver is the server host before sending it."); + } else { pending.forEach(DataComponentInitializers.PendingComponents::apply); } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java index e38dd881a17..d05faf085f2 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -22,10 +22,16 @@ import net.fabricmc.fabric.impl.holder.component.data.DataHolderComponentInitializer; import net.fabricmc.fabric.impl.holder.component.sync.ClientboundUpdateComponentsPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class HolderComponentEntrypoint implements ModInitializer { // TODO: This size is enormous, I copied it straight out of FabricRegistryInit. This should be smaller but I don't know how to choose a good value. private static final int MAX_PACKET_SIZE = Integer.getInteger("fabric.holder.component.sync.max_packet_size", 128 * 1024 * 1024); + public static final String MOD_ID = "fabric-holder-component-api-v1"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + @Override public void onInitialize() { FabricDataComponentInitializers.registerInitializer( diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java index 3b901994a93..33d0c4d9b19 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -22,6 +22,10 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +import net.fabricmc.fabric.api.networking.v1.context.PacketContext; + import net.minecraft.core.Registry; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; @@ -29,6 +33,7 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerPlayer; public record ClientboundUpdateComponentsPayload( // component values have to be sent as nbt tags as the StreamCodecs for them require RegistryFriendlyByteBuf which we don't have during configure @@ -58,4 +63,9 @@ public record ClientboundUpdateComponentsPayload( public Type type() { return TYPE; } + + public static boolean shouldSend(ServerPlayer player) { + return ServerPlayNetworking.canSend(player, ClientboundUpdateComponentsPayload.TYPE) && + !player.getPacketContext().orElseThrow(PacketContext.CONNECTION).isMemoryConnection(); + } } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java index f3759403d90..ec94cee707b 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java @@ -38,20 +38,16 @@ @Mixin(DataComponentInitializers.class) public class DataComponentInitializersMixin { - @Unique - private static final Logger LOGGER = LogUtils.getLogger(); - @ModifyReturnValue(method = "runInitializers", at = @At("RETURN")) private Map, DataComponentMap.Builder> runFabricInitializers( Map, DataComponentMap.Builder> original, @Local(argsOnly = true) HolderLookup.Provider holders ) { if (!FabricDataComponentInitializersImpl.RESOURCE_MANAGER.isBound()) { - // we got called either by the report or by a mod - // if a mod called this, it is doing something cursed, and cant get our components - // if this is the component report, it didnt need our components - // TODO: More descriptive error - LOGGER.warn("DataComponentInitializers.runInitializers() was called, but RESOURCE_MANAGER is not bound!"); + // I would like to include a warning here if another mod calls this method, but we do expect vanilla to call it- + // with this unbound in RegistryDataCollector. I could probably stackwalk but that seems like a lot of effort. + + // LOGGER.warn("DataComponentInitializers.runInitializers() was called, but RESOURCE_MANAGER is not bound!"); return original; } diff --git a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java index a7fcff2927e..9f93c19ce66 100644 --- a/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -41,9 +41,6 @@ @Mixin(PlayerList.class) public abstract class PlayerListMixin { - @Shadow - public abstract void broadcastAll(Packet packet); - @Shadow @Final private LayeredRegistryAccess registries; @@ -59,7 +56,7 @@ private void sendComponents(CallbackInfo ci) { ); for (ServerPlayer player : this.players) { - if (ServerPlayNetworking.canSend(player, ClientboundUpdateComponentsPayload.TYPE)) { + if (ClientboundUpdateComponentsPayload.shouldSend(player)) { player.connection.send(payload); } }