Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
62d8340
Initial prototype for holder components
CallMeEchoCodes May 4, 2026
43505e2
fix: redo data loading to support namespaces
CallMeEchoCodes May 4, 2026
654885c
fix: add namespace to componentsdir
CallMeEchoCodes May 4, 2026
b479c20
chore: checkstyle and spotless
CallMeEchoCodes May 4, 2026
bc63f80
Merge branch '26.1.2' into holder-components
CallMeEchoCodes May 4, 2026
3baf6dc
feat: Initializer toposort
CallMeEchoCodes May 5, 2026
92858e4
chore: spotless
CallMeEchoCodes May 5, 2026
eccc337
invert format
CallMeEchoCodes May 5, 2026
32273e1
Fix import in javadoc for `FabricDataComponentInitializer`
CallMeEchoCodes May 5, 2026
41c46f5
Extend some objects with `builtInRegistryHolder`s to support getting …
CallMeEchoCodes May 5, 2026
069c350
Init gametests
CallMeEchoCodes May 5, 2026
5f42df0
Add `@ApiStatus.Experimental` to api package
CallMeEchoCodes May 5, 2026
42f30ff
checkstyle
CallMeEchoCodes May 6, 2026
113bc0b
Synchronization (VERY wip)
CallMeEchoCodes May 10, 2026
61201ca
chore: spotless
CallMeEchoCodes May 10, 2026
a165eb3
Fix sync payload map types
CallMeEchoCodes May 11, 2026
9560dac
fix: update interface injection to be defaulted
CallMeEchoCodes May 11, 2026
854b916
Add javadoc to `FabricDataComponentHolder`
CallMeEchoCodes May 11, 2026
b558bd5
Add deps to fmj
CallMeEchoCodes May 11, 2026
b9c3bbd
Component removal (via `DataComponentPatch`)
CallMeEchoCodes May 12, 2026
6dfe02f
Implement replace
CallMeEchoCodes May 12, 2026
68724bf
spotless
CallMeEchoCodes May 12, 2026
8da600e
Start documentation
CallMeEchoCodes May 13, 2026
39d0380
Slight de-janking of sync
CallMeEchoCodes May 14, 2026
b8948ea
Only sync components if client supports packet (play only)
CallMeEchoCodes May 14, 2026
1fe02d6
Smol packets
CallMeEchoCodes May 14, 2026
547db4e
Prefix components directory with `fabric/`
CallMeEchoCodes May 15, 2026
2dfbc0c
More sync checks
CallMeEchoCodes May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions fabric-holder-component-api-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version = getSubprojectVersion(project)

loom {
accessWidenerPath = file("src/main/resources/fabric-holder-component-api-v1.classtweaker")
}

moduleDependencies(project, [
'fabric-api-base',
'fabric-networking-api-v1'
])

testDependencies(project, [
'fabric-command-api-v2'
])
Original file line number Diff line number Diff line change
@@ -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.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.fabricmc.fabric.impl.holder.component.HolderComponentEntrypoint;

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.HolderComponentSynchronization;
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);
Comment thread
CallMeEchoCodes marked this conversation as resolved.
}
);

ClientPlayNetworking.registerGlobalReceiver(
ClientboundUpdateComponentsPayload.TYPE,
(payload, context) -> {
List<DataComponentInitializers.PendingComponents<?>> pending = HolderComponentSynchronization.deserialize(
payload,
context.packetContext().orElseThrow(PacketContext.REGISTRY_ACCESS)
);

// 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);
}
}
);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.llamalad7.mixinextras.sugar.Local;
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.RegistryAccess;
import net.minecraft.core.RegistrySynchronization;
import net.minecraft.core.component.DataComponentInitializers;
import net.minecraft.resources.Identifier;
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.HolderComponentSynchronization;

@Mixin(RegistryDataCollector.class)
public class RegistryDataCollectorMixin implements FabricRegistryDataCollector {
@Unique
private final ClientboundUpdateComponentsPayload components = new ClientboundUpdateComponentsPayload(new HashMap<>());

@Override
public void fabric$appendComponents(ClientboundUpdateComponentsPayload payload) {
payload.registryToComponents().forEach((key, value) -> {
Map<Identifier, List<HolderComponentSynchronization.PackedComponentMap>> holderMaps =
components.registryToComponents()
.computeIfAbsent(key, _ -> new HashMap<>(value.size()));

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))
private void updateSyncedComponents(
ResourceProvider knownDataSource,
RegistryAccess.Frozen originalRegistries,
boolean tagsAndComponentsForSynchronizedRegistriesOnly,
CallbackInfoReturnable<RegistryAccess.Frozen> cir,
@Local(name = "frozenRegistries") RegistryAccess.Frozen frozenRegistries
) {
List<DataComponentInitializers.PendingComponents<?>> pending = HolderComponentSynchronization.deserialize(
components,
frozenRegistries
);

boolean includeSharedRegistries = !tagsAndComponentsForSynchronizedRegistriesOnly;

for (DataComponentInitializers.PendingComponents<?> prepared : pending) {
if (includeSharedRegistries || RegistrySynchronization.isNetworkable(prepared.key())) {
prepared.apply();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
]
}
Original file line number Diff line number Diff line change
@@ -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.api.holder.component.v1;

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() {
throw new UnsupportedOperationException("Implemented via mixin.");
}
}
Original file line number Diff line number Diff line change
@@ -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 [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);

interface Context {
HolderLookup.Provider lookupProvider();
ResourceManager resourceManager();
Comment thread
CallMeEchoCodes marked this conversation as resolved.

DataComponentMap.Builder builder(ResourceKey<?> key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs much better docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs have not been written, that is one of the final things I need to do before this is done

/// @see FabricDataComponentInitializers#addInitializerOrdering(Identifier, Identifier)
public static final Identifier DATA_HOLDER_COMPONENTS = Identifier.fromNamespaceAndPath("fabric", "data_holder_components");

private FabricDataComponentInitializers() {
}

public static void registerInitializer(Identifier id, FabricDataComponentInitializer initializer) {
FabricDataComponentInitializersImpl.registerInitializer(id, initializer);
}

public static void addInitializerOrdering(Identifier first, Identifier second) {
FabricDataComponentInitializersImpl.addInitializerOrdering(first, second);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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;
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.");
}

default DataComponentMap.Builder clear() {
throw new UnsupportedOperationException("Implemented via mixin.");
}
}
Loading