Optimized packet snapshot. Readers closing. Restructured schematic classes

This commit is contained in:
Nanit 2022-01-24 20:57:38 +02:00
parent 864a84b9b6
commit 1c3cb9b77d
12 changed files with 227 additions and 154 deletions

View File

@ -160,9 +160,7 @@ public class ByteMessage extends ByteBuf {
} }
public void writeCompoundTagArray(CompoundBinaryTag[] compoundTags) { public void writeCompoundTagArray(CompoundBinaryTag[] compoundTags) {
try { try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
ByteBufOutputStream stream = new ByteBufOutputStream(buf);
writeVarInt(compoundTags.length); writeVarInt(compoundTags.length);
for (CompoundBinaryTag tag : compoundTags) { for (CompoundBinaryTag tag : compoundTags) {
@ -174,16 +172,16 @@ public class ByteMessage extends ByteBuf {
} }
public CompoundBinaryTag readCompoundTag() { public CompoundBinaryTag readCompoundTag() {
try { try (ByteBufInputStream stream = new ByteBufInputStream(buf)) {
return BinaryTagIO.reader().read((InputStream) new ByteBufInputStream(buf)); return BinaryTagIO.reader().read((InputStream) stream);
} catch (IOException thrown) { } catch (IOException thrown) {
throw new DecoderException("Cannot read NBT CompoundTag"); throw new DecoderException("Cannot read NBT CompoundTag");
} }
} }
public void writeCompoundTag(CompoundBinaryTag compoundTag) { public void writeCompoundTag(CompoundBinaryTag compoundTag) {
try { try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
BinaryTagIO.writer().write(compoundTag, (OutputStream) new ByteBufOutputStream(buf)); BinaryTagIO.writer().write(compoundTag, (OutputStream) stream);
} catch (IOException e) { } catch (IOException e) {
throw new EncoderException("Cannot write NBT CompoundTag"); throw new EncoderException("Cannot write NBT CompoundTag");
} }

View File

@ -22,14 +22,19 @@ import ru.nanit.limbo.protocol.registry.Version;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* PacketSnapshot encodes packet to byt array for each MC version.
* Some versions have same bytes snapshot, so there are mappings
* to avoid storing same byte array for different versions
*/
public class PacketSnapshot implements PacketOut { public class PacketSnapshot implements PacketOut {
private final PacketOut packet; private final PacketOut packet;
private final Map<Version, byte[]> versionMessages; private final Map<Version, byte[]> versionMessages = new HashMap<>();
private final Map<Version, Version> mappings = new HashMap<>();
public PacketSnapshot(PacketOut packet) { public PacketSnapshot(PacketOut packet) {
this.packet = packet; this.packet = packet;
this.versionMessages = new HashMap<>();
} }
public PacketOut getWrappedPacket() { public PacketOut getWrappedPacket() {
@ -37,11 +42,24 @@ public class PacketSnapshot implements PacketOut {
} }
public PacketSnapshot encodePacket() { public PacketSnapshot encodePacket() {
Map<Integer, Version> hashes = new HashMap<>();
for (Version version : Version.values()) { for (Version version : Version.values()) {
if (version.equals(Version.UNDEFINED)) continue;
ByteMessage encodedMessage = ByteMessage.create(); ByteMessage encodedMessage = ByteMessage.create();
packet.encode(encodedMessage, version); packet.encode(encodedMessage, version);
byte[] message = encodedMessage.toByteArray();
versionMessages.put(version, message); int hash = encodedMessage.hashCode();
Version hashed = hashes.get(hash);
if (hashed != null) {
mappings.put(version, hashed);
continue;
}
hashes.put(hash, version);
versionMessages.put(version, encodedMessage.toByteArray());
encodedMessage.release(); encodedMessage.release();
} }
@ -50,11 +68,11 @@ public class PacketSnapshot implements PacketOut {
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg, Version version) {
byte[] message = versionMessages.get(version); Version mapped = mappings.get(version);
byte[] message = versionMessages.get(mapped);
if (message != null) { if (message != null)
msg.writeBytes(message); msg.writeBytes(message);
}
} }
@Override @Override

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.nanit.limbo.world;
import ru.nanit.limbo.protocol.registry.Version;
public final class BlockMap {
public BlockData convert(int id, byte data, Version version) {
// TODO
return null;
}
public BlockData convert(String state, Version version) {
// TODO
return null;
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.nanit.limbo.world;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.HashMap;
import java.util.Map;
public final class BlockMappings {
private final Map<String, String> idToState = new HashMap<>();
private final Map<String, String> stateToId = new HashMap<>();
public BlockData convert(int id, byte data, Version version) {
if (version.less(Version.V1_13))
return new BlockData(id, data);
String state = idToState.get(toId(id, data));
return state != null ? new BlockData(state) : null;
}
public BlockData convert(String state, Version version) {
if (state == null) return null;
if (version.moreOrEqual(Version.V1_13)) {
return new BlockData(state);
}
String id = stateToId.get(state);
if (id != null) {
String[] arr = id.split(":");
int blockId = Integer.parseInt(arr[0]);
byte data = Byte.parseByte(arr[1]);
return new BlockData(blockId, data);
}
return null;
}
public void register(int id, byte data, String state) {
String strId = toId(id, data);
idToState.put(strId, state);
stateToId.put(state, strId);
}
private String toId(int id, byte data) {
return id + ":" + data;
}
}

View File

@ -89,9 +89,15 @@ public final class DimensionRegistry {
return TagStringIO.get().asCompound(streamToString(in)); return TagStringIO.get().asCompound(streamToString(in));
} }
private String streamToString(InputStream in) { private String streamToString(InputStream in) throws IOException {
return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) InputStreamReader isReader = new InputStreamReader(in, StandardCharsets.UTF_8);
.lines() BufferedReader bufReader = new BufferedReader(isReader);
String content = bufReader.lines()
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
isReader.close();
bufReader.close();
return content;
} }
} }

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.nanit.limbo.world.schematic;
import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMappings;
import java.util.List;
public abstract class AbstractSchematic implements Schematic {
protected final BlockMappings mappings;
private int width;
private int height;
private int length;
private List<BlockEntity> blockEntities;
public AbstractSchematic(BlockMappings mappings) {
this.mappings = mappings;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getLength() {
return length;
}
@Override
public List<BlockEntity> getBlockEntities() {
return blockEntities;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public void setLength(int length) {
this.length = length;
}
public void setBlockEntities(List<BlockEntity> blockEntities) {
this.blockEntities = blockEntities;
}
}

View File

@ -20,7 +20,6 @@ package ru.nanit.limbo.world.schematic;
import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.world.BlockData; import ru.nanit.limbo.world.BlockData;
import ru.nanit.limbo.world.BlockEntity; import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.Location; import ru.nanit.limbo.world.Location;
import java.util.List; import java.util.List;
@ -35,6 +34,6 @@ public interface Schematic {
List<BlockEntity> getBlockEntities(); List<BlockEntity> getBlockEntities();
BlockData getBlock(Location loc, Version version, BlockMap mappings); BlockData getBlock(Location loc, Version version);
} }

View File

@ -21,6 +21,7 @@ import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import ru.nanit.limbo.world.BlockEntity; import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.Location; import ru.nanit.limbo.world.Location;
import ru.nanit.limbo.world.schematic.versions.LegacySchematic; import ru.nanit.limbo.world.schematic.versions.LegacySchematic;
import ru.nanit.limbo.world.schematic.versions.SpongeSchematic; import ru.nanit.limbo.world.schematic.versions.SpongeSchematic;
@ -36,6 +37,12 @@ import java.util.Map;
public class SchematicLoader { public class SchematicLoader {
private final BlockMappings mappings;
public SchematicLoader(BlockMappings mappings) {
this.mappings = mappings;
}
public Schematic load(Path file) throws IOException { public Schematic load(Path file) throws IOException {
return load(Files.newInputStream(file)); return load(Files.newInputStream(file));
} }
@ -43,16 +50,16 @@ public class SchematicLoader {
public Schematic load(InputStream stream) throws IOException { public Schematic load(InputStream stream) throws IOException {
CompoundBinaryTag nbt = BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP); CompoundBinaryTag nbt = BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP);
if (nbt.getCompound("BlockData") == CompoundBinaryTag.empty()) { if (nbt.keySet().contains("BlockData")) {
return loadLegacy(nbt);
} else {
return loadSponge(nbt); return loadSponge(nbt);
} else {
return loadLegacy(nbt);
} }
} }
// Specification: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md // Specification: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md
private Schematic loadSponge(CompoundBinaryTag nbt) { private Schematic loadSponge(CompoundBinaryTag nbt) {
SpongeSchematic schematic = new SpongeSchematic(); SpongeSchematic schematic = new SpongeSchematic(mappings);
schematic.setDataVersion(nbt.getInt("DataVersion")); schematic.setDataVersion(nbt.getInt("DataVersion"));
@ -93,7 +100,7 @@ public class SchematicLoader {
} }
private Schematic loadLegacy(CompoundBinaryTag nbt) { private Schematic loadLegacy(CompoundBinaryTag nbt) {
LegacySchematic schematic = new LegacySchematic(); LegacySchematic schematic = new LegacySchematic(mappings);
schematic.setWidth(nbt.getShort("Width")); schematic.setWidth(nbt.getShort("Width"));
schematic.setHeight(nbt.getShort("Height")); schematic.setHeight(nbt.getShort("Height"));

View File

@ -19,68 +19,33 @@ package ru.nanit.limbo.world.schematic.versions;
import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.world.BlockData; import ru.nanit.limbo.world.BlockData;
import ru.nanit.limbo.world.BlockEntity; import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.Location; import ru.nanit.limbo.world.Location;
import ru.nanit.limbo.world.schematic.Schematic; import ru.nanit.limbo.world.schematic.AbstractSchematic;
import java.util.List;
/** /**
* Legacy schematic format (1.12-) * Legacy schematic format (1.12-)
*/ */
public class LegacySchematic implements Schematic { public class LegacySchematic extends AbstractSchematic {
private short width;
private short height;
private short length;
private Materials materials; private Materials materials;
private byte[] blocks; private byte[] blocks;
private byte[] addBlocks; private byte[] addBlocks;
private byte[] data; private byte[] data;
private List<BlockEntity> blockEntities;
@Override public LegacySchematic(BlockMappings mappings) {
public int getWidth() { super(mappings);
return width;
} }
@Override @Override
public int getHeight() { public BlockData getBlock(Location loc, Version version) {
return height; int index = (loc.getBlockY() * getLength() + loc.getBlockZ()) * getWidth() + loc.getBlockX();
}
@Override
public int getLength() {
return length;
}
@Override
public List<BlockEntity> getBlockEntities() {
return blockEntities;
}
@Override
public BlockData getBlock(Location loc, Version version, BlockMap mappings) {
int index = (loc.getBlockY() * length + loc.getBlockZ()) * width + loc.getBlockX();
byte id = this.blocks[index]; byte id = this.blocks[index];
byte data = this.data[index]; byte data = this.data[index];
return mappings.convert(id, data, version); return mappings.convert(id, data, version);
} }
public void setWidth(short width) {
this.width = width;
}
public void setHeight(short height) {
this.height = height;
}
public void setLength(short length) {
this.length = length;
}
public void setMaterials(Materials materials) { public void setMaterials(Materials materials) {
this.materials = materials; this.materials = materials;
} }
@ -97,21 +62,17 @@ public class LegacySchematic implements Schematic {
this.data = data; this.data = data;
} }
public void setBlockEntities(List<BlockEntity> blockEntities) {
this.blockEntities = blockEntities;
}
@Override @Override
public String toString() { public String toString() {
return "Schematic{" + return "Schematic{" +
"width=" + width + "width=" + getWidth() +
", height=" + height + ", height=" + getHeight() +
", length=" + length + ", length=" + getLength() +
", materials=" + materials + ", materials=" + materials +
", blocks length=" + blocks.length + ", blocks length=" + blocks.length +
", addBlocks length=" + addBlocks.length + ", addBlocks length=" + addBlocks.length +
", data length=" + data.length + ", data length=" + data.length +
", blockEntities=" + blockEntities + ", blockEntities=" + getBlockEntities() +
'}'; '}';
} }

View File

@ -19,51 +19,29 @@ package ru.nanit.limbo.world.schematic.versions;
import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.world.BlockData; import ru.nanit.limbo.world.BlockData;
import ru.nanit.limbo.world.BlockEntity; import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.Location; import ru.nanit.limbo.world.Location;
import ru.nanit.limbo.world.schematic.Schematic; import ru.nanit.limbo.world.schematic.AbstractSchematic;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Modern schematic format (Sponge second specification) * Modern schematic format (Sponge second specification)
*/ */
public class SpongeSchematic implements Schematic { public class SpongeSchematic extends AbstractSchematic {
private int dataVersion; private int dataVersion;
private int width;
private int height;
private int length;
private int paletteMax; private int paletteMax;
private Map<Integer, String> palette; private Map<Integer, String> palette;
private byte[] blockData; private byte[] blockData;
private List<BlockEntity> blockEntities;
@Override public SpongeSchematic(BlockMappings mappings) {
public int getWidth() { super(mappings);
return width;
} }
@Override @Override
public int getHeight() { public BlockData getBlock(Location loc, Version version) {
return height; int index = loc.getBlockX() + loc.getBlockZ() * getWidth() + loc.getBlockY() * getWidth() * getLength();
}
@Override
public int getLength() {
return length;
}
@Override
public List<BlockEntity> getBlockEntities() {
return blockEntities;
}
@Override
public BlockData getBlock(Location loc, Version version, BlockMap mappings) {
int index = loc.getBlockX() + loc.getBlockZ() * width + loc.getBlockY() * width * length;
int id = blockData[index]; int id = blockData[index];
String state = palette.get(id); String state = palette.get(id);
return mappings.convert(state, version); return mappings.convert(state, version);
@ -73,18 +51,6 @@ public class SpongeSchematic implements Schematic {
this.dataVersion = dataVersion; this.dataVersion = dataVersion;
} }
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public void setLength(int length) {
this.length = length;
}
public void setPaletteMax(int paletteMax) { public void setPaletteMax(int paletteMax) {
this.paletteMax = paletteMax; this.paletteMax = paletteMax;
} }
@ -97,7 +63,17 @@ public class SpongeSchematic implements Schematic {
this.blockData = blockData; this.blockData = blockData;
} }
public void setBlockEntities(List<BlockEntity> blockEntities) { @Override
this.blockEntities = blockEntities; public String toString() {
return "SpongeSchematic{" +
"dataVersion=" + dataVersion +
", width=" + getWidth() +
", height=" + getHeight() +
", length=" + getLength() +
", paletteMax=" + paletteMax +
", palette=" + palette +
", blockData bytes=" + blockData.length +
", blockEntities=" + getBlockEntities() +
'}';
} }
} }

View File

@ -16,6 +16,7 @@
*/ */
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.schematic.SchematicLoader; import ru.nanit.limbo.world.schematic.SchematicLoader;
import java.io.IOException; import java.io.IOException;
@ -25,10 +26,10 @@ public class SchematicTest {
@Test @Test
public void testLoading() throws IOException { public void testLoading() throws IOException {
SchematicLoader loader = new SchematicLoader(); BlockMappings mappings = new BlockMappings();
InputStream stream = getClass().getResourceAsStream("test.schematic"); SchematicLoader loader = new SchematicLoader(mappings);
InputStream stream = getClass().getResourceAsStream("spawn.schem");
System.out.println(loader.load(stream)); System.out.println(loader.load(stream));
} }
} }

Binary file not shown.