Split BossBarManager into multiple classes for clarity

This commit is contained in:
Kieran Wallbanks 2021-03-25 15:53:05 +00:00
parent 658d07e8e4
commit 7afca9554d
5 changed files with 340 additions and 263 deletions

View File

@ -1,7 +1,7 @@
package net.minestom.server;
import net.minestom.server.advancements.AdvancementManager;
import net.minestom.server.adventure.BossBarManager;
import net.minestom.server.adventure.bossbar.BossBarManager;
import net.minestom.server.adventure.SerializationManager;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.benchmark.BenchmarkManager;

View File

@ -1,262 +0,0 @@
package net.minestom.server.adventure;
import com.google.common.collect.MapMaker;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.bossbar.BossBar.Flag;
import net.kyori.adventure.bossbar.BossBar.Overlay;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.network.packet.server.play.BossBarPacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static net.minestom.server.network.packet.server.play.BossBarPacket.Action.*;
/**
* Manages all boss bars known to this Minestom instance. This implementation is heavily
* based on <a href="https://github.com/VelocityPowered/Velocity">Velocity</a>'s
* boss bar management system.
*/
public class BossBarManager implements BossBar.Listener {
private static final int CONCURRENCY_LEVEL = 4;
private final Map<BossBar, Holder> bars;
/**
* Creates a new boss bar manager.
*/
public BossBarManager() {
this.bars = new MapMaker().concurrencyLevel(CONCURRENCY_LEVEL).weakKeys().makeMap();
MinecraftServer.getGlobalEventHandler().addEventCallback(PlayerDisconnectEvent.class, this::onDisconnect);
}
/**
* Adds the specified player to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param player the intended viewer
* @param bar the boss bar to show
*/
public void addBossBar(@NotNull Player player, @NotNull BossBar bar) {
Holder holder = this.getOrCreateHandler(bar);
if (holder.players.add(player.getUuid())) {
player.getPlayerConnection().sendPacket(holder.createAddPacket());
}
}
/**
* Removes the specified player from the boss bar's viewers and despawns the boss bar.
*
* @param player the intended viewer
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Player player, @NotNull BossBar bar) {
Holder holder = this.getOrCreateHandler(bar);
if (holder.players.remove(player.getUuid())) {
player.getPlayerConnection().sendPacket(holder.createRemovePacket());
}
}
/**
* Adds the specified players to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param players the players
* @param bar the boss bar
*/
public void addBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
Holder holder = this.getOrCreateHandler(bar);
Collection<Player> addedPlayers = new ArrayList<>();
for (Player player : players) {
if (holder.players.add(player.getUuid())) {
addedPlayers.add(player);
}
}
if (!addedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(players, holder.createAddPacket());
}
}
/**
* Removes the specified players from the boss bar's viewers and despawns the boss bar.
*
* @param players the intended viewers
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
Holder holder = this.getOrCreateHandler(bar);
Collection<Player> removedPlayers = new ArrayList<>();
for (Player player : players) {
if (holder.players.remove(player.getUuid())) {
removedPlayers.add(player);
}
}
if (!removedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(players, holder.createRemovePacket());
}
}
@Override
public void bossBarNameChanged(@NotNull BossBar bar, @NotNull Component oldName, @NotNull Component newName) {
Holder holder = this.bars.get(bar);
this.updatePlayers(holder.createTitleUpdate(newName), holder.players);
}
@Override
public void bossBarProgressChanged(@NotNull BossBar bar, float oldProgress, float newProgress) {
Holder holder = this.bars.get(bar);
this.updatePlayers(holder.createPercentUpdate(newProgress), holder.players);
}
@Override
public void bossBarColorChanged(@NotNull BossBar bar, @NotNull Color oldColor, @NotNull Color newColor) {
Holder holder = this.bars.get(bar);
this.updatePlayers(holder.createColorUpdate(newColor), holder.players);
}
@Override
public void bossBarOverlayChanged(@NotNull BossBar bar, BossBar.@NotNull Overlay oldOverlay, BossBar.@NotNull Overlay newOverlay) {
Holder holder = this.bars.get(bar);
this.updatePlayers(holder.createOverlayUpdate(newOverlay), holder.players);
}
@Override
public void bossBarFlagsChanged(@NotNull BossBar bar, @NotNull Set<BossBar.Flag> flagsAdded, @NotNull Set<BossBar.Flag> flagsRemoved) {
Holder holder = this.bars.get(bar);
this.updatePlayers(holder.createFlagsUpdate(), holder.players);
}
/**
* Sends the packet to all players in the set, removing them if they no longer exist
* in the connection manager.
*
* @param packet the packet
* @param uuids the players
*/
private void updatePlayers(BossBarPacket packet, Set<UUID> uuids) {
Iterator<UUID> iterator = uuids.iterator();
Collection<Player> players = new ArrayList<>();
while (iterator.hasNext()) {
Player player = MinecraftServer.getConnectionManager().getPlayer(iterator.next());
if (player == null) {
iterator.remove();
} else {
players.add(player);
}
}
PacketUtils.sendGroupedPacket(players, packet);
}
/**
* Gets or creates a handler for this bar.
*
* @param bar the bar
*
* @return the handler
*/
private @NotNull Holder getOrCreateHandler(@NotNull BossBar bar) {
Holder holder = this.bars.computeIfAbsent(bar, Holder::new);
if (!holder.registered) {
bar.addListener(this);
holder.registered = true;
}
return holder;
}
/**
* Called when a player disconnects. This removes the player from any boss bars they
* may be subscribed to.
*
* @param event the event
*/
private void onDisconnect(@NotNull PlayerDisconnectEvent event) {
for (Holder holder : this.bars.values()) {
holder.players.remove(event.getPlayer().getUuid());
}
}
private static class Holder {
protected final UUID uuid;
protected final BossBar bar;
protected final Set<UUID> players;
protected boolean registered;
Holder(@NotNull BossBar bar) {
this.uuid = UUID.randomUUID();
this.bar = bar;
this.players = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
this.registered = false;
}
@NotNull BossBarPacket createRemovePacket() {
return this.createGenericPacket(REMOVE, packet -> {});
}
@NotNull BossBarPacket createAddPacket() {
return this.createGenericPacket(ADD, packet -> {
packet.title = bar.name();
packet.color = bar.color();
packet.overlay = bar.overlay();
packet.health = bar.progress();
packet.flags = AdventurePacketConvertor.getBossBarFlagValue(bar.flags());
});
}
@NotNull BossBarPacket createPercentUpdate(float newPercent) {
return this.createGenericPacket(UPDATE_HEALTH, packet -> packet.health = newPercent);
}
@NotNull BossBarPacket createColorUpdate(@NotNull Color color) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.color = color;
packet.overlay = bar.overlay();
});
}
@NotNull BossBarPacket createTitleUpdate(@NotNull Component title) {
return this.createGenericPacket(UPDATE_TITLE, packet -> packet.title = title);
}
@NotNull BossBarPacket createFlagsUpdate() {
return createFlagsUpdate(bar.flags());
}
@NotNull BossBarPacket createFlagsUpdate(@NotNull Set<Flag> newFlags) {
return this.createGenericPacket(UPDATE_FLAGS, packet -> packet.flags = AdventurePacketConvertor.getBossBarFlagValue(bar.flags()));
}
@NotNull BossBarPacket createOverlayUpdate(@NotNull Overlay overlay) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.overlay = overlay;
packet.color = bar.color();
});
}
@NotNull BossBarPacket createGenericPacket(@NotNull BossBarPacket.Action action, @NotNull Consumer<BossBarPacket> consumer) {
BossBarPacket packet = new BossBarPacket();
packet.uuid = this.uuid;
packet.action = action;
consumer.accept(packet);
return packet;
}
}
}

