Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add system for Dynamic Registry Flags #330

Merged
merged 17 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public final class DynamicMetaRegistry {
* @param entryCodec the codec used to deserialize entries from datapacks
* @throws IllegalStateException if this registry of registries already got frozen
*/
public static <E> void register(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec) {
DynamicMetaRegistryImpl.register(key, entryCodec);
public static <E> void register(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, DynamicRegistryFlag... flags) {
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
DynamicMetaRegistryImpl.register(key, entryCodec, flags);
}

/**
Expand All @@ -63,10 +63,10 @@ public static <E> void register(RegistryKey<? extends Registry<E>> key, Codec<E>
* @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)
* @see #registerSynced(RegistryKey, Codec, Codec, DynamicRegistryFlag...)
*/
public static <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, entryCodec);
public static <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, DynamicRegistryFlag... flags) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, entryCodec, flags);
}

/**
Expand All @@ -81,9 +81,9 @@ public static <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Co
* @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)
* @see #registerSynced(RegistryKey, Codec, DynamicRegistryFlag...)
*/
public static <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, Codec<E> syncCodec) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, syncCodec);
public static <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, Codec<E> syncCodec, DynamicRegistryFlag... flags) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, syncCodec, flags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.
* <p>All flags default to being disabled/turned off, and can be enabled or disabled using static methods in this class.
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
*
* @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.
* <p>
* <b>Note:</b> This flag is intended only for synchronized dynamic registries. On non-synced dynamic registries, this flag does nothing.
* <p></p>
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
* 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 <i>and</i> 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
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
* @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
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
* @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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,39 @@
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;
import net.minecraft.registry.RegistryLoader;
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<Identifier> 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 <E> void register(RegistryKey<? extends Registry<E>> ref, Codec<E> entryCodec) {
public static <E> void register(RegistryKey<? extends Registry<E>> ref, Codec<E> 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 <E> void registerSynced(RegistryKey<? extends Registry<E>> ref, Codec<E> entryCodec, Codec<E> syncCodec) {
register(ref, entryCodec);
public static <E> void registerSynced(RegistryKey<? extends Registry<E>> ref, Codec<E> entryCodec, Codec<E> syncCodec, DynamicRegistryFlag... flags) {
register(ref, entryCodec, flags);
var builder = ImmutableMap.<RegistryKey<? extends Registry<?>>, Object>builder().putAll(DynamicRegistrySyncAccessor.quilt$getSyncedCodecs());
DynamicRegistrySyncAccessor.quilt$invokeAddSyncedRegistry(builder, ref, syncCodec);
DynamicRegistrySyncAccessor.quilt$setSyncedCodecs(builder.build());
Expand All @@ -55,4 +63,8 @@ public static <E> void registerSynced(RegistryKey<? extends Registry<E>> ref, Co
public static void freeze() {
frozen = true;
}

static boolean isFrozen() {
return frozen;
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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<Identifier, DynamicRegistryFlag> DYNAMIC_REGISTRY_FLAGS =
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
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);
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
}
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
public static boolean isOptional(Identifier registryId) {
if (DYNAMIC_REGISTRY_FLAGS.containsKey(registryId)) {
return DYNAMIC_REGISTRY_FLAGS.get(registryId).contains(DynamicRegistryFlag.OPTIONAL);
}
return false;
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
* Copyright 2016, 2017, 2018, 2019 FabricMC
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
* Copyright 2023 The Quilt Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -17,21 +18,52 @@
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
@Shadow
@Final
@Mutable
private static Map<RegistryKey<? extends Registry<?>>, ?> 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
cocona20xx marked this conversation as resolved.
Show resolved Hide resolved
}

@Shadow
private static Stream<DynamicRegistryManager.RegistryEntry<?>> 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<DynamicRegistryManager.RegistryEntry<?>> filterNonSyncedEntries(DynamicRegistryManager drm) {
return streamSyncedRegistries(drm).filter(DynamicRegistrySyncMixin::filterRegistryEntry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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.testing.api.game.QuiltGameTest;
import org.quiltmc.qsl.testing.api.game.QuiltTestContext;
Expand All @@ -42,7 +43,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));
}

Expand All @@ -51,6 +52,7 @@ public void greetingsGetLoaded(QuiltTestContext ctx) {
Registry<Greetings> 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");
Expand Down