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) {
try {
ByteBufOutputStream stream = new ByteBufOutputStream(buf);
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
writeVarInt(compoundTags.length);
for (CompoundBinaryTag tag : compoundTags) {
@ -174,16 +172,16 @@ public class ByteMessage extends ByteBuf {
}
public CompoundBinaryTag readCompoundTag() {
try {
return BinaryTagIO.reader().read((InputStream) new ByteBufInputStream(buf));
try (ByteBufInputStream stream = new ByteBufInputStream(buf)) {
return BinaryTagIO.reader().read((InputStream) stream);
} catch (IOException thrown) {
throw new DecoderException("Cannot read NBT CompoundTag");
}
}
public void writeCompoundTag(CompoundBinaryTag compoundTag) {
try {
BinaryTagIO.writer().write(compoundTag, (OutputStream) new ByteBufOutputStream(buf));
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
BinaryTagIO.writer().write(compoundTag, (OutputStream) stream);
} catch (IOException e) {
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.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 {
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) {
this.packet = packet;
this.versionMessages = new HashMap<>();
}
public PacketOut getWrappedPacket() {
@ -37,11 +42,24 @@ public class PacketSnapshot implements PacketOut {
}
public PacketSnapshot encodePacket() {
Map<Integer, Version> hashes = new HashMap<>();
for (Version version : Version.values()) {
if (version.equals(Version.UNDEFINED)) continue;
ByteMessage encodedMessage = ByteMessage.create();
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();
}
@ -50,11 +68,11 @@ public class PacketSnapshot implements PacketOut {
@Override
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);
}
}
@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));
}
private String streamToString(InputStream in) {
return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
.lines()
private String streamToString(InputStream in) throws IOException {
InputStreamReader isReader = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader bufReader = new BufferedReader(isReader);
String content = bufReader.lines()
.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.world.BlockData;
import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.Location;
import java.util.List;
@ -35,6 +34,6 @@ public interface Schematic {
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.CompoundBinaryTag;
import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.Location;
import ru.nanit.limbo.world.schematic.versions.LegacySchematic;
import ru.nanit.limbo.world.schematic.versions.SpongeSchematic;
@ -36,6 +37,12 @@ import java.util.Map;
public class SchematicLoader {
private final BlockMappings mappings;
public SchematicLoader(BlockMappings mappings) {
this.mappings = mappings;
}
public Schematic load(Path file) throws IOException {
return load(Files.newInputStream(file));
}
@ -43,16 +50,16 @@ public class SchematicLoader {
public Schematic load(InputStream stream) throws IOException {
CompoundBinaryTag nbt = BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP);
if (nbt.getCompound("BlockData") == CompoundBinaryTag.empty()) {
return loadLegacy(nbt);
} else {
if (nbt.keySet().contains("BlockData")) {
return loadSponge(nbt);
} else {
return loadLegacy(nbt);
}
}
// Specification: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md
private Schematic loadSponge(CompoundBinaryTag nbt) {
SpongeSchematic schematic = new SpongeSchematic();
SpongeSchematic schematic = new SpongeSchematic(mappings);
schematic.setDataVersion(nbt.getInt("DataVersion"));
@ -93,7 +100,7 @@ public class SchematicLoader {
}
private Schematic loadLegacy(CompoundBinaryTag nbt) {
LegacySchematic schematic = new LegacySchematic();
LegacySchematic schematic = new LegacySchematic(mappings);
schematic.setWidth(nbt.getShort("Width"));
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.world.BlockData;
import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.Location;
import ru.nanit.limbo.world.schematic.Schematic;
import java.util.List;
import ru.nanit.limbo.world.schematic.AbstractSchematic;
/**
* 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 byte[] blocks;
private byte[] addBlocks;
private byte[] data;
private List<BlockEntity> blockEntities;
@Override
public int getWidth() {
return width;
public LegacySchematic(BlockMappings mappings) {
super(mappings);
}
@Override
public int getHeight() {
return height;
}
@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();
public BlockData getBlock(Location loc, Version version) {
int index = (loc.getBlockY() * getLength() + loc.getBlockZ()) * getWidth() + loc.getBlockX();
byte id = this.blocks[index];
byte data = this.data[index];
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) {
this.materials = materials;
}
@ -97,21 +62,17 @@ public class LegacySchematic implements Schematic {
this.data = data;
}
public void setBlockEntities(List<BlockEntity> blockEntities) {
this.blockEntities = blockEntities;
}
@Override
public String toString() {
return "Schematic{" +
"width=" + width +
", height=" + height +
", length=" + length +
"width=" + getWidth() +
", height=" + getHeight() +
", length=" + getLength() +
", materials=" + materials +
", blocks length=" + blocks.length +
", addBlocks length=" + addBlocks.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.world.BlockData;
import ru.nanit.limbo.world.BlockEntity;
import ru.nanit.limbo.world.BlockMap;
import ru.nanit.limbo.world.BlockMappings;
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;
/**
* Modern schematic format (Sponge second specification)
*/
public class SpongeSchematic implements Schematic {
public class SpongeSchematic extends AbstractSchematic {
private int dataVersion;
private int width;
private int height;
private int length;
private int paletteMax;
private Map<Integer, String> palette;
private byte[] blockData;
private List<BlockEntity> blockEntities;
@Override
public int getWidth() {
return width;
public SpongeSchematic(BlockMappings mappings) {
super(mappings);
}
@Override
public int getHeight() {
return height;
}
@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;
public BlockData getBlock(Location loc, Version version) {
int index = loc.getBlockX() + loc.getBlockZ() * getWidth() + loc.getBlockY() * getWidth() * getLength();
int id = blockData[index];
String state = palette.get(id);
return mappings.convert(state, version);
@ -73,18 +51,6 @@ public class SpongeSchematic implements Schematic {
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) {
this.paletteMax = paletteMax;
}
@ -97,7 +63,17 @@ public class SpongeSchematic implements Schematic {
this.blockData = blockData;
}
public void setBlockEntities(List<BlockEntity> blockEntities) {
this.blockEntities = blockEntities;
@Override
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 ru.nanit.limbo.world.BlockMappings;
import ru.nanit.limbo.world.schematic.SchematicLoader;
import java.io.IOException;
@ -25,10 +26,10 @@ public class SchematicTest {
@Test
public void testLoading() throws IOException {
SchematicLoader loader = new SchematicLoader();
InputStream stream = getClass().getResourceAsStream("test.schematic");
BlockMappings mappings = new BlockMappings();
SchematicLoader loader = new SchematicLoader(mappings);
InputStream stream = getClass().getResourceAsStream("spawn.schem");
System.out.println(loader.load(stream));
}
}

Binary file not shown.