From 0899bf88edbb32725583f801fc5bb0bf767f20a2 Mon Sep 17 00:00:00 2001 From: ajgeiss0702 Date: Thu, 8 Jun 2023 17:00:49 -0700 Subject: [PATCH] Add API events system --- .github/workflows/javadocs.yml | 46 +++++++++++++++++ .../java/us/ajg0702/queue/api/AjQueueAPI.java | 3 ++ .../ajg0702/queue/api/events/Cancellable.java | 15 ++++++ .../us/ajg0702/queue/api/events/Event.java | 4 ++ .../queue/api/events/PositionChangeEvent.java | 51 +++++++++++++++++++ .../queue/api/events/PreQueueEvent.java | 45 ++++++++++++++++ .../queue/api/events/SuccessfulSendEvent.java | 34 +++++++++++++ .../queue/api/events/utils/EventReceiver.java | 6 +++ .../queue/api/players/AdaptedPlayer.java | 6 +++ .../queue/common/EventHandlerImpl.java | 4 ++ .../us/ajg0702/queue/common/QueueMain.java | 30 ++++++++++- .../queue/common/QueueManagerImpl.java | 9 ++++ .../us/ajg0702/queue/common/TaskManager.java | 13 +++-- .../queue/common/queues/QueueServerImpl.java | 9 ++++ .../common/utils/QueueThreadFactory.java | 21 ++++++++ .../bungeecord/players/BungeePlayer.java | 6 +++ .../velocity/players/VelocityPlayer.java | 10 +++- 17 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/javadocs.yml create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/Cancellable.java create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/Event.java create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/PositionChangeEvent.java create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/PreQueueEvent.java create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/SuccessfulSendEvent.java create mode 100644 api/src/main/java/us/ajg0702/queue/api/events/utils/EventReceiver.java create mode 100644 common/src/main/java/us/ajg0702/queue/common/utils/QueueThreadFactory.java diff --git a/.github/workflows/javadocs.yml b/.github/workflows/javadocs.yml new file mode 100644 index 0000000..1318501 --- /dev/null +++ b/.github/workflows/javadocs.yml @@ -0,0 +1,46 @@ +name: JavaDocs Generation + +on: + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7 + with: + arguments: :api:javadoc + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'api/build/docs/javadoc' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 \ No newline at end of file diff --git a/api/src/main/java/us/ajg0702/queue/api/AjQueueAPI.java b/api/src/main/java/us/ajg0702/queue/api/AjQueueAPI.java index dd3aea5..5a2fa72 100644 --- a/api/src/main/java/us/ajg0702/queue/api/AjQueueAPI.java +++ b/api/src/main/java/us/ajg0702/queue/api/AjQueueAPI.java @@ -1,5 +1,6 @@ package us.ajg0702.queue.api; +import us.ajg0702.queue.api.events.utils.EventReceiver; import us.ajg0702.queue.api.premium.Logic; import us.ajg0702.queue.api.premium.LogicGetter; import us.ajg0702.queue.api.spigot.AjQueueSpigotAPI; @@ -114,5 +115,7 @@ public abstract class AjQueueAPI { */ public abstract void shutdown(); + public abstract void listen(Class event, EventReceiver handler); + public abstract ExecutorService getServersUpdateExecutor(); } diff --git a/api/src/main/java/us/ajg0702/queue/api/events/Cancellable.java b/api/src/main/java/us/ajg0702/queue/api/events/Cancellable.java new file mode 100644 index 0000000..a7b61f0 --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/Cancellable.java @@ -0,0 +1,15 @@ +package us.ajg0702.queue.api.events; + +public interface Cancellable { + /** + * Whether this event is canceled. + * @return True if canceled. False if not. + */ + boolean isCancelled(); + + /** + * Allows you to cancel or un-cancel this event + * @param cancelled True to cancel the event, false to un-cancel + */ + void setCancelled(boolean cancelled); +} diff --git a/api/src/main/java/us/ajg0702/queue/api/events/Event.java b/api/src/main/java/us/ajg0702/queue/api/events/Event.java new file mode 100644 index 0000000..776b7d2 --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/Event.java @@ -0,0 +1,4 @@ +package us.ajg0702.queue.api.events; + +public interface Event { +} diff --git a/api/src/main/java/us/ajg0702/queue/api/events/PositionChangeEvent.java b/api/src/main/java/us/ajg0702/queue/api/events/PositionChangeEvent.java new file mode 100644 index 0000000..61bfe0b --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/PositionChangeEvent.java @@ -0,0 +1,51 @@ +package us.ajg0702.queue.api.events; + +import org.jetbrains.annotations.Nullable; +import us.ajg0702.queue.api.players.AdaptedPlayer; +import us.ajg0702.queue.api.players.QueuePlayer; +import us.ajg0702.queue.api.queues.QueueServer; + +/** + * Called when someone is added or removed from a queue that the player is in, causing the player's position to change + */ +public class PositionChangeEvent implements Event { + private final QueuePlayer player; + private final int position; + + public PositionChangeEvent(QueuePlayer player) { + this.player = player; + position = player.getPosition(); + } + + /** + * Gets the QueuePlayer object that represents this player + * @return the QueuePlayer object + */ + public QueuePlayer getQueuePlayer() { + return player; + } + + /** + * Gets the AdaptedPlayer that this event is about. May return null! + * @return The AdaptedPlayer that this event is about. Returns null if the player is offline. + */ + public @Nullable AdaptedPlayer getPlayer() { + return player.getPlayer(); + } + + /** + * Gets the player's new position in the queue + * @return The player's new position. 1 being 1st, 2 being 2nd, etc + */ + public int getPosition() { + return position; + } + + /** + * Gets the queue that this event is from + * @return The QueueServer that the player is in that their position changed + */ + public QueueServer getQueue() { + return player.getQueueServer(); + } +} diff --git a/api/src/main/java/us/ajg0702/queue/api/events/PreQueueEvent.java b/api/src/main/java/us/ajg0702/queue/api/events/PreQueueEvent.java new file mode 100644 index 0000000..3195044 --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/PreQueueEvent.java @@ -0,0 +1,45 @@ +package us.ajg0702.queue.api.events; + +import us.ajg0702.queue.api.players.AdaptedPlayer; +import us.ajg0702.queue.api.queues.QueueServer; + +/** + * Called after all checks are made, right before a player is actually added to the queue. + * If canceled, the player will not be added to the queue. + * If you cancel this event, it is up to you to send a message telling the player why they were not added to the queue. + */ +public class PreQueueEvent implements Event, Cancellable { + private final AdaptedPlayer player; + private final QueueServer target; + + private boolean cancelled = false; + + public PreQueueEvent(AdaptedPlayer player, QueueServer target) { + this.player = player; + this.target = target; + } + + /** + * Gets the player that is joining the queue + * @return + */ + public AdaptedPlayer getPlayer() { + return player; + } + + /** + * Gets the target QueueServer that the player is trying to queue for + * @return The QueueServer that the player is trying to queue for + */ + public QueueServer getTarget() { + return target; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/api/src/main/java/us/ajg0702/queue/api/events/SuccessfulSendEvent.java b/api/src/main/java/us/ajg0702/queue/api/events/SuccessfulSendEvent.java new file mode 100644 index 0000000..2336d7c --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/SuccessfulSendEvent.java @@ -0,0 +1,34 @@ +package us.ajg0702.queue.api.events; + +import us.ajg0702.queue.api.players.AdaptedPlayer; +import us.ajg0702.queue.api.players.QueuePlayer; +import us.ajg0702.queue.api.server.AdaptedServer; + +/** + * Called after a player is successfully sent to a server. + */ +public class SuccessfulSendEvent implements Event { + private final QueuePlayer player; + private final AdaptedServer server; + + public SuccessfulSendEvent(QueuePlayer player, AdaptedServer server) { + this.player = player; + this.server = server; + } + + /** + * Gets the player that was sent + * @return the player that was sent + */ + public AdaptedPlayer getPlayer() { + return player.getPlayer(); + } + + /** + * Gets the server that the player was sent to + * @return The server that the player was sent to + */ + public AdaptedServer getServer() { + return server; + } +} diff --git a/api/src/main/java/us/ajg0702/queue/api/events/utils/EventReceiver.java b/api/src/main/java/us/ajg0702/queue/api/events/utils/EventReceiver.java new file mode 100644 index 0000000..04c1424 --- /dev/null +++ b/api/src/main/java/us/ajg0702/queue/api/events/utils/EventReceiver.java @@ -0,0 +1,6 @@ +package us.ajg0702.queue.api.events.utils; + +@FunctionalInterface +public interface EventReceiver { + void execute(E event); +} diff --git a/api/src/main/java/us/ajg0702/queue/api/players/AdaptedPlayer.java b/api/src/main/java/us/ajg0702/queue/api/players/AdaptedPlayer.java index 2ed2baa..456f09c 100644 --- a/api/src/main/java/us/ajg0702/queue/api/players/AdaptedPlayer.java +++ b/api/src/main/java/us/ajg0702/queue/api/players/AdaptedPlayer.java @@ -55,6 +55,12 @@ public interface AdaptedPlayer extends Handle, Audience { */ String getServerName(); + /** + * Gets the server that the player is currently connected to + * @return The server that the player is currently connected to. + */ + AdaptedServer getCurrentServer(); + /** * Gets the player's unique id (UUID) * @return The player's uuid diff --git a/common/src/main/java/us/ajg0702/queue/common/EventHandlerImpl.java b/common/src/main/java/us/ajg0702/queue/common/EventHandlerImpl.java index cde11a5..0565f17 100644 --- a/common/src/main/java/us/ajg0702/queue/common/EventHandlerImpl.java +++ b/common/src/main/java/us/ajg0702/queue/common/EventHandlerImpl.java @@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.jetbrains.annotations.NotNull; import us.ajg0702.queue.api.EventHandler; +import us.ajg0702.queue.api.events.SuccessfulSendEvent; import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.players.QueuePlayer; import us.ajg0702.queue.api.queues.QueueServer; @@ -87,6 +88,9 @@ public class EventHandlerImpl implements EventHandler { server.removePlayer(player); server.setLastSentTime(System.currentTimeMillis()); main.getQueueManager().getSendingAttempts().remove(queuePlayer); + main.getTaskManager().runNow(() -> { + main.call(new SuccessfulSendEvent(queuePlayer, player.getCurrentServer())); + }); } } diff --git a/common/src/main/java/us/ajg0702/queue/common/QueueMain.java b/common/src/main/java/us/ajg0702/queue/common/QueueMain.java index b46f843..3b69bab 100644 --- a/common/src/main/java/us/ajg0702/queue/common/QueueMain.java +++ b/common/src/main/java/us/ajg0702/queue/common/QueueMain.java @@ -2,6 +2,8 @@ package us.ajg0702.queue.common; import org.spongepowered.configurate.ConfigurateException; import us.ajg0702.queue.api.*; +import us.ajg0702.queue.api.events.Event; +import us.ajg0702.queue.api.events.utils.EventReceiver; import us.ajg0702.queue.api.premium.Logic; import us.ajg0702.queue.api.premium.LogicGetter; import us.ajg0702.queue.api.util.QueueLogger; @@ -12,7 +14,8 @@ import us.ajg0702.utils.common.Messages; import us.ajg0702.utils.common.Updater; import java.io.File; -import java.util.LinkedHashMap; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; @@ -124,6 +127,31 @@ public class QueueMain extends AjQueueAPI { updater.shutdown(); } + + private final Map, ArrayList>> listeners = new ConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") + @Override + public void listen(Class event, EventReceiver handler) { + if(!Arrays.asList(event.getInterfaces()).contains(Event.class)) { + throw new IllegalArgumentException("You can only listen to ajQueue events!"); + } + List> existingList = listeners.computeIfAbsent(event, (k) -> new ArrayList<>()); + existingList.add((e) -> handler.execute((E) e)); + } + + public void call(Event event) { + List> list = listeners.computeIfAbsent(event.getClass(), (k) -> new ArrayList<>()); + list.forEach(eventReceiver -> { + try { + eventReceiver.execute(event); + } catch(Exception e) { + logger.severe("An external plugin threw an error while handling an event (this is probably not the fault of ajQueue!)", e); + } + }); + + } + @Override public ExecutorService getServersUpdateExecutor() { return taskManager.getServersUpdateExecutor(); diff --git a/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java b/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java index ba6ad62..f144e6c 100644 --- a/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java +++ b/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import net.kyori.adventure.text.Component; import net.kyori.adventure.title.Title; import us.ajg0702.queue.api.QueueManager; +import us.ajg0702.queue.api.events.PreQueueEvent; import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.players.QueuePlayer; import us.ajg0702.queue.api.premium.Logic; @@ -208,6 +209,14 @@ public class QueueManagerImpl implements QueueManager { } } + + PreQueueEvent preQueueEvent = new PreQueueEvent(player, server); + main.call(preQueueEvent); + if(preQueueEvent.isCancelled()) return false; + + + // Player should be added! + ImmutableList list = server.getQueue(); QueuePlayer queuePlayer; AdaptedServer ideal = server.getIdealServer(player); diff --git a/common/src/main/java/us/ajg0702/queue/common/TaskManager.java b/common/src/main/java/us/ajg0702/queue/common/TaskManager.java index 52f3b0b..6abe8be 100644 --- a/common/src/main/java/us/ajg0702/queue/common/TaskManager.java +++ b/common/src/main/java/us/ajg0702/queue/common/TaskManager.java @@ -1,5 +1,7 @@ package us.ajg0702.queue.common; +import us.ajg0702.queue.common.utils.QueueThreadFactory; + import java.util.Arrays; import java.util.List; import java.util.concurrent.*; @@ -7,10 +9,9 @@ import java.util.concurrent.*; public class TaskManager { - final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); - final ScheduledExecutorService updateExecutor = Executors.newScheduledThreadPool(1); - - final ExecutorService serversUpdateExecutor = Executors.newCachedThreadPool(); + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new QueueThreadFactory("GENERIC")); + final ScheduledExecutorService updateExecutor = Executors.newScheduledThreadPool(1, new QueueThreadFactory("UPDATE-EXECUTOR")); + final ExecutorService serversUpdateExecutor = Executors.newCachedThreadPool(new QueueThreadFactory("SERVER-UPDATE")); final QueueMain main; public TaskManager(QueueMain main) { @@ -128,6 +129,10 @@ public class TaskManager { return executor.schedule(runnable, delay, unit); } + public Future runNow(Runnable runnable) { + return executor.submit(runnable); + } + private ScheduledFuture scheduleAtFixedRate(ScheduledExecutorService executor, Runnable command, long initialDelay, long period, TimeUnit unit) { return executor.scheduleAtFixedRate(() -> { diff --git a/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java b/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java index cbdfd53..50a0e89 100644 --- a/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java +++ b/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java @@ -1,6 +1,7 @@ package us.ajg0702.queue.common.queues; import com.google.common.collect.ImmutableList; +import us.ajg0702.queue.api.events.PositionChangeEvent; import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.players.QueuePlayer; import us.ajg0702.queue.api.queues.Balancer; @@ -203,6 +204,7 @@ public class QueueServerImpl implements QueueServer { public synchronized void removePlayer(QueuePlayer player) { main.getQueueManager().getSendingAttempts().remove(player); queue.remove(player); + positionChange(); } @Override @@ -226,6 +228,7 @@ public class QueueServerImpl implements QueueServer { } else { queue.add(player); } + positionChange(); } @Override @@ -316,4 +319,10 @@ public class QueueServerImpl implements QueueServer { return balancer; } + private void positionChange() { + main.getTaskManager().runNow( + () -> queue.forEach(queuePlayer -> main.call(new PositionChangeEvent(queuePlayer))) + ); + } + } diff --git a/common/src/main/java/us/ajg0702/queue/common/utils/QueueThreadFactory.java b/common/src/main/java/us/ajg0702/queue/common/utils/QueueThreadFactory.java new file mode 100644 index 0000000..514b9f1 --- /dev/null +++ b/common/src/main/java/us/ajg0702/queue/common/utils/QueueThreadFactory.java @@ -0,0 +1,21 @@ +package us.ajg0702.queue.common.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class QueueThreadFactory implements ThreadFactory { + private final String name; + private final AtomicInteger i = new AtomicInteger(0); + + public QueueThreadFactory(String name) { + this.name = name; + } + + @Override + public Thread newThread(@NotNull Runnable runnable) { + return new Thread(runnable, "AJQUEUE-" + name + "-" + i.incrementAndGet()); + + } +} diff --git a/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/players/BungeePlayer.java b/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/players/BungeePlayer.java index 1627d2a..e9f3794 100644 --- a/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/players/BungeePlayer.java +++ b/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/players/BungeePlayer.java @@ -122,6 +122,12 @@ public class BungeePlayer implements AdaptedPlayer, Audience { return handle.getServer().getInfo().getName(); } + @Override + public AdaptedServer getCurrentServer() { + if(handle.getServer() == null) return null; + return new BungeeServer(handle.getServer().getInfo()); + } + @Override public UUID getUniqueId() { return handle.getUniqueId(); diff --git a/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/players/VelocityPlayer.java b/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/players/VelocityPlayer.java index dda062e..c73a1dc 100644 --- a/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/players/VelocityPlayer.java +++ b/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/players/VelocityPlayer.java @@ -17,6 +17,7 @@ import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.server.AdaptedServer; import us.ajg0702.queue.common.QueueMain; import us.ajg0702.queue.common.utils.Debug; +import us.ajg0702.queue.platforms.velocity.server.VelocityServer; import java.util.List; import java.util.Objects; @@ -111,10 +112,17 @@ public class VelocityPlayer implements AdaptedPlayer, Audience { @Override public String getServerName() { + AdaptedServer currentServer = getCurrentServer(); + if(currentServer == null) return null; + return currentServer.getName(); + } + + @Override + public AdaptedServer getCurrentServer() { Optional serverConnection = handle.getCurrentServer(); if(!serverConnection.isPresent()) return null; ServerConnection connection = serverConnection.get(); - return connection.getServerInfo().getName(); + return new VelocityServer(connection.getServer()); } @Override