Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,41 @@
}

@Override
@@ -957,7 +_,13 @@

private void wakeUpAllPlayers() {
this.sleepStatus.removeAllSleepers();
- this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
+ // Gale start - Do less work - Avoid stream and list allocation
+ for (ServerPlayer player : this.players) {
+ if (player.isSleeping()) {
+ player.stopSleepInBed(false, false);
+ }
+ }
+ // Gale end - Do less work - Avoid stream and list allocation
}

// Paper start - optimise random ticking
@@ -1037,13 +_,17 @@
}

public void tickThunder(final LevelChunk chunk) {
+ // Gale start - Do less work - Skip the profiler and random calls when thunder cannot happen
+ if (!this.isThundering() || this.paperConfig().environment.disableThunder || !this.isRaining() || this.spigotConfig.thunderChance <= 0) {
+ return;
+ }
+ // Gale end - Do less work - Skip the profiler and random calls when thunder cannot happen
ChunkPos chunkPos = chunk.getPos();
- boolean raining = this.isRaining();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
ProfilerFiller profiler = Profiler.get();
profiler.push("thunder");
- if (!this.paperConfig().environment.disableThunder && raining && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
+ if (this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
BlockPos pos = this.findLightningTargetAround(this.getBlockRandomPos(minX, 0, minZ, 15));
if (this.isRainingAt(pos)) {
DifficultyInstance difficulty = this.getCurrentDifficultyAt(pos);
@@ -1396,7 +_,7 @@
// Paper end - log detailed entity tick information
entity.setOldPosAndRot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
public SimpleBitStorage(final int bits, final int size) {
this(bits, size, (long[])null);
}
@@ -399,6 +_,41 @@
@@ -397,6 +_,41 @@
@Override
public BitStorage copy() {
return new SimpleBitStorage(this.bits, this.size, (long[])this.data.clone());
}
+ }
+
+ // Gale - Chunk serialization
+ @Override
Expand Down Expand Up @@ -74,7 +75,6 @@
+ bits >>= this.bits;
+ }
+ }
+ }
}

