Fix porting issues in Dynamic Registries API ()

* Fix TAGS_LOADED event not being invoked on client join

* Skip loading empty dynamic registries (client-side)

* Update ClientTest to handle 24w04a

* Skip syncing empty dynamic registries (server-side)

* Fix dynamic registry using wrong codec, tag syncing depending on class load order

* Fix DynamicRegistrySetupEvent being called on client

* Test syncing empty registry's tag

* Actually check skipOnEmpty

* Downgrade AssertionError to log due to MC-268096
This commit is contained in:
apple502j 2024-01-28 21:56:20 +09:00 committed by GitHub
parent 9bfa344c7d
commit bbf5165de7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 130 additions and 22 deletions
fabric-lifecycle-events-v1/src/client/java/net/fabricmc/fabric/mixin/event/lifecycle/client
fabric-registry-sync-v0/src
client
java/net/fabricmc/fabric/mixin/registry/sync/client
resources
main
testmod
java/net/fabricmc/fabric/test/registry/sync
resources/data/fabric-registry-sync-v0-testmod/tags/fabric/test_dynamic_synced_empty
testmodClient/java/net/fabricmc/fabric/test/registry/sync/client

View file

@ -26,7 +26,6 @@ import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.network.packet.s2c.common.SynchronizeTagsS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket;
import net.minecraft.world.chunk.WorldChunk;
@ -96,15 +95,12 @@ abstract class ClientPlayNetworkHandlerMixin {
}
}
/**
* Also invoked during GameJoin, but before Networking API fires the Ready event.
*/
@SuppressWarnings("ConstantConditions")
@Inject(
method = "onSynchronizeTags",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;refreshTagBasedData()V"
)
)
private void hookOnSynchronizeTags(SynchronizeTagsS2CPacket packet, CallbackInfo ci) {
@Inject(method = "refreshTagBasedData", at = @At("RETURN"))
private void hookOnSynchronizeTags(CallbackInfo ci) {
ClientPlayNetworkHandler self = (ClientPlayNetworkHandler) (Object) this;
CommonLifecycleEvents.TAGS_LOADED.invoker().onTagsLoaded(self.getRegistryManager(), true);
}

View file

@ -0,0 +1,53 @@
/*
* 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.client;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.registry.SerializableRegistries;
import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl;
@Mixin(targets = "net/minecraft/class_9173$class_9174")
public class ClientRegistriesDynamicRegistriesMixin {
@Shadow
@Final
private Map<RegistryKey<? extends Registry<?>>, List<SerializableRegistries.class_9176>> field_48769;
/**
* Keep the pre-24w04a behavior of removing empty registries, even if the client knows that registry.
*/
@WrapOperation(method = "method_56589", at = @At(value = "FIELD", target = "Lnet/minecraft/registry/RegistryLoader;field_48709:Ljava/util/List;", opcode = Opcodes.GETSTATIC))
private List<RegistryLoader.Entry<?>> skipEmptyRegistries(Operation<List<RegistryLoader.Entry<?>>> operation) {
List<RegistryLoader.Entry<?>> result = new ArrayList<>(operation.call());
result.removeIf(entry -> DynamicRegistriesImpl.SKIP_EMPTY_SYNC_REGISTRIES.contains(entry.key()) && !this.field_48769.containsKey(entry.key()));
return result;
}
}

View file

