/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.world.chunks.blockdata;
import org.terasology.world.chunks.deflate.TeraVisitingDeflator;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* TeraArray is the base class used to store block related data in Chunks.
*
*/
public abstract class TeraArray {
private int sizeX;
private int sizeY;
private int sizeZ;
private int sizeXZ;
private int sizeXZHalf;
private int sizeXYZ;
private int sizeXYZHalf;
protected TeraArray() {
}
protected TeraArray(int sizeX, int sizeY, int sizeZ, boolean initialize) {
checkArgument(sizeX > 0);
checkArgument(sizeY > 0);
checkArgument(sizeZ > 0);
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
sizeXZ = sizeX * sizeZ;
sizeXZHalf = sizeXZ / 2;
sizeXYZ = sizeY * sizeXZ;
sizeXYZHalf = sizeXYZ / 2;
checkArgument(getSizeXYZ() % 2 == 0, "The product of the parameters 'sizeX', 'sizeY' and 'sizeZ' has to be a multiple of 2 (" + getSizeXYZ() + ")");
checkArgument(getSizeXZ() % 2 == 0, "The product of the parameters 'sizeX' and 'sizeZ' has to be a multiple of 2 (" + getSizeXZ() + ")");
if (initialize) {
initialize();
}
}
protected final void writeExternalHeader(ObjectOutput out) throws IOException {
out.writeInt(sizeX);
out.writeInt(sizeY);
out.writeInt(sizeZ);
}
protected final void readExternalHeader(ObjectInput in) throws IOException {
sizeX = in.readInt();
sizeY = in.readInt();
sizeZ = in.readInt();
sizeXZ = sizeX * sizeZ;
sizeXZHalf = sizeXZ / 2;
sizeXYZ = sizeY * sizeXZ;
sizeXYZHalf = sizeXYZ / 2;
}
protected final int pos(int x, int y, int z) {
return y * getSizeXZ() + z * getSizeX() + x;
}
protected final int pos(int x, int z) {
return z * getSizeX() + x;
}
protected abstract void initialize();
public final int getSizeX() {
return sizeX;
}
public final int getSizeY() {
return sizeY;
}
public final int getSizeZ() {
return sizeZ;
}
public final int getSizeXZ() {
return sizeXZ;
}
public final int getSizeXZHalf() {
return sizeXZHalf;
}
public final int getSizeXYZ() {
return sizeXYZ;
}
public final int getSizeXYZHalf() {
return sizeXYZHalf;
}
public final boolean contains(int x, int y, int z) {
return (x >= 0 && x < sizeX && y >= 0 && y < sizeY && z >= 0 && z < sizeZ);
}
@Override
public String toString() {
return getClass().getName() + "(" + getSizeX() + ", " + getSizeY() + ", " + getSizeZ() + ", "
+ (isSparse() ? "sparse" : "dense") + ", " + getElementSizeInBits() + "bit, " + getEstimatedMemoryConsumptionInBytes() + "byte)";
}
public abstract boolean isSparse();
public abstract TeraArray copy();
public abstract TeraArray deflate(TeraVisitingDeflator deflator);
public abstract int getEstimatedMemoryConsumptionInBytes();
public abstract int getElementSizeInBits();
public abstract int get(int x, int y, int z);
public abstract int set(int x, int y, int z, int value);
public abstract boolean set(int x, int y, int z, int value, int expected);
/**
* This is the interface for tera array factories. Every tera array is required to implement a factory.
* It should be implemented as a static subclass of the corresponding tera array class and it should be called Factory.
*
* @see org.terasology.world.chunks.blockdata.TeraDenseArray16Bit.Factory
*/
public interface Factory<T extends TeraArray> {
Class<T> getArrayClass();
SerializationHandler<T> createSerializationHandler();
T create();
T create(int sizeX, int sizeY, int sizeZ);
}
/**
* This is the interface for serialization handlers for tera arrays. Every tera array is required to implement
* a serialization handler. It is recommended to subclass
* {@link org.terasology.world.chunks.blockdata.TeraArray.BasicSerializationHandler TeraArray.BasicSerializationHandler}
* instead of using this interface directly. It should be implemented as a static subclass of the corresponding tera array class.
*
* @see org.terasology.world.chunks.blockdata.TeraArray.BasicSerializationHandler
*/
public interface SerializationHandler<T extends TeraArray> {
int computeMinimumBufferSize(T array);
ByteBuffer serialize(T array);
ByteBuffer serialize(T array, ByteBuffer toBuffer);
T deserialize(ByteBuffer buffer);
boolean canHandle(Class<?> clazz);
}
/**
* Extending this class is the recommended way to implement serialization handlers for tera arrays.
* Tera arrays should implement their serialization handlers as a static subclass called SerializationHandler.
*
* @see org.terasology.world.chunks.blockdata.TeraDenseArray16Bit.SerializationHandler
* @see org.terasology.world.chunks.blockdata.TeraDenseArray16Bit.Factory
*/
protected abstract static class BasicSerializationHandler<T extends TeraArray> implements SerializationHandler<T> {
protected abstract int internalComputeMinimumBufferSize(T array);
protected abstract void internalSerialize(T array, ByteBuffer buffer);
protected abstract T internalDeserialize(int sizeX, int sizeY, int sizeZ, ByteBuffer buffer);
@Override
public final int computeMinimumBufferSize(T array) {
checkNotNull(array, "The parameter 'array' must not be null");
return 16 + internalComputeMinimumBufferSize(array);
}
@Override
public final ByteBuffer serialize(T array) {
checkNotNull(array, "The parameter 'array' must not be null");
return serialize(array, ByteBuffer.allocateDirect(computeMinimumBufferSize(array)));
}
@Override
public final ByteBuffer serialize(T array, ByteBuffer toBuffer) {
checkNotNull(array, "The parameter 'array' must not be null");
checkNotNull(toBuffer, "The parameter 'toBuffer' must not be null");
checkArgument(canHandle(array.getClass()), "Unable to handle the supplied array (" + array.getClass().getName() + ")");
final int lengthPos = toBuffer.position();
toBuffer.putInt(0);
toBuffer.putInt(array.getSizeX());
toBuffer.putInt(array.getSizeY());
toBuffer.putInt(array.getSizeZ());
internalSerialize(array, toBuffer);
toBuffer.putInt(lengthPos, toBuffer.position() - lengthPos - 4);
return toBuffer;
}
@Override
public final T deserialize(ByteBuffer buffer) {
checkNotNull(buffer, "The parameter 'buffer' must not be null");
final int length = buffer.getInt();
if (buffer.remaining() < length) {
throw new BufferUnderflowException();
}
final int sizeX = buffer.getInt();
final int sizeY = buffer.getInt();
final int sizeZ = buffer.getInt();
return internalDeserialize(sizeX, sizeY, sizeZ, buffer);
}
}
}