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

Java tutorial

Introduction

Here is the source code for org.spout.vanilla.protocol.codec.world.chunk.ChunkDataCodec.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.Session;
import org.spout.api.protocol.reposition.NullRepositionManager;

import org.spout.vanilla.protocol.VanillaProtocol;
import org.spout.vanilla.protocol.msg.world.chunk.ChunkDataMessage;

public final class ChunkDataCodec extends MessageCodec<ChunkDataMessage> {
    private static final int COMPRESSION_LEVEL = Deflater.BEST_SPEED;
    private static final int MAX_SECTIONS = 16;
    private final byte[] UNLOAD_COMPRESSED = { 0x78, (byte) 0x9C, 0x63, 0x64, 0x1C, (byte) 0xD9, 0x00, 0x00,
            (byte) 0x81, (byte) 0x80, 0x01, 0x01 }; //Fake compressed data, client expects this when unloading

    public ChunkDataCodec() {
        super(ChunkDataMessage.class, 0x33);
    }

    @Override
    public ChunkDataMessage decode(ByteBuf buffer) throws IOException {
        int x = buffer.readInt();
        int z = buffer.readInt();
        boolean contiguous = buffer.readByte() == 1;

        short primaryBitMap = buffer.readShort();
        short addBitMap = buffer.readShort();
        int compressedSize = buffer.readInt();
        byte[] compressedData = new byte[compressedSize];
        buffer.readBytes(compressedData);

        boolean[] hasAdditionalData = new boolean[MAX_SECTIONS];
        byte[][] data = new byte[MAX_SECTIONS][];

        int size = 0;
        for (int i = 0; i < MAX_SECTIONS; ++i) {
            if ((primaryBitMap & 1 << i) != 0) { // This chunk exists! Let's initialize the data for it.
                int sectionSize = Chunk.BLOCKS.HALF_VOLUME * 5;
                if ((addBitMap & 1 << i) != 0) {
                    hasAdditionalData[i] = true;
                    sectionSize += Chunk.BLOCKS.HALF_VOLUME;
                }

                data[i] = new byte[sectionSize];
                size += sectionSize;
            }
        }

        if (contiguous) {
            size += Chunk.BLOCKS.AREA;
        }

        byte[] uncompressedData = new byte[size];

        Inflater inflater = new Inflater();
        inflater.setInput(compressedData);
        inflater.getRemaining();
        int index = 0;
        try {
            while (index < uncompressedData.length) {
                int uncompressed = inflater.inflate(uncompressedData, index, uncompressedData.length - index);
                index += uncompressed;
                if (uncompressed == 0) {
                    break;
                }
            }
            if (index != uncompressedData.length) {
                throw new IOException("Not all bytes uncompressed.");
            }
        } catch (DataFormatException e) {
            e.printStackTrace();
            throw new IOException("Bad compressed data.");
        } finally {
            inflater.end();
        }

        size = 0;
        // TODO - fix this total hack
        size = readSectionData(uncompressedData, size, data, 0, 4096);
        size = readSectionData(uncompressedData, size, data, 2 * 2048, 2048);
        size = readSectionData(uncompressedData, size, data, 3 * 2048, 2048);
        size = readSectionData(uncompressedData, size, data, 4 * 2048, 2048);

        /*size = 0;
        for (byte[] sectionData : data) {
           if (sectionData != null && sectionData.length + size < uncompressedData.length) {
        System.arraycopy(uncompressedData, size, sectionData, 0, sectionData.length);
        size += sectionData.length;
           }
        }*/
        byte[] biomeData = new byte[Chunk.BLOCKS.AREA];

        if (contiguous) {
            System.arraycopy(uncompressedData, size, biomeData, 0, biomeData.length);
            size += biomeData.length;
        }

        return new ChunkDataMessage(x, z, contiguous, hasAdditionalData, data, biomeData, null,
                NullRepositionManager.getInstance());
    }

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

        buffer.writeInt(message.getX());
        buffer.writeInt(message.getZ());
        buffer.writeByte(message.isContiguous() ? 1 : 0);
        if (message.shouldUnload()) {
            buffer.writeShort(0);
            buffer.writeShort(0);
            buffer.writeInt(UNLOAD_COMPRESSED.length);
            buffer.writeBytes(UNLOAD_COMPRESSED);
            return buffer;
        }
        short sectionsSentBitmap = 0;
        short additionalDataBitMap = 0;

        byte[][] data = message.getData();

        int uncompressedSize = 0;
        for (int i = 0; i < MAX_SECTIONS; ++i) {
            if (data[i] != null) { // This chunk exists! Let's initialize the data for it.
                sectionsSentBitmap |= 1 << i;
                if (message.hasAdditionalData()[i]) {
                    additionalDataBitMap |= 1 << i;
                }
                uncompressedSize += data[i].length;
            }
        }

        if (message.isContiguous()) {
            uncompressedSize += message.getBiomeData().length;
        }

        buffer.writeShort(sectionsSentBitmap);
        buffer.writeShort(additionalDataBitMap);
        byte[] uncompressedData = new byte[uncompressedSize];
        int index = 0;

        // TODO - fix this total hack
        index = writeSectionData(data, 0, uncompressedData, index, 4096);
        index = writeSectionData(data, 2 * 2048, uncompressedData, index, 2048);
        index = writeSectionData(data, 3 * 2048, uncompressedData, index, 2048);
        index = writeSectionData(data, 4 * 2048, uncompressedData, index, 2048);

        if (message.isContiguous()) {
            System.arraycopy(message.getBiomeData(), 0, uncompressedData, index, message.getBiomeData().length);
            index += message.getBiomeData().length;
        }

        Session session = message.getSession();
        if (session != null) {
            uncompressedData = message.getSession().getDataMap().get(VanillaProtocol.CHUNK_NET_CACHE)
                    .handle(uncompressedData);
        }

        byte[] compressedData = new byte[uncompressedSize >> 2];

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

        int compressed;
        try {
            compressed = deflater.deflate(compressedData);
            if (compressed == 0) {
                throw new IOException("No compressed data found");
            } else if (compressed == compressedData.length) {
                boolean done = false;
                while (!done) {
                    byte[] newCompressedData = new byte[1 + compressedData.length + (compressedData.length >> 1)];
                    System.arraycopy(compressedData, 0, newCompressedData, 0, compressedData.length);
                    compressedData = newCompressedData;
                    compressed += deflater.deflate(compressedData, compressed, compressedData.length - compressed);
                    done = compressed < compressedData.length;
                }
            }
        } finally {
            deflater.end();
        }

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

        return buffer;
    }

    private int readSectionData(byte[] data, int off, byte[][] target, int targetOff, int len) {
        for (byte[] sectionTarget : target) {
            if (sectionTarget != null) {
                for (int i = targetOff; i < targetOff + len && i < sectionTarget.length; ++i) {
                    sectionTarget[i] = data[off++];
                }
            }
        }
        return off;
    }

    private int writeSectionData(byte[][] data, int off, byte[] target, int targetOff, int len) {
        for (byte[] sectionData : data) {
            if (sectionData != null) {
                int j = off;
                for (int i = 0; i < len; i++) {
                    target[targetOff++] = sectionData[j++];
                }
            }
        }
        return targetOff;
    }
}