diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryList.java new file mode 100644 index 0000000000..1c4f30ad23 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryList.java @@ -0,0 +1,32 @@ +/* + * 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.entrylists; + +import net.minecraft.registry.entry.RegistryEntryList; + +/** + * + * @param the type of elements contained by this list + */ +public interface CustomRegistryEntryList extends RegistryEntryList { + /** + * + * @return a registered serializer capable of serializing this list + * @see CustomRegistryEntryListSerializer#registerSerializer + */ + CustomRegistryEntryListSerializer getSerializer(); +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryListSerializer.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryListSerializer.java new file mode 100644 index 0000000000..1a7883aaad --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/CustomRegistryEntryListSerializer.java @@ -0,0 +1,60 @@ +/* + * 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.entrylists; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.registry.sync.entrylists.CustomRegistryEntryListSerializerImpl; + +public interface CustomRegistryEntryListSerializer { + /** + * registers a {@link CustomRegistryEntryListSerializer}. this must be done before the list is used + * @param serializer the serializer to register + */ + static void registerSerializer(@NotNull CustomRegistryEntryListSerializer serializer) { + CustomRegistryEntryListSerializerImpl.registerSerializer(serializer); + } + + /** + * gets a registered serializer. + * @param id the identifier of the serializer + * @return the serializer, or {@code null} if no serializer is registered with the given id + */ + static @Nullable CustomRegistryEntryListSerializer getSerializer(Identifier id) { + return CustomRegistryEntryListSerializerImpl.getSerializer(id); + } + + /** + * used to encode the entry list over the network, or in json. + * @return the registry id of this serializer + */ + Identifier getIdentifier(); + + MapCodec> createCodec(RegistryKey> registryKey, Codec> entryCodec, boolean forceList); + + PacketCodec> createPacketCodec(RegistryKey> registryKey); +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/DefaultCustomRegistryEntryLists.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/DefaultCustomRegistryEntryLists.java new file mode 100644 index 0000000000..9ae89335c9 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/DefaultCustomRegistryEntryLists.java @@ -0,0 +1,89 @@ +/* + * 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.entrylists; + +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.registry.entry.RegistryEntryList; + +import net.fabricmc.fabric.impl.registry.sync.entrylists.defaults.DefaultCustomRegistryEntryListsImpl; + +/** + * convenience implementations for {@link CustomRegistryEntryList}. + */ +public class DefaultCustomRegistryEntryLists { + /** + * creates a {@link RegistryEntryList} that contains anything contained by any element of {@code parts}. + * + * @param parts the component parts to OR + * @param the type of elements contained in all the lists + * @return the custom list + */ + @SafeVarargs + public static RegistryEntryList union(RegistryEntryList... parts) { + return DefaultCustomRegistryEntryListsImpl.union(parts); + } + + /** + * creates a {@link RegistryEntryList} that contains anything contained by all element of {@code parts}. + * + * @param parts the component parts to AND + * @param the type of elements contained in all the lists + * @return the custom list + */ + @SafeVarargs + public static RegistryEntryList intersection(RegistryEntryList... parts) { + return DefaultCustomRegistryEntryListsImpl.intersection(parts); + } + + /** + * creates a {@link RegistryEntryList} that contains anything not contained by its opposite. + * + * @param lookup the source registry for this list + * @param opposite the list that contains everything the returned list will not + * @param the type of elements contained in all the lists + * @return the custom list + * @implNote {@link net.minecraft.item.Items#AIR} and {@link net.minecraft.block.Blocks#AIR} are included in the returned list, provided that they are not in opposite. for certain use cases, such as {@link net.minecraft.recipe.Ingredient}s, the caller must ensure that air is not in the end result, or else the ingredient will fail to serialize + */ + public static RegistryEntryList inverse(RegistryEntryLookup lookup, RegistryEntryList opposite) { + return DefaultCustomRegistryEntryListsImpl.inverse(lookup, opposite); + } + + /** + * creates a {@link RegistryEntryList} that contains everything in the source lookup. + * + * @param lookup the source registry for this list + * @param the type of elements contained in all the lists + * @return the custom list + * @implNote {@link net.minecraft.item.Items#AIR} and {@link net.minecraft.block.Blocks#AIR} are included in the returned list. for certain use cases, such as {@link net.minecraft.recipe.Ingredient}s, the caller must ensure that air is not in the end result, or else the ingredient will fail to serialize + */ + public static RegistryEntryList universal(RegistryEntryLookup lookup) { + return DefaultCustomRegistryEntryListsImpl.universal(lookup); + } + + /** + * creates a {@link RegistryEntryList} that contains everything in {@code initial} that is not also in {@code subtracted}. + * + * @param lookup the source registry for this list + * @param initial the beginning list + * @param subtracted the list to subtract from initial + * @param the type of elements contained in all the lists + * @return the custom list + */ + public static RegistryEntryList subtraction(RegistryEntryLookup lookup, RegistryEntryList initial, RegistryEntryList subtracted) { + return DefaultCustomRegistryEntryListsImpl.subtraction(lookup, initial, subtracted); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/FabricRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/FabricRegistryEntryList.java new file mode 100644 index 0000000000..87574d203c --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/api/event/registry/entrylists/FabricRegistryEntryList.java @@ -0,0 +1,91 @@ +/* + * 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.entrylists; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +import net.minecraft.registry.entry.RegistryEntryList; + +// Injected to RegistryEntryList +@ApiStatus.NonExtendable +public interface FabricRegistryEntryList { + // creating a cycle in the graph will lead to stack overflow, do with this knowledge what you will + Map, Set>> DEPENDENCIES = new WeakHashMap<>(); + + default Set> getDependencies() { + return Collections.unmodifiableSet(castToT(DEPENDENCIES.getOrDefault(this.asSelf(), Set.of()))); + } + + /** + * Unregisters a dependency on this. + * Does nothing is the dependency is not registered. + * + * @param dependency the dependency to unregister + */ + default void unregisterDependency(RegistryEntryList dependency) { + Set> dependencies = castToT(DEPENDENCIES.get(this.asSelf())); + + if (dependencies != null) { + dependencies.remove(dependency); + } + } + + /** + * registers a dependency on this. + * + * @param dependency the dependency to register + */ + default void registerDependency(RegistryEntryList dependency) { + DEPENDENCIES.computeIfAbsent( + this.asSelf(), + k -> Collections.newSetFromMap(new WeakHashMap<>()) + ) + .add(dependency); + } + + /** + * Invalidate dependents is called by this list when it is invalidated. + */ + private void invalidateDependents() { + DEPENDENCIES.getOrDefault(this.asSelf(), Set.of()).forEach(FabricRegistryEntryList::invalidate); + } + + /** + * Invalidate is called by any list that this depends on when the parent list is invalidated. + */ + @MustBeInvokedByOverriders + default void invalidate() { + this.invalidateDependents(); + } + + // this interface should only be implemented on RegistryEntryList, so the case **should** be safe... + private RegistryEntryList asSelf() { + return (RegistryEntryList) this; + } + + // why can't java just have field generics or something? + @SuppressWarnings("unchecked") + private static Set> castToT(Set> entryList) { + return (Set>) (Object) entryList; + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java index 0a5fede7e3..0b160f1187 100644 --- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/FabricRegistryInit.java @@ -24,6 +24,8 @@ import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; +import net.fabricmc.fabric.impl.registry.sync.entrylists.defaults.DefaultCustomRegistryEntryListsImpl; +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.ChannelUtil; import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler; public class FabricRegistryInit implements ModInitializer { @@ -211,5 +213,8 @@ public void onInitialize() { // Synced via PacketCodecs.registryValue RegistryAttributeHolder.get(Registries.RECIPE_BOOK_CATEGORY) .addAttribute(RegistryAttribute.SYNCED); + + ChannelUtil.init(); + DefaultCustomRegistryEntryListsImpl.register(); } } diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/CustomRegistryEntryListSerializerImpl.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/CustomRegistryEntryListSerializerImpl.java new file mode 100644 index 0000000000..e90a6840a1 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/CustomRegistryEntryListSerializerImpl.java @@ -0,0 +1,78 @@ +/* + * 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.entrylists; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; + +public final class CustomRegistryEntryListSerializerImpl { + private CustomRegistryEntryListSerializerImpl() { + throw new UnsupportedOperationException(); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomRegistryEntryListSerializerImpl.class); + private static final Map SERIALIZERS = new HashMap<>(); + public static final Codec SERIALIZER_CODEC = Identifier.CODEC.flatXmap( + id -> Optional.ofNullable(SERIALIZERS.get(id)) + .map(DataResult::success) + .orElseGet( + () -> DataResult.error( + () -> "Unknown CustomRegistryEntryListSerializer: " + id + ) + ), + serializer -> Optional.of(serializer.getIdentifier()) + .filter(SERIALIZERS::containsKey) + .map(DataResult::success) + .orElseGet( + () -> DataResult.error( + () -> "Unknown CustomRegistryEntryListSerializer: " + serializer + ) + ) + ); + + public static boolean isSerializedCustomRegistryEntryList(DynamicOps ops, T entryList) { + return ops.getMap(entryList) + .map(it -> it.get("fabric:type")) + .map(Objects::nonNull) + .result() + .orElse(false); + } + + public static void registerSerializer(CustomRegistryEntryListSerializer serializer) { + CustomRegistryEntryListSerializer previous = SERIALIZERS.put(serializer.getIdentifier(), serializer); + + if (previous != null) { + LOGGER.warn("Overwriting CustomRegistryEntryListSerializer {} with {}", previous, serializer); + } + } + + public static CustomRegistryEntryListSerializer getSerializer(Identifier id) { + return SERIALIZERS.get(id); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/DefaultCustomRegistryEntryListsImpl.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/DefaultCustomRegistryEntryListsImpl.java new file mode 100644 index 0000000000..179ebc931f --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/DefaultCustomRegistryEntryListsImpl.java @@ -0,0 +1,78 @@ +/* + * 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.entrylists.defaults; + +import java.util.List; +import java.util.Objects; + +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.registry.entry.RegistryEntryList; + +import net.fabricmc.fabric.impl.registry.sync.entrylists.CustomRegistryEntryListSerializerImpl; +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.RegistryWrapperUtils; + +public class DefaultCustomRegistryEntryListsImpl { + private static boolean initialized = false; + + public static void register() { + if (initialized) { + return; + } + + initialized = true; + CustomRegistryEntryListSerializerImpl.registerSerializer(UnionRegistryEntryList.SERIALIZER); + CustomRegistryEntryListSerializerImpl.registerSerializer(IntersectionRegistryEntryList.SERIALIZER); + CustomRegistryEntryListSerializerImpl.registerSerializer(InverseRegistryEntryList.SERIALIZER); + CustomRegistryEntryListSerializerImpl.registerSerializer(UniversalRegistryEntryList.SERIALIZER); + } + + public static RegistryEntryList union(RegistryEntryList[] parts) { + return new UnionRegistryEntryList<>(List.of(validateNotNull(parts))); + } + + public static RegistryEntryList intersection(RegistryEntryList[] parts) { + return new IntersectionRegistryEntryList<>(List.of(validateNotNull(parts))); + } + + public static RegistryEntryList inverse(RegistryEntryLookup lookup, RegistryEntryList opposite) { + Objects.requireNonNull(lookup, "lookup"); + Objects.requireNonNull(opposite, "opposite"); + return new InverseRegistryEntryList<>(RegistryWrapperUtils.castOrCreateFromEntryLookup(lookup), opposite); + } + + public static RegistryEntryList universal(RegistryEntryLookup lookup) { + Objects.requireNonNull(lookup, "lookup"); + return new UniversalRegistryEntryList<>(RegistryWrapperUtils.castOrCreateFromEntryLookup(lookup)); + } + + public static RegistryEntryList subtraction(RegistryEntryLookup lookup, RegistryEntryList initial, RegistryEntryList subtracted) { + Objects.requireNonNull(lookup, "lookup"); + Objects.requireNonNull(initial, "initial"); + Objects.requireNonNull(subtracted, "subtracted"); + return new IntersectionRegistryEntryList<>(List.of(initial, inverse(lookup, subtracted))); + } + + private static RegistryEntryList[] validateNotNull(RegistryEntryList[] lists) { + Objects.requireNonNull(lists, "list array"); + + for (RegistryEntryList list : lists) { + Objects.requireNonNull(list, "list"); + } + + return lists; + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/IntersectionRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/IntersectionRegistryEntryList.java new file mode 100644 index 0000000000..68225d9f65 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/IntersectionRegistryEntryList.java @@ -0,0 +1,122 @@ +/* + * 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.entrylists.defaults; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryListCodec; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; + +/** + * A default implementation of {@link RegistryEntryList} that contains everything in all of it's backing lists. + * the resulting list is cached as a set and a list, to provide a consistent iteration order, and O(1) contains + * + * @param type of entries held by this list + */ + +public class IntersectionRegistryEntryList extends MultiPartRegistryEntryList { + public static final CustomRegistryEntryListSerializer SERIALIZER = new Serializer(); + + public IntersectionRegistryEntryList(List> parts) { + super(parts); + } + + @Override + protected Set> createCache() { + if (this.getParts().isEmpty()) { + return Set.of(); + } + + if (this.getParts().size() == 1) { + return this.getParts() + .getFirst() + .stream() + .collect(Collectors.toUnmodifiableSet()); + } + + List> remaining = this.getParts().subList(1, getParts().size()); + + return getParts() + .getFirst() + .stream() + .filter(it -> remaining.stream().allMatch( + registryEntryList -> registryEntryList.contains(it) + )) + .collect(Collectors.toUnmodifiableSet()); + } + + @Override + public CustomRegistryEntryListSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public String toString() { + return "Intersection{" + this.getParts() + "}"; + } + + private record Serializer() implements CustomRegistryEntryListSerializer { + private static final Identifier ID = Identifier.of("fabric", "and"); + + @Override + public Identifier getIdentifier() { + return ID; + } + + @Override + public MapCodec> createCodec(RegistryKey> registryKey, Codec> entryCodec, boolean forceList) { + return RegistryEntryListCodec.create(registryKey, entryCodec, forceList) + .listOf() + .xmap( + IntersectionRegistryEntryList::new, + MultiPartRegistryEntryList::getParts + ) + .fieldOf("values"); + } + + @Override + public PacketCodec> createPacketCodec(RegistryKey> registryKey) { + return PacketCodecs.registryEntryList(registryKey) + .collect(PacketCodecs.toList()) + .xmap( + IntersectionRegistryEntryList::new, + MultiPartRegistryEntryList::getParts + ); + } + + @Override + public @NotNull String toString() { + return "CustomRegistryEntryListSerializer{\"" + ID + "\"}"; + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/InverseRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/InverseRegistryEntryList.java new file mode 100644 index 0000000000..607198d3ee --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/InverseRegistryEntryList.java @@ -0,0 +1,192 @@ +/* + * 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.entrylists.defaults; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryListCodec; +import net.minecraft.registry.entry.RegistryEntryOwner; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.math.random.Random; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.RegistryWrapperUtils; + +/** + * A default implementation of {@link RegistryEntryList} that contains everything not in a given list. + * A best effort is made to keep a cache of the values, to avoid iterating the registry for every call. + * + * @param type of entries held by this list + */ +public class InverseRegistryEntryList implements CustomRegistryEntryList { + public static final CustomRegistryEntryListSerializer SERIALIZER = new Serializer(); + + private final RegistryWrapper wrapper; + private final RegistryEntryList opposite; + @Nullable + private List> cache = null; + + public InverseRegistryEntryList(RegistryWrapper wrapper, RegistryEntryList opposite) { + this.wrapper = wrapper; + this.opposite = opposite; + this.opposite.registerDependency(this); + } + + public RegistryEntryList getOpposite() { + return opposite; + } + + public RegistryWrapper getWrapper() { + return wrapper; + } + + @Override + public Stream> stream() { + return getList().stream(); + } + + @Override + public int size() { + return getList().size(); + } + + @Override + public boolean isBound() { + return opposite.isBound(); + } + + @Override + public Either, List>> getStorage() { + return Either.right(this.getList()); + } + + @Override + public Optional> getRandom(Random random) { + return Util.getRandomOrEmpty(getList(), random); + } + + @Override + public RegistryEntry get(int index) { + return getList().get(index); + } + + @Override + public boolean contains(RegistryEntry entry) { + return !this.opposite.contains(entry); + } + + @Override + public boolean ownerEquals(RegistryEntryOwner owner) { + return this.opposite.ownerEquals(owner); + } + + @Override + public Optional> getTagKey() { + return Optional.empty(); + } + + @Override + public @NotNull Iterator> iterator() { + return getList().iterator(); + } + + private List> getList() { + if (this.cache == null) { + this.cache = this.wrapper + .streamEntries() + .filter(Predicate.not(this.opposite::contains)) + .collect(Collectors.toUnmodifiableList()); + } + + return this.cache; + } + + @Override + public CustomRegistryEntryListSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public void invalidate() { + this.cache = null; + CustomRegistryEntryList.super.invalidate(); + } + + @Override + public String toString() { + return "Inverse{" + this.getOpposite().toString() + "}"; + } + + private record Serializer() implements CustomRegistryEntryListSerializer { + private static final Identifier ID = Identifier.of("fabric", "not"); + + @Override + public Identifier getIdentifier() { + return ID; + } + + @Override + public MapCodec> createCodec(RegistryKey> registryKey, Codec> registryEntryCodec, boolean forceList) { + return RecordCodecBuilder.>mapCodec( + instance -> instance.apply2( + InverseRegistryEntryList::new, + RegistryWrapperUtils.createMapCodec(registryKey).forGetter(InverseRegistryEntryList::getWrapper), + RegistryEntryListCodec.create(registryKey, registryEntryCodec, forceList).fieldOf("value").forGetter(InverseRegistryEntryList::getOpposite) + ) + ); + } + + @Override + public PacketCodec> createPacketCodec(RegistryKey> registryKey) { + return PacketCodec., RegistryWrapper, RegistryEntryList>tuple( + RegistryWrapperUtils.createPacketCodec(registryKey), + InverseRegistryEntryList::getWrapper, + PacketCodecs.registryEntryList(registryKey), + InverseRegistryEntryList::getOpposite, + InverseRegistryEntryList::new + ); + } + + @Override + public @NotNull String toString() { + return "CustomRegistryEntryListSerializer{\"" + ID + "\"}"; + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/MultiPartRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/MultiPartRegistryEntryList.java new file mode 100644 index 0000000000..9fb4ca5938 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/MultiPartRegistryEntryList.java @@ -0,0 +1,140 @@ +/* + * 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.entrylists.defaults; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryOwner; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Util; +import net.minecraft.util.math.random.Random; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; + +/** + * A default template implementation of {@link RegistryEntryList} that derives it's contents from some set of backing lists. + * the resulting list is cached as a set and a list, to provide a consistent iteration order, and O(1) contains + * + * @param type of entries held by this list + */ +public abstract class MultiPartRegistryEntryList implements CustomRegistryEntryList { + private final ImmutableList> parts; + @Nullable + private Set> cachedSet = null; + @Nullable + private List> cachedList = null; + + public MultiPartRegistryEntryList(List> parts) { + this.parts = ImmutableList.copyOf(parts); + + for (RegistryEntryList part : parts) { + part.registerDependency(this); + } + } + + public ImmutableList> getParts() { + return this.parts; + } + + protected abstract Set> createCache(); + + private Set> getCachedSet() { + if (this.cachedSet == null) { + this.cachedSet = this.createCache(); + } + + return this.cachedSet; + } + + private List> getCachedList() { + if (this.cachedList == null) { + this.cachedList = List.copyOf(this.getCachedSet()); + } + + return this.cachedList; + } + + @Override + public void invalidate() { + this.cachedSet = null; + this.cachedList = null; + CustomRegistryEntryList.super.invalidate(); + } + + @Override + public Stream> stream() { + return this.getCachedList().stream(); + } + + @Override + public int size() { + return this.getCachedList().size(); + } + + @Override + public boolean isBound() { + return this.parts.stream().allMatch(RegistryEntryList::isBound); + } + + @Override + public Either, List>> getStorage() { + return Either.right(this.getCachedList()); + } + + @Override + public Optional> getRandom(Random random) { + return Util.getRandomOrEmpty(getCachedList(), random); + } + + @Override + public RegistryEntry get(int index) { + return this.getCachedList().get(index); + } + + @Override + public boolean contains(RegistryEntry entry) { + return this.getCachedSet().contains(entry); + } + + @Override + public boolean ownerEquals(RegistryEntryOwner owner) { + return this.parts.stream() + .allMatch(it -> it.ownerEquals(owner)); + } + + @Override + public Optional> getTagKey() { + return Optional.empty(); + } + + @Override + @NotNull + public Iterator> iterator() { + return this.getCachedList().iterator(); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UnionRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UnionRegistryEntryList.java new file mode 100644 index 0000000000..d3aa032c05 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UnionRegistryEntryList.java @@ -0,0 +1,105 @@ +/* + * 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.entrylists.defaults; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryListCodec; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; + +/** + * A default implementation of {@link RegistryEntryList} that contains everything in any of its backing lists. + * the resulting list is cached as a set and a list, to provide a consistent iteration order, and O(1) contains + * + * @param type of entries held by this list + */ +public class UnionRegistryEntryList extends MultiPartRegistryEntryList { + public static final CustomRegistryEntryListSerializer SERIALIZER = new Serializer(); + + public UnionRegistryEntryList(List> parts) { + super(parts); + } + + @Override + protected Set> createCache() { + return this.getParts() + .stream() + .flatMap(RegistryEntryList::stream) + .collect(Collectors.toUnmodifiableSet()); + } + + @Override + public CustomRegistryEntryListSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public String toString() { + return "Union{" + this.getParts() + "}"; + } + + private record Serializer() implements CustomRegistryEntryListSerializer { + private static final Identifier ID = Identifier.of("fabric", "or"); + + @Override + public Identifier getIdentifier() { + return ID; + } + + @Override + public MapCodec> createCodec(RegistryKey> registryKey, Codec> entryCodec, boolean forceList) { + return RegistryEntryListCodec.create(registryKey, entryCodec, forceList) + .listOf() + .xmap( + UnionRegistryEntryList::new, + MultiPartRegistryEntryList::getParts + ) + .fieldOf("values"); + } + + @Override + public PacketCodec> createPacketCodec(RegistryKey> registryKey) { + return PacketCodecs.registryEntryList(registryKey) + .collect(PacketCodecs.toList()) + .xmap( + UnionRegistryEntryList::new, + MultiPartRegistryEntryList::getParts + ); + } + + @Override + public @NotNull String toString() { + return "CustomRegistryEntryListSerializer{\"" + ID + "\"}"; + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UniversalRegistryEntryList.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UniversalRegistryEntryList.java new file mode 100644 index 0000000000..a5c6c52a3d --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/defaults/UniversalRegistryEntryList.java @@ -0,0 +1,147 @@ +/* + * 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.entrylists.defaults; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryOwner; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.math.random.Random; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.RegistryWrapperUtils; + +/** + * A default implementation of {@link RegistryEntryList} that contains everything in a given registry + * No caching is done. + * + * @param type of entries held by this list + */ +public record UniversalRegistryEntryList(RegistryWrapper source) implements CustomRegistryEntryList { + public static final CustomRegistryEntryListSerializer SERIALIZER = new Serializer(); + + @Override + public Stream> stream() { + return source.streamEntries().map(Function.identity()); + } + + @Override + public int size() { + return Math.toIntExact(source.streamEntries().count()); + } + + @Override + public boolean isBound() { + return true; + } + + @Override + public Either, List>> getStorage() { + return Either.right(stream().toList()); + } + + @Override + public Optional> getRandom(Random random) { + return Util.getRandomOrEmpty(stream().toList(), random); + } + + @Override + public RegistryEntry get(int index) { + return stream().toList().get(index); + } + + @Override + public boolean contains(RegistryEntry entry) { + return entry.getKey() + .flatMap(source::getOptional) + .isPresent(); + } + + @Override + public boolean ownerEquals(RegistryEntryOwner owner) { + return true; + } + + @Override + public Optional> getTagKey() { + return Optional.empty(); + } + + @Override + public @NotNull Iterator> iterator() { + return stream().iterator(); + } + + @Override + public CustomRegistryEntryListSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public @NotNull String toString() { + return "Universal{" + source.toString() + "}"; + } + + private record Serializer() implements CustomRegistryEntryListSerializer { + private static final Identifier ID = Identifier.of("fabric", "all"); + + @Override + public Identifier getIdentifier() { + return ID; + } + + @Override + public MapCodec> createCodec(RegistryKey> registryKey, Codec> registryEntryCodec, boolean forceList) { + return RegistryWrapperUtils.createMapCodec(registryKey).xmap( + UniversalRegistryEntryList::new, + UniversalRegistryEntryList::source + ); + } + + @Override + public PacketCodec> createPacketCodec(RegistryKey> registryKey) { + return RegistryWrapperUtils.createPacketCodec(registryKey).xmap( + UniversalRegistryEntryList::new, + UniversalRegistryEntryList::source + ); + } + + @Override + public @NotNull String toString() { + return "CustomRegistryEntryListSerializer{\"" + ID + "\"}"; + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/ChannelUtil.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/ChannelUtil.java new file mode 100644 index 0000000000..185ac7d0e2 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/ChannelUtil.java @@ -0,0 +1,61 @@ +/* + * 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.entrylists.util; + +import java.util.Set; + +import io.netty.buffer.ByteBuf; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; +import net.fabricmc.fabric.impl.networking.FabricRegistryByteBuf; + +/** + * Allows checking if a {@link RegistryByteBuf} has the registry sync module installed on the other side. + */ +public class ChannelUtil { + //this is a dummy, it should never actually be used + private static final PacketCodec CODEC = PacketCodec.unit(null); + + private static final CustomPayload.Id ID = new CustomPayload.Id<>( + Identifier.of("fabric", "is_installed_v0") + ); + + public static void init() { + PayloadTypeRegistry.configurationC2S().register(ID, CODEC); + PayloadTypeRegistry.configurationS2C().register(ID, CODEC); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isInstalled(RegistryByteBuf buf) { + if (!(buf instanceof FabricRegistryByteBuf fabricRegistryByteBuf)) { + return false; + } + + Set channels = fabricRegistryByteBuf.fabric_getSendableConfigurationChannels(); + + if (channels == null) { + return false; + } + + return channels.contains(ID.id()); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperFromRegistryEntryLookup.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperFromRegistryEntryLookup.java new file mode 100644 index 0000000000..bbf41da56f --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperFromRegistryEntryLookup.java @@ -0,0 +1,69 @@ +/* + * 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.entrylists.util; + +import java.util.Optional; +import java.util.stream.Stream; + +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.TagKey; + +import net.fabricmc.fabric.mixin.registry.sync.entrylists.RegistryKeyAccessor; + +record RegistryWrapperFromRegistryEntryLookup(RegistryEntryLookup entryLookup) implements RegistryWrapper { + /** + * @param entryLookup the entry lookup backing this wrapper + * @see RegistryWrapperUtils#castOrCreateFromEntryLookup(RegistryEntryLookup) + */ + RegistryWrapperFromRegistryEntryLookup { + if (entryLookup instanceof RegistryWrapper) { + throw new IllegalArgumentException("use RegistryWrapperFromRegistryEntryLookup#tryFromEntryLookup instead"); + } + } + + @Override + @SuppressWarnings("unchecked") + public Stream> streamEntries() { + return RegistryKeyAccessor + .getInstances() + .values() + .stream() + .flatMap(key -> entryLookup.getOptional((RegistryKey) key).stream()); + } + + @Override + @SuppressWarnings("unchecked") + public Stream> getTags() { + return TagKeyCache.stream().flatMap( + key -> entryLookup.getOptional((TagKey) key).stream() + ); + } + + @Override + public Optional> getOptional(RegistryKey key) { + return entryLookup.getOptional(key); + } + + @Override + public Optional> getOptional(TagKey tag) { + return entryLookup.getOptional(tag); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperUtils.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperUtils.java new file mode 100644 index 0000000000..3fafcc6bd6 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/RegistryWrapperUtils.java @@ -0,0 +1,112 @@ +/* + * 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.entrylists.util; + +import java.util.stream.Stream; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.MapLike; +import com.mojang.serialization.RecordBuilder; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryEntryLookup; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.RegistryWrapper; + +/** + * Utility class for working with {@link RegistryWrapper}s. + */ +public class RegistryWrapperUtils { + /** + * gets a {@link RegistryWrapper} from a {@link RegistryEntryLookup}. + * first checks if the entryLookup is a registryWrapper, then, if not, constructs a more expensive wrapper the bridges the loss of information + * + * @param the type of elements in the registry + * @param registryEntryLookup the entryLookup to wrap + * @return the entryLookup cast to a registryWrapper, or, if it isn't one, an expensive mimicry + */ + public static RegistryWrapper castOrCreateFromEntryLookup(RegistryEntryLookup registryEntryLookup) { + if (registryEntryLookup instanceof RegistryWrapper registryWrapper) { + return registryWrapper; + } + + return new RegistryWrapperFromRegistryEntryLookup<>(registryEntryLookup); + } + + /** + * Creates a {@link MapCodec} for a {@link RegistryWrapper} based on the provided {@link RegistryKey}. + * the Wrapper is not encoded in any way and is fetched from the RegistryOps on decode. + * + * @param registryKey the key of the registry to create a wrapper for + * @param the type of elements in the registry + * @return a MapCodec that can decode a {@link RegistryWrapper} from the registry key + */ + public static MapCodec> createMapCodec(RegistryKey> registryKey) { + return new MapCodec<>() { + @Override + public Stream keys(DynamicOps ops) { + // no keys, this doesn't touch this input or the output + return Stream.empty(); + } + + @Override + public DataResult> decode(DynamicOps ops, MapLike input) { + if (!(ops instanceof RegistryOps registryOps)) { + return DataResult.error(() -> "RegistryWrapperCodec requires RegistryOps"); + } + + return registryOps.getEntryLookup(registryKey) + .map(RegistryWrapperUtils::castOrCreateFromEntryLookup) + .map(DataResult::success) + .orElseGet(() -> DataResult.error(() -> "Registry Not Found")); + } + + @Override + public RecordBuilder encode(RegistryWrapper input, DynamicOps ops, RecordBuilder prefix) { + // No need to encode + return prefix; + } + }; + } + + /** + * Creates a {@link PacketCodec} for a {@link RegistryWrapper} based on the provided {@link RegistryKey}. + * the Wrapper is not encoded in any way and is fetched from the RegistryByteBuf on decode. + * + * @param registryKey the key of the registry to create a wrapper for + * @param the type of elements in the registry + * @return a PacketCodec that can decode a {@link RegistryWrapper} from the registry key + */ + public static PacketCodec> createPacketCodec(RegistryKey> registryKey) { + return new PacketCodec<>() { + @Override + public RegistryWrapper decode(RegistryByteBuf buf) { + return buf.getRegistryManager().getOrThrow(registryKey); + } + + @Override + public void encode(RegistryByteBuf buf, RegistryWrapper value) { + // No need to encode + } + }; + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/TagKeyCache.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/TagKeyCache.java new file mode 100644 index 0000000000..a05bf08c52 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/entrylists/util/TagKeyCache.java @@ -0,0 +1,35 @@ +/* + * 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.entrylists.util; + +import java.util.Set; +import java.util.stream.Stream; + +import net.minecraft.registry.tag.TagKey; + +public class TagKeyCache { + // a weak immutable set containing all the TagKey instances in the game + private static Set> CACHE; + + public static void acceptCache(Set> cache) { + CACHE = cache; + } + + static Stream> stream() { + return CACHE.stream(); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/PacketCodecs$25Mixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/PacketCodecs$25Mixin.java new file mode 100644 index 0000000000..f4cd5d039c --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/PacketCodecs$25Mixin.java @@ -0,0 +1,113 @@ +/* + * 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.entrylists; + +import java.util.HashMap; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Cancellable; +import io.netty.buffer.ByteBuf; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +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.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.encoding.VarInts; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryListSerializer; +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.ChannelUtil; + +@SuppressWarnings("unchecked") +@Mixin(targets = "net.minecraft.network.codec.PacketCodecs$25") +class PacketCodecs$25Mixin { + @Unique + private static final String COMMENT_IN_MIXIN_EXPORT = "-45 is ours. if anyone else uses it, they deserve the consequences"; + @Unique + private static final int FABRIC_CUSTOM_REGISTRY_LIST_ID = -45; + + @Shadow + @Final + RegistryKey> field_54511; + + @Unique + HashMap>> cache = new HashMap<>(); + + private PacketCodecs$25Mixin() { + } + + @Inject( + method = "encode(Lnet/minecraft/network/RegistryByteBuf;Lnet/minecraft/registry/entry/RegistryEntryList;)V", + at = @At("HEAD"), + cancellable = true + ) + private void encodeCustomRegistryEntryList(RegistryByteBuf registryByteBuf, RegistryEntryList registryEntryList, CallbackInfo ci) { + if (!ChannelUtil.isInstalled(registryByteBuf) || !(registryEntryList instanceof CustomRegistryEntryList customRegistryEntryList)) { + return; + } + + VarInts.write(registryByteBuf, FABRIC_CUSTOM_REGISTRY_LIST_ID); + Identifier.PACKET_CODEC.encode(registryByteBuf, customRegistryEntryList.getSerializer().getIdentifier()); + PacketCodec> packetCodec = cache.computeIfAbsent( + customRegistryEntryList.getSerializer(), + serializer1 -> (PacketCodec>) serializer1.createPacketCodec(field_54511) + ); + packetCodec.encode(registryByteBuf, (CustomRegistryEntryList) customRegistryEntryList); + ci.cancel(); + } + + @WrapOperation( + method = "decode(Lnet/minecraft/network/RegistryByteBuf;)Lnet/minecraft/registry/entry/RegistryEntryList;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/encoding/VarInts;read(Lio/netty/buffer/ByteBuf;)I" + ) + ) + private int decodeCustomRegistryEntryList(ByteBuf buf, Operation original, RegistryByteBuf registryByteBuf, @Cancellable CallbackInfoReturnable> cir) { + int id = original.call(buf); + + if (!ChannelUtil.isInstalled(registryByteBuf) || id != FABRIC_CUSTOM_REGISTRY_LIST_ID || cir.isCancelled()) { + return id; + } + + Identifier identifier = Identifier.PACKET_CODEC.decode(buf); + CustomRegistryEntryListSerializer serializer = CustomRegistryEntryListSerializer.getSerializer(identifier); + + if (serializer == null) { + throw new IllegalStateException("Unknown CustomRegistryEntryListSerializer: " + identifier); + } + + PacketCodec> packetCodec = cache.computeIfAbsent( + serializer, + serializer1 -> (PacketCodec>) serializer1.createPacketCodec(field_54511) + ); + + cir.setReturnValue(packetCodec.decode(registryByteBuf)); + return id; + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListCodecMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListCodecMixin.java new file mode 100644 index 0000000000..f47cbdb201 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListCodecMixin.java @@ -0,0 +1,90 @@ +/* + * 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.entrylists; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryListCodec; + +import net.fabricmc.fabric.api.event.registry.entrylists.CustomRegistryEntryList; +import net.fabricmc.fabric.impl.registry.sync.entrylists.CustomRegistryEntryListSerializerImpl; + +@SuppressWarnings("unchecked") +@Mixin(RegistryEntryListCodec.class) +class RegistryEntryListCodecMixin { + @Unique + private Codec> fabric$codec; + + @Inject( + method = "", + at = @At("TAIL") + ) + private void bindFabricCodec(RegistryKey> registry, Codec> entryCodec, boolean alwaysSerializeAsList, CallbackInfo ci) { + fabric$codec = CustomRegistryEntryListSerializerImpl.SERIALIZER_CODEC.dispatch( + "fabric:type", + CustomRegistryEntryList::getSerializer, + serializer -> serializer.createCodec(registry, entryCodec, alwaysSerializeAsList) + ); + } + + @Inject( + method = "encode(Lnet/minecraft/registry/entry/RegistryEntryList;Lcom/mojang/serialization/DynamicOps;Ljava/lang/Object;)Lcom/mojang/serialization/DataResult;", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/registry/entry/RegistryEntryListCodec;entryListStorageCodec:Lcom/mojang/serialization/Codec;" + ), + cancellable = true + ) + private void encodeCustomWithCustomCodec(RegistryEntryList registryEntryList, DynamicOps dynamicOps, T prefix, CallbackInfoReturnable> cir) { + if (registryEntryList instanceof CustomRegistryEntryList customRegistryEntryList) { + cir.setReturnValue(fabric$codec.encode(customRegistryEntryList, dynamicOps, prefix)); + } + } + + @Inject( + method = "decode", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/registry/entry/RegistryEntryListCodec;entryListStorageCodec:Lcom/mojang/serialization/Codec;" + ), + cancellable = true + ) + private void decodeCustomWithCustomCodec(DynamicOps ops, T input, CallbackInfoReturnable, T>>> cir) { + if (CustomRegistryEntryListSerializerImpl.isSerializedCustomRegistryEntryList(ops, input)) { + cir.setReturnValue( + fabric$codec.decode(ops, input).map( + pair -> pair.mapFirst( + list -> (RegistryEntryList) list + ) + ) + ); + } + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListMixin.java new file mode 100644 index 0000000000..56438ec3ec --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListMixin.java @@ -0,0 +1,27 @@ +/* + * 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.entrylists; + +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.registry.entry.RegistryEntryList; + +import net.fabricmc.fabric.api.event.registry.entrylists.FabricRegistryEntryList; + +@Mixin(RegistryEntryList.class) +interface RegistryEntryListMixin extends FabricRegistryEntryList { +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListNamedMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListNamedMixin.java new file mode 100644 index 0000000000..3edb60dc62 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryEntryListNamedMixin.java @@ -0,0 +1,38 @@ +/* + * 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.entrylists; + +import java.util.List; + +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.CallbackInfo; + +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; + +@Mixin(RegistryEntryList.Named.class) +public abstract class RegistryEntryListNamedMixin implements RegistryEntryListMixin { + @Inject( + method = "setEntries", + at = @At("TAIL") + ) + private void invalidateDependents(List> entries, CallbackInfo ci) { + this.invalidate(); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryKeyAccessor.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryKeyAccessor.java new file mode 100644 index 0000000000..1333c2da08 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/RegistryKeyAccessor.java @@ -0,0 +1,32 @@ +/* + * 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.entrylists; + +import java.util.concurrent.ConcurrentMap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.registry.RegistryKey; + +@Mixin(RegistryKey.class) +public interface RegistryKeyAccessor { + @Accessor("INSTANCES") + static ConcurrentMap> getInstances() { + throw new AssertionError("Mixin failed to apply"); + } +} diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/TagKeyMixin.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/TagKeyMixin.java new file mode 100644 index 0000000000..9503cbc8e9 --- /dev/null +++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/entrylists/TagKeyMixin.java @@ -0,0 +1,52 @@ +/* + * 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.entrylists; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.registry.tag.TagKey; + +import net.fabricmc.fabric.impl.registry.sync.entrylists.util.TagKeyCache; + +@Mixin(TagKey.class) +class TagKeyMixin { + @Unique + private static final Set> CACHE = Collections.newSetFromMap(new WeakHashMap<>()); + + private TagKeyMixin() { + } + + @Inject( + method = "", + at = @At("TAIL") + ) + private void storeKey(CallbackInfo ci) { + CACHE.add((TagKey) (Object) this); + } + + static { + TagKeyCache.acceptCache(Collections.unmodifiableSet(CACHE)); + } +} 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 c51948bd22..b1ed3492d1 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 @@ -5,7 +5,6 @@ "mixins": [ "BlocksMixin", "BootstrapMixin", - "SerializedChunkMixin", "DebugChunkGeneratorAccessor", "ExperimentalRegistriesValidatorMixin", "IdListMixin", @@ -17,8 +16,15 @@ "RegistryMixin", "SaveLoadingMixin", "SerializableRegistriesMixin", + "SerializedChunkMixin", "SimpleRegistryAccessor", - "SimpleRegistryMixin" + "SimpleRegistryMixin", + "entrylists.RegistryEntryListNamedMixin", + "entrylists.PacketCodecs$25Mixin", + "entrylists.RegistryEntryListCodecMixin", + "entrylists.RegistryEntryListMixin", + "entrylists.RegistryKeyAccessor", + "entrylists.TagKeyMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-registry-sync-v0/src/main/resources/fabric.mod.json b/fabric-registry-sync-v0/src/main/resources/fabric.mod.json index dc0cb9bb8b..f9af85110d 100644 --- a/fabric-registry-sync-v0/src/main/resources/fabric.mod.json +++ b/fabric-registry-sync-v0/src/main/resources/fabric.mod.json @@ -40,7 +40,8 @@ "custom": { "fabric-api:module-lifecycle": "stable", "loom:injected_interfaces": { - "net/minecraft/class_2378": ["net/fabricmc/fabric/api/event/registry/FabricRegistry"] + "net/minecraft/class_2378": ["net/fabricmc/fabric/api/event/registry/FabricRegistry"], + "net/minecraft/class_6885": ["net/fabricmc/fabric/api/event/registry/entrylists/FabricRegistryEntryList"] } } } diff --git a/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsUnitTests.java b/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsUnitTests.java new file mode 100644 index 0000000000..a1766ac245 --- /dev/null +++ b/fabric-registry-sync-v0/src/test/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsUnitTests.java @@ -0,0 +1,123 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Stream; + +import com.google.gson.JsonElement; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; + +import net.minecraft.Bootstrap; +import net.minecraft.SharedConstants; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.entry.RegistryEntryListCodec; + +import net.fabricmc.fabric.impl.registry.sync.entrylists.CustomRegistryEntryListSerializerImpl; +import net.fabricmc.fabric.impl.registry.sync.entrylists.defaults.DefaultCustomRegistryEntryListsImpl; +import net.fabricmc.fabric.impl.registry.sync.entrylists.defaults.InverseRegistryEntryList; + +public class CustomRegistryEntryListsUnitTests { + @BeforeAll + static void beforeAll() { + SharedConstants.createGameVersion(); + Bootstrap.initialize(); + DefaultCustomRegistryEntryListsImpl.register(); + } + + @Test + void testSerialization() { + DynamicRegistryManager drm = mockDRM(); + Registry reg = drm.getOrThrow(RegistryKeys.BLOCK); + RegistryOps ops = drm.getOps(JsonOps.INSTANCE); + RegistryEntryList entryList = RegistryEntryList.of(reg.getEntry(Blocks.ACACIA_LEAVES), reg.getEntry(Blocks.BRAIN_CORAL_BLOCK)); + InverseRegistryEntryList customEntryList = new InverseRegistryEntryList<>(reg, entryList); + Codec> codec = swapType(RegistryEntryListCodec.create(reg.getKey(), reg.getEntryCodec(), false)); + JsonElement serialized = Assertions.assertDoesNotThrow(() -> codec.encodeStart(ops, customEntryList).getOrThrow()); + Assertions.assertTrue(CustomRegistryEntryListSerializerImpl.isSerializedCustomRegistryEntryList(ops, serialized)); + RegistryEntryList deserialized = Assertions.assertDoesNotThrow(() -> codec.parse(ops, serialized).getOrThrow()); + Assertions.assertInstanceOf(InverseRegistryEntryList.class, deserialized); + } + + @Test + @SuppressWarnings("unchecked") + void testInvalidation() { + RegistryEntryList parent = mockParentList(); + RegistryEntryList child1 = spy(RegistryEntryList.class); + RegistryEntryList child2 = spy(RegistryEntryList.class); + parent.registerDependency(child1); + parent.registerDependency(child2); + parent.invalidate(); + verify(child1, times(1)).invalidate(); + verify(child2, times(1)).invalidate(); + } + + @SuppressWarnings("unchecked") + private static DynamicRegistryManager mockDRM() { + DynamicRegistryManager drm = mock(DynamicRegistryManager.class); + when(drm.getOps(any())).thenReturn((RegistryOps) (Object) RegistryOps.of(JsonOps.INSTANCE, drm)); + when(drm.getOptional(any())).thenAnswer(invocation -> { + RegistryKey key = invocation.getArgument(0); + return Optional.ofNullable(Registries.REGISTRIES.get(key.getValue())); + }); + doAnswer(invocation -> drm.getOptional(invocation.getArgument(0)).orElseThrow( + () -> new NoSuchElementException("No registry found for key: " + invocation.getArgument(0)) + )).when(drm).getOrThrow(any()); + return drm; + } + + @SuppressWarnings("unchecked") + private static RegistryEntryList mockParentList() { + RegistryEntryList parentList = mock(RegistryEntryList.class); + when(parentList.stream()).thenReturn(Stream.empty()); + doAnswer(InvocationOnMock::callRealMethod) + .when(parentList) + .invalidate(); + + doAnswer(InvocationOnMock::callRealMethod) + .when(parentList) + .registerDependency(any()); + return parentList; + } + + @SuppressWarnings("unchecked") + private static Codec swapType(Codec codec) { + return (Codec) codec; + } +} diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsTest.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsTest.java new file mode 100644 index 0000000000..a056514d09 --- /dev/null +++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/CustomRegistryEntryListsTest.java @@ -0,0 +1,105 @@ +/* + * 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.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.BlockTags; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents; +import net.fabricmc.fabric.api.event.registry.entrylists.DefaultCustomRegistryEntryLists; + +public class CustomRegistryEntryListsTest implements ModInitializer { + private static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public void onInitialize() { + CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> { + LOGGER.info("Starting custom registry list tests..."); + + if (client) { + return; + } + + RegistryEntryList anvils = registries.getOrThrow(RegistryKeys.BLOCK).getOrThrow(BlockTags.ANVIL); + RegistryEntryList corals = registries.getOrThrow(RegistryKeys.BLOCK).getOrThrow(BlockTags.CORAL_BLOCKS); + RegistryEntry anvil = registries.getOrThrow(RegistryKeys.BLOCK).getEntry(Blocks.ANVIL); + RegistryEntry chippedAnvil = registries.getOrThrow(RegistryKeys.BLOCK).getEntry(Blocks.CHIPPED_ANVIL); + RegistryEntry coral = registries.getOrThrow(RegistryKeys.BLOCK).getEntry(Blocks.BRAIN_CORAL_BLOCK); + RegistryEntry log = registries.getOrThrow(RegistryKeys.BLOCK).getEntry(Blocks.OAK_LOG); + + RegistryEntryList union = DefaultCustomRegistryEntryLists.union( + anvils, + corals + ); + + warnIfFalse(union.contains(anvil), "CustomRegistryEntryList.union doesn't contain anvil"); + warnIfFalse(union.contains(coral), "CustomRegistryEntryList.union doesn't contain brain coral"); + + RegistryEntryList intersection = DefaultCustomRegistryEntryLists.intersection( + anvils, + corals + ); + + warnIfFalse(!intersection.contains(anvil), "CustomRegistryEntryList.intersection contains anvil"); + warnIfFalse(!intersection.contains(coral), "CustomRegistryEntryList.intersection contains brain coral"); + + RegistryEntryList inverse = DefaultCustomRegistryEntryLists.inverse( + registries.getOrThrow(RegistryKeys.BLOCK), + union + ); + + warnIfFalse(!inverse.contains(anvil), "CustomRegistryEntryList.inverse contains anvil"); + warnIfFalse(!inverse.contains(coral), "CustomRegistryEntryList.inverse contains brain coral"); + warnIfFalse(inverse.contains(log), "CustomRegistryEntryList.inverse doesn't contain oak log"); + + RegistryEntryList universal = DefaultCustomRegistryEntryLists.universal( + registries.getOrThrow(RegistryKeys.BLOCK) + ); + + warnIfFalse(universal.contains(anvil), "CustomRegistryEntryList.universal doesn't contain anvil"); + warnIfFalse(universal.contains(coral), "CustomRegistryEntryList.universal doesn't contain brain coral"); + warnIfFalse(universal.contains(log), "CustomRegistryEntryList.universal doesn't contain oak log"); + + RegistryEntryList subtraction = DefaultCustomRegistryEntryLists.subtraction( + registries.getOrThrow(RegistryKeys.BLOCK), + universal, + RegistryEntryList.of(registries.getOrThrow(RegistryKeys.BLOCK).getEntry(Blocks.ANVIL)) + ); + + warnIfFalse(!subtraction.contains(anvil), "CustomRegistryEntryList.subtraction contains anvil"); + warnIfFalse(subtraction.contains(chippedAnvil), "CustomRegistryEntryList.subtraction doesn't contain chipped anvil"); + + // a future pr test serialization logic here. + + LOGGER.info("Finished custom registry list tests..."); + }); + } + + private static void warnIfFalse(boolean condition, String message) { + if (!condition) { + LOGGER.warn(message); + } + } +} 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 0ed2f135fe..912221160a 100644 --- a/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json +++ b/fabric-registry-sync-v0/src/testmod/resources/fabric.mod.json @@ -15,6 +15,7 @@ "main": [ "net.fabricmc.fabric.test.registry.sync.CustomDynamicRegistryTest", "net.fabricmc.fabric.test.registry.sync.RegistrySyncTest", + "net.fabricmc.fabric.test.registry.sync.CustomRegistryEntryListsTest", "net.fabricmc.fabric.test.registry.sync.RegistryAliasTest" ], "client": [