diff --git a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicMetaRegistry.java b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicMetaRegistry.java
index d5a5337240..bafe264f3a 100644
--- a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicMetaRegistry.java
+++ b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicMetaRegistry.java
@@ -36,6 +36,57 @@
* This registry will be frozen at the same time as static registries.
*/
public final class DynamicMetaRegistry {
+ /**
+ * Registers a server-side dynamic registry.
+ *
+ * Entries will be loaded from {@code "data///"} for every datapack
+ * {@code namespace}, where {@code registry_namespace} and {@code registry_path}'s values are respectively
+ * {@code key.getLocation().getNamespace()} and {@code key.getLocation().getPath()}.
+ *
+ * @param the type of elements in the dynamic registry
+ * @param key a {@link RegistryKey#ofRegistry(Identifier) key for the new dynamic registry}
+ * @param entryCodec the codec used to deserialize entries from datapacks
+ * @throws IllegalStateException if this registry of registries already got frozen
+ */
+ public static void register(RegistryKey extends Registry> key, Codec entryCodec, DynamicRegistryFlag... flags) {
+ DynamicMetaRegistryImpl.register(key, entryCodec, flags);
+ }
+
+ /**
+ * Registers a dynamic registry which contents get synced between the server and connected clients.
+ *
+ * Entries will be loaded from {@code "data///"} for every datapack
+ * {@code namespace}, where {@code registry_namespace} and {@code registry_path}'s values are respectively
+ * {@code key.getLocation().getNamespace()} and {@code key.getLocation().getPath()}.
+ *
+ * @param the type of elements in the dynamic registry
+ * @param key a {@link RegistryKey#ofRegistry(Identifier) key for the new dynamic registry}
+ * @param entryCodec the codec used to both deserialize entries from datapacks and (de)serialize entries to and from packets
+ * @throws IllegalStateException if this registry of registries already got frozen
+ * @see #registerSynced(RegistryKey, Codec, Codec, DynamicRegistryFlag...)
+ */
+ public static void registerSynced(RegistryKey extends Registry> key, Codec entryCodec, DynamicRegistryFlag... flags) {
+ DynamicMetaRegistryImpl.registerSynced(key, entryCodec, entryCodec, flags);
+ }
+
+ /**
+ * Registers a dynamic registry which contents get synced between the server and connected clients.
+ *
+ * Entries will be loaded from {@code "data///"} for every datapack
+ * {@code namespace}, where {@code registry_namespace} and {@code registry_path}'s values are respectively
+ * {@code key.getLocation().getNamespace()} and {@code key.getLocation().getPath()}.
+ *
+ * @param the type of elements in the dynamic registry
+ * @param key a {@link RegistryKey#ofRegistry(Identifier) key for the new dynamic registry}
+ * @param entryCodec the codec used to deserialize entries from datapacks
+ * @param syncCodec the codec used to (de)serialize entries to and from packets - may be the same as {@code entryCodec}
+ * @throws IllegalStateException if this registry of registries already got frozen
+ * @see #registerSynced(RegistryKey, Codec, DynamicRegistryFlag...)
+ */
+ public static void registerSynced(RegistryKey extends Registry> key, Codec entryCodec, Codec syncCodec, DynamicRegistryFlag... flags) {
+ DynamicMetaRegistryImpl.registerSynced(key, entryCodec, syncCodec, flags);
+ }
+
/**
* Registers a server-side dynamic registry.
*
diff --git a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicRegistryFlag.java b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicRegistryFlag.java
new file mode 100644
index 0000000000..dc9b3a56d2
--- /dev/null
+++ b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/api/dynamic/DynamicRegistryFlag.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 The Quilt Project
+ *
+ * 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 org.quiltmc.qsl.registry.api.dynamic;
+
+import net.minecraft.registry.RegistryKey;
+import net.minecraft.util.Identifier;
+
+import org.quiltmc.qsl.registry.impl.dynamic.DynamicMetaRegistryImpl;
+import org.quiltmc.qsl.registry.impl.dynamic.DynamicRegistryFlagManager;
+
+/**
+ * Flags that can be set on dynamic registries to define their behavior.
+ *
+ * All flags are off by default, and can be enabled using static methods in this class or on dynamic registry creation via varargs.
+ *
+ * @see org.quiltmc.qsl.registry.api.sync.RegistrySynchronization org.quiltmc.qsl.registry.api.sync.RegistrySynchronization,
+ * which contains similar flag setters/getters for static registries
+ */
+public enum DynamicRegistryFlag {
+ /**
+ * Indicates that this registry (and the entries within) do not necessarily need to be sent
+ * to (logical) clients for synchronization in multiplayer contexts.
+ *
+ * Note: This flag is intended only for synchronized dynamic registries. On non-synced dynamic registries, this flag does nothing.
+ *
+ * One use-case for this flag is for creating mods that are entirely compatible with vanilla, and thus do not
+ * require the dynamic registry to exist clientside to connect to the server.
+ * This allows for both vanilla clients/clients without the mod to connect and for clients with the mod
+ * supplying the registry to connect, with the latter being able to see the contents of the registry and possibly
+ * enable extra clientside features accordingly.
+ */
+ OPTIONAL;
+
+ /**
+ * Enables a specific flag on a dynamic registry.
+ *
+ * @param registryId the value id ({@link RegistryKey#getValue()}) of the target dynamic registry
+ * @param flag the flag value to enable on the dynamic registry
+ */
+ public static void setFlag(Identifier registryId, DynamicRegistryFlag flag) {
+ try {
+ DynamicRegistryFlagManager.setFlag(registryId, flag);
+ } catch (Exception e) {
+ logFlagModifyException(registryId, flag, e);
+ }
+ }
+
+ /**
+ * Checks if a dynamic registry has the {@link DynamicRegistryFlag#OPTIONAL} flag enabled on it.
+ *
+ * @param registryId the value id ({@link RegistryKey#getValue()}) of the dynamic registry to check
+ * @return whether the checked dynamic registry has the {@link DynamicRegistryFlag#OPTIONAL} flag enabled
+ */
+ public static boolean isOptional(Identifier registryId) {
+ return DynamicRegistryFlagManager.isOptional(registryId);
+ }
+
+ /**
+ * Helper method for logging exceptions to avoid code duplication.
+ */
+ private static void logFlagModifyException(Identifier registryId, DynamicRegistryFlag flag, Exception e) {
+ DynamicMetaRegistryImpl.LOGGER.error("Caught exception while attempting to enable flag {} on registry id {}: {}", flag.toString(), registryId, e.toString());
+ }
+}
diff --git a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicMetaRegistryImpl.java b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicMetaRegistryImpl.java
index 34721eaa8b..b6304e1548 100644
--- a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicMetaRegistryImpl.java
+++ b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicMetaRegistryImpl.java
@@ -22,6 +22,8 @@
import com.google.common.collect.ImmutableMap;
import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
@@ -29,24 +31,30 @@
import net.minecraft.util.Identifier;
import org.quiltmc.qsl.registry.mixin.DynamicRegistrySyncAccessor;
+import org.quiltmc.qsl.registry.api.dynamic.DynamicRegistryFlag;
@ApiStatus.Internal
public class DynamicMetaRegistryImpl {
private static boolean frozen;
private static final Set MODDED_REGISTRY_IDS = new HashSet<>();
+ public static final Logger LOGGER = LoggerFactory.getLogger("quilt_dynamic_registries");
+
public static boolean isModdedRegistryId(Identifier id) {
return MODDED_REGISTRY_IDS.contains(id);
}
- public static void register(RegistryKey extends Registry> ref, Codec entryCodec) {
+ public static void register(RegistryKey extends Registry> ref, Codec entryCodec, DynamicRegistryFlag... flags) {
if (frozen) throw new IllegalStateException("Registry is already frozen");
MODDED_REGISTRY_IDS.add(ref.getValue());
RegistryLoader.WORLDGEN_REGISTRIES.add(new RegistryLoader.DecodingData<>(ref, entryCodec));
+ for (DynamicRegistryFlag flag : flags) {
+ DynamicRegistryFlagManager.setFlag(ref.getValue(), flag);
+ }
}
- public static void registerSynced(RegistryKey extends Registry> ref, Codec entryCodec, Codec syncCodec) {
- register(ref, entryCodec);
+ public static void registerSynced(RegistryKey extends Registry> ref, Codec entryCodec, Codec syncCodec, DynamicRegistryFlag... flags) {
+ register(ref, entryCodec, flags);
var builder = ImmutableMap.>, Object>builder().putAll(DynamicRegistrySyncAccessor.quilt$getSyncedCodecs());
DynamicRegistrySyncAccessor.quilt$invokeAddSyncedRegistry(builder, ref, syncCodec);
DynamicRegistrySyncAccessor.quilt$setSyncedCodecs(builder.build());
@@ -55,4 +63,8 @@ public static void registerSynced(RegistryKey extends Registry> ref, Co
public static void freeze() {
frozen = true;
}
+
+ static boolean isFrozen() {
+ return frozen;
+ }
}
diff --git a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicRegistryFlagManager.java b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicRegistryFlagManager.java
new file mode 100644
index 0000000000..a92f7a6ddd
--- /dev/null
+++ b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/impl/dynamic/DynamicRegistryFlagManager.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Quilt Project
+ *
+ * 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 org.quiltmc.qsl.registry.impl.dynamic;
+
+import org.jetbrains.annotations.ApiStatus;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+
+import net.minecraft.util.Identifier;
+
+import org.quiltmc.qsl.registry.api.dynamic.DynamicRegistryFlag;
+
+@ApiStatus.Internal
+public final class DynamicRegistryFlagManager {
+ private static final Multimap DYNAMIC_REGISTRY_FLAGS =
+ MultimapBuilder.hashKeys().enumSetValues(DynamicRegistryFlag.class).build();
+
+ public static void setFlag(Identifier registryId, DynamicRegistryFlag flag) throws IllegalStateException {
+ if (DynamicMetaRegistryImpl.isFrozen()) {
+ throw new IllegalStateException("Dynamic registries are frozen, and thus flags cannot be changed!");
+ }
+
+ if (!DynamicMetaRegistryImpl.isModdedRegistryId(registryId)) return;
+
+ DYNAMIC_REGISTRY_FLAGS.put(registryId, flag);
+ }
+
+ public static boolean isOptional(Identifier registryId) {
+ if (DYNAMIC_REGISTRY_FLAGS.containsKey(registryId)) {
+ return DYNAMIC_REGISTRY_FLAGS.get(registryId).contains(DynamicRegistryFlag.OPTIONAL);
+ }
+
+ return false;
+ }
+}
diff --git a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/mixin/DynamicRegistrySyncMixin.java b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/mixin/DynamicRegistrySyncMixin.java
index 30e643e9c6..20f1c4e415 100644
--- a/library/core/registry/src/main/java/org/quiltmc/qsl/registry/mixin/DynamicRegistrySyncMixin.java
+++ b/library/core/registry/src/main/java/org/quiltmc/qsl/registry/mixin/DynamicRegistrySyncMixin.java
@@ -1,4 +1,5 @@
/*
+ * Copyright 2016, 2017, 2018, 2019 FabricMC
* Copyright 2023 The Quilt Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,16 +18,24 @@
package org.quiltmc.qsl.registry.mixin;
import java.util.Map;
+import java.util.stream.Stream;
+import org.spongepowered.asm.mixin.Dynamic;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.DynamicRegistrySync;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
+import org.quiltmc.qsl.registry.impl.dynamic.DynamicRegistryFlagManager;
+
@Mixin(DynamicRegistrySync.class)
public abstract class DynamicRegistrySyncMixin {
@SuppressWarnings("unused") // makes the field mutable for use by the accessor
@@ -34,4 +43,28 @@ public abstract class DynamicRegistrySyncMixin {
@Final
@Mutable
private static Map>, ?> SYNCED_CODECS;
+ @Unique
+ private static boolean filterRegistryEntry(DynamicRegistryManager.RegistryEntry> entry) {
+ // OPTIONAL
+ if (DynamicRegistryFlagManager.isOptional(entry.key().getValue())) {
+ return entry.value().size() > 0;
+ }
+
+ return true; // If no flags apply, always return true
+ }
+
+ @Shadow
+ private static Stream> streamSyncedRegistries(DynamicRegistryManager registryManager) {
+ throw new IllegalStateException("Mixin injection failed.");
+ }
+
+ /**
+ * This redirect mixin's annotation was taken directly from Fabric API (and adapted for Quilt Mappings), the rest of this file was not.
+ */
+ @Dynamic("method_45961: Codec.xmap in buildManagerCodec")
+ @Redirect(method = "method_45961",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/DynamicRegistrySync;streamSyncedRegistries(Lnet/minecraft/registry/DynamicRegistryManager;)Ljava/util/stream/Stream;"))
+ private static Stream> filterNonSyncedEntries(DynamicRegistryManager drm) {
+ return streamSyncedRegistries(drm).filter(DynamicRegistrySyncMixin::filterRegistryEntry);
+ }
}
diff --git a/library/core/registry/src/testmod/java/org/quiltmc/qsl/registry/test/dynamic/RegistryLibDynamicRegistryTest.java b/library/core/registry/src/testmod/java/org/quiltmc/qsl/registry/test/dynamic/RegistryLibDynamicRegistryTest.java
index 945fbbc1d3..149a647882 100644
--- a/library/core/registry/src/testmod/java/org/quiltmc/qsl/registry/test/dynamic/RegistryLibDynamicRegistryTest.java
+++ b/library/core/registry/src/testmod/java/org/quiltmc/qsl/registry/test/dynamic/RegistryLibDynamicRegistryTest.java
@@ -34,6 +34,7 @@
import org.quiltmc.loader.api.ModContainer;
import org.quiltmc.qsl.base.api.entrypoint.ModInitializer;
import org.quiltmc.qsl.registry.api.dynamic.DynamicMetaRegistry;
+import org.quiltmc.qsl.registry.api.dynamic.DynamicRegistryFlag;
import org.quiltmc.qsl.registry.api.event.RegistryEvents;
import org.quiltmc.qsl.tag.api.TagRegistry;
import org.quiltmc.qsl.tag.api.TagRegistry.TagValues;
@@ -49,7 +50,7 @@ public class RegistryLibDynamicRegistryTest implements QuiltGameTest, ModInitial
@Override
public void onInitialize(ModContainer mod) {
- DynamicMetaRegistry.registerSynced(Greetings.REGISTRY_KEY, Greetings.CODEC);
+ DynamicMetaRegistry.registerSynced(Greetings.REGISTRY_KEY, Greetings.CODEC, DynamicRegistryFlag.OPTIONAL);
RegistryEvents.DYNAMIC_REGISTRY_SETUP.register(context -> context.register(Greetings.REGISTRY_KEY, GREETING_B_ID, () -> GREETING_B));
}
@@ -58,6 +59,7 @@ public void greetingsGetLoaded(QuiltTestContext ctx) {
Registry greetingsRegistry = ctx.getWorld().getRegistryManager().get(Greetings.REGISTRY_KEY);
ctx.succeedIf(() -> {
+ ctx.assertTrue(DynamicRegistryFlag.isOptional(Greetings.REGISTRY_KEY.getValue()), "Registry should always have the OPTIONAL flag enabled");
ctx.assertTrue(greetingsRegistry.containsId(GREETING_A_ID), "Registry should contain modded data value from datapack");
ctx.assertTrue(Objects.requireNonNull(greetingsRegistry.get(GREETING_A_ID)).equals(GREETING_A), "Modded value should be properly parsed from data file");
ctx.assertTrue(GREETING_B.equals(greetingsRegistry.get(GREETING_B_ID)), "Registry should contain modded data value from event");