Skip to content

Commit

Permalink
Backport changes allowing Fabric API parity to 1.19.4 (#347)
Browse files Browse the repository at this point in the history
* Implement Dynamic Registry Flags.
- Currently, only one flag, `OPTIONAL`, exists.
- Modified DynamicRegistrySyncMixin in order to implement these flags. Due to the Redirect mixin header used to implement the flag filtering being taken directly from FAPI, the header of DynamicRegistrySyncMixin was changed to mention FabricMC copyright, in accordance with QSL contribution rules in CONTRIBUTING.md

* Implement Dynamic Registry Flags p2
- Modify DynamicRegistryFlagManager to use Identifiers over RegistryKeys internally, in order to avoid generic-related jank.
- Fix mixin ported from FAPI by correcting method target string to use Quilt Mappings.
- Implement very basic tests.

* Expanded upon OPTIONAL flag javadocs to elaborate use-case

* Expanded further on OPTIONAL javadocs

* Implemented some changes suggested by Pyrofab

- Added protected helper method DynamicMetaRegistryImpl#isFrozen for use in DynamicRegistryFlagManager
- Added DynamicMetaRegistryImpl#LOGGER with the name "quilt_dynamic_registries" as a logger for catching exceptions related to the setting of flags
- Removed the ability to disable dynamic registry flags, changed the signature of the related flag-enabling methods from enableFlag -> setFlag (on both DynamicRegistryFlag and DynamicRegistryFlagManager)
- DynamicRegistryFlagManager#setFlag now throws an IllegalStateException if a mod attempts to enable a flag when dynamic registries are frozen. This is caught and printed to the logger mentioned prior in DynamicRegistryFlag calls
- Added varargs for setting DynamicRegistryFlag values on dynamic registry creation
- Improved DynamicRegistryFlag#OPTIONAL javadocs slightly

* Add missing icon to data callback library (#331)

* Commit most style changes

One of them needs a more specific wording change, so it's going to be done in another commit since that's easier for us

Co-authored-by: Ennui Langeweile <[email protected]>

* Make documentation for DynamicRegistryFlag more accurate

* Improve the javadocs on DynamicRegistryFlag AGAIN

accidentally dropped a word

guh

* BUT WAIT, THERE'S MORE!

Hopefully the final style change moment

Co-authored-by: Ennui Langeweile <[email protected]>

* Implement style change in DynamicRegistryFlagManager

Co-authored-by: LambdAurora <[email protected]>

* Fix binary incompatibility hopefully?
- Added back methods to DynamicMetaRegistry with the same signature as the old ones to avoid binary incompatibility. These are marked as deprecated and should be removed in a breaking/major QSL release.

* Remove deprecation annotations on compat. method overloads in DynamicMetaRegistry

* Fix: Modify Tag path behavior in Dynamic Registries (#329)

* Implement tweak to Tags API to append `tags/` to the start of Dynamic Registries for tag creation that do not already have said path appended to the start of them already.
This behavior also exits in FAPI as of the most recent release, and so adding it to QSL unifies the behavior of pure QSL and the upcoming QFAPI release in that regard.
This can easily be moved to the Registry API to reduce intra-module requirements, but was put in the Tags API as that's the part of the game that is being modified here.

* Move TagManagerLoaderMixin from Tags API to Registry API so that Tags API does not need to depend on Registry API for one very small mixin

* Modify mixin injector name to be more descriptive and have `quilt$` prefix for debugging

* Revert changes to quilt_tags.mixins.json

* Added tests for tag namespace changes

* go go gadget license generation

---------

Co-authored-by: Ennui Langeweile <[email protected]>

* Flip a boolean to sync with FAPI behavior

see 2ff57f2 and FabricMC/fabric@d3afe6c

* Clean up the dynamic tag test

* Add stopguard on registering dynamic registries
No, you cannot use the `minecraft` namespace and blow up the world

* Ran the licenser. Dear gods that's a lotta copyright year changes wowie

* Revert "Ran the licenser. Dear gods that's a lotta copyright year changes wowie"

This reverts commit 696a299.

---------

Co-authored-by: Lilly Rose Berner <[email protected]>
Co-authored-by: Ennui Langeweile <[email protected]>
Co-authored-by: LambdAurora <[email protected]>
  • Loading branch information
4 people authored Oct 1, 2023
1 parent ecaf126 commit e3ce69e
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 11 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions library/core/registry/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ qslModule {
testmodOnly("resource_loader")
testmodOnly("testing")
}
data {
testmodOnly("tags")
}
}
entrypoints {
client_init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* Entries will be loaded from {@code "data/<namespace>/<registry_namespace>/<registry_path>"} 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 <E> 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 <E> void register(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, DynamicRegistryFlag... flags) {
DynamicMetaRegistryImpl.register(key, entryCodec, flags);
}

/**
* Registers a dynamic registry which contents get synced between the server and connected clients.
* <p>
* Entries will be loaded from {@code "data/<namespace>/<registry_namespace>/<registry_path>"} 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 <E> 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 <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, DynamicRegistryFlag... flags) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, entryCodec, flags);
}

/**
* Registers a dynamic registry which contents get synced between the server and connected clients.
* <p>
* Entries will be loaded from {@code "data/<namespace>/<registry_namespace>/<registry_path>"} 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 <E> 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 <E> void registerSynced(RegistryKey<? extends Registry<E>> key, Codec<E> entryCodec, Codec<E> syncCodec, DynamicRegistryFlag... flags) {
DynamicMetaRegistryImpl.registerSynced(key, entryCodec, syncCodec, flags);
}

/**
* Registers a server-side dynamic registry.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* <b>Note:</b> This flag is intended only for synchronized dynamic registries. On non-synced dynamic registries, this flag does nothing.
* <p>
* 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
* @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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,44 @@
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 (ref.getValue().getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) {
throw new IllegalStateException("The '" + ref.getValue() + "' dynamic registry cannot have 'minecraft' as its identifier's namespace!");
}

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 +68,8 @@ public static <E> void registerSynced(RegistryKey<? extends Registry<E>> ref, Co
public static void freeze() {
frozen = true;
}

static boolean isFrozen() {
return frozen;
}
}
Original file line number Diff line number Diff line change
@@ -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<Identifier, DynamicRegistryFlag> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -17,21 +18,53 @@
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
}

@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
@@ -0,0 +1,40 @@
/*
* 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.mixin;

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 org.quiltmc.qsl.registry.impl.dynamic.DynamicMetaRegistryImpl;

@Mixin(TagManagerLoader.class)
public class TagManagerLoaderMixin {
@Inject(method = "getRegistryDirectory", at = @At("HEAD"), cancellable = true)
private static void quilt$replaceModdedDynamicRegistryTagPath(RegistryKey<? extends Registry<?>> registry, CallbackInfoReturnable<String> cir) {
Identifier id = registry.getValue();
if (DynamicMetaRegistryImpl.isModdedRegistryId(id)) {
cir.setReturnValue("tags/" + id.getNamespace() + "/" + id.getPath());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerAccessor",
"SimpleRegistryMixin",
"TagManagerLoaderMixin",
"patch.BeaconBlockEntityMixin",
"patch.MooshroomEntityMixin",
"patch.StatusEffectInstanceMixin",
Expand Down
Loading

0 comments on commit e3ce69e

Please sign in to comment.