Optimization & proper ItemStack reader

This commit is contained in:
Felix Cravic 2020-02-13 15:14:41 +01:00
parent e9809b20ac
commit b8319217ab
15 changed files with 421 additions and 75 deletions

View File

@ -535,6 +535,21 @@ public abstract class Entity implements Viewable, DataContainer {
return position;
}
public boolean sameChunk(Position position) {
Position pos = getPosition();
int chunkX1 = ChunkUtils.getChunkX((int) pos.getX());
int chunkZ1 = ChunkUtils.getChunkX((int) pos.getZ());
int chunkX2 = ChunkUtils.getChunkX((int) position.getX());
int chunkZ2 = ChunkUtils.getChunkX((int) position.getZ());
return chunkX1 == chunkX2 && chunkZ1 == chunkZ2;
}
public boolean sameChunk(Entity entity) {
return sameChunk(entity.getPosition());
}
public void remove() {
this.shouldRemove = true;
entityById.remove(id);

View File

@ -15,6 +15,9 @@ public class EntityManager {
private static InstanceManager instanceManager = Main.getInstanceManager();
private UpdateType updateType = UpdateType.PER_CHUNK;
private Set<Instance> instances = instanceManager.getInstances();
private ExecutorService entitiesPool = new MinestomThread(Main.THREAD_COUNT_ENTITIES, "Ms-EntitiesPool");
private ExecutorService playersPool = new MinestomThread(Main.THREAD_COUNT_PLAYERS_ENTITIES, "Ms-PlayersPool");
@ -23,10 +26,45 @@ public class EntityManager {
public void update() {
final long time = System.currentTimeMillis();
// Connect waiting players
waitingPlayersTick();
for (Instance instance : instanceManager.getInstances()) {
testTick2(instance, time);
// Update entities
switch (updateType) {
case PER_CHUNK:
chunkUpdate(instances, time);
break;
case PER_ENTITY_TYPE:
entityTypeUpdate(instances, time);
break;
case PER_INSTANCE:
instanceUpdate(instances, time);
break;
}
}
/**
* Update is chunk based
*
* @param time
*/
private void chunkUpdate(Set<Instance> instances, long time) {
// TODO optimize for when there are too many entities on one chunk
for (Instance instance : instanceManager.getInstances()) {
for (Chunk chunk : instance.getChunks()) {
Set<Entity> entities = instance.getChunkEntities(chunk);
if (!entities.isEmpty()) {
entitiesPool.execute(() -> {
for (Entity entity : entities) {
entity.tick(time);
}
});
}
}
}
}
private void waitingPlayersTick() {
@ -43,48 +81,81 @@ public class EntityManager {
}
}
// TODO optimize for when there are too many entities on one chunk
private void testTick2(Instance instance, long time) {
for (Chunk chunk : instance.getChunks()) {
Set<Entity> entities = instance.getChunkEntities(chunk);
/**
* Update each entity type separately independently of their location
*
* @param time
*/
private void entityTypeUpdate(Set<Instance> instances, long time) {
for (Instance instance : instanceManager.getInstances()) {
Set<Player> players = instance.getPlayers();
Set<EntityCreature> creatures = instance.getCreatures();
Set<ObjectEntity> objects = instance.getObjectEntities();
if (!entities.isEmpty()) {
if (!players.isEmpty()) {
playersPool.execute(() -> {
for (Player player : players) {
player.tick(time);
}
});
}
if (!creatures.isEmpty() || !objects.isEmpty()) {
entitiesPool.execute(() -> {
for (Entity entity : entities) {
entity.tick(time);
for (EntityCreature creature : creatures) {
creature.tick(time);
}
for (ObjectEntity objectEntity : objects) {
objectEntity.tick(time);
}
});
}
}
}
private void testTick1(Instance instance, long time) {
Set<ObjectEntity> objects = instance.getObjectEntities();
Set<EntityCreature> creatures = instance.getCreatures();
Set<Player> players = instance.getPlayers();
/**
* Each instance get its pool, should suppress most of the problems related to thread-safety
*
* @param instances
* @param time
*/
private void instanceUpdate(Set<Instance> instances, long time) {
for (Instance instance : instances) {
Set<Player> players = instance.getPlayers();
Set<EntityCreature> creatures = instance.getCreatures();
Set<ObjectEntity> objects = instance.getObjectEntities();
if (!creatures.isEmpty() || !objects.isEmpty()) {
entitiesPool.execute(() -> {
for (EntityCreature creature : creatures) {
creature.tick(time);
}
for (ObjectEntity objectEntity : objects) {
objectEntity.tick(time);
}
});
if (!players.isEmpty() || !creatures.isEmpty() || !objects.isEmpty()) {
entitiesPool.execute(() -> {
for (Player player : players) {
player.tick(time);
}
for (EntityCreature creature : creatures) {
creature.tick(time);
}
for (ObjectEntity objectEntity : objects) {
objectEntity.tick(time);
}
});
}
}
}
if (!players.isEmpty()) {
playersPool.execute(() -> {
for (Player player : players) {
player.tick(time);
}
});
}
public UpdateType getUpdateType() {
return updateType;
}
public void addWaitingPlayer(Player player) {
this.waitingPlayers.add(player);
}
public void setUpdateType(UpdateType updateType) {
this.updateType = updateType;
}
public enum UpdateType {
PER_CHUNK,
PER_ENTITY_TYPE,
PER_INSTANCE;
}
}

View File

@ -181,7 +181,12 @@ public class Player extends LivingEntity {
//itemEntity.remove();
}*/
getInventory().addItemStack(new ItemStack(1, (byte) 75));
ItemStack item = new ItemStack(1, (byte) 75);
item.setDisplayName("LE NOM PUTAIN");
item.getLore().add("lol le lore");
item.getLore().add("lol le lore2");
item.getLore().add("lol le lore3");
getInventory().addItemStack(item);
//getInventory().addItemStack(new ItemStack(1, (byte) 100));
/*TeamManager teamManager = Main.getTeamManager();

View File

@ -159,8 +159,8 @@ public abstract class Instance implements BlockModifier, DataContainer {
}
public Chunk getChunkAt(double x, double z) {
int chunkX = Math.floorDiv((int) x, 16);
int chunkZ = Math.floorDiv((int) z, 16);
int chunkX = ChunkUtils.getChunkX((int) x);
int chunkZ = ChunkUtils.getChunkX((int) z);
return getChunk(chunkX, chunkZ);
}

View File

@ -3,6 +3,8 @@ package fr.themode.minestom.item;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.data.DataContainer;
import java.util.ArrayList;
public class ItemStack implements DataContainer {
public static final ItemStack AIR_ITEM = new ItemStack(0, (byte) 1);
@ -13,6 +15,7 @@ public class ItemStack implements DataContainer {
private String displayName;
private boolean unbreakable;
private ArrayList<String> lore;
private Data data;
@ -20,6 +23,7 @@ public class ItemStack implements DataContainer {
this.material = material;
this.amount = amount;
this.damage = damage;
this.lore = new ArrayList<>();
}
public ItemStack(int id, byte amount) {
@ -30,11 +34,18 @@ public class ItemStack implements DataContainer {
return material == Material.AIR;
}
/**
* Do not take amount in consideration
*
* @param itemStack
* @return
*/
public boolean isSimilar(ItemStack itemStack) {
return itemStack.getMaterial() == material &&
itemStack.getDisplayName() == displayName &&
itemStack.isUnbreakable() == unbreakable &&
itemStack.getDamage() == damage;
itemStack.getDamage() == damage &&
itemStack.getData() == data;
}
public byte getAmount() {
@ -65,6 +76,22 @@ public class ItemStack implements DataContainer {
this.displayName = displayName;
}
public boolean hasDisplayName() {
return displayName != null;
}
public ArrayList<String> getLore() {
return lore;
}
public void setLore(ArrayList<String> lore) {
this.lore = lore;
}
public boolean hasLore() {
return lore != null && !lore.isEmpty();
}
public boolean isUnbreakable() {
return unbreakable;
}
@ -73,6 +100,10 @@ public class ItemStack implements DataContainer {
this.unbreakable = unbreakable;
}
public boolean hasNbtTag() {
return hasDisplayName() || hasLore() || isUnbreakable();
}
public ItemStack clone() {
ItemStack itemStack = new ItemStack(material, amount, damage);
itemStack.setDisplayName(displayName);

View File

@ -50,6 +50,11 @@ public class PacketReader {
client.readShort(consumer);
}
public void readInteger(IntConsumer consumer) {
sizeOffset += Integer.BYTES;
client.readInt(consumer);
}
public void readLong(LongConsumer consumer) {
sizeOffset += Long.BYTES;
client.readLong(consumer);
@ -66,13 +71,17 @@ public class PacketReader {
}
public void readSizedString(StringConsumer consumer) {
Utils.readString(client, consumer);
Utils.readStringVarIntSized(client, consumer);
}
public void readSizedString(Consumer<String> consumer) {
readSizedString((string, length1) -> consumer.accept(string));
}
public void readShortSizedString(StringConsumer consumer) {
Utils.readStringShortSized(client, consumer);
}
public void getRemainingBytes(int offset, Consumer<byte[]> consumer) {
int size = length - 1 - offset;
client.readBytes(size, consumer);
@ -90,4 +99,11 @@ public class PacketReader {
Utils.readItemStack(this, consumer);
}
public int getPacketLength() {
return length;
}
public int getReaderOffset() {
return sizeOffset;
}
}

View File

@ -14,6 +14,7 @@ public class ClientPacketsHandler {
}
public ClientPacket getPacketInstance(int id) {
//System.out.println("RECEIVED PACKET 0x" + Integer.toHexString(id));
if (id > SIZE)
throw new IllegalStateException("Packet ID 0x" + Integer.toHexString(id) + " has been tried to be parsed, debug needed");

View File

@ -5,28 +5,31 @@ import fr.themode.minestom.net.packet.client.play.*;
public class ClientPlayPacketsHandler extends ClientPacketsHandler {
public ClientPlayPacketsHandler() {
register(0x05, ClientSettingsPacket.class);
register(0x0B, ClientPluginMessagePacket.class);
register(0x11, ClientPlayerPositionPacket.class);
register(0x12, ClientPlayerPositionAndLookPacket.class);
register(0x00, ClientTeleportConfirmPacket.class);
register(0x0F, ClientKeepAlivePacket.class);
register(0x19, ClientPlayerAbilitiesPacket.class);
register(0x13, ClientPlayerLookPacket.class);
register(0x14, ClientPlayerPacket.class);
register(0x2A, ClientAnimationPacket.class);
register(0x1B, ClientEntityActionPacket.class);
register(0x0E, ClientUseEntityPacket.class);
register(0x03, ClientChatMessagePacket.class);
register(0x1A, ClientPlayerDiggingPacket.class);
register(0x2C, ClientPlayerBlockPlacementPacket.class);
register(0x23, ClientHeldItemChangePacket.class);
register(0x04, ClientStatusPacket.class);
register(0x05, ClientSettingsPacket.class);
register(0x07, ClientWindowConfirmationPacket.class);
register(0x08, ClientClickWindowButtonPacket.class); // Marked as 0x07 on wiki.vg
register(0x09, ClientClickWindowPacket.class);
register(0x0A, ClientCloseWindow.class);
register(0x07, ClientClickWindowButtonPacket.class);
register(0x0B, ClientPluginMessagePacket.class);
register(0x1C, ClientSteerVehiclePacket.class);
register(0x2D, ClientUseItemPacket.class);
register(0x04, ClientStatusPacket.class);
register(0x0E, ClientUseEntityPacket.class);
register(0x0F, ClientKeepAlivePacket.class);
register(0x11, ClientPlayerPositionPacket.class);
register(0x12, ClientPlayerPositionAndLookPacket.class);
register(0x13, ClientPlayerLookPacket.class);
register(0x14, ClientPlayerPacket.class);
register(0x19, ClientPlayerAbilitiesPacket.class);
register(0x1A, ClientPlayerDiggingPacket.class);
register(0x1B, ClientEntityActionPacket.class);
register(0x23, ClientHeldItemChangePacket.class);
register(0x26, ClientCreativeInventoryActionPacket.class);
register(0x2A, ClientAnimationPacket.class);
register(0x2C, ClientPlayerBlockPlacementPacket.class);
register(0x2D, ClientUseItemPacket.class);
}
}

View File

@ -10,7 +10,6 @@ public class ClientClickWindowButtonPacket extends ClientPlayPacket {
@Override
public void read(PacketReader reader, Runnable callback) {
// FIXME: 2 packets have the same id (Confirm Transaction / Click window button)
reader.readByte(value -> windowId = value);
reader.readByte(value -> {
buttonId = value;

View File

@ -12,7 +12,6 @@ public class ClientClickWindowPacket extends ClientPlayPacket {
public short actionNumber;
public int mode;
public ItemStack item;
// TODO clicked item
@Override
public void read(PacketReader reader, Runnable callback) {

View File

@ -0,0 +1,21 @@
package fr.themode.minestom.net.packet.client.play;
import fr.themode.minestom.net.packet.PacketReader;
import fr.themode.minestom.net.packet.client.ClientPlayPacket;
public class ClientWindowConfirmationPacket extends ClientPlayPacket {
public byte windowId;
public short actionNumber;
public boolean accepted;
@Override
public void read(PacketReader reader, Runnable callback) {
reader.readByte(value -> windowId = value);
reader.readShort(value -> actionNumber = value);
reader.readBoolean(value -> {
accepted = value;
callback.run();
});
}
}

View File

@ -8,6 +8,14 @@ public class ChunkUtils {
return instance.getChunk((int) Math.floor(x / 16), (int) Math.floor(z / 16)) == null;
}
public static int getChunkX(int x) {
return Math.floorDiv(x, 16);
}
public static int getChunkZ(int z) {
return Math.floorDiv(z, 16);
}
public static long getChunkIndex(int chunkX, int chunkZ) {
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
}

View File

@ -0,0 +1,139 @@
package fr.themode.minestom.utils;
import club.thectm.minecraft.text.LegacyText;
import club.thectm.minecraft.text.TextObject;
import com.google.gson.JsonParser;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.PacketReader;
import java.util.ArrayList;
public class NbtReaderUtils {
public static void readItemStackNBT(PacketReader reader, ItemStack item) {
reader.readByte(typeId -> {
switch (typeId) {
case 0x00: // TAG_End
// End of item NBT
break;
case 0x01: // TAG_Byte
break;
case 0x02: // TAG_Short
reader.readShortSizedString((name, l) -> {
// Damage NBT
if (name.equals("Damage")) {
reader.readShort(damage -> {
item.setDamage(damage);
readItemStackNBT(reader, item);
});
}
});
break;
case 0x03: // TAG_Int
reader.readShortSizedString((name, length) -> {
// Unbreakable
if (name.equals("Unbreakable")) {
reader.readInteger(value -> {
item.setUnbreakable(value == 1);
readItemStackNBT(reader, item);
});
}
});
break;
case 0x04: // TAG_Long
break;
case 0x05: // TAG_Float
break;
case 0x06: // TAG_Double
break;
case 0x07: // TAG_Byte_Array
break;
case 0x08: // TAG_String
break;
case 0x09: // TAG_List
break;
case 0x0A: // TAG_Compound
reader.readShortSizedString((compoundName, length) -> {
// Display Compound
if (compoundName.equals("display")) {
readItemStackDisplayNBT(reader, item);
}
});
break;
}
});
}
public static void readItemStackDisplayNBT(PacketReader reader, ItemStack item) {
reader.readByte(typeId -> {
switch (typeId) {
case 0x00: // TAG_End
// End of the display compound
readItemStackNBT(reader, item);
break;
case 0x08: // TAG_String
reader.readShortSizedString((name, length) -> {
if (name.equals("Name")) {
reader.readShortSizedString((jsonDisplayName, length1) -> {
TextObject textObject = TextObject.fromJson(new JsonParser().parse(jsonDisplayName).getAsJsonObject());
String displayName = LegacyText.toLegacy(textObject);
item.setDisplayName(displayName);
readItemStackDisplayNBT(reader, item);
});
}
});
break;
case 0x09: // TAG_List
reader.readShortSizedString((name, length) -> {
if (name.equals("Lore")) {
reader.readByte(loreType -> { // Should always be 0x08 (TAG_String)
reader.readInteger(size -> {
ArrayList<String> lore = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
reader.readShortSizedString((string, length1) -> {
TextObject textObject = TextObject.fromJson(new JsonParser().parse(string).getAsJsonObject());
String line = LegacyText.toLegacy(textObject);
lore.add(line);
});
if (i == size - 1) { // Last iteration
readItemStackDisplayNBT(reader, item);
}
}
});
});
}
});
break;
}
});
}
}

View File

@ -15,7 +15,7 @@ public class PacketUtils {
PacketWriter packetWriter = new PacketWriter(packet);
serverPacket.write(packetWriter);
System.out.println("WRITE PACKET: " + id + " " + serverPacket.getClass().getSimpleName());
//System.out.println("WRITE PACKET: " + id + " " + serverPacket.getClass().getSimpleName());
callback.accept(packet.prepend(p -> {
Utils.writeVarInt(packet, packet.getSize());

View File

@ -10,6 +10,7 @@ import fr.themode.minestom.utils.buffer.BufferWrapper;
import fr.themode.minestom.utils.consumer.StringConsumer;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.function.Consumer;
public class Utils {
@ -29,7 +30,7 @@ public class Utils {
}
}
public static void readString(Client client, StringConsumer consumer) {
public static void readStringVarIntSized(Client client, StringConsumer consumer) {
ConnectionUtils.readVarInt(client, length -> {
int stringLength = Utils.lengthVarInt(length) + length;
client.readBytes(length, bytes -> {
@ -43,6 +44,20 @@ public class Utils {
});
}
public static void readStringShortSized(Client client, StringConsumer consumer) {
client.readShort(length -> {
client.readBytes(length, bytes -> {
try {
consumer.accept(new String(bytes, "UTF-8"), length);
} catch (UnsupportedEncodingException e) {
consumer.accept(null, length);
e.printStackTrace();
}
});
});
}
public static void writeVarIntBuffer(BufferWrapper buffer, int value) {
do {
byte temp = (byte) (value & 0b01111111);
@ -93,15 +108,20 @@ public class Utils {
}
public static void writeItemStack(Packet packet, ItemStack itemStack) {
if (itemStack == null) {
if (itemStack == null || itemStack.isAir()) {
packet.putBoolean(false);
} else {
packet.putBoolean(true);
Utils.writeVarInt(packet, itemStack.getMaterial().getId());
packet.putByte(itemStack.getAmount());
if (!itemStack.hasNbtTag()) {
packet.putByte((byte) 0x00); // No nbt
return;
}
packet.putByte((byte) 0x0A); // Compound
packet.putShort((short) 0);
packet.putShort((short) 0); // Empty compound name
// Unbreakable
if (itemStack.isUnbreakable()) {
@ -116,29 +136,40 @@ public class Utils {
packet.putShort(itemStack.getDamage());
// Display
packet.putByte((byte) 0x0A); // Compound
packet.putString("display");
boolean hasDisplayName = itemStack.hasDisplayName();
boolean hasLore = itemStack.hasLore();
if (itemStack.getDisplayName() != null) {
packet.putByte((byte) 0x08);
packet.putString("Name");
packet.putString(Chat.legacyTextString(itemStack.getDisplayName()));
if (hasDisplayName || hasLore) {
packet.putByte((byte) 0x0A); // Start display compound
packet.putString("display");
if (hasDisplayName) {
packet.putByte((byte) 0x08);
packet.putString("Name");
packet.putString(Chat.legacyTextString(itemStack.getDisplayName()));
}
if (hasLore) {
ArrayList<String> lore = itemStack.getLore();
packet.putByte((byte) 0x09);
packet.putString("Lore");
packet.putByte((byte) 0x08);
packet.putInt(lore.size());
for (String line : lore) {
packet.putString(Chat.legacyTextString(line));
}
}
packet.putByte((byte) 0); // End display compound
}
// TODO lore
/*packet.putByte((byte) 0x08);
packet.putString("Lore");
packet.putString(Chat.rawText("a line"));*/
packet.putByte((byte) 0); // End display compound
// End display
packet.putByte((byte) 0); // End nbt
}
}
public static void readItemStack(PacketReader reader, Consumer<ItemStack> consumer) {
// FIXME: need finishing
reader.readBoolean(present -> {
if (!present) {
consumer.accept(ItemStack.AIR_ITEM); // Consume air item if empty
@ -146,11 +177,18 @@ public class Utils {
}
reader.readVarInt(id -> {
reader.readByte(count -> {
ItemStack item = new ItemStack(id, count);
reader.readByte(nbt -> { // Should be compound start (0x0A) or 0 if there isn't NBT data
if (nbt == 0x00) {
consumer.accept(item);
return;
} else if (nbt == 0x0A) {
reader.readShort(compoundName -> { // Ignored, should be empty (main compound name)
NbtReaderUtils.readItemStackNBT(reader, item);
});
reader.readByte(nbt -> { // FIXME: assume that there is no NBT data
consumer.accept(new ItemStack(id, count));
}
});
});