diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java index 71588502d..08c510757 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricDynamicRegistryProvider.java @@ -51,6 +51,7 @@ import net.minecraft.world.gen.carver.ConfiguredCarver; import net.minecraft.world.gen.feature.PlacedFeature; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; /** * A provider to help with data-generation of dynamic registry objects, @@ -79,7 +80,7 @@ public abstract class FabricDynamicRegistryProvider implements DataProvider { @ApiStatus.Internal Entries(RegistryWrapper.WrapperLookup registries, String modId) { this.registries = registries; - this.queuedEntries = RegistryLoader.DYNAMIC_REGISTRIES.stream() + this.queuedEntries = DynamicRegistries.getDynamicRegistries().stream() .collect(Collectors.toMap( e -> e.key().getValue(), e -> RegistryEntries.create(registries, e) diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java new file mode 100644 index 000000000..1c52338ca --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java @@ -0,0 +1,154 @@ +/* + * 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.event.registry; + +import java.util.List; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.Unmodifiable; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryLoader; + +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; + +/** + * Contains methods for registering and accessing dynamic {@linkplain Registry registries}. + * + * <h2>Basic usage</h2> + * Custom dynamic registries can be registered with {@link #register(RegistryKey, Codec)}. These registries will not be + * <a href="#sync">synced to the client</a>. + * + * <p>The list of all dynamic registries, whether from vanilla or mods, can be accessed using + * {@link #getDynamicRegistries()}. + * + * <h2 id="sync">Synchronization</h2> + * Dynamic registries are not synchronized to the client by default. + * To register a <em>synced dynamic registry</em>, you can replace the {@link #register} call + * with a call to {@link #registerSynced(RegistryKey, Codec, SyncOption...)}. + * + * <p>If you want to use a different codec for syncing, e.g. to skip unnecessary data, + * you can use the overload with two codecs: {@link #registerSynced(RegistryKey, Codec, Codec, SyncOption...)}. + * + * <p>Synced dynamic registries can also be prevented from syncing if they have no entries. + * This is useful for compatibility with clients that might not have your dynamic registry. + * This behavior can be enabled by passing the {@link SyncOption#SKIP_WHEN_EMPTY} flag to {@code registerSynced}. + * + * <h2>Examples</h2> + * {@snippet : + * // @link region substring=RegistryKey target=RegistryKey + * // @link region substring=ofRegistry target="RegistryKey#ofRegistry" + * // @link region substring=Identifier target="net.minecraft.util.Identifier#Identifier(String, String)" + * public static final RegistryKey<Registry<MyData>> MY_DATA_KEY = RegistryKey.ofRegistry(new Identifier("my_mod", "my_data")); + * // @end @end @end + * + * // Option 1: Register a non-synced registry + * // @link substring=register target="#register": + * DynamicRegistries.register(MY_DATA_KEY, MyData.CODEC); + * + * // Option 2a: Register a synced registry + * // @link substring=registerSynced target="#registerSynced(RegistryKey, Codec, SyncOption...)": + * DynamicRegistries.registerSynced(MY_DATA_KEY, MyData.CODEC); + * + * // Option 2b: Register a synced registry with a different network codec + * // @link substring=registerSynced target="#registerSynced(RegistryKey, Codec, Codec, SyncOption...)": + * DynamicRegistries.registerSynced(MY_DATA_KEY, MyData.CODEC, MyData.NETWORK_CODEC); + * } + */ +public final class DynamicRegistries { + private DynamicRegistries() { + } + + /** + * Returns an unmodifiable list of all dynamic registries, including modded ones. + * + * <p>The list will not reflect any changes caused by later registrations. + * + * @return an unmodifiable list of all dynamic registries + */ + public static @Unmodifiable List<RegistryLoader.Entry<?>> getDynamicRegistries() { + return DynamicRegistriesImpl.getDynamicRegistries(); + } + + /** + * Registers a non-synced dynamic registry. + * + * <p>The entries of the registry will be loaded from data packs at the file path + * {@code data/<entry namespace>/<registry namespace>/<registry path>/<entry path>.json}. + * + * @param key the unique key of the registry + * @param codec the codec used to load registry entries from data packs + * @param <T> the entry type of the registry + */ + public static <T> void register(RegistryKey<? extends Registry<T>> key, Codec<T> codec) { + DynamicRegistriesImpl.register(key, codec); + } + + /** + * Registers a synced dynamic registry. + * + * <p>The entries of the registry will be loaded from data packs at the file path + * {@code data/<entry namespace>/<registry namespace>/<registry path>/<entry path>.json}. + * + * <p>The registry will be synced from the server to players' clients using the same codec + * that is used to load the registry. + * + * <p>If the object contained in the registry is complex and contains a lot of data + * that is not relevant on the client, another codec for networking can be specified with + * {@link #registerSynced(RegistryKey, Codec, Codec, SyncOption...)}. + * + * @param key the unique key of the registry + * @param codec the codec used to load registry entries from data packs and the network + * @param options options to configure syncing + * @param <T> the entry type of the registry + */ + public static <T> void registerSynced(RegistryKey<? extends Registry<T>> key, Codec<T> codec, SyncOption... options) { + registerSynced(key, codec, codec, options); + } + + /** + * Registers a synced dynamic registry. + * + * <p>The entries of the registry will be loaded from data packs at the file path + * {@code data/<entry namespace>/<registry namespace>/<registry path>/<entry path>.json} + * + * <p>The registry will be synced from the server to players' clients using the given network codec. + * + * @param key the unique key of the registry + * @param dataCodec the codec used to load registry entries from data packs + * @param networkCodec the codec used to load registry entries from the network + * @param options options to configure syncing + * @param <T> the entry type of the registry + */ + public static <T> void registerSynced(RegistryKey<? extends Registry<T>> key, Codec<T> dataCodec, Codec<T> networkCodec, SyncOption... options) { + DynamicRegistriesImpl.register(key, dataCodec); + DynamicRegistriesImpl.addSyncedRegistry(key, networkCodec, options); + } + + /** + * Flags for configuring dynamic registry syncing. + */ + public enum SyncOption { + /** + * Only synchronizes the dynamic registry if it's not empty. + * This is useful for compatibility with vanilla clients, + * or other clients that might not have the registry. + */ + SKIP_WHEN_EMPTY + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java new file mode 100644 index 000000000..dacbb565d --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistriesImpl.java @@ -0,0 +1,83 @@ +/* + * 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.registry.sync; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.Unmodifiable; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryLoader; +import net.minecraft.registry.SerializableRegistries; + +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; + +public final class DynamicRegistriesImpl { + private static final List<RegistryLoader.Entry<?>> DYNAMIC_REGISTRIES = new ArrayList<>(RegistryLoader.DYNAMIC_REGISTRIES); + public static final Set<RegistryKey<? extends Registry<?>>> DYNAMIC_REGISTRY_KEYS = new HashSet<>(); + public static final Set<RegistryKey<? extends Registry<?>>> SKIP_EMPTY_SYNC_REGISTRIES = new HashSet<>(); + + static { + for (RegistryLoader.Entry<?> vanillaEntry : RegistryLoader.DYNAMIC_REGISTRIES) { + DYNAMIC_REGISTRY_KEYS.add(vanillaEntry.key()); + } + } + + private DynamicRegistriesImpl() { + } + + public static @Unmodifiable List<RegistryLoader.Entry<?>> getDynamicRegistries() { + return List.copyOf(DYNAMIC_REGISTRIES); + } + + public static <T> void register(RegistryKey<? extends Registry<T>> key, Codec<T> codec) { + Objects.requireNonNull(key, "Registry key cannot be null"); + Objects.requireNonNull(codec, "Codec cannot be null"); + + if (!DYNAMIC_REGISTRY_KEYS.add(key)) { + throw new IllegalArgumentException("Dynamic registry " + key + " has already been registered!"); + } + + var entry = new RegistryLoader.Entry<>(key, codec); + DYNAMIC_REGISTRIES.add(entry); + } + + public static <T> void addSyncedRegistry(RegistryKey<? extends Registry<T>> registryKey, Codec<T> networkCodec, DynamicRegistries.SyncOption... options) { + Objects.requireNonNull(registryKey, "Registry key cannot be null"); + Objects.requireNonNull(networkCodec, "Network codec cannot be null"); + Objects.requireNonNull(options, "Options cannot be null"); + + if (!(SerializableRegistries.REGISTRIES instanceof HashMap<?, ?>)) { + SerializableRegistries.REGISTRIES = new HashMap<>(SerializableRegistries.REGISTRIES); + } + + SerializableRegistries.REGISTRIES.put(registryKey, new SerializableRegistries.Info<>(registryKey, networkCodec)); + + for (DynamicRegistries.SyncOption option : options) { + if (option == DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY) { + SKIP_EMPTY_SYNC_REGISTRIES.add(registryKey); + } + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java index 5486ea43f..33f93359e 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/RegistryLoaderMixin.java @@ -34,6 +34,7 @@ import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryLoader; import net.minecraft.registry.RegistryOps; import net.minecraft.resource.ResourceManager; +import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; import net.fabricmc.fabric.impl.registry.sync.DynamicRegistryViewImpl; @@ -58,4 +59,14 @@ public class RegistryLoaderMixin { DynamicRegistrySetupCallback.EVENT.invoker().onRegistrySetup(new DynamicRegistryViewImpl(registries)); } + + // Vanilla doesn't mark namespaces in the directories of dynamic registries at all, + // so we prepend the directories with the namespace if it's a modded registry id. + @Inject(method = "getPath", at = @At("RETURN"), cancellable = true) + private static void prependDirectoryWithNamespace(Identifier id, CallbackInfoReturnable<String> info) { + if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) { + String newPath = id.getNamespace() + "/" + info.getReturnValue(); + info.setReturnValue(newPath); + } + } } diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.java new file mode 100644 index 000000000..1366f2ea7 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SaveLoadingMixin.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.mixin.registry.sync; + +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import net.minecraft.registry.RegistryLoader; +import net.minecraft.server.SaveLoading; + +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; + +// Implements dynamic registry loading. +@Mixin(SaveLoading.class) +abstract class SaveLoadingMixin { + @ModifyArg(method = "load", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/SaveLoading;withRegistriesLoaded(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/registry/CombinedDynamicRegistries;Lnet/minecraft/registry/ServerDynamicRegistryType;Ljava/util/List;)Lnet/minecraft/registry/CombinedDynamicRegistries;"), allow = 1) + private static List<RegistryLoader.Entry<?>> modifyLoadedEntries(List<RegistryLoader.Entry<?>> entries) { + return DynamicRegistries.getDynamicRegistries(); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java new file mode 100644 index 000000000..3224d62a3 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/SerializableRegistriesMixin.java @@ -0,0 +1,48 @@ +/* + * 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.registry.sync; + +import java.util.stream.Stream; + +import org.spongepowered.asm.mixin.Dynamic; +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.Redirect; + +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.SerializableRegistries; + +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; + +// Implements skipping empty dynamic registries with the SKIP_WHEN_EMPTY sync option. +@Mixin(SerializableRegistries.class) +abstract class SerializableRegistriesMixin { + @Shadow + private static Stream<DynamicRegistryManager.Entry<?>> stream(DynamicRegistryManager dynamicRegistryManager) { + return null; + } + + @Dynamic("method_45961: Codec.xmap in createDynamicRegistryManagerCodec") + @Redirect(method = "method_45961", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/SerializableRegistries;stream(Lnet/minecraft/registry/DynamicRegistryManager;)Ljava/util/stream/Stream;")) + private static Stream<DynamicRegistryManager.Entry<?>> filterNonSyncedEntries(DynamicRegistryManager drm) { + return stream(drm).filter(entry -> { + boolean canSkip = DynamicRegistriesImpl.SKIP_EMPTY_SYNC_REGISTRIES.contains(entry.key()); + return !canSkip || entry.value().size() > 0; + }); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java new file mode 100644 index 000000000..2e1b78fdd --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/TagManagerLoaderMixin.java @@ -0,0 +1,49 @@ +/* + * 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.registry.sync; + +import org.spongepowered.asm.mixin.Mixin; +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.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.tag.TagManagerLoader; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl; + +// Adds namespaces to tag directories for registries added by mods. +@Mixin(TagManagerLoader.class) +abstract class TagManagerLoaderMixin { + @Inject(method = "getPath", at = @At("HEAD"), cancellable = true) + private static void onGetPath(RegistryKey<? extends Registry<?>> registry, CallbackInfoReturnable<String> info) { + // TODO: Expand this change to static registries in the future. + if (!DynamicRegistriesImpl.DYNAMIC_REGISTRY_KEYS.contains(registry)) { + return; + } + + Identifier id = registry.getValue(); + + // Vanilla doesn't mark namespaces in the directories of tags at all, + // so we prepend the directories with the namespace if it's a modded registry id. + if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) { + info.setReturnValue("tags/" + id.getNamespace() + "/" + id.getPath()); + } + } +} diff --git a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener index 62858b64b..18f96ed90 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener +++ b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.accesswidener @@ -3,3 +3,8 @@ accessWidener v2 named accessible field net/minecraft/registry/SimpleRegistry frozen Z accessible method net/minecraft/registry/entry/RegistryEntry$Reference setValue (Ljava/lang/Object;)V accessible method net/minecraft/registry/Registries init ()V + +accessible class net/minecraft/registry/SerializableRegistries$Info +accessible method net/minecraft/registry/SerializableRegistries$Info <init> (Lnet/minecraft/registry/RegistryKey;Lcom/mojang/serialization/Codec;)V +accessible field net/minecraft/registry/SerializableRegistries REGISTRIES Ljava/util/Map; +mutable field net/minecraft/registry/SerializableRegistries REGISTRIES Ljava/util/Map; diff --git a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json index 5d4dc01cf..f028c3ad4 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json +++ b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json @@ -12,8 +12,11 @@ "RegistriesAccessor", "RegistriesMixin", "RegistryLoaderMixin", + "SaveLoadingMixin", + "SerializableRegistriesMixin", "SimpleRegistryMixin", - "StructuresToConfiguredStructuresFixMixin" + "StructuresToConfiguredStructuresFixMixin", + "TagManagerLoaderMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java new file mode 100644 index 000000000..1971cf7e9 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomDynamicRegistryTest.java @@ -0,0 +1,87 @@ +/* + * 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.registry.sync; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents; +import net.fabricmc.fabric.api.event.registry.DynamicRegistries; +import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import net.fabricmc.fabric.api.event.registry.DynamicRegistryView; + +public final class CustomDynamicRegistryTest implements ModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static final RegistryKey<Registry<TestDynamicObject>> TEST_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic")); + public static final RegistryKey<Registry<TestNestedDynamicObject>> TEST_NESTED_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_nested")); + public static final RegistryKey<Registry<TestDynamicObject>> TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_1")); + public static final RegistryKey<Registry<TestDynamicObject>> TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_2")); + public static final RegistryKey<Registry<TestDynamicObject>> TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY = + RegistryKey.ofRegistry(new Identifier("fabric", "test_dynamic_synced_empty")); + + private static final RegistryKey<TestDynamicObject> SYNCED_ENTRY_KEY = + RegistryKey.of(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, new Identifier("fabric-registry-sync-v0-testmod", "synced")); + private static final TagKey<TestDynamicObject> TEST_DYNAMIC_OBJECT_TAG = + TagKey.of(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, new Identifier("fabric-registry-sync-v0-testmod", "test")); + + @Override + public void onInitialize() { + DynamicRegistries.register(TEST_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC, TestDynamicObject.NETWORK_CODEC); + DynamicRegistries.registerSynced(TEST_NESTED_DYNAMIC_REGISTRY_KEY, TestNestedDynamicObject.CODEC); + DynamicRegistries.registerSynced(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC, DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY); + + DynamicRegistrySetupCallback.EVENT.register(registryView -> { + addListenerForDynamic(registryView, TEST_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY); + addListenerForDynamic(registryView, TEST_NESTED_DYNAMIC_REGISTRY_KEY); + }); + + CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { + // Check that the tag has applied + RegistryEntry.Reference<TestDynamicObject> entry = registries.get(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY) + .getEntry(SYNCED_ENTRY_KEY) + .orElseThrow(); + + if (!entry.isIn(TEST_DYNAMIC_OBJECT_TAG)) { + throw new AssertionError("Required dynamic registry entry is not in the expected tag! client: " + client); + } + + LOGGER.info("Found {} in tag {} (client: {})", entry, TEST_DYNAMIC_OBJECT_TAG, client); + }); + } + + private static void addListenerForDynamic(DynamicRegistryView registryView, RegistryKey<? extends Registry<?>> key) { + registryView.registerEntryAdded(key, (rawId, id, object) -> { + LOGGER.info("Loaded entry of {}: {} = {}", key, id, object); + }); + } +} diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java new file mode 100644 index 000000000..403ed57c4 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestDynamicObject.java @@ -0,0 +1,31 @@ +/* + * 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.registry.sync; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +public record TestDynamicObject(String name, boolean usesNetworkCodec) { + public static final Codec<TestDynamicObject> CODEC = codec(false); + public static final Codec<TestDynamicObject> NETWORK_CODEC = codec(true); + + private static Codec<TestDynamicObject> codec(boolean networkCodec) { + return RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(TestDynamicObject::name) + ).apply(instance, name -> new TestDynamicObject(name, networkCodec))); + } +} diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java new file mode 100644 index 000000000..572ea4a13 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/TestNestedDynamicObject.java @@ -0,0 +1,31 @@ +/* + * 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.registry.sync; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.registry.entry.RegistryElementCodec; +import net.minecraft.registry.entry.RegistryEntry; + +public record TestNestedDynamicObject(RegistryEntry<TestDynamicObject> nested) { + public static final Codec<TestNestedDynamicObject> CODEC = RecordCodecBuilder.create(instance -> instance.group( + RegistryElementCodec.of(CustomDynamicRegistryTest.TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, TestDynamicObject.CODEC) + .fieldOf("nested") + .forGetter(TestNestedDynamicObject::nested) + ).apply(instance, TestNestedDynamicObject::new)); +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json new file mode 100644 index 000000000..23ada4c50 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/first.json @@ -0,0 +1,3 @@ +{ + "name": "First" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json new file mode 100644 index 000000000..5e5eddfe3 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic/second.json @@ -0,0 +1,3 @@ +{ + "name": "Second" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json new file mode 100644 index 000000000..fc51ec72d --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_nested/synced.json @@ -0,0 +1,3 @@ +{ + "nested": "fabric-registry-sync-v0-testmod:synced" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json new file mode 100644 index 000000000..3bddd0ded --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_1/synced.json @@ -0,0 +1,3 @@ +{ + "name": "Synced #1" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json new file mode 100644 index 000000000..80e67782f --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/fabric/test_dynamic_synced_2/synced.json @@ -0,0 +1,3 @@ +{ + "name": "Synced #2" +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json new file mode 100644 index 000000000..d892584a3 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_1/test.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "fabric-registry-sync-v0-testmod:synced" + ] +} diff --git a/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json b/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json index 93c6c7229..bd89658c1 100644 --- a/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json +++ b/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json @@ -10,7 +10,11 @@ }, "entrypoints": { "main": [ + "net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest", "net.fabricmc.fabric.test.registry.sync.RegistrySyncTest" + ], + "client": [ + "net.fabricmc.fabric.test.registry.sync.client.DynamicRegistryClientTest" ] } } diff --git a/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java b/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java new file mode 100644 index 000000000..b72d033ce --- /dev/null +++ b/fabric-registry-sync-v0/src/testmodClient/java/net/fabricmc/fabric/test/registry/sync/client/DynamicRegistryClientTest.java @@ -0,0 +1,97 @@ +/* + * 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.registry.sync.client; + +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_NESTED_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY; +import static net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest.TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.test.registry.sync.TestDynamicObject; +import net.fabricmc.fabric.test.registry.sync.TestNestedDynamicObject; + +public final class DynamicRegistryClientTest implements ClientModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Identifier SYNCED_ID = new Identifier("fabric-registry-sync-v0-testmod", "synced"); + + @Override + public void onInitializeClient() { + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { + LOGGER.info("Starting dynamic registry sync tests..."); + + TestDynamicObject synced1 = handler.getRegistryManager() + .get(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + TestDynamicObject synced2 = handler.getRegistryManager() + .get(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + TestNestedDynamicObject simpleNested = handler.getRegistryManager() + .get(TEST_NESTED_DYNAMIC_REGISTRY_KEY) + .get(SYNCED_ID); + + LOGGER.info("Synced - simple: {}", synced1); + LOGGER.info("Synced - custom network codec: {}", synced2); + LOGGER.info("Synced - simple nested: {}", simpleNested); + + if (synced1 == null) { + didNotReceive(TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + } + + if (synced1.usesNetworkCodec()) { + throw new AssertionError("Entries in " + TEST_SYNCED_1_DYNAMIC_REGISTRY_KEY + " should not use network codec"); + } + + if (synced2 == null) { + didNotReceive(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + } + + // The client server check is needed since the registries are passed through in singleplayer. + // The network codec flag would always be false in those cases. + if (client.getServer() == null && !synced2.usesNetworkCodec()) { + throw new AssertionError("Entries in " + TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY + " should use network codec"); + } + + if (simpleNested == null) { + didNotReceive(TEST_NESTED_DYNAMIC_REGISTRY_KEY, SYNCED_ID); + } + + if (simpleNested.nested().value() != synced1) { + throw new AssertionError("Did not match up synced nested entry to the other synced value"); + } + + // If the registries weren't passed through in SP, check that the empty registry was skipped. + if (client.getServer() == null && handler.getRegistryManager().getOptional(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY).isPresent()) { + throw new AssertionError("Received empty registry that should have been skipped"); + } + + LOGGER.info("Dynamic registry sync tests passed!"); + }); + } + + private static void didNotReceive(RegistryKey<? extends Registry<?>> registryKey, Identifier entryId) { + throw new AssertionError("Did not receive " + registryKey + "/" + entryId); + } +}