Ensure every packet is ran on the same thread per player (#2473)

This removes a lot of concurrency checking that needs to be done, because there should be no way two packets can be handled at the same time.
This commit is contained in:
Camotoy 2021-08-16 20:39:29 -04:00 committed by GitHub
parent eca0691db0
commit 7ae91a40ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 198 additions and 233 deletions

View file

@ -13,8 +13,8 @@
<properties> <properties>
<adventure.version>4.8.0</adventure.version> <adventure.version>4.8.0</adventure.version>
<fastutil.version>8.5.2</fastutil.version> <fastutil.version>8.5.2</fastutil.version>
<jackson.version>2.10.2</jackson.version> <jackson.version>2.12.4</jackson.version>
<netty.version>4.1.59.Final</netty.version> <netty.version>4.1.66.Final</netty.version>
</properties> </properties>
<dependencies> <dependencies>
@ -167,7 +167,7 @@
<dependency> <dependency>
<groupId>com.github.GeyserMC</groupId> <groupId>com.github.GeyserMC</groupId>
<artifactId>PacketLib</artifactId> <artifactId>PacketLib</artifactId>
<version>25eb4c4</version> <version>86c9c38</version>
<scope>compile</scope> <scope>compile</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>

View file

@ -30,7 +30,9 @@ import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.BedrockServerSession;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.DatagramPacket;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.configuration.GeyserConfiguration;
@ -56,6 +58,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
private static final int MAGIC_RAKNET_LENGTH = 338; private static final int MAGIC_RAKNET_LENGTH = 338;
private final GeyserConnector connector; private final GeyserConnector connector;
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(new DefaultThreadFactory("Geyser player thread"));
public ConnectorServerEventHandler(GeyserConnector connector) { public ConnectorServerEventHandler(GeyserConnector connector) {
this.connector = connector; this.connector = connector;
@ -162,7 +165,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
public void onSessionCreation(BedrockServerSession bedrockServerSession) { public void onSessionCreation(BedrockServerSession bedrockServerSession) {
bedrockServerSession.setLogging(true); bedrockServerSession.setLogging(true);
bedrockServerSession.setCompressionLevel(connector.getConfig().getBedrock().getCompressionLevel()); bedrockServerSession.setCompressionLevel(connector.getConfig().getBedrock().getCompressionLevel());
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession))); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession, eventLoopGroup.next())));
// Set the packet codec to default just in case we need to send disconnect packets. // Set the packet codec to default just in case we need to send disconnect packets.
bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC); bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC);
} }

View file

@ -52,6 +52,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session); return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session);
} }
@Override
boolean defaultHandler(BedrockPacket packet) {
return translateAndDefault(packet);
}
@Override @Override
public boolean handle(LoginPacket loginPacket) { public boolean handle(LoginPacket loginPacket) {
BedrockPacketCodec packetCodec = BedrockProtocol.getBedrockCodec(loginPacket.getProtocolVersion()); BedrockPacketCodec packetCodec = BedrockProtocol.getBedrockCodec(loginPacket.getProtocolVersion());
@ -156,7 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override @Override
public boolean handle(ModalFormResponsePacket packet) { public boolean handle(ModalFormResponsePacket packet) {
session.getFormCache().handleResponse(packet); session.getEventLoop().execute(() -> session.getFormCache().handleResponse(packet));
return true; return true;
} }
@ -209,11 +214,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return translateAndDefault(packet); return translateAndDefault(packet);
} }
@Override
boolean defaultHandler(BedrockPacket packet) {
return translateAndDefault(packet);
}
@Override @Override
public boolean handle(ResourcePackChunkRequestPacket packet) { public boolean handle(ResourcePackChunkRequestPacket packet) {
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket(); ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();

View file

@ -57,9 +57,9 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.packet.*;
import io.netty.channel.EventLoop;
import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator; import it.unimi.dsi.fastutil.objects.ObjectIterator;
@ -102,7 +102,10 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Getter @Getter
@ -110,6 +113,10 @@ public class GeyserSession implements CommandSender {
private final GeyserConnector connector; private final GeyserConnector connector;
private final UpstreamSession upstream; private final UpstreamSession upstream;
/**
* The loop where all packets and ticking is processed to prevent concurrency issues.
*/
private final EventLoop eventLoop;
private TcpClientSession downstream; private TcpClientSession downstream;
@Setter @Setter
private AuthData authData; private AuthData authData;
@ -158,11 +165,6 @@ public class GeyserSession implements CommandSender {
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
private final AtomicInteger itemNetId = new AtomicInteger(2); private final AtomicInteger itemNetId = new AtomicInteger(2);
@Getter(AccessLevel.NONE)
private final Object inventoryLock = new Object();
@Getter(AccessLevel.NONE)
private CompletableFuture<Void> inventoryFuture;
@Setter @Setter
private ScheduledFuture<?> craftingGridFuture; private ScheduledFuture<?> craftingGridFuture;
@ -183,8 +185,8 @@ public class GeyserSession implements CommandSender {
@Setter @Setter
private ItemMappings itemMappings; private ItemMappings itemMappings;
private final Map<Vector3i, SkullPlayerEntity> skullCache = new ConcurrentHashMap<>(); private final Map<Vector3i, SkullPlayerEntity> skullCache = new Object2ObjectOpenHashMap<>();
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = new Long2ObjectOpenHashMap<>();
/** /**
* Stores the map between Java and Bedrock biome network IDs. * Stores the map between Java and Bedrock biome network IDs.
@ -426,9 +428,10 @@ public class GeyserSession implements CommandSender {
private MinecraftProtocol protocol; private MinecraftProtocol protocol;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
this.connector = connector; this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession); this.upstream = new UpstreamSession(bedrockServerSession);
this.eventLoop = eventLoop;
this.advancementsCache = new AdvancementsCache(this); this.advancementsCache = new AdvancementsCache(this);
this.bookEditCache = new BookEditCache(this); this.bookEditCache = new BookEditCache(this);
@ -447,7 +450,6 @@ public class GeyserSession implements CommandSender {
this.playerInventory = new PlayerInventory(); this.playerInventory = new PlayerInventory();
this.openInventory = null; this.openInventory = null;
this.inventoryFuture = CompletableFuture.completedFuture(null);
this.craftingRecipes = new Int2ObjectOpenHashMap<>(); this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.unlockedRecipes = new ObjectOpenHashSet<>(); this.unlockedRecipes = new ObjectOpenHashSet<>();
this.lastRecipeNetId = new AtomicInteger(1); this.lastRecipeNetId = new AtomicInteger(1);
@ -664,7 +666,7 @@ public class GeyserSession implements CommandSender {
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE; boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
// Start ticking // Start ticking
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
downstream = new TcpClientSession(this.remoteAddress, this.remotePort, protocol); downstream = new TcpClientSession(this.remoteAddress, this.remotePort, protocol);
disableSrvResolving(); disableSrvResolving();
@ -1095,39 +1097,6 @@ public class GeyserSession implements CommandSender {
upstream.sendPacket(startGamePacket); upstream.sendPacket(startGamePacket);
} }
/**
* Adds a new inventory task.
* Inventory tasks are executed one at a time, in order.
*
* @param task the task to run
*/
public void addInventoryTask(Runnable task) {
synchronized (inventoryLock) {
inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/**
* Adds a new inventory task with a delay.
* The delay is achieved by scheduling with the Geyser general thread pool.
* Inventory tasks are executed one at a time, in order.
*
* @param task the delayed task to run
* @param delayMillis delay in milliseconds
*/
public void addInventoryTask(Runnable task, long delayMillis) {
synchronized (inventoryLock) {
Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS);
inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/** /**
* @return the next Bedrock item network ID to use for a new item * @return the next Bedrock item network ID to use for a new item
*/ */
@ -1229,7 +1198,18 @@ public class GeyserSession implements CommandSender {
* @param packet the java edition packet from MCProtocolLib * @param packet the java edition packet from MCProtocolLib
*/ */
public void sendDownstreamPacket(Packet packet) { public void sendDownstreamPacket(Packet packet) {
if (downstream != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { if (!closed && this.downstream != null) {
EventLoop eventLoop = this.downstream.getChannel().eventLoop();
if (eventLoop.inEventLoop()) {
sendDownstreamPacket0(packet);
} else {
eventLoop.execute(() -> sendDownstreamPacket0(packet));
}
}
}
private void sendDownstreamPacket0(Packet packet) {
if (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class) {
downstream.send(packet); downstream.send(packet);
} else { } else {
connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server");

View file

@ -26,6 +26,8 @@
package org.geysermc.connector.network.session.cache; package org.geysermc.connector.network.session.cache;
import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter; import lombok.Getter;
import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.Tickable;
@ -44,15 +46,15 @@ public class EntityCache {
private final GeyserSession session; private final GeyserSession session;
@Getter @Getter
private Long2ObjectMap<Entity> entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final Long2ObjectMap<Entity> entities = new Long2ObjectOpenHashMap<>();
/** /**
* A list of all entities that must be ticked. * A list of all entities that must be ticked.
*/ */
private final List<Tickable> tickableEntities = Collections.synchronizedList(new ArrayList<>()); private final List<Tickable> tickableEntities = new ObjectArrayList<>();
private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private final Long2LongMap entityIdTranslations = new Long2LongOpenHashMap();
private Map<UUID, PlayerEntity> playerEntities = Collections.synchronizedMap(new HashMap<>()); private final Map<UUID, PlayerEntity> playerEntities = new Object2ObjectOpenHashMap<>();
private Map<UUID, BossBar> bossBars = Collections.synchronizedMap(new HashMap<>()); private final Map<UUID, BossBar> bossBars = new Object2ObjectOpenHashMap<>();
private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); private final Long2LongMap cachedPlayerEntityLinks = new Long2LongOpenHashMap();
@Getter @Getter
private final AtomicLong nextEntityId = new AtomicLong(2L); private final AtomicLong nextEntityId = new AtomicLong(2L);
@ -156,13 +158,6 @@ public class EntityCache {
bossBars.values().forEach(BossBar::updateBossBar); bossBars.values().forEach(BossBar::updateBossBar);
} }
public void clear() {
entities = null;
entityIdTranslations = null;
playerEntities = null;
bossBars = null;
}
public long getCachedPlayerEntityLink(long playerId) { public long getCachedPlayerEntityLink(long playerId) {
return cachedPlayerEntityLinks.remove(playerId); return cachedPlayerEntityLinks.remove(playerId);
} }

View file

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDa
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket;
import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.packet.Packet;
import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockPacket;
import io.netty.channel.EventLoop;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.common.PlatformType; import org.geysermc.common.PlatformType;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
@ -89,7 +90,12 @@ public class PacketTranslatorRegistry<T> {
try { try {
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz); PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
if (translator != null) { if (translator != null) {
translator.translate(packet, session); EventLoop eventLoop = session.getEventLoop();
if (eventLoop.inEventLoop()) {
translator.translate(packet, session);
} else {
eventLoop.execute(() -> translator.translate(packet, session));
}
return true; return true;
} else { } else {
if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) { if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {

View file

@ -48,7 +48,7 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
switch (packet.getAction()) { switch (packet.getAction()) {
case SWING_ARM: case SWING_ARM:
// Delay so entity damage can be processed first // Delay so entity damage can be processed first
session.getConnector().getGeneralThreadPool().schedule(() -> session.getEventLoop().schedule(() ->
session.sendDownstreamPacket(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)), session.sendDownstreamPacket(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)),
25, 25,
TimeUnit.MILLISECONDS TimeUnit.MILLISECONDS

View file

@ -39,29 +39,27 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
@Override @Override
public void translate(ContainerClosePacket packet, GeyserSession session) { public void translate(ContainerClosePacket packet, GeyserSession session) {
session.addInventoryTask(() -> { byte windowId = packet.getId();
byte windowId = packet.getId();
//Client wants close confirmation //Client wants close confirmation
session.sendUpstreamPacket(packet); session.sendUpstreamPacket(packet);
session.setClosingInventory(false); session.setClosingInventory(false);
if (windowId == -1 && session.getOpenInventory() instanceof MerchantContainer) { if (windowId == -1 && session.getOpenInventory() instanceof MerchantContainer) {
// 1.16.200 - window ID is always -1 sent from Bedrock // 1.16.200 - window ID is always -1 sent from Bedrock
windowId = (byte) session.getOpenInventory().getId(); windowId = (byte) session.getOpenInventory().getId();
}
Inventory openInventory = session.getOpenInventory();
if (openInventory != null) {
if (windowId == openInventory.getId()) {
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId, false);
} else if (openInventory.isPending()) {
InventoryUtils.displayInventory(session, openInventory);
openInventory.setPending(false);
} }
}
Inventory openInventory = session.getOpenInventory();
if (openInventory != null) {
if (windowId == openInventory.getId()) {
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId, false);
} else if (openInventory.isPending()) {
InventoryUtils.displayInventory(session, openInventory);
openInventory.setPending(false);
}
}
});
} }
} }

View file

@ -83,26 +83,24 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
InventoryActionData containerAction = packet.getActions().get(1); InventoryActionData containerAction = packet.getActions().get(1);
if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
&& worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { && worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
session.addInventoryTask(() -> { if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() ||
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() || session.getPlayerInventory().getItemInHand().isEmpty()) {
session.getPlayerInventory().getItemInHand().isEmpty()) { return;
return; }
}
boolean dropAll = worldAction.getToItem().getCount() > 1; boolean dropAll = worldAction.getToItem().getCount() > 1;
ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket( ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket(
dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
BlockUtils.POSITION_ZERO, BlockUtils.POSITION_ZERO,
BlockFace.DOWN BlockFace.DOWN
); );
session.sendDownstreamPacket(dropAllPacket); session.sendDownstreamPacket(dropAllPacket);
if (dropAll) { if (dropAll) {
session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY); session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
} else { } else {
session.getPlayerInventory().getItemInHand().sub(1); session.getPlayerInventory().getItemInHand().sub(1);
} }
});
} }
} }
break; break;
@ -222,7 +220,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) { if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) {
// Delay the interaction in case the client doesn't intend to actually use the bucket // Delay the interaction in case the client doesn't intend to actually use the bucket
// See BedrockActionTranslator.java // See BedrockActionTranslator.java
session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> { session.setBucketScheduledFuture(session.getEventLoop().schedule(() -> {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket); session.sendDownstreamPacket(itemPacket);
}, 5, TimeUnit.MILLISECONDS)); }, 5, TimeUnit.MILLISECONDS));

View file

@ -45,6 +45,6 @@ public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStac
return; return;
InventoryTranslator translator = session.getInventoryTranslator(); InventoryTranslator translator = session.getInventoryTranslator();
session.addInventoryTask(() -> translator.translateRequests(session, inventory, packet.getRequests())); translator.translateRequests(session, inventory, packet.getRequests());
} }
} }

View file

@ -37,6 +37,8 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
import java.util.concurrent.TimeUnit;
@Translator(packet = EntityEventPacket.class) @Translator(packet = EntityEventPacket.class)
public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPacket> { public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPacket> {
@ -48,12 +50,10 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
session.sendUpstreamPacket(packet); session.sendUpstreamPacket(packet);
return; return;
case COMPLETE_TRADE: case COMPLETE_TRADE:
session.addInventoryTask(() -> { ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData()); session.sendDownstreamPacket(selectTradePacket);
session.sendDownstreamPacket(selectTradePacket);
});
session.addInventoryTask(() -> { session.getEventLoop().schedule(() -> {
Entity villager = session.getPlayerEntity(); Entity villager = session.getPlayerEntity();
Inventory openInventory = session.getOpenInventory(); Inventory openInventory = session.getOpenInventory();
if (openInventory instanceof MerchantContainer) { if (openInventory instanceof MerchantContainer) {
@ -66,7 +66,7 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
villager.updateBedrockMetadata(session); villager.updateBedrockMetadata(session);
} }
} }
}, 100); }, 100, TimeUnit.MILLISECONDS);
return; return;
} }
session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString()); session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString());

