org.spout.vanilla.protocol.codec.world.chunk.ChunkBulkCodec.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.vanilla.protocol.codec.world.chunk.ChunkBulkCodec.java

Source

/*
 * This file is part of Vanilla.
 *
 * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
 * Vanilla is licensed under the Spout License Version 1.
 *
 * Vanilla is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * In addition, 180 days after any changes are published, you can use the
 * software, incorporating those changes, under the terms of the MIT license,
 * as described in the Spout License Version 1.
 *
 * Vanilla is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License,
 * the MIT license and the Spout License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://spout.in/licensev1> for the full license, including
 * the MIT license.
 */
package org.spout.vanilla.protocol.codec.world.chunk;

import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import org.spout.api.geo.cuboid.Chunk;
import org.spout.api.protocol.MessageCodec;
import org.spout.api.protocol.reposition.NullRepositionManager;

import org.spout.vanilla.protocol.msg.world.chunk.ChunkBulkMessage;

public final class ChunkBulkCodec extends MessageCodec<ChunkBulkMessage> {
    private static final int COMPRESSION_LEVEL = Deflater.BEST_SPEED;

    public ChunkBulkCodec() {
        super(ChunkBulkMessage.class, 0x38);
    }

    @Override
    public ChunkBulkMessage decode(ByteBuf buffer) throws IOException {
        int length = buffer.readShort() & 0xFFFF;

        int compressed = buffer.readInt();

        byte[] compressedDataFlat = new byte[compressed];

        buffer.readBytes(compressedDataFlat);

        int[] x = new int[length];
        int[] z = new int[length];
        byte[][][] data = new byte[length][][];
        boolean[][] hasAdd = new boolean[length][];
        byte[][] biomeData = new byte[length][];

        for (int i = 0; i < length; i++) {
            x[i] = buffer.readInt();
            z[i] = buffer.readInt();
            short primaryBitmap = buffer.readShort();
            hasAdd[i] = shortToBooleanArray(buffer.readShort());
            data[i] = shortToByteByteArray(primaryBitmap, hasAdd[i]);
            biomeData[i] = new byte[Chunk.BLOCKS.AREA];
        }

        int flatLength = 0;

        for (int i = 0; i < length; i++) {
            byte[][] columnData = data[i];
            for (int j = 0; j < columnData.length; j++) {
                flatLength += columnData[j].length;
            }
            flatLength += biomeData[i].length;
        }

        byte[] uncompressedDataFlat = new byte[flatLength];

        Inflater inflater = new Inflater();
        inflater.setInput(compressedDataFlat);
        inflater.getRemaining();
        try {
            int uncompressed = inflater.inflate(uncompressedDataFlat);
            if (uncompressed == 0) {
                throw new IOException("Not all bytes uncompressed.");
            } else if (uncompressed != uncompressedDataFlat.length) {
                throw new IOException("Wrong length for compressed data");
            }
        } catch (DataFormatException e) {
            e.printStackTrace();
            throw new IOException("Bad compressed data.");
        } finally {
            inflater.end();
        }

        int pos = 0;
        for (int i = 0; i < length; i++) {
            byte[][] columnData = data[i];
            for (int j = 0; j < columnData.length; j++) {
                int l = columnData[j].length;
                System.arraycopy(uncompressedDataFlat, pos, columnData[j], 0, l);
                pos += l;
            }
            int l = biomeData[i].length;
            System.arraycopy(uncompressedDataFlat, pos, biomeData[i], 0, l);
            pos += l;
        }

        if (pos != flatLength) {
            throw new IllegalStateException("Flat data length miscalculated");
        }

        return new ChunkBulkMessage(x, z, hasAdd, data, biomeData, NullRepositionManager.getInstance());
    }

    @Override
    public ByteBuf encode(ChunkBulkMessage message) throws IOException {
        ByteBuf buffer = Unpooled.buffer();

        int length = message.getX().length;
        buffer.writeShort(length);

        int dataLength = 0;
        byte[][][] uncompressedData = message.getData();
        byte[][] biomeData = message.getBiomeData();
        for (int i = 0; i < length; i++) {
            byte[][] uncompressedColumnData = uncompressedData[i];
            for (int j = 0; j < uncompressedColumnData.length; j++) {
                byte[] uncompressedChunkData = uncompressedColumnData[j];
                if (uncompressedChunkData != null) {
                    dataLength += uncompressedChunkData.length;
                }
            }
            dataLength += biomeData[i].length;
        }

        byte[] uncompressedDataFlat = new byte[dataLength];
        int pos = 0;
        for (int i = 0; i < length; i++) {
            byte[][] uncompressedColumnData = uncompressedData[i];
            for (int j = 0; j < uncompressedColumnData.length; j++) {
                byte[] uncompressedChunkData = uncompressedColumnData[j];
                if (uncompressedChunkData != null) {
                    int copyLength = uncompressedChunkData.length;
                    System.arraycopy(uncompressedChunkData, 0, uncompressedDataFlat, pos, copyLength);
                    pos += copyLength;
                }
            }
            System.arraycopy(biomeData[i], 0, uncompressedDataFlat, pos, biomeData[i].length);
            pos += biomeData[i].length;
        }

        if (pos != dataLength) {
            throw new IllegalStateException("Flat data length miscalculated");
        }

        Deflater deflater = new Deflater(COMPRESSION_LEVEL);
        deflater.setInput(uncompressedDataFlat);
        deflater.finish();

        byte[] compressedDataFlat = new byte[uncompressedDataFlat.length + 1024];

        int compressed = deflater.deflate(compressedDataFlat);
        try {
            if (compressed == 0) {
                throw new IOException("Not all bytes compressed.");
            }
        } finally {
            deflater.end();
        }

        buffer.writeInt(compressed);
        buffer.writeBytes(compressedDataFlat, 0, compressed);

        for (int i = 0; i < length; i++) {
            buffer.writeInt(message.getX()[i]);
            buffer.writeInt(message.getZ()[i]);
            buffer.writeShort(byteByteArrayToShort(message.getData()[i]));
            buffer.writeShort(booleanArrayToShort(message.hasAdditionalData()[i]));
        }

        return buffer;
    }

    private static short booleanArrayToShort(boolean[] array) {
        short s = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i]) {
                s |= 1 << i;
            }
        }
        return s;
    }

    private static boolean[] shortToBooleanArray(short s) {
        boolean[] array = new boolean[16];
        for (int i = 0; i < 16; i++) {
            if ((s & (1 << i)) != 0) {
                array[i] = true;
            } else {
                array[i] = false;
            }
        }
        return array;
    }

    private static short byteByteArrayToShort(byte[][] array) {
        short s = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] != null) {
                s |= 1 << i;
            }
        }
        return s;
    }

    private static byte[][] shortToByteByteArray(short s, boolean[] add) {
        byte[][] array = new byte[16][];
        for (int i = 0; i < 16; i++) {
            if ((s & (1 << i)) != 0) {
                int length = 5 * Chunk.BLOCKS.HALF_VOLUME;
                if (add[i]) {
                    length += Chunk.BLOCKS.HALF_VOLUME;
                }
                array[i] = new byte[length];
            } else {
                array[i] = null;
            }
        }
        return array;
    }
}