View File

@ -0,0 +1,113 @@
package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.Viewable;
import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.BossBarPacket;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import static net.minestom.server.network.packet.server.play.BossBarPacket.Action.*;
/**
* A holder of a boss bar. This class is not intended for public use, instead you should
* use {@link BossBarManager} to manage boss bars for players.
*/
final class BossBarHolder implements Viewable {
final UUID uuid;
final BossBar bar;
final Set<UUID> players;
boolean registered;
BossBarHolder(@NotNull BossBar bar) {
this.uuid = UUID.randomUUID();
this.bar = bar;
this.players = ConcurrentHashMap.newKeySet();
this.registered = false;
}
@NotNull BossBarPacket createRemovePacket() {
return this.createGenericPacket(REMOVE, packet -> {});
}
@NotNull BossBarPacket createAddPacket() {
return this.createGenericPacket(ADD, packet -> {
packet.title = bar.name();
packet.color = bar.color();
packet.overlay = bar.overlay();
packet.health = bar.progress();
packet.flags = AdventurePacketConvertor.getBossBarFlagValue(bar.flags());
});
}
@NotNull BossBarPacket createPercentUpdate(float newPercent) {
return this.createGenericPacket(UPDATE_HEALTH, packet -> packet.health = newPercent);
}
@NotNull BossBarPacket createColorUpdate(@NotNull BossBar.Color color) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.color = color;
packet.overlay = bar.overlay();
});
}
@NotNull BossBarPacket createTitleUpdate(@NotNull Component title) {
return this.createGenericPacket(UPDATE_TITLE, packet -> packet.title = title);
}
@NotNull BossBarPacket createFlagsUpdate() {
return createFlagsUpdate(bar.flags());
}
@NotNull BossBarPacket createFlagsUpdate(@NotNull Set<BossBar.Flag> newFlags) {
return this.createGenericPacket(UPDATE_FLAGS, packet -> packet.flags = AdventurePacketConvertor.getBossBarFlagValue(newFlags));
}
@NotNull BossBarPacket createOverlayUpdate(@NotNull BossBar.Overlay overlay) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.overlay = overlay;
packet.color = bar.color();
});
}
private @NotNull BossBarPacket createGenericPacket(@NotNull BossBarPacket.Action action, @NotNull Consumer<BossBarPacket> consumer) {
BossBarPacket packet = new BossBarPacket();
packet.uuid = this.uuid;
packet.action = action;
consumer.accept(packet);
return packet;
}
@Override
public boolean addViewer(@NotNull Player player) {
return this.players.add(player.getUuid());
}
@Override
public boolean removeViewer(@NotNull Player player) {
return this.players.remove(player.getUuid());
}
@Override
public @NotNull Set<Player> getViewers() {
Set<Player> playerList = new HashSet<>();
for (UUID uuid : this.players) {
Player player = MinecraftServer.getConnectionManager().getPlayer(uuid);
if (player != null) {
playerList.add(player);
}
}
return playerList;
}
}