@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_17",
"client": [
"BlockColorsMixin",
"ClientRegistriesDynamicRegistriesMixin",
"ItemColorsMixin",
"ItemModelsMixin",
"MinecraftClientMixin",

View file

@ -140,8 +140,8 @@ public final class DynamicRegistries {
* @param <T> the entry type of the registry
*/
public static <T> void registerSynced(RegistryKey<? extends Registry<T>> key, Codec<T> dataCodec, Codec<T> networkCodec, SyncOption... options) {
RegistryLoader.Entry<T> entry = DynamicRegistriesImpl.register(key, dataCodec);
DynamicRegistriesImpl.addSyncedRegistry(entry, options);
DynamicRegistriesImpl.register(key, dataCodec);
DynamicRegistriesImpl.addSyncedRegistry(key, networkCodec, options);
}
/**

View file

@ -28,6 +28,7 @@ import org.jetbrains.annotations.Unmodifiable;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.registry.SerializableRegistries;
import net.fabricmc.fabric.api.event.registry.DynamicRegistries;
@ -64,19 +65,26 @@ public final class DynamicRegistriesImpl {
return entry;
}
public static <T> void addSyncedRegistry(RegistryLoader.Entry<T> entry, DynamicRegistries.SyncOption... options) {
Objects.requireNonNull(entry, "Registry loader entry cannot be null");
public static <T> void addSyncedRegistry(RegistryKey<? extends Registry<T>> key, Codec<T> networkCodec, DynamicRegistries.SyncOption... options) {
Objects.requireNonNull(key, "Registry key cannot be null");
Objects.requireNonNull(networkCodec, "Network codec cannot be null");
Objects.requireNonNull(options, "Options cannot be null");
if (!(RegistryLoader.field_48709 instanceof ArrayList<RegistryLoader.Entry<?>>)) {
RegistryLoader.field_48709 = new ArrayList<>(RegistryLoader.field_48709);
}
RegistryLoader.field_48709.add(entry);
RegistryLoader.field_48709.add(new RegistryLoader.Entry<>(key, networkCodec));
if (!(SerializableRegistries.field_48771 instanceof HashSet<RegistryKey<? extends Registry<?>>>)) {
SerializableRegistries.field_48771 = new HashSet<>(SerializableRegistries.field_48771);
}
SerializableRegistries.field_48771.add(key);
for (DynamicRegistries.SyncOption option : options) {
if (option == DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY) {
SKIP_EMPTY_SYNC_REGISTRIES.add(entry.key());
SKIP_EMPTY_SYNC_REGISTRIES.add(key);
}
}
}

View file

@ -20,8 +20,11 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
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.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
@ -39,6 +42,23 @@ import net.fabricmc.fabric.impl.registry.sync.DynamicRegistryViewImpl;
@Mixin(RegistryLoader.class)
public class RegistryLoaderMixin {
@Unique
private static final ThreadLocal<Boolean> IS_SERVER = ThreadLocal.withInitial(() -> false);
/**
* Sets IS_SERVER flag. Note that this must be reset after call, as the render thread
* invokes this method as well.
*/
@WrapOperation(method = "method_56515", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/RegistryLoader;load(Lnet/minecraft/registry/RegistryLoader$RegistryLoadable;Lnet/minecraft/registry/DynamicRegistryManager;Ljava/util/List;)Lnet/minecraft/registry/DynamicRegistryManager$Immutable;"))
private static DynamicRegistryManager.Immutable wrapIsServerCall(@Coerce Object registryLoadable, DynamicRegistryManager baseRegistryManager, List<RegistryLoader.Entry<?>> entries, Operation<DynamicRegistryManager.Immutable> original) {
try {
IS_SERVER.set(true);
return original.call(registryLoadable, baseRegistryManager, entries);
} finally {
IS_SERVER.set(false);
}
}
@Inject(
method = "load(Lnet/minecraft/registry/RegistryLoader$RegistryLoadable;Lnet/minecraft/registry/DynamicRegistryManager;Ljava/util/List;)Lnet/minecraft/registry/DynamicRegistryManager$Immutable;",
at = @At(
@ -48,6 +68,8 @@ public class RegistryLoaderMixin {
)
)
private static void beforeLoad(@Coerce Object registryLoadable, DynamicRegistryManager baseRegistryManager, List<RegistryLoader.Entry<?>> entries, CallbackInfoReturnable<DynamicRegistryManager.Immutable> cir, @Local(ordinal = 1) List<RegistryLoader.class_9158<?>> registriesList) {
if (!IS_SERVER.get()) return;
Map<RegistryKey<? extends Registry<?>>, Registry<?>> registries = new IdentityHashMap<>(registriesList.size());
for (RegistryLoader.class_9158<?> entry : registriesList) {

View file

@ -16,13 +16,19 @@
package net.fabricmc.fabric.mixin.registry.sync;
import java.util.function.BiConsumer;
import com.mojang.serialization.DynamicOps;
import org.spongepowered.asm.mixin.Dynamic;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.registry.SerializableRegistries;
import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl;
@ -30,6 +36,9 @@ import net.fabricmc.fabric.impl.registry.sync.DynamicRegistriesImpl;
// Implements skipping empty dynamic registries with the SKIP_WHEN_EMPTY sync option.
@Mixin(SerializableRegistries.class)
abstract class SerializableRegistriesMixin {
/**
* Used for tag syncing.
*/
@Dynamic("method_45961: Stream.filter in stream")
@Inject(method = "method_56601", at = @At("HEAD"), cancellable = true)
private static void filterNonSyncedEntries(DynamicRegistryManager.Entry<?> entry, CallbackInfoReturnable<Boolean> cir) {
@ -39,4 +48,17 @@ abstract class SerializableRegistriesMixin {
cir.setReturnValue(false);
}
}
/**
* Used for registry serialization.
*/
@Dynamic("method_56597: Optional.ifPresent in serialize")
@Inject(method = "method_56596", at = @At("HEAD"), cancellable = true)
private static void filterNonSyncedEntriesAgain(RegistryLoader.Entry entry, DynamicOps dynamicOps, BiConsumer biConsumer, Registry registry, CallbackInfo ci) {
boolean canSkip = DynamicRegistriesImpl.SKIP_EMPTY_SYNC_REGISTRIES.contains(registry.getKey());
if (canSkip && registry.size() == 0) {
ci.cancel();
}
}
}

View file

@ -6,5 +6,7 @@ accessible method net/minecraft/registry/Registries init ()V
accessible field net/minecraft/registry/RegistryLoader field_48709 Ljava/util/List;
mutable field net/minecraft/registry/RegistryLoader field_48709 Ljava/util/List;
accessible field net/minecraft/registry/SerializableRegistries field_48771 Ljava/util/Set;
mutable field net/minecraft/registry/SerializableRegistries field_48771 Ljava/util/Set;
accessible class net/minecraft/registry/RegistryLoader$class_9158

View file

@ -72,7 +72,7 @@ public final class CustomDynamicRegistryTest implements ModInitializer {
.orElseThrow();
if (!entry.isIn(TEST_DYNAMIC_OBJECT_TAG)) {
throw new AssertionError("Required dynamic registry entry is not in the expected tag! client: " + client);
LOGGER.error("Required dynamic registry entry is not in the expected tag! client: " + client);
}
LOGGER.info("Found {} in tag {} (client: {})", entry, TEST_DYNAMIC_OBJECT_TAG, client);

View file

@ -68,10 +68,9 @@ public final class DynamicRegistryClientTest implements ClientModInitializer {
didNotReceive(TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY, SYNCED_ID);
}
// The client server check is needed since the registries are passed through in singleplayer.
// The network codec flag would always be false in those cases.
if (client.getServer() == null && !synced2.usesNetworkCodec()) {
throw new AssertionError("Entries in " + TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY + " should use network codec");
// In 24w04a, dynamic registries are always serialized and sent even in singleplayer.
if (!synced2.usesNetworkCodec()) {
LOGGER.error("Entries in " + TEST_SYNCED_2_DYNAMIC_REGISTRY_KEY + " should use network codec");
}
// TODO 1.20.2
@ -83,8 +82,8 @@ public final class DynamicRegistryClientTest implements ClientModInitializer {
// throw new AssertionError("Did not match up synced nested entry to the other synced value");
//}
// If the registries weren't passed through in SP, check that the empty registry was skipped.
if (client.getServer() == null && handler.getRegistryManager().getOptional(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY).isPresent()) {
// See ClientRegistriesDynamicRegistriesMixin
if (handler.getRegistryManager().getOptional(TEST_EMPTY_SYNCED_DYNAMIC_REGISTRY_KEY).isPresent()) {
throw new AssertionError("Received empty registry that should have been skipped");
}