mirror of
https://github.com/PaperMC/Paper.git
synced 2025-07-26 09:42:06 -07:00
Optimise EntityScheduler ticking
The vast majority of the time, there are no tasks scheduled to the EntityScheduler. We can avoid iterating the entire entity list by tracking which schedulers have any tasks scheduled.
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||||
|
Date: Tue, 24 Jun 2025 07:05:51 -0700
|
||||||
|
Subject: [PATCH] Optimise EntityScheduler ticking
|
||||||
|
|
||||||
|
The vast majority of the time, there are no tasks scheduled to
|
||||||
|
the EntityScheduler. We can avoid iterating the entire entity list
|
||||||
|
by tracking which schedulers have any tasks scheduled.
|
||||||
|
|
||||||
|
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
|
||||||
|
index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af94916135e84 100644
|
||||||
|
--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
|
||||||
|
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java
|
||||||
|
@@ -96,6 +96,7 @@ public final class ServerEntityLookup extends EntityLookup {
|
||||||
|
if (entity instanceof ThrownEnderpearl enderpearl) {
|
||||||
|
this.addEnderPearl(CoordinateUtils.getChunkKey(enderpearl.chunkPosition()));
|
||||||
|
}
|
||||||
|
+ entity.registerScheduler(); // Paper - optimise Folia entity scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||||
|
index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..b8d864b9a05ba2822b6610a2ebd4ef5d2d96bd9a 100644
|
||||||
|
--- a/net/minecraft/server/MinecraftServer.java
|
||||||
|
+++ b/net/minecraft/server/MinecraftServer.java
|
||||||
|
@@ -1654,33 +1654,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ public final io.papermc.paper.threadedregions.EntityScheduler.EntitySchedulerTickList entitySchedulerTickList = new io.papermc.paper.threadedregions.EntityScheduler.EntitySchedulerTickList(); // Paper - optimise Folia entity scheduler
|
||||||
|
+
|
||||||
|
protected void tickChildren(BooleanSupplier hasTimeLeft) {
|
||||||
|
ProfilerFiller profilerFiller = Profiler.get();
|
||||||
|
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
|
||||||
|
this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
|
||||||
|
- // Paper start - Folia scheduler API
|
||||||
|
- ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick();
|
||||||
|
- for (ServerPlayer player : this.playerList.players) {
|
||||||
|
- if (!this.playerList.players.contains(player)) {
|
||||||
|
+ // Paper start - optimise Folia entity scheduler
|
||||||
|
+ for (io.papermc.paper.threadedregions.EntityScheduler scheduler : this.entitySchedulerTickList.getAllSchedulers()) {
|
||||||
|
+ if (scheduler.isRetired()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = player.getBukkitEntityRaw();
|
||||||
|
- if (bukkit != null) {
|
||||||
|
- bukkit.taskScheduler.executeTick();
|
||||||
|
- }
|
||||||
|
+
|
||||||
|
+ scheduler.executeTick();
|
||||||
|
}
|
||||||
|
- getAllLevels().forEach(level -> {
|
||||||
|
- for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) {
|
||||||
|
- if (entity.isRemoved() || entity instanceof ServerPlayer) {
|
||||||
|
- continue;
|
||||||
|
- }
|
||||||
|
- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw();
|
||||||
|
- if (bukkit != null) {
|
||||||
|
- bukkit.taskScheduler.executeTick();
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- });
|
||||||
|
- // Paper end - Folia scheduler API
|
||||||
|
+ // Paper end - optimise Folia entity scheduler
|
||||||
|
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
|
||||||
|
profilerFiller.push("commandFunctions");
|
||||||
|
this.getFunctions().tick();
|
||||||
|
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||||
|
index 45cdbea0bbf12697ffd1fb2193c2eafe34142ea9..81413ac0de7b3c7a72bc606fe5ae6fb4ae7055e3 100644
|
||||||
|
--- a/net/minecraft/world/entity/Entity.java
|
||||||
|
+++ b/net/minecraft/world/entity/Entity.java
|
||||||
|
@@ -5175,6 +5175,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||||
|
this.getBukkitEntity().taskScheduler.retire();
|
||||||
|
}
|
||||||
|
// Paper end - Folia schedulers
|
||||||
|
+ // Paper start - optimise Folia entity scheduler
|
||||||
|
+ public final void registerScheduler() {
|
||||||
|
+ this.getBukkitEntity().taskScheduler.registerTo(net.minecraft.server.MinecraftServer.getServer().entitySchedulerTickList);
|
||||||
|
+ }
|
||||||
|
+ // Paper end - optimise Folia entity scheduler
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLevelCallback(EntityInLevelCallback levelCallback) {
|
@@ -1,6 +1,7 @@
|
|||||||
package io.papermc.paper.threadedregions;
|
package io.papermc.paper.threadedregions;
|
||||||
|
|
||||||
import ca.spottedleaf.concurrentutil.util.Validate;
|
import ca.spottedleaf.concurrentutil.util.Validate;
|
||||||
|
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
@@ -9,6 +10,8 @@ import org.bukkit.craftbukkit.entity.CraftEntity;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.locks.StampedLock;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +46,8 @@ public final class EntityScheduler {
|
|||||||
private static final long RETIRED_TICK_COUNT = -1L;
|
private static final long RETIRED_TICK_COUNT = -1L;
|
||||||
private final Object stateLock = new Object();
|
private final Object stateLock = new Object();
|
||||||
private final Long2ObjectOpenHashMap<List<ScheduledTask>> oneTimeDelayed = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectOpenHashMap<List<ScheduledTask>> oneTimeDelayed = new Long2ObjectOpenHashMap<>();
|
||||||
|
private EntitySchedulerTickList scheduledList;
|
||||||
|
private boolean insideScheduledList;
|
||||||
|
|
||||||
private final ArrayDeque<ScheduledTask> currentlyExecuting = new ArrayDeque<>();
|
private final ArrayDeque<ScheduledTask> currentlyExecuting = new ArrayDeque<>();
|
||||||
|
|
||||||
@@ -50,6 +55,51 @@ public final class EntityScheduler {
|
|||||||
this.entity = Validate.notNull(entity);
|
this.entity = Validate.notNull(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// must own state lock
|
||||||
|
private boolean hasTasks() {
|
||||||
|
return !this.currentlyExecuting.isEmpty() || !this.oneTimeDelayed.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerTo(final EntitySchedulerTickList newTickList) {
|
||||||
|
synchronized (this.stateLock) {
|
||||||
|
final EntitySchedulerTickList prevList = this.scheduledList;
|
||||||
|
if (prevList == newTickList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.scheduledList = newTickList;
|
||||||
|
|
||||||
|
// make sure tasks scheduled before registration can be ticked
|
||||||
|
if (prevList == null && this.hasTasks()) {
|
||||||
|
this.insideScheduledList = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer to new list
|
||||||
|
if (this.insideScheduledList) {
|
||||||
|
if (prevList != null) {
|
||||||
|
prevList.remove(this);
|
||||||
|
}
|
||||||
|
if (newTickList != null) {
|
||||||
|
newTickList.add(this);
|
||||||
|
} else {
|
||||||
|
// retired
|
||||||
|
this.insideScheduledList = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this scheduler is retired.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note: This should only be invoked on the owning thread for the entity.
|
||||||
|
* </p>
|
||||||
|
* @return whether this scheduler is retired.
|
||||||
|
*/
|
||||||
|
public boolean isRetired() {
|
||||||
|
return this.tickCount == RETIRED_TICK_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback
|
* Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback
|
||||||
* on all currently scheduled tasks.
|
* on all currently scheduled tasks.
|
||||||
@@ -66,6 +116,7 @@ public final class EntityScheduler {
|
|||||||
throw new IllegalStateException("Already retired");
|
throw new IllegalStateException("Already retired");
|
||||||
}
|
}
|
||||||
this.tickCount = RETIRED_TICK_COUNT;
|
this.tickCount = RETIRED_TICK_COUNT;
|
||||||
|
this.registerTo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Entity thisEntity = this.entity.getHandleRaw();
|
final Entity thisEntity = this.entity.getHandleRaw();
|
||||||
@@ -127,6 +178,11 @@ public final class EntityScheduler {
|
|||||||
this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> {
|
this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}).add(task);
|
}).add(task);
|
||||||
|
|
||||||
|
if (!this.insideScheduledList && this.scheduledList != null) {
|
||||||
|
this.scheduledList.add(this);
|
||||||
|
this.insideScheduledList = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -147,6 +203,12 @@ public final class EntityScheduler {
|
|||||||
throw new IllegalStateException("Ticking retired scheduler");
|
throw new IllegalStateException("Ticking retired scheduler");
|
||||||
}
|
}
|
||||||
++this.tickCount;
|
++this.tickCount;
|
||||||
|
|
||||||
|
if (this.scheduledList != null && !this.hasTasks()) {
|
||||||
|
this.scheduledList.remove(this);
|
||||||
|
this.insideScheduledList = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.oneTimeDelayed.isEmpty()) {
|
if (this.oneTimeDelayed.isEmpty()) {
|
||||||
toRun = null;
|
toRun = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -178,4 +240,34 @@ public final class EntityScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class EntitySchedulerTickList {
|
||||||
|
|
||||||
|
private static final EntityScheduler[] ENTITY_SCHEDULER_ARRAY = new EntityScheduler[0];
|
||||||
|
|
||||||
|
private final ReferenceList<EntityScheduler> entitySchedulers = new ReferenceList<>(ENTITY_SCHEDULER_ARRAY);
|
||||||
|
|
||||||
|
public boolean add(final EntityScheduler scheduler) {
|
||||||
|
synchronized (this) {
|
||||||
|
return this.entitySchedulers.add(scheduler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(final EntityScheduler scheduler) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.entitySchedulers.remove(scheduler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityScheduler[] getAllSchedulers() {
|
||||||
|
EntityScheduler[] ret = new EntityScheduler[this.entitySchedulers.size()];
|
||||||
|
synchronized (this) {
|
||||||
|
if (ret.length != this.entitySchedulers.size()) {
|
||||||
|
ret = new EntityScheduler[this.entitySchedulers.size()];
|
||||||
|
}
|
||||||
|
System.arraycopy(this.entitySchedulers.getRawDataUnchecked(), 0, ret, 0, this.entitySchedulers.size());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user