All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.freya02.botcommands.api.waiter.EventWaiter Maven / Gradle / Ivy

package com.freya02.botcommands.api.waiter;

import com.freya02.botcommands.api.Logging;
import com.freya02.botcommands.internal.utils.EventUtils;
import com.freya02.botcommands.internal.utils.Utils;
import com.freya02.botcommands.internal.waiter.WaitingEvent;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.hooks.EventListener;
import net.dv8tion.jda.api.hooks.SubscribeEvent;
import net.dv8tion.jda.api.requests.GatewayIntent;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * An event waiter - if you need to wait for an event to occur while not blocking threads or having listeners everywhere.
 * 
You provide the type of the JDA event you want to get *
You can then set properties such as preconditions, timeouts and actions to run when the event gets received / has an exception, etc... *
This event waiter cannot be constructed and does not need to be registered to the JDA instance, it is already done automatically * *

Example

*

This example uses every actions, has a timeout of 1 second and only triggers if the caller is the same as the user who triggered the previously entered command

*

 * final{@literal Future} future = EventWaiter.of(GuildMessageReceivedEvent.class)
 * 		.setOnComplete((f, e, t){@literal ->} System.out.println("Completed"))
 * 		.setOnTimeout((){@literal ->} System.err.println("Timeout"))
 * 		.setOnSuccess(e{@literal ->} System.out.println("Success"))
 * 		.setOnCancelled((){@literal ->} System.err.println("Cancelled"))
 * 		.setTimeout(1, TimeUnit.SECONDS)
 * 		.addPrecondition(e{@literal ->} e.getAuthor().getIdLong() == event.getAuthor().getIdLong())
 * 		.submit();
 *
 * //future.cancel(true);
 *
 * try { //Use this **only** if you need to block your thread waiting for the event, this is considered bad practise so rather use setOnSuccess / setonComplete
 * 	final GuildMessageReceivedEvent guildMessageReceivedEvent = future.get();
 * } catch (InterruptedException | ExecutionException e) {
 * 	e.printStackTrace();
 * }
 * 
*/ public class EventWaiter implements EventListener { private static final Map, List>> waitingMap = new HashMap<>(); private static final Logger LOGGER = Logging.getLogger(); private static final Object EVENT_LIST_LOCK = new Object(); private static int commandThreadNumber = 0; private static final ExecutorService waiterCompleteService = Utils.createCommandPool(r -> { final Thread thread = new Thread(r); thread.setDaemon(false); thread.setUncaughtExceptionHandler((t, e) -> Utils.printExceptionString("An unexpected exception happened in an event waiter thread '" + t.getName() + "':", e)); thread.setName("Event waiter thread #" + commandThreadNumber++); return thread; }); private static JDA jda; private static EnumSet intents = EnumSet.noneOf(GatewayIntent.class); private static boolean initialized; public EventWaiter(@NotNull JDA jda) { if (initialized) throw new IllegalStateException("Cannot build an EventWaiter more than once"); initialized = true; LOGGER.debug("Initialized EventWaiter"); EventWaiter.jda = jda; EventWaiter.intents = jda.getGatewayIntents(); } /** * Creates a new event waiter builder, waiting for the specified event to occur * * @param eventType The JDA event to wait after * @param Type of the JDA event * @return A new event waiter builder */ public static EventWaiterBuilder of(Class eventType) { if (!initialized) throw new IllegalStateException("Framework must be constructed before using the EventWaiter"); EventUtils.checkEvent(jda, intents, eventType); return new EventWaiterBuilder<>(eventType); } static CompletableFuture submit(WaitingEvent waitingEvent) { CompletableFuture future = waitingEvent.getCompletableFuture(); final List> waitingEvents = getWaitingEventsByType(waitingEvent); if (waitingEvent.getTimeout() > 0) { future.orTimeout(waitingEvent.getTimeout(), waitingEvent.getTimeoutUnit()); } future.whenCompleteAsync((t, throwable) -> { final CompletedFutureEvent onComplete = waitingEvent.getOnComplete(); if (onComplete != null) onComplete.accept(future, t, throwable); if (throwable instanceof TimeoutException) { final Runnable onTimeout = waitingEvent.getOnTimeout(); if (onTimeout != null) onTimeout.run(); synchronized (EVENT_LIST_LOCK) { waitingEvents.remove(waitingEvent); //Not removed automatically by Iterator#remove before this method is called } } else if (t != null) { final Consumer onSuccess = waitingEvent.getOnSuccess(); if (onSuccess != null) onSuccess.accept(t); } else if (future.isCancelled()) { synchronized (EVENT_LIST_LOCK) { waitingEvents.remove(waitingEvent); //Not removed automatically by Iterator#remove before this method is called } final Runnable onCancelled = waitingEvent.getOnCancelled(); if (onCancelled != null) onCancelled.run(); } else { LOGGER.warn("Unexpected object received in EventWaiter Future#whenCompleteAsync, please report this to devs"); } }, waiterCompleteService); waitingEvents.add(waitingEvent); return future; } @NotNull private static List> getWaitingEventsByType(WaitingEvent waitingEvent) { return waitingMap.computeIfAbsent(waitingEvent.getEventType(), x -> Collections.synchronizedList(new ArrayList<>())); } @SuppressWarnings("unchecked") @SubscribeEvent @Override public void onEvent(@NotNull GenericEvent event) { final List> waitingEvents = waitingMap.get(event.getClass()); if (waitingEvents != null) { synchronized (EVENT_LIST_LOCK) { //Prevent concurrent modification between iterator and on-timeout List#remove eventLoop: for (Iterator> iterator = waitingEvents.iterator(); iterator.hasNext(); ) { WaitingEvent waitingEvent = iterator.next(); for (Predicate p : waitingEvent.getPreconditions()) { final Predicate precondition = (Predicate) p; if (!precondition.test(event)) { continue eventLoop; } } final CompletableFuture completableFuture = (CompletableFuture) waitingEvent.getCompletableFuture(); if (completableFuture.complete(event)) { iterator.remove(); } else { LOGGER.warn("Completable future was already completed somehow, please report to the dev"); } } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy