2020-08-28 15:47:52 +00:00
|
|
|
/*
|
2021-01-01 15:10:36 +00:00
|
|
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
2020-08-28 15:47:52 +00:00
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*
|
|
|
|
* @author GeyserMC
|
|
|
|
* @link https://github.com/GeyserMC/Geyser
|
|
|
|
*/
|
|
|
|
|
2021-11-20 21:34:30 +00:00
|
|
|
package org.geysermc.geyser.scoreboard;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
import lombok.Getter;
|
|
|
|
import lombok.RequiredArgsConstructor;
|
2021-11-20 21:34:30 +00:00
|
|
|
import org.geysermc.geyser.GeyserImpl;
|
|
|
|
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
2021-11-22 19:52:26 +00:00
|
|
|
import org.geysermc.geyser.session.GeyserSession;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.session.cache.WorldCache;
|
|
|
|
import org.geysermc.geyser.text.GeyserLocale;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-11-12 14:02:14 +00:00
|
|
|
import java.util.Collection;
|
2020-08-28 15:47:52 +00:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
public final class ScoreboardUpdater extends Thread {
|
2020-08-28 15:47:52 +00:00
|
|
|
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
|
2021-09-29 18:36:27 +00:00
|
|
|
private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000; // 1 update per second
|
2020-08-28 15:47:52 +00:00
|
|
|
|
|
|
|
private static final boolean DEBUG_ENABLED;
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
static {
|
2021-11-20 21:34:30 +00:00
|
|
|
GeyserConfiguration config = GeyserImpl.getInstance().getConfig();
|
2021-09-29 18:36:27 +00:00
|
|
|
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.getScoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
|
|
|
|
DEBUG_ENABLED = config.isDebugMode();
|
|
|
|
}
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-11-20 21:34:30 +00:00
|
|
|
private final GeyserImpl geyser = GeyserImpl.getInstance();
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
private long lastUpdate = System.currentTimeMillis();
|
2020-08-28 15:47:52 +00:00
|
|
|
private long lastPacketsPerSecondUpdate = System.currentTimeMillis();
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
public static void init() {
|
|
|
|
new ScoreboardUpdater().start();
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2021-11-20 21:34:30 +00:00
|
|
|
while (!geyser.isShuttingDown()) {
|
2021-10-31 00:47:49 +00:00
|
|
|
try {
|
|
|
|
long timeTillAction = getTimeTillNextAction();
|
|
|
|
if (timeTillAction > 0) {
|
|
|
|
sleepFor(timeTillAction);
|
|
|
|
continue;
|
|
|
|
}
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
long currentTime = System.currentTimeMillis();
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
// reset score-packets per second every second
|
2021-11-22 19:52:26 +00:00
|
|
|
Collection<GeyserSession> sessions = geyser.getSessionManager().getSessions().values();
|
2021-10-31 00:47:49 +00:00
|
|
|
if (currentTime - lastPacketsPerSecondUpdate >= 1000) {
|
|
|
|
lastPacketsPerSecondUpdate = currentTime;
|
2021-11-22 19:52:26 +00:00
|
|
|
for (GeyserSession session : sessions) {
|
2021-10-31 00:47:49 +00:00
|
|
|
ScoreboardSession scoreboardSession = session.getWorldCache().getScoreboardSession();
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
int oldPps = scoreboardSession.getPacketsPerSecond();
|
|
|
|
int newPps = scoreboardSession.getPendingPacketsPerSecond().get();
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
scoreboardSession.packetsPerSecond = newPps;
|
|
|
|
scoreboardSession.pendingPacketsPerSecond.set(0);
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
// 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();
|
|
|
|
}
|
2021-09-29 18:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
if (currentTime - lastUpdate >= FIRST_MILLIS_BETWEEN_UPDATES) {
|
|
|
|
lastUpdate = currentTime;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-11-22 19:52:26 +00:00
|
|
|
for (GeyserSession session : sessions) {
|
2021-10-31 00:47:49 +00:00
|
|
|
WorldCache worldCache = session.getWorldCache();
|
|
|
|
ScoreboardSession scoreboardSession = worldCache.getScoreboardSession();
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
int pps = scoreboardSession.getPacketsPerSecond();
|
|
|
|
if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
|
|
|
|
boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
int millisBetweenUpdates = reachedSecondThreshold ?
|
|
|
|
SECOND_MILLIS_BETWEEN_UPDATES :
|
|
|
|
FIRST_MILLIS_BETWEEN_UPDATES;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
if (currentTime - scoreboardSession.lastUpdate >= millisBetweenUpdates) {
|
|
|
|
worldCache.getScoreboard().onUpdate();
|
|
|
|
scoreboardSession.lastUpdate = currentTime;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
if (DEBUG_ENABLED && (currentTime - scoreboardSession.lastLog >= 60000)) { // one minute
|
|
|
|
int threshold = reachedSecondThreshold ?
|
|
|
|
SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD :
|
|
|
|
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD;
|
2020-08-28 15:47:52 +00:00
|
|
|
|
2021-11-20 21:34:30 +00:00
|
|
|
geyser.getLogger().info(
|
2021-11-21 18:36:42 +00:00
|
|
|
GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.name(), threshold, pps) +
|
2021-11-20 23:29:46 +00:00
|
|
|
GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached", (millisBetweenUpdates / 1000.0))
|
2021-10-31 00:47:49 +00:00
|
|
|
);
|
2021-09-29 18:36:27 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
scoreboardSession.lastLog = currentTime;
|
|
|
|
}
|
2021-09-29 18:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-01 17:30:40 +00:00
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
if (DEBUG_ENABLED) {
|
|
|
|
long timeSpent = System.currentTimeMillis() - currentTime;
|
|
|
|
if (timeSpent > 0) {
|
2021-11-20 21:34:30 +00:00
|
|
|
geyser.getLogger().info(String.format(
|
2021-10-31 00:47:49 +00:00
|
|
|
"Scoreboard updater: took %s ms. Updated %s players",
|
2021-11-12 14:02:14 +00:00
|
|
|
timeSpent, sessions.size()
|
2021-10-31 00:47:49 +00:00
|
|
|
));
|
|
|
|
}
|
2021-09-29 18:36:27 +00:00
|
|
|
}
|
|
|
|
|
2021-10-31 00:47:49 +00:00
|
|
|
long timeTillNextAction = getTimeTillNextAction();
|
|
|
|
sleepFor(timeTillNextAction);
|
|
|
|
} catch (Throwable e) {
|
2021-11-20 21:34:30 +00:00
|
|
|
geyser.getLogger().error("Error while translating scoreboard information!", e);
|
2021-10-31 00:47:49 +00:00
|
|
|
// Wait so we don't try to run the scoreboard immediately after this
|
|
|
|
sleepFor(FIRST_MILLIS_BETWEEN_UPDATES);
|
|
|
|
}
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
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);
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
private void sleepFor(long millis) {
|
|
|
|
if (millis <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
Thread.sleep(millis);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
|
2021-09-29 18:36:27 +00:00
|
|
|
@RequiredArgsConstructor
|
|
|
|
@Getter
|
|
|
|
public static final class ScoreboardSession {
|
2021-11-22 19:52:26 +00:00
|
|
|
private final GeyserSession session;
|
2021-09-29 18:36:27 +00:00
|
|
|
private final AtomicInteger pendingPacketsPerSecond = new AtomicInteger(0);
|
|
|
|
private int packetsPerSecond;
|
|
|
|
private long lastUpdate;
|
|
|
|
private long lastLog;
|
2020-08-28 15:47:52 +00:00
|
|
|
}
|
|
|
|
}
|