diff --git a/fabric-holder-component-api-v1/build.gradle b/fabric-holder-component-api-v1/build.gradle new file mode 100644 index 00000000000..cc4c88be687 --- /dev/null +++ b/fabric-holder-component-api-v1/build.gradle @@ -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' +]) 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..419aeb186ab --- /dev/null +++ b/fabric-holder-component-api-v1/src/client/java/net/fabricmc/fabric/impl/client/holder/component/HolderComponentClientEntrypoint.java @@ -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); + } + ); + + ClientPlayNetworking.registerGlobalReceiver( + ClientboundUpdateComponentsPayload.TYPE, + (payload, context) -> { + List> 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); + } + } + ); + } +} 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..9be0f40680a --- /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.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> 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 cir, + @Local(name = "frozenRegistries") RegistryAccess.Frozen frozenRegistries + ) { + List> pending = HolderComponentSynchronization.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/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..2c86ff2702b --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentHolder.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.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."); + } +} 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..0f0fb04ecfd --- /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 [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(); + + 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..1c4ec6b34f3 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentInitializers.java @@ -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 { + /// @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); + } +} 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..5eabac99c84 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/FabricDataComponentMapBuilder.java @@ -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."); + } +} 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..4b3653e9ba1 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/api/holder/component/v1/package-info.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. + */ + +/** + * API code for fabric-holder-component-api-v1. + * + *

