Improve handling of contraption data for syncing and minecart pickup

- filter out null contraptions in ContraptionRenderingWorld
- fix and unify contraption data size estimates
- add config for max contraption size for syncing
- Minecart pickup max is increased if XL Packets is loaded
This commit is contained in:
TropheusJ 2022-12-23 22:50:31 -05:00
parent 8d89080bc0
commit 41697aca7f
7 changed files with 102 additions and 44 deletions

View file

@ -1,6 +1,5 @@
package com.simibubi.create.content.contraptions.components.structureMovement;
import java.io.IOException;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
@ -11,11 +10,11 @@ import java.util.UUID;
import javax.annotation.Nullable;
import com.simibubi.create.foundation.utility.ContraptionData;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.tuple.MutablePair;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllMovementBehaviours;
@ -44,7 +43,6 @@ import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
@ -598,22 +596,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
CompoundTag compound = new CompoundTag();
writeAdditional(compound, true);
try {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
NbtIo.write(compound, dataOutput);
byte[] byteArray = dataOutput.toByteArray();
int estimatedPacketSize = byteArray.length;
if (estimatedPacketSize > 2_000_000) {
Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): "
+ getContraption().getType().id + " @" + position() + " (" + getUUID().toString() + ")");
buffer.writeNbt(new CompoundTag());
return;
}
} catch (IOException e) {
e.printStackTrace();
buffer.writeNbt(new CompoundTag());
return;
if (ContraptionData.isTooLargeForSync(compound)) {
String info = getContraption().getType().id + " @" + position() + " (" + getStringUUID() + ")";
Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): " + info);
compound = null;
}
buffer.writeNbt(compound);
@ -633,7 +619,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
@Override
public void readSpawnData(FriendlyByteBuf additionalData) {
readAdditional(additionalData.readNbt(), true);
CompoundTag nbt = additionalData.readAnySizeNbt();
if (nbt != null) {
readAdditional(nbt, true);
}
}
@Override

View file

