2022-02-26 00:10:26 +01:00
|
|
|
package net.minestom.server.entity;
|
2021-11-01 18:04:00 +01:00
|
|
|
|
2022-02-18 11:13:45 +01:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2022-03-01 04:21:16 +01:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
2021-12-22 04:47:48 +01:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
|
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
2022-01-26 18:37:21 +01:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-11-01 18:04:00 +01:00
|
|
|
import net.minestom.server.coordinate.Point;
|
|
|
|
import net.minestom.server.instance.EntityTracker;
|
2022-02-08 05:28:19 +01:00
|
|
|
import net.minestom.server.instance.Instance;
|
2021-11-01 18:04:00 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
2021-12-09 17:26:27 +01:00
|
|
|
import java.util.*;
|
2021-11-01 18:04:00 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
|
|
|
import java.util.function.Consumer;
|
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
2022-02-26 00:10:26 +01:00
|
|
|
final class EntityView {
|
|
|
|
private static final int RANGE = MinecraftServer.getEntityViewDistance() * 16;
|
2021-11-01 18:04:00 +01:00
|
|
|
private final Entity entity;
|
2022-02-08 05:28:19 +01:00
|
|
|
private final Set<Player> manualViewers = new HashSet<>();
|
2021-11-01 18:04:00 +01:00
|
|
|
|
|
|
|
// Decide if this entity should be viewable to X players
|
|
|
|
public final Option<Player> viewableOption;
|
|
|
|
// Decide if this entity should view X entities
|
|
|
|
public final Option<Entity> viewerOption;
|
|
|
|
|
2022-02-26 00:10:26 +01:00
|
|
|
final Set<Player> set = new SetImpl();
|
2021-11-01 18:04:00 +01:00
|
|
|
private final Object mutex = this;
|
|
|
|
|
2022-02-14 07:46:19 +01:00
|
|
|
private volatile TrackedLocation trackedLocation;
|
|
|
|
|
2022-02-26 00:10:26 +01:00
|
|
|
public EntityView(Entity entity) {
|
2021-11-01 18:04:00 +01:00
|
|
|
this.entity = entity;
|
2022-02-26 00:10:26 +01:00
|
|
|
this.viewableOption = new Option<>(EntityTracker.Target.PLAYERS, Entity::autoViewEntities,
|
|
|
|
player -> {
|
|
|
|
// Add viewable
|
|
|
|
var lock1 = player.getEntityId() < entity.getEntityId() ? player : entity;
|
|
|
|
var lock2 = lock1 == entity ? player : entity;
|
|
|
|
synchronized (lock1.viewEngine.mutex) {
|
|
|
|
synchronized (lock2.viewEngine.mutex) {
|
|
|
|
if (!entity.viewEngine.viewableOption.predicate(player) ||
|
|
|
|
!player.viewEngine.viewerOption.predicate(entity)) return;
|
|
|
|
entity.viewEngine.viewableOption.register(player);
|
|
|
|
player.viewEngine.viewerOption.register(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entity.updateNewViewer(player);
|
2022-03-01 04:21:16 +01:00
|
|
|
},
|
|
|
|
player -> {
|
|
|
|
// Remove viewable
|
|
|
|
var lock1 = player.getEntityId() < entity.getEntityId() ? player : entity;
|
|
|
|
var lock2 = lock1 == entity ? player : entity;
|
|
|
|
synchronized (lock1.viewEngine.mutex) {
|
|
|
|
synchronized (lock2.viewEngine.mutex) {
|
|
|
|
entity.viewEngine.viewableOption.unregister(player);
|
|
|
|
player.viewEngine.viewerOption.unregister(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
entity.updateOldViewer(player);
|
|
|
|
});
|
2022-02-26 00:10:26 +01:00
|
|
|
this.viewerOption = new Option<>(EntityTracker.Target.ENTITIES, Entity::isAutoViewable,
|
|
|
|
entity instanceof Player player ? e -> e.viewEngine.viewableOption.addition.accept(player) : null,
|
|
|
|
entity instanceof Player player ? e -> e.viewEngine.viewableOption.removal.accept(player) : null);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
2022-02-13 07:51:47 +01:00
|
|
|
public void updateTracker(@Nullable Instance instance, @NotNull Point point) {
|
2022-02-14 07:46:19 +01:00
|
|
|
this.trackedLocation = instance != null ? new TrackedLocation(instance, point) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
record TrackedLocation(Instance instance, Point point) {
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean manualAdd(@NotNull Player player) {
|
|
|
|
if (player == this.entity) return false;
|
|
|
|
synchronized (mutex) {
|
2022-02-13 17:34:09 +01:00
|
|
|
if (manualViewers.add(player)) {
|
|
|
|
viewableOption.bitSet.add(player.getEntityId());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean manualRemove(@NotNull Player player) {
|
|
|
|
if (player == this.entity) return false;
|
|
|
|
synchronized (mutex) {
|
2022-02-13 17:34:09 +01:00
|
|
|
if (manualViewers.remove(player)) {
|
|
|
|
viewableOption.bitSet.remove(player.getEntityId());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-25 21:30:58 +01:00
|
|
|
public void forManuals(@NotNull Consumer<Player> consumer) {
|
|
|
|
synchronized (mutex) {
|
|
|
|
this.manualViewers.forEach(consumer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
public boolean hasPredictableViewers() {
|
|
|
|
// Verify if this entity's viewers can be predicted from surrounding entities
|
|
|
|
synchronized (mutex) {
|
2022-03-03 16:32:52 +01:00
|
|
|
return viewableOption.isAuto() && viewableOption.predicate == null && manualViewers.isEmpty();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void handleAutoViewAddition(Entity entity) {
|
2022-03-01 04:21:16 +01:00
|
|
|
handleAutoView(entity, viewerOption.addition, viewableOption.addition);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void handleAutoViewRemoval(Entity entity) {
|
2022-03-01 04:21:16 +01:00
|
|
|
handleAutoView(entity, viewerOption.removal, viewableOption.removal);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
2022-03-01 04:21:16 +01:00
|
|
|
private void handleAutoView(Entity entity, Consumer<Entity> viewer, Consumer<Player> viewable) {
|
2021-11-01 18:04:00 +01:00
|
|
|
if (this.entity instanceof Player && viewerOption.isAuto() && entity.isAutoViewable()) {
|
2022-01-12 10:47:38 +01:00
|
|
|
if (viewer != null) viewer.accept(entity); // Send packet to this player
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
if (entity instanceof Player player && player.autoViewEntities() && viewableOption.isAuto()) {
|
2022-01-12 10:47:38 +01:00
|
|
|
if (viewable != null) viewable.accept(player); // Send packet to the range-visible player
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public final class Option<T extends Entity> {
|
|
|
|
@SuppressWarnings("rawtypes")
|
2022-02-26 00:10:26 +01:00
|
|
|
private static final AtomicIntegerFieldUpdater<EntityView.Option> UPDATER = AtomicIntegerFieldUpdater.newUpdater(EntityView.Option.class, "auto");
|
2022-02-08 05:28:19 +01:00
|
|
|
// Entities that should be tracked from this option
|
|
|
|
private final EntityTracker.Target<T> target;
|
2021-11-01 18:04:00 +01:00
|
|
|
// The condition that must be met for this option to be considered auto.
|
|
|
|
private final Predicate<T> loopPredicate;
|
|
|
|
// The consumers to be called when an entity is added/removed.
|
|
|
|
public final Consumer<T> addition, removal;
|
2021-12-16 14:47:20 +01:00
|
|
|
// Contains all the auto-entity ids that are viewable by this option.
|
2021-12-22 04:47:48 +01:00
|
|
|
public final IntSet bitSet = new IntOpenHashSet();
|
2021-11-01 18:04:00 +01:00
|
|
|
// 1 if auto, 0 if manual
|
|
|
|
private volatile int auto = 1;
|
|
|
|
// The custom rule used to determine if an entity is viewable.
|
2022-03-03 16:32:52 +01:00
|
|
|
// null if auto-viewable
|
|
|
|
private Predicate<T> predicate = null;
|
2022-03-03 16:12:05 +01:00
|
|
|
|
2022-02-08 05:28:19 +01:00
|
|
|
public Option(EntityTracker.Target<T> target, Predicate<T> loopPredicate,
|
2021-11-01 18:04:00 +01:00
|
|
|
Consumer<T> addition, Consumer<T> removal) {
|
2022-02-08 05:28:19 +01:00
|
|
|
this.target = target;
|
2021-11-01 18:04:00 +01:00
|
|
|
this.loopPredicate = loopPredicate;
|
|
|
|
this.addition = addition;
|
|
|
|
this.removal = removal;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isAuto() {
|
|
|
|
return auto == 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean predicate(T entity) {
|
2022-03-03 16:32:52 +01:00
|
|
|
final Predicate<T> predicate = this.predicate;
|
|
|
|
return predicate == null || predicate.test(entity);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isRegistered(T entity) {
|
2021-12-13 12:54:27 +01:00
|
|
|
return bitSet.contains(entity.getEntityId());
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void register(T entity) {
|
2022-03-09 17:28:37 +01:00
|
|
|
assert Entity.getEntity(entity.getEntityId()) == entity : "Unregistered entity shouldn't be registered as viewer";
|
2021-12-13 12:54:27 +01:00
|
|
|
this.bitSet.add(entity.getEntityId());
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void unregister(T entity) {
|
2021-12-13 12:54:27 +01:00
|
|
|
this.bitSet.remove(entity.getEntityId());
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void updateAuto(boolean autoViewable) {
|
|
|
|
final boolean previous = UPDATER.getAndSet(this, autoViewable ? 1 : 0) == 1;
|
|
|
|
if (previous != autoViewable) {
|
|
|
|
synchronized (mutex) {
|
2022-02-08 05:28:19 +01:00
|
|
|
if (autoViewable) update(loopPredicate, addition);
|
|
|
|
else update(this::isRegistered, removal);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void updateRule(Predicate<T> predicate) {
|
|
|
|
synchronized (mutex) {
|
|
|
|
this.predicate = predicate;
|
2022-02-14 07:46:19 +01:00
|
|
|
updateRule0(predicate);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void updateRule() {
|
|
|
|
synchronized (mutex) {
|
2022-02-14 07:46:19 +01:00
|
|
|
updateRule0(predicate);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-14 07:46:19 +01:00
|
|
|
void updateRule0(Predicate<T> predicate) {
|
2022-03-03 16:32:52 +01:00
|
|
|
if (predicate == null) {
|
|
|
|
update(loopPredicate, entity -> {
|
|
|
|
if (!isRegistered(entity)) addition.accept(entity);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
update(loopPredicate, entity -> {
|
|
|
|
final boolean result = predicate.test(entity);
|
|
|
|
if (result != isRegistered(entity)) {
|
|
|
|
if (result) addition.accept(entity);
|
|
|
|
else removal.accept(entity);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-02-14 07:46:19 +01:00
|
|
|
}
|
|
|
|
|
2022-02-08 05:28:19 +01:00
|
|
|
private void update(Predicate<T> visibilityPredicate,
|
2021-11-01 18:04:00 +01:00
|
|
|
Consumer<T> action) {
|
2022-02-08 05:28:19 +01:00
|
|
|
references().forEach(entity -> {
|
2022-02-26 00:10:26 +01:00
|
|
|
if (entity == EntityView.this.entity || !visibilityPredicate.test(entity)) return;
|
2022-02-08 05:28:19 +01:00
|
|
|
if (entity instanceof Player player && manualViewers.contains(player)) return;
|
|
|
|
if (entity.getVehicle() != null) return;
|
|
|
|
action.accept(entity);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-02-18 11:13:45 +01:00
|
|
|
private int lastSize;
|
|
|
|
|
|
|
|
private Collection<T> references() {
|
2022-02-26 00:10:26 +01:00
|
|
|
final TrackedLocation trackedLocation = EntityView.this.trackedLocation;
|
2022-02-18 11:13:45 +01:00
|
|
|
if (trackedLocation == null) return List.of();
|
2022-02-14 07:46:19 +01:00
|
|
|
final Instance instance = trackedLocation.instance();
|
|
|
|
final Point point = trackedLocation.point();
|
2022-02-18 11:13:45 +01:00
|
|
|
|
2022-02-18 16:52:33 +01:00
|
|
|
Int2ObjectOpenHashMap<T> entityMap = new Int2ObjectOpenHashMap<>(lastSize);
|
2022-02-26 00:10:26 +01:00
|
|
|
instance.getEntityTracker().nearbyEntities(point, RANGE, target,
|
2022-02-25 02:30:01 +01:00
|
|
|
(entity) -> entityMap.putIfAbsent(entity.getEntityId(), entity));
|
2022-02-18 11:13:45 +01:00
|
|
|
this.lastSize = entityMap.size();
|
|
|
|
return entityMap.values();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-26 00:10:26 +01:00
|
|
|
final class SetImpl extends AbstractSet<Player> {
|
2021-11-01 18:04:00 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull Iterator<Player> iterator() {
|
2022-03-09 17:28:37 +01:00
|
|
|
List<Player> players;
|
2021-11-01 18:04:00 +01:00
|
|
|
synchronized (mutex) {
|
2022-03-01 04:21:16 +01:00
|
|
|
var bitSet = viewableOption.bitSet;
|
2022-03-01 05:36:33 +01:00
|
|
|
if (bitSet.isEmpty()) return Collections.emptyIterator();
|
2022-03-09 17:28:37 +01:00
|
|
|
players = new ArrayList<>(bitSet.size());
|
2022-03-01 04:21:16 +01:00
|
|
|
for (IntIterator it = bitSet.intIterator(); it.hasNext(); ) {
|
|
|
|
final int id = it.nextInt();
|
|
|
|
final Player player = (Player) Entity.getEntity(id);
|
2022-03-09 17:28:37 +01:00
|
|
|
if (player != null) players.add(player);
|
2022-03-01 04:21:16 +01:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
2022-03-09 17:28:37 +01:00
|
|
|
return players.iterator();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int size() {
|
|
|
|
synchronized (mutex) {
|
2022-02-13 17:34:09 +01:00
|
|
|
return viewableOption.bitSet.size();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isEmpty() {
|
|
|
|
synchronized (mutex) {
|
2022-02-13 17:34:09 +01:00
|
|
|
return viewableOption.bitSet.isEmpty();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean contains(Object o) {
|
|
|
|
if (!(o instanceof Player player)) return false;
|
|
|
|
synchronized (mutex) {
|
2022-02-13 17:34:09 +01:00
|
|
|
return viewableOption.isRegistered(player);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|