123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- /*
- * This file is part of SpoutPlugin.
- *
- * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
- * SpoutPlugin is licensed under the GNU Lesser General Public License.
- *
- * SpoutPlugin 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.
- *
- * SpoutPlugin 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.gmail.nossr50.util.blockmeta;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
- import java.io.*;
- import java.util.BitSet;
- import java.util.zip.DeflaterOutputStream;
- import java.util.zip.InflaterInputStream;
- /**
- * File format:
- * bytes 0-4096 contain 1024 integer values representing the segment index of each chunk
- * bytes 4096-8192 contain 1024 integer values representing the byte length of each chunk
- * bytes 8192-8196 is the integer value of the segment exponent
- * bytes 8196-12288 are reserved for future use
- * bytes 12288+ contain the data segments, by default 1024 byte segments.
- * Chunk data is compressed and stored in 1 or more segments as needed.
- */
- public class McMMOSimpleRegionFile {
- private static final int DEFAULT_SEGMENT_EXPONENT = 10; // TODO, analyze real world usage and determine if a smaller segment(512) is worth it or not. (need to know average chunkstore bytesize)
- private static final int DEFAULT_SEGMENT_SIZE = (int)Math.pow(2, DEFAULT_SEGMENT_EXPONENT); // 1024
- private static final int RESERVED_HEADER_BYTES = 12288; // This needs to be divisible by segment size
- private static final int NUM_CHUNKS = 1024; // 32x32
- private static final int SEEK_CHUNK_SEGMENT_INDICES = 0;
- private static final int SEEK_CHUNK_BYTE_LENGTHS = 4096;
- private static final int SEEK_FILE_INFO = 8192;
- // Chunk info
- private final int[] chunkSegmentIndex = new int[NUM_CHUNKS];
- private final int[] chunkNumBytes = new int[NUM_CHUNKS];
- private final int[] chunkNumSegments = new int[NUM_CHUNKS];
- // Segments
- private final BitSet segments = new BitSet(); // Used to denote which segments are in use or not
- // Segment size/mask
- private final int segmentExponent;
- private final int segmentMask;
- // File location
- private final @NotNull File parent;
- // File access
- private final RandomAccessFile file;
- // Region index
- private final int rx;
- private final int rz;
- public McMMOSimpleRegionFile(@NotNull File f, int rx, int rz) {
- this.rx = rx;
- this.rz = rz;
- this.parent = f;
- try {
- this.file = new RandomAccessFile(parent, "rw");
- // New file, write out header bytes
- if (file.length() < RESERVED_HEADER_BYTES) {
- file.write(new byte[RESERVED_HEADER_BYTES]);
- file.seek(SEEK_FILE_INFO);
- file.writeInt(DEFAULT_SEGMENT_EXPONENT);
- }
- file.seek(SEEK_FILE_INFO);
- this.segmentExponent = file.readInt();
- this.segmentMask = (1 << segmentExponent) - 1;
- // Mark reserved segments reserved
- int reservedSegments = this.bytesToSegments(RESERVED_HEADER_BYTES);
- segments.set(0, reservedSegments, true);
- // Read chunk header data
- file.seek(SEEK_CHUNK_SEGMENT_INDICES);
- for (int i = 0; i < NUM_CHUNKS; i++)
- chunkSegmentIndex[i] = file.readInt();
- file.seek(SEEK_CHUNK_BYTE_LENGTHS);
- for (int i = 0; i < NUM_CHUNKS; i++) {
- chunkNumBytes[i] = file.readInt();
- chunkNumSegments[i] = bytesToSegments(chunkNumBytes[i]);
- markChunkSegments(i, true);
- }
- fixFileLength();
- }
- catch (IOException fnfe) {
- throw new RuntimeException(fnfe);
- }
- }
- public synchronized @NotNull DataOutputStream getOutputStream(int x, int z) {
- int index = getChunkIndex(x, z); // Get chunk index
- return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
- }
- private static class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
- final McMMOSimpleRegionFile rf;
- final int index;
- McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
- super(DEFAULT_SEGMENT_SIZE);
- this.rf = rf;
- this.index = index;
- }
- @Override
- public void close() throws IOException {
- rf.write(index, buf, count);
- }
- }
- private synchronized void write(int index, byte[] buffer, int size) throws IOException {
- int oldSegmentIndex = chunkSegmentIndex[index]; // Get current segment index
- markChunkSegments(index, false); // Clear our old segments
- int newSegmentIndex = findContiguousSegments(oldSegmentIndex, size); // Find contiguous segments to save to
- file.seek(newSegmentIndex << segmentExponent); // Seek to file location
- file.write(buffer, 0, size); // Write data
- // update in memory info
- chunkSegmentIndex[index] = newSegmentIndex;
- chunkNumBytes[index] = size;
- chunkNumSegments[index] = bytesToSegments(size);
- // Mark segments in use
- markChunkSegments(index, true);
- // Update header info
- file.seek(SEEK_CHUNK_SEGMENT_INDICES + (4 * index));
- file.writeInt(chunkSegmentIndex[index]);
- file.seek(SEEK_CHUNK_BYTE_LENGTHS + (4 * index));
- file.writeInt(chunkNumBytes[index]);
- }
- public synchronized @Nullable DataInputStream getInputStream(int x, int z) throws IOException {
- int index = getChunkIndex(x, z); // Get chunk index
- int byteLength = chunkNumBytes[index]; // Get byte length of data
- // No bytes
- if (byteLength == 0)
- return null;
- byte[] data = new byte[byteLength];
- file.seek(chunkSegmentIndex[index] << segmentExponent); // Seek to file location
- file.readFully(data); // Read in the data
- return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
- }
- public synchronized void close() {
- try {
- file.close();
- segments.clear();
- }
- catch (IOException ioe) {
- throw new RuntimeException("Unable to close file", ioe);
- }
- }
- private synchronized void markChunkSegments(int index, boolean inUse) {
- // No bytes used
- if (chunkNumBytes[index] == 0)
- return;
- int start = chunkSegmentIndex[index];
- int end = start + chunkNumSegments[index];
- // If we are writing, assert we don't write over any in-use segments
- if (inUse)
- {
- int nextSetBit = segments.nextSetBit(start);
- if (nextSetBit != -1 && nextSetBit < end)
- throw new IllegalStateException("Attempting to overwrite an in-use segment");
- }
- segments.set(start, end, inUse);
- }
- private synchronized void fixFileLength() throws IOException {
- int fileLength = (int)file.length();
- int extend = -fileLength & segmentMask; // how many bytes do we need to be divisible by segment size
- // Go to end of file
- file.seek(fileLength);
- // Append bytes
- file.write(new byte[extend], 0, extend);
- }
- private synchronized int findContiguousSegments(int hint, int size) {
- if (size == 0)
- return 0; // Zero byte data will not claim any chunks anyways
- int segments = bytesToSegments(size); // Number of segments we need
- // Check the hinted location (previous location of chunk) most of the time we can fit where we were.
- boolean oldFree = true;
- for (int i = hint; i < this.segments.size() && i < hint + segments; i++) {
- if (this.segments.get(i)) {
- oldFree = false;
- break;
- }
- }
- // We fit!
- if (oldFree)
- return hint;
- // Find somewhere to put us
- int start = 0;
- int current = 0;
- while (current < this.segments.size()) {
- boolean segmentInUse = this.segments.get(current); // check if segment is in use
- current++; // Move up a segment
- // Move up start if the segment was in use
- if (segmentInUse)
- start = current;
- // If we have enough segments now, return
- if (current - start >= segments)
- return start;
- }
- // Return the end of the segments (will expand to fit them)
- return start;
- }
- private synchronized int bytesToSegments(int bytes) {
- if (bytes <= 0)
- return 1;
- return ((bytes - 1) >> segmentExponent) + 1; // ((bytes - 1) / segmentSize) + 1
- }
- private synchronized int getChunkIndex(int x, int z) {
- if (rx != (x >> 5) || rz != (z >> 5))
- throw new IndexOutOfBoundsException();
- x = x & 0x1F; // 5 bits (mod 32)
- z = z & 0x1F; // 5 bits (mod 32)
- return (x << 5) + z; // x in the upper 5 bits, z in the lower 5 bits
- }
- }
|