View File

@ -0,0 +1,56 @@
package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A listener for boss bar updates. This class is not intended for public use and it is
* automatically added to boss bars shown to players using the methods in
* {@link Audience}, instead you should use {@link BossBarManager} to manage boss bars
* for players.
*/
class BossBarListener implements BossBar.Listener {
private final BossBarManager manager;
/**
* Creates a new boss bar listener.
* @param manager the manager instance
*/
BossBarListener(BossBarManager manager) {
this.manager = manager;
}
@Override
public void bossBarNameChanged(@NotNull BossBar bar, @NotNull Component oldName, @NotNull Component newName) {
BossBarHolder holder = this.manager.bars.get(bar);
this.manager.updatePlayers(holder.createTitleUpdate(newName), holder.players);
}
@Override
public void bossBarProgressChanged(@NotNull BossBar bar, float oldProgress, float newProgress) {
BossBarHolder holder = this.manager.bars.get(bar);
this.manager.updatePlayers(holder.createPercentUpdate(newProgress), holder.players);
}
@Override
public void bossBarColorChanged(@NotNull BossBar bar, @NotNull BossBar.Color oldColor, @NotNull BossBar.Color newColor) {
BossBarHolder holder = this.manager.bars.get(bar);
this.manager.updatePlayers(holder.createColorUpdate(newColor), holder.players);
}
@Override
public void bossBarOverlayChanged(@NotNull BossBar bar, BossBar.@NotNull Overlay oldOverlay, BossBar.@NotNull Overlay newOverlay) {
BossBarHolder holder = this.manager.bars.get(bar);
this.manager.updatePlayers(holder.createOverlayUpdate(newOverlay), holder.players);
}
@Override
public void bossBarFlagsChanged(@NotNull BossBar bar, @NotNull Set<BossBar.Flag> flagsAdded, @NotNull Set<BossBar.Flag> flagsRemoved) {
BossBarHolder holder = this.manager.bars.get(bar);
this.manager.updatePlayers(holder.createFlagsUpdate(), holder.players);
}
}

