diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/entity/player/ServerPlayerEntityBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/entity/player/ServerPlayerEntityBridge.java index 7793a44c..c7f60c15 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/entity/player/ServerPlayerEntityBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/entity/player/ServerPlayerEntityBridge.java @@ -37,4 +37,8 @@ public interface ServerPlayerEntityBridge extends PlayerEntityBridge { Entity bridge$changeDimension(ServerLevel world, PlayerTeleportEvent.TeleportCause cause); boolean bridge$initialized(); + + boolean bridge$isTrackerDirty(); + + void bridge$setTrackerDirty(boolean flag); } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/server/ChunkMap_TrackedEntityBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/server/ChunkMap_TrackedEntityBridge.java index e6b68a15..646c640a 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/server/ChunkMap_TrackedEntityBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/server/ChunkMap_TrackedEntityBridge.java @@ -1,8 +1,16 @@ package io.izzel.arclight.common.bridge.core.world.server; +import net.minecraft.core.SectionPos; import net.minecraft.server.level.ServerEntity; +import net.minecraft.world.entity.Entity; public interface ChunkMap_TrackedEntityBridge { ServerEntity bridge$getServerEntity(); + + Entity bridge$getEntity(); + + SectionPos bridge$getLastSectionPos(); + + void bridge$setLastSectionPos(SectionPos pos); } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java index 87eafa5a..279701e8 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java @@ -2,6 +2,7 @@ package io.izzel.arclight.common.mixin.core.server.level; import io.izzel.arclight.common.bridge.core.world.ServerEntityBridge; import io.izzel.arclight.common.bridge.core.world.server.ChunkMap_TrackedEntityBridge; +import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.network.ServerPlayerConnection; @@ -21,6 +22,8 @@ public abstract class ChunkMap_TrackedEntityMixin implements ChunkMap_TrackedEnt // @formatter:off @Shadow @Final ServerEntity serverEntity; @Shadow @Final public Set seenBy; + @Shadow @Final Entity entity; + @Shadow SectionPos lastSectionPos; // @formatter:on @Inject(method = "", at = @At("RETURN")) @@ -32,4 +35,19 @@ public abstract class ChunkMap_TrackedEntityMixin implements ChunkMap_TrackedEnt public ServerEntity bridge$getServerEntity() { return this.serverEntity; } + + @Override + public Entity bridge$getEntity() { + return this.entity; + } + + @Override + public SectionPos bridge$getLastSectionPos() { + return this.lastSectionPos; + } + + @Override + public void bridge$setLastSectionPos(SectionPos pos) { + this.lastSectionPos = pos; + } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ChunkMapMixin_Optimize.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ChunkMapMixin_Optimize.java new file mode 100644 index 00000000..0c3bc257 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ChunkMapMixin_Optimize.java @@ -0,0 +1,88 @@ +package io.izzel.arclight.common.mixin.optimization.general.network; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.bridge.core.world.server.ChunkMap_TrackedEntityBridge; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerPlayerConnection; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.Set; + +@Mixin(ChunkMap.class) +public class ChunkMapMixin_Optimize { + + // @formatter:off + @Shadow @Final public Int2ObjectMap entityMap; + @Shadow @Final public ServerLevel level; + // @formatter:on + + @Redirect(method = "move", at = @At(value = "INVOKE", remap = false, target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;values()Lit/unimi/dsi/fastutil/objects/ObjectCollection;")) + private ObjectCollection arclight$markDirty(Int2ObjectMap instance, ServerPlayer player) { + ((ServerPlayerEntityBridge) player).bridge$setTrackerDirty(true); + return new ObjectArraySet<>(); + } + + @Inject(method = "tick()V", cancellable = true, at = @At("HEAD")) + private void arclight$optimizedTick(CallbackInfo ci) { + var list = new ArrayList(this.level.players().size()); + + for (var trackedEntity : this.entityMap.values()) { + var entity = ((ChunkMap_TrackedEntityBridge) trackedEntity).bridge$getEntity(); + if (entity instanceof ServerPlayer player && ((ServerPlayerEntityBridge) player).bridge$isTrackerDirty()) { + list.add(trackedEntity); + ((ServerPlayerEntityBridge) player).bridge$setTrackerDirty(false); + } + ((ChunkMap_TrackedEntityBridge) trackedEntity).bridge$getServerEntity().sendChanges(); + } + + for (var trackedEntity : this.entityMap.values()) { + var entity = ((ChunkMap_TrackedEntityBridge) trackedEntity).bridge$getEntity(); + SectionPos lastSectionPos = ((ChunkMap_TrackedEntityBridge) trackedEntity).bridge$getLastSectionPos(); + SectionPos newSectionPos = SectionPos.of(entity); + ((ChunkMap_TrackedEntityBridge) trackedEntity).bridge$setLastSectionPos(newSectionPos); + if (entity instanceof ServerPlayer player) { + for (var otherTracker : list) { + var other = (ServerPlayer) ((ChunkMap_TrackedEntityBridge) otherTracker).bridge$getEntity(); + if (other.getId() > entity.getId()) { + trackedEntity.updatePlayer(other); + otherTracker.updatePlayer(player); + } + } + } else { + boolean chunkChanged = !Objects.equals(lastSectionPos, newSectionPos); + if (chunkChanged) { + trackedEntity.updatePlayers(this.level.players()); + } else { + for (var other : list) { + trackedEntity.updatePlayer((ServerPlayer) ((ChunkMap_TrackedEntityBridge) other).bridge$getEntity()); + } + } + } + } + ci.cancel(); + } + + @Mixin(ChunkMap.TrackedEntity.class) + public static class TrackedEntityMixin { + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Sets;newIdentityHashSet()Ljava/util/Set;")) + private Set arclight$useFastUtilSet() { + return new ReferenceOpenHashSet<>(); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/ConnectionMixin_Optimize.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ConnectionMixin_Optimize.java similarity index 91% rename from arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/ConnectionMixin_Optimize.java rename to arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ConnectionMixin_Optimize.java index 6438543d..d530757a 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/ConnectionMixin_Optimize.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ConnectionMixin_Optimize.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.common.mixin.optimization.general; +package io.izzel.arclight.common.mixin.optimization.general.network; import io.netty.util.concurrent.AbstractEventExecutor; import net.minecraft.network.Connection; diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerGamePacketListenerImplMixin_Optimize.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerGamePacketListenerImplMixin_Optimize.java new file mode 100644 index 00000000..43599bc0 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerGamePacketListenerImplMixin_Optimize.java @@ -0,0 +1,29 @@ +package io.izzel.arclight.common.mixin.optimization.general.network; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ServerGamePacketListenerImpl.class) +public class ServerGamePacketListenerImplMixin_Optimize { + + @Shadow public ServerPlayer player; + + @Redirect(method = "handleMovePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;move(Lnet/minecraft/server/level/ServerPlayer;)V")) + private void arclight$markTrackerDirty(ServerChunkCache instance, ServerPlayer player, ServerboundMovePlayerPacket packet) { + if (!packet.hasPosition()) { + // do not update tracker when no position is updated + var old = ((ServerPlayerEntityBridge) this.player).bridge$isTrackerDirty(); + instance.move(player); + ((ServerPlayerEntityBridge) this.player).bridge$setTrackerDirty(old); + } else { + instance.move(player); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerPlayerMixin_Optimize.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerPlayerMixin_Optimize.java new file mode 100644 index 00000000..c0494e7a --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/optimization/general/network/ServerPlayerMixin_Optimize.java @@ -0,0 +1,22 @@ +package io.izzel.arclight.common.mixin.optimization.general.network; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ServerPlayer.class) +public abstract class ServerPlayerMixin_Optimize implements ServerPlayerEntityBridge { + + @Unique private boolean trackerDirty; + + @Override + public boolean bridge$isTrackerDirty() { + return this.trackerDirty; + } + + @Override + public void bridge$setTrackerDirty(boolean flag) { + this.trackerDirty = flag; + } +} diff --git a/arclight-common/src/main/resources/mixins.arclight.impl.forge.optimization.json b/arclight-common/src/main/resources/mixins.arclight.impl.forge.optimization.json index db2b0c83..44998a50 100644 --- a/arclight-common/src/main/resources/mixins.arclight.impl.forge.optimization.json +++ b/arclight-common/src/main/resources/mixins.arclight.impl.forge.optimization.json @@ -6,7 +6,6 @@ "compatibilityLevel": "JAVA_11", "mixins": [ "ClassInheritanceMultiMapMixin", - "ConnectionMixin_Optimize", "EntityDataManagerMixin_Optimize", "EntityMixin_Optimize", "GoalMixin", @@ -23,6 +22,11 @@ "activationrange.entity.ItemEntityMixin_ActivationRange", "activationrange.entity.LivingEntityMixin_ActivationRange", "activationrange.entity.VillagerEntityMixin_ActivationRange", + "network.ChunkMapMixin_Optimize", + "network.ChunkMapMixin_Optimize$TrackedEntityMixin", + "network.ConnectionMixin_Optimize", + "network.ServerGamePacketListenerImplMixin_Optimize", + "network.ServerPlayerMixin_Optimize", "realtime.ItemEntityMixin_Realtime", "realtime.PlayerInteractionManagerMixin_Realtime", "trackingrange.ChunkManagerMixin_TrackingRange"