Improve serialisation of terminals

- Write to a PacketBuffer instead of generating an NBT tag. This is
   then converted to an NBT byte array when we send across the network.
 - Pack background/foreground colours into a single byte.

This derives from some work I did back in 2017, and some of the changes
made/planned in #409. However, this patch does not change how terminals
are represented, it simply makes the transfer more compact.

This makes the patch incredibly small (100 lines!), but also limited in
what improvements it can make compared with #409. We send 26626 bytes
for a full-sized monitor. While a 2x improvement over the previous 58558
bytes, there's a lot of room for improvement.
This commit is contained in:
SquidDev 2020-05-03 10:34:24 +01:00
parent 4553e404b2
commit 17b7727262
4 changed files with 110 additions and 30 deletions

View File

@ -7,16 +7,17 @@
import dan200.computercraft.shared.util.Palette;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
public class Terminal
{
private static final String base16 = "0123456789abcdef";
private int m_cursorX;
private int m_cursorY;
private boolean m_cursorBlink;
private int m_cursorColour;
private int m_cursorBackgroundColour;
private int m_cursorX = 0;
private int m_cursorY = 0;
private boolean m_cursorBlink = false;
private int m_cursorColour = 0;
private int m_cursorBackgroundColour = 15;
private int m_width;
private int m_height;
@ -25,9 +26,9 @@ public class Terminal
private TextBuffer[] m_textColour;
private TextBuffer[] m_backgroundColour;
private final Palette m_palette;
private final Palette m_palette = new Palette();
private boolean m_changed;
private boolean m_changed = false;
private final Runnable onChanged;
public Terminal( int width, int height )
@ -41,9 +42,6 @@ public Terminal( int width, int height, Runnable changedCallback )
m_height = height;
onChanged = changedCallback;
m_cursorColour = 0;
m_cursorBackgroundColour = 15;
m_text = new TextBuffer[m_height];
m_textColour = new TextBuffer[m_height];
m_backgroundColour = new TextBuffer[m_height];
@ -53,14 +51,6 @@ public Terminal( int width, int height, Runnable changedCallback )
m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width );
m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width );
}
m_cursorX = 0;
m_cursorY = 0;
m_cursorBlink = false;
m_changed = false;
m_palette = new Palette();
}
public synchronized void reset()
@ -336,6 +326,59 @@ public final void clearChanged()
m_changed = false;
}
public synchronized void write( PacketBuffer buffer )
{
buffer.writeInt( m_cursorX );
buffer.writeInt( m_cursorY );
buffer.writeBoolean( m_cursorBlink );
buffer.writeByte( m_cursorBackgroundColour << 4 | m_cursorColour );
for( int y = 0; y < m_height; y++ )
{
TextBuffer text = m_text[y];
TextBuffer textColour = m_textColour[y];
TextBuffer backColour = m_backgroundColour[y];
for( int x = 0; x < m_width; x++ )
{
buffer.writeByte( text.charAt( x ) & 0xFF );
buffer.writeByte( colourIndex( backColour.charAt( x ) ) << 4 | colourIndex( textColour.charAt( x ) ) );
}
}
m_palette.write( buffer );
}
public synchronized void read( PacketBuffer buffer )
{
m_cursorX = buffer.readInt();
m_cursorY = buffer.readInt();
m_cursorBlink = buffer.readBoolean();
byte cursorColour = buffer.readByte();
m_cursorBackgroundColour = (cursorColour >> 4) & 0xF;
m_cursorColour = cursorColour & 0xF;
for( int y = 0; y < m_height; y++ )
{
TextBuffer text = m_text[y];
TextBuffer textColour = m_textColour[y];
TextBuffer backColour = m_backgroundColour[y];
for( int x = 0; x < m_width; x++ )
{
text.setChar( x, (char) (buffer.readByte() & 0xFF) );
byte colour = buffer.readByte();
backColour.setChar( x, base16.charAt( (colour >> 4) & 0xF ) );
textColour.setChar( x, base16.charAt( colour & 0xF ) );
}
}
m_palette.read( buffer );
setChanged();
}
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbt )
{
nbt.setInteger( "term_cursorX", m_cursorX );
@ -349,10 +392,8 @@ public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbt )
nbt.setString( "term_textColour_" + n, m_textColour[n].toString() );
nbt.setString( "term_textBgColour_" + n, m_backgroundColour[n].toString() );
}
if( m_palette != null )
{
m_palette.writeToNBT( nbt );
}
m_palette.writeToNBT( nbt );
return nbt;
}
@ -382,10 +423,15 @@ public synchronized void readFromNBT( NBTTagCompound nbt )
m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) );
}
}
if( m_palette != null )
{
m_palette.readFromNBT( nbt );
}
m_palette.readFromNBT( nbt );
setChanged();
}
private static int colourIndex( char c )
{
if( c >= '0' && c <= '9' ) return c - '0';
if( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
return 0;
}
}

View File

@ -6,7 +6,9 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
public class ClientTerminal implements ITerminal
{
@ -53,7 +55,7 @@ public void readDescription( NBTTagCompound nbt )
{
NBTTagCompound terminal = nbt.getCompoundTag( "terminal" );
resizeTerminal( terminal.getInteger( "term_width" ), terminal.getInteger( "term_height" ) );
m_terminal.readFromNBT( terminal );
m_terminal.read( new PacketBuffer( Unpooled.wrappedBuffer( terminal.getByteArray( "term_contents" ) ) ) );
}
else
{

View File

@ -5,8 +5,12 @@
*/
package dan200.computercraft.shared.common;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
@ -83,17 +87,28 @@ public boolean isColour()
return m_colour;
}
// Networking stuff
public void writeDescription( NBTTagCompound nbt )
{
nbt.setBoolean( "colour", m_colour );
if( m_terminal != null )
{
// We have a 10 byte header (2 integer positions, then blinking and current colours), followed by the
// contents and palette.
// Yes, this serialisation code is terrible, but we need to serialise to NBT in order to work with monitors
// (or rather tile entity serialisation).
final int length = 10 + (2 * m_terminal.getWidth() * m_terminal.getHeight()) + (16 * 3);
ByteBuf buffer = Unpooled.buffer( length );
m_terminal.write( new PacketBuffer( buffer ) );
if( buffer.writableBytes() != 0 )
{
ComputerCraft.log.warn( "Should have written {} bytes, but have {} ({} remaining).", length, buffer.writerIndex(), buffer.writableBytes() );
}
NBTTagCompound terminal = new NBTTagCompound();
terminal.setInteger( "term_width", m_terminal.getWidth() );
terminal.setInteger( "term_height", m_terminal.getHeight() );
m_terminal.writeToNBT( terminal );
terminal.setByteArray( "term_contents", buffer.array() );
nbt.setTag( "terminal", terminal );
}
}

View File

@ -6,6 +6,7 @@
package dan200.computercraft.shared.util;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.PacketBuffer;
public class Palette
{
@ -78,6 +79,22 @@ public static double[] decodeRGB8( int rgb )
};
}
public void write( PacketBuffer buffer )
{
for( double[] colour : colours )
{
for( double channel : colour ) buffer.writeByte( (int) (channel * 0xFF) & 0xFF );
}
}
public void read( PacketBuffer buffer )
{
for( double[] colour : colours )
{
for( int i = 0; i < colour.length; i++ ) colour[i] = buffer.readByte() * 255;
}
}
public NBTTagCompound writeToNBT( NBTTagCompound nbt )
{
int[] rgb8 = new int[colours.length];