Experimental feature, may be removed or changed without further notice. + */ +@NullMarked +@ApiStatus.Experimental +package net.fabricmc.fabric.api.holder.component.v1; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; 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..752e0616468 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitContextImpl.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.impl.holder.component; + +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 net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; + +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..7cad86e53b6 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/FabricDataComponentInitializersImpl.java @@ -0,0 +1,112 @@ +/* + * 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.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +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 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<>(); + 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 new file mode 100644 index 00000000000..d05faf085f2 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/HolderComponentEntrypoint.java @@ -0,0 +1,54 @@ +/* + * 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.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; + +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( + 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 new file mode 100644 index 00000000000..dd36c95a39c --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentFile.java @@ -0,0 +1,33 @@ +/* + * 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.data; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.core.component.DataComponentPatch; + +// TODO: Make public api and use for datagen +public record DataHolderComponentFile( + boolean replace, + DataComponentPatch components +) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.optionalFieldOf("replace", false).forGetter(DataHolderComponentFile::replace), + 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 new file mode 100644 index 00000000000..e2a27c12051 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/data/DataHolderComponentInitializer.java @@ -0,0 +1,96 @@ +/* + * 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.data; + +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.JsonOps; +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; +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 net.fabricmc.fabric.api.holder.component.v1.FabricDataComponentInitializer; + +public class DataHolderComponentInitializer implements FabricDataComponentInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public void run(Context context) { + RegistryOps ops = context.lookupProvider().createSerializationContext(JsonOps.INSTANCE); + + context.lookupProvider().listRegistryKeys().forEach(key -> parse(context, ops, key)); + } + + private void parse( + Context context, + 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("fabric/" + Registries.componentsDirPath(lookup.key())); + + for (Map.Entry> entry : lister.listMatchingResourceStacks(context.resourceManager()).entrySet()) { + Identifier location = entry.getKey(); + Identifier id = lister.fileToId(location); + + lookup.get(ResourceKey.create(key, id)).ifPresent(holder -> { + parse(context, ops, entry.getValue(), holder, location, id); + }); + } + } + + private void parse( + Context context, + RegistryOps ops, + List resources, + Holder.Reference holder, + Identifier location, + Identifier id + ) { + for (Resource resource : resources) { + try (Reader reader = resource.openAsReader()) { + JsonElement element = StrictJsonParser.parse(reader); + + 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/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..33d0c4d9b19 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/ClientboundUpdateComponentsPayload.java @@ -0,0 +1,71 @@ +/* + * 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.List; +import java.util.Map; + +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; +import net.minecraft.network.codec.StreamCodec; +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 + // 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( + Reference2ObjectOpenHashMap::new, + StreamCodec.ofMember( + (key, buf) -> buf.writeResourceKey(key), + FriendlyByteBuf::readRegistryKey + ), + ByteBufCodecs.map( + Object2ObjectOpenHashMap::new, + Identifier.STREAM_CODEC, + HolderComponentSynchronization.PackedComponentMap.STREAM_CODEC.apply(ByteBufCodecs.list()) + ) + ).map(ClientboundUpdateComponentsPayload::new, ClientboundUpdateComponentsPayload::registryToComponents); + + @Override + 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/impl/holder/component/sync/HolderComponentSynchronization.java b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java new file mode 100644 index 00000000000..9ba54a3fab4 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/impl/holder/component/sync/HolderComponentSynchronization.java @@ -0,0 +1,166 @@ +/* + * 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 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; +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.component.TypedDataComponent; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.RegistryLayer; + +public class HolderComponentSynchronization { + public static ClientboundUpdateComponentsPayload serialize(LayeredRegistryAccess registries) { + return new ClientboundUpdateComponentsPayload( + RegistrySynchronization.networkSafeRegistries(registries) + .collect(Collectors.toMap( + RegistryAccess.RegistryEntry::key, + entry -> serialize(registries.compositeAccess(), entry.value()) + )) + ); + } + + private static Map> serialize( + RegistryAccess registries, + Registry registry + ) { + Map> result = new HashMap<>(); + + registry.listElements().forEach(holder -> { + if (holder.components().isEmpty()) return; + + 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, + buf + ) + ); + } + }); + + 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); + } + } + + public static List> deserialize( + ClientboundUpdateComponentsPayload payload, + RegistryAccess registries + ) { + List> result = new ArrayList<>(); + + payload.registryToComponents().forEach((registryKey, holderComponents) -> { + result.add(deserialize(registries, registries.lookupOrThrow(registryKey), holderComponents)); + }); + + return result; + } + + private static DataComponentInitializers.PendingComponents deserialize(RegistryAccess registries, Registry registry, Map> holderComponents) { + List> entries = new ArrayList<>(); + + holderComponents.forEach((id, components) -> { + DataComponentMap.Builder builder = DataComponentMap.builder(); + + for (PackedComponentMap map : components) { + DataComponentType type = BuiltInRegistries.DATA_COMPONENT_TYPE.getOptional(map.id).orElseThrow(); + parse(registries, type, map.data, builder); + + map.data.release(); + } + + 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(RegistryAccess registries, DataComponentType type, ByteBuf buf, DataComponentMap.Builder builder) { + T result = type.streamCodec().decode(new RegistryFriendlyByteBuf(buf, registries)); + builder.set( + type, + result + ); + } + + public record PackedComponentMap(Identifier id, ByteBuf data) { + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Identifier.STREAM_CODEC, PackedComponentMap::id, + 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/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..ec94cee707b --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/DataComponentInitializersMixin.java @@ -0,0 +1,68 @@ +/* + * 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 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; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.resources.ResourceManager; + +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 { + @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()) { + // 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; + } + + ResourceManager resourceManager = FabricDataComponentInitializersImpl.RESOURCE_MANAGER.get(); + + FabricDataComponentInitializer.Context context = new FabricDataComponentInitContextImpl( + holders, + resourceManager, + original + ); + + for (FabricDataComponentInitializer initializer : FabricDataComponentInitializersImpl.sort()) { + initializer.run(context); + } + + 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 new file mode 100644 index 00000000000..9f93c19ce66 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/PlayerListMixin.java @@ -0,0 +1,64 @@ +/* + * 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.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; +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.server.RegistryLayer; +import net.minecraft.server.players.PlayerList; + +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 + @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) { + Packet payload = ServerPlayNetworking.createClientboundPacket( + HolderComponentSynchronization.serialize(registries) + ); + + for (ServerPlayer player : this.players) { + if (ClientboundUpdateComponentsPayload.shouldSend(player)) { + player.connection.send(payload); + } + } + } +} 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..e7f518090c2 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/ReloadableServerResourcesMixin.java @@ -0,0 +1,50 @@ +/* + * 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; +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 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;")) + private static CompletableFuture wrapWithScopedValue( + Supplier supplier, + Executor executor, + Operation> original, + @Local(argsOnly = true) 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/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..6d929add631 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/SynchronizeRegistriesTaskMixin.java @@ -0,0 +1,55 @@ +/* + * 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 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.server.RegistryLayer; +import net.minecraft.server.network.config.SynchronizeRegistriesTask; +import net.minecraft.server.packs.repository.KnownPack; + +import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.holder.component.sync.HolderComponentSynchronization; + +@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 + ) { + connection.accept(ServerConfigurationNetworking.createClientboundPacket(HolderComponentSynchronization.serialize(registries))); + } +} 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..39e20bef1f2 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockEntityTypeMixin.java @@ -0,0 +1,38 @@ +/* + * 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 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; + +@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..56473f72947 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/BlockMixin.java @@ -0,0 +1,38 @@ +/* + * 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 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; + +@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/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..b3d6011f8bb --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/DataComponentMap$BuilderMixin.java @@ -0,0 +1,64 @@ +/* + * 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 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 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 { + @Shadow + public abstract DataComponentMap.Builder set(DataComponentType type, @Nullable T value); + + @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()) { + entry.getValue().ifPresentOrElse( + value -> this.setUnchecked(entry.getKey(), value), + () -> this.set(entry.getKey(), null) + ); + } + + return (DataComponentMap.Builder) (Object) this; + } + + @Override + public DataComponentMap.Builder clear() { + map.clear(); + + return (DataComponentMap.Builder) (Object) this; + } +} 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..318c5b07713 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/EntityTypeMixin.java @@ -0,0 +1,38 @@ +/* + * 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 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; + +@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..0a47739377f --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/FluidMixin.java @@ -0,0 +1,38 @@ +/* + * 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 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; + +@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..3828242f7e6 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/HolderMixin.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.mixin.holder.component.extension; + +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; + +@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..a14626f6292 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/java/net/fabricmc/fabric/mixin/holder/component/extension/ItemMixin.java @@ -0,0 +1,38 @@ +/* + * 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 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; + +@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/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 00000000000..12c4531de92 Binary files /dev/null and b/fabric-holder-component-api-v1/src/main/resources/assets/fabric-holder-component-api-v1/icon.png differ 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..8e7cd47c7f5 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.classtweaker @@ -0,0 +1,10 @@ +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 +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 + 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 new file mode 100644 index 00000000000..0a58d510447 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/resources/fabric-holder-component-api-v1.mixins.json @@ -0,0 +1,24 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.holder.component", + "compatibilityLevel": "JAVA_25", + "mixins": [ + "DataComponentInitializersMixin", + "PlayerListMixin", + "ReloadableServerResourcesMixin", + "SynchronizeRegistriesTaskMixin", + "extension.BlockEntityTypeMixin", + "extension.BlockMixin", + "extension.DataComponentMap$BuilderMixin", + "extension.EntityTypeMixin", + "extension.FluidMixin", + "extension.HolderMixin", + "extension.ItemMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + } +} 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 new file mode 100644 index 00000000000..3a24399e030 --- /dev/null +++ b/fabric-holder-component-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,43 @@ +{ + "schemaVersion": 1, + "id": "fabric-holder-component-api-v1", + "name": "Fabric Holder Component API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-holder-component-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.18.4", + "fabric-api-base": "*", + "fabric-networking-api-v1": "*" + }, + "description": "Allows conveniently attaching data to holders via datapack", + "mixins": [ + "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", + "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..adde0ea3dd0 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentCommand.java @@ -0,0 +1,172 @@ +/* + * 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; +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; + +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 -> { + Object val = holder.components().get(component); + + if (val != null) { + DataComponentPatch.Builder 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/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/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..0ecf57f36c8 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/java/net/fabricmc/fabric/test/holder/component/HolderComponentTestModEntrypoint.java @@ -0,0 +1,50 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.resources.Identifier; + +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"; + 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); + } +} diff --git a/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/components/block/acacia_slab.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/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/fabric/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/fabric/components/item/diamond_sword.json b/fabric-holder-component-api-v1/src/testmod/resources/data/minecraft/fabric/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/fabric/components/item/diamond_sword.json @@ -0,0 +1,6 @@ +{ + "components": { + "minecraft:rarity": "rare", + "!minecraft:item_model": {} + } +} 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..f1e754cdb44 --- /dev/null +++ b/fabric-holder-component-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,16 @@ +{ + "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" + ], + "fabric-gametest": [ + "net.fabricmc.fabric.test.holder.component.HolderComponentGameTest" + ] + } +} 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; + } } diff --git a/gradle.properties b/gradle.properties index 5dcbbe1e27e..e0484af2e93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,6 +29,7 @@ fabric-entity-events-v1-version=5.0.2 fabric-events-interaction-v0-version=5.2.0 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.1 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'