View File

@ -0,0 +1,170 @@
package net.minestom.server.adventure.bossbar;
import com.google.common.collect.MapMaker;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.network.packet.server.play.BossBarPacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Manages all boss bars known to this Minestom instance. Although this class can be used
* to show boss bars to players, it is preferable to use the boss bar methods in the
* {@link Audience} class instead.
*
* <p>This implementation is heavily based on
* <a href="https://github.com/VelocityPowered/Velocity">Velocity</a>'s boss bar
* management system.</p>
*
* @see Audience#showBossBar(BossBar)
* @see Audience#hideBossBar(BossBar)
*/
public class BossBarManager {
private static final int CONCURRENCY_LEVEL = 4;
private final BossBarListener listener;
final Map<BossBar, BossBarHolder> bars;
/**
* Creates a new boss bar manager.
*/
public BossBarManager() {
this.listener = new BossBarListener(this);
this.bars = new MapMaker().concurrencyLevel(CONCURRENCY_LEVEL).weakKeys().makeMap();
MinecraftServer.getGlobalEventHandler().addEventCallback(PlayerDisconnectEvent.class, this::onDisconnect);
}
/**
* Adds the specified player to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param player the intended viewer
* @param bar the boss bar to show
*/
public void addBossBar(@NotNull Player player, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
if (holder.addViewer(player)) {
player.getPlayerConnection().sendPacket(holder.createAddPacket());
}
}
/**
* Removes the specified player from the boss bar's viewers and despawns the boss bar.
*
* @param player the intended viewer
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Player player, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
if (holder.removeViewer(player)) {
player.getPlayerConnection().sendPacket(holder.createRemovePacket());
}
}
/**
* Adds the specified players to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param players the players
* @param bar the boss bar
*/
public void addBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
Collection<Player> addedPlayers = new ArrayList<>();
for (Player player : players) {
if (holder.addViewer(player)) {
addedPlayers.add(player);
}
}
if (!addedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(players, holder.createAddPacket());
}
}
/**
* Removes the specified players from the boss bar's viewers and despawns the boss bar.
*
* @param players the intended viewers
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
Collection<Player> removedPlayers = new ArrayList<>();
for (Player player : players) {
if (holder.removeViewer(player)) {
removedPlayers.add(player);
}
}
if (!removedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(players, holder.createRemovePacket());
}
}
/**
* Sends the packet to all players in the set, removing them if they no longer exist
* in the connection manager.
*
* @param packet the packet
* @param uuids the players
*/
void updatePlayers(BossBarPacket packet, Set<UUID> uuids) {
Iterator<UUID> iterator = uuids.iterator();
Collection<Player> players = new ArrayList<>();
while (iterator.hasNext()) {
Player player = MinecraftServer.getConnectionManager().getPlayer(iterator.next());
if (player == null) {
iterator.remove();
} else {
players.add(player);
}
}
PacketUtils.sendGroupedPacket(players, packet);
}
/**
* Gets or creates a handler for this bar.
*
* @param bar the bar
*
* @return the handler
*/
private @NotNull BossBarHolder getOrCreateHandler(@NotNull BossBar bar) {
BossBarHolder holder = this.bars.computeIfAbsent(bar, BossBarHolder::new);
if (!holder.registered) {
bar.addListener(this.listener);
holder.registered = true;
}
return holder;
}
/**
* Called when a player disconnects. This removes the player from any boss bars they
* may be subscribed to.
*
* @param event the event
*/
private void onDisconnect(@NotNull PlayerDisconnectEvent event) {
for (BossBarHolder holder : this.bars.values()) {
holder.players.remove(event.getPlayer().getUuid());
}
}
}