mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Cache the Keep Alive timestamp for forwarding ping (#4002)
* Cache the (clientbound) Keep Alive timestamp and use that for forwarding ping * Use a Queue of keep alive IDs to handle KeepAlive packets sent in succession * Don't force NetworkStackLatencyTranslator on the session's event loop * Send clientbound NetworkStackLatencyPacket immediately * Avoid sending negative NetworkStackLatencyPackets that are not from the form-image-hack in FormCache * Downsize timestamps that would lead to overflow on the client
This commit is contained in:
parent
d89b55e9ac
commit
7b409fd55b
4 changed files with 67 additions and 33 deletions
|
@ -114,6 +114,7 @@ import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.api.network.RemoteServer;
|
import org.geysermc.geyser.api.network.RemoteServer;
|
||||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||||
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
||||||
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
@ -155,6 +156,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -564,6 +566,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||||
@Setter
|
@Setter
|
||||||
private ScheduledFuture<?> mountVehicleScheduledFuture = null;
|
private ScheduledFuture<?> mountVehicleScheduledFuture = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server.
|
||||||
|
* Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled.
|
||||||
|
*/
|
||||||
|
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
private MinecraftProtocol protocol;
|
private MinecraftProtocol protocol;
|
||||||
|
|
||||||
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
|
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
|
||||||
|
|
|
@ -42,6 +42,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class FormCache {
|
public class FormCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The magnitude of this doesn't actually matter, but it must be negative so that
|
||||||
|
* BedrockNetworkStackLatencyTranslator can detect the hack.
|
||||||
|
*/
|
||||||
|
private static final long MAGIC_FORM_IMAGE_HACK_TIMESTAMP = -1234567890L;
|
||||||
|
|
||||||
private final FormDefinitions formDefinitions = FormDefinitions.instance();
|
private final FormDefinitions formDefinitions = FormDefinitions.instance();
|
||||||
private final AtomicInteger formIdCounter = new AtomicInteger(0);
|
private final AtomicInteger formIdCounter = new AtomicInteger(0);
|
||||||
private final Int2ObjectMap<Form> forms = new Int2ObjectOpenHashMap<>();
|
private final Int2ObjectMap<Form> forms = new Int2ObjectOpenHashMap<>();
|
||||||
|
@ -73,7 +80,7 @@ public class FormCache {
|
||||||
if (form instanceof SimpleForm) {
|
if (form instanceof SimpleForm) {
|
||||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||||
latencyPacket.setFromServer(true);
|
latencyPacket.setFromServer(true);
|
||||||
latencyPacket.setTimestamp(-System.currentTimeMillis());
|
latencyPacket.setTimestamp(MAGIC_FORM_IMAGE_HACK_TIMESTAMP);
|
||||||
session.scheduleInEventLoop(
|
session.scheduleInEventLoop(
|
||||||
() -> session.sendUpstreamPacket(latencyPacket),
|
() -> session.sendUpstreamPacket(latencyPacket),
|
||||||
500, TimeUnit.MILLISECONDS
|
500, TimeUnit.MILLISECONDS
|
||||||
|
|
|
@ -29,9 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundKe
|
||||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
import org.geysermc.floodgate.util.DeviceOs;
|
|
||||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
import org.geysermc.geyser.network.GameProtocol;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
@ -47,42 +45,43 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, NetworkStackLatencyPacket packet) {
|
public void translate(GeyserSession session, NetworkStackLatencyPacket packet) {
|
||||||
long pingId;
|
|
||||||
// so apparently, as of 1.16.200
|
|
||||||
// PS4 divides the network stack latency timestamp FOR US!!!
|
|
||||||
// WTF
|
|
||||||
if (GameProtocol.isPre1_20_10(session)) {
|
|
||||||
if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
|
|
||||||
pingId = packet.getTimestamp();
|
|
||||||
} else {
|
|
||||||
pingId = packet.getTimestamp() / 1000;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// changed in 1.20.10 todo: is ps4 still different?
|
|
||||||
pingId = packet.getTimestamp() / (1000 * 1000 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// negative timestamps are used as hack to fix the url image loading bug
|
// negative timestamps are used as hack to fix the url image loading bug
|
||||||
if (packet.getTimestamp() > 0) {
|
if (packet.getTimestamp() >= 0) {
|
||||||
if (session.getGeyser().getConfig().isForwardPlayerPing()) {
|
if (session.getGeyser().getConfig().isForwardPlayerPing()) {
|
||||||
ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(pingId);
|
// use our cached value because
|
||||||
|
// a) bedrock can be inaccurate with the value returned
|
||||||
|
// b) playstation replies with a different magnitude than other platforms
|
||||||
|
// c) 1.20.10 and later reply with a different magnitude
|
||||||
|
Long keepAliveId = session.getKeepAliveCache().poll();
|
||||||
|
if (keepAliveId == null) {
|
||||||
|
session.getGeyser().getLogger().debug("Received a latency packet that we don't have a KeepAlive for: " + packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(keepAliveId);
|
||||||
session.sendDownstreamPacket(keepAlivePacket);
|
session.sendDownstreamPacket(keepAlivePacket);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack to fix the url image loading bug
|
session.scheduleInEventLoop(() -> {
|
||||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
// Hack to fix the url image loading bug
|
||||||
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||||
|
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||||
|
|
||||||
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
|
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
attributesPacket.setAttributes(Collections.singletonList(attribute));
|
attributesPacket.setAttributes(Collections.singletonList(attribute));
|
||||||
} else {
|
} else {
|
||||||
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
|
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(attributesPacket),
|
session.sendUpstreamPacket(attributesPacket);
|
||||||
500, TimeUnit.MILLISECONDS);
|
}, 500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldExecuteInEventLoop() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,30 @@ public class JavaKeepAliveTranslator extends PacketTranslator<ClientboundKeepAli
|
||||||
if (!session.getGeyser().getConfig().isForwardPlayerPing()) {
|
if (!session.getGeyser().getConfig().isForwardPlayerPing()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We use this once the client replies (see BedrockNetworkStackLatencyTranslator)
|
||||||
|
session.getKeepAliveCache().add(packet.getPingId());
|
||||||
|
|
||||||
|
long timestamp = packet.getPingId();
|
||||||
|
|
||||||
|
// We take the abs because we rely on the client responding with a negative value ONLY when we send
|
||||||
|
// a negative timestamp in the form-image-hack performed in FormCache.
|
||||||
|
// Apart from that case, we don't actually use the value the client responds with, instead using our keep alive cache.
|
||||||
|
if (timestamp == Long.MIN_VALUE) {
|
||||||
|
timestamp = Long.MAX_VALUE; // There is not an abs representation of MIN_VALUE (2's complement)
|
||||||
|
} else {
|
||||||
|
timestamp = Math.absExact(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bedrock will overflow on timestamps that are too large, and respond with a mangled negative value.
|
||||||
|
// Keeping leftmost digits allows for easier debugging
|
||||||
|
while (timestamp > 1e10) {
|
||||||
|
timestamp /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||||
latencyPacket.setFromServer(true);
|
latencyPacket.setFromServer(true);
|
||||||
latencyPacket.setTimestamp(packet.getPingId() * 1000);
|
latencyPacket.setTimestamp(timestamp);
|
||||||
session.sendUpstreamPacket(latencyPacket);
|
session.sendUpstreamPacketImmediately(latencyPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue