--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -5,17 +5,21 @@
 import com.google.common.collect.Sets;
 import com.mojang.logging.LogUtils;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.objects.ObjectIterator;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.io.Writer;
 import java.util.List;
+import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -25,24 +29,28 @@
 import net.minecraft.util.CsvOutput;
 import net.minecraft.util.VisibleForDebug;
 import net.minecraft.world.entity.Entity;
-import net.minecraft.world.level.ChunkPos;
 import org.slf4j.Logger;
+import net.minecraft.world.level.ChunkPos;
+// CraftBukkit start
+import net.minecraft.world.level.chunk.storage.EntityStorage;
+import org.bukkit.craftbukkit.v1_19_R1.event.CraftEventFactory;
+// CraftBukkit end
 
 public class PersistentEntitySectionManager<T extends EntityAccess> implements AutoCloseable {
+
     static final Logger LOGGER = LogUtils.getLogger();
     final Set<UUID> knownUuids = Sets.newHashSet();
     final LevelCallback<T> callbacks;
     public final EntityPersistentStorage<T> permanentStorage;
-    private final EntityLookup<T> visibleEntityStorage;
+    private final EntityLookup<T> visibleEntityStorage = new EntityLookup<>();
     final EntitySectionStorage<T> sectionStorage;
     private final LevelEntityGetter<T> entityGetter;
-    private final Long2ObjectMap<Visibility> chunkVisibility = new Long2ObjectOpenHashMap<>();
-    private final Long2ObjectMap<PersistentEntitySectionManager.ChunkLoadStatus> chunkLoadStatuses = new Long2ObjectOpenHashMap<>();
+    private final Long2ObjectMap<Visibility> chunkVisibility = new Long2ObjectOpenHashMap();
+    private final Long2ObjectMap<PersistentEntitySectionManager.ChunkLoadStatus> chunkLoadStatuses = new Long2ObjectOpenHashMap();
     private final LongSet chunksToUnload = new LongOpenHashSet();
     private final Queue<ChunkEntities<T>> loadingInbox = Queues.newConcurrentLinkedQueue();
 
     public PersistentEntitySectionManager(Class<T> entityClass, LevelCallback<T> handler, EntityPersistentStorage<T> dataAccess) {
-        this.visibleEntityStorage = new EntityLookup<>();
         this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility);
         this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN);
         this.chunkLoadStatuses.defaultReturnValue(PersistentEntitySectionManager.ChunkLoadStatus.FRESH);
@@ -51,6 +59,16 @@
         this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
     }
 
+    // CraftBukkit start - add method to get all entities in chunk
+    public List<Entity> getEntities(ChunkPos chunkCoordIntPair) {
+        return this.sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList());
+    }
+
+    public boolean isPending(long pair) {
+        return this.chunkLoadStatuses.get(pair) == ChunkLoadStatus.PENDING;
+    }
+    // CraftBukkit end
+
     void removeSectionIfEmpty(long sectionPos, EntitySection<T> section) {
         if (section.isEmpty()) {
             this.sectionStorage.remove(sectionPos);
@@ -59,8 +77,9 @@
     }
 
     private boolean addEntityUuid(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper
         if (!this.knownUuids.add(entity.getUUID())) {
-            LOGGER.warn("UUID of added entity already exists: {}", (Object)entity);
+            PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity);
             return false;
         } else {
             return true;
@@ -72,18 +91,33 @@
     }
 
     private boolean addEntity(T entity, boolean existing) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
+        // Paper start - chunk system hooks
+        if (existing) {
+            // I don't want to know why this is a generic type.
+            Entity entityCasted = (Entity)entity;
+            boolean wasRemoved = entityCasted.isRemoved();
+            net.minecraft.server.ChunkSystem.onEntityPreAdd((net.minecraft.server.level.ServerLevel)entityCasted.level, entityCasted);
+            if (!wasRemoved && entityCasted.isRemoved()) {
+                // removed by callback
+                return false;
+            }
+        }
+        // Paper end - chunk system hooks
         if (!this.addEntityUuid(entity)) {
             return false;
         } else {
-            long l = SectionPos.asLong(entity.blockPosition());
-            EntitySection<T> entitySection = this.sectionStorage.getOrCreateSection(l);
-            entitySection.add(entity);
-            entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, l, entitySection));
+            long i = SectionPos.asLong(entity.blockPosition());
+            EntitySection<T> entitysection = this.sectionStorage.getOrCreateSection(i);
+
+            entitysection.add(entity);
+            entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection));
             if (!existing) {
                 this.callbacks.onCreated(entity);
             }
 
-            Visibility visibility = getEffectiveStatus(entity, entitySection.getStatus());
+            Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(entity, entitysection.getStatus());
+
             if (visibility.isAccessible()) {
                 this.startTracking(entity);
             }
@@ -101,76 +135,84 @@
     }
 
     public void addLegacyChunkEntities(Stream<T> entities) {
-        entities.forEach((entity) -> {
-            this.addEntity(entity, true);
+        entities.forEach((entityaccess) -> {
+            this.addEntity(entityaccess, true);
         });
     }
 
     public void addWorldGenChunkEntities(Stream<T> entities) {
-        entities.forEach((entity) -> {
-            this.addEntity(entity, false);
+        entities.forEach((entityaccess) -> {
+            this.addEntity(entityaccess, false);
         });
     }
 
     void startTicking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper
         this.callbacks.onTickingStart(entity);
     }
 
     void stopTicking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper
         this.callbacks.onTickingEnd(entity);
     }
 
     void startTracking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper
         this.visibleEntityStorage.add(entity);
         this.callbacks.onTrackingStart(entity);
     }
 
     void stopTracking(T entity) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper
         this.callbacks.onTrackingEnd(entity);
         this.visibleEntityStorage.remove(entity);
     }
 
     public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) {
         Visibility visibility = Visibility.fromFullChunkStatus(levelType);
+
         this.updateChunkStatus(chunkPos, visibility);
     }
 
     public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) {
-        long l = chunkPos.toLong();
+        org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper
+        long i = chunkPos.toLong();
+
         if (trackingStatus == Visibility.HIDDEN) {
-            this.chunkVisibility.remove(l);
-            this.chunksToUnload.add(l);
+            this.chunkVisibility.remove(i);
+            this.chunksToUnload.add(i);
         } else {
-            this.chunkVisibility.put(l, trackingStatus);
-            this.chunksToUnload.remove(l);
-            this.ensureChunkQueuedForLoad(l);
+            this.chunkVisibility.put(i, trackingStatus);
+            this.chunksToUnload.remove(i);
+            this.ensureChunkQueuedForLoad(i);
         }
 
-        this.sectionStorage.getExistingSectionsInChunk(l).forEach((group) -> {
-            Visibility visibility2 = group.updateChunkStatus(trackingStatus);
-            boolean bl = visibility2.isAccessible();
-            boolean bl2 = trackingStatus.isAccessible();
-            boolean bl3 = visibility2.isTicking();
-            boolean bl4 = trackingStatus.isTicking();
-            if (bl3 && !bl4) {
-                group.getEntities().filter((entity) -> {
-                    return !entity.isAlwaysTicking();
+        this.sectionStorage.getExistingSectionsInChunk(i).forEach((entitysection) -> {
+            Visibility visibility1 = entitysection.updateChunkStatus(trackingStatus);
+            boolean flag = visibility1.isAccessible();
+            boolean flag1 = trackingStatus.isAccessible();
+            boolean flag2 = visibility1.isTicking();
+            boolean flag3 = trackingStatus.isTicking();
+
+            if (flag2 && !flag3) {
+                entitysection.getEntities().filter((entityaccess) -> {
+                    return !entityaccess.isAlwaysTicking();
                 }).forEach(this::stopTicking);
             }
 
-            if (bl && !bl2) {
-                group.getEntities().filter((entity) -> {
-                    return !entity.isAlwaysTicking();
+            if (flag && !flag1) {
+                entitysection.getEntities().filter((entityaccess) -> {
+                    return !entityaccess.isAlwaysTicking();
                 }).forEach(this::stopTracking);
-            } else if (!bl && bl2) {
-                group.getEntities().filter((entity) -> {
-                    return !entity.isAlwaysTicking();
+            } else if (!flag && flag1) {
+                entitysection.getEntities().filter((entityaccess) -> {
+                    return !entityaccess.isAlwaysTicking();
                 }).forEach(this::startTracking);
             }
 
-            if (!bl3 && bl4) {
-                group.getEntities().filter((entity) -> {
-                    return !entity.isAlwaysTicking();
+            if (!flag2 && flag3) {
+                entitysection.getEntities().filter((entityaccess) -> {
+                    return !entityaccess.isAlwaysTicking();
                 }).forEach(this::startTicking);
             }
 
@@ -178,52 +220,71 @@
     }
 
     public void ensureChunkQueuedForLoad(long chunkPos) {
-        PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPos);
-        if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper
+        PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos);
+
+        if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
             this.requestChunkLoad(chunkPos);
         }
 
     }
 
     public boolean storeChunkSections(long chunkPos, Consumer<T> action) {
-        PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPos);
-        if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
+        // CraftBukkit start - add boolean for event call
+        return this.storeChunkSections(chunkPos, action, false);
+    }
+
+    private boolean storeChunkSections(long i, Consumer<T> consumer, boolean callEvent) {
+        // CraftBukkit end
+        PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i);
+
+        if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
             return false;
         } else {
-            List<T> list = this.sectionStorage.getExistingSectionsInChunk(chunkPos).flatMap((section) -> {
-                return section.getEntities().filter(EntityAccess::shouldBeSaved);
+            List<T> list = (List) this.sectionStorage.getExistingSectionsInChunk(i).flatMap((entitysection) -> {
+                return entitysection.getEntities().filter(EntityAccess::shouldBeSaved);
             }).collect(Collectors.toList());
+
             if (list.isEmpty()) {
-                if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
-                    this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), ImmutableList.of()));
+                if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
+                    if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), ImmutableList.of()); // CraftBukkit
+                    this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), ImmutableList.of()));
                 }
 
                 return true;
-            } else if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
-                this.requestChunkLoad(chunkPos);
+            } else if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
+                this.requestChunkLoad(i);
                 return false;
             } else {
-                this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPos), list));
-                list.forEach(action);
+                if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) this.permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
+                this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), list));
+                list.forEach(consumer);
                 return true;
             }
         }
     }
 
     private void requestChunkLoad(long chunkPos) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper
         this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
-        ChunkPos chunkPos2 = new ChunkPos(chunkPos);
-        this.permanentStorage.loadEntities(chunkPos2).thenAccept(this.loadingInbox::add).exceptionally((throwable) -> {
-            LOGGER.error("Failed to read chunk {}", chunkPos2, throwable);
+        ChunkPos chunkcoordintpair = new ChunkPos(chunkPos);
+        CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair);
+        Queue queue = this.loadingInbox;
+
+        Objects.requireNonNull(this.loadingInbox);
+        completablefuture.thenAccept(queue::add).exceptionally((throwable) -> {
+            PersistentEntitySectionManager.LOGGER.error("Failed to read chunk {}", chunkcoordintpair, throwable);
             return null;
         });
     }
 
     private boolean processChunkUnload(long chunkPos) {
-        boolean bl = this.storeChunkSections(chunkPos, (entity) -> {
-            entity.getPassengersAndSelf().forEach(this::unloadEntity);
-        });
-        if (!bl) {
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper
+        boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> {
+            entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
+        }, true); // CraftBukkit - add boolean for event call
+
+        if (!flag) {
             return false;
         } else {
             this.chunkLoadStatuses.remove(chunkPos);
@@ -237,46 +298,58 @@
     }
 
     private void processUnloads() {
-        this.chunksToUnload.removeIf((pos) -> {
-            return this.chunkVisibility.get(pos) != Visibility.HIDDEN ? true : this.processChunkUnload(pos);
+        this.chunksToUnload.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error
+            return this.chunkVisibility.get(i) != Visibility.HIDDEN ? true : this.processChunkUnload(i);
         });
     }
 
     private void processPendingLoads() {
-        ChunkEntities<T> chunkEntities;
-        while((chunkEntities = this.loadingInbox.poll()) != null) {
-            chunkEntities.getEntities().forEach((entity) -> {
-                this.addEntity(entity, true);
+        org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper
+        ChunkEntities<T> chunkentities; // CraftBukkit - decompile error
+
+        while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) {
+            chunkentities.getEntities().forEach((entityaccess) -> {
+                this.addEntity(entityaccess, true);
             });
-            this.chunkLoadStatuses.put(chunkEntities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
+            this.chunkLoadStatuses.put(chunkentities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
+            // CraftBukkit start - call entity load event
+            List<Entity> entities = this.getEntities(chunkentities.getPos());
+            CraftEventFactory.callEntitiesLoadEvent(((EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities);
+            // CraftBukkit end
         }
 
     }
 
     public void tick() {
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper
         this.processPendingLoads();
         this.processUnloads();
     }
 
     private LongSet getAllChunksToSave() {
-        LongSet longSet = this.sectionStorage.getAllChunksWithExistingSections();
+        LongSet longset = this.sectionStorage.getAllChunksWithExistingSections();
+        ObjectIterator objectiterator = Long2ObjectMaps.fastIterable(this.chunkLoadStatuses).iterator();
+
+        while (objectiterator.hasNext()) {
+            Entry<PersistentEntitySectionManager.ChunkLoadStatus> entry = (Entry) objectiterator.next();
 
-        for(Long2ObjectMap.Entry<PersistentEntitySectionManager.ChunkLoadStatus> entry : Long2ObjectMaps.fastIterable(this.chunkLoadStatuses)) {
             if (entry.getValue() == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
-                longSet.add(entry.getLongKey());
+                longset.add(entry.getLongKey());
             }
         }
 
-        return longSet;
+        return longset;
     }
 
     public void autoSave() {
-        this.getAllChunksToSave().forEach((pos) -> {
-            boolean bl = this.chunkVisibility.get(pos) == Visibility.HIDDEN;
-            if (bl) {
-                this.processChunkUnload(pos);
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper
+        this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
+            boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
+
+            if (flag) {
+                this.processChunkUnload(i);
             } else {
-                this.storeChunkSections(pos, (entity) -> {
+                this.storeChunkSections(i, (entityaccess) -> {
                 });
             }
 
@@ -284,14 +357,16 @@
     }
 
     public void saveAll() {
-        LongSet longSet = this.getAllChunksToSave();
+        org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper
+        LongSet longset = this.getAllChunksToSave();
 
-        while(!longSet.isEmpty()) {
+        while (!longset.isEmpty()) {
             this.permanentStorage.flush(false);
             this.processPendingLoads();
-            longSet.removeIf((pos) -> {
-                boolean bl = this.chunkVisibility.get(pos) == Visibility.HIDDEN;
-                return bl ? this.processChunkUnload(pos) : this.storeChunkSections(pos, (entity) -> {
+            longset.removeIf((java.util.function.LongPredicate) (i) -> { // CraftBukkit - decompile error
+                boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
+
+                return flag ? this.processChunkUnload(i) : this.storeChunkSections(i, (entityaccess) -> {
                 });
             });
         }
@@ -299,9 +374,16 @@
         this.permanentStorage.flush(true);
     }
 
-    @Override
     public void close() throws IOException {
-        this.saveAll();
+        // CraftBukkit start - add save boolean
+        this.close(true);
+    }
+
+    public void close(boolean save) throws IOException {
+        if (save) {
+            this.saveAll();
+        }
+        // CraftBukkit end
         this.permanentStorage.close();
     }
 
@@ -314,11 +396,11 @@
     }
 
     public boolean canPositionTick(BlockPos pos) {
-        return this.chunkVisibility.get(ChunkPos.asLong(pos)).isTicking();
+        return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking();
     }
 
     public boolean canPositionTick(ChunkPos pos) {
-        return this.chunkVisibility.get(pos.toLong()).isTicking();
+        return ((Visibility) this.chunkVisibility.get(pos.toLong())).isTicking();
     }
 
     public boolean areEntitiesLoaded(long chunkPos) {
@@ -326,16 +408,19 @@
     }
 
     public void dumpSections(Writer writer) throws IOException {
-        CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("visibility").addColumn("load_status").addColumn("entity_count").build(writer);
-        this.sectionStorage.getAllChunksWithExistingSections().forEach((chunkPos) -> {
-            PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPos);
-            this.sectionStorage.getExistingSectionPositionsInChunk(chunkPos).forEach((sectionPos) -> {
-                EntitySection<T> entitySection = this.sectionStorage.getSection(sectionPos);
-                if (entitySection != null) {
+        CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("visibility").addColumn("load_status").addColumn("entity_count").build(writer);
+
+        this.sectionStorage.getAllChunksWithExistingSections().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
+            PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i);
+
+            this.sectionStorage.getExistingSectionPositionsInChunk(i).forEach((j) -> {
+                EntitySection<T> entitysection = this.sectionStorage.getSection(j);
+
+                if (entitysection != null) {
                     try {
-                        csvOutput.writeRow(SectionPos.x(sectionPos), SectionPos.y(sectionPos), SectionPos.z(sectionPos), entitySection.getStatus(), chunkLoadStatus, entitySection.size());
-                    } catch (IOException var7) {
-                        throw new UncheckedIOException(var7);
+                        csvwriter.writeRow(SectionPos.x(j), SectionPos.y(j), SectionPos.z(j), entitysection.getStatus(), persistententitysectionmanager_b, entitysection.size());
+                    } catch (IOException ioexception) {
+                        throw new UncheckedIOException(ioexception);
                     }
                 }
 
@@ -345,66 +430,83 @@
 
     @VisibleForDebug
     public String gatherStats() {
-        return this.knownUuids.size() + "," + this.visibleEntityStorage.count() + "," + this.sectionStorage.count() + "," + this.chunkLoadStatuses.size() + "," + this.chunkVisibility.size() + "," + this.loadingInbox.size() + "," + this.chunksToUnload.size();
+        int i = this.knownUuids.size();
+
+        return i + "," + this.visibleEntityStorage.count() + "," + this.sectionStorage.count() + "," + this.chunkLoadStatuses.size() + "," + this.chunkVisibility.size() + "," + this.loadingInbox.size() + "," + this.chunksToUnload.size();
     }
 
-    class Callback implements EntityInLevelCallback {
+    private static enum ChunkLoadStatus {
+
+        FRESH, PENDING, LOADED;
+
+        private ChunkLoadStatus() {}
+    }
+
+    private class Callback implements EntityInLevelCallback {
+
         private final T entity;
         private long currentSectionKey;
         private EntitySection<T> currentSection;
 
-        Callback(T entity, long sectionPos, EntitySection<T> section) {
-            this.entity = entity;
-            this.currentSectionKey = sectionPos;
-            this.currentSection = section;
+        Callback(EntityAccess entityaccess, long i, EntitySection entitysection) {
+            this.entity = (T) entityaccess; // CraftBukkit - decompile error
+            this.currentSectionKey = i;
+            this.currentSection = entitysection;
         }
 
         @Override
         public void onMove() {
-            BlockPos blockPos = this.entity.blockPosition();
-            long l = SectionPos.asLong(blockPos);
-            if (l != this.currentSectionKey) {
+            BlockPos blockposition = this.entity.blockPosition();
+            long i = SectionPos.asLong(blockposition);
+
+            if (i != this.currentSectionKey) {
+                org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper
                 Visibility visibility = this.currentSection.getStatus();
+
                 if (!this.currentSection.remove(this.entity)) {
-                    PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", this.entity, SectionPos.of(this.currentSectionKey), l);
+                    PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), i});
                 }
 
                 PersistentEntitySectionManager.this.removeSectionIfEmpty(this.currentSectionKey, this.currentSection);
-                EntitySection<T> entitySection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(l);
-                entitySection.add(this.entity);
-                this.currentSection = entitySection;
-                this.currentSectionKey = l;
-                this.updateStatus(visibility, entitySection.getStatus());
+                EntitySection<T> entitysection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(i);
+
+                entitysection.add(this.entity);
+                this.currentSection = entitysection;
+                this.currentSectionKey = i;
+                this.updateStatus(visibility, entitysection.getStatus());
             }
 
         }
 
         private void updateStatus(Visibility oldStatus, Visibility newStatus) {
-            Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, oldStatus);
-            Visibility visibility2 = PersistentEntitySectionManager.getEffectiveStatus(this.entity, newStatus);
-            if (visibility == visibility2) {
-                if (visibility2.isAccessible()) {
+            Visibility visibility2 = PersistentEntitySectionManager.getEffectiveStatus(this.entity, oldStatus);
+            Visibility visibility3 = PersistentEntitySectionManager.getEffectiveStatus(this.entity, newStatus);
+
+            if (visibility2 == visibility3) {
+                if (visibility3.isAccessible()) {
                     PersistentEntitySectionManager.this.callbacks.onSectionChange(this.entity);
                 }
 
             } else {
-                boolean bl = visibility.isAccessible();
-                boolean bl2 = visibility2.isAccessible();
-                if (bl && !bl2) {
+                boolean flag = visibility2.isAccessible();
+                boolean flag1 = visibility3.isAccessible();
+
+                if (flag && !flag1) {
                     PersistentEntitySectionManager.this.stopTracking(this.entity);
-                } else if (!bl && bl2) {
+                } else if (!flag && flag1) {
                     PersistentEntitySectionManager.this.startTracking(this.entity);
                 }
 
-                boolean bl3 = visibility.isTicking();
-                boolean bl4 = visibility2.isTicking();
-                if (bl3 && !bl4) {
+                boolean flag2 = visibility2.isTicking();
+                boolean flag3 = visibility3.isTicking();
+
+                if (flag2 && !flag3) {
                     PersistentEntitySectionManager.this.stopTicking(this.entity);
-                } else if (!bl3 && bl4) {
+                } else if (!flag2 && flag3) {
                     PersistentEntitySectionManager.this.startTicking(this.entity);
                 }
 
-                if (bl2) {
+                if (flag1) {
                     PersistentEntitySectionManager.this.callbacks.onSectionChange(this.entity);
                 }
 
@@ -413,11 +515,13 @@
 
         @Override
         public void onRemove(Entity.RemovalReason reason) {
+            org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper
             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.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason});
             }
 
             Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus());
+
             if (visibility.isTicking()) {
                 PersistentEntitySectionManager.this.stopTicking(this.entity);
             }
@@ -431,14 +535,8 @@
             }
 
             PersistentEntitySectionManager.this.knownUuids.remove(this.entity.getUUID());
-            this.entity.setLevelCallback(NULL);
+            this.entity.setLevelCallback(PersistentEntitySectionManager.Callback.NULL);
             PersistentEntitySectionManager.this.removeSectionIfEmpty(this.currentSectionKey, this.currentSection);
         }
     }
-
-    static enum ChunkLoadStatus {
-        FRESH,
-        PENDING,
-        LOADED;
-    }
 }
