Proper NBT streaming + random access optimizations
This commit is contained in:
parent
78bb55380d
commit
ef06ca3b19
49
core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java
Normal file
49
core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java
Normal file
@ -0,0 +1,49 @@
|
||||
package com.boydti.fawe.jnbt;
|
||||
|
||||
import com.boydti.fawe.object.RunnableVal2;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class NBTStreamer {
|
||||
private final NBTInputStream is;
|
||||
private final HashMap<String, RunnableVal2> readers;
|
||||
|
||||
public NBTStreamer(NBTInputStream stream) {
|
||||
this.is = stream;
|
||||
readers = new HashMap<>();
|
||||
}
|
||||
|
||||
public void readFully() throws IOException {
|
||||
is.readNamedTagLazy(new RunnableVal2<String, RunnableVal2>() {
|
||||
@Override
|
||||
public void run(String node, RunnableVal2 result) {
|
||||
this.value2 = readers.get(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public <T, V> void addReader(String node, RunnableVal2<T, V> run) {
|
||||
if (run instanceof NBTStreamReader) {
|
||||
((NBTStreamReader) run).init(node);
|
||||
}
|
||||
readers.put(node, run);
|
||||
}
|
||||
|
||||
public <T, V> void addReader(RunnableVal2<T, V> run, String... nodes) {
|
||||
for (String node : nodes) {
|
||||
addReader(node, run);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class NBTStreamReader<T, V> extends RunnableVal2<T, V> {
|
||||
private String node;
|
||||
public void init(String node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public String getNode() {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
175
core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java
Normal file
175
core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java
Normal file
@ -0,0 +1,175 @@
|
||||
package com.boydti.fawe.jnbt;
|
||||
|
||||
import com.boydti.fawe.object.RunnableVal2;
|
||||
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.ListTag;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.entity.BaseEntity;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SchematicStreamer extends NBTStreamer {
|
||||
private final UUID uuid;
|
||||
|
||||
public SchematicStreamer(NBTInputStream stream, UUID uuid) throws IOException {
|
||||
super(stream);
|
||||
this.uuid = uuid;
|
||||
addReader("Schematic.Height", new RunnableVal2<Integer, Short>() {
|
||||
@Override
|
||||
public void run(Integer index, Short value) {
|
||||
height = (value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.Width", new RunnableVal2<Integer, Short>() {
|
||||
@Override
|
||||
public void run(Integer index, Short value) {
|
||||
width = (value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.Length", new RunnableVal2<Integer, Short>() {
|
||||
@Override
|
||||
public void run(Integer index, Short value) {
|
||||
length = (value);
|
||||
}
|
||||
});
|
||||
final AtomicInteger originX = new AtomicInteger();
|
||||
final AtomicInteger originY = new AtomicInteger();
|
||||
final AtomicInteger originZ = new AtomicInteger();
|
||||
addReader("Schematic.WEOriginX", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
originX.set(value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.WEOriginY", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
originY.set(value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.WEOriginZ", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
originZ.set(value);
|
||||
}
|
||||
});
|
||||
final AtomicInteger offsetX = new AtomicInteger();
|
||||
final AtomicInteger offsetY = new AtomicInteger();
|
||||
final AtomicInteger offsetZ = new AtomicInteger();
|
||||
addReader("Schematic.WEOffsetX", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
offsetX.set(value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.WEOffsetY", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
offsetY.set(value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.WEOffsetZ", new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer index, Integer value) {
|
||||
offsetZ.set(value);
|
||||
}
|
||||
});
|
||||
// Blocks
|
||||
RunnableVal2<Integer, Integer> initializer = new RunnableVal2<Integer, Integer>() {
|
||||
@Override
|
||||
public void run(Integer length, Integer type) {
|
||||
setupClipboard(length);
|
||||
}
|
||||
};
|
||||
addReader("Schematic.Blocks.?", initializer);
|
||||
addReader("Schematic.Data.?", initializer);
|
||||
addReader("Schematic.AddBlocks.?", initializer);
|
||||
addReader("Schematic.Blocks.#", new RunnableVal2<Integer, Byte>() {
|
||||
int i;
|
||||
@Override
|
||||
public void run(Integer index, Byte value) {
|
||||
fc.setId(i++, value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.Data.#", new RunnableVal2<Integer, Byte>() {
|
||||
int i;
|
||||
@Override
|
||||
public void run(Integer index, Byte value) {
|
||||
fc.setData(i++, value);
|
||||
}
|
||||
});
|
||||
addReader("Schematic.AddBlocks.#", new RunnableVal2<Integer, Byte>() {
|
||||
int i;
|
||||
@Override
|
||||
public void run(Integer index, Byte value) {
|
||||
fc.setAdd(i++, value);
|
||||
}
|
||||
});
|
||||
// Tiles
|
||||
addReader("Schematic.TileEntities.#", new RunnableVal2<Integer, CompoundTag>() {
|
||||
@Override
|
||||
public void run(Integer index, CompoundTag value) {
|
||||
if (fc == null) {
|
||||
setupClipboard(0);
|
||||
}
|
||||
int x = value.getInt("x");
|
||||
int y = value.getInt("y");
|
||||
int z = value.getInt("z");
|
||||
fc.setTile(x, y, z, value);
|
||||
}
|
||||
});
|
||||
// Entities
|
||||
addReader("Schematic.Entities.#", new RunnableVal2<Integer, CompoundTag>() {
|
||||
@Override
|
||||
public void run(Integer index, CompoundTag compound) {
|
||||
if (fc == null) {
|
||||
setupClipboard(0);
|
||||
}
|
||||
String id = compound.getString("id");
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ListTag positionTag = compound.getListTag("Pos");
|
||||
ListTag directionTag = compound.getListTag("Rotation");
|
||||
BaseEntity state = new BaseEntity(id, compound);
|
||||
fc.createEntity(null, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state);
|
||||
}
|
||||
});
|
||||
readFully();
|
||||
Vector min = new Vector(originX.get(), originY.get(), originZ.get());
|
||||
Vector offset = new Vector(offsetX.get(), offsetY.get(), offsetZ.get());
|
||||
Vector origin = min.subtract(offset);
|
||||
Vector dimensions = new Vector(width, height, length);
|
||||
fc.setDimensions(dimensions);
|
||||
CuboidRegion region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
|
||||
clipboard = new BlockArrayClipboard(region, fc);
|
||||
clipboard.setOrigin(origin);
|
||||
}
|
||||
|
||||
private int height;
|
||||
private int width;
|
||||
private int length;
|
||||
|
||||
private Clipboard clipboard;
|
||||
private DiskOptimizedClipboard fc;
|
||||
|
||||
private DiskOptimizedClipboard setupClipboard(int size) {
|
||||
if (fc != null) {
|
||||
if (fc.getDimensions().getX() == 0) {
|
||||
fc.setDimensions(new Vector(size, 1, 1));
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
return fc = new DiskOptimizedClipboard(size, 1, 1, uuid);
|
||||
}
|
||||
|
||||
public Clipboard getClipboard() {
|
||||
return clipboard;
|
||||
}
|
||||
}
|
@ -244,6 +244,31 @@ public final class BufferedRandomAccessFile extends RandomAccessFile
|
||||
this.curr_ = pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek and do not flush if within the current buffer when going backwards
|
||||
* - Assumes no writes were made
|
||||
* @param pos
|
||||
* @throws IOException
|
||||
*/
|
||||
public void seekUnsafe(long pos) throws IOException
|
||||
{
|
||||
if (pos >= this.hi_ || pos < this.lo_)
|
||||
{
|
||||
// seeking outside of current buffer -- flush and read
|
||||
this.flushBuffer();
|
||||
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
|
||||
this.maxHi_ = this.lo_ + (long) this.buff_.length;
|
||||
if (this.diskPos_ != this.lo_)
|
||||
{
|
||||
super.seek(this.lo_);
|
||||
this.diskPos_ = this.lo_;
|
||||
}
|
||||
int n = this.fillBuffer();
|
||||
this.hi_ = this.lo_ + (long) n;
|
||||
}
|
||||
this.curr_ = pos;
|
||||
}
|
||||
|
||||
public long getFilePointer()
|
||||
{
|
||||
return this.curr_;
|
||||
@ -273,6 +298,23 @@ public final class BufferedRandomAccessFile extends RandomAccessFile
|
||||
return ((int) res) & 0xFF; // convert byte -> int
|
||||
}
|
||||
|
||||
public byte read1() throws IOException {
|
||||
if (this.curr_ >= this.hi_)
|
||||
{
|
||||
// test for EOF
|
||||
// if (this.hi < this.maxHi) return -1;
|
||||
if (this.hitEOF_)
|
||||
return -1;
|
||||
|
||||
// slow path -- read another buffer
|
||||
this.seek(this.curr_);
|
||||
if (this.curr_ == this.hi_)
|
||||
return -1;
|
||||
}
|
||||
byte res = this.buff_[(int) (this.curr_++ - this.lo_)];
|
||||
return res;
|
||||
}
|
||||
|
||||
public int read(byte[] b) throws IOException
|
||||
{
|
||||
return this.read(b, 0, b.length);
|
||||
|
@ -17,4 +17,9 @@ public abstract class RunnableVal2<T, U> implements Runnable {
|
||||
}
|
||||
|
||||
public abstract void run(T value1, U value2);
|
||||
|
||||
public RunnableVal2<T, U> runAndGet(T value1, U value2) {
|
||||
run(value1, value2);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -69,16 +69,17 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
long size = (raf.length() - HEADER_SIZE) >> 1;
|
||||
raf.seek(2);
|
||||
last = -1;
|
||||
raf.read(buffer);
|
||||
width = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF)));
|
||||
raf.read(buffer);
|
||||
height = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF)));
|
||||
raf.read(buffer);
|
||||
length = (((buffer[1] & 0xFF) << 8) + ((buffer[0] & 0xFF)));
|
||||
width = raf.readChar();
|
||||
height = raf.readChar();
|
||||
length = raf.readChar();
|
||||
area = width * length;
|
||||
autoCloseTask();
|
||||
}
|
||||
|
||||
public Vector getDimensions() {
|
||||
return new Vector(width, height, length);
|
||||
}
|
||||
|
||||
public BlockArrayClipboard toClipboard() {
|
||||
try {
|
||||
CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width - 1, height - 1, length - 1)) {
|
||||
@ -92,9 +93,9 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
}
|
||||
raf.seek(8);
|
||||
last = -1;
|
||||
int ox = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
|
||||
int oy = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
|
||||
int oz = (((byte) raf.read() << 8) | ((byte) raf.read()) & 0xFF);
|
||||
int ox = raf.readShort();
|
||||
int oy = raf.readShort();
|
||||
int oz = raf.readShort();
|
||||
BlockArrayClipboard clipboard = new BlockArrayClipboard(region, this);
|
||||
clipboard.setOrigin(new Vector(ox, oy, oz));
|
||||
return clipboard;
|
||||
@ -132,14 +133,29 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
}
|
||||
raf.seek(8);
|
||||
last = -1;
|
||||
raf.write((byte) (offset.getBlockX() >> 8));
|
||||
raf.write((byte) (offset.getBlockX()));
|
||||
raf.writeShort(offset.getBlockX());
|
||||
raf.writeShort(offset.getBlockY());
|
||||
raf.writeShort(offset.getBlockZ());
|
||||
} catch (IOException e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
raf.write((byte) (offset.getBlockY() >> 8));
|
||||
raf.write((byte) (offset.getBlockY()));
|
||||
|
||||
raf.write((byte) (offset.getBlockZ() >> 8));
|
||||
raf.write((byte) (offset.getBlockZ()));
|
||||
public void setDimensions(Vector dimensions) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
width = dimensions.getBlockX();
|
||||
height = dimensions.getBlockY();
|
||||
length = dimensions.getBlockZ();
|
||||
long size = width * height * length * 2l + HEADER_SIZE;
|
||||
raf.setLength(size);
|
||||
raf.seek(1);
|
||||
last = -0;
|
||||
raf.writeChar(width);
|
||||
raf.writeChar(height);
|
||||
raf.writeChar(length);
|
||||
} catch (IOException e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
@ -183,13 +199,10 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
raf.setLength(size);
|
||||
// write length etc
|
||||
raf.seek(1);
|
||||
last = 0;
|
||||
raf.write((width) & 0xff);
|
||||
raf.write(((width) >> 8) & 0xff);
|
||||
raf.write((height) & 0xff);
|
||||
raf.write(((height) >> 8) & 0xff);
|
||||
raf.write((length) & 0xff);
|
||||
raf.write(((length) >> 8) & 0xff);
|
||||
last = -1;
|
||||
raf.writeChar(width);
|
||||
raf.writeChar(height);
|
||||
raf.writeChar(length);
|
||||
}
|
||||
autoCloseTask();
|
||||
}
|
||||
@ -238,18 +251,12 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
}
|
||||
}
|
||||
raf.seek(i);
|
||||
raf.read(buffer);
|
||||
int id = ((((int) buffer[1] & 0xFF) << 4) + (((int) buffer[0] & 0xFF) >> 4));
|
||||
if (id == 0 && !air) {
|
||||
int combinedId = raf.readChar();
|
||||
if (combinedId == 0 && !air) {
|
||||
continue;
|
||||
}
|
||||
BaseBlock block;
|
||||
if (!FaweCache.hasData(id)) {
|
||||
block = FaweCache.CACHE_BLOCK[id << 4];
|
||||
} else {
|
||||
block = FaweCache.CACHE_BLOCK[(id << 4) + (buffer[0] & 0xF)];
|
||||
}
|
||||
if (FaweCache.hasNBT(id)) {
|
||||
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
|
||||
if (FaweCache.hasNBT(block.getId())) {
|
||||
CompoundTag nbt = nbtMap.get(new IntegerTrio((int) pos.x, (int) pos.y, (int) pos.z));
|
||||
if (nbt != null) {
|
||||
block = new BaseBlock(block.getId(), block.getData());
|
||||
@ -274,16 +281,10 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
raf.read(buffer);
|
||||
last = i;
|
||||
int id = ((((int) buffer[1] & 0xFF) << 4) + (((int) buffer[0] & 0xFF) >> 4));
|
||||
BaseBlock block;
|
||||
if (!FaweCache.hasData(id)) {
|
||||
block = FaweCache.CACHE_BLOCK[id << 4];
|
||||
} else {
|
||||
block = FaweCache.CACHE_BLOCK[(id << 4) + (buffer[0] & 0xF)];
|
||||
}
|
||||
if (FaweCache.hasNBT(id)) {
|
||||
int combinedId = raf.readChar();
|
||||
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
|
||||
if (FaweCache.hasNBT(block.getId())) {
|
||||
CompoundTag nbt = nbtMap.get(new IntegerTrio(x, y, z));
|
||||
if (nbt != null) {
|
||||
block = new BaseBlock(block.getId(), block.getData());
|
||||
@ -318,9 +319,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
final int id = block.getId();
|
||||
final int data = block.getData();
|
||||
int combined = (id << 4) + data;
|
||||
buffer[0] = (byte) ((combined) & 0xff);
|
||||
buffer[1] = (byte) (((combined) >> 8) & 0xFF);
|
||||
raf.write(buffer);
|
||||
raf.writeChar(combined);
|
||||
if (FaweCache.hasNBT(id)) {
|
||||
nbtMap.put(new IntegerTrio(x, y, z), block.getNbtData());
|
||||
}
|
||||
@ -331,6 +330,101 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setId(int i, int id) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
if (i != last + 1) {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
last = i;
|
||||
int combined = FaweCache.getData(raf.readChar()) + (id << 4);
|
||||
raf.seekUnsafe(raf.getFilePointer() - 2);
|
||||
raf.writeChar(combined);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setCombined(int i, int combined) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
if (i != last + 1) {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
last = i;
|
||||
raf.writeChar(combined);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setAdd(int i, int add) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
if (i != last + 1) {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
last = i;
|
||||
int combined = raf.readChar() + (add << 4);
|
||||
raf.seekUnsafe(raf.getFilePointer() - 2);
|
||||
raf.writeChar(combined);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getCombined(int i) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
if (i != last + 1) {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
last = i;
|
||||
return raf.readChar();
|
||||
} catch (Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean setData(int i, int data) {
|
||||
try {
|
||||
if (raf == null) {
|
||||
open();
|
||||
}
|
||||
if (i != last + 1) {
|
||||
raf.seek((HEADER_SIZE) + (i << 1));
|
||||
lastAccessed = System.currentTimeMillis();
|
||||
}
|
||||
last = i;
|
||||
int combined = (FaweCache.getId(raf.readChar()) << 4) + data;
|
||||
raf.seekUnsafe(raf.getFilePointer() - 2);
|
||||
raf.writeChar(combined);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
MainUtil.handleError(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity createEntity(Extent world, double x, double y, double z, float yaw, float pitch, BaseEntity entity) {
|
||||
FaweClipboard.ClipboardEntity ret = new ClipboardEntity(world, x, y, z, yaw, pitch, entity);
|
||||
|
@ -32,7 +32,7 @@ public class SetQueue {
|
||||
private long last;
|
||||
private long secondLast;
|
||||
private long lastSuccess;
|
||||
|
||||
|
||||
/**
|
||||
* A queue of tasks that will run when the queue is empty
|
||||
*/
|
||||
|
347
core/src/main/java/com/sk89q/jnbt/NBTInputStream.java
Normal file
347
core/src/main/java/com/sk89q/jnbt/NBTInputStream.java
Normal file
@ -0,0 +1,347 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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.sk89q.jnbt;
|
||||
|
||||
import com.boydti.fawe.object.RunnableVal2;
|
||||
import java.io.Closeable;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class reads <strong>NBT</strong>, or <strong>Named Binary Tag</strong>
|
||||
* streams, and produces an object graph of subclasses of the {@code Tag}
|
||||
* object.
|
||||
*
|
||||
* <p>The NBT format was created by Markus Persson, and the specification may be
|
||||
* found at <a href="http://www.minecraft.net/docs/NBT.txt">
|
||||
* http://www.minecraft.net/docs/NBT.txt</a>.</p>
|
||||
*/
|
||||
public final class NBTInputStream implements Closeable {
|
||||
|
||||
private final DataInputStream is;
|
||||
|
||||
/**
|
||||
* Creates a new {@code NBTInputStream}, which will source its data
|
||||
* from the specified input stream.
|
||||
*
|
||||
* @param is the input stream
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public NBTInputStream(InputStream is) throws IOException {
|
||||
this.is = new DataInputStream(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an NBT tag from the stream.
|
||||
*
|
||||
* @return The tag that was read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public NamedTag readNamedTag() throws IOException {
|
||||
return readNamedTag(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an NBT from the stream.
|
||||
*
|
||||
* @param depth the depth of this tag
|
||||
* @return The tag that was read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private NamedTag readNamedTag(int depth) throws IOException {
|
||||
int type = is.readByte();
|
||||
return new NamedTag(readNamedTagName(type), readTagPayload(type, depth));
|
||||
}
|
||||
|
||||
public void readNamedTagLazy(RunnableVal2<String, RunnableVal2> getReader) throws IOException {
|
||||
int type = is.readByte();
|
||||
String name = readNamedTagName(type);
|
||||
RunnableVal2 reader = getReader.runAndGet(name, null).value2;
|
||||
if (reader != null) {
|
||||
reader.run(0, readTagPaylodRaw(type, 0));
|
||||
return;
|
||||
}
|
||||
readTagPaylodLazy(type, 0, name, getReader);
|
||||
}
|
||||
|
||||
private String readNamedTagName(int type) throws IOException {
|
||||
String name;
|
||||
if (type != NBTConstants.TYPE_END) {
|
||||
int nameLength = is.readShort() & 0xFFFF;
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
is.readFully(nameBytes);
|
||||
return new String(nameBytes, NBTConstants.CHARSET);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void readTagPaylodLazy(int type, int depth, String node, RunnableVal2<String, RunnableVal2> getReader) throws IOException {
|
||||
switch (type) {
|
||||
case NBTConstants.TYPE_END:
|
||||
return;
|
||||
case NBTConstants.TYPE_BYTE:
|
||||
is.skip(1);
|
||||
return;
|
||||
case NBTConstants.TYPE_SHORT:
|
||||
is.skip(2);
|
||||
return;
|
||||
case NBTConstants.TYPE_INT:
|
||||
is.skip(4);
|
||||
return;
|
||||
case NBTConstants.TYPE_LONG:
|
||||
is.skip(8);
|
||||
return;
|
||||
case NBTConstants.TYPE_FLOAT:
|
||||
is.skip(4);
|
||||
return;
|
||||
case NBTConstants.TYPE_DOUBLE:
|
||||
is.skip(8);
|
||||
return;
|
||||
case NBTConstants.TYPE_STRING:
|
||||
int length = is.readShort();
|
||||
is.skip(length);
|
||||
return;
|
||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
||||
RunnableVal2 reader = getReader.runAndGet(node + ".?", null).value2;
|
||||
length = is.readInt();
|
||||
if (reader != null) {
|
||||
reader.run(length, NBTConstants.TYPE_BYTE);
|
||||
}
|
||||
reader = getReader.runAndGet(node + ".#", null).value2;
|
||||
if (reader == null) {
|
||||
is.skip(length);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
reader.run(i, is.readByte());
|
||||
}
|
||||
return;
|
||||
case NBTConstants.TYPE_LIST:
|
||||
int childType = is.readByte();
|
||||
length = is.readInt();
|
||||
reader = getReader.runAndGet(node + ".?", null).value2;
|
||||
if (reader != null) {
|
||||
reader.run(length, childType);
|
||||
}
|
||||
node += ".#";
|
||||
reader = getReader.runAndGet(node, null).value2;
|
||||
depth++;
|
||||
if (reader == null) {
|
||||
for (int i = 0; i < length; ++i) {
|
||||
readTagPaylodLazy(childType, depth, node, getReader);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < length; ++i) {
|
||||
reader.run(i, readTagPayload(childType, depth));
|
||||
}
|
||||
return;
|
||||
case NBTConstants.TYPE_COMPOUND:
|
||||
depth++;
|
||||
for (int i = 0;;i++) {
|
||||
childType = is.readByte();
|
||||
if (childType == NBTConstants.TYPE_END) {
|
||||
return;
|
||||
}
|
||||
String name = readNamedTagName(childType);
|
||||
String childNode = node + "." + name;
|
||||
reader = getReader.runAndGet(childNode, null).value2;
|
||||
if (reader == null) {
|
||||
readTagPaylodLazy(childType, depth, childNode, getReader);
|
||||
continue;
|
||||
}
|
||||
reader.run(i, readTagPaylodRaw(childType, depth));
|
||||
}
|
||||
case NBTConstants.TYPE_INT_ARRAY:
|
||||
length = is.readInt();
|
||||
reader = getReader.runAndGet(node + ".?", null).value2;
|
||||
if (reader != null) {
|
||||
reader.run(length, NBTConstants.TYPE_INT);
|
||||
}
|
||||
reader = getReader.runAndGet(node + ".#", null).value2;
|
||||
if (reader == null) {
|
||||
is.skip(length << 2);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
reader.run(i, is.readInt());
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IOException("Invalid tag type: " + type + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private Object readTagPaylodRaw(int type, int depth) throws IOException {
|
||||
switch (type) {
|
||||
case NBTConstants.TYPE_END:
|
||||
if (depth == 0) {
|
||||
throw new IOException(
|
||||
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case NBTConstants.TYPE_BYTE:
|
||||
return (is.readByte());
|
||||
case NBTConstants.TYPE_SHORT:
|
||||
return (is.readShort());
|
||||
case NBTConstants.TYPE_INT:
|
||||
return (is.readInt());
|
||||
case NBTConstants.TYPE_LONG:
|
||||
return (is.readLong());
|
||||
case NBTConstants.TYPE_FLOAT:
|
||||
return (is.readFloat());
|
||||
case NBTConstants.TYPE_DOUBLE:
|
||||
return (is.readDouble());
|
||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
||||
int length = is.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
is.readFully(bytes);
|
||||
return (bytes);
|
||||
case NBTConstants.TYPE_STRING:
|
||||
length = is.readShort();
|
||||
bytes = new byte[length];
|
||||
is.readFully(bytes);
|
||||
return (new String(bytes, NBTConstants.CHARSET));
|
||||
case NBTConstants.TYPE_LIST:
|
||||
int childType = is.readByte();
|
||||
length = is.readInt();
|
||||
List<Tag> tagList = new ArrayList<Tag>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
Tag tag = readTagPayload(childType, depth + 1);
|
||||
if (tag instanceof EndTag) {
|
||||
throw new IOException("TAG_End not permitted in a list.");
|
||||
}
|
||||
tagList.add(tag);
|
||||
}
|
||||
return (tagList);
|
||||
case NBTConstants.TYPE_COMPOUND:
|
||||
Map<String, Tag> tagMap = new HashMap<String, Tag>();
|
||||
while (true) {
|
||||
NamedTag namedTag = readNamedTag(depth + 1);
|
||||
Tag tag = namedTag.getTag();
|
||||
if (tag instanceof EndTag) {
|
||||
break;
|
||||
} else {
|
||||
tagMap.put(namedTag.getName(), tag);
|
||||
}
|
||||
}
|
||||
return (tagMap);
|
||||
case NBTConstants.TYPE_INT_ARRAY:
|
||||
length = is.readInt();
|
||||
int[] data = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
data[i] = is.readInt();
|
||||
}
|
||||
return (data);
|
||||
default:
|
||||
throw new IOException("Invalid tag type: " + type + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the payload of a tag given the type.
|
||||
*
|
||||
* @param type the type
|
||||
* @param depth the depth
|
||||
* @return the tag
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
private Tag readTagPayload(int type, int depth) throws IOException {
|
||||
switch (type) {
|
||||
case NBTConstants.TYPE_END:
|
||||
if (depth == 0) {
|
||||
throw new IOException(
|
||||
"TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
||||
} else {
|
||||
return new EndTag();
|
||||
}
|
||||
case NBTConstants.TYPE_BYTE:
|
||||
return new ByteTag(is.readByte());
|
||||
case NBTConstants.TYPE_SHORT:
|
||||
return new ShortTag(is.readShort());
|
||||
case NBTConstants.TYPE_INT:
|
||||
return new IntTag(is.readInt());
|
||||
case NBTConstants.TYPE_LONG:
|
||||
return new LongTag(is.readLong());
|
||||
case NBTConstants.TYPE_FLOAT:
|
||||
return new FloatTag(is.readFloat());
|
||||
case NBTConstants.TYPE_DOUBLE:
|
||||
return new DoubleTag(is.readDouble());
|
||||
case NBTConstants.TYPE_BYTE_ARRAY:
|
||||
int length = is.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
is.readFully(bytes);
|
||||
return new ByteArrayTag(bytes);
|
||||
case NBTConstants.TYPE_STRING:
|
||||
length = is.readShort();
|
||||
bytes = new byte[length];
|
||||
is.readFully(bytes);
|
||||
return new StringTag(new String(bytes, NBTConstants.CHARSET));
|
||||
case NBTConstants.TYPE_LIST:
|
||||
int childType = is.readByte();
|
||||
length = is.readInt();
|
||||
List<Tag> tagList = new ArrayList<Tag>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
Tag tag = readTagPayload(childType, depth + 1);
|
||||
if (tag instanceof EndTag) {
|
||||
throw new IOException("TAG_End not permitted in a list.");
|
||||
}
|
||||
tagList.add(tag);
|
||||
}
|
||||
|
||||
return new ListTag(NBTUtils.getTypeClass(childType), tagList);
|
||||
case NBTConstants.TYPE_COMPOUND:
|
||||
Map<String, Tag> tagMap = new HashMap<String, Tag>();
|
||||
while (true) {
|
||||
NamedTag namedTag = readNamedTag(depth + 1);
|
||||
Tag tag = namedTag.getTag();
|
||||
if (tag instanceof EndTag) {
|
||||
break;
|
||||
} else {
|
||||
tagMap.put(namedTag.getName(), tag);
|
||||
}
|
||||
}
|
||||
|
||||
return new CompoundTag(tagMap);
|
||||
case NBTConstants.TYPE_INT_ARRAY:
|
||||
length = is.readInt();
|
||||
int[] data = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
data[i] = is.readInt();
|
||||
}
|
||||
return new IntArrayTag(data);
|
||||
default:
|
||||
throw new IOException("Invalid tag type: " + type + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
is.close();
|
||||
}
|
||||
|
||||
}
|
@ -161,8 +161,8 @@ public class NavigationCommands {
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = { "jumpto [world,x,y,z]", "j" },
|
||||
usage = "",
|
||||
aliases = { "jumpto", "j" },
|
||||
usage = "jumpto [world,x,y,z]",
|
||||
desc = "Teleport to a location",
|
||||
min = 0,
|
||||
max = 0
|
||||
|
@ -20,6 +20,8 @@
|
||||
package com.sk89q.worldedit.extent.clipboard.io;
|
||||
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.jnbt.SchematicStreamer;
|
||||
import com.sk89q.jnbt.ByteArrayTag;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
@ -76,6 +78,9 @@ public class SchematicReader implements ClipboardReader {
|
||||
}
|
||||
|
||||
public Clipboard read(WorldData data, UUID clipboardId) throws IOException {
|
||||
if (Settings.CLIPBOARD.USE_DISK) {
|
||||
return new SchematicStreamer(inputStream, clipboardId).getClipboard();
|
||||
}
|
||||
// Schematic tag
|
||||
NamedTag rootTag = inputStream.readNamedTag();
|
||||
if (!rootTag.getName().equals("Schematic")) {
|
||||
|
Loading…
Reference in New Issue
Block a user