View file

@ -49,11 +49,9 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
entity.setHealth(entity.getMaxHealth()); entity.setHealth(entity.getMaxHealth());
entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute()); entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute());
session.addInventoryTask(() -> { session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); session.setOpenInventory(null);
session.setOpenInventory(null); session.setClosingInventory(false);
session.setClosingInventory(false);
});
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal());

View file

@ -36,14 +36,12 @@ public class JavaPlayerChangeHeldItemTranslator extends PacketTranslator<ServerP
@Override @Override
public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) { public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) {
session.addInventoryTask(() -> { PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket();
PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); hotbarPacket.setContainerId(0);
hotbarPacket.setContainerId(0); hotbarPacket.setSelectedHotbarSlot(packet.getSlot());
hotbarPacket.setSelectedHotbarSlot(packet.getSlot()); hotbarPacket.setSelectHotbarSlot(true);
hotbarPacket.setSelectHotbarSlot(true); session.sendUpstreamPacket(hotbarPacket);
session.sendUpstreamPacket(hotbarPacket);
session.getPlayerInventory().setHeldItemSlot(packet.getSlot()); session.getPlayerInventory().setHeldItemSlot(packet.getSlot());
});
} }
} }

View file

@ -36,9 +36,8 @@ public class JavaCloseWindowTranslator extends PacketTranslator<ServerCloseWindo
@Override @Override
public void translate(ServerCloseWindowPacket packet, GeyserSession session) { public void translate(ServerCloseWindowPacket packet, GeyserSession session) {
session.addInventoryTask(() -> // Sometimes the server can request a window close of ID 0... when the window isn't even open
// Sometimes the server can request a window close of ID 0... when the window isn't even open // Don't confirm in this instance
// Don't confirm in this instance InventoryUtils.closeInventory(session, packet.getWindowId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getWindowId()));
InventoryUtils.closeInventory(session, packet.getWindowId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getWindowId())));
} }
} }

