Optimize registry sync packet

This commit is contained in:
deirn 2021-11-22 21:29:13 +07:00
parent fa34ed2c10
commit c646d1b33a
14 changed files with 516 additions and 115 deletions

View file

@ -19,25 +19,35 @@ package net.fabricmc.fabric.impl.registry.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.text.LiteralText;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.NbtRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.RegistrySyncPacket;
public class FabricRegistryClientInit implements ClientModInitializer {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(RegistrySyncManager.ID, (client, handler, buf, responseSender) -> {
// if not hosting server, apply packet
RegistrySyncManager.receivePacket(client, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer(), (e) -> {
LOGGER.error("Registry remapping failed!", e);
ClientPlayNetworking.registerGlobalReceiver(DirectRegistrySyncPacket.ID, (client, handler, buf, responseSender) ->
receivePacket(client, handler, DirectRegistrySyncPacket.getInstance(), buf));
client.execute(() -> {
handler.getConnection().disconnect(new LiteralText("Registry remapping failed: " + e.getMessage()));
});
});
ClientPlayNetworking.registerGlobalReceiver(NbtRegistrySyncPacket.ID, (client, handler, buf, responseSender) ->
receivePacket(client, handler, NbtRegistrySyncPacket.getInstance(), buf));
}
private void receivePacket(MinecraftClient client, ClientPlayNetworkHandler handler, RegistrySyncPacket packet, PacketByteBuf buf) {
RegistrySyncManager.receivePacket(client, packet, buf, RegistrySyncManager.DEBUG || !client.isInSingleplayer(), (e) -> {
LOGGER.error("Registry remapping failed!", e);
client.execute(() ->
handler.getConnection().disconnect(new LiteralText("Registry remapping failed: " + e.getMessage())));
});
}
}

View file

@ -21,10 +21,16 @@ import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
public class FabricRegistryInit implements ModInitializer {
@Override
public void onInitialize() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
// TODO: If integrated and local, don't send the packet (it's ignored)
RegistrySyncManager.sendPacket(handler.player);
});
// Synced in PlaySoundS2CPacket.
RegistryAttributeHolder.get(Registry.SOUND_EVENT)
.addAttribute(RegistryAttribute.SYNCED);

View file

