mirror of
https://github.com/PaperMC/Paper.git
synced 2024-08-30 16:49:42 +02:00
This PR contains all of Tuinity's patches. Very notable ones are: - Highly optimised collisions - Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16) - Starlight https://github.com/PaperMC/Starlight - Rewritten dataconverter system https://github.com/PaperMC/DataConverter - Random block ticking optimisation (wrongly dropped from Paper 1.17) - Chunk ticking optimisations - Anything else I've forgotten in the 60 or so patches If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml. * Port tuinity, initial patchset * Update gradle to 7.2 jmp said it fixes rebuildpatches not working for me. it fucking better * Completely clean apply * Remove tuinity config, add per player api patch * Remove paper reobf mappings patch * Properly update gradlew * Force clean rebuild * Mark fixups Comments and ATs still need to be done * grep -r "Tuinity" * Fixup * Ensure gameprofile lastaccess is written only under the state lock * update URL for dataconverter * Only clean rebuild tuinity patches might fix merge conflicts * Use UTF-8 for gradlew * Clean rb patches again * Convert block ids used as item ids Neither the converters of pre 1.13 nor DFU handled these cases, as by the time they were written the game at the time didn't consider these ids valid - they would be air. Because of this, some worlds have logspam since only DataConverter (not DFU or legacy converters) will warn when an invalid id has been seen. While quite a few do need to now be considered as air, quite a lot do not. So it makes sense to add conversion for these items, instead of simply suppressing or ignoring the logs. I've now added id -> string conversion for all block ids that could be used as items that existed in the game before 1.7.10 (I have no interest in tracking down the exact version block ids stopped working) that were on https://minecraft-ids.grahamedgecombe.com/ Items that did not directly convert to new items will be instead converted to air: stems, wheat crops, piston head, tripwire wire block * Fix LightPopulated parsing in V1466 The DFU code was checking if the number existed, not if it didn't exist. I misread the original code. * Always parse protochunk light sources unless it is marked as non-lit Chunks not marked as lit will always go through the light engine, so they should always have their block sources parsed. * Update custom names to JSON for players Missed this fix from CB, as it was inside the DataFixers class. I decided to double check all of the CB changes again: DataFixers.java was the only area I missed, as I had inspected all datafixer diffs and implemented them all into DataConverter. I also checked Bootstrap.java again, and re-evaluated their changes. I had previously done this, but determined that they were all bad. The changes to make standing_sign block map to oak_sign block in V1450 is bad, because that's not the item id V1450 accepts. Only in 1.14 did oak_sign even exist, and as expected there is a converter to rename all existing sign items/blocks. The fix to register the portal block under id 1440 is useless, as the flattenning logic will default to the lowest registered id - which is the exact blockstate that CB registers into 1440. So it just doesn't do anything. The extra item ids in the id -> string converter are already added, but I found this from EMC originally. The change for the spawn egg id 23 -> Arrow is just wrong, that id DOES correspond to TippedArrow, NOT Arrow. As expected, the spawn egg already has a dedicated mapping for Arrow, which is id 10 - which was Arrow's entity id. I also ported a fix for the cooked_fished id update. This doesn't really matter since there is already a dataconverter to fix this, but the game didn't accept cooked_fished at the time. So I see no harm. * Review all converters and walkers - Refactor V99 to have helper methods for defining entity/tile entity types - Automatically namespace all ids that should be namespaced. While vanilla never saved non-namespaced data for things that are namespaced, plugins/users might have. - Synchronised the identity ensure map in HelperBlockFlatteningV1450 - Code style consistency - Add missing log warning in V102 for ITEM_NAME type conversion - Use getBoolean instead of getByte - Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow rename, as it will affect ENTITY_NAME type - Always set isVillager to false in V502 for Zombie - Register V808's converter under subversion 1 like DFU - Register a breakpoint for V1.17.1. In the future, all final versions of major releases will have a breakpoint so that the work required to determine if a converter needs a breakpoint is minimal - Validate that a dataconverter is only registered for a version that is registered - ConverterFlattenTileEntity is actually ConverterFlattenEntity It even registered the converters under TILE_ENTITY, instead of ENTITY. - Fix id comparison in V1492 STRUCTURE_FEATURE renamer - Use ConverterAbstractStatsRename for V1510 stats renamer At the time I had written that class, the abstract renamer didn't exist. - Ensure OwnerUUID is at least set to empty string in V1904 if the ocelot is converted to a cat (this is likely so that it retains a collar) - Use generic read/write for Records in V1946 Records is actually a list, not a map. So reading map was invalid. * Always set light to zero when propagating decrease This fixes an almost infinite loop where light values would be spam queued on a very small subset on blocks. This also likely fixes the memory issues people were seeing. * re-organize patches * Apply and fix conflicts * Revert some patches getChunkAt retains chunks so that plugins don't spam loads revert mc-4 fix will remain unless issues pop up * Shuffle iterated chunks if per player is not enabled Can help with some mob spawning stacking up at locations * Make per player default, migrate all configs * Adjust comments in fixups * Rework config for player chunk loader Old config is not compatible. Move all configs to be under `settings` in paper.yml The player chunk loader has been modified to less aggressively load chunks, but to send chunks at higher rates compared to tuinity. There are new config entries to tune this behavior. * Add back old constructor to CompressionEncoder/Decoder (fixes Tuinity #358) * Raise chunk loading default limits * Reduce worldgen thread workers for lower core count cpus * Raise limits for chunk loading config Also place it under `chunk-loading` * Disable max chunk send rate by default * Fix conflicts and rebuild patches * Drop default send rate again Appears to be still causing problems for no known reason * Raise chunk send limits to 100 per player While a low limit fixes ping issues for some people, most people do not suffer from this issue and thus should not suffer from an extremely slow load-in rate. * Rebase part 1 Autosquash the fixups * Move not implemented up * Fixup mc-dev fixes Missed this one * Rebase per player viewdistance api into the original api patch * Remove old light engine patch part 1 The prioritisation must be kept from it, so that part has been rebased into the priority patch. Part 2 will deal with rebasing all of the patches _after_ * Rebase remaining patches for old light patch removal * Remove other mid tick patch * Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch Replaced by `Do not copy visible chunks` * Revert AT for Vec3i setX/Y/Z The class is immutable. set should not be exposed * Remove old IntegerUtil class * Replace old CraftChunk#getEntities patch * Remove import for SWMRNibbleArray in ChunkAccess * Finished merge checklist * Remove ensureTickThread impl in urgency patch Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com> Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
1321 lines
59 KiB
Diff
1321 lines
59 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Thu, 17 Jun 2021 19:55:02 -0700
|
|
Subject: [PATCH] Rewrite entity bounding box lookup calls
|
|
|
|
For whatever reason, Mojang thought it was OK to make this system
|
|
scale logn relative to the number of entity sections loaded.
|
|
On top of that, they do a hashtable lookup per section - before
|
|
this was just a basic array access.
|
|
|
|
This patch brings back entity slices for lookup only.
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
@@ -0,0 +1,500 @@
|
|
+package io.papermc.paper.world;
|
|
+
|
|
+import com.destroystokyo.paper.util.maplist.EntityList;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
|
+import net.minecraft.server.level.ChunkHolder;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.entity.EntityType;
|
|
+import net.minecraft.world.entity.boss.EnderDragonPart;
|
|
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
|
|
+import net.minecraft.world.phys.AABB;
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public final class ChunkEntitySlices {
|
|
+
|
|
+ protected final int minSection;
|
|
+ protected final int maxSection;
|
|
+ protected final int chunkX;
|
|
+ protected final int chunkZ;
|
|
+ protected final ServerLevel world;
|
|
+
|
|
+ protected final EntityCollectionBySection allEntities;
|
|
+ protected final EntityCollectionBySection hardCollidingEntities;
|
|
+ protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
|
+ protected final EntityList entities = new EntityList();
|
|
+
|
|
+ public ChunkHolder.FullChunkStatus status;
|
|
+
|
|
+ // TODO implement container search optimisations
|
|
+
|
|
+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus status,
|
|
+ final int minSection, final int maxSection) { // inclusive, inclusive
|
|
+ this.minSection = minSection;
|
|
+ this.maxSection = maxSection;
|
|
+ this.chunkX = chunkX;
|
|
+ this.chunkZ = chunkZ;
|
|
+ this.world = world;
|
|
+
|
|
+ this.allEntities = new EntityCollectionBySection(this);
|
|
+ this.hardCollidingEntities = new EntityCollectionBySection(this);
|
|
+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>();
|
|
+
|
|
+ this.status = status;
|
|
+ }
|
|
+
|
|
+ // Paper start - optimise CraftChunk#getEntities
|
|
+ public org.bukkit.entity.Entity[] getChunkEntities() {
|
|
+ List<org.bukkit.entity.Entity> ret = new java.util.ArrayList<>();
|
|
+ final Entity[] entities = this.entities.getRawData();
|
|
+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) {
|
|
+ final Entity entity = entities[i];
|
|
+ if (entity == null) {
|
|
+ continue;
|
|
+ }
|
|
+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity();
|
|
+ if (bukkit != null && bukkit.isValid()) {
|
|
+ ret.add(bukkit);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret.toArray(new org.bukkit.entity.Entity[0]);
|
|
+ }
|
|
+ // Paper end - optimise CraftChunk#getEntities
|
|
+
|
|
+ public boolean isEmpty() {
|
|
+ return this.entities.size() == 0;
|
|
+ }
|
|
+
|
|
+ private void updateTicketLevels() {
|
|
+ final Entity[] entities = this.entities.getRawData();
|
|
+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) {
|
|
+ final Entity entity = entities[i];
|
|
+ entity.chunkStatus = this.status;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void updateStatus(final ChunkHolder.FullChunkStatus status) {
|
|
+ this.status = status;
|
|
+ this.updateTicketLevels();
|
|
+ }
|
|
+
|
|
+ public synchronized void addEntity(final Entity entity, final int chunkSection) {
|
|
+ if (!this.entities.add(entity)) {
|
|
+ return;
|
|
+ }
|
|
+ entity.chunkStatus = this.status;
|
|
+ final int sectionIndex = chunkSection - this.minSection;
|
|
+
|
|
+ this.allEntities.addEntity(entity, sectionIndex);
|
|
+
|
|
+ if (entity.hardCollides()) {
|
|
+ this.hardCollidingEntities.addEntity(entity, sectionIndex);
|
|
+ }
|
|
+
|
|
+ for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
|
|
+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
+ final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
|
|
+
|
|
+ if (entry.getKey().isInstance(entity)) {
|
|
+ entry.getValue().addEntity(entity, sectionIndex);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void removeEntity(final Entity entity, final int chunkSection) {
|
|
+ if (!this.entities.remove(entity)) {
|
|
+ return;
|
|
+ }
|
|
+ entity.chunkStatus = ChunkHolder.FullChunkStatus.INACCESSIBLE;
|
|
+ final int sectionIndex = chunkSection - this.minSection;
|
|
+
|
|
+ this.allEntities.removeEntity(entity, sectionIndex);
|
|
+
|
|
+ if (entity.hardCollides()) {
|
|
+ this.hardCollidingEntities.removeEntity(entity, sectionIndex);
|
|
+ }
|
|
+
|
|
+ for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
|
|
+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
+ final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
|
|
+
|
|
+ if (entry.getKey().isInstance(entity)) {
|
|
+ entry.getValue().removeEntity(entity, sectionIndex);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void getHardCollidingEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
+ this.hardCollidingEntities.getEntities(except, box, into, predicate);
|
|
+ }
|
|
+
|
|
+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate);
|
|
+ }
|
|
+
|
|
+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
|
|
+ final Predicate<? super T> predicate) {
|
|
+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate);
|
|
+ }
|
|
+
|
|
+ protected EntityCollectionBySection initClass(final Class<? extends Entity> clazz) {
|
|
+ final EntityCollectionBySection ret = new EntityCollectionBySection(this);
|
|
+
|
|
+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) {
|
|
+ final BasicEntityList<Entity> sectionEntities = this.allEntities.entitiesBySection[sectionIndex];
|
|
+ if (sectionEntities == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Entity[] storage = sectionEntities.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) {
|
|
+ final Entity entity = storage[i];
|
|
+
|
|
+ if (clazz.isInstance(entity)) {
|
|
+ ret.addEntity(entity, sectionIndex);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public <T extends Entity> void getEntities(final Class<? extends T> clazz, final Entity except, final AABB box, final List<? super T> into,
|
|
+ final Predicate<? super T> predicate) {
|
|
+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz);
|
|
+ if (collection != null) {
|
|
+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate);
|
|
+ } else {
|
|
+ synchronized (this) {
|
|
+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz));
|
|
+ }
|
|
+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void updateEntity(final Entity entity) {
|
|
+ /*// TODO
|
|
+ if (prev aabb != entity.getBoundingBox()) {
|
|
+ this.entityMap.delete(entity, prev aabb);
|
|
+ this.entityMap.insert(entity, prev aabb = entity.getBoundingBox());
|
|
+ }*/
|
|
+ }
|
|
+
|
|
+ protected static final class BasicEntityList<E extends Entity> {
|
|
+
|
|
+ protected static final Entity[] EMPTY = new Entity[0];
|
|
+ protected static final int DEFAULT_CAPACITY = 4;
|
|
+
|
|
+ protected E[] storage;
|
|
+ protected int size;
|
|
+
|
|
+ public BasicEntityList() {
|
|
+ this(0);
|
|
+ }
|
|
+
|
|
+ public BasicEntityList(final int cap) {
|
|
+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]);
|
|
+ }
|
|
+
|
|
+ public boolean isEmpty() {
|
|
+ return this.size == 0;
|
|
+ }
|
|
+
|
|
+ public int size() {
|
|
+ return this.size;
|
|
+ }
|
|
+
|
|
+ private void resize() {
|
|
+ if (this.storage == EMPTY) {
|
|
+ this.storage = (E[])new Entity[DEFAULT_CAPACITY];
|
|
+ } else {
|
|
+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void add(final E entity) {
|
|
+ final int idx = this.size++;
|
|
+ if (idx >= this.storage.length) {
|
|
+ this.resize();
|
|
+ this.storage[idx] = entity;
|
|
+ } else {
|
|
+ this.storage[idx] = entity;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int indexOf(final E entity) {
|
|
+ final E[] storage = this.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) {
|
|
+ if (storage[i] == entity) {
|
|
+ return i;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ public boolean remove(final E entity) {
|
|
+ final int idx = this.indexOf(entity);
|
|
+ if (idx == -1) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final int size = --this.size;
|
|
+ final E[] storage = this.storage;
|
|
+ if (idx != size) {
|
|
+ System.arraycopy(storage, idx + 1, storage, idx, size - idx);
|
|
+ }
|
|
+
|
|
+ storage[size] = null;
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public boolean has(final E entity) {
|
|
+ return this.indexOf(entity) != -1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected static final class EntityCollectionBySection {
|
|
+
|
|
+ protected final ChunkEntitySlices manager;
|
|
+ protected final long[] nonEmptyBitset;
|
|
+ protected final BasicEntityList<Entity>[] entitiesBySection;
|
|
+ protected int count;
|
|
+
|
|
+ public EntityCollectionBySection(final ChunkEntitySlices manager) {
|
|
+ this.manager = manager;
|
|
+
|
|
+ final int sectionCount = manager.maxSection - manager.minSection + 1;
|
|
+
|
|
+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE
|
|
+ this.entitiesBySection = new BasicEntityList[sectionCount];
|
|
+ }
|
|
+
|
|
+ public void addEntity(final Entity entity, final int sectionIndex) {
|
|
+ BasicEntityList<Entity> list = this.entitiesBySection[sectionIndex];
|
|
+
|
|
+ if (list != null && list.has(entity)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (list == null) {
|
|
+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>();
|
|
+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1)));
|
|
+ }
|
|
+
|
|
+ list.add(entity);
|
|
+ ++this.count;
|
|
+ }
|
|
+
|
|
+ public void removeEntity(final Entity entity, final int sectionIndex) {
|
|
+ final BasicEntityList<Entity> list = this.entitiesBySection[sectionIndex];
|
|
+
|
|
+ if (list == null || !list.remove(entity)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ --this.count;
|
|
+
|
|
+ if (list.isEmpty()) {
|
|
+ this.entitiesBySection[sectionIndex] = null;
|
|
+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1)));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
+ if (this.count == 0) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minSection = this.manager.minSection;
|
|
+ final int maxSection = this.manager.maxSection;
|
|
+
|
|
+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
|
+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
|
+
|
|
+ // TODO use the bitset
|
|
+
|
|
+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
|
+
|
|
+ for (int section = min; section <= max; ++section) {
|
|
+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
|
+
|
|
+ if (list == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Entity[] storage = list.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
|
+ final Entity entity = storage[i];
|
|
+
|
|
+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate != null && !predicate.test(entity)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ into.add(entity);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List<Entity> into,
|
|
+ final Predicate<? super Entity> predicate) {
|
|
+ if (this.count == 0) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minSection = this.manager.minSection;
|
|
+ final int maxSection = this.manager.maxSection;
|
|
+
|
|
+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
|
+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
|
+
|
|
+ // TODO use the bitset
|
|
+
|
|
+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
|
+
|
|
+ for (int section = min; section <= max; ++section) {
|
|
+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
|
+
|
|
+ if (list == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Entity[] storage = list.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
|
+ final Entity entity = storage[i];
|
|
+
|
|
+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate == null || predicate.test(entity)) {
|
|
+ into.add(entity);
|
|
+ } // else: continue to test the ender dragon parts
|
|
+
|
|
+ if (entity instanceof EnderDragon) {
|
|
+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) {
|
|
+ if (part == except || !part.getBoundingBox().intersects(box)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate != null && !predicate.test(part)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ into.add(part);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class<?> clazz, final AABB box, final List<Entity> into,
|
|
+ final Predicate<? super Entity> predicate) {
|
|
+ if (this.count == 0) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minSection = this.manager.minSection;
|
|
+ final int maxSection = this.manager.maxSection;
|
|
+
|
|
+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
|
+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
|
+
|
|
+ // TODO use the bitset
|
|
+
|
|
+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
|
+
|
|
+ for (int section = min; section <= max; ++section) {
|
|
+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
|
+
|
|
+ if (list == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Entity[] storage = list.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
|
+ final Entity entity = storage[i];
|
|
+
|
|
+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate == null || predicate.test(entity)) {
|
|
+ into.add(entity);
|
|
+ } // else: continue to test the ender dragon parts
|
|
+
|
|
+ if (entity instanceof EnderDragon) {
|
|
+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) {
|
|
+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate != null && !predicate.test(part)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ into.add(part);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
|
|
+ final Predicate<? super T> predicate) {
|
|
+ if (this.count == 0) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int minSection = this.manager.minSection;
|
|
+ final int maxSection = this.manager.maxSection;
|
|
+
|
|
+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
|
|
+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
|
|
+
|
|
+ // TODO use the bitset
|
|
+
|
|
+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
|
|
+
|
|
+ for (int section = min; section <= max; ++section) {
|
|
+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
|
|
+
|
|
+ if (list == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Entity[] storage = list.storage;
|
|
+
|
|
+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
|
|
+ final Entity entity = storage[i];
|
|
+
|
|
+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate != null && !predicate.test((T)entity)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ into.add((T)entity);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/world/EntitySliceManager.java b/src/main/java/io/papermc/paper/world/EntitySliceManager.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3ba094e640d7fe7803e2bbdab8ff3beb6f50e8a0
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/world/EntitySliceManager.java
|
|
@@ -0,0 +1,391 @@
|
|
+package io.papermc.paper.world;
|
|
+
|
|
+import io.papermc.paper.util.CoordinateUtils;
|
|
+import io.papermc.paper.util.WorldUtil;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.server.level.ChunkHolder;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.entity.EntityType;
|
|
+import net.minecraft.world.phys.AABB;
|
|
+import java.util.List;
|
|
+import java.util.concurrent.locks.StampedLock;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public final class EntitySliceManager {
|
|
+
|
|
+ protected static final int REGION_SHIFT = 5;
|
|
+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1;
|
|
+ protected static final int REGION_SIZE = 1 << REGION_SHIFT;
|
|
+
|
|
+ public final ServerLevel world;
|
|
+
|
|
+ private final StampedLock stateLock = new StampedLock();
|
|
+ protected final Long2ObjectOpenHashMap<ChunkSlicesRegion> regions = new Long2ObjectOpenHashMap<>(64, 0.7f);
|
|
+
|
|
+ private final int minSection; // inclusive
|
|
+ private final int maxSection; // inclusive
|
|
+
|
|
+ protected final Long2ObjectOpenHashMap<ChunkHolder.FullChunkStatus> statusMap = new Long2ObjectOpenHashMap<>();
|
|
+ {
|
|
+ this.statusMap.defaultReturnValue(ChunkHolder.FullChunkStatus.INACCESSIBLE);
|
|
+ }
|
|
+
|
|
+ public EntitySliceManager(final ServerLevel world) {
|
|
+ this.world = world;
|
|
+ this.minSection = WorldUtil.getMinSection(world);
|
|
+ this.maxSection = WorldUtil.getMaxSection(world);
|
|
+ }
|
|
+
|
|
+ public void chunkStatusChange(final int x, final int z, final ChunkHolder.FullChunkStatus newStatus) {
|
|
+ if (newStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) {
|
|
+ this.statusMap.remove(CoordinateUtils.getChunkKey(x, z));
|
|
+ } else {
|
|
+ this.statusMap.put(CoordinateUtils.getChunkKey(x, z), newStatus);
|
|
+ final ChunkEntitySlices slices = this.getChunk(x, z);
|
|
+ if (slices != null) {
|
|
+ slices.updateStatus(newStatus);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void addEntity(final Entity entity) {
|
|
+ final BlockPos pos = entity.blockPosition();
|
|
+ final int sectionX = pos.getX() >> 4;
|
|
+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection);
|
|
+ final int sectionZ = pos.getZ() >> 4;
|
|
+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ);
|
|
+ slices.addEntity(entity, sectionY);
|
|
+
|
|
+ entity.sectionX = sectionX;
|
|
+ entity.sectionY = sectionY;
|
|
+ entity.sectionZ = sectionZ;
|
|
+ }
|
|
+
|
|
+ public synchronized void removeEntity(final Entity entity) {
|
|
+ final ChunkEntitySlices slices = this.getChunk(entity.sectionX, entity.sectionZ);
|
|
+ slices.removeEntity(entity, entity.sectionY);
|
|
+ if (slices.isEmpty()) {
|
|
+ this.removeChunk(entity.sectionX, entity.sectionZ);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void moveEntity(final Entity entity) {
|
|
+ final BlockPos newPos = entity.blockPosition();
|
|
+ final int newSectionX = newPos.getX() >> 4;
|
|
+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection);
|
|
+ final int newSectionZ = newPos.getZ() >> 4;
|
|
+
|
|
+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ synchronized (this) {
|
|
+ // are we changing chunks?
|
|
+ if (newSectionX != entity.sectionX || newSectionZ != entity.sectionZ) {
|
|
+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ);
|
|
+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ);
|
|
+ synchronized (old) {
|
|
+ old.removeEntity(entity, entity.sectionY);
|
|
+ if (old.isEmpty()) {
|
|
+ this.removeChunk(entity.sectionX, entity.sectionZ);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ synchronized (slices) {
|
|
+ slices.addEntity(entity, newSectionY);
|
|
+
|
|
+ entity.sectionX = newSectionX;
|
|
+ entity.sectionY = newSectionY;
|
|
+ entity.sectionZ = newSectionZ;
|
|
+ }
|
|
+ } else {
|
|
+ final ChunkEntitySlices slices = this.getChunk(newSectionX, newSectionZ);
|
|
+ // same chunk
|
|
+ synchronized (slices) {
|
|
+ slices.removeEntity(entity, entity.sectionY);
|
|
+ slices.addEntity(entity, newSectionY);
|
|
+ }
|
|
+ entity.sectionY = newSectionY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
|
|
+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
|
|
+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
|
|
+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
|
|
+
|
|
+ final int minRegionX = minChunkX >> REGION_SHIFT;
|
|
+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
|
|
+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
+
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ chunk.getEntities(except, box, into, predicate);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void getHardCollidingEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
|
|
+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
|
|
+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
|
|
+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
|
|
+
|
|
+ final int minRegionX = minChunkX >> REGION_SHIFT;
|
|
+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
|
|
+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
+
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ chunk.getHardCollidingEntities(except, box, into, predicate);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
|
|
+ final Predicate<? super T> predicate) {
|
|
+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
|
|
+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
|
|
+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
|
|
+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
|
|
+
|
|
+ final int minRegionX = minChunkX >> REGION_SHIFT;
|
|
+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
|
|
+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
+
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ chunk.getEntities(type, box, (List)into, (Predicate)predicate);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public <T extends Entity> void getEntities(final Class<? extends T> clazz, final Entity except, final AABB box, final List<? super T> into,
|
|
+ final Predicate<? super T> predicate) {
|
|
+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
|
|
+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
|
|
+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
|
|
+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
|
|
+
|
|
+ final int minRegionX = minChunkX >> REGION_SHIFT;
|
|
+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
|
|
+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
+
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ chunk.getEntities(clazz, except, box, into, predicate);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
|
|
+ if (region == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT));
|
|
+ }
|
|
+
|
|
+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
|
|
+ ChunkEntitySlices ret;
|
|
+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) {
|
|
+ ret = new ChunkEntitySlices(this.world, chunkX, chunkZ, this.statusMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)),
|
|
+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world));
|
|
+
|
|
+ this.addChunk(chunkX, chunkZ, ret);
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) {
|
|
+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ);
|
|
+ final long attempt = this.stateLock.tryOptimisticRead();
|
|
+ if (attempt != 0L) {
|
|
+ try {
|
|
+ final ChunkSlicesRegion ret = this.regions.get(key);
|
|
+
|
|
+ if (this.stateLock.validate(attempt)) {
|
|
+ return ret;
|
|
+ }
|
|
+ } catch (final Error error) {
|
|
+ throw error;
|
|
+ } catch (final Throwable thr) {
|
|
+ // ignore
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.stateLock.readLock();
|
|
+ try {
|
|
+ return this.regions.get(key);
|
|
+ } finally {
|
|
+ this.stateLock.tryUnlockRead();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void removeChunk(final int chunkX, final int chunkZ) {
|
|
+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
|
|
+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT);
|
|
+
|
|
+ final ChunkSlicesRegion region = this.regions.get(key);
|
|
+ final int remaining = region.remove(relIndex);
|
|
+
|
|
+ if (remaining == 0) {
|
|
+ this.stateLock.writeLock();
|
|
+ try {
|
|
+ this.regions.remove(key);
|
|
+ } finally {
|
|
+ this.stateLock.tryUnlockWrite();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) {
|
|
+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
|
|
+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT);
|
|
+
|
|
+ ChunkSlicesRegion region = this.regions.get(key);
|
|
+ if (region != null) {
|
|
+ region.add(relIndex, slices);
|
|
+ } else {
|
|
+ region = new ChunkSlicesRegion();
|
|
+ region.add(relIndex, slices);
|
|
+ this.stateLock.writeLock();
|
|
+ try {
|
|
+ this.regions.put(key, region);
|
|
+ } finally {
|
|
+ this.stateLock.tryUnlockWrite();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class ChunkSlicesRegion {
|
|
+
|
|
+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE];
|
|
+ protected int sliceCount;
|
|
+
|
|
+ public ChunkEntitySlices get(final int index) {
|
|
+ return this.slices[index];
|
|
+ }
|
|
+
|
|
+ public int remove(final int index) {
|
|
+ final ChunkEntitySlices slices = this.slices[index];
|
|
+ if (slices == null) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ this.slices[index] = null;
|
|
+
|
|
+ return --this.sliceCount;
|
|
+ }
|
|
+
|
|
+ public void add(final int index, final ChunkEntitySlices slices) {
|
|
+ final ChunkEntitySlices curr = this.slices[index];
|
|
+ if (curr != null) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ this.slices[index] = slices;
|
|
+
|
|
+ ++this.sliceCount;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 7000909d2910e3e0e87e05f0a8bda8269f8d7e05..7fd55f26ad278aae27c102638ad8ab6776f27c7d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -437,7 +437,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
DataFixer datafixer = minecraftserver.getFixerUpper();
|
|
EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, new File(convertable_conversionsession.getDimensionPath(resourcekey), "entities"), datafixer, flag2, minecraftserver);
|
|
|
|
- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage);
|
|
+ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper
|
|
StructureManager definedstructuremanager = minecraftserver.getStructureManager();
|
|
int j = this.spigotConfig.viewDistance; // Spigot
|
|
PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
|
|
diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
index 0f6b534a4c789a2f09f6c4624e5d58b99c7ed0e6..21d1e0c9c471e9e556b5bd70166a769b46105c7a 100644
|
|
--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
|
|
@@ -77,6 +77,23 @@ public class WorldGenRegion implements WorldGenLevel {
|
|
@Nullable
|
|
private Supplier<String> currentlyGenerating;
|
|
|
|
+ // Paper start
|
|
+ // No-op, this class doesn't provide entity access
|
|
+ @Override
|
|
+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
|
|
+
|
|
+ @Override
|
|
+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
|
|
+
|
|
+ @Override
|
|
+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, AABB box, List<? super T> into, Predicate<? super T> predicate) {}
|
|
+ // Paper end
|
|
+
|
|
public WorldGenRegion(ServerLevel world, List<ChunkAccess> list, ChunkStatus chunkstatus, int i) {
|
|
this.generatingStatus = chunkstatus;
|
|
this.writeRadiusCutoff = i;
|
|
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
index b7c0574aa8e3aec3bc13064b59c5f7efb075cf13..74fcc1b45a9e57280da82f7c181530d4183872a5 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
@@ -417,6 +417,56 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
|
|
}
|
|
// Paper end - make end portalling safe
|
|
|
|
+ // Paper start
|
|
+ /**
|
|
+ * Overriding this field will cause memory leaks.
|
|
+ */
|
|
+ private final boolean hardCollides;
|
|
+
|
|
+ private static final java.util.Map<Class<? extends Entity>, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>());
|
|
+ {
|
|
+ /* // Goodbye, broken on reobf...
|
|
+ Boolean hardCollides = cachedOverrides.get(this.getClass());
|
|
+ if (hardCollides == null) {
|
|
+ try {
|
|
+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class);
|
|
+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith");
|
|
+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod)
|
|
+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) {
|
|
+ hardCollides = Boolean.TRUE;
|
|
+ } else {
|
|
+ hardCollides = Boolean.FALSE;
|
|
+ }
|
|
+ cachedOverrides.put(this.getClass(), hardCollides);
|
|
+ }
|
|
+ catch (ThreadDeath thr) { throw thr; }
|
|
+ catch (Throwable thr) {
|
|
+ // shouldn't happen, just explode
|
|
+ throw new RuntimeException(thr);
|
|
+ }
|
|
+ } */
|
|
+ this.hardCollides = this instanceof Boat
|
|
+ || this instanceof net.minecraft.world.entity.monster.Shulker
|
|
+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart
|
|
+ || this.shouldHardCollide();
|
|
+ }
|
|
+
|
|
+ // plugins can override
|
|
+ protected boolean shouldHardCollide() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public final boolean hardCollides() {
|
|
+ return this.hardCollides;
|
|
+ }
|
|
+
|
|
+ public net.minecraft.server.level.ChunkHolder.FullChunkStatus chunkStatus;
|
|
+
|
|
+ public int sectionX = Integer.MIN_VALUE;
|
|
+ public int sectionY = Integer.MIN_VALUE;
|
|
+ public int sectionZ = Integer.MIN_VALUE;
|
|
+ // Paper end
|
|
+
|
|
public Entity(EntityType<?> type, Level world) {
|
|
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
|
|
this.passengers = ImmutableList.of();
|
|
@@ -2280,11 +2330,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
- public boolean canCollideWith(Entity other) {
|
|
+ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override
|
|
return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other);
|
|
}
|
|
|
|
- public boolean canBeCollidedWith() {
|
|
+ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override
|
|
return false;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
|
|
index 325e244c46ec208a2e7e18d71ccbbfcc25fc1bce..94130509e3a7980c378cc95c46821cf0fc753ce6 100644
|
|
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
|
|
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
|
|
@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
|
|
public interface EntityGetter {
|
|
+
|
|
+ // Paper start
|
|
+ List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate);
|
|
+
|
|
+ void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into);
|
|
+
|
|
+ void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into);
|
|
+
|
|
+ <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, final AABB box, List<? super T> into,
|
|
+ Predicate<? super T> predicate);
|
|
+ // Paper end
|
|
+
|
|
List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate);
|
|
|
|
<T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate);
|
|
@@ -55,8 +67,8 @@ public interface EntityGetter {
|
|
return Stream.empty();
|
|
} else {
|
|
AABB aABB = box.inflate(1.0E-7D);
|
|
- return this.getEntities(entity, aABB, predicate.and((entityx) -> {
|
|
- if (entityx.getBoundingBox().intersects(aABB)) {
|
|
+ Predicate<Entity> hardCollides = (entityx) -> { // Paper - optimise entity hard collisions
|
|
+ if (true || entityx.getBoundingBox().intersects(aABB)) { // Paper - always true
|
|
if (entity == null) {
|
|
if (entityx.canBeCollidedWith()) {
|
|
return true;
|
|
@@ -67,7 +79,11 @@ public interface EntityGetter {
|
|
}
|
|
|
|
return false;
|
|
- })).stream().map(Entity::getBoundingBox).map(Shapes::create);
|
|
+ }; // Paper start - optimise entity hard collisions
|
|
+ predicate = predicate == null ? hardCollides : hardCollides.and(predicate);
|
|
+ return (entity != null && entity.hardCollides() ? this.getEntities(entity, aABB, predicate) : this.getHardCollidingEntities(entity, aABB, predicate))
|
|
+ .stream().map(Entity::getBoundingBox).map(Shapes::create);
|
|
+ // Paper end - optimise entity hard collisions
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index a7485746417e0dddb392c89a5a1d467c0bc83fbe..82da0f2226f71a5081e24c17a8a39be22e3c5316 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -203,6 +203,50 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
return this.typeKey;
|
|
}
|
|
|
|
+ // Paper start
|
|
+ protected final io.papermc.paper.world.EntitySliceManager entitySliceManager;
|
|
+
|
|
+ // Paper start - optimise CraftChunk#getEntities
|
|
+ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) {
|
|
+ io.papermc.paper.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ);
|
|
+ if (slices == null) {
|
|
+ return new org.bukkit.entity.Entity[0];
|
|
+ }
|
|
+ return slices.getChunkEntities();
|
|
+ }
|
|
+ // Paper end - optimise CraftChunk#getEntities
|
|
+
|
|
+ @Override
|
|
+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
|
|
+ List<Entity> ret = new java.util.ArrayList<>();
|
|
+ this.entitySliceManager.getEntities(except, box, ret, predicate);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {
|
|
+ this.entitySliceManager.getEntities(except, box, into, predicate);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {
|
|
+ this.entitySliceManager.getHardCollidingEntities(except, box, into, predicate);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, final AABB box, List<? super T> into,
|
|
+ Predicate<? super T> predicate) {
|
|
+ this.entitySliceManager.getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB box, Predicate<? super T> predicate) {
|
|
+ List<T> ret = new java.util.ArrayList<>();
|
|
+ this.entitySliceManager.getEntities(entityClass, null, box, ret, predicate);
|
|
+ return ret;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
|
|
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
|
|
@@ -279,6 +323,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
this.chunkPacketBlockController = this.paperConfig.antiXray ?
|
|
new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor)
|
|
: com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
|
|
+ this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper
|
|
}
|
|
|
|
// Paper start
|
|
@@ -996,26 +1041,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
|
|
this.getProfiler().incrementCounter("getEntities");
|
|
List<Entity> list = Lists.newArrayList();
|
|
-
|
|
- this.getEntities().get(box, (entity1) -> {
|
|
- if (entity1 != except && predicate.test(entity1)) {
|
|
- list.add(entity1);
|
|
- }
|
|
-
|
|
- if (entity1 instanceof EnderDragon) {
|
|
- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities();
|
|
- int i = aentitycomplexpart.length;
|
|
-
|
|
- for (int j = 0; j < i; ++j) {
|
|
- EnderDragonPart entitycomplexpart = aentitycomplexpart[j];
|
|
-
|
|
- if (entity1 != except && predicate.test(entitycomplexpart)) {
|
|
- list.add(entitycomplexpart);
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper
|
|
+ this.entitySliceManager.getEntities(except, box, list, predicate); // Paper - optimise this call
|
|
return list;
|
|
}
|
|
|
|
@@ -1024,26 +1050,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
this.getProfiler().incrementCounter("getEntities");
|
|
List<T> list = Lists.newArrayList();
|
|
|
|
- this.getEntities().get(filter, box, (entity) -> {
|
|
- if (predicate.test(entity)) {
|
|
- list.add(entity);
|
|
- }
|
|
-
|
|
- if (entity instanceof EnderDragon) {
|
|
- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity).getSubEntities();
|
|
- int i = aentitycomplexpart.length;
|
|
-
|
|
- for (int j = 0; j < i; ++j) {
|
|
- EnderDragonPart entitycomplexpart = aentitycomplexpart[j];
|
|
- T t0 = filter.tryCast(entitycomplexpart);
|
|
-
|
|
- if (t0 != null && predicate.test(t0)) {
|
|
- list.add(t0);
|
|
- }
|
|
- }
|
|
+ // Paper start - optimise this call
|
|
+ if (filter instanceof net.minecraft.world.entity.EntityType) {
|
|
+ this.entitySliceManager.getEntities((net.minecraft.world.entity.EntityType)filter, box, list, predicate);
|
|
+ } else {
|
|
+ Predicate<? super T> test = (obj) -> {
|
|
+ return filter.tryCast(obj) != null;
|
|
+ };
|
|
+ predicate = predicate == null ? test : test.and((Predicate)predicate);
|
|
+ Class base;
|
|
+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) {
|
|
+ this.entitySliceManager.getEntities((Entity) null, box, (List)list, (Predicate)predicate);
|
|
+ } else {
|
|
+ this.entitySliceManager.getEntities(base, null, box, (List)list, (Predicate)predicate); // Paper - optimise this call
|
|
}
|
|
-
|
|
- });
|
|
+ }
|
|
+ // Paper end - optimise this call
|
|
return list;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
index e4e4b48250d32d9e6c034d62e13ac2277bffda8f..a0dd49d17e1a83edbb0bf5cfbaba3f8fb665f75a 100644
|
|
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
@@ -45,8 +45,10 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
private final Long2ObjectMap<PersistentEntitySectionManager.ChunkLoadStatus> chunkLoadStatuses = new Long2ObjectOpenHashMap();
|
|
private final LongSet chunksToUnload = new LongOpenHashSet();
|
|
private final Queue<ChunkEntities<T>> loadingInbox = Queues.newConcurrentLinkedQueue();
|
|
+ public final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper
|
|
|
|
- public PersistentEntitySectionManager(Class<T> entityClass, LevelCallback<T> handler, EntityPersistentStorage<T> dataAccess) {
|
|
+ public PersistentEntitySectionManager(Class<T> entityClass, LevelCallback<T> handler, EntityPersistentStorage<T> dataAccess, io.papermc.paper.world.EntitySliceManager entitySliceManager) { // Paper
|
|
+ this.entitySliceManager = entitySliceManager; // Paper
|
|
this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility);
|
|
this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN);
|
|
this.chunkLoadStatuses.defaultReturnValue(PersistentEntitySectionManager.ChunkLoadStatus.FRESH);
|
|
@@ -97,6 +99,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
EntitySection<T> entitysection = this.sectionStorage.getOrCreateSection(i);
|
|
|
|
entitysection.add(entity); // CraftBukkit - decompile error
|
|
+ this.entitySliceManager.addEntity((Entity)entity); // Paper
|
|
entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection));
|
|
if (!existing) {
|
|
this.callbacks.onCreated(entity);
|
|
@@ -154,6 +157,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper
|
|
Visibility visibility = Visibility.fromFullChunkStatus(levelType);
|
|
|
|
+ this.entitySliceManager.chunkStatusChange(chunkPos.x, chunkPos.z, levelType); // Paper
|
|
this.updateChunkStatus(chunkPos, visibility);
|
|
}
|
|
|
|
@@ -434,6 +438,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
long i = SectionPos.asLong(blockposition);
|
|
|
|
if (i != this.currentSectionKey) {
|
|
+ PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper
|
|
Visibility visibility = this.currentSection.getStatus();
|
|
|
|
if (!this.currentSection.remove(this.entity)) {
|
|
@@ -482,6 +487,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
|
|
if (!this.currentSection.remove(this.entity)) {
|
|
PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason);
|
|
}
|
|
+ PersistentEntitySectionManager.this.entitySliceManager.removeEntity((Entity)this.entity); // Paper
|
|
|
|
Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus());
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
index 591a66dcdb717ad041120ab27de8f2f3a1975358..b27945cc7beb36a10bb9f3503b060f6c2ad18b77 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
@@ -110,11 +110,7 @@ public class CraftChunk implements Chunk {
|
|
this.getWorld().getChunkAt(x, z); // Transient load for this tick
|
|
}
|
|
|
|
- Location location = new Location(null, 0, 0, 0);
|
|
- return this.getWorld().getEntities().stream().filter((entity) -> {
|
|
- entity.getLocation(location);
|
|
- return location.getBlockX() >> 4 == this.x && location.getBlockZ() >> 4 == this.z;
|
|
- }).toArray(Entity[]::new);
|
|
+ return ((CraftWorld)this.getWorld()).getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
index c5d132dcf70a55de041c0187ff8fb889248bdfee..950d4381459d31d02acf55c4aef4f5e33367748b 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
|
|
@@ -254,4 +254,20 @@ public class DummyGeneratorAccess implements WorldGenLevel {
|
|
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos pos, StructureFeature<?> feature) {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
+
|
|
+ // Paper start
|
|
+ @Override
|
|
+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
|
|
+ return java.util.Collections.emptyList();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
|
|
+
|
|
+ @Override
|
|
+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
|
|
+
|
|
+ @Override
|
|
+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, AABB box, List<? super T> into, Predicate<? super T> predicate) {}
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
|
|
index 951e8ba1e05436aa0afcd0a72358550422afa791..b5da2f39ff6e2e7cb519c5d22be6ae4d77dc60ab 100644
|
|
--- a/src/main/java/org/spigotmc/ActivationRange.java
|
|
+++ b/src/main/java/org/spigotmc/ActivationRange.java
|
|
@@ -205,7 +205,13 @@ public class ActivationRange
|
|
ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, 256, waterActivationRange );
|
|
// Paper end
|
|
|
|
- world.getEntities().get(maxBB, ActivationRange::activateEntity);
|
|
+ // Paper start
|
|
+ java.util.List<Entity> entities = world.getEntities((Entity)null, maxBB, null);
|
|
+ for (int i = 0; i < entities.size(); i++) {
|
|
+ Entity entity = entities.get(i);
|
|
+ ActivationRange.activateEntity(entity);
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
MinecraftTimings.entityActivationCheckTimer.stopTiming();
|
|
}
|