// Paper start - block counting
@Override
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/npc/villager/Villager.java
+++ b/net/minecraft/world/entity/npc/villager/Villager.java
@@ -887,7 +_,11 @@
if (this.lastGossipDecayTime == 0L) {
this.lastGossipDecayTime = timestamp;
} else if (timestamp >= this.lastGossipDecayTime + 24000L) {
+ // Gale start - Do less work - Skip gossip decay for villagers without any gossip
+ if (!this.gossips.gossips.isEmpty()) {
this.gossips.decay();
+ }
+ // Gale end - Do less work - Skip gossip decay for villagers without any gossip
this.lastGossipDecayTime = timestamp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- a/net/minecraft/world/level/BaseSpawner.java
+++ b/net/minecraft/world/level/BaseSpawner.java
@@ -107,6 +_,7 @@
RandomSource random = level.getRandom();
SpawnData nextSpawnData = this.getOrCreateNextSpawnData(level, random, pos);

+ AABB nearbyEntitiesBox = new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange); // Gale - Do less work - Hoist nearby entity query box out of the spawn loop
for (int c = 0; c < this.spawnCount; c++) {
try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(this::toString, LOGGER)) {
ValueInput input = TagValueInput.create(reporter, level.registryAccess(), nextSpawnData.getEntityToSpawn());
@@ -165,7 +_,7 @@

int nearBy = level.getEntities(
EntityTypeTest.forExactClass(entity.getClass()),
- new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange),
+ nearbyEntitiesBox, // Gale - Do less work - Hoist nearby entity query box out of the spawn loop
EntitySelector.NO_SPECTATORS
)
.size();
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,33 @@
this.tickingBlockEntities = true;
if (!this.pendingBlockEntityTickers.isEmpty()) {
this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
@@ -1498,11 +_,13 @@
// Paper start - Fix MC-117075 use removeAll
final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<@Nullable TickingBlockEntity> toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
toRemove.add(null);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be a reason for the toRemove.add(null) above, did you look into this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That line is Paper's, not ours - it's part of Paper's MC-117075 fix (blockEntityTickers can contain nulls, so null is added to the reference set so removeAll strips them too). It only appears as an addition here because our removeAny change pulled that region into the patch as context. Our change in this method is just the removeAny guard.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be a reason for the toRemove.add(null) above, did you look into this?

I don't know why they add this add(null) and expect removeAll to also remove null blockEntity from the tick list, because I don't see there is a place to add null to the list (If I missed some places, correct me((( ).

This line of change was added in this commit PaperMC/Paper-archive@6f064f9#diff-477840aa089f974ea049572e2fc59e5dec986abc597ea9b1ce0f1487caad587c

And Machine Maker didn't leave a note to explain why he added add(null) compared to the original patch.

If the removeAny here is false, it will not remove null block entity from the list, but it can still possibly be removed in future ticks; not sure whether it's a big issue. (If we really have null elements, if they don't exist, then it's fine I think)

+ boolean removeAny = false; // Gale - Do less work - Skip the removeAll scan when nothing was removed
for (int tickerIndex = 0; tickerIndex < this.blockEntityTickers.size(); tickerIndex++) {
final TickingBlockEntity ticker = this.blockEntityTickers.get(tickerIndex);
// Paper end - Fix MC-117075 use removeAll
if (ticker.isRemoved()) {
toRemove.add(ticker); // Paper - Fix MC-117075 use removeAll
+ removeAny = true; // Gale - Do less work - Skip the removeAll scan when nothing was removed
} else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) {
ticker.tick();
// Paper start - rewrite chunk system
@@ -1513,7 +_,11 @@
}
}

- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 use removeAll
+ // Gale start - Do less work - Skip the removeAll scan when nothing was removed
+ if (removeAny) {
+ this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 use removeAll
+ }
+ // Gale end - Do less work - Skip the removeAll scan when nothing was removed
this.tickingBlockEntities = false;
}

@@ -2127,6 +_,13 @@
public BiomeManager getBiomeManager() {
return this.biomeManager;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
@@ -335,6 +_,7 @@
}

public static void serverTick(final Level level, final BlockPos blockPos, final BlockState state, final BeehiveBlockEntity entity) {
+ if (entity.stored.isEmpty()) return; // Gale - Do less work - Skip bee hive ticking when it holds no bees
tickOccupants(level, blockPos, state, entity.stored, entity.savedFlowerPos);
if (!entity.stored.isEmpty() && level.getRandom().nextDouble() < 0.005) {
double x = blockPos.getX() + 0.5;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -38,6 +_,8 @@
public int cooldownTime = -1;
private long tickedGameTime;
private Direction facing;
+ private @Nullable AABB cachedSuckAabb; // Gale - Do less work - Cache hopper suck AABB
+ private @Nullable BlockPos cachedSuckPos; // Gale - Do less work - Cache hopper suck AABB (invalidate on position change)

// CraftBukkit start - add fields and methods
public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
@@ -668,7 +_,9 @@
}

public static List<ItemEntity> getItemsAtAndAbove(final Level level, final Hopper hopper) {
- AABB aabb = hopper.getSuckAabb().move(hopper.getLevelX() - 0.5, hopper.getLevelY() - 0.5, hopper.getLevelZ() - 0.5);
+ AABB aabb = hopper instanceof HopperBlockEntity hopperBlockEntity // Gale - Do less work - Cache hopper suck AABB
+ ? hopperBlockEntity.gale$getCachedSuckAabb()
+ : hopper.getSuckAabb().move(hopper.getLevelX() - 0.5, hopper.getLevelY() - 0.5, hopper.getLevelZ() - 0.5);
return level.getEntitiesOfClass(ItemEntity.class, aabb, EntitySelector.ENTITY_STILL_ALIVE);
}

@@ -742,6 +_,18 @@
return true;
}

+ // Gale start - Do less work - Cache hopper suck AABB
+ public AABB gale$getCachedSuckAabb() {
+ AABB aabb = this.cachedSuckAabb;
+ BlockPos pos = this.getBlockPos();
+ if (aabb == null || !pos.equals(this.cachedSuckPos)) {
+ this.cachedSuckAabb = aabb = this.getSuckAabb().move(this.getLevelX() - 0.5, this.getLevelY() - 0.5, this.getLevelZ() - 0.5);
+ this.cachedSuckPos = pos;
+ }
+ return aabb;
+ }
+ // Gale end - Do less work - Cache hopper suck AABB
+
public void setCooldown(final int time) {
this.cooldownTime = time;
}
@@ -767,7 +_,7 @@
public static void entityInside(final Level level, final BlockPos pos, final BlockState blockState, final Entity entity, final HopperBlockEntity hopper) {
if (entity instanceof ItemEntity itemEntity
&& !itemEntity.getItem().isEmpty()
- && entity.getBoundingBox().move(-pos.getX(), -pos.getY(), -pos.getZ()).intersects(hopper.getSuckAabb())) {
+ && entity.getBoundingBox().intersects(hopper.gale$getCachedSuckAabb())) { // Gale - Do less work - Cache hopper suck AABB, avoids AABB allocation per item entity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done the logic in my head to figure out whether this is actually the same.

Is it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - it's the same, by translation invariance of AABB.intersects. gale() returns getSuckAabb().move(getLevelX() - 0.5, getLevelY() - 0.5, getLevelZ() - 0.5), and for a HopperBlockEntity getLevelX()/getLevelY()/getLevelZ() are worldPosition + 0.5, so the cache holds suckAabb.move(pos) in world coordinates. A.intersects(B) <=> A.move(v).intersects(B.move(v)) for any v, since both boxes shift by the same vector and the min/max comparisons cancel out. So box.move(-pos).intersects(suckAabb) <=> box.intersects(suckAabb.move(pos)), which is exactly what the cached path checks. (The non-HopperBlockEntity path, i.e. MinecartHopper, still uses the exact vanilla expression.)

tryMoveItems(level, pos, blockState, hopper, () -> addItem(hopper, itemEntity));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,7 @@

public boolean isSolidRender() {
return this.solidRender;
@@ -996,8 +_,10 @@
return this.is(tag) && predicate.test(this);
}

- public boolean hasBlockEntity() {
- return this.getBlock() instanceof EntityBlock;
+ public final boolean hasBlockEntity() {
+ // Gale start - Pre-compute - BlockBehaviour.hasBlockEntity()
+ return this.gale$precompute_hasBlockEntity;
+ // Gale end - Pre-compute - BlockBehaviour.hasBlockEntity()
}

public boolean shouldChangedStateKeepBlockEntity(final BlockState oldState) {
@@ -1037,7 +_,15 @@
public VoxelShape getCollisionShape(final BlockGetter level, final BlockPos pos) {
return this.cache != null ? this.cache.collisionShape : this.getCollisionShape(level, pos, CollisionContext.empty());
@@ -820,7 +_,17 @@
}

public VoxelShape getCollisionShape(final BlockGetter level, final BlockPos pos, final CollisionContext context) {
Expand All @@ -132,6 +117,21 @@
+ }
+ return shape;
}

public VoxelShape getEntityInsideCollisionShape(final BlockGetter level, final BlockPos pos, final Entity entity) {
@@ -996,8 +_,10 @@
return this.is(tag) && predicate.test(this);
}

- public boolean hasBlockEntity() {
- return this.getBlock() instanceof EntityBlock;
+ // Gale start - Pre-compute - BlockBehaviour.hasBlockEntity()
+ public final boolean hasBlockEntity() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this made me notice the // Gale start needs to be moved up by one line because we made it final.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - moved the // Gale start marker up one line so it covers the final keyword.

+ return this.gale$precompute_hasBlockEntity;
+ // Gale end - Pre-compute - BlockBehaviour.hasBlockEntity()
}

public boolean shouldChangedStateKeepBlockEntity(final BlockState oldState) {
@@ -1447,6 +_,18 @@
public interface StateArgumentPredicate<A> {
boolean test(BlockState state, BlockGetter level, BlockPos pos, A a);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@
private static <T> int[] reencodeContents(final BitStorage storage, final Palette<T> oldPalette, final Palette<T> newPalette) {
int[] buffer = new int[storage.getSize()];
storage.unpack(buffer);
@@ -390,6 +_,48 @@
return buffer;
}
@@ -388,6 +_,48 @@
}

return buffer;
+ }
+
+ // Gale - Chunk serialization
+ private static Optional<LongStream> asOptional(final long[] values) {
+ return Optional.of(Arrays.stream(values));
Expand Down Expand Up @@ -86,8 +88,6 @@
+ } finally {
+ this.release();
+ }
+ }
+
}
@Override
public int getSerializedSize() {
return this.data.getSerializedSize(this.strategy.globalMap());
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--- a/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java
+++ b/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java
@@ -88,11 +_,13 @@
if (this.listenersToRemove.remove(listener)) {
iterator.remove();
} else {
- Optional<Vec3> optionalPosition = getPostableListenerPosition(this.level, sourcePosition, listener);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to make a gale$getPostableListenerPositionNullable that returns @Nullable Vec3. Then copy the implementation from getPostableListenerPosition into gale$getPostableListenerPositionNullable but make it nullable. Then replace the implementation of getPostableListenerPosition by calling gale$getPostableListenerPositionNullable with Optional.ofNullable (so that the method still exists).

This way we have a clearer and more traceable diff

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done as suggested - gale returns @nullable Vec3, and getPostableListenerPosition is now a thin Optional.ofNullable wrapper around it.

- if (optionalPosition.isPresent()) {
- action.visit(listener, optionalPosition.get());
+ // Gale start - Do less work - Inline the range check to avoid Optional allocations per listener per event
+ Vec3 listenerPosition = gale$getPostableListenerPositionNullable(this.level, sourcePosition, listener);
+ if (listenerPosition != null) {
+ action.visit(listener, listenerPosition);
applicable = true;
}
+ // Gale end - Do less work - Inline the range check to avoid Optional allocations per listener per event
}
}
} finally {
@@ -112,16 +_,22 @@
return applicable;
}

- private static Optional<Vec3> getPostableListenerPosition(final ServerLevel level, final Vec3 sourcePosition, final GameEventListener listener) {
- Optional<Vec3> position = listener.getListenerSource().getPosition(level);
- if (position.isEmpty()) {
- return Optional.empty();
+ // Gale start - Do less work - Avoid Optional allocations per listener per event
+ private static @org.jspecify.annotations.Nullable Vec3 gale$getPostableListenerPositionNullable(final ServerLevel level, final Vec3 sourcePosition, final GameEventListener listener) {
+ Vec3 position = listener.getListenerSource().getPosition(level).orElse(null);
+ if (position == null) {
+ return null;
}

- double distanceFromOrigin = BlockPos.containing(position.get()).distSqr(BlockPos.containing(sourcePosition));
+ double distanceFromOrigin = BlockPos.containing(position).distSqr(BlockPos.containing(sourcePosition));
int radiusSqr = listener.getListenerRadius() * listener.getListenerRadius();
- return distanceFromOrigin > radiusSqr ? Optional.empty() : position;
- }
+ return distanceFromOrigin > radiusSqr ? null : position;
+ }
+
+ private static Optional<Vec3> getPostableListenerPosition(final ServerLevel level, final Vec3 sourcePosition, final GameEventListener listener) {
+ return Optional.ofNullable(gale$getPostableListenerPositionNullable(level, sourcePosition, listener));
+ }
+ // Gale end - Do less work - Avoid Optional allocations per listener per event

@FunctionalInterface
public interface OnEmptyAction {
Loading
Loading