mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-27 04:55:47 +01:00
1330 lines
59 KiB
Diff
1330 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 80f59abeb0081fe4784f3c0d027e8dfce752814b..3351c4d43b41db5a0718d7ff761b06d950b7ee87 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 59703eb6610c21df22f25c22cc884c4450f0316c..bdd50c28a76576be7950e3ccf8e00b622f3860d1 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -204,6 +204,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
|
|
@@ -281,6 +325,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
|
|
@@ -998,26 +1043,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;
|
|
}
|
|
|
|
@@ -1026,26 +1052,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 0c4ffc0049679b911cc1e1bea36eb21ff6324c46..dbb78ee61a6a3a7336f0656414cd729efd013009 100644
|
|
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
|
|
@@ -49,8 +49,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);
|
|
@@ -107,6 +109,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);
|
|
@@ -164,6 +167,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);
|
|
}
|
|
|
|
@@ -456,6 +460,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)) {
|
|
@@ -504,6 +509,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 9292db5ebf706d89a69dff9ac1e26e06e4ea7cba..87368816fdb23f804752351de502126f2cd062c7 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
@@ -127,9 +127,7 @@ public class CraftChunk implements Chunk {
|
|
long pair = ChunkPos.asLong(x, z);
|
|
|
|
if (entityManager.areEntitiesLoaded(pair)) { // PAIL rename isEntitiesLoaded
|
|
- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
|
|
- .map(net.minecraft.world.entity.Entity::getBukkitEntity)
|
|
- .filter(Objects::nonNull).toArray(Entity[]::new);
|
|
+ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
|
|
}
|
|
|
|
entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
|
|
@@ -149,9 +147,7 @@ public class CraftChunk implements Chunk {
|
|
return entityManager.areEntitiesLoaded(pair);
|
|
});
|
|
|
|
- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
|
|
- .map(net.minecraft.world.entity.Entity::getBukkitEntity)
|
|
- .filter(Objects::nonNull).toArray(Entity[]::new);
|
|
+ return getCraftWorld().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();
|
|
}
|