View file

@ -31,48 +31,46 @@ import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
@Translator(packet = ServerOpenWindowPacket.class) @Translator(packet = ServerOpenWindowPacket.class)
public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> { public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> {
@Override @Override
public void translate(ServerOpenWindowPacket packet, GeyserSession session) { public void translate(ServerOpenWindowPacket packet, GeyserSession session) {
session.addInventoryTask(() -> { if (packet.getWindowId() == 0) {
if (packet.getWindowId() == 0) { return;
return; }
}
InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType()); InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType());
Inventory openInventory = session.getOpenInventory(); Inventory openInventory = session.getOpenInventory();
//No translator exists for this window type. Close all windows and return. //No translator exists for this window type. Close all windows and return.
if (newTranslator == null) { if (newTranslator == null) {
if (openInventory != null) {
InventoryUtils.closeInventory(session, openInventory.getId(), true);
}
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId());
session.sendDownstreamPacket(closeWindowPacket);
return;
}
String name = MessageTranslator.convertMessageLenient(packet.getName(), session.getLocale());
name = LocaleUtils.getLocaleString(name, session.getLocale());
Inventory newInventory = newTranslator.createInventory(name, packet.getWindowId(), packet.getType(), session.getPlayerInventory());
if (openInventory != null) { if (openInventory != null) {
// If the window type is the same, don't close. InventoryUtils.closeInventory(session, openInventory.getId(), true);
// In rare cases, inventories can do funny things where it keeps the same window type up but change the contents.
if (openInventory.getWindowType() != packet.getType()) {
// Sometimes the server can double-open an inventory with the same ID - don't confirm in that instance.
InventoryUtils.closeInventory(session, openInventory.getId(), openInventory.getId() != packet.getWindowId());
}
} }
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId());
session.sendDownstreamPacket(closeWindowPacket);
return;
}
session.setInventoryTranslator(newTranslator); String name = MessageTranslator.convertMessageLenient(packet.getName(), session.getLocale());
InventoryUtils.openInventory(session, newInventory); name = LocaleUtils.getLocaleString(name, session.getLocale());
});
Inventory newInventory = newTranslator.createInventory(name, packet.getWindowId(), packet.getType(), session.getPlayerInventory());
if (openInventory != null) {
// If the window type is the same, don't close.
// In rare cases, inventories can do funny things where it keeps the same window type up but change the contents.
if (openInventory.getWindowType() != packet.getType()) {
// Sometimes the server can double-open an inventory with the same ID - don't confirm in that instance.
InventoryUtils.closeInventory(session, openInventory.getId(), openInventory.getId() != packet.getWindowId());
}
}
session.setInventoryTranslator(newTranslator);
InventoryUtils.openInventory(session, newInventory);
} }
} }

View file

@ -59,39 +59,37 @@ public class JavaSetSlotTranslator extends PacketTranslator<ServerSetSlotPacket>
@Override @Override
public void translate(ServerSetSlotPacket packet, GeyserSession session) { public void translate(ServerSetSlotPacket packet, GeyserSession session) {
session.addInventoryTask(() -> { if (packet.getWindowId() == 255) { //cursor
if (packet.getWindowId() == 255) { //cursor GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); session.getPlayerInventory().setCursor(newItem, session);
session.getPlayerInventory().setCursor(newItem, session); InventoryUtils.updateCursor(session);
InventoryUtils.updateCursor(session); return;
return; }
//TODO: support window id -2, should update player inventory
Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
if (inventory == null)
return;
inventory.setStateId(packet.getStateId());
InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) {
if (session.getCraftingGridFuture() != null) {
session.getCraftingGridFuture().cancel(false);
} }
session.setCraftingGridFuture(session.getEventLoop().schedule(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS));
//TODO: support window id -2, should update player inventory GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); if (packet.getWindowId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
if (inventory == null) // In rare cases, the window ID can still be 0 but Java treats it as valid
return; session.getPlayerInventory().setItem(packet.getSlot(), newItem, session);
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), packet.getSlot());
inventory.setStateId(packet.getStateId()); } else {
inventory.setItem(packet.getSlot(), newItem, session);
InventoryTranslator translator = session.getInventoryTranslator(); translator.updateSlot(session, inventory, packet.getSlot());
if (translator != null) {
if (session.getCraftingGridFuture() != null) {
session.getCraftingGridFuture().cancel(false);
}
session.setCraftingGridFuture(session.getConnector().getGeneralThreadPool().schedule(() -> session.addInventoryTask(() -> updateCraftingGrid(session, packet, inventory, translator)), 150, TimeUnit.MILLISECONDS));
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
if (packet.getWindowId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
// In rare cases, the window ID can still be 0 but Java treats it as valid
session.getPlayerInventory().setItem(packet.getSlot(), newItem, session);
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), packet.getSlot());
} else {
inventory.setItem(packet.getSlot(), newItem, session);
translator.updateSlot(session, inventory, packet.getSlot());
}
} }
}); }
} }
private static void updateCraftingGrid(GeyserSession session, ServerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { private static void updateCraftingGrid(GeyserSession session, ServerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) {

View file

@ -39,25 +39,23 @@ public class JavaWindowItemsTranslator extends PacketTranslator<ServerWindowItem
@Override @Override
public void translate(ServerWindowItemsPacket packet, GeyserSession session) { public void translate(ServerWindowItemsPacket packet, GeyserSession session) {
session.addInventoryTask(() -> { Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); if (inventory == null)
if (inventory == null) return;
return;
inventory.setStateId(packet.getStateId()); inventory.setStateId(packet.getStateId());
for (int i = 0; i < packet.getItems().length; i++) { for (int i = 0; i < packet.getItems().length; i++) {
GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]);
inventory.setItem(i, newItem, session); inventory.setItem(i, newItem, session);
} }
InventoryTranslator translator = session.getInventoryTranslator(); InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) { if (translator != null) {
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);
} }
session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session); session.getPlayerInventory().setCursor(GeyserItemStack.from(packet.getCarriedItem()), session);
InventoryUtils.updateCursor(session); InventoryUtils.updateCursor(session);
});
} }
} }