@ -1,14 +1,15 @@
package com.simibubi.create.content.contraptions.components.structureMovement.mounted;
import java.io.IOException;
import java.util.List;
import javax.annotation.Nullable;
import com.simibubi.create.foundation.utility.ContraptionData;
import net.minecraft.network.chat.MutableComponent;
import org.apache.commons.lang3.tuple.MutablePair;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.simibubi.create.AllItems;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.content.contraptions.components.actors.PortableStorageInterfaceMovement;
@ -31,7 +32,6 @@ import net.minecraft.core.NonNullList;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import net.minecraft.core.dispenser.DispenseItemBehavior;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
@ -251,18 +251,10 @@ public class MinecartContraptionItem extends Item {
ItemStack generatedStack = create(type, oce).setHoverName(entity.getCustomName());
try {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
NbtIo.write(generatedStack.serializeNBT(), dataOutput);
int estimatedPacketSize = dataOutput.toByteArray().length;
if (estimatedPacketSize > 2_000_000) {
player.displayClientMessage(Lang.translateDirect("contraption.minecart_contraption_too_big")
.withStyle(ChatFormatting.RED), true);
return;
}
} catch (IOException e) {
e.printStackTrace();
if (ContraptionData.isTooLargeForPickup(generatedStack.serializeNBT())) {
MutableComponent message = Lang.translateDirect("contraption.minecart_contraption_too_big")
.withStyle(ChatFormatting.RED);
player.displayClientMessage(message, true);
return;
}

View file

@ -64,6 +64,7 @@ public abstract class ContraptionRenderingWorld<C extends ContraptionRenderInfo>
.map(Reference::get)
.filter(Objects::nonNull)
.map(AbstractContraptionEntity::getContraption)
.filter(Objects::nonNull) // contraptions that are too large will not be synced, and un-synced contraptions will be null
.forEach(this::getRenderInfo);
}

View file

@ -1,6 +1,7 @@
package com.simibubi.create.foundation.config;
import com.simibubi.create.foundation.config.ui.ConfigAnnotations;
import com.simibubi.create.foundation.utility.ContraptionData;
public class CKinetics extends ConfigBase {
@ -30,6 +31,8 @@ public class CKinetics extends ConfigBase {
public final ConfigGroup contraptions = group(1, "contraptions", "Moving Contraptions");
public final ConfigInt maxBlocksMoved = i(2048, 1, "maxBlocksMoved", Comments.maxBlocksMoved);
public final ConfigInt maxDataSize =
i(ContraptionData.DEFAULT_MAX, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2);
public final ConfigInt maxChassisRange = i(16, 1, "maxChassisRange", Comments.maxChassisRange);
public final ConfigInt maxPistonPoles = i(64, 1, "maxPistonPoles", Comments.maxPistonPoles);
public final ConfigInt maxRopeLength = i(256, 1, "maxRopeLength", Comments.maxRopeLength);
@ -75,6 +78,9 @@ public class CKinetics extends ConfigBase {
"multiplier used for calculating exhaustion from speed when a crank is turned.";
static String maxBlocksMoved =
"Maximum amount of blocks in a structure movable by Pistons, Bearings or other means.";
static String maxDataSize = "Maximum amount of data a contraption can have before it can't be synced with players.";
static String maxDataSize2 = "Un-synced contraptions will not be visible and will not have collision.";
static String maxDataDisable = "[0 to disable this limit]";
static String maxChassisRange = "Maximum value of a chassis attachment range.";
static String maxPistonPoles = "Maximum amount of extension poles behind a Mechanical Piston.";
static String maxRopeLength = "Max length of rope available off a Rope Pulley.";
@ -86,6 +92,7 @@ public class CKinetics extends ConfigBase {
static String stats = "Configure speed/capacity levels for requirements and indicators.";
static String rpm = "[in Revolutions per Minute]";
static String su = "[in Stress Units]";
static String bytes = "[in Bytes]";
static String mediumSpeed = "Minimum speed of rotation to be considered 'medium'";
static String fastSpeed = "Minimum speed of rotation to be considered 'fast'";
static String mediumStressImpact = "Minimum stress impact to be considered 'medium'";

View file

@ -0,0 +1,12 @@
package com.simibubi.create.foundation.mixin.accessor;
import net.minecraft.nbt.NbtAccounter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(NbtAccounter.class)
public interface NbtAccounterAccessor {
@Accessor("usage")
long create$getUsage();
}

View file

@ -0,0 +1,56 @@
package com.simibubi.create.foundation.utility;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraftforge.fml.ModList;
public class ContraptionData {
/**
* A sane, default maximum for contraption data size.
*/
public static final int DEFAULT_MAX = 2_000_000;
/**
* XL Packets expands the NBT packet limit to 2 GB.
*/
public static final int EXPANDED_MAX = 2_000_000_000;
/**
* Minecart item sizes are limited by the vanilla slot change packet ({@link ClientboundContainerSetSlotPacket}).
* {@link ContraptionData#DEFAULT_MAX} is used as the default.
* XL Packets expands the size limit to ~2 GB. If the mod is loaded, we take advantage of it and use the higher limit.
*/
public static final int PICKUP_MAX = ModList.get().isLoaded("xlpackets") ? EXPANDED_MAX : DEFAULT_MAX;
/**
* @return true if the given NBT is too large for a contraption to be synced to clients.
*/
public static boolean isTooLargeForSync(CompoundTag data) {
int max = AllConfigs.SERVER.kinetics.maxDataSize.get();
return max != 0 && packetSize(data) > max;
}
/**
* @return true if the given NBT is too large for a contraption to be picked up with a wrench.
*/
public static boolean isTooLargeForPickup(CompoundTag data) {
return packetSize(data) > PICKUP_MAX;
}
/**
* @return the size of the given NBT when put through a packet, in bytes.
*/
public static long packetSize(CompoundTag data) {
FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer());
test.writeNbt(data);
NbtAccounter sizeTracker = new NbtAccounter(Long.MAX_VALUE);
test.readNbt(sizeTracker);
long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage();
test.release();
return size;
}
}

View file

@ -13,6 +13,7 @@
"accessor.DispenserBlockAccessor",
"accessor.FallingBlockEntityAccessor",
"accessor.LivingEntityAccessor",
"accessor.NbtAccounterAccessor",
"accessor.ServerLevelAccessor"
],
"client": [