mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-03 10:39:57 -04:00
Add dynamic registry API (#3163)
* Add API for adding custom dynamic registries Closes #1012, supersedes #1031 and #2719. * Add missing license headers * Clarify RegistryLoaderMixin namespace injection * Replace event with static registration, add skeleton for sorting registries * Fix typo * Refactor event phase sorting system for use with dynamic registries (#1) * Make minor changes to Technici4n's PR * Add test for nested dynamic objects * Revert "Add test for nested dynamic objects" This reverts commit486e3e1ce0
. * Revert "Make minor changes to Technici4n's PR" This reverts commit741bd52c1e
. * Revert "Refactor event phase sorting system for use with dynamic registries (#1)" This reverts commitbb7c8b8790
. * Remove sorting API * Add support for defaulted dynamic registries * Re-add test for nested dynamic objects * Add missing license headers * Fix typo * Remove defaulted dynamic registries; flatten registration methods * Remove last reference to registry sorting * Add option to skip syncing for empty dynregs * Update DynamicRegistrySyncOption docs Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com> * Address review feedback * Add registry namespace to tag paths for modded registries * Move dynamic registry tests into their own class for readibility * Finish DynamicRegistries doc * Only apply tag change to dynamic registries * Fix checkstyle * Update fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/DynamicRegistries.java Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com> --------- Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
This commit is contained in:
parent
9386d8a793
commit
2e061fd481
20 changed files with 664 additions and 2 deletions
fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider
fabric-registry-sync-v0/src
main
java/net/fabricmc/fabric
api/event/registry
impl/registry/sync
mixin/registry/sync
resources
testmod
java/net/fabricmc/fabric/test/registry/sync
resources
data/fabric-registry-sync-v0-testmod
fabric
test_dynamic
test_dynamic_nested
test_dynamic_synced_1
test_dynamic_synced_2
tags/fabric/test_dynamic_synced_1
testmodClient/java/net/fabricmc/fabric/test/registry/sync/client
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
"RegistriesAccessor",
|
||||
"RegistriesMixin",
|
||||
"RegistryLoaderMixin",
|
||||
"SaveLoadingMixin",
|
||||
"SerializableRegistriesMixin",
|
||||
"SimpleRegistryMixin",
|
||||
"StructuresToConfiguredStructuresFixMixin"
|
||||
"StructuresToConfiguredStructuresFixMixin",
|
||||
"TagManagerLoaderMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "First"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "Second"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"nested": "fabric-registry-sync-v0-testmod:synced"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "Synced #1"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "Synced #2"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"fabric-registry-sync-v0-testmod:synced"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue