From b26b9d17fbedbddef192e88a9a4fb39e3594396b Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 30 Nov 2025 02:25:43 +0100 Subject: [PATCH] Implement dirty entities to send updates once a tick --- .../geyser/entity/GeyserDirtyMetadata.java | 5 ----- .../org/geysermc/geyser/entity/type/Entity.java | 17 +++++++++-------- .../geysermc/geyser/session/GeyserSession.java | 8 +++++++- .../geyser/session/cache/EntityCache.java | 9 +++++++++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java index 0aa15691f..1ee84f4b5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserDirtyMetadata.java @@ -26,24 +26,19 @@ package org.geysermc.geyser.entity; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import lombok.AllArgsConstructor; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType; -import org.geysermc.geyser.entity.type.Entity; import java.util.Map; /** * A wrapper for temporarily storing entity metadata that will be sent to Bedrock. */ -@AllArgsConstructor public final class GeyserDirtyMetadata { - private final Entity entity; private final Map, Object> metadata = new Object2ObjectLinkedOpenHashMap<>(); public void put(EntityDataType entityData, T value) { metadata.put(entityData, value); - entity.getMetadata().put(entityData, value); } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 8bdd9d547..1a33012e3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -145,7 +145,7 @@ public class Entity implements GeyserEntity { /** * A container to store temporary metadata before it's sent to Bedrock. */ - protected final GeyserDirtyMetadata dirtyMetadata = new GeyserDirtyMetadata(this); + protected final GeyserDirtyMetadata dirtyMetadata = new GeyserDirtyMetadata(); /** * A container storing all current metadata for an entity. */ @@ -413,7 +413,7 @@ public class Entity implements GeyserEntity { return; } - if (dirtyMetadata.hasEntries() || flagsDirty) { + if (dirtyMetadata.hasEntries() || flagsDirty || (propertyManager != null && propertyManager.hasProperties())) { SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); entityDataPacket.setRuntimeEntityId(geyserId); if (flagsDirty) { @@ -837,20 +837,20 @@ public class Entity implements GeyserEntity { }); if (propertyManager.hasProperties()) { - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.setRuntimeEntityId(geyserId()); - propertyManager.applyFloatProperties(packet.getProperties().getFloatProperties()); - propertyManager.applyIntProperties(packet.getProperties().getIntProperties()); if (immediate) { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId()); + propertyManager.applyFloatProperties(packet.getProperties().getFloatProperties()); + propertyManager.applyIntProperties(packet.getProperties().getIntProperties()); session.sendUpstreamPacketImmediately(packet); } else { - session.sendUpstreamPacket(packet); + session.getEntityCache().markDirty(this); } } } public void offset(float offset) { - // TODO queue!! + // TODO queue? if (isValid()) { this.moveRelative(0, offset, 0, 0, 0, isOnGround()); } @@ -875,6 +875,7 @@ public class Entity implements GeyserEntity { public void update(@NonNull GeyserEntityDataType dataType, @Nullable T value) { if (dataType instanceof GeyserEntityDataImpl geyserEntityDataImpl) { geyserEntityDataImpl.update(this, value); + session.getEntityCache().markDirty(this); } else { throw new IllegalArgumentException("Invalid data type: " + dataType.getClass().getSimpleName()); } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 8c29870f6..fed487cdd 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -125,8 +125,8 @@ import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfig; -import org.geysermc.geyser.entity.VanillaEntities; import org.geysermc.geyser.entity.GeyserEntityData; +import org.geysermc.geyser.entity.VanillaEntities; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -235,6 +235,7 @@ import java.util.BitSet; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -1262,6 +1263,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { clientVehicle.getVehicleComponent().tickVehicle(); } + for (Iterator it = entityCache.getDirtyEntities().iterator(); it.hasNext(); ) { + it.next().updateBedrockMetadata(); + it.remove(); + } + for (Tickable entity : entityCache.getTickableEntities()) { entity.drawTick(); if (gameShouldUpdate) { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 45fc536ad..83d0f68ee 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.Getter; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Tickable; @@ -42,6 +43,7 @@ import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -63,6 +65,8 @@ public class EntityCache { private final Object2LongMap entityUuidTranslations = new Object2LongOpenHashMap<>(); private final Map playerEntities = new Object2ObjectOpenHashMap<>(); private final Map bossBars = new Object2ObjectOpenHashMap<>(); + @Getter + private final Set dirtyEntities = new ObjectOpenHashSet<>(); @Getter private final AtomicLong nextEntityId = new AtomicLong(2L); @@ -123,6 +127,11 @@ public class EntityCache { if (entity instanceof Tickable) { tickableEntities.remove(entity); } + dirtyEntities.remove(entity); + } + + public void markDirty(Entity entity) { + dirtyEntities.add(entity); } public void removeAllEntities() {