Made a ShareManager and designed methods to save/retrieve a statResult, save timestamp of sharing, and check waiting-time for players

This commit is contained in:
Artemis-the-gr8 2022-07-10 15:31:01 +02:00
parent e44e0e7b0e
commit 347e59220c
14 changed files with 138 additions and 84 deletions

View File

@ -3,13 +3,12 @@ package com.gmail.artemis.the.gr8.playerstats;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
import com.gmail.artemis.the.gr8.playerstats.statistic.ShareQueue;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.statistic.ShareManager;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
@ -22,23 +21,24 @@ public class ThreadManager {
private final BukkitAudiences adventure;
private static ConfigHandler config;
private static MessageWriter messageWriter;
private final ShareQueue shareQueue;
private final ShareManager shareManager;
private ReloadThread lastActiveReloadThread;
private StatThread lastActiveStatThread;
private final HashMap<String, Thread> statThreads;
private static long lastRecordedCalcTime;
public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageWriter m, @Nullable ShareQueue s) {
public ThreadManager(BukkitAudiences a, ConfigHandler c, MessageWriter m) {
adventure = a;
config = c;
messageWriter = m;
shareQueue = s;
shareManager = new ShareManager(config);
statThreads = new HashMap<>();
statThreadID = 0;
reloadThreadID = 0;
lastRecordedCalcTime = 0;
startReloadThread(null);
}
@ -83,7 +83,7 @@ public class ThreadManager {
}
private void startNewStatThread(StatRequest request) {
lastActiveStatThread = new StatThread(adventure, config, messageWriter, statThreadID, threshold, request, lastActiveReloadThread);
lastActiveStatThread = new StatThread(adventure, config, messageWriter, statThreadID, threshold, request, lastActiveReloadThread, shareManager);
statThreads.put(request.getCommandSender().getName(), lastActiveStatThread);
lastActiveStatThread.start();
}

View File

@ -9,6 +9,8 @@ public class ShareCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, String label, String[] args) {
//TODO use UUID code as arg to share the appropriate statResult
return false;
}
}

View File

@ -3,7 +3,7 @@ package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;

View File

@ -85,11 +85,11 @@ public class ConfigHandler {
return config.getBoolean("enable-stat-sharing", true);
}
/** Returns the number of minutes a command-sender has to wait before being able to
/** Returns the number of minutes a player has to wait before being able to
share another stat-result.
<p>Default: 0</p>*/
public int getStatShareLimit() {
return config.getInt("sharing-time-limit", 0);
public int getStatShareWaitingTime() {
return config.getInt("waiting-time-before-sharing-again", 0);
}
/** Returns the config setting for include-whitelist-only.
@ -176,13 +176,13 @@ public class ConfigHandler {
/** Whether to use festive formatting, such as pride colors.
<p>Default: true</p> */
public boolean useFestiveFormatting() {
public boolean enableFestiveFormatting() {
return config.getBoolean("enable-festive-formatting", true);
}
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
<p>Default: false</p> */
public boolean useRainbowMode() {
public boolean enableRainbowMode() {
return config.getBoolean("rainbow-mode", false);
}

View File

@ -1,4 +1,4 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
package com.gmail.artemis.the.gr8.playerstats.models;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import org.bukkit.Bukkit;

View File

@ -0,0 +1,9 @@
package com.gmail.artemis.the.gr8.playerstats.models;
import net.kyori.adventure.text.TextComponent;
import java.util.UUID;
public record StatResult(String playerName, TextComponent statResult, int ID, UUID uuid) {
}

View File

@ -7,7 +7,7 @@ import com.gmail.artemis.the.gr8.playerstats.enums.Unit;
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.ExampleMessage;
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.HelpMessage;
import com.gmail.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
@ -42,7 +42,7 @@ public class MessageWriter {
}
private static void getComponentFactory() {
if (config.useFestiveFormatting() || config.useRainbowMode()) {
if (config.enableFestiveFormatting() || config.enableRainbowMode()) {
componentFactory = new PrideComponentFactory(config);
}
else {

View File

@ -61,7 +61,7 @@ public class PrideComponentFactory extends ComponentFactory {
if festive formatting is disabled or it is not pride month,
or the commandsender is a Bukkit or Spigot console.*/
private boolean cancelRainbow(boolean isBukkitConsole) {
return !(config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
return !(config.enableRainbowMode() || (config.enableFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
(isBukkitConsole);
}
}

View File

@ -55,7 +55,7 @@ public class ReloadThread extends Thread {
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
statThread.join();
} catch (InterruptedException e) {
MyLogger.logException(e, "ReloadThread", "run(), trying to join" + statThread.getName());
MyLogger.logException(e, "ReloadThread", "run(), trying to join " + statThread.getName());
throw new RuntimeException(e);
}
}

View File

@ -0,0 +1,95 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.models.StatResult;
import net.kyori.adventure.text.TextComponent;
import javax.annotation.Nullable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class ShareManager {
private boolean isEnabled;
private int waitingTime;
private volatile AtomicInteger resultID; //always starts with value 0
private ConcurrentHashMap<UUID, StatResult> statResults = null;
private ConcurrentHashMap<String, Instant> shareTimeStamp = null;
public ShareManager(ConfigHandler config) {
isEnabled = config.enableStatSharing();
waitingTime = config.getStatShareWaitingTime();
if (isEnabled) {
statResults = new ConcurrentHashMap<>();
shareTimeStamp = new ConcurrentHashMap<>();
}
}
public boolean isEnabled() {
return this.isEnabled;
}
public void updateSettings(ConfigHandler config) {
isEnabled = config.enableStatSharing();
waitingTime = config.getStatShareWaitingTime();
if (isEnabled && statResults == null) {
statResults = new ConcurrentHashMap<>();
shareTimeStamp = new ConcurrentHashMap<>();
}
}
public UUID saveStatResult(String playerName, TextComponent statResult) {
removeExcessResults(playerName);
int ID = getNextIDNumber();
UUID identifier = UUID.randomUUID();
statResults.put(identifier, new StatResult(playerName, statResult, ID, identifier));
return identifier;
}
public @Nullable TextComponent getStatResult(String playerName, UUID identifier) {
if (statResults.containsKey(identifier) && playerCanShare(playerName)) {
shareTimeStamp.put(playerName, Instant.now());
return statResults.remove(identifier).statResult();
} else {
return null;
}
}
private boolean playerCanShare(String playerName) {
if (waitingTime == 0 || !shareTimeStamp.containsKey(playerName)) {
return true;
} else {
long seconds = shareTimeStamp.get(playerName).until(Instant.now(), ChronoUnit.SECONDS);
return seconds >= waitingTime;
}
}
/** If the given player already has more than x (in this case 25) StatResults saved,
remove the oldest one.*/
private void removeExcessResults(String playerName) {
List<StatResult> alreadySavedResults = statResults.values()
.parallelStream()
.filter(result -> result.playerName().equalsIgnoreCase(playerName))
.toList();
if (alreadySavedResults.size() > 25) {
UUID uuid = alreadySavedResults
.parallelStream()
.min(Comparator.comparing(StatResult::ID))
.orElseThrow().uuid();
statResults.remove(uuid);
}
}
private int getNextIDNumber() {
return resultID.incrementAndGet();
}
}

View File

@ -1,53 +0,0 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import net.kyori.adventure.text.TextComponent;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
public class ShareQueue {
private volatile long timeThreshold;
private final ConcurrentHashMap<String, TextComponent> statResults;
private final ConcurrentHashMap<String, Instant> shareTimestamp;
public ShareQueue(long timeThreshold) {
this.timeThreshold = timeThreshold;
statResults = new ConcurrentHashMap<>();
shareTimestamp = new ConcurrentHashMap<>();
}
public void saveStatResults(String senderName, TextComponent statResult) {
statResults.put(senderName, statResult);
}
public boolean senderCanShare(String senderName) {
return senderCanShare(senderName, 0);
}
/** Returns true if the given sender has a statResult that can be shared,
if they have not shared a statResult yet, or if they have passed the timeLimit.
@param senderName name of the commandSender to evaluate
@param timeLimit the waiting time in seconds during which sharing is not allowed*/
public boolean senderCanShare(String senderName, long timeLimit) {
if (timeLimit == 0 || !shareTimestamp.containsKey(senderName)) {
return statResults.containsKey(senderName);
} else {
long seconds = shareTimestamp.get(senderName).until(Instant.now(), ChronoUnit.SECONDS);
return seconds >= timeLimit;
}
}
/** Removes and returns the last statResults for this sender,
and stores the timestamp the results were retrieved on.*/
public @Nullable TextComponent getLastStatResult(String senderName) {
if (statResults.containsKey(senderName)) {
shareTimestamp.put(senderName, Instant.now());
return statResults.remove(senderName);
} else {
return null;
}
}
}

View File

@ -1,6 +1,7 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
import com.gmail.artemis.the.gr8.playerstats.reload.ReloadThread;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
@ -23,21 +24,18 @@ public class StatThread extends Thread {
private final int threshold;
private final ReloadThread reloadThread;
private final ShareQueue shareQueue;
private final ShareManager shareManager;
private final BukkitAudiences adventure;
private static ConfigHandler config;
private static MessageWriter messageWriter;
private final StatRequest request;
public StatThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, int ID, int threshold, StatRequest s, @Nullable ReloadThread r) {
this(a, c, m, ID, threshold, s, r, null);
}
public StatThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, int ID, int threshold, StatRequest s, @Nullable ReloadThread r, @Nullable ShareQueue q) {
public StatThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, int ID, int threshold, StatRequest s, @Nullable ReloadThread r, @Nullable ShareManager sm) {
this.threshold = threshold;
reloadThread = r;
shareQueue = q;
shareManager = sm;
adventure = a;
config = c;
@ -65,7 +63,7 @@ public class StatThread extends Thread {
reloadThread.join();
} catch (InterruptedException e) {
MyLogger.logException(e, "StatThread", "Trying to join" + reloadThread.getName());
MyLogger.logException(e, "StatThread", "Trying to join " + reloadThread.getName());
throw new RuntimeException(e);
}
}
@ -85,7 +83,9 @@ public class StatThread extends Thread {
} else {
statResult = messageWriter.formatServerStat(getServerTotal(), request);
}
if (shareManager.isEnabled()) {
UUID shareCode = shareManager.saveStatResult(request.getCommandSender().getName(), statResult);
}
adventure.sender(request.getCommandSender()).sendMessage(statResult);
}
catch (ConcurrentModificationException e) {
@ -103,7 +103,7 @@ public class StatThread extends Thread {
}
private long getServerTotal() {
List<Integer> numbers = getAllStats().values().stream().toList();
List<Integer> numbers = getAllStats().values().parallelStream().toList();
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
}
@ -111,7 +111,7 @@ public class StatThread extends Thread {
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException {
long time = System.currentTimeMillis();
int size = OfflinePlayerHandler.getOfflinePlayerCount() != 0 ? (int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05) : 16;
int size = OfflinePlayerHandler.getOfflinePlayerCount() != 0 ? OfflinePlayerHandler.getOfflinePlayerCount() : 16;
ConcurrentHashMap<String, Integer> playerStats = new ConcurrentHashMap<>(size);
ImmutableList<String> playerNames = ImmutableList.copyOf(OfflinePlayerHandler.getOfflinePlayerNames());

View File

@ -1,5 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;

View File

@ -21,9 +21,9 @@ only-allow-one-lookup-at-a-time-per-player: true
# Whether statistics can be shared with everyone in chat
enable-stat-sharing: true
# How often players can share statistics in chat. You can specify a waiting time (in minutes) below,
# or leave this setting on 0 to not use any time-limit
sharing-time-limit: 0
# How often players can share statistics in chat (use this if you want to limit chat spam)
# Leave this on 0 to disable the cool-down, or specify the number of minutes you want players to wait
waiting-time-before-sharing-again: 0
# Filtering options to control which players should be included in statistic calculations
include-whitelist-only: false