Minestom/src/main/java/net/minestom/server/utils/ViewEngine.java

301 lines
11 KiB
Java
Raw Normal View History

2021-11-01 18:04:00 +01:00
package net.minestom.server.utils;
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.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
2022-02-13 07:51:47 +01:00
import net.minestom.server.instance.SharedInstance;
2021-11-01 18:04:00 +01:00
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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;
import java.util.stream.Stream;
2021-11-01 18:04:00 +01:00
/**
* Defines which players are able to see this element.
*/
@ApiStatus.Internal
public final class ViewEngine {
private final Entity entity;
private final int range;
private final Set<Player> manualViewers = new HashSet<>();
2021-11-01 18:04:00 +01:00
private Instance instance;
private Point lastPoint;
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;
private final Set<Player> set;
2021-11-01 18:04:00 +01:00
private final Object mutex = this;
public ViewEngine(@Nullable Entity entity,
Consumer<Player> autoViewableAddition, Consumer<Player> autoViewableRemoval,
Consumer<Entity> autoViewerAddition, Consumer<Entity> autoViewerRemoval) {
this.entity = entity;
if (entity != null) {
this.range = MinecraftServer.getEntityViewDistance();
this.set = new EntitySet();
} else {
this.range = MinecraftServer.getChunkViewDistance();
this.set = new ChunkSet();
}
this.viewableOption = new Option<>(EntityTracker.Target.PLAYERS, Entity::autoViewEntities, autoViewableAddition, autoViewableRemoval);
this.viewerOption = new Option<>(EntityTracker.Target.ENTITIES, Entity::isAutoViewable, autoViewerAddition, autoViewerRemoval);
2021-11-01 18:04:00 +01:00
}
2022-01-12 10:47:38 +01:00
public ViewEngine(@Nullable Entity entity) {
this(entity, null, null, null, null);
}
2021-11-01 18:04:00 +01:00
public ViewEngine() {
2022-01-12 10:47:38 +01:00
this(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) {
2021-11-01 18:04:00 +01:00
synchronized (mutex) {
this.instance = instance;
this.lastPoint = point;
2021-11-01 18:04:00 +01:00
}
}
public boolean manualAdd(@NotNull Player player) {
if (player == this.entity) return false;
synchronized (mutex) {
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) {
if (manualViewers.remove(player)) {
viewableOption.bitSet.remove(player.getEntityId());
return true;
}
return false;
2021-11-01 18:04:00 +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) {
return viewableOption.isAuto() && manualViewers.isEmpty();
}
}
public void handleAutoViewAddition(Entity entity) {
handleAutoView(entity, viewerOption.addition, viewableOption.addition);
}
public void handleAutoViewRemoval(Entity entity) {
handleAutoView(entity, viewerOption.removal, viewableOption.removal);
}
private void handleAutoView(Entity entity, Consumer<Entity> viewer, Consumer<Player> viewable) {
if (entity.getVehicle() != null)
return; // Passengers are handled by the vehicle, inheriting its viewing settings
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
}
}
2021-12-23 23:44:22 +01:00
public Object mutex() {
return mutex;
}
2021-11-01 18:04:00 +01:00
public Set<Player> asSet() {
return set;
}
public final class Option<T extends Entity> {
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater<Option> UPDATER = AtomicIntegerFieldUpdater.newUpdater(Option.class, "auto");
// 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;
// Contains all the auto-entity ids that are viewable by this option.
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.
private Predicate<T> predicate = entity -> true;
public Option(EntityTracker.Target<T> target, Predicate<T> loopPredicate,
2021-11-01 18:04:00 +01:00
Consumer<T> addition, Consumer<T> removal) {
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) {
return predicate.test(entity);
}
public boolean isRegistered(T entity) {
return bitSet.contains(entity.getEntityId());
2021-11-01 18:04:00 +01:00
}
public void register(T entity) {
this.bitSet.add(entity.getEntityId());
2021-11-01 18:04:00 +01:00
}
public void unregister(T entity) {
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) {
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;
updateRule();
}
}
public void updateRule() {
synchronized (mutex) {
update(loopPredicate, entity -> {
2021-11-01 18:04:00 +01:00
final boolean result = predicate.test(entity);
if (result != isRegistered(entity)) {
if (result) addition.accept(entity);
else removal.accept(entity);
}
});
}
}
private void update(Predicate<T> visibilityPredicate,
2021-11-01 18:04:00 +01:00
Consumer<T> action) {
references().forEach(entity -> {
if (entity == ViewEngine.this.entity || !visibilityPredicate.test(entity)) return;
if (entity instanceof Player player && manualViewers.contains(player)) return;
if (entity.getVehicle() != null) return;
action.accept(entity);
});
}
private Stream<T> references() {
2022-02-13 07:51:47 +01:00
final Instance instance = ViewEngine.this.instance;
if (instance == null) return Stream.empty();
var references = instance.getEntityTracker().references(lastPoint, range, target);
Stream<T> result = references.stream().flatMap(Collection::stream);
if (instance instanceof InstanceContainer container) {
// References from shared instances must be added to the result.
2022-02-13 07:51:47 +01:00
final List<SharedInstance> shared = container.getSharedInstances();
if (!shared.isEmpty()) {
2022-02-13 07:51:47 +01:00
Stream<T> sharedInstanceStream = shared.stream().<List<T>>mapMulti((inst, consumer) -> {
var ref = inst.getEntityTracker().references(lastPoint, range, target);
ref.forEach(consumer);
}).flatMap(Collection::stream);
2022-02-13 07:51:47 +01:00
result = Stream.concat(result, sharedInstanceStream);
2021-11-01 18:04:00 +01:00
}
2021-12-23 23:44:22 +01:00
}
return result.distinct();
2021-11-01 18:04:00 +01:00
}
}
final class ChunkSet extends AbstractSet<Player> {
@Override
public @NotNull Iterator<Player> iterator() {
synchronized (mutex) {
return viewableOption.references().toList().iterator();
}
}
@Override
public int size() {
synchronized (mutex) {
return (int) viewableOption.references().count();
}
}
@Override
public void forEach(Consumer<? super Player> action) {
synchronized (mutex) {
viewableOption.references().forEach(action);
}
}
}
final class EntitySet extends AbstractSet<Player> {
2021-11-01 18:04:00 +01:00
@Override
public @NotNull Iterator<Player> iterator() {
synchronized (mutex) {
return viewableOption.bitSet.intStream()
.mapToObj(operand -> (Player) Entity.getEntity(operand))
.toList().iterator();
2021-11-01 18:04:00 +01:00
}
}
@Override
public int size() {
synchronized (mutex) {
return viewableOption.bitSet.size();
2021-11-01 18:04:00 +01:00
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
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) {
return viewableOption.isRegistered(player);
2021-11-01 18:04:00 +01:00
}
}
@Override
public void forEach(Consumer<? super Player> action) {
synchronized (mutex) {
viewableOption.bitSet.forEach((int id) -> action.accept((Player) Entity.getEntity(id)));
2021-11-01 18:04:00 +01:00
}
}
}
}