mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Optimize registry sync packet
This commit is contained in:
parent
fa34ed2c10
commit
c646d1b33a
14 changed files with 516 additions and 115 deletions
fabric-registry-sync-v0/src
main
java/net/fabricmc/fabric
impl/registry/sync
mixin/registry/sync
resources
testmod
java/net/fabricmc/fabric/test/registry/sync
resources
|
@ -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())));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@
|
|||
"MixinIdList",
|
||||
"MixinIdRegistry",
|
||||
"MixinMain",
|
||||
"MixinPlayerManager",
|
||||
"MixinLevelStorageSession",
|
||||
"MixinRegistry",
|
||||
"MixinSimpleRegistry",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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!");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -11,6 +11,9 @@
|
|||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTest"
|
||||
],
|
||||
"client": [
|
||||
"net.fabricmc.fabric.test.registry.sync.RegistrySyncTestClient"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue