Handle chunks on the player thread

This commit is contained in:
Camotoy 2021-11-13 11:03:55 -05:00
parent 59e6fc0285
commit 393c2b0f91
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
28 changed files with 219 additions and 239 deletions

View file

@ -36,6 +36,7 @@ import com.nukkitx.protocol.bedrock.BedrockServer;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.kqueue.KQueue;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.Getter;
import lombok.Setter;
@ -118,7 +119,7 @@ public class GeyserConnector {
private volatile boolean shuttingDown = false;
private final ScheduledExecutorService generalThreadPool;
private final ScheduledExecutorService scheduledThread;
private final BedrockServer bedrockServer;
private final PlatformType platformType;
@ -144,7 +145,7 @@ public class GeyserConnector {
logger.info("");
logger.info("******************************************");
this.generalThreadPool = Executors.newScheduledThreadPool(config.getGeneralThreadPool());
this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread"));
logger.setDebug(config.isDebugMode());
@ -404,7 +405,7 @@ public class GeyserConnector {
bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.kick.done"));
}
generalThreadPool.shutdown();
scheduledThread.shutdown();
bedrockServer.close();
if (timeSyncer != null) {
timeSyncer.shutdown();

View file

@ -97,7 +97,7 @@ public final class LocalSession extends TcpSession {
exceptionCaught(null, future.cause());
}
});
} catch(Throwable t) {
} catch (Throwable t) {
exceptionCaught(null, t);
}
}

View file

