mirror of https://github.com/Minestom/Minestom.git
Fix `nearbyEntities`
This commit is contained in:
parent
43734a9c58
commit
3674fcc97d
|
@ -3,14 +3,18 @@ package net.minestom.server.instance;
|
|||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static net.minestom.server.utils.chunk.ChunkUtils.forChunksInRange;
|
||||
import static net.minestom.server.utils.chunk.ChunkUtils.getChunkIndex;
|
||||
|
||||
final class ChunkView {
|
||||
private static final int RANGE = MinecraftServer.getChunkViewDistance() * 16;
|
||||
private final Instance instance;
|
||||
private final Point point;
|
||||
final Set<Player> set = new SetImpl();
|
||||
|
@ -25,22 +29,29 @@ final class ChunkView {
|
|||
private Collection<Player> references() {
|
||||
final Instance instance = this.instance;
|
||||
final Point point = this.point;
|
||||
final var target = EntityTracker.Target.PLAYERS;
|
||||
|
||||
Int2ObjectOpenHashMap<Player> entityMap = new Int2ObjectOpenHashMap<>(lastReferenceCount);
|
||||
// Current Instance
|
||||
instance.getEntityTracker().nearbyEntities(point, RANGE, target,
|
||||
(entity) -> entityMap.putIfAbsent(entity.getEntityId(), entity));
|
||||
// Shared Instances
|
||||
if (instance instanceof InstanceContainer container) {
|
||||
final List<SharedInstance> shared = container.getSharedInstances();
|
||||
if (!shared.isEmpty()) {
|
||||
for (var sharedInstance : shared) {
|
||||
sharedInstance.getEntityTracker().nearbyEntities(point, RANGE, target,
|
||||
(entity) -> entityMap.putIfAbsent(entity.getEntityId(), entity));
|
||||
}
|
||||
}
|
||||
|
||||
final List<SharedInstance> shared = instance instanceof InstanceContainer container && !container.getSharedInstances().isEmpty() ?
|
||||
container.getSharedInstances() : List.of();
|
||||
Long2ObjectSyncMap<List<Entity>>[] entries = new Long2ObjectSyncMap[1 + shared.size()];
|
||||
entries[0] = entities(instance);
|
||||
for (int i = 0; i < shared.size(); i++) {
|
||||
entries[i + 1] = entities(shared.get(i));
|
||||
}
|
||||
|
||||
forChunksInRange(point, MinecraftServer.getChunkViewDistance(),
|
||||
(chunkX, chunkZ) -> {
|
||||
final long index = getChunkIndex(chunkX, chunkZ);
|
||||
for (var entry : entries) {
|
||||
var chunkEntities = entry.get(index);
|
||||
if (chunkEntities != null && !chunkEntities.isEmpty()) {
|
||||
for (var player : chunkEntities) {
|
||||
entityMap.putIfAbsent(player.getEntityId(), (Player) player);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.lastReferenceCount = entityMap.size();
|
||||
return entityMap.values();
|
||||
}
|
||||
|
@ -61,4 +72,9 @@ final class ChunkView {
|
|||
references().forEach(action);
|
||||
}
|
||||
}
|
||||
|
||||
static Long2ObjectSyncMap<List<Entity>> entities(Instance instance) {
|
||||
final EntityTrackerImpl tracker = (EntityTrackerImpl) instance.getEntityTracker();
|
||||
return tracker.entries[EntityTracker.Target.PLAYERS.ordinal()].chunkEntities;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.minestom.server.instance;
|
|||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
@ -27,7 +28,7 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||
|
||||
// Store all data associated to a Target
|
||||
// The array index is the Target enum ordinal
|
||||
private final TargetEntry<Entity>[] entries = EntityTracker.Target.TARGETS.stream().map((Function<Target<?>, TargetEntry>) TargetEntry::new).toArray(TargetEntry[]::new);
|
||||
final TargetEntry<Entity>[] entries = EntityTracker.Target.TARGETS.stream().map((Function<Target<?>, TargetEntry>) TargetEntry::new).toArray(TargetEntry[]::new);
|
||||
private final Int2ObjectSyncMap<Point> entityPositions = Int2ObjectSyncMap.hashmap();
|
||||
|
||||
@Override
|
||||
|
@ -112,20 +113,23 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||
@Override
|
||||
public <T extends Entity> void nearbyEntities(@NotNull Point point, double range, @NotNull Target<T> target, @NotNull Consumer<T> query) {
|
||||
final Long2ObjectSyncMap<List<Entity>> entities = entries[target.ordinal()].chunkEntities;
|
||||
int chunkRange = (int) (range / Chunk.CHUNK_SECTION_SIZE);
|
||||
if (point.x() % 16 != 0 || point.z() % 16 != 0) {
|
||||
chunkRange++; // Need to loop through surrounding chunks to properly support borders
|
||||
}
|
||||
// Loop through range
|
||||
if (range % 16 == 0) {
|
||||
// Fast path for exact chunk range
|
||||
forChunksInRange(point, chunkRange, (chunkX, chunkZ) -> {
|
||||
final var chunkEntities = (List<T>) entities.get(getChunkIndex(chunkX, chunkZ));
|
||||
if (chunkEntities != null && !chunkEntities.isEmpty()) chunkEntities.forEach(query);
|
||||
});
|
||||
final int minChunkX = ChunkUtils.getChunkCoordinate(point.x() - range);
|
||||
final int minChunkZ = ChunkUtils.getChunkCoordinate(point.z() - range);
|
||||
final int maxChunkX = ChunkUtils.getChunkCoordinate(point.x() + range);
|
||||
final int maxChunkZ = ChunkUtils.getChunkCoordinate(point.z() + range);
|
||||
final double squaredRange = range * range;
|
||||
if (minChunkX == maxChunkX && minChunkZ == maxChunkZ) {
|
||||
// Single chunk
|
||||
final var chunkEntities = (List<T>) entities.get(getChunkIndex(point));
|
||||
if (chunkEntities != null && !chunkEntities.isEmpty()) {
|
||||
chunkEntities.forEach(entity -> {
|
||||
final Point position = entityPositions.get(entity.getEntityId());
|
||||
if (point.distanceSquared(position) <= squaredRange) query.accept(entity);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Slow path for non-exact chunk range
|
||||
final double squaredRange = range * range;
|
||||
// Multiple chunks
|
||||
final int chunkRange = (int) (range / Chunk.CHUNK_SECTION_SIZE) + 1;
|
||||
forChunksInRange(point, chunkRange, (chunkX, chunkZ) -> {
|
||||
final var chunkEntities = (List<T>) entities.get(getChunkIndex(chunkX, chunkZ));
|
||||
if (chunkEntities == null || chunkEntities.isEmpty()) return;
|
||||
|
@ -162,12 +166,12 @@ final class EntityTrackerImpl implements EntityTracker {
|
|||
});
|
||||
}
|
||||
|
||||
private static final class TargetEntry<T extends Entity> {
|
||||
static final class TargetEntry<T extends Entity> {
|
||||
private final EntityTracker.Target<T> target;
|
||||
private final Set<T> entities = ConcurrentHashMap.newKeySet(); // Thread-safe since exposed
|
||||
private final Set<T> entitiesView = Collections.unmodifiableSet(entities);
|
||||
// Chunk index -> entities inside it
|
||||
private final Long2ObjectSyncMap<List<T>> chunkEntities = Long2ObjectSyncMap.hashmap();
|
||||
final Long2ObjectSyncMap<List<T>> chunkEntities = Long2ObjectSyncMap.hashmap();
|
||||
|
||||
TargetEntry(Target<T> target) {
|
||||
this.target = target;
|
||||
|
|
|
@ -3,7 +3,6 @@ package net.minestom.server.instance;
|
|||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.instance.EntityTracker;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -177,6 +176,46 @@ public class EntityTrackerTest {
|
|||
assertEquals(0, entities.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nearbySingleChunk() {
|
||||
var ent1 = new Entity(EntityType.ZOMBIE);
|
||||
var ent2 = new Entity(EntityType.ZOMBIE);
|
||||
var ent3 = new Entity(EntityType.ZOMBIE);
|
||||
var updater = new EntityTracker.Update<>() {
|
||||
@Override
|
||||
public void add(@NotNull Entity entity) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull Entity entity) {
|
||||
// Empty
|
||||
}
|
||||
};
|
||||
|
||||
EntityTracker tracker = EntityTracker.newTracker();
|
||||
tracker.register(ent1, new Vec(5, 0, 5), EntityTracker.Target.ENTITIES, updater);
|
||||
tracker.register(ent2, new Vec(8, 0, 8), EntityTracker.Target.ENTITIES, updater);
|
||||
tracker.register(ent3, new Vec(17, 0, 17), EntityTracker.Target.ENTITIES, updater);
|
||||
|
||||
Set<Entity> entities = new HashSet<>();
|
||||
|
||||
entities.add(ent1);
|
||||
entities.add(ent2);
|
||||
tracker.nearbyEntities(Vec.ZERO, 16, EntityTracker.Target.ENTITIES, entities::add);
|
||||
assertEquals(Set.of(ent1, ent2), entities);
|
||||
entities.clear();
|
||||
|
||||
entities.add(ent1);
|
||||
entities.add(ent2);
|
||||
tracker.nearbyEntities(new Vec(8, 0, 8), 5, EntityTracker.Target.ENTITIES, entity -> assertTrue(entities.remove(entity)));
|
||||
assertEquals(0, entities.size());
|
||||
|
||||
entities.add(ent2);
|
||||
tracker.nearbyEntities(new Vec(8, 0, 8), 1, EntityTracker.Target.ENTITIES, entity -> assertTrue(entities.remove(entity)));
|
||||
assertEquals(0, entities.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectionView() {
|
||||
var ent1 = new Entity(EntityType.ZOMBIE);
|
||||
|
|
Loading…
Reference in New Issue