Package org.getspout.spoutapi.chunkstore

Source Code of org.getspout.spoutapi.chunkstore.ChunkMetaData

/*
* This file is part of SpoutcraftPlugin.
*
* Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org//>
* SpoutcraftPlugin is licensed under the GNU Lesser General Public License.
*
* SpoutcraftPlugin 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.
*
* SpoutcraftPlugin 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 org.getspout.spoutapi.chunkstore;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.UUID;

import gnu.trove.iterator.TIntObjectIterator;

import org.getspout.spoutapi.Spout;
import org.getspout.spoutapi.SpoutWorld;
import org.getspout.spoutapi.chunkstore.Utils.SerializedData;
import org.getspout.spoutapi.inventory.ItemMap;
import org.getspout.spoutapi.inventory.MaterialManager;
import org.getspout.spoutapi.util.map.TByteShortByteKeyedMap;
import org.getspout.spoutapi.util.map.TByteShortByteKeyedObjectHashMap;

public class ChunkMetaData implements Serializable {
  // Field serialization only
  private static final long serialVersionUID = 3L;

  // This data is saved. This means data can handle different map heights
  // Changes may be needed to the positionToKey method
  private int cx;
  private int cz;
  private UUID worldUid;
  // Storage for objects saved to this chunk
  private HashMap<String, Serializable> chunkData;
  // Storage for custom block IDs
  private short[] customBlockIds = null;
  // Storage for custom block rotations
  private byte[] customBlockData = null;
  // Storage for local block data
  private TByteShortByteKeyedObjectHashMap<HashMap<String, Serializable>> blockData;
  private static final int CURRENT_VERSION = 4;
  private static final int MAGIC_NUMBER = 0xEA5EDEBB;
  transient private boolean dirty = false;
  // Quais-final, need to be set in serialization
  transient private int worldHeight;
  transient private int worldHeightMinusOne;
  transient private int xBitShifts;
  transient private int zBitShifts;
  transient private ItemMap worldItemMap;
  transient private ItemMap serverItemMap;
  transient private boolean conversionNeeded;

  ChunkMetaData(UUID worldId, ItemMap worldItemMap, int cx, int cz) {
    blockData = new TByteShortByteKeyedObjectHashMap<HashMap<String, Serializable>>(100);
    chunkData = new HashMap<String, Serializable>();

    this.cx = cx;
    this.cz = cz;
    this.worldUid = worldId;

    SpoutWorld world = Spout.getServer().getWorld(this.worldUid);

    this.worldHeight = world != null ? world.getMaxHeight() : 128;
    this.xBitShifts = world != null ? world.getXBitShifts() : 11;
    this.zBitShifts = world != null ? world.getZBitShifts() : 7;
    worldHeightMinusOne = worldHeight - 1;

    this.worldItemMap = worldItemMap;
    this.serverItemMap = ItemMap.getRootMap();
    conversionNeeded = false;
  }

  /**
   * True if this chunk's data has been altered and needs to be serialized into storage
   * @return dirty
   */
  public boolean isDirty() {
    return dirty;
  }

  /**
   * Sets this chunk's data dirty flag
   * @param dirty
   */
  public void setDirty(boolean dirty) {
    this.dirty = dirty;
  }

  public int getChunkX() {
    return cx;
  }

  public int getChunkZ() {
    return cz;
  }

  public UUID getWorldUID() {
    return worldUid;
  }

  /**
   * Removes the data associated with the id at this chunk
   * @param id of data
   * @return data removed
   */
  public Serializable removeChunkData(String id) {
    Serializable serial = chunkData.remove(id);
    if (serial != null) {
      dirty = true;
      return serial;
    }
    return null;
  }

  /**
   * Gets the data associated with the id at this chunk.
   * <p/>
   * If the data is still in a serialized form, this will deserialize it.
   * @param id of data
   * @return data at the given id, or null if none found
   */
  public Serializable getChunkData(String id) {
    Serializable serial = chunkData.get(id);
    if (serial != null && serial instanceof SerializedData) {
      try {
        serial = Utils.deserializeRaw(((SerializedData) serial).serialData);
        chunkData.put(id, serial);
      } catch (ClassNotFoundException e) {
        return null;
      } catch (IOException e) {
        return null;
      }
    }
    return serial;
  }

  public Serializable putChunkData(String id, Serializable o) {
    Serializable serial = chunkData.put(id, o);
    dirty = true;
    return serial;
  }

  /**
   * Returns the array that is backing the block id data for this chunk.
   * <p/>
   * If the contents of the array are altered, setDirty(true) must be used so that the updated contents will be saved.
   * Alternatively, use setCustomBlockIds(array) when you are finished manipulating the array and it will set the dirty flag for you.
   * @return array of block id data for this chunk
   */
  public short[] getCustomBlockIds() {
    return customBlockIds;
  }

  /**
   * Sets the array that is used for the block id data for this chunk.
   * <p/>
   * This array will <b>override</b> any existing data, and wipe it out, so be sure this is what you intend to do.
   * @param ids to set
   */
  public void setCustomBlockIds(short[] ids) {
    customBlockIds = ids;
    setDirty(true);
  }

  public byte[] getCustomBlockData() {
    return customBlockData;
  }

  public void setCustomBlockData(byte[] rots) {
    customBlockData = rots;
    setDirty(true);
  }

  public Serializable removeBlockData(String id, int x, int y, int z) {
    if (id.equals(MaterialManager.blockIdString)) {
      if (customBlockIds != null) {
        int key = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne);
        short old = customBlockIds[key];
        if (old != 0) {
          dirty = true;
          customBlockIds[key] = 0;
        }
        return old;
      }
    } else {
      HashMap<String, Serializable> localBlockData = blockData.get(x, y, z);
      if (localBlockData != null) {
        Serializable old = localBlockData.remove(id);
        if (old != null) {
          dirty = true;
          if (localBlockData.size() == 0) {
            blockData.remove(x, y, z);
          }
        }
        return old;
      }
    }
    return null;
  }

  public Serializable getBlockData(String id, int x, int y, int z) {
    if (id.equals(MaterialManager.blockIdString)) {
      if (customBlockIds != null) {
        int key = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne);
        return customBlockIds[key];
      }
    } else {
      HashMap<String, Serializable> localBlockData = blockData.get(x, y, z);
      if (localBlockData != null) {
        Serializable serial = localBlockData.get(id);
        // Check if we need to deserialize it
        if (serial != null && serial instanceof SerializedData) {
          try {
            serial = Utils.deserializeRaw(((SerializedData) serial).serialData);
            localBlockData.put(id, serial);
          } catch (ClassNotFoundException e) {
          } catch (IOException e) {
          }
        }

        return serial;
      }
    }
    return null;
  }

  public Serializable putBlockData(String id, int x, int y, int z, Serializable o) {
    if (id.equals(MaterialManager.blockIdString)) {
      if (customBlockIds == null) {
        customBlockIds = new short[16 * 16 * worldHeight];
      }
      int key = ((x & 0xF) << xBitShifts) | ((z & 0xF) << zBitShifts) | (y & worldHeightMinusOne);
      customBlockIds[key] = ((Integer) o).shortValue();
      dirty = true;
    } else {
      HashMap<String, Serializable> localBlockData = blockData.get(x, y, z);
      if (localBlockData == null) {
        localBlockData = new HashMap<String, Serializable>();
        blockData.put(x, y, z, localBlockData);
      }
      localBlockData.put(id, o);
      dirty = true;
    }

    return o;
  }

  private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeInt(MAGIC_NUMBER);
    out.writeInt(CURRENT_VERSION);

    out.writeLong(worldUid.getLeastSignificantBits());
    out.writeLong(worldUid.getMostSignificantBits());
    out.writeInt(cx);
    out.writeInt(cz);
    if (customBlockIds != null) {
      out.writeBoolean(true);
      for (int i = 0; i < (16 * 16 * worldHeight); i++) {
        Integer worldId = worldItemMap.convertFrom(this.serverItemMap, customBlockIds[i]);
        if (worldId == null) {
          worldId = 0;
        }
        out.writeShort(worldId);
      }
      worldItemMap.save();
      serverItemMap.save();
    } else {
      out.writeBoolean(false);
    }
    out.writeInt(blockData != null ? blockData.size() : 0);
    if (blockData != null) {
      TIntObjectIterator<HashMap<String, Serializable>> i = blockData.iterator();
      while (i.hasNext()) {
        i.advance();
        int key = i.key();
        byte x = TByteShortByteKeyedMap.getXFromKey(key);
        short y = TByteShortByteKeyedMap.getYFromKey(key);
        byte z = TByteShortByteKeyedMap.getZFromKey(key);
        out.writeByte(x);
        out.writeShort(y);
        out.writeByte(z);
        writeMap(out, i.value());
      }
    }
    if (customBlockData != null) {
      out.writeBoolean(true);
      out.write(customBlockData);
    } else {
      out.writeBoolean(false);
    }
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    blockData = new TByteShortByteKeyedObjectHashMap<HashMap<String, Serializable>>(100);
    chunkData = new HashMap<String, Serializable>();

    int fileVersionNumber; // Can be used to determine the format of the file

    long lsb = in.readLong();
    if (((int) (lsb >> 32)) == MAGIC_NUMBER) {
      fileVersionNumber = (int) lsb;
      lsb = in.readLong();
    } else {
      fileVersionNumber = 0;
    }

    long msb = in.readLong();
    worldUid = new UUID(msb, lsb);
    cx = in.readInt();
    cz = in.readInt();
    boolean customBlockIdsExist = in.readBoolean();

    // Constructor is not invoked, need to set these fields
    SpoutWorld world = Spout.getServer().getWorld(this.worldUid);

    this.worldHeight = world.getMaxHeight();
    this.xBitShifts = world.getXBitShifts();
    this.zBitShifts = world.getZBitShifts();
    worldHeightMinusOne = worldHeight - 1;

    if (customBlockIdsExist) {
      if (fileVersionNumber >= 2) {
        conversionNeeded = true;
      }

      customBlockIds = new short[16 * 16 * worldHeight];
      int size = (16 * 16 * worldHeight);
      if (fileVersionNumber < 3) {
        size = 16 * 16 * 128;
      }
      for (int i = 0; i < size; i++) {
        if (fileVersionNumber > 2) {
          customBlockIds[i] = in.readShort();
        } else {
          int oldX = (i >> 11) & 0xF;
          int oldY = i & 0x7F;
          int oldZ = (i >> 7) & 0xF;
          int newKey = ((oldX & 0xF) << 12) | ((oldZ & 0xF) << 8) | (oldY & 0xFF);
          customBlockIds[newKey] = in.readShort();
        }
      }
    }
    int size = in.readInt();
    for (int i = 0; i < size; i++) {
      int x = in.readByte();
      int y = in.readShort();
      int z = in.readByte();
      HashMap<String, Serializable> map = readMap(in);
      blockData.put(x, y, z, map);
    }

    if (fileVersionNumber >= 4) {
      boolean hasRotations = in.readBoolean();
      customBlockData = new byte[16 * 16 * worldHeight];
      if (hasRotations) {
        in.readFully(customBlockData);
      }
    }

    if (fileVersionNumber < CURRENT_VERSION) {
      dirty = true;
    }
  }

  public void setWorldItemMap(ItemMap worldItemMap) {
    this.serverItemMap = ItemMap.getRootMap();
    this.worldItemMap = worldItemMap;
    if (conversionNeeded) {
      convertIds(worldItemMap);
    }
  }

  private void convertIds(ItemMap worldItemMap) {
    int length = customBlockIds.length;
    for (int i = 0; i < length; i++) {
      Integer globalId = worldItemMap.convertTo(serverItemMap, customBlockIds[i]);

      if (globalId == null) {
        System.out.println("Custom ID " + customBlockIds[i] + " does not exist in custom item map, replacing with 0");
        globalId = 0;
      }
      customBlockIds[i] = (short) (int) globalId;
    }
    conversionNeeded = false;
  }

  private void writeMap(ObjectOutputStream out, HashMap<String, Serializable> map) throws IOException {
    if (map == null) {
      out.writeBoolean(false);
      return;
    } else {
      out.writeBoolean(true);
    }

    out.writeObject(map);
  }

  @SuppressWarnings("unchecked")
  private HashMap<String, Serializable> readMap(ObjectInputStream in) throws IOException {
    if (!in.readBoolean()) {
      return null;
    }

    HashMap<String, Serializable> map = new HashMap<String, Serializable>();

    try {
      map = (HashMap<String, Serializable>) in.readObject();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (OptionalDataException ode) {
      if (ode.eof) {
        throw new RuntimeException("EOF reached", ode);
      } else {
        throw new RuntimeException("Primitive data in object stream of length " + ode.length, ode);
      }
    }

    return map;
  }
}
TOP

Related Classes of org.getspout.spoutapi.chunkstore.ChunkMetaData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.