@ -68,8 +68,6 @@ public interface GeyserConfiguration {
boolean isDebugMode();
int getGeneralThreadPool();
boolean isAllowThirdPartyCapes();
boolean isAllowThirdPartyEars();

View file

@ -96,9 +96,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("debug-mode")
private boolean debugMode = false;
@JsonProperty("general-thread-pool")
private int generalThreadPool = 32;
@JsonProperty("allow-third-party-capes")
private boolean allowThirdPartyCapes = true;

View file

@ -133,9 +133,7 @@ public class BoatEntity extends Entity {
// Get the entity by the first stored passenger and convey motion in this manner
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateLeftPaddle(session, entity)
);
updateLeftPaddle(session, entity);
}
}
} else {
@ -150,9 +148,7 @@ public class BoatEntity extends Entity {
if (!this.passengers.isEmpty()) {
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entity)
);
updateRightPaddle(session, entity);
}
}
} else {
@ -180,7 +176,7 @@ public class BoatEntity extends Entity {
paddleTimeLeft += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft);
session.getConnector().getGeneralThreadPool().schedule(() ->
session.scheduleInEventLoop(() ->
updateLeftPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
@ -193,7 +189,7 @@ public class BoatEntity extends Entity {
paddleTimeRight += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight);
session.getConnector().getGeneralThreadPool().schedule(() ->
session.scheduleInEventLoop(() ->
updateRightPaddle(session, rower),
100,
TimeUnit.MILLISECONDS

View file

@ -42,8 +42,6 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.registry.type.ItemMapping;
import java.util.concurrent.TimeUnit;
/**
* Item frames are an entity in Java but a block entity in Bedrock.
*/
@ -99,11 +97,8 @@ public class ItemFrameEntity extends Entity {
session.getItemFrameCache().put(bedrockPosition, this);
// Delay is required, or else loading in frames on chunk load is sketchy at best
session.getConnector().getGeneralThreadPool().schedule(() -> {
updateBlock(session);
session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId);
}, 500, TimeUnit.MILLISECONDS);
updateBlock(session);
session.getConnector().getLogger().debug("Spawned item frame at location " + bedrockPosition + " with java id " + entityId);
valid = true;
}

View file

@ -29,13 +29,11 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TNTEntity extends Entity {
public class TNTEntity extends Entity implements Tickable {
private int currentTick;
@ -49,16 +47,26 @@ public class TNTEntity extends Entity {
currentTick = (int) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.IGNITED, true);
metadata.put(EntityData.FUSE_LENGTH, currentTick);
ScheduledFuture<?> future = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
if (currentTick % 5 == 0) {
metadata.put(EntityData.FUSE_LENGTH, currentTick);
}
currentTick--;
super.updateBedrockMetadata(entityMetadata, session);
}, 50, 50, TimeUnit.MILLISECONDS); // 5 ticks
session.getConnector().getGeneralThreadPool().schedule(() -> future.cancel(true), (int) entityMetadata.getValue() / 20, TimeUnit.SECONDS);
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void tick(GeyserSession session) {
if (currentTick == 0) {
// No need to update the fuse when there is none
return;
}
if (currentTick % 5 == 0) {
metadata.put(EntityData.FUSE_LENGTH, currentTick);
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityData.FUSE_LENGTH, currentTick);
session.sendUpstreamPacket(packet);
}
currentTick--;
}
}

View file

@ -129,7 +129,7 @@ public class PlayerEntity extends LivingEntity {
if (session.getEntityCache().getPlayerEntity(uuid) == null)
return;
if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
if (session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
session.getEntityCache().spawnEntity(this);
} else {
spawnEntity(session);
@ -288,7 +288,7 @@ public class PlayerEntity extends LivingEntity {
linkPacket.setEntityLink(new EntityLinkData(geyserId, parrot.getGeyserId(), type, false, false));
// Delay, or else spawned-in players won't get the link
// TODO: Find a better solution. This problem also exists with item frames
session.getConnector().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS);
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(linkPacket), 500, TimeUnit.MILLISECONDS);
if (isLeft) {
leftParrot = parrot;
} else {

View file

@ -114,7 +114,7 @@ public class Metrics {
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
connector.getGeneralThreadPool().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES);
connector.getScheduledThread().scheduleAtFixedRate(this::submitData, 1, 30, TimeUnit.MINUTES);
// Submit the data every 30 minutes, first time after 1 minutes to give other plugins enough time to start
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!

View file

@ -104,6 +104,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
}
resourcePacksInfo.setForcedToAccept(GeyserConnector.getInstance().getConfig().isForceResourcePacks());
session.sendUpstreamPacket(resourcePacksInfo);
LanguageUtils.loadGeyserLocale(session.getLocale());
return true;
}
@ -111,7 +113,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
public boolean handle(ResourcePackClientResponsePacket packet) {
switch (packet.getStatus()) {
case COMPLETED:
session.connect();
if (connector.getConfig().getRemote().getAuthType() != AuthType.ONLINE) {
session.authenticate(session.getAuthData().getName());
} else if (!couldLoginUserByName(session.getAuthData().getName())) {
// We must spawn the white world
session.connect();
}
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName()));
break;
@ -182,9 +189,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
session.setMicrosoftAccount(info.isMicrosoftAccount());
session.authenticate(info.getEmail(), info.getPassword());
// TODO send a message to bedrock user telling them they are connected (if nothing like a motd
// somes from the Java server w/in a few seconds)
return true;
}
}
@ -192,20 +196,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
return false;
}
@Override
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
LanguageUtils.loadGeyserLocale(session.getLocale());
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) {
// TODO it is safer to key authentication on something that won't change (UUID, not username)
if (!couldLoginUserByName(session.getAuthData().getName())) {
LoginEncryptionUtils.buildAndShowLoginWindow(session);
}
// else we were able to log the user in
}
return translateAndDefault(packet);
}
@Override
public boolean handle(MovePlayerPacket packet) {
if (session.isLoggingIn()) {

View file

@ -229,6 +229,8 @@ public class GeyserSession implements CommandSender {
private Vector2i lastChunkPosition = null;
private int renderDistance;
private boolean sentSpawnPacket;
private boolean loggedIn;
private boolean loggingIn;
@ -501,6 +503,10 @@ public class GeyserSession implements CommandSender {
disconnect(disconnectReason.name());
connector.getSessionManager().removeSession(this);
});
this.remoteAddress = connector.getConfig().getRemote().getAddress();
this.remotePort = connector.getConfig().getRemote().getPort();
this.remoteAuthType = connector.getConfig().getRemote().getAuthType();
}
/**
@ -508,9 +514,7 @@ public class GeyserSession implements CommandSender {
*/
public void connect() {
startGame();
this.remoteAddress = connector.getConfig().getRemote().getAddress();
this.remotePort = connector.getConfig().getRemote().getPort();
this.remoteAuthType = connector.getConfig().getRemote().getAuthType();
sentSpawnPacket = true;
// Set the hardcoded shield ID to the ID we just defined in StartGamePacket
upstream.getSession().getHardcodedBlockingId().set(this.itemMappings.getStoredItems().shield().getBedrockId());
@ -685,27 +689,36 @@ public class GeyserSession implements CommandSender {
if (loggedIn || closed) {
return;
}
try {
msaAuthenticationService.login();
GameProfile profile = msaAuthenticationService.getSelectedProfile();
if (profile == null) {
// Java account is offline
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
CompletableFuture.supplyAsync(() -> {
try {
msaAuthenticationService.login();
GameProfile profile = msaAuthenticationService.getSelectedProfile();
if (profile == null) {
// Java account is offline
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
return null;
}
return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken());
} catch (RequestException e) {
throw new CompletionException(e);
}
}).whenComplete((response, ex) -> {
if (ex != null) {
if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) {
connector.getLogger().error("Failed to log in with Microsoft code!", ex);
disconnect(ex.toString());
} else {
// Wait one second before trying again
connector.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
}
return;
}
protocol = new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken());
connectDownstream();
} catch (RequestException e) {
if (!(e instanceof AuthPendingException)) {
connector.getLogger().error("Failed to log in with Microsoft code!", e);
disconnect(e.toString());
} else {
// Wait one second before trying again
connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
if (!closed) {
this.protocol = response;
connectDownstream();
}
}
});
}
/**
@ -725,6 +738,7 @@ public class GeyserSession implements CommandSender {
downstream = new TcpClientSession(this.remoteAddress, this.remotePort, this.protocol);
disableSrvResolving();
}
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
@ -1133,7 +1147,11 @@ public class GeyserSession implements CommandSender {
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(playerEntity.getGeyserId());
startGamePacket.setRuntimeEntityId(playerEntity.getGeyserId());
startGamePacket.setPlayerGameType(GameType.SURVIVAL);
startGamePacket.setPlayerGameType(switch (gameMode) {
case CREATIVE -> GameType.CREATIVE;
case ADVENTURE -> GameType.ADVENTURE;
default -> GameType.SURVIVAL;
});
startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0));
startGamePacket.setRotation(Vector2f.from(1, 1));

View file

@ -65,8 +65,7 @@ public class FormCache {
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(-System.currentTimeMillis());
session.getConnector().getGeneralThreadPool().schedule(
() -> session.sendUpstreamPacket(latencyPacket),
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(latencyPacket),
500, TimeUnit.MILLISECONDS);
}

View file

@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket;
import com.nukkitx.protocol.bedrock.packet.MapInfoRequestPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -44,7 +43,7 @@ public class BedrockMapInfoRequestTranslator extends PacketTranslator<MapInfoReq
ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId);
if (mapPacket != null) {
// Delay the packet 100ms to prevent the client from ignoring the packet
GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> session.sendUpstreamPacket(mapPacket),
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket),
100, TimeUnit.MILLISECONDS);
}
}

View file

@ -61,7 +61,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
// Activate shield since we are already sneaking
// (No need to send a release item packet - Java doesn't do this when swapping items)
// Required to do it a tick later or else it doesn't register
session.getConnector().getGeneralThreadPool().schedule(() -> session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND)),
session.scheduleInEventLoop(() -> session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND)),
50, TimeUnit.MILLISECONDS);
}

View file

@ -76,8 +76,7 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
}
session.getConnector().getGeneralThreadPool().schedule(
() -> session.sendUpstreamPacket(attributesPacket),
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(attributesPacket),
500, TimeUnit.MILLISECONDS);
}
}

View file

@ -43,7 +43,7 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator<Ser
int windowId = session.getFormCache().addForm(window);
// Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay
session.getConnector().getGeneralThreadPool().schedule(() -> {
session.scheduleInEventLoop(() -> {
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
serverSettingsResponsePacket.setFormData(window.getJsonData());
serverSettingsResponsePacket.setFormId(windowId);

View file

@ -25,14 +25,12 @@
package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.skin.SkullSkinManager;
import org.geysermc.connector.utils.LoginEncryptionUtils;
@Translator(packet = SetLocalPlayerAsInitializedPacket.class)
public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator<SetLocalPlayerAsInitializedPacket> {
@ -41,23 +39,12 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) {
if (!session.getUpstream().isInitialized()) {
session.getUpstream().setInitialized(true);
session.login();
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
if (!entity.isValid()) {
SkinManager.requestAndHandleSkinAndCape(entity, session, null);
entity.sendPlayer(session);
if (session.getRemoteAuthType() == AuthType.ONLINE) {
if (!session.isLoggedIn()) {
LoginEncryptionUtils.buildAndShowLoginWindow(session);
}
}
// Send Skulls
for (PlayerEntity entity : session.getSkullCache().values()) {
entity.spawnEntity(session);
SkullSkinManager.requestAndHandleSkin(entity, session, (skin) -> {
entity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
entity.updateBedrockMetadata(session);
});
// else we were able to log the user in
}
}
}

View file

@ -70,19 +70,25 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
BiomeTranslator.loadServerBiomes(session, packet.getDimensionCodec());
session.getTagCache().clear();
session.setGameMode(packet.getGameMode());
boolean needsSpawnPacket = !session.isSentSpawnPacket();
if (needsSpawnPacket) {
// The player has yet to spawn so let's do that using some of the information in this Java packet
session.setDimension(newDimension);
session.connect();
}
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId());
bedrockPacket.setPlayerPermission(PlayerPermission.MEMBER);
session.sendUpstreamPacket(bedrockPacket);
PlayStatusPacket playStatus = new PlayStatusPacket();
playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
// session.sendPacket(playStatus);
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(packet.getGameMode().ordinal());
session.sendUpstreamPacket(playerGameTypePacket);
session.setGameMode(packet.getGameMode());
if (!needsSpawnPacket) {
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(packet.getGameMode().ordinal());
session.sendUpstreamPacket(playerGameTypePacket);
}
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());

View file

@ -109,7 +109,7 @@ public class JavaPlayerInfoTranslator extends PacketTranslator<ClientboundPlayer
}
}
if (!translate.getEntries().isEmpty() && (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized())) {
if (!translate.getEntries().isEmpty()) {
session.sendUpstreamPacket(translate);
}
}

View file

@ -43,6 +43,8 @@ import org.geysermc.connector.network.translators.world.BiomeTranslator;
import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
import org.geysermc.connector.utils.ChunkUtils;
import java.io.IOException;
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT;
import static org.geysermc.connector.utils.ChunkUtils.MINIMUM_ACCEPTED_HEIGHT_OVERWORLD;
@ -61,85 +63,79 @@ public class JavaLevelChunkTranslator extends PacketTranslator<ClientboundLevelC
// Ensure that, if the player is using lower world heights, the position is not offset
int yOffset = session.getChunkCache().getChunkMinY();
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
try {
if (session.isClosed()) {
return;
}
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, column, yOffset);
ChunkSection[] sections = chunkData.sections();
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, column, yOffset);
ChunkSection[] sections = chunkData.sections();
// Find highest section
int sectionCount = sections.length - 1;
while (sectionCount >= 0 && sections[sectionCount] == null) {
sectionCount--;
}
sectionCount++;
// Find highest section
int sectionCount = sections.length - 1;
while (sectionCount >= 0 && sections[sectionCount] == null) {
sectionCount--;
}
sectionCount++;
// Estimate chunk size
int size = 0;
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = sections[i];
size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize();
}
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
size += 1; // Border blocks
size += 1; // Extra data length (always 0)
size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
// Estimate chunk size
int size = 0;
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = sections[i];
size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize();
}
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
size += 1; // Border blocks
size += 1; // Extra data length (always 0)
size += chunkData.blockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
// Allocate output buffer
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
byte[] payload;
try {
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = sections[i];
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
}
// At this point we're dealing with Bedrock chunk sections
boolean overworld = session.getChunkCache().isExtendedHeight();
int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4;
for (int i = 0; i < sectionCount; i++) {
int biomeYOffset = dimensionOffset + i;
if (biomeYOffset < yOffset) {
// Ignore this biome section since it goes below the height of the Java world
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
continue;
}
BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf);
}
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
int remainingEmptyBiomes = 32 - sectionCount;
for (int i = 0; i < remainingEmptyBiomes; i++) {
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
}
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
// Encode tile entities into buffer
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
for (NbtMap blockEntity : chunkData.blockEntities()) {
nbtStream.writeTag(blockEntity);
}
// Copy data into byte[], because the protocol lib really likes things that are s l o w
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
} finally {
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
}
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(sectionCount);
levelChunkPacket.setCachingEnabled(false);
levelChunkPacket.setChunkX(column.getX());
levelChunkPacket.setChunkZ(column.getZ());
levelChunkPacket.setData(payload);
session.sendUpstreamPacket(levelChunkPacket);
} catch (Exception ex) {
ex.printStackTrace();
// Allocate output buffer
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
byte[] payload;
try {
for (int i = 0; i < sectionCount; i++) {
ChunkSection section = sections[i];
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
}
});
// At this point we're dealing with Bedrock chunk sections
boolean overworld = session.getChunkCache().isExtendedHeight();
int dimensionOffset = (overworld ? MINIMUM_ACCEPTED_HEIGHT_OVERWORLD : MINIMUM_ACCEPTED_HEIGHT) >> 4;
for (int i = 0; i < sectionCount; i++) {
int biomeYOffset = dimensionOffset + i;
if (biomeYOffset < yOffset) {
// Ignore this biome section since it goes below the height of the Java world
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
continue;
}
BiomeTranslator.toNewBedrockBiome(session, column.getBiomeData(), i + (dimensionOffset - yOffset)).writeToNetwork(byteBuf);
}
// As of 1.17.10, Bedrock hardcodes to always read 32 biome sections
int remainingEmptyBiomes = 32 - sectionCount;
for (int i = 0; i < remainingEmptyBiomes; i++) {
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
}
byteBuf.writeByte(0); // Border blocks - Edu edition only
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
// Encode tile entities into buffer
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
for (NbtMap blockEntity : chunkData.blockEntities()) {
nbtStream.writeTag(blockEntity);
}
// Copy data into byte[], because the protocol lib really likes things that are s l o w
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
} catch (IOException e) {
session.getConnector().getLogger().error("IO error while encoding chunk", e);
return;
} finally {
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
}
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(sectionCount);
levelChunkPacket.setCachingEnabled(false);
levelChunkPacket.setChunkX(column.getX());
levelChunkPacket.setChunkZ(column.getZ());
levelChunkPacket.setData(payload);
session.sendUpstreamPacket(levelChunkPacket);
}
}

View file

@ -141,15 +141,12 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
// Cache entity
session.getSkullCache().put(blockPosition, player);
// Only send to session if we are initialized, otherwise it will happen then.
if (session.getUpstream().isInitialized()) {
player.spawnEntity(session);
player.spawnEntity(session);
SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> {
// Delay to minimize split-second "player" pop-in
player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
player.updateBedrockMetadata(session);
}, 250, TimeUnit.MILLISECONDS)));
}
SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> {
// Delay to minimize split-second "player" pop-in
player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
player.updateBedrockMetadata(session);
}, 250, TimeUnit.MILLISECONDS)));
}
}

View file

@ -62,7 +62,7 @@ public class GeyserLegacyPingPassthrough implements IGeyserPingPassthrough, Runn
// Ensure delay is not zero
int interval = (connector.getConfig().getPingPassthroughInterval() == 0) ? 1 : connector.getConfig().getPingPassthroughInterval();
connector.getLogger().debug("Scheduling ping passthrough at an interval of " + interval + " second(s).");
connector.getGeneralThreadPool().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS);
connector.getScheduledThread().scheduleAtFixedRate(pingPassthrough, 1, interval, TimeUnit.SECONDS);
return pingPassthrough;
}
return null;

View file

@ -212,14 +212,14 @@ public final class FloodgateSkinUploader {
private void reconnectLater(GeyserConnector connector) {
// we ca only reconnect when the thread pool is open
if (connector.getGeneralThreadPool().isShutdown() || closed) {
if (connector.getScheduledThread().isShutdown() || closed) {
logger.info("The skin uploader has been closed");
return;
}
long additionalTime = ThreadLocalRandom.current().nextInt(7);
// we don't have to check the result. onClose will handle that for us
connector.getGeneralThreadPool()
connector.getScheduledThread()
.schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS);
}

View file

@ -167,31 +167,29 @@ public class SkinManager {
}
}
if (session.getUpstream().isInitialized()) {
PlayerListPacket.Entry updatedEntry = buildEntryManually(
session,
entity.getUuid(),
entity.getUsername(),
entity.getGeyserId(),
skin.getTextureUrl(),
skin.getSkinData(),
cape.getCapeId(),
cape.getCapeData(),
geometry
);
PlayerListPacket.Entry updatedEntry = buildEntryManually(
session,
entity.getUuid(),
entity.getUsername(),
entity.getGeyserId(),
skin.getTextureUrl(),
skin.getSkinData(),
cape.getCapeId(),
cape.getCapeData(),
geometry
);
PlayerListPacket playerAddPacket = new PlayerListPacket();
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
playerAddPacket.getEntries().add(updatedEntry);
session.sendUpstreamPacket(playerAddPacket);
PlayerListPacket playerAddPacket = new PlayerListPacket();
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
playerAddPacket.getEntries().add(updatedEntry);
session.sendUpstreamPacket(playerAddPacket);
if (!entity.isPlayerList()) {
PlayerListPacket playerRemovePacket = new PlayerListPacket();
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
playerRemovePacket.getEntries().add(updatedEntry);
session.sendUpstreamPacket(playerRemovePacket);
}
if (!entity.isPlayerList()) {
PlayerListPacket playerRemovePacket = new PlayerListPacket();
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
playerRemovePacket.getEntries().add(updatedEntry);
session.sendUpstreamPacket(playerRemovePacket);
}
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);

View file

@ -101,7 +101,7 @@ public class SkinProvider {
// Schedule Daily Image Expiry if we are caching them
if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) {
GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> {
GeyserConnector.getInstance().getScheduledThread().scheduleAtFixedRate(() -> {
File cacheFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("images").toFile();
if (!cacheFolder.exists()) {
return;

View file

@ -55,15 +55,13 @@ public class SkullSkinManager extends SkinManager {
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
.whenCompleteAsync((skin, throwable) -> {
try {
if (session.getUpstream().isInitialized()) {
PlayerSkinPacket packet = new PlayerSkinPacket();
packet.setUuid(entity.getUuid());
packet.setOldSkinName("");
packet.setNewSkinName(skin.getTextureUrl());
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet);
}
PlayerSkinPacket packet = new PlayerSkinPacket();
packet.setUuid(entity.getUuid());
packet.setOldSkinName("");
packet.setNewSkinName(skin.getTextureUrl());
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet);
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
}

View file

@ -92,7 +92,8 @@ public class CooldownUtils {
titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket);
if (hasCooldown(session)) {
session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50
session.scheduleInEventLoop(() ->
computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50
} else {
SetTitlePacket removeTitlePacket = new SetTitlePacket();
if (sessionPreference == CooldownType.ACTIONBAR) {

View file

@ -110,9 +110,6 @@ max-players: 100
# If debug messages should be sent through console
debug-mode: false
# Thread pool size
general-thread-pool: 32
# Allow third party capes to be visible. Currently allowing:
# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes
allow-third-party-capes: true