View file

@ -38,15 +38,13 @@ public class JavaWindowPropertyTranslator extends PacketTranslator<ServerWindowP
@Override @Override
public void translate(ServerWindowPropertyPacket packet, GeyserSession session) { public void translate(ServerWindowPropertyPacket packet, GeyserSession session) {
session.addInventoryTask(() -> { Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId());
Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); if (inventory == null)
if (inventory == null) return;
return;
InventoryTranslator translator = session.getInventoryTranslator(); InventoryTranslator translator = session.getInventoryTranslator();
if (translator != null) { if (translator != null) {
translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue()); translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue());
} }
});
} }
} }

View file

@ -147,7 +147,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
if (session.getUpstream().isInitialized()) { if (session.getUpstream().isInitialized()) {
player.spawnEntity(session); player.spawnEntity(session);
SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.getConnector().getGeneralThreadPool().schedule(() -> { SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.getEventLoop().schedule(() -> {
// Delay to minimize split-second "player" pop-in // Delay to minimize split-second "player" pop-in
player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
player.updateBedrockMetadata(session); player.updateBedrockMetadata(session);

View file

@ -37,7 +37,6 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Container;
import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.GeyserItemStack;
@ -80,17 +79,16 @@ public class InventoryUtils {
if (translator != null) { if (translator != null) {
translator.prepareInventory(session, inventory); translator.prepareInventory(session, inventory);
if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> session.getEventLoop().schedule(() -> {
session.addInventoryTask(() -> { Inventory openInv = session.getOpenInventory();
Inventory openInv = session.getOpenInventory(); if (openInv != null && openInv.getId() == inventory.getId()) {
if (openInv != null && openInv.getId() == inventory.getId()) { translator.openInventory(session, inventory);
translator.openInventory(session, inventory); translator.updateInventory(session, inventory);
translator.updateInventory(session, inventory); } else if (openInv != null && openInv.isPending()) {
} else if (openInv != null && openInv.isPending()) { // Presumably, this inventory is no longer relevant, and the client doesn't care about it
// Presumably, this inventory is no longer relevant, and the client doesn't care about it displayInventory(session, openInv);
displayInventory(session, openInv); }
} }, 200, TimeUnit.MILLISECONDS);
}), 200, TimeUnit.MILLISECONDS);
} else { } else {
translator.openInventory(session, inventory); translator.openInventory(session, inventory);
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);