diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1bfa700fb..ecfbdf9ea 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -56,6 +56,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.collision.CollisionTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.scoreboard.ScoreboardUpdater; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; @@ -147,6 +148,7 @@ public class GeyserConnector { RecipeRegistry.init(); SoundRegistry.init(); SoundHandlerRegistry.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 ce49d2a09..40117b459 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 @@ -31,37 +31,35 @@ 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; @Setter private Difficulty difficulty = Difficulty.EASY; private boolean showCoordinates = true; - 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); + for (Objective objective : scoreboard.getObjectives()) { + scoreboard.deleteObjective(objective); } 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); } 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 b42ac78e4..4ee0a8faa 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 8cc01662a..310095e45 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,38 @@ 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(ServerScoreboardObjectivePacket packet, GeyserSession session) { 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) { + 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/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index 35033ca52..6173633a5 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 scores = new ConcurrentHashMap<>(); + // todo add a 'to add' map so that we don't have to use a concurrentHashMap private Objective(Scoreboard scoreboard) { this.id = scoreboard.getNextId().getAndIncrement(); @@ -69,7 +71,7 @@ public final class Objective { public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { this(scoreboard); this.objectiveName = objectiveName; - this.displaySlot = correctDisplaySlot(displaySlot); + this.displaySlot = displaySlot; this.displaySlotName = translateDisplaySlot(displaySlot); this.displayName = displayName; this.type = type; @@ -86,17 +88,6 @@ public final class Objective { } } - private static ScoreboardPosition correctDisplaySlot(ScoreboardPosition displaySlot) { - switch (displaySlot) { - case BELOW_NAME: - return ScoreboardPosition.BELOW_NAME; - case PLAYER_LIST: - return ScoreboardPosition.PLAYER_LIST; - default: - return ScoreboardPosition.SIDEBAR; - } - } - public void registerScore(String id, int score) { if (!scores.containsKey(id)) { long scoreId = scoreboard.getNextId().getAndIncrement(); @@ -151,12 +142,74 @@ public final class Objective { public void setActive(ScoreboardPosition displaySlot) { if (!active) { active = true; - this.displaySlot = correctDisplaySlot(displaySlot); + this.displaySlot = displaySlot; displaySlotName = translateDisplaySlot(displaySlot); } } + public void deactivate() { + active = false; + } + + public ScoreboardPosition getPositionCategory() { + switch (displaySlot) { + case PLAYER_LIST: + return ScoreboardPosition.PLAYER_LIST; + case BELOW_NAME: + return ScoreboardPosition.BELOW_NAME; + default: + return ScoreboardPosition.SIDEBAR; + } + } + + public boolean hasTeamColor() { + return displaySlot != ScoreboardPosition.PLAYER_LIST && + displaySlot != ScoreboardPosition.BELOW_NAME && + displaySlot != ScoreboardPosition.SIDEBAR; + } + + public TeamColor getTeamColor() { + switch (displaySlot) { + case SIDEBAR_TEAM_RED: + return TeamColor.RED; + case SIDEBAR_TEAM_AQUA: + return TeamColor.AQUA; + case SIDEBAR_TEAM_BLUE: + return TeamColor.BLUE; + case SIDEBAR_TEAM_GOLD: + return TeamColor.GOLD; + case SIDEBAR_TEAM_GRAY: + return TeamColor.GRAY; + case SIDEBAR_TEAM_BLACK: + return TeamColor.BLACK; + case SIDEBAR_TEAM_GREEN: + return TeamColor.GREEN; + case SIDEBAR_TEAM_WHITE: + return TeamColor.WHITE; + case SIDEBAR_TEAM_YELLOW: + return TeamColor.YELLOW; + case SIDEBAR_TEAM_DARK_RED: + return TeamColor.DARK_RED; + case SIDEBAR_TEAM_DARK_AQUA: + return TeamColor.DARK_AQUA; + case SIDEBAR_TEAM_DARK_BLUE: + return TeamColor.DARK_BLUE; + case SIDEBAR_TEAM_DARK_GRAY: + return TeamColor.DARK_GRAY; + case SIDEBAR_TEAM_DARK_GREEN: + return TeamColor.DARK_GREEN; + case SIDEBAR_TEAM_DARK_PURPLE: + return TeamColor.DARK_PURPLE; + case SIDEBAR_TEAM_LIGHT_PURPLE: + return TeamColor.LIGHT_PURPLE; + default: + return null; + } + } + public void removed() { + active = false; + updateType = UpdateType.REMOVE; scores = null; } } 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 a389a373b..e7a353dc7 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,16 @@ 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 objectives = new HashMap<>(); + private final Map objectiveSlots = new HashMap<>(); + private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads + // todo add a 'to add' map so that we don't have to use a concurrentHashMap private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -59,11 +61,17 @@ public final class Scoreboard { this.logger = GeyserConnector.getInstance().getLogger(); } - public Objective registerNewObjective(String objectiveId, boolean active) { + public Objective registerNewObjective(String objectiveId) { Objective objective = objectives.get(objectiveId); - if (active || objective != null) { - return objective; + 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); } + objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; @@ -71,32 +79,24 @@ public final class Scoreboard { public Objective 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 null; } - objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); - objectives.put(objectiveId, objective); - removeOldObjectives(objective); + if (!objective.isActive()) { + objective.setActive(displaySlot); + // for reactivated objectives + objective.setUpdateType(ADD); + } + + Objective storedObjective = objectiveSlots.get(displaySlot); + if (storedObjective != null) { + deactivateObjective(storedObjective); + } + objectiveSlots.put(displaySlot, 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); - } - } - } - public Team registerNewTeam(String teamName, Set players) { Team team = teams.get(teamName); if (team != null) { @@ -113,6 +113,10 @@ public final class Scoreboard { return objectives.get(objectiveName); } + public Collection getObjectives() { + return objectives.values(); + } + public Team getTeam(String teamName) { return teams.get(teamName); } @@ -121,6 +125,7 @@ public final class Scoreboard { Objective objective = getObjective(objectiveName); if (objective != null) { objective.setUpdateType(REMOVE); + objective.deactivate(); } } @@ -132,114 +137,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, removedObjectives); + handleObjective(correctSidebar, addScores, removeScores, removedObjectives); + handleObjective(objectiveSlots.get(ScoreboardPosition.BELOW_NAME), addScores, removeScores, removedObjectives); + Iterator teamIterator = teams.values().iterator(); while (teamIterator.hasNext()) { Team current = teamIterator.next(); - switch (current.getUpdateType()) { + switch (current.getCachedUpdateType()) { case ADD: case UPDATE: current.markUpdated(); @@ -265,15 +194,156 @@ public final class Scoreboard { // prevents crashes in some cases for (Objective objective : removedObjectives) { - despawnObjective(objective); + deleteObjective(objective); } lastAddScoreCount = addScores.size(); lastRemoveScoreCount = removeScores.size(); } - public void despawnObjective(Objective objective) { + private void handleObjective(Objective objective, List addScores, List removeScores, List removedObjectives) { + if (objective == null) { + return; + } + + // objective has been removed when scores is null + if (objective.getScores() == null) { + objectiveSlots.remove(objective.getDisplaySlot()); + return; + } + + if (!objective.isActive()) { + deactivateObjective(objective); + objectiveSlots.remove(objective.getDisplaySlot()); + 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 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) { + removedObjectives.add(objective); + } + + if (objectiveUpdate) { + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.sendUpstreamPacket(removeObjectivePacket); + } + + 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); + } + + private void deactivateObjective(Objective objective) { + // Scoreboard has been removed already + if (objective.getScores() == null) { + return; + } + + List removedScores = new ArrayList<>(objective.getScores().size()); + for (Score score : objective.getScores().values()) { + removedScores.add(score.getCachedInfo()); + } + + objective.deactivate(); + + SetScorePacket scorePacket = new SetScorePacket(); + scorePacket.setAction(SetScorePacket.Action.REMOVE); + scorePacket.setInfos(removedScores); + session.sendUpstreamPacket(scorePacket); + + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.sendUpstreamPacket(removeObjectivePacket); + } + + public void deleteObjective(Objective objective) { objectives.remove(objective.getObjectiveName()); + + Objective storedSlot = objectiveSlots.get(objective.getDisplaySlot()); + if (storedSlot != null && storedSlot.getId() == objective.getId()) { + deactivateObjective(objective); + objective.removed(); + return; + } + 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 3812fb141..7be03d00d 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardUpdater.java @@ -25,6 +25,8 @@ 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; @@ -34,91 +36,107 @@ import org.geysermc.connector.utils.LanguageUtils; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -public class ScoreboardUpdater extends Thread { +public class ScoreboardUpdater implements Runnable { 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 * 3; // 1 update per seconds 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(); - } + private static GeyserConnector connector; 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 long lastUpdate = System.currentTimeMillis(); + private long lastPacketsPerSecondUpdate = System.currentTimeMillis(); + + public static void init() { + connector = GeyserConnector.getInstance(); + ScoreboardUpdater updater = new ScoreboardUpdater(); + updater.run(); + } + + public void run() { + long timeTillAction = getTimeTillNextAction(); + if (timeTillAction > 0) { + connector.getGeneralThreadPool().schedule(this, timeTillAction, TimeUnit.MILLISECONDS); + return; + } + + 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(); + scoreboardSession.packetsPerSecond = scoreboardSession.getPendingPacketsPerSecond().get(); + scoreboardSession.pendingPacketsPerSecond.set(0); + } + } + + 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; + if (reachedSecondThreshold) { + scoreboardSession.millisBetweenUpdates = SECOND_MILLIS_BETWEEN_UPDATES; + } else { + scoreboardSession.millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + } + + if (currentTime - scoreboardSession.lastUpdate > scoreboardSession.millisBetweenUpdates) { + worldCache.getScoreboard().onUpdate(); + + if (DEBUG_ENABLED && (currentTime - scoreboardSession.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", (scoreboardSession.millisBetweenUpdates / 1000.0)) + ); + + scoreboardSession.lastLog = currentTime; + } + } + } + } + } + + connector.getGeneralThreadPool().schedule(this, getTimeTillNextAction(), TimeUnit.MILLISECONDS); + } + + 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); + } + + @RequiredArgsConstructor + @Getter + public static class ScoreboardSession { + private final GeyserSession session; + private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0); + private int packetsPerSecond; + private int millisBetweenUpdates = FIRST_MILLIS_BETWEEN_UPDATES; + 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 377a15f1e..cce08cbff 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -66,7 +66,7 @@ public final class Team { } // we don't have to change the updateType, // because the scores itself need updating, not the team - for (Objective objective : scoreboard.getObjectives().values()) { + for (Objective objective : scoreboard.getObjectives()) { for (String addedEntity : added) { Score score = objective.getScores().get(addedEntity); if (score != null) { @@ -169,6 +169,10 @@ public final class Team { } public UpdateType getUpdateType() { + return currentData.updateType; + } + + public UpdateType getCachedUpdateType() { return cachedData != null ? cachedData.updateType : currentData.updateType; }