@ -38,55 +38,70 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.registry.sync.map.IdMap;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.NbtRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.RegistrySyncPacket;
public final class RegistrySyncManager {
static final boolean DEBUG = System.getProperty("fabric.registry.debug", "false").equalsIgnoreCase("true");
static final Identifier ID = new Identifier("fabric", "registry/sync");
private static final Logger LOGGER = LogManager.getLogger("FabricRegistrySync");
private static final boolean DEBUG_WRITE_REGISTRY_DATA = System.getProperty("fabric.registry.debug.writeContentsAsCsv", "false").equalsIgnoreCase("true");
private static final boolean FORCE_NBT_SYNC = System.getProperty("fabric.registry.forceNbtSync", "false").equalsIgnoreCase("true");
//Set to true after vanilla's bootstrap has completed
public static boolean postBootstrap = false;
private RegistrySyncManager() { }
public static Packet<?> createPacket() {
LOGGER.debug("Creating registry sync packet");
NbtCompound tag = toTag(true, null);
if (tag == null) {
return null;
public static void sendPacket(ServerPlayerEntity player) {
if (FORCE_NBT_SYNC) {
LOGGER.warn("Force NBT sync is enabled");
sendPacket(player, NbtRegistrySyncPacket.getInstance());
return;
}
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeNbt(tag);
return ServerPlayNetworking.createS2CPacket(ID, buf);
if (ServerPlayNetworking.canSend(player, DirectRegistrySyncPacket.ID)) {
sendPacket(player, DirectRegistrySyncPacket.getInstance());
} else {
LOGGER.warn("Player {} can't receive direct packet, using nbt packet instead", player.getEntityName());
sendPacket(player, NbtRegistrySyncPacket.getInstance());
}
}
public static void receivePacket(ThreadExecutor<?> executor, PacketByteBuf buf, boolean accept, Consumer<Exception> errorHandler) {
NbtCompound compound = buf.readNbt();
private static void sendPacket(ServerPlayerEntity player, RegistrySyncPacket packet) {
RegistryMap map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
if (map != null) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
packet.writeBuffer(buf, map);
ServerPlayNetworking.send(player, packet.getPacketId(), buf);
}
}
public static void receivePacket(ThreadExecutor<?> executor, RegistrySyncPacket packet, PacketByteBuf buf, boolean accept, Consumer<Exception> errorHandler) {
RegistryMap map = packet.readBuffer(buf);
if (accept) {
try {
executor.submit(() -> {
if (compound == null) {
errorHandler.accept(new RemapException("Received null compound tag in sync packet!"));
if (map == null) {
errorHandler.accept(new RemapException("Received null map in sync packet!"));
return null;
}
try {
apply(compound, RemappableRegistry.RemapMode.REMOTE);
apply(map, RemappableRegistry.RemapMode.REMOTE);
} catch (RemapException e) {
errorHandler.accept(e);
}
@ -103,12 +118,12 @@ public final class RegistrySyncManager {
* Creates a {@link NbtCompound} used to save or sync the registry ids.
*
* @param isClientSync true when syncing to the client, false when saving
* @param activeTag contains the registry ids that were previously read and applied, can be null.
* @param activeMap contains the registry ids that were previously read and applied, can be null.
* @return a {@link NbtCompound} to save or sync, null when empty
*/
@Nullable
public static NbtCompound toTag(boolean isClientSync, @Nullable NbtCompound activeTag) {
NbtCompound mainTag = new NbtCompound();
public static RegistryMap createAndPopulateRegistryMap(boolean isClientSync, @Nullable RegistryMap activeMap) {
RegistryMap map = new RegistryMap();
for (Identifier registryId : Registry.REGISTRIES.getIds()) {
Registry registry = Registry.REGISTRIES.get(registryId);
@ -153,10 +168,10 @@ public final class RegistrySyncManager {
* This contains the previous state's registry data, this is used for a few things:
* Such as ensuring that previously modded registries or registry entries are not lost or overwritten.
*/
NbtCompound previousRegistryData = null;
IdMap previousIdMap = null;
if (activeTag != null && activeTag.contains(registryId.toString())) {
previousRegistryData = activeTag.getCompound(registryId.toString());
if (activeMap != null && activeMap.containsKey(registryId)) {
previousIdMap = activeMap.get(registryId);
}
RegistryAttributeHolder attributeHolder = RegistryAttributeHolder.get(registry);
@ -174,10 +189,10 @@ public final class RegistrySyncManager {
* or a previous version of fabric registry sync, but will save these ids to disk in case the mod or mods
* are added back.
*/
if ((previousRegistryData == null || isClientSync) && !attributeHolder.hasAttribute(RegistryAttribute.MODDED)) {
if ((previousIdMap == null || isClientSync) && !attributeHolder.hasAttribute(RegistryAttribute.MODDED)) {
LOGGER.debug("Skipping un-modded registry: " + registryId);
continue;
} else if (previousRegistryData != null) {
} else if (previousIdMap != null) {
LOGGER.debug("Preserving previously modded registry: " + registryId);
}
@ -188,7 +203,7 @@ public final class RegistrySyncManager {
}
if (registry instanceof RemappableRegistry) {
NbtCompound registryTag = new NbtCompound();
IdMap idMap = new IdMap();
IntSet rawIdsFound = DEBUG ? new IntOpenHashSet() : null;
for (Object o : registry) {
@ -213,57 +228,52 @@ public final class RegistrySyncManager {
}
}
registryTag.putInt(id.toString(), rawId);
idMap.put(id, rawId);
}
/*
* Look for existing registry key/values that are not in the current registries.
* This can happen when registry entries are removed, preventing that ID from being re-used by something else.
*/
if (!isClientSync && previousRegistryData != null) {
for (String key : previousRegistryData.getKeys()) {
if (!registryTag.contains(key)) {
if (!isClientSync && previousIdMap != null) {
for (Identifier key : previousIdMap.keySet()) {
if (!idMap.containsKey(key)) {
LOGGER.debug("Saving orphaned registry entry: " + key);
registryTag.putInt(key, previousRegistryData.getInt(key));
idMap.put(key, previousIdMap.getInt(key));
}
}
}
mainTag.put(registryId.toString(), registryTag);
map.put(registryId, idMap);
}
}
// Ensure any orphaned registry's are kept on disk
if (!isClientSync && activeTag != null) {
for (String registryKey : activeTag.getKeys()) {
if (!mainTag.contains(registryKey)) {
if (!isClientSync && activeMap != null) {
for (Identifier registryKey : activeMap.keySet()) {
if (!map.containsKey(registryKey)) {
LOGGER.debug("Saving orphaned registry: " + registryKey);
mainTag.put(registryKey, activeTag.getCompound(registryKey));
map.put(registryKey, activeMap.get(registryKey));
}
}
}
if (mainTag.getKeys().isEmpty()) {
if (map.isEmpty()) {
return null;
}
NbtCompound tag = new NbtCompound();
tag.putInt("version", 1);
tag.put("registries", mainTag);
return tag;
return map;
}
public static NbtCompound apply(NbtCompound tag, RemappableRegistry.RemapMode mode) throws RemapException {
NbtCompound mainTag = tag.getCompound("registries");
Set<String> containedRegistries = Sets.newHashSet(mainTag.getKeys());
public static void apply(RegistryMap map, RemappableRegistry.RemapMode mode) throws RemapException {
Set<Identifier> containedRegistries = Sets.newHashSet(map.keySet());
for (Identifier registryId : Registry.REGISTRIES.getIds()) {
if (!containedRegistries.remove(registryId.toString())) {
if (!containedRegistries.remove(registryId)) {
continue;
}
NbtCompound registryTag = mainTag.getCompound(registryId.toString());
IdMap registryMap = map.get(registryId);
Registry registry = Registry.REGISTRIES.get(registryId);
RegistryAttributeHolder attributeHolder = RegistryAttributeHolder.get(registry);
@ -276,8 +286,8 @@ public final class RegistrySyncManager {
if (registry instanceof RemappableRegistry) {
Object2IntMap<Identifier> idMap = new Object2IntOpenHashMap<>();
for (String key : registryTag.getKeys()) {
idMap.put(new Identifier(key), registryTag.getInt(key));
for (Identifier key : registryMap.keySet()) {
idMap.put(key, registryMap.getInt(key));
}
((RemappableRegistry) registry).remap(registryId.toString(), idMap, mode);
@ -287,8 +297,6 @@ public final class RegistrySyncManager {
if (!containedRegistries.isEmpty()) {
LOGGER.warn("[fabric-registry-sync] Could not find the following registries: " + Joiner.on(", ").join(containedRegistries));
}
return mainTag;
}
public static void unmap() throws RemapException {

View file

@ -0,0 +1,24 @@
/*
* 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.map;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import net.minecraft.util.Identifier;
public class IdMap extends Object2IntLinkedOpenHashMap<Identifier> {
}

View file

@ -0,0 +1,65 @@
/*
* 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.map;
import java.util.LinkedHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Identifier;
public class RegistryMap extends LinkedHashMap<Identifier, IdMap> {
public static final int VERSION = 1;
public static RegistryMap fromNbt(NbtCompound nbt) {
NbtCompound mainNbt = nbt.getCompound("registries");
RegistryMap map = new RegistryMap();
for (String registryId : mainNbt.getKeys()) {
IdMap idMap = new IdMap();
NbtCompound idNbt = mainNbt.getCompound(registryId);
for (String id : idNbt.getKeys()) {
idMap.put(new Identifier(id), idNbt.getInt(id));
}
map.put(new Identifier(registryId), idMap);
}
return map;
}
public NbtCompound toNbt() {
NbtCompound mainNbt = new NbtCompound();
forEach((registryId, idMap) -> {
NbtCompound registryNbt = new NbtCompound();
for (Object2IntMap.Entry<Identifier> idPair : idMap.object2IntEntrySet()) {
registryNbt.putInt(idPair.getKey().toString(), idPair.getIntValue());
}
mainNbt.put(registryId.toString(), registryNbt);
});
NbtCompound nbt = new NbtCompound();
nbt.putInt("version", VERSION);
nbt.put("registries", mainNbt);
return nbt;
}
}

View file

@ -0,0 +1,173 @@
/*
* 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.packet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.registry.sync.map.IdMap;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
/**
* A more optimized method to sync registry ids to client.
* Produce smaller packet than old {@link NbtRegistrySyncPacket nbt-based} method.
*
* <p>This method optimize the packet in multiple way:
* <ul>
* <li>Directly write into the buffer instead of using an nbt;</li>
* <li>Group all {@link Identifier} with same namespace together and only send those unique namespaces once for each group;</li>
* <li>Group adjacent rawIds together and only send the last rawId and size of the group.
* This is based on the assumption that mods generally register all of their object at once,
* therefore making the rawIds somewhat densely packed.</li>
* </ul>
*/
public class DirectRegistrySyncPacket implements RegistrySyncPacket {
public static final Identifier ID = new Identifier("fabric", "registry/sync/direct");
private static final DirectRegistrySyncPacket INSTANCE = new DirectRegistrySyncPacket();
private DirectRegistrySyncPacket() {
}
public static DirectRegistrySyncPacket getInstance() {
return INSTANCE;
}
@Override
public Identifier getPacketId() {
return ID;
}
@Override
public void writeBuffer(PacketByteBuf buf, RegistryMap map) {
// Group registry ids with same namespace
Map<String, List<Identifier>> regNamespaceGroups = map.keySet().stream()
.collect(Collectors.groupingBy(Identifier::getNamespace));
buf.writeVarInt(regNamespaceGroups.size());
regNamespaceGroups.forEach((regNamespace, regIds) -> {
buf.writeString(regNamespace);
buf.writeVarInt(regIds.size());
for (Identifier regId : regIds) {
buf.writeString(regId.getPath());
IdMap idMap = map.get(regId);
// Group object ids with name namespace
Map<String, Int2ObjectMap<List<String>>> idNamespaceGroups = new LinkedHashMap<>();
for (Object2IntMap.Entry<Identifier> idPair : idMap.object2IntEntrySet()) {
Identifier id = idPair.getKey();
int rawId = idPair.getIntValue();
Int2ObjectMap<List<String>> adjacentRawIds = idNamespaceGroups.computeIfAbsent(getNamespace(id), s -> new Int2ObjectLinkedOpenHashMap<>());
// Group adjacent rawIds together
List<String> rawIdGroup;
if (adjacentRawIds.containsKey(rawId - 1)) {
rawIdGroup = adjacentRawIds.remove(rawId - 1);
} else {
rawIdGroup = new ArrayList<>();
}
rawIdGroup.add(id.getPath());
adjacentRawIds.put(rawId, rawIdGroup);
}
buf.writeVarInt(idNamespaceGroups.size());
idNamespaceGroups.forEach((idNamespace, adjacentRawIds) -> {
buf.writeString(idNamespace);
buf.writeVarInt(adjacentRawIds.size());
for (Int2ObjectMap.Entry<List<String>> entry : adjacentRawIds.int2ObjectEntrySet()) {
int lastRawId = entry.getIntKey();
List<String> adjacentPaths = entry.getValue();
buf.writeVarInt(lastRawId);
buf.writeVarInt(adjacentPaths.size());
for (String path : adjacentPaths) {
buf.writeString(path);
}
}
});
}
});
}
@Override
@Nullable
public RegistryMap readBuffer(PacketByteBuf buf) {
RegistryMap map = new RegistryMap();
int regNamespaceGroupAmount = buf.readVarInt();
for (int i = 0; i < regNamespaceGroupAmount; i++) {
String regNamespace = buf.readString();
int regNamespaceGroupLength = buf.readVarInt();
for (int j = 0; j < regNamespaceGroupLength; j++) {
String regPath = buf.readString();
IdMap idMap = new IdMap();
int idNamespaceGroupAmount = buf.readVarInt();
for (int k = 0; k < idNamespaceGroupAmount; k++) {
String idNamespace = buf.readString();
int idNamespaceGroupLength = buf.readVarInt();
for (int l = 0; l < idNamespaceGroupLength; l++) {
int lastRawId = buf.readVarInt();
int rawIdGroupLength = buf.readVarInt();
int firstRawId = lastRawId - (rawIdGroupLength - 1);
for (int m = 0; m < rawIdGroupLength; m++) {
String idPath = buf.readString();
idMap.put(new Identifier(idNamespace, idPath), firstRawId + m);
}
}
}
map.put(new Identifier(regNamespace, regPath), idMap);
}
}
return map;
}
private String getNamespace(Identifier id) {
String namespace = id.getNamespace();
if (namespace.equals("minecraft")) {
namespace = "";
}
return namespace;
}
}

View file

@ -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.packet;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
/**
* A method to sync registry ids using {@link NbtCompound} and {@link PacketByteBuf#writeNbt}.
* Kept here for old version support.
*/
// TODO: Remove
@Deprecated
public class NbtRegistrySyncPacket implements RegistrySyncPacket {
public static final Identifier ID = new Identifier("fabric", "registry/sync");
private static final NbtRegistrySyncPacket INSTANCE = new NbtRegistrySyncPacket();
private NbtRegistrySyncPacket() {
}
public static NbtRegistrySyncPacket getInstance() {
return INSTANCE;
}
@Override
public Identifier getPacketId() {
return ID;
}
@Override
public void writeBuffer(PacketByteBuf buf, RegistryMap map) {
buf.writeNbt(map.toNbt());
}
@Override
@Nullable
public RegistryMap readBuffer(PacketByteBuf buf) {
NbtCompound nbt = buf.readNbt();
return nbt != null ? RegistryMap.fromNbt(nbt) : null;
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.packet;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
public interface RegistrySyncPacket {
Identifier getPacketId();
void writeBuffer(PacketByteBuf buf, RegistryMap t);
@Nullable
RegistryMap readBuffer(PacketByteBuf buf);
}

View file

@ -44,17 +44,18 @@ import net.minecraft.util.registry.DynamicRegistryManager;
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.map.RegistryMap;
@Mixin(LevelStorage.Session.class)
public class MixinLevelStorageSession {
@Unique
private static final int FABRIC_ID_REGISTRY_BACKUPS = 3;
@Unique
private static Logger FABRIC_LOGGER = LogManager.getLogger("FabricRegistrySync");
private static final Logger FABRIC_LOGGER = LogManager.getLogger("FabricRegistrySync");
@Unique
private NbtCompound fabric_lastSavedIdMap = null;
private RegistryMap fabric_lastSavedRegistryMap = null;
@Unique
private NbtCompound fabric_activeTag = null;
private RegistryMap fabric_activeRegistryMap = null;
@Shadow
@Final
@ -70,7 +71,8 @@ public class MixinLevelStorageSession {
fileInputStream.close();
if (tag != null) {
fabric_activeTag = RegistrySyncManager.apply(tag, RemappableRegistry.RemapMode.AUTHORITATIVE);
fabric_activeRegistryMap = RegistryMap.fromNbt(tag);
RegistrySyncManager.apply(fabric_activeRegistryMap, RemappableRegistry.RemapMode.AUTHORITATIVE);
return true;
}
}
@ -86,14 +88,14 @@ public class MixinLevelStorageSession {
@Unique
private void fabric_saveRegistryData() {
FABRIC_LOGGER.debug("Starting registry save");
NbtCompound newIdMap = RegistrySyncManager.toTag(false, fabric_activeTag);
RegistryMap newMap = RegistrySyncManager.createAndPopulateRegistryMap(false, fabric_activeRegistryMap);
if (newIdMap == null) {
if (newMap == null) {
FABRIC_LOGGER.debug("Not saving empty registry data");
return;
}
if (!newIdMap.equals(fabric_lastSavedIdMap)) {
if (!newMap.equals(fabric_lastSavedRegistryMap)) {
for (int i = FABRIC_ID_REGISTRY_BACKUPS - 1; i >= 0; i--) {
File file = fabric_getWorldIdMapFile(i);
@ -117,15 +119,15 @@ public class MixinLevelStorageSession {
}
}
FABRIC_LOGGER.debug("Saving registry data to " + file.toString());
FABRIC_LOGGER.debug("Saving registry data to " + file);
FileOutputStream fileOutputStream = new FileOutputStream(file);
NbtIo.writeCompressed(newIdMap, fileOutputStream);
NbtIo.writeCompressed(newMap.toNbt(), fileOutputStream);
fileOutputStream.close();
} catch (IOException e) {
FABRIC_LOGGER.warn("[fabric-registry-sync] Failed to save registry file!", e);
}
fabric_lastSavedIdMap = newIdMap;
fabric_lastSavedRegistryMap = newMap;
}
}

View file

@ -1,43 +0,0 @@
/*
* 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;
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.network.ClientConnection;
import net.minecraft.network.Packet;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
@Mixin(PlayerManager.class)
public abstract class MixinPlayerManager {
@Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/DifficultyS2CPacket;<init>(Lnet/minecraft/world/Difficulty;Z)V"))
public void onPlayerConnect(ClientConnection lvt1, ServerPlayerEntity lvt2, CallbackInfo info) {
// TODO: If integrated and local, don't send the packet (it's ignored)
// TODO: Refactor out into network + move registry hook to event
Packet<?> packet = RegistrySyncManager.createPacket();
if (packet != null) {
lvt2.networkHandler.sendPacket(packet);
}
}
}

View file

@ -10,7 +10,6 @@
"MixinIdList",
"MixinIdRegistry",
"MixinMain",
"MixinPlayerManager",
"MixinLevelStorageSession",
"MixinRegistry",
"MixinSimpleRegistry",

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.test.registry.sync;
import io.netty.buffer.Unpooled;
import org.apache.commons.lang3.Validate;
import net.minecraft.block.AbstractBlock;
@ -23,6 +24,7 @@ import net.minecraft.block.Block;
import net.minecraft.block.Material;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.DynamicRegistryManager;
@ -38,6 +40,12 @@ 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.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.NbtRegistrySyncPacket;
public class RegistrySyncTest implements ModInitializer {
/**
@ -46,8 +54,18 @@ public class RegistrySyncTest implements ModInitializer {
public static final boolean REGISTER_BLOCKS = Boolean.parseBoolean(System.getProperty("fabric.registry.sync.test.register.blocks", "true"));
public static final boolean REGISTER_ITEMS = Boolean.parseBoolean(System.getProperty("fabric.registry.sync.test.register.items", "true"));
public static final Identifier PACKET_CHECK = new Identifier("fabric-registry-sync-v0-v1-testmod:packet_check");
@Override
public void onInitialize() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
RegistryMap map = RegistrySyncManager.createAndPopulateRegistryMap(true, null);
NbtRegistrySyncPacket.getInstance().writeBuffer(buf, map);
DirectRegistrySyncPacket.getInstance().writeBuffer(buf, map);
ServerPlayNetworking.send(handler.player, PACKET_CHECK, buf);
});
testBuiltInRegistrySync();
if (REGISTER_BLOCKS) {

View file

@ -0,0 +1,42 @@
/*
* 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 java.util.Objects;
import com.google.common.base.Preconditions;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.impl.registry.sync.map.RegistryMap;
import net.fabricmc.fabric.impl.registry.sync.packet.DirectRegistrySyncPacket;
import net.fabricmc.fabric.impl.registry.sync.packet.NbtRegistrySyncPacket;
@Environment(EnvType.CLIENT)
public class RegistrySyncTestClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(RegistrySyncTest.PACKET_CHECK, (client, handler, buf, responseSender) -> {
RegistryMap nbtPacketMap = NbtRegistrySyncPacket.getInstance().readBuffer(buf);
RegistryMap directPacketMap = DirectRegistrySyncPacket.getInstance().readBuffer(buf);
Preconditions.checkArgument(Objects.requireNonNull(nbtPacketMap).equals(directPacketMap), "nbt packet and direct packet are not equal!");
});
}
}

View file

@ -11,6 +11,9 @@
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTest"
],
"client": [
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTestClient"
]
}
}