From 3323e5732cb06f26e567ea8e5ffd3b1307c30196 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 29 Sep 2021 20:36:27 +0200 Subject: [PATCH] Scoreboard improvements (#2078) * Fixed some Scoreboard bugs and ScoreboardUpdater is now global * Begin implementing below name support and better name display * Use final for classes * Revert "Begin implementing below name support and better name display" This reverts commit 01babd636ab317aa3e36cc6fdfa97cc8d9b4a532. * Don't remove objective if we're showing it * Prevent concurrency exceptions when switching servers * Properly fix the concurrency issue * Fix inconsistencies in update cycle * Few minor changes * Port over this fix * Fixed a problem that was introduced yesterday * Cleanup * Scores don't have to be removed before removing the objective itself * Moved away from the general thread pool and some more changes * Small changes * Converted switch statements Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../geysermc/connector/GeyserConnector.java | 2 + .../network/session/cache/WorldCache.java | 19 +- .../JavaDisplayScoreboardTranslator.java | 3 +- .../JavaScoreboardObjectiveTranslator.java | 23 +- .../java/scoreboard/JavaTeamTranslator.java | 17 +- .../scoreboard/JavaUpdateScoreTranslator.java | 2 +- .../connector/scoreboard/Objective.java | 58 +++- .../geysermc/connector/scoreboard/Score.java | 12 +- .../connector/scoreboard/Scoreboard.java | 277 ++++++++++-------- .../scoreboard/ScoreboardUpdater.java | 201 ++++++++----- .../geysermc/connector/scoreboard/Team.java | 54 ++-- 11 files changed, 384 insertions(+), 284 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 7e22792fa..37bfd2413 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -53,6 +53,7 @@ import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.crypto.AesCipher; @@ -155,6 +156,7 @@ public class GeyserConnector { ItemTranslator.init(); MessageTranslator.init(); LocaleUtils.init(); + ScoreboardUpdater.init(); ResourcePack.loadPacks(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 4a2939621..bec206ba1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -29,38 +29,33 @@ import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardUpdater; +import org.geysermc.connector.scoreboard.ScoreboardUpdater.ScoreboardSession; @Getter public class WorldCache { private final GeyserSession session; + private final ScoreboardSession scoreboardSession; + private Scoreboard scoreboard; @Setter private Difficulty difficulty = Difficulty.EASY; - private Scoreboard scoreboard; - private final ScoreboardUpdater scoreboardUpdater; - public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); - scoreboardUpdater = new ScoreboardUpdater(this); - scoreboardUpdater.start(); + scoreboardSession = new ScoreboardSession(session); } public void removeScoreboard() { if (scoreboard != null) { - for (Objective objective : scoreboard.getObjectives().values()) { - scoreboard.despawnObjective(objective); - } + scoreboard.removeScoreboard(); scoreboard = new Scoreboard(session); } } public int increaseAndGetScoreboardPacketsPerSecond() { - int pendingPps = scoreboardUpdater.incrementAndGetPacketsPerSecond(); - int pps = scoreboardUpdater.getPacketsPerSecond(); + int pendingPps = scoreboardSession.getPendingPacketsPerSecond().incrementAndGet(); + int pps = scoreboardSession.getPacketsPerSecond(); return Math.max(pps, pendingPps); } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java index a92cc628e..577e206b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java @@ -25,12 +25,11 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; - @Translator(packet = ServerDisplayScoreboardPacket.class) public class JavaDisplayScoreboardTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index dcdc9ac9b..ab5f7b350 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -25,30 +25,39 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.network.translators.chat.MessageTranslator; - -import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; -import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; +import org.geysermc.connector.scoreboard.UpdateType; @Translator(packet = ServerScoreboardObjectivePacket.class) public class JavaScoreboardObjectiveTranslator extends PacketTranslator { + private final GeyserLogger logger = GeyserConnector.getInstance().getLogger(); @Override public void translate(GeyserSession session, ServerScoreboardObjectivePacket packet) { WorldCache worldCache = session.getWorldCache(); Scoreboard scoreboard = worldCache.getScoreboard(); - Objective objective = scoreboard.getObjective(packet.getName()); int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); - if (objective == null && packet.getAction() != ObjectiveAction.REMOVE) { - objective = scoreboard.registerNewObjective(packet.getName(), false); + Objective objective = scoreboard.getObjective(packet.getName()); + if (objective != null && objective.getUpdateType() != UpdateType.REMOVE && packet.getAction() == ObjectiveAction.ADD) { + // matches vanilla behaviour + logger.warning("An objective with the same name '" + packet.getName() + "' already exists! Ignoring packet"); + return; + } + + if ((objective == null || objective.getUpdateType() == UpdateType.REMOVE) && packet.getAction() != ObjectiveAction.REMOVE) { + objective = scoreboard.registerNewObjective(packet.getName()); } switch (packet.getAction()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index d8d2273b9..9e9d2813c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -25,26 +25,25 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.ScoreboardUpdater; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.scoreboard.UpdateType; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.Arrays; -import java.util.Set; @Translator(packet = ServerTeamPacket.class) public class JavaTeamTranslator extends PacketTranslator { - private static final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger(); + private final GeyserLogger LOGGER = GeyserConnector.getInstance().getLogger(); @Override public void translate(GeyserSession session, ServerTeamPacket packet) { @@ -52,12 +51,16 @@ public class JavaTeamTranslator extends PacketTranslator { LOGGER.debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers())); } + if ((packet.getAction() == TeamAction.ADD_PLAYER || packet.getAction() == TeamAction.REMOVE_PLAYER) && packet.getPlayers().length == 0) { + return; + } + int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond(); Scoreboard scoreboard = session.getWorldCache().getScoreboard(); Team team = scoreboard.getTeam(packet.getTeamName()); switch (packet.getAction()) { - case CREATE -> scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) + case CREATE -> scoreboard.registerNewTeam(packet.getTeamName(), packet.getPlayers()) .setName(MessageTranslator.convertMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) @@ -108,8 +111,4 @@ public class JavaTeamTranslator extends PacketTranslator { scoreboard.onUpdate(); } } - - private Set toPlayerSet(String[] players) { - return new ObjectOpenHashSet<>(players); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index daf4d3832..6a210ce51 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -66,7 +66,7 @@ public class JavaUpdateScoreTranslator extends PacketTranslator ScoreboardPosition.BELOW_NAME; - case PLAYER_LIST -> ScoreboardPosition.PLAYER_LIST; - default -> ScoreboardPosition.SIDEBAR; - }; - } - public void registerScore(String id, int score) { if (!scores.containsKey(id)) { long scoreId = scoreboard.getNextId().getAndIncrement(); @@ -145,12 +138,57 @@ public final class Objective { public void setActive(ScoreboardPosition displaySlot) { if (!active) { active = true; - this.displaySlot = correctDisplaySlot(displaySlot); + this.displaySlot = displaySlot; displaySlotName = translateDisplaySlot(displaySlot); } } + /** + * The objective will be removed on the next update + */ + public void pendingRemove() { + updateType = UpdateType.REMOVE; + } + + public ScoreboardPosition getPositionCategory() { + return switch (displaySlot) { + case PLAYER_LIST -> ScoreboardPosition.PLAYER_LIST; + case BELOW_NAME -> ScoreboardPosition.BELOW_NAME; + default -> ScoreboardPosition.SIDEBAR; + }; + } + + public boolean hasTeamColor() { + return displaySlot != ScoreboardPosition.PLAYER_LIST && + displaySlot != ScoreboardPosition.BELOW_NAME && + displaySlot != ScoreboardPosition.SIDEBAR; + } + + public TeamColor getTeamColor() { + return switch (displaySlot) { + case SIDEBAR_TEAM_RED -> TeamColor.RED; + case SIDEBAR_TEAM_AQUA -> TeamColor.AQUA; + case SIDEBAR_TEAM_BLUE -> TeamColor.BLUE; + case SIDEBAR_TEAM_GOLD -> TeamColor.GOLD; + case SIDEBAR_TEAM_GRAY -> TeamColor.GRAY; + case SIDEBAR_TEAM_BLACK -> TeamColor.BLACK; + case SIDEBAR_TEAM_GREEN -> TeamColor.GREEN; + case SIDEBAR_TEAM_WHITE -> TeamColor.WHITE; + case SIDEBAR_TEAM_YELLOW -> TeamColor.YELLOW; + case SIDEBAR_TEAM_DARK_RED -> TeamColor.DARK_RED; + case SIDEBAR_TEAM_DARK_AQUA -> TeamColor.DARK_AQUA; + case SIDEBAR_TEAM_DARK_BLUE -> TeamColor.DARK_BLUE; + case SIDEBAR_TEAM_DARK_GRAY -> TeamColor.DARK_GRAY; + case SIDEBAR_TEAM_DARK_GREEN -> TeamColor.DARK_GREEN; + case SIDEBAR_TEAM_DARK_PURPLE -> TeamColor.DARK_PURPLE; + case SIDEBAR_TEAM_LIGHT_PURPLE -> TeamColor.LIGHT_PURPLE; + default -> null; + }; + } + public void removed() { + active = false; + updateType = UpdateType.REMOVE; scores = null; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index 79c90b096..7f3ac6e60 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -39,7 +39,7 @@ public final class Score { /** * Changes that have been made since the last cached data. */ - private Score.ScoreData currentData; + private final Score.ScoreData currentData; /** * The data that is currently displayed to the Bedrock client. */ @@ -72,14 +72,14 @@ public final class Score { if (currentData.team != null && team != null) { if (!currentData.team.equals(team)) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } // simplified from (this.team != null && team == null) || (this.team == null && team != null) if (currentData.team != null || team != null) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } @@ -126,13 +126,13 @@ public final class Score { @Getter public static final class ScoreData { - protected UpdateType updateType; - protected long updateTime; + private UpdateType updateType; + private long updateTime; private Team team; private int score; - protected ScoreData() { + private ScoreData() { updateType = UpdateType.ADD; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index de27db0bf..3795db83f 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -42,14 +42,15 @@ import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; -@Getter public final class Scoreboard { private final GeyserSession session; private final GeyserLogger logger; + @Getter private final AtomicLong nextId = new AtomicLong(0); private final Map objectives = new ConcurrentHashMap<>(); - private final Map teams = new HashMap<>(); + private final Map objectiveSlots = new HashMap<>(); + private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -59,45 +60,52 @@ public final class Scoreboard { this.logger = GeyserConnector.getInstance().getLogger(); } - public Objective registerNewObjective(String objectiveId, boolean active) { - Objective objective = objectives.get(objectiveId); - if (active || objective != null) { - return objective; + public void removeScoreboard() { + Iterator iterator = objectives.values().iterator(); + while (iterator.hasNext()) { + Objective objective = iterator.next(); + iterator.remove(); + + deleteObjective(objective, false); } + } + + public Objective registerNewObjective(String objectiveId) { + Objective objective = objectives.get(objectiveId); + if (objective != null) { + // we have no other choice, or we have to make a new map? + // if the objective hasn't been deleted, we have to force it + if (objective.getUpdateType() != REMOVE) { + return null; + } + deleteObjective(objective, true); + } + objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } - public Objective displayObjective(String objectiveId, ScoreboardPosition displaySlot) { + public void displayObjective(String objectiveId, ScoreboardPosition displaySlot) { Objective objective = objectives.get(objectiveId); - if (objective != null) { - if (!objective.isActive()) { - objective.setActive(displaySlot); - removeOldObjectives(objective); - return objective; - } - despawnObjective(objective); + if (objective == null) { + return; } - objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); - objectives.put(objectiveId, objective); - removeOldObjectives(objective); - return objective; - } - - private void removeOldObjectives(Objective newObjective) { - for (Objective next : objectives.values()) { - if (next.getId() == newObjective.getId()) { - continue; - } - if (next.getDisplaySlot() == newObjective.getDisplaySlot()) { - next.setUpdateType(REMOVE); - } + if (!objective.isActive()) { + objective.setActive(displaySlot); + // for reactivated objectives + objective.setUpdateType(ADD); } + + Objective storedObjective = objectiveSlots.get(displaySlot); + if (storedObjective != null && storedObjective != objective) { + objective.pendingRemove(); + } + objectiveSlots.put(displaySlot, objective); } - public Team registerNewTeam(String teamName, Set players) { + public Team registerNewTeam(String teamName, String[] players) { Team team = teams.get(teamName); if (team != null) { logger.info(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_overrides", teamName)); @@ -113,6 +121,10 @@ public final class Scoreboard { return objectives.get(objectiveName); } + public Collection getObjectives() { + return objectives.values(); + } + public Team getTeam(String teamName) { return teams.get(teamName); } @@ -120,7 +132,7 @@ public final class Scoreboard { public void unregisterObjective(String objectiveName) { Objective objective = getObjective(objectiveName); if (objective != null) { - objective.setUpdateType(REMOVE); + objective.pendingRemove(); } } @@ -132,114 +144,38 @@ public final class Scoreboard { } public void onUpdate() { - List addScores = new ArrayList<>(getLastAddScoreCount()); - List removeScores = new ArrayList<>(getLastRemoveScoreCount()); + List addScores = new ArrayList<>(lastAddScoreCount); + List removeScores = new ArrayList<>(lastRemoveScoreCount); List removedObjectives = new ArrayList<>(); + Team playerTeam = getTeamFor(session.getPlayerEntity().getUsername()); + Objective correctSidebar = null; + for (Objective objective : objectives.values()) { - if (!objective.isActive()) { - logger.debug("Ignoring non-active Scoreboard Objective '" + objective.getObjectiveName() + '\''); - continue; - } - - // hearts can't hold teams, so we treat them differently - if (objective.getType() == 1) { - for (Score score : objective.getScores().values()) { - boolean update = score.shouldUpdate(); - - if (update) { - score.update(objective.getObjectiveName()); - } - - if (score.getUpdateType() != REMOVE && update) { - addScores.add(score.getCachedInfo()); - } - if (score.getUpdateType() != ADD && update) { - removeScores.add(score.getCachedInfo()); - } - } - continue; - } - - boolean objectiveUpdate = objective.getUpdateType() == UPDATE; - boolean objectiveAdd = objective.getUpdateType() == ADD; - boolean objectiveRemove = objective.getUpdateType() == REMOVE; - - for (Score score : objective.getScores().values()) { - Team team = score.getTeam(); - - boolean add = objectiveAdd || objectiveUpdate; - boolean remove = false; - if (team != null) { - if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { - score.setTeam(null); - add = true; - remove = true; - } - } - - add |= score.shouldUpdate(); - remove |= score.shouldUpdate(); - - if (score.getUpdateType() == REMOVE || objectiveRemove) { - add = false; - } - - if (score.getUpdateType() == ADD || objectiveRemove) { - remove = false; - } - - if (objectiveRemove && score.getCachedData() != null) { - // This score has been sent to the client and needs to be removed since the objective is being removed - remove = true; - } else if (score.shouldUpdate()) { - score.update(objective.getObjectiveName()); - } - - if (add) { - addScores.add(score.getCachedInfo()); - } - - if (remove) { - removeScores.add(score.getCachedInfo()); - } - - // score is pending to be removed, so we can remove it from the objective - if (score.getUpdateType() == REMOVE) { - objective.removeScore0(score.getName()); - } - - score.setUpdateType(NOTHING); - } - - if (objectiveRemove) { + // objective has been deleted + if (objective.getUpdateType() == REMOVE) { removedObjectives.add(objective); + continue; } - if (objectiveUpdate) { - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); - session.sendUpstreamPacket(removeObjectivePacket); + if (playerTeam != null && playerTeam.getColor() == objective.getTeamColor()) { + correctSidebar = objective; } - - if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { - SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); - displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); - displayObjectivePacket.setDisplayName(objective.getDisplayName()); - displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); - displayObjectivePacket.setSortOrder(1); // ?? - session.sendUpstreamPacket(displayObjectivePacket); - } - - objective.setUpdateType(NOTHING); } + if (correctSidebar == null) { + correctSidebar = objectiveSlots.get(ScoreboardPosition.SIDEBAR); + } + + handleObjective(objectiveSlots.get(ScoreboardPosition.PLAYER_LIST), addScores, removeScores); + handleObjective(correctSidebar, addScores, removeScores); + handleObjective(objectiveSlots.get(ScoreboardPosition.BELOW_NAME), addScores, removeScores); + Iterator teamIterator = teams.values().iterator(); while (teamIterator.hasNext()) { Team current = teamIterator.next(); - switch (current.getUpdateType()) { + switch (current.getCachedUpdateType()) { case ADD, UPDATE -> current.markUpdated(); case REMOVE -> teamIterator.remove(); } @@ -259,17 +195,100 @@ public final class Scoreboard { session.sendUpstreamPacket(setScorePacket); } - // prevents crashes in some cases for (Objective objective : removedObjectives) { - despawnObjective(objective); + deleteObjective(objective, true); } lastAddScoreCount = addScores.size(); lastRemoveScoreCount = removeScores.size(); } - public void despawnObjective(Objective objective) { - objectives.remove(objective.getObjectiveName()); + private void handleObjective(Objective objective, List addScores, List removeScores) { + if (objective == null || objective.getUpdateType() == REMOVE) { + return; + } + + // hearts can't hold teams, so we treat them differently + if (objective.getType() == 1) { + for (Score score : objective.getScores().values()) { + boolean update = score.shouldUpdate(); + + if (update) { + score.update(objective.getObjectiveName()); + } + + if (score.getUpdateType() != REMOVE && update) { + addScores.add(score.getCachedInfo()); + } + if (score.getUpdateType() != ADD && update) { + removeScores.add(score.getCachedInfo()); + } + } + return; + } + + boolean objectiveAdd = objective.getUpdateType() == ADD; + boolean objectiveUpdate = objective.getUpdateType() == UPDATE; + + for (Score score : objective.getScores().values()) { + if (score.getUpdateType() == REMOVE) { + removeScores.add(score.getCachedInfo()); + // score is pending to be removed, so we can remove it from the objective + objective.removeScore0(score.getName()); + break; + } + + Team team = score.getTeam(); + + boolean add = objectiveAdd || objectiveUpdate; + + if (team != null) { + if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { + score.setTeam(null); + add = true; + } + } + + if (score.shouldUpdate()) { + score.update(objective.getObjectiveName()); + add = true; + } + + if (add) { + addScores.add(score.getCachedInfo()); + } + + score.setUpdateType(NOTHING); + } + + if (objectiveUpdate) { + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.sendUpstreamPacket(removeObjectivePacket); + } + + if (objectiveAdd || objectiveUpdate) { + SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); + displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); + displayObjectivePacket.setDisplayName(objective.getDisplayName()); + displayObjectivePacket.setCriteria("dummy"); + displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); + displayObjectivePacket.setSortOrder(1); // 0 = ascending, 1 = descending + session.sendUpstreamPacket(displayObjectivePacket); + } + + objective.setUpdateType(NOTHING); + } + + /** + * @param remove if we should remove the objective from the objectives map. + */ + public void deleteObjective(Objective objective, boolean remove) { + if (remove) { + objectives.remove(objective.getObjectiveName()); + } + objectiveSlots.remove(objective.getDisplaySlot(), objective); + objective.removed(); RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java index e5a802507..2fb21337e 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java @@ -25,100 +25,149 @@ package org.geysermc.connector.scoreboard; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.utils.LanguageUtils; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -public class ScoreboardUpdater extends Thread { +public final class ScoreboardUpdater extends Thread { public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second - private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000 * 3; // 1 update per 3 seconds + private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000; // 1 update per second private static final boolean DEBUG_ENABLED; - private final WorldCache worldCache; - private final GeyserSession session; - - private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; - private long lastUpdate = System.currentTimeMillis(); - private long lastLog = -1; - - private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); - private final AtomicInteger packetsPerSecond = new AtomicInteger(0); - private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); - - public ScoreboardUpdater(WorldCache worldCache) { - super("Scoreboard Updater"); - this.worldCache = worldCache; - session = worldCache.getSession(); - } - - @Override - public void run() { - if (!session.isClosed()) { - long currentTime = System.currentTimeMillis(); - - // reset score-packets per second every second - if (currentTime - lastPacketsPerSecondUpdate > 1000) { - lastPacketsPerSecondUpdate = currentTime; - packetsPerSecond.set(pendingPacketsPerSecond.get()); - pendingPacketsPerSecond.set(0); - } - - if (currentTime - lastUpdate > millisBetweenUpdates) { - lastUpdate = currentTime; - - int pps = packetsPerSecond.get(); - if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { - boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; - if (reachedSecondThreshold) { - millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES; - } else { - millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; - } - - worldCache.getScoreboard().onUpdate(); - - if (DEBUG_ENABLED && (currentTime - lastLog > 60000)) { // one minute - int threshold = reachedSecondThreshold ? - SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : - FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; - - GeyserConnector.getInstance().getLogger().info( - LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) + - LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) - ); - - lastLog = currentTime; - } - } - } - - session.getConnector().getGeneralThreadPool().schedule(this, 50, TimeUnit.MILLISECONDS); - } - } - - public int getPacketsPerSecond() { - return packetsPerSecond.get(); - } - - /** - * Increase the Scoreboard Packets Per Second and return the updated value - */ - public int incrementAndGetPacketsPerSecond() { - return pendingPacketsPerSecond.incrementAndGet(); - } - static { GeyserConfiguration config = GeyserConnector.getInstance().getConfig(); FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD); DEBUG_ENABLED = config.isDebugMode(); } + + private final GeyserConnector connector = GeyserConnector.getInstance(); + + private long lastUpdate = System.currentTimeMillis(); + private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); + + public static void init() { + new ScoreboardUpdater().start(); + } + + @Override + public void run() { + while (!connector.isShuttingDown()) { + long timeTillAction = getTimeTillNextAction(); + if (timeTillAction > 0) { + sleepFor(timeTillAction); + continue; + } + + long currentTime = System.currentTimeMillis(); + + // reset score-packets per second every second + if (currentTime - lastPacketsPerSecondUpdate >= 1000) { + lastPacketsPerSecondUpdate = currentTime; + for (GeyserSession session : connector.getPlayers()) { + ScoreboardSession scoreboardSession = session.getWorldCache().getScoreboardSession(); + + int oldPps = scoreboardSession.getPacketsPerSecond(); + int newPps = scoreboardSession.getPendingPacketsPerSecond().get(); + + scoreboardSession.packetsPerSecond = newPps; + scoreboardSession.pendingPacketsPerSecond.set(0); + + // just making sure that all updates are pushed before giving up control + if (oldPps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD && + newPps < FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + session.getWorldCache().getScoreboard().onUpdate(); + } + } + } + + if (currentTime - lastUpdate >= FIRST_MILLIS_BETWEEN_UPDATES) { + lastUpdate = currentTime; + + for (GeyserSession session : connector.getPlayers()) { + WorldCache worldCache = session.getWorldCache(); + ScoreboardSession scoreboardSession = worldCache.getScoreboardSession(); + + int pps = scoreboardSession.getPacketsPerSecond(); + if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) { + boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + int millisBetweenUpdates = reachedSecondThreshold ? + SECOND_MILLIS_BETWEEN_UPDATES : + FIRST_MILLIS_BETWEEN_UPDATES; + + if (currentTime - scoreboardSession.lastUpdate >= millisBetweenUpdates) { + worldCache.getScoreboard().onUpdate(); + scoreboardSession.lastUpdate = currentTime; + + if (DEBUG_ENABLED && (currentTime - scoreboardSession.lastLog >= 60000)) { // one minute + int threshold = reachedSecondThreshold ? + SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD : + FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD; + + connector.getLogger().info( + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.getName(), threshold, pps) + + LanguageUtils.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0)) + ); + + scoreboardSession.lastLog = currentTime; + } + } + } + } + } + + if (DEBUG_ENABLED) { + long timeSpent = System.currentTimeMillis() - currentTime; + if (timeSpent > 0) { + connector.getLogger().info(String.format( + "Scoreboard updater: took %s ms. Updated %s players", + timeSpent, connector.getPlayers().size() + )); + } + } + + long timeTillNextAction = getTimeTillNextAction(); + sleepFor(timeTillNextAction); + } + } + + private long getTimeTillNextAction() { + long currentTime = System.currentTimeMillis(); + + long timeUntilNextUpdate = FIRST_MILLIS_BETWEEN_UPDATES - (currentTime - lastUpdate); + long timeUntilPacketReset = 1000 - (currentTime - lastPacketsPerSecondUpdate); + + return Math.min(timeUntilNextUpdate, timeUntilPacketReset); + } + + private void sleepFor(long millis) { + if (millis <= 0) { + return; + } + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @RequiredArgsConstructor + @Getter + public static final class ScoreboardSession { + private final GeyserSession session; + private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); + private int packetsPerSecond; + private long lastUpdate; + private long lastLog; + } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index efefb339a..41891ee5b 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -48,7 +48,7 @@ public final class Team { @Setter private NameTagVisibility nameTagVisibility; @Setter private TeamColor color; - private TeamData currentData; + private final TeamData currentData; private TeamData cachedData; private boolean updating; @@ -60,22 +60,6 @@ public final class Team { entities = new ObjectOpenHashSet<>(); } - private void checkAddedEntities(List added) { - if (added.size() == 0) { - return; - } - // we don't have to change the updateType, - // because the scores itself need updating, not the team - for (Objective objective : scoreboard.getObjectives().values()) { - for (String addedEntity : added) { - Score score = objective.getScores().get(addedEntity); - if (score != null) { - score.setTeam(this); - } - } - } - } - public Team addEntities(String... names) { List added = new ArrayList<>(); for (String name : names) { @@ -83,18 +67,20 @@ public final class Team { added.add(name); } } - checkAddedEntities(added); - return this; - } - public Team addEntities(Set names) { - List added = new ArrayList<>(); - for (String name : names) { - if (entities.add(name)) { - added.add(name); + if (added.size() == 0) { + return this; + } + // we don't have to change the updateType, + // because the scores itself need updating, not the team + for (Objective objective : scoreboard.getObjectives()) { + for (String addedEntity : added) { + Score score = objective.getScores().get(addedEntity); + if (score != null) { + score.setTeam(this); + } } } - checkAddedEntities(added); return this; } @@ -169,6 +155,10 @@ public final class Team { } public UpdateType getUpdateType() { + return currentData.updateType; + } + + public UpdateType getCachedUpdateType() { return cachedData != null ? cachedData.updateType : currentData.updateType; } @@ -196,14 +186,14 @@ public final class Team { @Getter public static final class TeamData { - protected UpdateType updateType; - protected long updateTime; + private UpdateType updateType; + private long updateTime; - protected String name; - protected String prefix; - protected String suffix; + private String name; + private String prefix; + private String suffix; - protected TeamData() { + private TeamData() { updateType = UpdateType.ADD; }