mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-26 01:44:48 +00:00
Rewrite monitor networking (#453)
This moves monitor networking into its own packet, rather than serialising using NBT. This allows us to be more flexible with how monitors are serialised. We now compress terminal data using gzip. This reduces the packet size of a max-sized-monitor from ~25kb to as little as 100b. On my test set of images (what I would consider to be the extreme end of the "reasonable" case), we have packets from 1.4kb bytes up to 12kb, with a mean of 6kb. Even in the worst case, this is a 2x reduction in packet size. While this is a fantastic win for the common case, it is not abuse-proof. One can create a terminal with high entropy (and so uncompressible). This will still be close to the original packet size. In order to prevent any other abuse, we also limit the amount of monitor data a client can possibly receive to 1MB (configurable).
This commit is contained in:
@@ -45,6 +45,7 @@ public final class NetworkHandler
|
||||
registerMainThread( 12, Side.CLIENT, ComputerDeletedClientMessage::new );
|
||||
registerMainThread( 13, Side.CLIENT, ComputerTerminalClientMessage::new );
|
||||
registerMainThread( 14, Side.CLIENT, PlayRecordClientMessage::new );
|
||||
registerMainThread( 15, Side.CLIENT, MonitorClientMessage::new );
|
||||
}
|
||||
|
||||
public static void sendToPlayer( EntityPlayer player, IMessage packet )
|
||||
@@ -67,6 +68,11 @@ public final class NetworkHandler
|
||||
network.sendToAllAround( packet, point );
|
||||
}
|
||||
|
||||
public static void sendToAllTracking( IMessage packet, NetworkRegistry.TargetPoint point )
|
||||
{
|
||||
network.sendToAllTracking( packet, point );
|
||||
}
|
||||
|
||||
/**
|
||||
* /**
|
||||
* Register packet, and a thread-unsafe handler for it.
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||
|
||||
@@ -14,12 +12,12 @@ import javax.annotation.Nonnull;
|
||||
|
||||
public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
{
|
||||
private NBTTagCompound tag;
|
||||
private TerminalState state;
|
||||
|
||||
public ComputerTerminalClientMessage( int instanceId, NBTTagCompound tag )
|
||||
public ComputerTerminalClientMessage( int instanceId, TerminalState state )
|
||||
{
|
||||
super( instanceId );
|
||||
this.tag = tag;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public ComputerTerminalClientMessage()
|
||||
@@ -30,19 +28,19 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.toBytes( buf );
|
||||
buf.writeCompoundTag( tag ); // TODO: Do we need to compress this?
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.fromBytes( buf );
|
||||
tag = NBTUtil.readCompoundTag( buf );
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( MessageContext context )
|
||||
{
|
||||
getComputer().readDescription( tag );
|
||||
getComputer().read( state );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityPlayerSP;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MonitorClientMessage implements NetworkMessage
|
||||
{
|
||||
private BlockPos pos;
|
||||
private TerminalState state;
|
||||
|
||||
public MonitorClientMessage( BlockPos pos, TerminalState state )
|
||||
{
|
||||
this.pos = pos;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public MonitorClientMessage()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeBlockPos( pos );
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
pos = buf.readBlockPos();
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( MessageContext context )
|
||||
{
|
||||
EntityPlayerSP player = Minecraft.getMinecraft().player;
|
||||
if( player == null || player.world == null ) return;
|
||||
|
||||
TileEntity te = player.world.getTileEntity( pos );
|
||||
if( !(te instanceof TileMonitor) ) return;
|
||||
|
||||
((TileMonitor) te).read( state );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.IoUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* A snapshot of a terminal's state.
|
||||
*
|
||||
* This is somewhat memory inefficient (we build a buffer, only to write it elsewhere), however it means we get a
|
||||
* complete and accurate description of a terminal, which avoids a lot of complexities with resizing terminals, dirty
|
||||
* states, etc...
|
||||
*/
|
||||
public class TerminalState
|
||||
{
|
||||
public final boolean colour;
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
|
||||
private final boolean compress;
|
||||
|
||||
@Nullable
|
||||
private final ByteBuf buffer;
|
||||
|
||||
private ByteBuf compressed;
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal )
|
||||
{
|
||||
this( colour, terminal, true );
|
||||
}
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal, boolean compress )
|
||||
{
|
||||
this.colour = colour;
|
||||
this.compress = compress;
|
||||
|
||||
if( terminal == null )
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = terminal.getWidth();
|
||||
this.height = terminal.getHeight();
|
||||
|
||||
ByteBuf buf = this.buffer = Unpooled.buffer();
|
||||
terminal.write( new PacketBuffer( buf ) );
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalState( PacketBuffer buf )
|
||||
{
|
||||
this.colour = buf.readBoolean();
|
||||
this.compress = buf.readBoolean();
|
||||
|
||||
if( buf.readBoolean() )
|
||||
{
|
||||
this.width = buf.readVarInt();
|
||||
this.height = buf.readVarInt();
|
||||
|
||||
int length = buf.readVarInt();
|
||||
this.buffer = readCompressed( buf, length, compress );
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void write( PacketBuffer buf )
|
||||
{
|
||||
buf.writeBoolean( colour );
|
||||
buf.writeBoolean( compress );
|
||||
|
||||
buf.writeBoolean( buffer != null );
|
||||
if( buffer != null )
|
||||
{
|
||||
buf.writeVarInt( width );
|
||||
buf.writeVarInt( height );
|
||||
|
||||
ByteBuf sendBuffer = getCompressed();
|
||||
buf.writeVarInt( sendBuffer.readableBytes() );
|
||||
buf.writeBytes( sendBuffer, sendBuffer.readerIndex(), sendBuffer.readableBytes() );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTerminal()
|
||||
{
|
||||
return buffer != null;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return buffer == null ? 0 : buffer.readableBytes();
|
||||
}
|
||||
|
||||
public void apply( Terminal terminal )
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
terminal.read( new PacketBuffer( buffer ) );
|
||||
}
|
||||
|
||||
private ByteBuf getCompressed()
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
if( !compress ) return buffer;
|
||||
if( compressed != null ) return compressed;
|
||||
|
||||
ByteBuf compressed = Unpooled.directBuffer();
|
||||
OutputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPOutputStream( new ByteBufOutputStream( compressed ) );
|
||||
stream.write( buffer.array(), buffer.arrayOffset(), buffer.readableBytes() );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
|
||||
return this.compressed = compressed;
|
||||
}
|
||||
|
||||
private static ByteBuf readCompressed( ByteBuf buf, int length, boolean compress )
|
||||
{
|
||||
if( compress )
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
InputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPInputStream( new ByteBufInputStream( buf, length ) );
|
||||
byte[] swap = new byte[8192];
|
||||
while( true )
|
||||
{
|
||||
int bytes = stream.read( swap );
|
||||
if( bytes == -1 ) break;
|
||||
buffer.writeBytes( swap, 0, bytes );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer( length );
|
||||
buf.readBytes( buffer, length );
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user