mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-30 19:38:21 -05:00
Add optional registries & refactor registry sync (#4233)
* Refactor registry sync * Remove old file * Checkstyle * Fixes * Improve error message * Fix * Fix test
This commit is contained in:
parent
b90fb141f3
commit
cc0fa2fec8
17 changed files with 826 additions and 260 deletions
|
@ -20,8 +20,11 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import net.minecraft.network.NetworkPhase;
|
import net.minecraft.network.NetworkPhase;
|
||||||
import net.minecraft.network.PacketCallbacks;
|
import net.minecraft.network.PacketCallbacks;
|
||||||
|
import net.minecraft.network.packet.BrandCustomPayload;
|
||||||
import net.minecraft.network.packet.CustomPayload;
|
import net.minecraft.network.packet.CustomPayload;
|
||||||
import net.minecraft.network.packet.Packet;
|
import net.minecraft.network.packet.Packet;
|
||||||
import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
|
import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
|
||||||
|
@ -44,6 +47,8 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
||||||
private final MinecraftServer server;
|
private final MinecraftServer server;
|
||||||
private final ServerConfigurationNetworking.Context context;
|
private final ServerConfigurationNetworking.Context context;
|
||||||
private RegisterState registerState = RegisterState.NOT_SENT;
|
private RegisterState registerState = RegisterState.NOT_SENT;
|
||||||
|
@Nullable
|
||||||
|
private String clientBrand = null;
|
||||||
|
|
||||||
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
|
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
|
||||||
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
|
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
|
||||||
|
@ -55,6 +60,16 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
||||||
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
|
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(CustomPayload payload) {
|
||||||
|
if (payload instanceof BrandCustomPayload brandCustomPayload) {
|
||||||
|
clientBrand = brandCustomPayload.brand();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.handle(payload);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invokeInitEvent() {
|
protected void invokeInitEvent() {
|
||||||
}
|
}
|
||||||
|
@ -169,6 +184,10 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
|
||||||
handler.send(packet, callback);
|
handler.send(packet, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable String getClientBrand() {
|
||||||
|
return clientBrand;
|
||||||
|
}
|
||||||
|
|
||||||
private enum RegisterState {
|
private enum RegisterState {
|
||||||
NOT_SENT,
|
NOT_SENT,
|
||||||
SENT,
|
SENT,
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
/*
|
||||||
|
* 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.client.registry.sync;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.screen.ScreenTexts;
|
||||||
|
import net.minecraft.text.MutableText;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.thread.ThreadExecutor;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemappableRegistry;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
|
||||||
|
|
||||||
|
public final class ClientRegistrySyncHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ClientRegistrySyncHandler.class);
|
||||||
|
|
||||||
|
private ClientRegistrySyncHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends RegistryPacketHandler.RegistrySyncPayload> CompletableFuture<Boolean> receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler<T> handler, T payload, boolean accept) {
|
||||||
|
handler.receivePayload(payload);
|
||||||
|
|
||||||
|
if (!handler.isPacketFinished()) {
|
||||||
|
return CompletableFuture.completedFuture(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RegistrySyncManager.DEBUG) {
|
||||||
|
String handlerName = handler.getClass().getSimpleName();
|
||||||
|
LOGGER.info("{} total packet: {}", handlerName, handler.getTotalPacketReceived());
|
||||||
|
LOGGER.info("{} raw size: {}", handlerName, handler.getRawBufSize());
|
||||||
|
LOGGER.info("{} deflated size: {}", handlerName, handler.getDeflatedBufSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryPacketHandler.SyncedPacketData data = handler.getSyncedPacketData();
|
||||||
|
|
||||||
|
if (!accept) {
|
||||||
|
return CompletableFuture.completedFuture(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return executor.submit(() -> {
|
||||||
|
if (data == null) {
|
||||||
|
throw new CompletionException(new RemapException("Received null map in sync packet!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
apply(data);
|
||||||
|
return true;
|
||||||
|
} catch (RemapException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void apply(RegistryPacketHandler.SyncedPacketData data) throws RemapException {
|
||||||
|
// First check that all of the data provided is valid before making any changes
|
||||||
|
checkRemoteRemap(data);
|
||||||
|
|
||||||
|
for (Map.Entry<Identifier, Object2IntMap<Identifier>> entry : data.idMap().entrySet()) {
|
||||||
|
final Identifier registryId = entry.getKey();
|
||||||
|
|
||||||
|
Registry<?> registry = Registries.REGISTRIES.get(registryId);
|
||||||
|
|
||||||
|
// Registry was not found on the client, is it optional?
|
||||||
|
// If so we can just ignore it.
|
||||||
|
// Otherwise we throw an exception and disconnect.
|
||||||
|
if (registry == null) {
|
||||||
|
if (isRegistryOptional(registryId, data)) {
|
||||||
|
LOGGER.info("Received registry data for unknown optional registry: {}", registryId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registry instanceof RemappableRegistry remappableRegistry) {
|
||||||
|
remappableRegistry.remap(entry.getValue(), RemappableRegistry.RemapMode.REMOTE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RemapException("Registry " + registryId + " is not remappable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static void checkRemoteRemap(RegistryPacketHandler.SyncedPacketData data) throws RemapException {
|
||||||
|
Map<Identifier, Object2IntMap<Identifier>> map = data.idMap();
|
||||||
|
ArrayList<Identifier> missingRegistries = new ArrayList<>();
|
||||||
|
Map<Identifier, List<Identifier>> missingEntries = new HashMap<>();
|
||||||
|
|
||||||
|
for (Identifier registryId : map.keySet()) {
|
||||||
|
final Object2IntMap<Identifier> remoteRegistry = map.get(registryId);
|
||||||
|
Registry<?> registry = Registries.REGISTRIES.get(registryId);
|
||||||
|
|
||||||
|
if (registry == null) {
|
||||||
|
if (!isRegistryOptional(registryId, data)) {
|
||||||
|
// Registry was not found on the client, and is not optional.
|
||||||
|
missingRegistries.add(registryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Identifier remoteId : remoteRegistry.keySet()) {
|
||||||
|
if (!registry.containsId(remoteId)) {
|
||||||
|
// Found a registry entry from the server that is missing on the client
|
||||||
|
missingEntries.computeIfAbsent(registryId, i -> new ArrayList<>()).add(remoteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingRegistries.isEmpty() && missingEntries.isEmpty()) {
|
||||||
|
// All good :)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print out details to the log
|
||||||
|
if (!missingRegistries.isEmpty()) {
|
||||||
|
LOGGER.error("Received unknown remote registries from server");
|
||||||
|
|
||||||
|
for (Identifier registryId : missingRegistries) {
|
||||||
|
LOGGER.error("Received unknown remote registry ({}) from server", registryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!missingEntries.isEmpty()) {
|
||||||
|
LOGGER.error("Received unknown remote registry entries from server");
|
||||||
|
|
||||||
|
for (Map.Entry<Identifier, List<Identifier>> entry : missingEntries.entrySet()) {
|
||||||
|
for (Identifier identifier : entry.getValue()) {
|
||||||
|
LOGGER.error("Registry entry ({}) is missing from local registry ({})", identifier, entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!missingRegistries.isEmpty()) {
|
||||||
|
throw new RemapException(missingRegistriesError(missingRegistries));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RemapException(missingEntriesError(missingEntries));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Text missingRegistriesError(List<Identifier> missingRegistries) {
|
||||||
|
MutableText text = Text.empty();
|
||||||
|
|
||||||
|
final int count = missingRegistries.size();
|
||||||
|
|
||||||
|
if (count == 1) {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-registry.title.singular"));
|
||||||
|
} else {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-registry.title.plural", count));
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-registry.subtitle.1").formatted(Formatting.GREEN));
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-registry.subtitle.2"));
|
||||||
|
|
||||||
|
final int toDisplay = 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(missingRegistries.size(), toDisplay); i++) {
|
||||||
|
text = text.append(Text.literal(missingRegistries.get(i).toString()).formatted(Formatting.YELLOW));
|
||||||
|
text = text.append(ScreenTexts.LINE_BREAK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingRegistries.size() > toDisplay) {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-registry.footer", missingRegistries.size() - toDisplay));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Text missingEntriesError(Map<Identifier, List<Identifier>> missingEntries) {
|
||||||
|
MutableText text = Text.empty();
|
||||||
|
|
||||||
|
final int count = missingEntries.values().stream().mapToInt(List::size).sum();
|
||||||
|
|
||||||
|
if (count == 1) {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.title.singular"));
|
||||||
|
} else {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.title.plural", count));
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.subtitle.1").formatted(Formatting.GREEN));
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.subtitle.2"));
|
||||||
|
|
||||||
|
final int toDisplay = 4;
|
||||||
|
// Get the distinct missing namespaces
|
||||||
|
final List<String> namespaces = missingEntries.values().stream()
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.map(Identifier::getNamespace)
|
||||||
|
.distinct()
|
||||||
|
.sorted()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(namespaces.size(), toDisplay); i++) {
|
||||||
|
text = text.append(Text.literal(namespaces.get(i)).formatted(Formatting.YELLOW));
|
||||||
|
text = text.append(ScreenTexts.LINE_BREAK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namespaces.size() > toDisplay) {
|
||||||
|
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.footer", namespaces.size() - toDisplay));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRegistryOptional(Identifier registryId, RegistryPacketHandler.SyncedPacketData data) {
|
||||||
|
EnumSet<RegistryAttribute> registryAttributes = data.attributes().get(registryId);
|
||||||
|
return registryAttributes.contains(RegistryAttribute.OPTIONAL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ public class FabricRegistryClientInit implements ClientModInitializer {
|
||||||
|
|
||||||
private <T extends RegistryPacketHandler.RegistrySyncPayload> void registerSyncPacketReceiver(RegistryPacketHandler<T> packetHandler) {
|
private <T extends RegistryPacketHandler.RegistrySyncPayload> void registerSyncPacketReceiver(RegistryPacketHandler<T> packetHandler) {
|
||||||
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (payload, context) -> {
|
ClientConfigurationNetworking.registerGlobalReceiver(packetHandler.getPacketId(), (payload, context) -> {
|
||||||
RegistrySyncManager.receivePacket(context.client(), packetHandler, payload, RegistrySyncManager.DEBUG || !context.client().isInSingleplayer())
|
ClientRegistrySyncHandler.receivePacket(context.client(), packetHandler, payload, RegistrySyncManager.DEBUG || !context.client().isInSingleplayer())
|
||||||
.whenComplete((complete, throwable) -> {
|
.whenComplete((complete, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
LOGGER.error("Registry remapping failed!", throwable);
|
LOGGER.error("Registry remapping failed!", throwable);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.slf4j.Logger;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
@ -28,9 +29,11 @@ import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.item.ItemGroups;
|
import net.minecraft.item.ItemGroups;
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
|
||||||
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemappableRegistry;
|
||||||
import net.fabricmc.fabric.impl.registry.sync.trackers.vanilla.BlockInitTracker;
|
import net.fabricmc.fabric.impl.registry.sync.trackers.vanilla.BlockInitTracker;
|
||||||
|
|
||||||
@Mixin(MinecraftClient.class)
|
@Mixin(MinecraftClient.class)
|
||||||
|
@ -43,7 +46,7 @@ public class MinecraftClientMixin {
|
||||||
@Inject(at = @At("RETURN"), method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V")
|
@Inject(at = @At("RETURN"), method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V")
|
||||||
public void disconnectAfter(Screen disconnectionScreen, boolean bl, CallbackInfo ci) {
|
public void disconnectAfter(Screen disconnectionScreen, boolean bl, CallbackInfo ci) {
|
||||||
try {
|
try {
|
||||||
RegistrySyncManager.unmap();
|
unmap();
|
||||||
} catch (RemapException e) {
|
} catch (RemapException e) {
|
||||||
LOGGER.warn("Failed to unmap Fabric registries!", e);
|
LOGGER.warn("Failed to unmap Fabric registries!", e);
|
||||||
}
|
}
|
||||||
|
@ -57,4 +60,15 @@ public class MinecraftClientMixin {
|
||||||
BlockInitTracker.postFreeze();
|
BlockInitTracker.postFreeze();
|
||||||
ItemGroups.collect();
|
ItemGroups.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static void unmap() throws RemapException {
|
||||||
|
for (Identifier registryId : Registries.REGISTRIES.getIds()) {
|
||||||
|
Registry<?> registry = Registries.REGISTRIES.get(registryId);
|
||||||
|
|
||||||
|
if (registry instanceof RemappableRegistry) {
|
||||||
|
((RemappableRegistry) registry).unmap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,10 @@ public enum RegistryAttribute {
|
||||||
/**
|
/**
|
||||||
* Registry has been modded.
|
* Registry has been modded.
|
||||||
*/
|
*/
|
||||||
MODDED
|
MODDED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry is optional, any connecting client will not be disconnected if the registry is not present.
|
||||||
|
*/
|
||||||
|
OPTIONAL
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,13 @@ import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
public final class RegistryAttributeImpl implements RegistryAttributeHolder {
|
public final class RegistryAttributeImpl implements RegistryAttributeHolder {
|
||||||
private static final Map<RegistryKey<?>, RegistryAttributeHolder> HOLDER_MAP = new ConcurrentHashMap<>();
|
private static final Map<RegistryKey<?>, RegistryAttributeHolder> HOLDER_MAP = new ConcurrentHashMap<>();
|
||||||
|
@ -43,6 +46,15 @@ public final class RegistryAttributeImpl implements RegistryAttributeHolder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void removeAttribute(RegistryAttribute attribute) {
|
||||||
|
if (!FabricLoader.getInstance().isDevelopmentEnvironment()) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.remove(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAttribute(RegistryAttribute attribute) {
|
public boolean hasAttribute(RegistryAttribute attribute) {
|
||||||
return attributes.contains(attribute);
|
return attributes.contains(attribute);
|
||||||
|
|
|
@ -20,31 +20,24 @@ import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CompletionException;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.VisibleForTesting;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.minecraft.network.packet.Packet;
|
import net.minecraft.network.packet.Packet;
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.registry.Registries;
|
||||||
import net.minecraft.registry.Registry;
|
import net.minecraft.registry.Registry;
|
||||||
import net.minecraft.registry.RegistryKey;
|
|
||||||
import net.minecraft.screen.ScreenTexts;
|
import net.minecraft.screen.ScreenTexts;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
|
||||||
|
@ -53,13 +46,12 @@ import net.minecraft.text.MutableText;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.thread.ThreadExecutor;
|
|
||||||
|
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||||
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
|
||||||
|
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
|
||||||
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
||||||
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
|
|
||||||
|
|
||||||
public final class RegistrySyncManager {
|
public final class RegistrySyncManager {
|
||||||
public static final boolean DEBUG = Boolean.getBoolean("fabric.registry.debug");
|
public static final boolean DEBUG = Boolean.getBoolean("fabric.registry.debug");
|
||||||
|
@ -80,11 +72,6 @@ public final class RegistrySyncManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ServerConfigurationNetworking.canSend(handler, DIRECT_PACKET_HANDLER.getPacketId())) {
|
|
||||||
// Don't send if the client cannot receive
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap();
|
final Map<Identifier, Object2IntMap<Identifier>> map = RegistrySyncManager.createAndPopulateRegistryMap();
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
|
@ -92,9 +79,62 @@ public final class RegistrySyncManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ServerConfigurationNetworking.canSend(handler, DIRECT_PACKET_HANDLER.getPacketId())) {
|
||||||
|
if (areAllRegistriesOptional(map)) {
|
||||||
|
// Allow the client to connect if all of the registries we want to sync are optional
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect incompatible clients
|
||||||
|
Text message = getIncompatibleClientText(ServerNetworkingImpl.getAddon(handler).getClientBrand(), map);
|
||||||
|
handler.disconnect(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
handler.addTask(new SyncConfigurationTask(handler, map));
|
handler.addTask(new SyncConfigurationTask(handler, map));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Text getIncompatibleClientText(@Nullable String brand, Map<Identifier, Object2IntMap<Identifier>> map) {
|
||||||
|
String brandText = switch (brand) {
|
||||||
|
case "fabric" -> "Fabric API";
|
||||||
|
case null, default -> "Fabric Loader and Fabric API";
|
||||||
|
};
|
||||||
|
|
||||||
|
final int toDisplay = 4;
|
||||||
|
|
||||||
|
List<String> namespaces = map.values().stream()
|
||||||
|
.map(Object2IntMap::keySet)
|
||||||
|
.flatMap(Set::stream)
|
||||||
|
.map(Identifier::getNamespace)
|
||||||
|
.filter(s -> !s.equals(Identifier.DEFAULT_NAMESPACE))
|
||||||
|
.distinct()
|
||||||
|
.sorted()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
MutableText text = Text.literal("The following registry entry namespaces may be related:\n\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(namespaces.size(), toDisplay); i++) {
|
||||||
|
text = text.append(Text.literal(namespaces.get(i)).formatted(Formatting.YELLOW));
|
||||||
|
text = text.append(ScreenTexts.LINE_BREAK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namespaces.size() > toDisplay) {
|
||||||
|
text = text.append(Text.literal("And %d more...".formatted(namespaces.size() - toDisplay)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text.literal("This server requires ").append(Text.literal(brandText).formatted(Formatting.GREEN)).append(" installed on your client!")
|
||||||
|
.append(ScreenTexts.LINE_BREAK).append(text)
|
||||||
|
.append(ScreenTexts.LINE_BREAK).append(ScreenTexts.LINE_BREAK).append(Text.literal("Contact the server's administrator for more information!").formatted(Formatting.GOLD));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean areAllRegistriesOptional(Map<Identifier, Object2IntMap<Identifier>> map) {
|
||||||
|
return map.keySet().stream()
|
||||||
|
.map(Registries.REGISTRIES::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(RegistryAttributeHolder::get)
|
||||||
|
.allMatch(attributes -> attributes.hasAttribute(RegistryAttribute.OPTIONAL));
|
||||||
|
}
|
||||||
|
|
||||||
public record SyncConfigurationTask(
|
public record SyncConfigurationTask(
|
||||||
ServerConfigurationNetworkHandler handler,
|
ServerConfigurationNetworkHandler handler,
|
||||||
Map<Identifier, Object2IntMap<Identifier>> map
|
Map<Identifier, Object2IntMap<Identifier>> map
|
||||||
|
@ -112,40 +152,6 @@ public final class RegistrySyncManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends RegistryPacketHandler.RegistrySyncPayload> CompletableFuture<Boolean> receivePacket(ThreadExecutor<?> executor, RegistryPacketHandler<T> handler, T payload, boolean accept) {
|
|
||||||
handler.receivePayload(payload);
|
|
||||||
|
|
||||||
if (!handler.isPacketFinished()) {
|
|
||||||
return CompletableFuture.completedFuture(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
String handlerName = handler.getClass().getSimpleName();
|
|
||||||
LOGGER.info("{} total packet: {}", handlerName, handler.getTotalPacketReceived());
|
|
||||||
LOGGER.info("{} raw size: {}", handlerName, handler.getRawBufSize());
|
|
||||||
LOGGER.info("{} deflated size: {}", handlerName, handler.getDeflatedBufSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<Identifier, Object2IntMap<Identifier>> map = handler.getSyncedRegistryMap();
|
|
||||||
|
|
||||||
if (!accept) {
|
|
||||||
return CompletableFuture.completedFuture(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return executor.submit(() -> {
|
|
||||||
if (map == null) {
|
|
||||||
throw new CompletionException(new RemapException("Received null map in sync packet!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
apply(map, RemappableRegistry.RemapMode.REMOTE);
|
|
||||||
return true;
|
|
||||||
} catch (RemapException e) {
|
|
||||||
throw new CompletionException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Map} used to sync the registry ids.
|
* Creates a {@link Map} used to sync the registry ids.
|
||||||
*
|
*
|
||||||
|
@ -254,121 +260,6 @@ public final class RegistrySyncManager {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void apply(Map<Identifier, Object2IntMap<Identifier>> map, RemappableRegistry.RemapMode mode) throws RemapException {
|
|
||||||
if (mode == RemappableRegistry.RemapMode.REMOTE) {
|
|
||||||
checkRemoteRemap(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Identifier> containedRegistries = Sets.newHashSet(map.keySet());
|
|
||||||
|
|
||||||
for (Identifier registryId : Registries.REGISTRIES.getIds()) {
|
|
||||||
if (!containedRegistries.remove(registryId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object2IntMap<Identifier> registryMap = map.get(registryId);
|
|
||||||
Registry<?> registry = Registries.REGISTRIES.get(registryId);
|
|
||||||
|
|
||||||
RegistryAttributeHolder attributeHolder = RegistryAttributeHolder.get(registry.getKey());
|
|
||||||
|
|
||||||
if (!attributeHolder.hasAttribute(RegistryAttribute.MODDED)) {
|
|
||||||
LOGGER.debug("Not applying registry data to vanilla registry {}", registryId.toString());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registry instanceof RemappableRegistry remappableRegistry) {
|
|
||||||
remappableRegistry.remap(registryId.toString(), registryMap, mode);
|
|
||||||
} else {
|
|
||||||
throw new RemapException("Registry " + registryId + " is not remappable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containedRegistries.isEmpty()) {
|
|
||||||
LOGGER.warn("[fabric-registry-sync] Could not find the following registries: " + Joiner.on(", ").join(containedRegistries));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public static void checkRemoteRemap(Map<Identifier, Object2IntMap<Identifier>> map) throws RemapException {
|
|
||||||
Map<Identifier, List<Identifier>> missingEntries = new HashMap<>();
|
|
||||||
|
|
||||||
for (Map.Entry<? extends RegistryKey<? extends Registry<?>>, ? extends Registry<?>> entry : Registries.REGISTRIES.getEntrySet()) {
|
|
||||||
final Registry<?> registry = entry.getValue();
|
|
||||||
final Identifier registryId = entry.getKey().getValue();
|
|
||||||
final Object2IntMap<Identifier> remoteRegistry = map.get(registryId);
|
|
||||||
|
|
||||||
if (remoteRegistry == null) {
|
|
||||||
// Registry sync does not contain data for this registry, will print a warning when applying.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Identifier remoteId : remoteRegistry.keySet()) {
|
|
||||||
if (!registry.containsId(remoteId)) {
|
|
||||||
// Found a registry entry from the server that is
|
|
||||||
missingEntries.computeIfAbsent(registryId, i -> new ArrayList<>()).add(remoteId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missingEntries.isEmpty()) {
|
|
||||||
// All good :)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print out details to the log
|
|
||||||
LOGGER.error("Received unknown remote registry entries from server");
|
|
||||||
|
|
||||||
for (Map.Entry<Identifier, List<Identifier>> entry : missingEntries.entrySet()) {
|
|
||||||
for (Identifier identifier : entry.getValue()) {
|
|
||||||
LOGGER.error("Registry entry ({}) is missing from local registry ({})", identifier, entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a nice user friendly error message.
|
|
||||||
MutableText text = Text.empty();
|
|
||||||
|
|
||||||
final int count = missingEntries.values().stream().mapToInt(List::size).sum();
|
|
||||||
|
|
||||||
if (count == 1) {
|
|
||||||
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.title.singular"));
|
|
||||||
} else {
|
|
||||||
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.title.plural", count));
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.subtitle.1").formatted(Formatting.GREEN));
|
|
||||||
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.subtitle.2"));
|
|
||||||
|
|
||||||
final int toDisplay = 4;
|
|
||||||
// Get the distinct missing namespaces
|
|
||||||
final List<String> namespaces = missingEntries.values().stream()
|
|
||||||
.flatMap(List::stream)
|
|
||||||
.map(Identifier::getNamespace)
|
|
||||||
.distinct()
|
|
||||||
.sorted()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(namespaces.size(), toDisplay); i++) {
|
|
||||||
text = text.append(Text.literal(namespaces.get(i)).formatted(Formatting.YELLOW));
|
|
||||||
text = text.append(ScreenTexts.LINE_BREAK);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namespaces.size() > toDisplay) {
|
|
||||||
text = text.append(Text.translatable("fabric-registry-sync-v0.unknown-remote.footer", namespaces.size() - toDisplay));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RemapException(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unmap() throws RemapException {
|
|
||||||
for (Identifier registryId : Registries.REGISTRIES.getIds()) {
|
|
||||||
Registry registry = Registries.REGISTRIES.get(registryId);
|
|
||||||
|
|
||||||
if (registry instanceof RemappableRegistry) {
|
|
||||||
((RemappableRegistry) registry).unmap(registryId.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void bootstrapRegistries() {
|
public static void bootstrapRegistries() {
|
||||||
postBootstrap = true;
|
postBootstrap = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,9 @@ public interface RemappableRegistry {
|
||||||
* client).
|
* client).
|
||||||
*/
|
*/
|
||||||
REMOTE,
|
REMOTE,
|
||||||
/**
|
|
||||||
* No differences in entry sets are allowed.
|
|
||||||
*/
|
|
||||||
EXACT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void remap(String name, Object2IntMap<Identifier> remoteIndexedEntries, RemapMode mode) throws RemapException;
|
void remap(Object2IntMap<Identifier> remoteIndexedEntries, RemapMode mode) throws RemapException;
|
||||||
|
|
||||||
void unmap(String name) throws RemapException;
|
void unmap() throws RemapException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package net.fabricmc.fabric.impl.registry.sync.packet;
|
package net.fabricmc.fabric.impl.registry.sync.packet;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -33,8 +35,12 @@ import org.jetbrains.annotations.Nullable;
|
||||||
import net.minecraft.network.PacketByteBuf;
|
import net.minecraft.network.PacketByteBuf;
|
||||||
import net.minecraft.network.codec.PacketCodec;
|
import net.minecraft.network.codec.PacketCodec;
|
||||||
import net.minecraft.network.packet.CustomPayload;
|
import net.minecraft.network.packet.CustomPayload;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +71,9 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
@Nullable
|
@Nullable
|
||||||
private Map<Identifier, Object2IntMap<Identifier>> syncedRegistryMap;
|
private Map<Identifier, Object2IntMap<Identifier>> syncedRegistryMap;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Map<Identifier, EnumSet<RegistryAttribute>> syncedRegistryAttributes;
|
||||||
|
|
||||||
private boolean isPacketFinished = false;
|
private boolean isPacketFinished = false;
|
||||||
private int totalPacketReceived = 0;
|
private int totalPacketReceived = 0;
|
||||||
|
|
||||||
|
@ -89,6 +98,7 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
|
|
||||||
for (Identifier regId : regIds) {
|
for (Identifier regId : regIds) {
|
||||||
buf.writeString(regId.getPath());
|
buf.writeString(regId.getPath());
|
||||||
|
buf.writeByte(encodeRegistryAttributes(regId));
|
||||||
|
|
||||||
Object2IntMap<Identifier> idMap = registryMap.get(regId);
|
Object2IntMap<Identifier> idMap = registryMap.get(regId);
|
||||||
|
|
||||||
|
@ -181,6 +191,7 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
|
|
||||||
computeBufSize(combinedBuf);
|
computeBufSize(combinedBuf);
|
||||||
syncedRegistryMap = new LinkedHashMap<>();
|
syncedRegistryMap = new LinkedHashMap<>();
|
||||||
|
syncedRegistryAttributes = new LinkedHashMap<>();
|
||||||
int regNamespaceGroupAmount = combinedBuf.readVarInt();
|
int regNamespaceGroupAmount = combinedBuf.readVarInt();
|
||||||
|
|
||||||
for (int i = 0; i < regNamespaceGroupAmount; i++) {
|
for (int i = 0; i < regNamespaceGroupAmount; i++) {
|
||||||
|
@ -189,6 +200,7 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
|
|
||||||
for (int j = 0; j < regNamespaceGroupLength; j++) {
|
for (int j = 0; j < regNamespaceGroupLength; j++) {
|
||||||
String regPath = combinedBuf.readString();
|
String regPath = combinedBuf.readString();
|
||||||
|
EnumSet<RegistryAttribute> attributes = decodeRegistryAttributes(combinedBuf.readByte());
|
||||||
Object2IntMap<Identifier> idMap = new Object2IntLinkedOpenHashMap<>();
|
Object2IntMap<Identifier> idMap = new Object2IntLinkedOpenHashMap<>();
|
||||||
int idNamespaceGroupAmount = combinedBuf.readVarInt();
|
int idNamespaceGroupAmount = combinedBuf.readVarInt();
|
||||||
|
|
||||||
|
@ -214,7 +226,9 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncedRegistryMap.put(Identifier.of(regNamespace, regPath), idMap);
|
Identifier registryId = Identifier.of(regNamespace, regPath);
|
||||||
|
syncedRegistryMap.put(registryId, idMap);
|
||||||
|
syncedRegistryAttributes.put(registryId, attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,13 +249,22 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Map<Identifier, Object2IntMap<Identifier>> getSyncedRegistryMap() {
|
public SyncedPacketData getSyncedPacketData() {
|
||||||
Preconditions.checkState(isPacketFinished);
|
Preconditions.checkState(isPacketFinished);
|
||||||
Map<Identifier, Object2IntMap<Identifier>> map = syncedRegistryMap;
|
|
||||||
|
if (syncedRegistryMap == null || syncedRegistryAttributes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Identifier, Object2IntMap<Identifier>> map = Collections.unmodifiableMap(syncedRegistryMap);
|
||||||
|
Map<Identifier, EnumSet<RegistryAttribute>> attributes = Collections.unmodifiableMap(syncedRegistryAttributes);
|
||||||
|
|
||||||
isPacketFinished = false;
|
isPacketFinished = false;
|
||||||
totalPacketReceived = 0;
|
totalPacketReceived = 0;
|
||||||
syncedRegistryMap = null;
|
syncedRegistryMap = null;
|
||||||
return map;
|
syncedRegistryAttributes = null;
|
||||||
|
|
||||||
|
return new SyncedPacketData(map, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirectRegistryPacketHandler.Payload createPayload(PacketByteBuf buf) {
|
private DirectRegistryPacketHandler.Payload createPayload(PacketByteBuf buf) {
|
||||||
|
@ -283,4 +306,32 @@ public class DirectRegistryPacketHandler extends RegistryPacketHandler<DirectReg
|
||||||
return ID;
|
return ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte encodeRegistryAttributes(Identifier identifier) {
|
||||||
|
Registry<?> registry = Registries.REGISTRIES.get(identifier);
|
||||||
|
|
||||||
|
if (registry == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryAttributeHolder holder = RegistryAttributeHolder.get(registry);
|
||||||
|
byte encoded = 0;
|
||||||
|
|
||||||
|
// Only send the optional marker.
|
||||||
|
if (holder.hasAttribute(RegistryAttribute.OPTIONAL)) {
|
||||||
|
encoded |= 0x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EnumSet<RegistryAttribute> decodeRegistryAttributes(byte encoded) {
|
||||||
|
EnumSet<RegistryAttribute> attributes = EnumSet.noneOf(RegistryAttribute.class);
|
||||||
|
|
||||||
|
if ((encoded & 0x1) != 0) {
|
||||||
|
attributes.add(RegistryAttribute.OPTIONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package net.fabricmc.fabric.impl.registry.sync.packet;
|
package net.fabricmc.fabric.impl.registry.sync.packet;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
|
@ -28,6 +29,7 @@ import net.minecraft.network.PacketByteBuf;
|
||||||
import net.minecraft.network.packet.CustomPayload;
|
import net.minecraft.network.packet.CustomPayload;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||||
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
||||||
|
|
||||||
|
@ -46,7 +48,7 @@ public abstract class RegistryPacketHandler<T extends RegistryPacketHandler.Regi
|
||||||
public abstract boolean isPacketFinished();
|
public abstract boolean isPacketFinished();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public abstract Map<Identifier, Object2IntMap<Identifier>> getSyncedRegistryMap();
|
public abstract SyncedPacketData getSyncedPacketData();
|
||||||
|
|
||||||
protected final void computeBufSize(PacketByteBuf buf) {
|
protected final void computeBufSize(PacketByteBuf buf) {
|
||||||
if (!RegistrySyncManager.DEBUG) {
|
if (!RegistrySyncManager.DEBUG) {
|
||||||
|
@ -92,4 +94,9 @@ public abstract class RegistryPacketHandler<T extends RegistryPacketHandler.Regi
|
||||||
|
|
||||||
public interface RegistrySyncPayload extends CustomPayload {
|
public interface RegistrySyncPayload extends CustomPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record SyncedPacketData(
|
||||||
|
Map<Identifier, Object2IntMap<Identifier>> idMap,
|
||||||
|
Map<Identifier, EnumSet<RegistryAttribute>> attributes
|
||||||
|
) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,6 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
@Unique
|
@Unique
|
||||||
private static final Set<String> VANILLA_NAMESPACES = Set.of("minecraft", "brigadier");
|
private static final Set<String> VANILLA_NAMESPACES = Set.of("minecraft", "brigadier");
|
||||||
|
|
||||||
@Unique
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("FabricRegistrySync");
|
|
||||||
|
|
||||||
@Shadow
|
@Shadow
|
||||||
@Final
|
@Final
|
||||||
private ObjectList<RegistryEntry.Reference<T>> rawIdToEntry;
|
private ObjectList<RegistryEntry.Reference<T>> rawIdToEntry;
|
||||||
|
@ -123,7 +120,7 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "<init>(Lnet/minecraft/registry/RegistryKey;Lcom/mojang/serialization/Lifecycle;Z)V", at = @At("RETURN"))
|
@Inject(method = "<init>(Lnet/minecraft/registry/RegistryKey;Lcom/mojang/serialization/Lifecycle;Z)V", at = @At("RETURN"))
|
||||||
private void init(RegistryKey key, Lifecycle lifecycle, boolean intrusive, CallbackInfo ci) {
|
private void init(RegistryKey<?> key, Lifecycle lifecycle, boolean intrusive, CallbackInfo ci) {
|
||||||
fabric_addObjectEvent = EventFactory.createArrayBacked(RegistryEntryAddedCallback.class,
|
fabric_addObjectEvent = EventFactory.createArrayBacked(RegistryEntryAddedCallback.class,
|
||||||
(callbacks) -> (rawId, id, object) -> {
|
(callbacks) -> (rawId, id, object) -> {
|
||||||
for (RegistryEntryAddedCallback<T> callback : callbacks) {
|
for (RegistryEntryAddedCallback<T> callback : callbacks) {
|
||||||
|
@ -165,7 +162,7 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remap(String name, Object2IntMap<Identifier> remoteIndexedEntries, RemapMode mode) throws RemapException {
|
public void remap(Object2IntMap<Identifier> remoteIndexedEntries, RemapMode mode) throws RemapException {
|
||||||
// Throw on invalid conditions.
|
// Throw on invalid conditions.
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case AUTHORITATIVE:
|
case AUTHORITATIVE:
|
||||||
|
@ -184,34 +181,7 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strings != null) {
|
if (strings != null) {
|
||||||
StringBuilder builder = new StringBuilder("Received ID map for " + name + " contains IDs unknown to the receiver!");
|
StringBuilder builder = new StringBuilder("Received ID map for " + getKey() + " contains IDs unknown to the receiver!");
|
||||||
|
|
||||||
for (String s : strings) {
|
|
||||||
builder.append('\n').append(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RemapException(builder.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EXACT: {
|
|
||||||
if (!idToEntry.keySet().equals(remoteIndexedEntries.keySet())) {
|
|
||||||
List<String> strings = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Identifier remoteId : remoteIndexedEntries.keySet()) {
|
|
||||||
if (!idToEntry.containsKey(remoteId)) {
|
|
||||||
strings.add(" - " + remoteId + " (missing on local)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Identifier localId : getIds()) {
|
|
||||||
if (!remoteIndexedEntries.containsKey(localId)) {
|
|
||||||
strings.add(" - " + localId + " (missing on remote)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder("Local and remote ID sets for " + name + " do not match!");
|
|
||||||
|
|
||||||
for (String s : strings) {
|
for (String s : strings) {
|
||||||
builder.append('\n').append(s);
|
builder.append('\n').append(s);
|
||||||
|
@ -350,7 +320,7 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unmap(String name) throws RemapException {
|
public void unmap() throws RemapException {
|
||||||
if (fabric_prevIndexedEntries != null) {
|
if (fabric_prevIndexedEntries != null) {
|
||||||
List<Identifier> addedIds = new ArrayList<>();
|
List<Identifier> addedIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -372,7 +342,7 @@ public abstract class SimpleRegistryMixin<T> implements MutableRegistry<T>, Rema
|
||||||
keyToEntry.put(entryKey, entry.getValue());
|
keyToEntry.put(entryKey, entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
remap(name, fabric_prevIndexedEntries, RemapMode.AUTHORITATIVE);
|
remap(fabric_prevIndexedEntries, RemapMode.AUTHORITATIVE);
|
||||||
|
|
||||||
for (Identifier id : addedIds) {
|
for (Identifier id : addedIds) {
|
||||||
fabric_getAddObjectEvent().invoker().onEntryAdded(entryToRawId.getInt(idToEntry.get(id)), id, get(id));
|
fabric_getAddObjectEvent().invoker().onEntryAdded(entryToRawId.getInt(idToEntry.get(id)), id, get(id));
|
||||||
|
|
|
@ -3,5 +3,10 @@
|
||||||
"fabric-registry-sync-v0.unknown-remote.title.plural" : "Received %d registry entries that are unknown to this client.\n",
|
"fabric-registry-sync-v0.unknown-remote.title.plural" : "Received %d registry entries that are unknown to this client.\n",
|
||||||
"fabric-registry-sync-v0.unknown-remote.subtitle.1" : "This is usually caused by a mismatched mod set between the client and server.",
|
"fabric-registry-sync-v0.unknown-remote.subtitle.1" : "This is usually caused by a mismatched mod set between the client and server.",
|
||||||
"fabric-registry-sync-v0.unknown-remote.subtitle.2" : " See the client logs for more details.\nThe following registry entry namespaces may be related:\n\n",
|
"fabric-registry-sync-v0.unknown-remote.subtitle.2" : " See the client logs for more details.\nThe following registry entry namespaces may be related:\n\n",
|
||||||
"fabric-registry-sync-v0.unknown-remote.footer" : "And %d more..."
|
"fabric-registry-sync-v0.unknown-remote.footer" : "And %d more...",
|
||||||
|
"fabric-registry-sync-v0.unknown-registry.title.singular" : "Received a registry that is unknown to this client.\n",
|
||||||
|
"fabric-registry-sync-v0.unknown-registry.title.plural" : "Received %d registries that are unknown to this client.\n",
|
||||||
|
"fabric-registry-sync-v0.unknown-registry.subtitle.1" : "This is usually caused by a mismatched mod set between the client and server.",
|
||||||
|
"fabric-registry-sync-v0.unknown-registry.subtitle.2" : " See the client logs for more details.\nThe following registries are not present on the client:\n\n",
|
||||||
|
"fabric-registry-sync-v0.unknown-registry.footer" : "And %d more..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,22 @@ import java.util.Map;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import net.minecraft.Bootstrap;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
||||||
|
|
||||||
public class DirectRegistryPacketHandlerTest {
|
public class DirectRegistryPacketHandlerTest {
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
SharedConstants.createGameVersion();
|
||||||
|
Bootstrap.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void emptyRegistrySync() {
|
void emptyRegistrySync() {
|
||||||
DirectRegistryPacketHandler handler = new DirectRegistryPacketHandler();
|
DirectRegistryPacketHandler handler = new DirectRegistryPacketHandler();
|
||||||
|
@ -48,7 +57,7 @@ public class DirectRegistryPacketHandlerTest {
|
||||||
handler.receivePayload(payload);
|
handler.receivePayload(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMatchesDeep(registry, handler.getSyncedRegistryMap());
|
assertMatchesDeep(registry, handler.getSyncedPacketData().idMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -69,7 +78,7 @@ public class DirectRegistryPacketHandlerTest {
|
||||||
handler.receivePayload(payload);
|
handler.receivePayload(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMatchesDeep(registry, handler.getSyncedRegistryMap());
|
assertMatchesDeep(registry, handler.getSyncedPacketData().idMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -93,7 +102,7 @@ public class DirectRegistryPacketHandlerTest {
|
||||||
handler.receivePayload(payload);
|
handler.receivePayload(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMatchesDeep(registry, handler.getSyncedRegistryMap());
|
assertMatchesDeep(registry, handler.getSyncedPacketData().idMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object2IntMap<Identifier> createRegistry(int size) {
|
private static Object2IntMap<Identifier> createRegistry(int size) {
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
/*
|
||||||
|
* 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.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import net.minecraft.Bootstrap;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.registry.RegistryKey;
|
||||||
|
import net.minecraft.registry.SimpleRegistry;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.thread.ThreadExecutor;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||||
|
import net.fabricmc.fabric.impl.client.registry.sync.ClientRegistrySyncHandler;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RegistryAttributeImpl;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemappableRegistry;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistryPacketHandler;
|
||||||
|
|
||||||
|
public class RegistryRemapTest {
|
||||||
|
private RegistryKey<Registry<String>> testRegistryKey;
|
||||||
|
private SimpleRegistry<String> testRegistry;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
SharedConstants.createGameVersion();
|
||||||
|
Bootstrap.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
testRegistryKey = RegistryKey.ofRegistry(id(UUID.randomUUID().toString()));
|
||||||
|
testRegistry = FabricRegistryBuilder.createSimple(testRegistryKey)
|
||||||
|
.attribute(RegistryAttribute.SYNCED)
|
||||||
|
.buildAndRegister();
|
||||||
|
|
||||||
|
Registry.register(testRegistry, id("zero"), "zero");
|
||||||
|
Registry.register(testRegistry, id("one"), "one");
|
||||||
|
Registry.register(testRegistry, id("two"), "two");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void afterEach() throws RemapException {
|
||||||
|
// If a test fails, make sure we unmap the registry to avoid affecting other tests
|
||||||
|
RemappableRegistry remappableRegistry = (RemappableRegistry) testRegistry;
|
||||||
|
remappableRegistry.unmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void remapRegistry() throws RemapException {
|
||||||
|
RemappableRegistry remappableRegistry = (RemappableRegistry) testRegistry;
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("zero"), 2,
|
||||||
|
id("one"), 1,
|
||||||
|
id("two"), 0
|
||||||
|
);
|
||||||
|
remappableRegistry.remap(asFastMap(idMap), RemappableRegistry.RemapMode.AUTHORITATIVE);
|
||||||
|
|
||||||
|
assertEquals(2, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(0, testRegistry.getRawId("two"));
|
||||||
|
|
||||||
|
remappableRegistry.unmap();
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void remapRegistryViaPacket() throws RemapException {
|
||||||
|
RemappableRegistry remappableRegistry = (RemappableRegistry) testRegistry;
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("two"), 0,
|
||||||
|
id("one"), 1,
|
||||||
|
id("zero"), 2
|
||||||
|
);
|
||||||
|
|
||||||
|
var payloads = new ArrayList<DirectRegistryPacketHandler.Payload>();
|
||||||
|
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER.sendPacket(
|
||||||
|
payloads::add,
|
||||||
|
Map.of(testRegistryKey.getValue(), asFastMap(idMap))
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Boolean> results = receivePayloads(payloads);
|
||||||
|
|
||||||
|
// Expect 2 packets, 1 with the data (as it fits in one packet) and 1 empty packet to signal the end
|
||||||
|
assertEquals(2, results.size());
|
||||||
|
assertFalse(results.getFirst());
|
||||||
|
assertTrue(results.get(1));
|
||||||
|
|
||||||
|
assertEquals(2, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(0, testRegistry.getRawId("two"));
|
||||||
|
|
||||||
|
remappableRegistry.unmap();
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unknownEntry() {
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("two"), 0,
|
||||||
|
id("one"), 1,
|
||||||
|
id("zero"), 2,
|
||||||
|
id("unknown"), 3
|
||||||
|
);
|
||||||
|
|
||||||
|
var payloads = new ArrayList<DirectRegistryPacketHandler.Payload>();
|
||||||
|
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER.sendPacket(
|
||||||
|
payloads::add,
|
||||||
|
Map.of(testRegistryKey.getValue(), asFastMap(idMap))
|
||||||
|
);
|
||||||
|
|
||||||
|
RemapException remapException = assertThrows(RemapException.class, () -> receivePayloads(payloads));
|
||||||
|
assertTrue(remapException.getMessage().contains("unknown-remote"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unknownRegistry() {
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("two"), 0,
|
||||||
|
id("one"), 1,
|
||||||
|
id("zero"), 2
|
||||||
|
);
|
||||||
|
|
||||||
|
var payloads = new ArrayList<DirectRegistryPacketHandler.Payload>();
|
||||||
|
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER.sendPacket(
|
||||||
|
payloads::add,
|
||||||
|
Map.of(id("unknown"), asFastMap(idMap))
|
||||||
|
);
|
||||||
|
|
||||||
|
RemapException remapException = assertThrows(RemapException.class, () -> receivePayloads(payloads));
|
||||||
|
assertTrue(remapException.getMessage().contains("unknown-registry"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unknownOptionalRegistry() throws RemapException {
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("two"), 0,
|
||||||
|
id("one"), 1,
|
||||||
|
id("zero"), 2
|
||||||
|
);
|
||||||
|
|
||||||
|
RegistryAttributeImpl holder = (RegistryAttributeImpl) RegistryAttributeHolder.get(testRegistryKey);
|
||||||
|
holder.addAttribute(RegistryAttribute.OPTIONAL);
|
||||||
|
|
||||||
|
var payloads = new ArrayList<DirectRegistryPacketHandler.Payload>();
|
||||||
|
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER.sendPacket(
|
||||||
|
payloads::add,
|
||||||
|
Map.of(testRegistryKey.getValue(), asFastMap(idMap))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Packet should be handled without issue.
|
||||||
|
List<Boolean> results = receivePayloads(payloads);
|
||||||
|
assertEquals(2, results.size());
|
||||||
|
assertFalse(results.getFirst());
|
||||||
|
assertTrue(results.get(1));
|
||||||
|
|
||||||
|
holder.removeAttribute(RegistryAttribute.OPTIONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingRemoteEntries() throws RemapException {
|
||||||
|
RemappableRegistry remappableRegistry = (RemappableRegistry) testRegistry;
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
|
||||||
|
Map<Identifier, Integer> idMap = Map.of(
|
||||||
|
id("two"), 0,
|
||||||
|
id("zero"), 1
|
||||||
|
);
|
||||||
|
|
||||||
|
var payloads = new ArrayList<DirectRegistryPacketHandler.Payload>();
|
||||||
|
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER.sendPacket(
|
||||||
|
payloads::add,
|
||||||
|
Map.of(testRegistryKey.getValue(), asFastMap(idMap))
|
||||||
|
);
|
||||||
|
|
||||||
|
receivePayloads(payloads);
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("two"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("zero"));
|
||||||
|
// assigned an ID at the end of the registry
|
||||||
|
assertEquals(2, testRegistry.getRawId("one"));
|
||||||
|
|
||||||
|
remappableRegistry.unmap();
|
||||||
|
|
||||||
|
assertEquals(0, testRegistry.getRawId("zero"));
|
||||||
|
assertEquals(1, testRegistry.getRawId("one"));
|
||||||
|
assertEquals(2, testRegistry.getRawId("two"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Boolean> receivePayloads(List<DirectRegistryPacketHandler.Payload> payloads) throws RemapException {
|
||||||
|
var results = new ArrayList<Boolean>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (DirectRegistryPacketHandler.Payload payload : payloads) {
|
||||||
|
CompletableFuture<Boolean> future = ClientRegistrySyncHandler.receivePacket(
|
||||||
|
ThisThreadExecutor.INSTANCE,
|
||||||
|
RegistrySyncManager.DIRECT_PACKET_HANDLER,
|
||||||
|
payload,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
results.add(future.get());
|
||||||
|
}
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
if (e.getCause() instanceof RemapException remapException) {
|
||||||
|
throw remapException;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object2IntMap<Identifier> asFastMap(Map<Identifier, Integer> map) {
|
||||||
|
var fastMap = new Object2IntOpenHashMap<Identifier>();
|
||||||
|
fastMap.putAll(map);
|
||||||
|
return fastMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Identifier id(String path) {
|
||||||
|
return Identifier.of("registry_sync_test", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the task on the current thread instantly
|
||||||
|
private static class ThisThreadExecutor extends ThreadExecutor<Runnable> {
|
||||||
|
public static final ThisThreadExecutor INSTANCE = new ThisThreadExecutor();
|
||||||
|
|
||||||
|
private ThisThreadExecutor() {
|
||||||
|
super("Test thread executor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canExecute(Runnable task) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Thread getThread() {
|
||||||
|
return Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Runnable createTask(Runnable runnable) {
|
||||||
|
return runnable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,13 +18,9 @@ package net.fabricmc.fabric.test.registry.sync;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
@ -37,19 +33,14 @@ import net.minecraft.registry.Registry;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.registry.RegistryKeys;
|
import net.minecraft.registry.RegistryKeys;
|
||||||
import net.minecraft.registry.SimpleRegistry;
|
import net.minecraft.registry.SimpleRegistry;
|
||||||
import net.minecraft.server.command.CommandManager;
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
|
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
|
||||||
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
|
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
|
||||||
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
|
|
||||||
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
|
||||||
|
|
||||||
public class RegistrySyncTest implements ModInitializer {
|
public class RegistrySyncTest implements ModInitializer {
|
||||||
private static final Logger LOGGER = LogUtils.getLogger();
|
private static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
@ -112,28 +103,6 @@ public class RegistrySyncTest implements ModInitializer {
|
||||||
|
|
||||||
// Vanilla status effects don't have an entry for the int id 0, test we can handle this.
|
// Vanilla status effects don't have an entry for the int id 0, test we can handle this.
|
||||||
RegistryAttributeHolder.get(Registries.STATUS_EFFECT).addAttribute(RegistryAttribute.MODDED);
|
RegistryAttributeHolder.get(Registries.STATUS_EFFECT).addAttribute(RegistryAttribute.MODDED);
|
||||||
|
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) ->
|
|
||||||
dispatcher.register(CommandManager.literal("remote_remap_error_test").executes(context -> {
|
|
||||||
Map<Identifier, Object2IntMap<Identifier>> registryData = Map.of(
|
|
||||||
RegistryKeys.BLOCK.getValue(), createFakeRegistryEntries(),
|
|
||||||
RegistryKeys.ITEM.getValue(), createFakeRegistryEntries()
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
RegistrySyncManager.checkRemoteRemap(registryData);
|
|
||||||
} catch (RemapException e) {
|
|
||||||
final ServerPlayerEntity player = context.getSource().getPlayer();
|
|
||||||
|
|
||||||
if (player != null) {
|
|
||||||
player.networkHandler.disconnect(Objects.requireNonNull(e.getText()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException();
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkSyncedRegistry(RegistryKey<? extends Registry<?>> registry) {
|
public static void checkSyncedRegistry(RegistryKey<? extends Registry<?>> registry) {
|
||||||
|
@ -169,14 +138,4 @@ public class RegistrySyncTest implements ModInitializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object2IntMap<Identifier> createFakeRegistryEntries() {
|
|
||||||
Object2IntMap<Identifier> map = new Object2IntOpenHashMap<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
map.put(Identifier.of("mod_" + i, "entry"), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTest"
|
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTest"
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
"net.fabricmc.fabric.test.registry.sync.client.DynamicRegistryClientTest"
|
"net.fabricmc.fabric.test.registry.sync.client.DynamicRegistryClientTest",
|
||||||
|
"net.fabricmc.fabric.test.registry.sync.client.RegistrySyncClientTest"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* 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.client;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
|
||||||
|
import net.minecraft.registry.RegistryKeys;
|
||||||
|
import net.minecraft.server.command.CommandManager;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
|
||||||
|
import net.fabricmc.fabric.impl.client.registry.sync.ClientRegistrySyncHandler;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.RemapException;
|
||||||
|
import net.fabricmc.fabric.impl.registry.sync.packet.RegistryPacketHandler;
|
||||||
|
|
||||||
|
public class RegistrySyncClientTest implements ClientModInitializer {
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) ->
|
||||||
|
dispatcher.register(CommandManager.literal("remote_remap_error_test").executes(context -> {
|
||||||
|
Map<Identifier, Object2IntMap<Identifier>> registryData = Map.of(
|
||||||
|
RegistryKeys.BLOCK.getValue(), createFakeRegistryEntries(),
|
||||||
|
RegistryKeys.ITEM.getValue(), createFakeRegistryEntries()
|
||||||
|
);
|
||||||
|
Map<Identifier, EnumSet<RegistryAttribute>> attributes = Map.of(
|
||||||
|
RegistryKeys.BLOCK.getValue(), EnumSet.noneOf(RegistryAttribute.class),
|
||||||
|
RegistryKeys.ITEM.getValue(), EnumSet.noneOf(RegistryAttribute.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ClientRegistrySyncHandler.checkRemoteRemap(new RegistryPacketHandler.SyncedPacketData(registryData, attributes));
|
||||||
|
} catch (RemapException e) {
|
||||||
|
final ServerPlayerEntity player = context.getSource().getPlayer();
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
player.networkHandler.disconnect(Objects.requireNonNull(e.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException();
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object2IntMap<Identifier> createFakeRegistryEntries() {
|
||||||
|
Object2IntMap<Identifier> map = new Object2IntOpenHashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
map.put(Identifier.of("mod_" + i, "entry"), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue