Got rough version of API to work!

This commit is contained in:
Artemis-the-gr8 2022-07-23 16:08:35 +02:00
parent 54040c4e3d
commit de150bfe51
11 changed files with 179 additions and 182 deletions

View File

@ -21,20 +21,11 @@ public class Main extends JavaPlugin {
private static BukkitAudiences adventure; private static BukkitAudiences adventure;
private static PlayerStats playerStatsAPI; private static PlayerStats playerStatsAPI;
private static OutputManager outputManager;
private static ShareManager shareManager;
private static StatManager statManager;
private static ThreadManager threadManager;
public static @NotNull BukkitAudiences adventure() throws IllegalStateException {
if (adventure == null) {
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
}
return adventure;
}
public static @NotNull PlayerStats getPlayerStatsAPI() throws IllegalStateException {
if (playerStatsAPI == null) {
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
}
return playerStatsAPI;
}
@Override @Override
public void onEnable() { public void onEnable() {
@ -42,14 +33,8 @@ public class Main extends JavaPlugin {
ConfigHandler config = new ConfigHandler(this); ConfigHandler config = new ConfigHandler(this);
OfflinePlayerHandler offlinePlayerHandler = new OfflinePlayerHandler(); OfflinePlayerHandler offlinePlayerHandler = new OfflinePlayerHandler();
OutputManager outputManager = OutputManager.getInstance(config); //initialize all the Managers and the API
StatManager statManager = StatManager.getInstance(outputManager, offlinePlayerHandler); initializeMainClasses(config, offlinePlayerHandler);
ThreadManager threadManager = ThreadManager.getInstance(config, outputManager, statManager, offlinePlayerHandler);
ShareManager shareManager = ShareManager.getInstance(config);
//initialize the Adventure library and the API
adventure = BukkitAudiences.create(this);
playerStatsAPI = PlayerStatsAPI.load(this, threadManager, outputManager, statManager);
//register all commands and the tabCompleter //register all commands and the tabCompleter
PluginCommand statcmd = this.getCommand("statistic"); PluginCommand statcmd = this.getCommand("statistic");
@ -77,4 +62,57 @@ public class Main extends JavaPlugin {
} }
this.getLogger().info("Disabled PlayerStats!"); this.getLogger().info("Disabled PlayerStats!");
} }
public static @NotNull BukkitAudiences getAdventure() throws IllegalStateException {
if (adventure == null) {
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
}
return adventure;
}
public static @NotNull PlayerStats getPlayerStatsAPI() throws IllegalStateException {
if (playerStatsAPI == null) {
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
}
return playerStatsAPI;
}
public static @NotNull OutputManager getOutputManager() throws IllegalStateException {
if (outputManager == null) {
throw new IllegalStateException("The OutputManager is not loaded! Is PlayerStats enabled?");
}
return outputManager;
}
public static @NotNull ShareManager getShareManager() throws IllegalStateException {
if (shareManager == null) {
throw new IllegalStateException("The ShareManager is not loaded! Is PlayerStats enabled?");
}
return shareManager;
}
public static @NotNull StatManager getStatManager() throws IllegalStateException {
if (statManager == null) {
throw new IllegalStateException("The StatManager is not loaded! Is PlayerStats enabled?");
}
return statManager;
}
public static @NotNull ThreadManager getThreadManager() throws IllegalStateException {
if (threadManager == null) {
throw new IllegalStateException("The ThreadManager is not loaded! Is PlayerStats enabled?");
}
return threadManager;
}
private void initializeMainClasses(ConfigHandler config, OfflinePlayerHandler offlinePlayerHandler) {
adventure = BukkitAudiences.create(this);
shareManager = new ShareManager(config);
outputManager = new OutputManager(getAdventure(), config, shareManager);
statManager = new StatManager(outputManager, offlinePlayerHandler, config.getTopListMaxSize());
threadManager = new ThreadManager(config, statManager, outputManager, offlinePlayerHandler);
playerStatsAPI = PlayerStatsAPI.load(statManager, outputManager);
}
} }

View File

@ -24,8 +24,6 @@ import static java.time.temporal.ChronoUnit.SECONDS;
results of past stat-lookups, so the results can be retrieved and shared when a Player clicks the share-button.*/ results of past stat-lookups, so the results can be retrieved and shared when a Player clicks the share-button.*/
public final class ShareManager { public final class ShareManager {
private static volatile ShareManager instance;
private static boolean isEnabled; private static boolean isEnabled;
private static int waitingTime; private static int waitingTime;
@ -34,23 +32,10 @@ public final class ShareManager {
private ConcurrentHashMap<String, Instant> shareTimeStamp; private ConcurrentHashMap<String, Instant> shareTimeStamp;
private ArrayBlockingQueue<UUID> sharedResults; private ArrayBlockingQueue<UUID> sharedResults;
private ShareManager(ConfigHandler config) { public ShareManager(ConfigHandler config) {
updateSettings(config); updateSettings(config);
} }
public static ShareManager getInstance(ConfigHandler config) {
ShareManager shareManager = instance;
if (shareManager != null) {
return shareManager;
}
synchronized (ShareManager.class) {
if (instance == null) {
instance = new ShareManager(config);
}
return instance;
}
}
public static boolean isEnabled() { public static boolean isEnabled() {
return isEnabled; return isEnabled;
} }

View File

@ -20,8 +20,6 @@ import java.util.HashMap;
to ensure those will never run at the same time. */ to ensure those will never run at the same time. */
public final class ThreadManager { public final class ThreadManager {
private static volatile ThreadManager instance;
private final static int threshold = 10; private final static int threshold = 10;
private int statThreadID; private int statThreadID;
private int reloadThreadID; private int reloadThreadID;
@ -36,11 +34,11 @@ public final class ThreadManager {
private final HashMap<String, Thread> statThreads; private final HashMap<String, Thread> statThreads;
private static long lastRecordedCalcTime; private static long lastRecordedCalcTime;
private ThreadManager(ConfigHandler c, OutputManager m, StatManager s, OfflinePlayerHandler o) { public ThreadManager(ConfigHandler config, StatManager statManager, OutputManager outputManager, OfflinePlayerHandler offlinePlayerHandler) {
config = c; ThreadManager.config = config;
outputManager = m; ThreadManager.outputManager = outputManager;
statManager = s; ThreadManager.statManager = statManager;
offlinePlayerHandler = o; this.offlinePlayerHandler = offlinePlayerHandler;
statThreads = new HashMap<>(); statThreads = new HashMap<>();
statThreadID = 0; statThreadID = 0;
@ -50,19 +48,6 @@ public final class ThreadManager {
startReloadThread(null); startReloadThread(null);
} }
public static ThreadManager getInstance(ConfigHandler config, OutputManager output, StatManager statManager, OfflinePlayerHandler offlinePlayerHandler) {
ThreadManager threadManager = instance;
if (threadManager != null) {
return threadManager;
}
synchronized (ThreadManager.class) {
if (instance == null) {
instance = new ThreadManager(config, output, statManager, offlinePlayerHandler);
}
return instance;
}
}
public static int getTaskThreshold() { public static int getTaskThreshold() {
return threshold; return threshold;
} }
@ -108,7 +93,7 @@ public final class ThreadManager {
} }
private void startNewStatThread(StatRequest request) { private void startNewStatThread(StatRequest request) {
lastActiveStatThread = new StatThread(config, outputManager, statManager, offlinePlayerHandler, statThreadID, request, lastActiveReloadThread); lastActiveStatThread = new StatThread(outputManager, statManager, statThreadID, request, lastActiveReloadThread);
statThreads.put(request.getCommandSender().getName(), lastActiveStatThread); statThreads.put(request.getCommandSender().getName(), lastActiveStatThread);
lastActiveStatThread.start(); lastActiveStatThread.start();
} }

View File

@ -27,7 +27,8 @@ public interface PlayerStats {
<p>- if applicable, a sub-stat-name (example: diorite)(</p> <p>- if applicable, a sub-stat-name (example: diorite)(</p>
<p>- a target for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p> <p>- a target for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
<p>- if "player" was chosen, include a player-name</p> <p>- if "player" was chosen, include a player-name</p>
@param sender the CommandSender that requested this specific statistic*/ @param sender the CommandSender that requested this specific statistic
@throws IllegalArgumentException if the args do not result in a valid statistic look-up*/
TextComponent getFancyStat(CommandSender sender, String[] args) throws IllegalArgumentException; TextComponent getFancyStat(CommandSender sender, String[] args) throws IllegalArgumentException;
/** Turns a TextComponent into its String representation. It will lose all color and style, /** Turns a TextComponent into its String representation. It will lose all color and style,

View File

@ -1,35 +1,30 @@
package com.gmail.artemis.the.gr8.playerstats.api; package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest; import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager; import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.LinkedHashMap;
import static org.jetbrains.annotations.ApiStatus.Internal; import static org.jetbrains.annotations.ApiStatus.Internal;
/** This is the implementation of the API Interface */ /** The implementation of the API Interface */
public final class PlayerStatsAPI extends JavaPlugin implements PlayerStats { public final class PlayerStatsAPI implements PlayerStats {
private final Main plugin;
private static ThreadManager threadManager;
private static StatFormatter statFormatter; private static StatFormatter statFormatter;
private static StatManager statManager; private static StatManager statManager;
@Internal @Internal
private PlayerStatsAPI(Main plugin, ThreadManager thread, StatFormatter format, StatManager stat) { private PlayerStatsAPI(StatManager stat, StatFormatter format) {
this.plugin = plugin;
threadManager = thread;
statFormatter = format; statFormatter = format;
statManager = stat; statManager = stat;
} }
@Internal @Internal
public static PlayerStatsAPI load(Main plugin, ThreadManager threadManager, StatFormatter formatter, StatManager statManager) { public static PlayerStatsAPI load(StatManager statManager, StatFormatter statFormatter) {
return new PlayerStatsAPI(plugin, threadManager, formatter, statManager); return new PlayerStatsAPI(statManager, statFormatter);
} }
@Override @Override
@ -42,17 +37,17 @@ public final class PlayerStatsAPI extends JavaPlugin implements PlayerStats {
return statFormatter.formatPlayerStat(request, stat); return statFormatter.formatPlayerStat(request, stat);
} }
case SERVER -> { case SERVER -> {
//do something async long stat = statManager.getServerStat(request);
return statFormatter.formatServerStat(request, stat);
} }
case TOP -> { case TOP -> {
//also do something async LinkedHashMap<String, Integer> stats = statManager.getTopStats(request);
return statFormatter.formatTopStat(request, stats);
}
} }
} }
} else {
throw new IllegalArgumentException("This is not a valid stat-request!"); throw new IllegalArgumentException("This is not a valid stat-request!");
} }
return null;
}
public String componentToString(TextComponent component) { public String componentToString(TextComponent component) {
return statFormatter.toString(component); return statFormatter.toString(component);

View File

@ -3,10 +3,10 @@ package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest; import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
/** The {@link RequestHandler} will help you turn a String (such as "stat animals_bred") into a specific {@link StatRequest} /** The {@link RequestGenerator} will help you turn a String (such as "stat animals_bred") into a specific {@link StatRequest}
with all the information {@link PlayerStatsAPI} needs to work with. You'll need this StatRequest Object to get the statistic with all the information {@link PlayerStatsAPI} needs to work with. You'll need this StatRequest Object to get the statistic
data that you want, and to format this data into a fancy Component or String, so you can output it somewhere.*/ data that you want, and to format this data into a fancy Component or String, so you can output it somewhere.*/
public interface RequestHandler { public interface RequestGenerator {
/** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console) /** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created. as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created.

View File

@ -1,6 +1,5 @@
package com.gmail.artemis.the.gr8.playerstats.msg; package com.gmail.artemis.the.gr8.playerstats.msg;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.ShareManager; import com.gmail.artemis.the.gr8.playerstats.ShareManager;
import com.gmail.artemis.the.gr8.playerstats.api.StatFormatter; import com.gmail.artemis.the.gr8.playerstats.api.StatFormatter;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
@ -33,8 +32,6 @@ import static com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage.*;
(mainly to deal with the lack of hover-text, and for Bukkit consoles to make up for the lack of hex-colors).*/ (mainly to deal with the lack of hover-text, and for Bukkit consoles to make up for the lack of hex-colors).*/
public final class OutputManager implements StatFormatter { public final class OutputManager implements StatFormatter {
private static volatile OutputManager instance;
private static BukkitAudiences adventure; private static BukkitAudiences adventure;
private static ShareManager shareManager; private static ShareManager shareManager;
private static MessageBuilder writer; private static MessageBuilder writer;
@ -42,27 +39,14 @@ public final class OutputManager implements StatFormatter {
private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages; private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages;
private OutputManager(ConfigHandler config) { public OutputManager(BukkitAudiences adventure, ConfigHandler config, ShareManager shareManager) {
adventure = Main.adventure(); OutputManager.adventure = adventure;
shareManager = ShareManager.getInstance(config); OutputManager.shareManager = shareManager;
getMessageWriters(config); getMessageWriters(config);
prepareFunctions(); prepareFunctions();
} }
public static OutputManager getInstance(ConfigHandler config) {
OutputManager outputManager = instance;
if (outputManager != null) {
return outputManager;
}
synchronized (OutputManager.class) {
if (instance == null) {
instance = new OutputManager(config);
}
return instance;
}
}
public void updateMessageWriters(ConfigHandler config) { public void updateMessageWriters(ConfigHandler config) {
getMessageWriters(config); getMessageWriters(config);
} }

View File

@ -1,5 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.reload; package com.gmail.artemis.the.gr8.playerstats.reload;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.ShareManager; import com.gmail.artemis.the.gr8.playerstats.ShareManager;
import com.gmail.artemis.the.gr8.playerstats.ThreadManager; import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
@ -7,6 +8,7 @@ import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage; import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager; import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread; import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -25,10 +27,10 @@ public class ReloadThread extends Thread {
private static ConfigHandler config; private static ConfigHandler config;
private static OutputManager outputManager; private static OutputManager outputManager;
private final OfflinePlayerHandler offlinePlayerHandler;
private static ShareManager shareManager; private static ShareManager shareManager;
private final OfflinePlayerHandler offlinePlayerHandler;
private final int reloadThreadID; private final int reloadThreadID;
private final StatThread statThread; private final StatThread statThread;
@ -39,7 +41,7 @@ public class ReloadThread extends Thread {
outputManager = m; outputManager = m;
offlinePlayerHandler = o; offlinePlayerHandler = o;
shareManager = ShareManager.getInstance(c); shareManager = Main.getShareManager();
reloadThreadID = ID; reloadThreadID = ID;
statThread = s; statThread = s;
@ -52,7 +54,8 @@ public class ReloadThread extends Thread {
/** This method will perform a series of tasks. If a {@link StatThread} is still running, /** This method will perform a series of tasks. If a {@link StatThread} is still running,
it will join the statThread and wait for it to finish. Then, it will reload the config, it will join the statThread and wait for it to finish. Then, it will reload the config,
update the offlinePlayerList in the {@link OfflinePlayerHandler}, update the {@link DebugLevel}, update the offlinePlayerList in the {@link OfflinePlayerHandler}, update the {@link DebugLevel},
update the share-settings in {@link ShareManager} and update the MessageBuilders in the {@link OutputManager}.*/ update the share-settings in {@link ShareManager} and topListSize-settings in {@link StatManager},
and update the MessageBuilders in the {@link OutputManager}.*/
@Override @Override
public void run() { public void run() {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
@ -88,6 +91,7 @@ public class ReloadThread extends Thread {
outputManager.updateMessageWriters(config); outputManager.updateMessageWriters(config);
offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers()); offlinePlayerHandler.updateOfflinePlayerList(loadOfflinePlayers());
shareManager.updateSettings(config); shareManager.updateSettings(config);
StatManager.updateSettings(config.getTopListMaxSize());
} }
private ConcurrentHashMap<String, UUID> loadOfflinePlayers() { private ConcurrentHashMap<String, UUID> loadOfflinePlayers() {

View File

@ -9,17 +9,17 @@ import org.bukkit.OfflinePlayer;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RecursiveAction; import java.util.concurrent.RecursiveTask;
/** The action that is executed when a stat-command is triggered. */ /** The action that is executed when a stat-command is triggered. */
public final class StatAction extends RecursiveAction { public final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
private static int threshold; private static int threshold;
private final OfflinePlayerHandler offlinePlayerHandler; private final OfflinePlayerHandler offlinePlayerHandler;
private final ImmutableList<String> playerNames; private final ImmutableList<String> playerNames;
private final StatRequest request; private final StatRequest request;
private final ConcurrentHashMap<String, Integer> playerStats; private final ConcurrentHashMap<String, Integer> allStats;
/** /**
* Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap * Gets the statistic numbers for all players whose name is on the list, puts them in a ConcurrentHashMap
@ -27,34 +27,36 @@ public final class StatAction extends RecursiveAction {
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players * @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations * @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
* @param statRequest a validated statRequest * @param statRequest a validated statRequest
* @param playerStats the ConcurrentHashMap to put the results on * @param allStats the ConcurrentHashMap to put the results on
*/ */
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> playerStats) { public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest statRequest, ConcurrentHashMap<String, Integer> allStats) {
threshold = ThreadManager.getTaskThreshold(); threshold = ThreadManager.getTaskThreshold();
this.offlinePlayerHandler = offlinePlayerHandler; this.offlinePlayerHandler = offlinePlayerHandler;
this.playerNames = playerNames; this.playerNames = playerNames;
this.request = statRequest; this.request = statRequest;
this.playerStats = playerStats; this.allStats = allStats;
MyLogger.subActionCreated(Thread.currentThread().getName()); MyLogger.subActionCreated(Thread.currentThread().getName());
} }
@Override @Override
protected void compute() { protected ConcurrentHashMap<String, Integer> compute() {
if (playerNames.size() < threshold) { if (playerNames.size() < threshold) {
getStatsDirectly(); return getStatsDirectly();
} }
else { else {
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), request, playerStats); final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), request, allStats);
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), request, playerStats); final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), request, allStats);
//queue and compute all subtasks in the right order //queue and compute all subtasks in the right order
invokeAll(subTask1, subTask2); subTask1.fork();
subTask2.compute();
return subTask1.join();
} }
} }
private void getStatsDirectly() { private ConcurrentHashMap<String, Integer> getStatsDirectly() {
Iterator<String> iterator = playerNames.iterator(); Iterator<String> iterator = playerNames.iterator();
if (iterator.hasNext()) { if (iterator.hasNext()) {
do { do {
@ -70,10 +72,11 @@ public final class StatAction extends RecursiveAction {
case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem()); case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem());
} }
if (statistic > 0) { if (statistic > 0) {
playerStats.put(playerName, statistic); allStats.put(playerName, statistic);
} }
} }
} while (iterator.hasNext()); } while (iterator.hasNext());
} }
return allStats;
} }
} }

View File

@ -1,12 +1,16 @@
package com.gmail.artemis.the.gr8.playerstats.statistic; package com.gmail.artemis.the.gr8.playerstats.statistic;
import com.gmail.artemis.the.gr8.playerstats.api.RequestHandler; import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
import com.gmail.artemis.the.gr8.playerstats.api.StatGetter;
import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage; import com.gmail.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.gmail.artemis.the.gr8.playerstats.enums.Target; 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.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager; import com.gmail.artemis.the.gr8.playerstats.msg.OutputManager;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler; import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic; import org.bukkit.Statistic;
@ -14,30 +18,27 @@ import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public final class StatManager implements RequestHandler { import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
private static volatile StatManager instance; public final class StatManager implements RequestGenerator, StatGetter {
private final OfflinePlayerHandler offlinePlayerHandler; private final OfflinePlayerHandler offlinePlayerHandler;
private static OutputManager outputManager; private static OutputManager outputManager;
private static int topListMaxSize;
private StatManager(OutputManager output, OfflinePlayerHandler offlinePlayerHandler) { public StatManager(OutputManager outputManager, OfflinePlayerHandler offlinePlayerHandler, int topListMaxSize) {
this.offlinePlayerHandler = offlinePlayerHandler; this.offlinePlayerHandler = offlinePlayerHandler;
outputManager = output; StatManager.outputManager = outputManager;
StatManager.topListMaxSize = topListMaxSize;
} }
public static StatManager getInstance(OutputManager outputManager, OfflinePlayerHandler offlinePlayerHandler) { public static void updateSettings(int topListMaxSize) {
StatManager statManager = instance; StatManager.topListMaxSize = topListMaxSize;
if (statManager != null) {
return statManager;
}
synchronized (StatManager.class) {
if (instance == null) {
instance = new StatManager(outputManager, offlinePlayerHandler);
}
return instance;
}
} }
public StatRequest generateRequest(CommandSender sender, String[] args) { public StatRequest generateRequest(CommandSender sender, String[] args) {
@ -174,4 +175,54 @@ public final class StatManager implements RequestHandler {
} }
return 0; return 0;
} }
public LinkedHashMap<String, Integer> getTopStats(StatRequest request) {
return getAllStatsAsync(request).entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(topListMaxSize)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
public long getServerStat(StatRequest request) {
List<Integer> numbers = getAllStatsAsync(request)
.values()
.parallelStream()
.toList();
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
}
/** Invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players
that are stored in the {@link OfflinePlayerHandler}) */
public @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(StatRequest request) {
long time = System.currentTimeMillis();
ForkJoinPool commonPool = ForkJoinPool.commonPool();
ConcurrentHashMap<String, Integer> allStats;
try {
allStats = commonPool.invoke(getStatTask(request));
} catch (ConcurrentModificationException e) {
MyLogger.logMsg("The request could not be executed due to a ConcurrentModificationException. " +
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
"Try again and it should be fine!", true);
throw new ConcurrentModificationException(e.toString());
}
MyLogger.actionFinished(2);
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
return allStats;
}
private StatAction getStatTask(StatRequest request) {
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
ConcurrentHashMap<String, Integer> allStats = new ConcurrentHashMap<>(size);
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
StatAction task = new StatAction(offlinePlayerHandler, playerNames, request, allStats);
MyLogger.actionCreated(playerNames.size());
return task;
}
} }

View File

@ -9,33 +9,23 @@ import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler; import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger; import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/** The Thread that is in charge of getting and calculating statistics.*/ /** The Thread that is in charge of getting and calculating statistics.*/
public class StatThread extends Thread { public class StatThread extends Thread {
private static ConfigHandler config;
private static OutputManager outputManager; private static OutputManager outputManager;
private static StatManager statManager; private static StatManager statManager;
private final OfflinePlayerHandler offlinePlayerHandler;
private final ReloadThread reloadThread; private final ReloadThread reloadThread;
private final StatRequest request; private final StatRequest request;
public StatThread(ConfigHandler c, OutputManager m, StatManager t, OfflinePlayerHandler o, int ID, StatRequest s, @Nullable ReloadThread r) { public StatThread(OutputManager m, StatManager t, int ID, StatRequest s, @Nullable ReloadThread r) {
config = c;
outputManager = m; outputManager = m;
statManager = t; statManager = t;
offlinePlayerHandler = o;
reloadThread = r; reloadThread = r;
request = s; request = s;
@ -72,8 +62,8 @@ public class StatThread extends Thread {
try { try {
TextComponent statResult = switch (selection) { TextComponent statResult = switch (selection) {
case PLAYER -> outputManager.formatPlayerStat(request, statManager.getPlayerStat(request)); case PLAYER -> outputManager.formatPlayerStat(request, statManager.getPlayerStat(request));
case TOP -> outputManager.formatTopStat(request, getTopStats()); case TOP -> outputManager.formatTopStat(request, statManager.getTopStats(request));
case SERVER -> outputManager.formatServerStat(request, getServerStat()); case SERVER -> outputManager.formatServerStat(request, statManager.getServerStat(request));
}; };
outputManager.sendToCommandSender(request.getCommandSender(), statResult); outputManager.sendToCommandSender(request.getCommandSender(), statResult);
} }
@ -83,43 +73,4 @@ public class StatThread extends Thread {
} }
} }
} }
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException {
return getAllStats().entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
private long getServerStat() {
List<Integer> numbers = getAllStats().values().parallelStream().toList();
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
}
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException {
long time = System.currentTimeMillis();
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
ConcurrentHashMap<String, Integer> playerStats = new ConcurrentHashMap<>(size);
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
StatAction task = new StatAction(offlinePlayerHandler, playerNames, request, playerStats);
MyLogger.actionCreated(playerNames.size());
ForkJoinPool commonPool = ForkJoinPool.commonPool();
try {
commonPool.invoke(task);
} catch (ConcurrentModificationException e) {
MyLogger.logMsg("The request could not be executed due to a ConcurrentModificationException. " +
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
"Try again and it should be fine!", true);
throw new ConcurrentModificationException(e.toString());
}
MyLogger.actionFinished(2);
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
return playerStats;
}
} }