From 20440f89dfef82f39bfcce8da504119d1832cd0b Mon Sep 17 00:00:00 2001 From: phinner <62483793+phinner@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:44:59 +0100 Subject: [PATCH] chore(common): Some more modules --- .../distributor/common/Distributor.java | 6 + .../common/DistributorCommonPlugin.java | 24 +++- .../common/annotation/EventHandler.java | 41 ++++++ .../common/annotation/package-info.java | 4 + .../common/event/EventManager.java | 126 ++++++++++++++++++ .../common/event/EventManagerImpl.java | 126 ++++++++++++++++++ .../common/event/EventSubscription.java | 30 +++++ .../common/event/package-info.java | 4 + .../common/internal/package-info.java | 4 + .../common/localization/package-info.java | 4 + .../common/player/package-info.java | 4 + .../common/scheduler/PluginScheduler.java | 8 ++ .../common/scheduler/PluginSchedulerImpl.java | 6 +- .../common/service/package-info.java | 4 + .../distributor/common/util/package-info.java | 4 + .../simple/DistributorLoggerPlugin.java | 2 +- 16 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/EventHandler.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManager.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManagerImpl.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventSubscription.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/event/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/internal/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/localization/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/player/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/service/package-info.java create mode 100644 distributor-common/src/main/java/com/xpdustry/distributor/common/util/package-info.java diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/Distributor.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/Distributor.java index 29a2cd50..d2269496 100644 --- a/distributor-common/src/main/java/com/xpdustry/distributor/common/Distributor.java +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/Distributor.java @@ -19,17 +19,23 @@ package com.xpdustry.distributor.common; import com.xpdustry.distributor.common.command.CommandFacadeManager; +import com.xpdustry.distributor.common.event.EventManager; import com.xpdustry.distributor.common.localization.LocalizationSourceManager; import com.xpdustry.distributor.common.permission.PermissionManager; +import com.xpdustry.distributor.common.scheduler.PluginScheduler; import com.xpdustry.distributor.common.service.ServiceManager; public interface Distributor { ServiceManager getServiceManager(); + EventManager getEventManager(); + CommandFacadeManager getCommandFacadeFactory(); PermissionManager getPermissionManager(); LocalizationSourceManager getLocalizationSourceManager(); + + PluginScheduler getPluginScheduler(); } diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/DistributorCommonPlugin.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/DistributorCommonPlugin.java index 908ddb00..234ac9b5 100644 --- a/distributor-common/src/main/java/com/xpdustry/distributor/common/DistributorCommonPlugin.java +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/DistributorCommonPlugin.java @@ -19,9 +19,12 @@ package com.xpdustry.distributor.common; import com.xpdustry.distributor.common.command.CommandFacadeManager; +import com.xpdustry.distributor.common.event.EventManager; import com.xpdustry.distributor.common.localization.LocalizationSourceManager; import com.xpdustry.distributor.common.permission.PermissionManager; import com.xpdustry.distributor.common.plugin.AbstractMindustryPlugin; +import com.xpdustry.distributor.common.scheduler.PluginScheduler; +import com.xpdustry.distributor.common.scheduler.PluginTimeSource; import com.xpdustry.distributor.common.service.ServiceManager; import com.xpdustry.distributor.common.util.Priority; import java.util.Objects; @@ -31,7 +34,9 @@ public final class DistributorCommonPlugin extends AbstractMindustryPlugin imple private final ServiceManager services = ServiceManager.create(); private final LocalizationSourceManager source = LocalizationSourceManager.create(); + private @Nullable EventManager events = null; private @Nullable CommandFacadeManager factory = null; + private @Nullable PluginScheduler scheduler = null; private @Nullable PermissionManager permissions = null; @Override @@ -39,9 +44,14 @@ public ServiceManager getServiceManager() { return this.services; } + @Override + public EventManager getEventManager() { + return ensureInitialized(this.events, "event"); + } + @Override public CommandFacadeManager getCommandFacadeFactory() { - return ensureInitialized(this.factory, "command-facade-manager"); + return ensureInitialized(this.factory, "command-facade"); } @Override @@ -54,16 +64,28 @@ public LocalizationSourceManager getLocalizationSourceManager() { return this.source; } + @Override + public PluginScheduler getPluginScheduler() { + return ensureInitialized(this.scheduler, "scheduler"); + } + @Override public void onInit() { DistributorProvider.set(this); + this.services.register(this, EventManager.class, Priority.LOW, EventManager::create); this.services.register(this, CommandFacadeManager.class, Priority.LOW, CommandFacadeManager::create); + this.services.register(this, PluginTimeSource.class, Priority.LOW, PluginTimeSource::arc); } @Override public void onLoad() { this.permissions = services.provide(PermissionManager.class); + this.events = services.provide(EventManager.class); this.factory = services.provide(CommandFacadeManager.class); + this.scheduler = PluginScheduler.create( + this, + this.services.provide(PluginTimeSource.class), + Runtime.getRuntime().availableProcessors()); } private T ensureInitialized(final @Nullable T instance, final String name) { diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/EventHandler.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/EventHandler.java new file mode 100644 index 00000000..fa51f77d --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/EventHandler.java @@ -0,0 +1,41 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.common.annotation; + +import com.xpdustry.distributor.common.util.Priority; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as an event handler, meaning it will be called by a {@link com.xpdustry.distributor.common.event.EventManager} when its corresponding event is + * posted. + *
+ * The annotated method must have exactly one parameter, which is the event class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface EventHandler { + + /** + * The priority of the event handler. + */ + Priority priority() default Priority.NORMAL; +} diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/package-info.java new file mode 100644 index 00000000..82669115 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/annotation/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManager.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManager.java new file mode 100644 index 00000000..561ecb98 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManager.java @@ -0,0 +1,126 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.common.event; + +import com.xpdustry.distributor.common.plugin.MindustryPlugin; +import com.xpdustry.distributor.common.util.Priority; +import java.util.function.Consumer; + +/** + * The event bus of this server. A better alternative to {@link arc.Events}. + *
+ * Event subscribers registered with this class will not crash the server if an exception is thrown but be logged instead. + * Subscribers also come with {@link EventSubscription} objects to dynamically unsubscribe them. And also + * {@link Priority} to make sure your subscribers are called in a specific order. + *
 {@code
+ *      final EventBus bus = DistributorProvider.get().getEventBus();
+ *      final MindustryPlugin plugin = ...;
+ *      final EventSubscription subscription = bus.subscribe(EventType.PlayerJoin.class, plugin, event -> {
+ *          event.player.sendMessage("Hello " + event.player.name() + "!");
+ *      });
+ *      // When no longer needed, you can unsubscribe the listener
+ *      subscription.unsubscribe();
+ * } 
+ *
+ */ +public interface EventManager { + + static EventManager create() { + return new EventManagerImpl(); + } + + /** + * Subscribe to an event. + * + * @param event the event class to subscribe to + * @param priority the priority of the listener + * @param plugin the plugin that owns the listener + * @param listener the listener to subscribe + * @param the type of the event + * @return the subscription of the subscribed listener + */ + EventSubscription subscribe( + final Class event, final Priority priority, final MindustryPlugin plugin, final Consumer listener); + + /** + * Subscribe to an event. + * + * @param event the event class to subscribe to + * @param plugin the plugin that owns the listener + * @param listener the listener to subscribe + * @param the type of the event + * @return the subscription of the subscribed listener + */ + default EventSubscription subscribe( + final Class event, final MindustryPlugin plugin, final Consumer listener) { + return this.subscribe(event, Priority.NORMAL, plugin, listener); + } + + /** + * Subscribe to an event. + * + * @param event the event enum to subscribe to + * @param priority the priority of the listener + * @param plugin the plugin that owns the listener + * @param listener the listener to subscribe + * @param the type of the enum event + * @return the subscription of the subscribed listener + */ + > EventSubscription subscribe( + final E event, final Priority priority, final MindustryPlugin plugin, final Runnable listener); + + /** + * Subscribe to an event. + * + * @param event the event enum to subscribe to + * @param plugin the plugin that owns the listener + * @param listener the listener to subscribe + * @param the type of the enum event + * @return the subscription of the subscribed listener + */ + default > EventSubscription subscribe( + final E event, final MindustryPlugin plugin, final Runnable listener) { + return this.subscribe(event, Priority.NORMAL, plugin, listener); + } + + /** + * Posts the event to the arc event bus. + * + * @param event the event to post + * @param the type of the event + */ + void post(final E event); + + /** + * Posts the event to the arc event bus to the listeners of the given super class. + * + * @param clazz the class of the event + * @param event the event to post + * @param the type of the event + */ + void post(final Class clazz, final E event); + + /** + * Posts the enum event to the arc event bus. + * + * @param event the enum event to post + * @param the type of the enum event + */ + > void post(final E event); +} diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManagerImpl.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManagerImpl.java new file mode 100644 index 00000000..a4fbee26 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventManagerImpl.java @@ -0,0 +1,126 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.common.event; + +import arc.Events; +import arc.func.Cons; +import arc.struct.ObjectMap; +import arc.struct.Seq; +import com.xpdustry.distributor.common.plugin.MindustryPlugin; +import com.xpdustry.distributor.common.plugin.PluginAware; +import com.xpdustry.distributor.common.util.Priority; +import java.util.Comparator; +import java.util.function.Consumer; + +final class EventManagerImpl implements EventManager { + + private static final Comparator> COMPARATOR = (a, b) -> { + final var priorityA = a instanceof ConsumerCons m ? m.priority : Priority.NORMAL; + final var priorityB = b instanceof ConsumerCons m ? m.priority : Priority.NORMAL; + return priorityA.compareTo(priorityB); + }; + + final ObjectMap>> events; + + @SuppressWarnings("unchecked") + EventManagerImpl() { + try { + final var field = Events.class.getDeclaredField("events"); + field.setAccessible(true); + this.events = (ObjectMap>>) field.get(null); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public EventSubscription subscribe( + final Class event, final Priority priority, final MindustryPlugin plugin, final Consumer listener) { + return this.subscribe(event, new ConsumerCons<>(listener, priority, plugin)); + } + + @Override + public > EventSubscription subscribe( + final E event, final Priority priority, final MindustryPlugin plugin, final Runnable listener) { + return this.subscribe(event, new ConsumerCons<>(e -> listener.run(), priority, plugin)); + } + + @SuppressWarnings("ConstantValue") // <- intellij thinks ObjectMap.get never return null, cringe... + private EventSubscription subscribe(final Object event, final ConsumerCons subscriber) { + this.events.get(event, () -> new Seq<>(Cons.class)).add(subscriber).sort(COMPARATOR); + return () -> { + final var subscribers = this.events.get(event); + if (subscribers != null) { + subscribers.remove(subscriber); + if (subscribers.isEmpty()) { + this.events.remove(event); + } + } + }; + } + + @Override + public void post(final E event) { + Events.fire(event.getClass(), event); + } + + @Override + public void post(final Class clazz, final E event) { + Events.fire(clazz, event); + } + + @Override + public > void post(final E event) { + Events.fire(event); + } + + @SuppressWarnings("ClassCanBeRecord") + private static final class ConsumerCons implements Cons, PluginAware { + + private final Consumer consumer; + private final Priority priority; + private final MindustryPlugin plugin; + + private ConsumerCons(final Consumer consumer, final Priority priority, final MindustryPlugin plugin) { + this.consumer = consumer; + this.priority = priority; + this.plugin = plugin; + } + + @Override + public void get(final T event) { + try { + this.consumer.accept(event); + } catch (final Throwable e) { + this.plugin + .getLogger() + .atError() + .setMessage("An error occurred while handling a {} event.") + .addArgument(event.getClass().getSimpleName()) + .setCause(e) + .log(); + } + } + + @Override + public MindustryPlugin getPlugin() { + return this.plugin; + } + } +} diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventSubscription.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventSubscription.java new file mode 100644 index 00000000..4a9c0851 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/EventSubscription.java @@ -0,0 +1,30 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.common.event; + +/** + * A subscription to an event. + */ +public interface EventSubscription { + + /** + * Unsubscribes the bound subscriber from the event. + */ + void unsubscribe(); +} diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/event/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/package-info.java new file mode 100644 index 00000000..8c341abc --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/event/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.event; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/internal/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/internal/package-info.java new file mode 100644 index 00000000..ce5becf0 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/internal/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.internal; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/localization/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/localization/package-info.java new file mode 100644 index 00000000..82e72e43 --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/localization/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.localization; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/player/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/player/package-info.java new file mode 100644 index 00000000..8635546e --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/player/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.player; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginScheduler.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginScheduler.java index fa2fd5e2..f56590d4 100644 --- a/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginScheduler.java +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginScheduler.java @@ -18,6 +18,7 @@ */ package com.xpdustry.distributor.common.scheduler; +import arc.Core; import com.xpdustry.distributor.common.plugin.MindustryPlugin; /** @@ -25,6 +26,13 @@ */ public interface PluginScheduler { + static PluginScheduler create( + final MindustryPlugin plugin, final PluginTimeSource timeSource, final int parallelism) { + final var instance = new PluginSchedulerImpl(timeSource, Core.app::post, parallelism); + plugin.addListener(instance); + return instance; + } + /** * Returns a new {@link PluginTask.Builder} instance scheduling a task. * diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginSchedulerImpl.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginSchedulerImpl.java index 1ba10517..ceb61fca 100644 --- a/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginSchedulerImpl.java +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/scheduler/PluginSchedulerImpl.java @@ -44,7 +44,7 @@ final class PluginSchedulerImpl implements PluginScheduler, PluginListener { private final Executor syncExecutor; private final PluginTimeSource source; - public PluginSchedulerImpl(final PluginTimeSource source, final Executor syncExecutor, final int parallelism) { + PluginSchedulerImpl(final PluginTimeSource source, final Executor syncExecutor, final int parallelism) { this.pool = new ForkJoinPool(parallelism, new PluginSchedulerWorkerThreadFactory(), null, false); this.syncExecutor = syncExecutor; this.source = source; @@ -73,11 +73,11 @@ public void onPluginUpdate() { @Override public void onPluginExit() { - logger.info("Shutdown scheduler."); + logger.info("Shutting down scheduler."); this.pool.shutdown(); try { if (!this.pool.awaitTermination(20, TimeUnit.SECONDS)) { - logger.error("Timed out waiting for the scheduler to terminate properly"); + logger.error("Timed out waiting for the scheduler to terminate properly."); Thread.getAllStackTraces().forEach((thread, stack) -> { if (thread.getName().startsWith(DISTRIBUTOR_WORKER_BASE_NAME)) { logger.error( diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/service/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/service/package-info.java new file mode 100644 index 00000000..d1b08b8c --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/service/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.service; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/common/util/package-info.java b/distributor-common/src/main/java/com/xpdustry/distributor/common/util/package-info.java new file mode 100644 index 00000000..003499bd --- /dev/null +++ b/distributor-common/src/main/java/com/xpdustry/distributor/common/util/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.common.util; + +import org.jspecify.annotations.NullMarked; diff --git a/distributor-logging-simple/src/main/java/com/xpdustry/distributor/logger/simple/DistributorLoggerPlugin.java b/distributor-logging-simple/src/main/java/com/xpdustry/distributor/logger/simple/DistributorLoggerPlugin.java index d8d142ee..a70fe236 100644 --- a/distributor-logging-simple/src/main/java/com/xpdustry/distributor/logger/simple/DistributorLoggerPlugin.java +++ b/distributor-logging-simple/src/main/java/com/xpdustry/distributor/logger/simple/DistributorLoggerPlugin.java @@ -48,7 +48,7 @@ private static void initialize() { SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); - LoggerFactory.getLogger(DistributorLoggerPlugin.class).info("Initialized"); + LoggerFactory.getLogger(DistributorLoggerPlugin.class).info("Initialized simple logger"); } finally { // Restore the class loader Thread.currentThread().setContextClassLoader(temp);