From b5ee0a6725094736dc5aabb3220b953196eeecbd Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:40:15 +0200 Subject: [PATCH] Removed google guave dependency. Added a new GlobalEventBus. Refined the EventPublisher. Moving events to own file. --- pom.xml | 17 +- src/main/java/org/toop/Main.java | 49 +--- src/main/java/org/toop/MainTest.java | 37 +-- .../java/org/toop/backend/ServerManager.java | 24 +- src/main/java/org/toop/core/Window.java | 100 +++---- .../org/toop/eventbus/EventPublisher.java | 252 +++++++++-------- .../org/toop/eventbus/GlobalEventBus.java | 255 ++++++++++++------ .../toop/eventbus/events/EventWithUuid.java | 2 +- .../java/org/toop/eventbus/events/Events.java | 24 +- .../toop/eventbus/events/NetworkEvents.java | 21 +- .../toop/eventbus/events/ServerEvents.java | 4 + .../toop/frontend/UI/RemoteGameSelector.java | 34 +-- .../toop/frontend/games/LocalTicTacToe.java | 33 +-- .../org/toop/frontend/graphics/Renderer.java | 100 +++---- .../org/toop/frontend/graphics/Shader.java | 54 ++-- .../frontend/graphics/node/NodeManager.java | 136 +++++----- .../frontend/networking/NetworkingClient.java | 3 +- .../networking/NetworkingClientManager.java | 47 ++-- .../NetworkingGameClientHandler.java | 2 - .../frontend/networking/ServerConnection.java | 5 +- .../platform/core/glfw/GlfwWindow.java | 218 +++++++-------- .../graphics/opengl/OpenglRenderer.java | 158 +++++------ .../graphics/opengl/OpenglShader.java | 114 ++++---- .../eventbus/EventPublisherSpeedTest.java | 6 +- .../eventbus/EventPublisherStressTest.java | 143 ++++++++-- .../org/toop/eventbus/EventPublisherTest.java | 12 +- .../org/toop/eventbus/GlobalEventBusTest.java | 192 +++++++------ 27 files changed, 1115 insertions(+), 927 deletions(-) create mode 100644 src/main/java/org/toop/eventbus/events/ServerEvents.java diff --git a/pom.xml b/pom.xml index 2f489f7..e184d76 100644 --- a/pom.xml +++ b/pom.xml @@ -38,12 +38,6 @@ - - com.google.guava - guava - 33.4.8-jre - - org.junit junit-bom @@ -98,6 +92,17 @@ log4j-core 2.25.1 + + org.slf4j + slf4j-api + 2.0.17 + + + org.slf4j + slf4j-simple + 2.0.17 + + diff --git a/src/main/java/org/toop/Main.java b/src/main/java/org/toop/Main.java index ce6adc2..cca3a60 100644 --- a/src/main/java/org/toop/Main.java +++ b/src/main/java/org/toop/Main.java @@ -1,11 +1,10 @@ package org.toop; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.function.Supplier; -import com.google.common.base.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.backend.ServerManager; @@ -16,7 +15,6 @@ import org.toop.eventbus.GlobalEventBus; import org.toop.eventbus.events.NetworkEvents; import org.toop.frontend.UI.LocalServerSelector; import org.toop.frontend.networking.NetworkingClientManager; -import org.toop.frontend.networking.NetworkingGameClientHandler; public class Main { private static final Logger logger = LogManager.getLogger(Main.class); @@ -38,49 +36,20 @@ public class Main { new Events.ServerEvents.StartServerRequest(5001, "tictactoe", serverIdFuture)); var serverId = serverIdFuture.get(); - new MainTest(); - - -// CompletableFuture conIdFuture = new CompletableFuture<>(); -// GlobalEventBus.post( -// new NetworkEvents.StartClientRequest(NetworkingGameClientHandler::new, -// "127.0.0.1", 5001, conIdFuture)); -// var conId = conIdFuture.get(); - - -// GlobalEventBus.post(new NetworkEvents.SendCommand(conId, "move", "5")); -// GlobalEventBus.post(new NetworkEvents.ForceCloseAllClients()); -// GlobalEventBus.post(new NetworkEvents.StartClient( -// NetworkingGameClientHandler::new, "127.0.0.1", 5001, serverId -// )); - - // JFrame frame = new JFrame("Server Settings"); - // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - // frame.setSize(800, 600); - // frame.setLocationRelativeTo(null); - // frame.setVisible(true); + var a = new MainTest(); javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new); - // new Thread(() -> { - // LocalServerSelector window = new LocalServerSelector(); - // }).start(); + } + private static void initSystems() { + new ServerManager(); + new NetworkingClientManager(); } private static void registerEvents() { - GlobalEventBus.subscribeAndRegister( - Events.WindowEvents.OnQuitRequested.class, - event -> { - quit(); - }); - - GlobalEventBus.subscribeAndRegister(Events.WindowEvents.OnMouseMove.class, event -> {}); - } - - public static void initSystems() { - new ServerManager(); - new NetworkingClientManager(); + new EventPublisher<>(Events.WindowEvents.OnQuitRequested.class, _ -> quit()); + new EventPublisher<>(Events.WindowEvents.OnMouseMove.class, _ -> {}); } private static void quit() { diff --git a/src/main/java/org/toop/MainTest.java b/src/main/java/org/toop/MainTest.java index c762bc5..54f8c96 100644 --- a/src/main/java/org/toop/MainTest.java +++ b/src/main/java/org/toop/MainTest.java @@ -1,39 +1,24 @@ package org.toop; -import com.google.common.base.Supplier; import org.toop.eventbus.EventPublisher; import org.toop.eventbus.GlobalEventBus; -import org.toop.eventbus.events.Events; import org.toop.eventbus.events.NetworkEvents; import org.toop.frontend.networking.NetworkingGameClientHandler; +import java.util.function.Supplier; + public class MainTest { - MainTest() { - - var ep = new EventPublisher<>( - Events.ServerEvents.StartServer.class, - 5001, - "tictactoe" - ).onEvent( - this::handleServerStarted - ).unregisterAfterSuccess().postEvent(); - -// var ep = new EventPublisher<>( -// NetworkEvents.SendCommand.class, -// (Supplier) NetworkingGameClientHandler::new, -// "127.0.0.1", -// 5001 -// ).onEventById(this::handleStartClientRequest).unregisterAfterSuccess().postEvent(); + var a = new EventPublisher<>( + NetworkEvents.StartClient.class, + (Supplier) NetworkingGameClientHandler::new, + "127.0.0.1", + 5001 + ).onEventById(NetworkEvents.StartClientSuccess.class, this::handleStartClientSuccess) + .unsubscribeAfterSuccess().asyncPostEvent(); } - private void handleStartClientRequest(NetworkEvents.StartClientSuccess event) { - GlobalEventBus.post(new NetworkEvents.CloseClient((String) event.connectionId())); + private void handleStartClientSuccess(NetworkEvents.StartClientSuccess event) { + GlobalEventBus.post(new NetworkEvents.CloseClient(event.clientId())); } - - private void handleServerStarted(Events.ServerEvents.ServerStarted event) { - System.out.println("Server started"); - } - - } diff --git a/src/main/java/org/toop/backend/ServerManager.java b/src/main/java/org/toop/backend/ServerManager.java index e2b0d04..4aabc06 100644 --- a/src/main/java/org/toop/backend/ServerManager.java +++ b/src/main/java/org/toop/backend/ServerManager.java @@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.backend.tictactoe.TicTacToeServer; +import org.toop.eventbus.EventPublisher; import org.toop.eventbus.events.Events; import org.toop.eventbus.GlobalEventBus; @@ -22,19 +23,12 @@ public class ServerManager { /** Starts a server manager, to manage, servers. */ public ServerManager() { - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.StartServerRequest.class, this::handleStartServerRequest); - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.StartServer.class, this::handleStartServer); - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.ForceCloseAllServers.class, _ -> shutdownAll()); - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.CreateTicTacToeGameRequest.class, - this::handleStartTicTacToeGameOnAServer); - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer); - GlobalEventBus.subscribeAndRegister( - Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer); + new EventPublisher<>(Events.ServerEvents.StartServerRequest.class, this::handleStartServerRequest); + new EventPublisher<>(Events.ServerEvents.StartServer.class, this::handleStartServer); + new EventPublisher<>(Events.ServerEvents.ForceCloseAllServers.class, _ -> shutdownAll()); + new EventPublisher<>(Events.ServerEvents.CreateTicTacToeGameRequest.class, this::handleStartTicTacToeGameOnAServer); + new EventPublisher<>(Events.ServerEvents.RunTicTacToeGame.class, this::handleRunTicTacToeGameOnAServer); + new EventPublisher<>(Events.ServerEvents.EndTicTacToeGame.class, this::handleEndTicTacToeGameOnAServer); } private String startServer(int port, String gameType) { @@ -67,9 +61,7 @@ public class ServerManager { } private void handleStartServer(Events.ServerEvents.StartServer event) { - GlobalEventBus.post( - new Events.ServerEvents.ServerStarted( - this.startServer(event.port(), event.gameType()), event.port())); + new EventPublisher<>(Events.ServerEvents.ServerStarted.class, this.startServer(event.port(), event.gameType()), event.port()); } private void handleStartTicTacToeGameOnAServer( diff --git a/src/main/java/org/toop/core/Window.java b/src/main/java/org/toop/core/Window.java index e9f890b..98a91cd 100644 --- a/src/main/java/org/toop/core/Window.java +++ b/src/main/java/org/toop/core/Window.java @@ -1,50 +1,50 @@ -package org.toop.core; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.frontend.platform.core.glfw.GlfwWindow; - -public abstract class Window { - public enum API { - NONE, - GLFW, - } - - public record Size(int width, int height) {} - - protected static final Logger logger = LogManager.getLogger(Window.class); - - private static API api = API.NONE; - private static Window instance = null; - - public static Window setup(API api, String title, Size size) { - if (instance != null) { - logger.warn("Window is already setup."); - return instance; - } - - switch (api) { - case GLFW: - instance = new GlfwWindow(title, size); - break; - - default: - logger.fatal("No valid window api chosen"); - return null; - } - - Window.api = api; - return instance; - } - - public static API getApi() { - return api; - } - - public void cleanup() { - instance = null; - logger.info("Window cleanup."); - } - - public abstract void update(); -} +//package org.toop.core; +// +//import org.apache.logging.log4j.LogManager; +//import org.apache.logging.log4j.Logger; +//import org.toop.frontend.platform.core.glfw.GlfwWindow; +// +//public abstract class Window { +// public enum API { +// NONE, +// GLFW, +// } +// +// public record Size(int width, int height) {} +// +// protected static final Logger logger = LogManager.getLogger(Window.class); +// +// private static API api = API.NONE; +// private static Window instance = null; +// +// public static Window setup(API api, String title, Size size) { +// if (instance != null) { +// logger.warn("Window is already setup."); +// return instance; +// } +// +// switch (api) { +// case GLFW: +// instance = new GlfwWindow(title, size); +// break; +// +// default: +// logger.fatal("No valid window api chosen"); +// return null; +// } +// +// Window.api = api; +// return instance; +// } +// +// public static API getApi() { +// return api; +// } +// +// public void cleanup() { +// instance = null; +// logger.info("Window cleanup."); +// } +// +// public abstract void update(); +//} diff --git a/src/main/java/org/toop/eventbus/EventPublisher.java b/src/main/java/org/toop/eventbus/EventPublisher.java index 8e03d9c..90e844f 100644 --- a/src/main/java/org/toop/eventbus/EventPublisher.java +++ b/src/main/java/org/toop/eventbus/EventPublisher.java @@ -1,7 +1,7 @@ package org.toop.eventbus; -import com.google.common.eventbus.EventBus; import org.toop.eventbus.events.EventWithUuid; +import org.toop.eventbus.events.IEvent; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -12,51 +12,84 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; /** - * EventPublisher is a helper class for creating, posting, and optionally subscribing to events - * in a type-safe and chainable manner. It automatically injects a unique UUID into the event - * and supports filtering subscribers by this UUID. + * EventPublisher is a utility class for creating, posting, and optionally subscribing to events + * in a type-safe and chainable manner. It is designed to work with the {@link GlobalEventBus}. * - *

Usage pattern (with chainable API): + *

This class supports automatic UUID assignment for {@link EventWithUuid} events, + * and allows filtering subscribers so they only respond to events with a specific UUID. + * All subscription methods are chainable, and you can configure automatic unsubscription + * after an event has been successfully handled.

+ * + *

Usage patterns:

+ * + *

1. Publish an event with optional subscription by UUID:

*
{@code
  * new EventPublisher<>(StartClient.class, handlerFactory, "127.0.0.1", 5001)
  *     .onEventById(ClientReady.class, clientReadyEvent -> logger.info(clientReadyEvent))
- *     .unregisterAfterSuccess()
+ *     .unsubscribeAfterSuccess()
  *     .postEvent();
  * }
* - * @param the type of event to publish, must extend EventWithUuid + *

2. Subscribe to a specific event type without UUID filtering:

+ *
{@code
+ * new EventPublisher<>(MyEvent.class)
+ *     .onEvent(MyEvent.class, e -> logger.info("Received: " + e))
+ *     .postEvent();
+ * }
+ * + *

3. Subscribe with runtime type inference:

+ *
{@code
+ * new EventPublisher<>((MyEvent e) -> logger.info("Received: " + e))
+ *     .postEvent();
+ * }
+ * + *

Notes:

+ *
    + *
  • For events extending {@link EventWithUuid}, a UUID is automatically generated + * and passed to the event constructor if none is provided.
  • + *
  • Listeners registered via {@code onEventById} will only be triggered + * if the event's UUID matches this publisher's UUID.
  • + *
  • Listeners can be unsubscribed automatically after the first successful trigger + * using {@link #unsubscribeAfterSuccess()}.
  • + *
  • All subscription and posting methods are chainable for fluent API usage.
  • + *
+ * + * @param the type of event to publish; must implement {@link IEvent} */ -public class EventPublisher { +public class EventPublisher { + + /** Lookup object used for dynamically invoking constructors via MethodHandles. */ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + /** Cache of constructor handles for event classes to avoid repeated reflection lookups. */ private static final Map, MethodHandle> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>(); - /** The UUID automatically assigned to this event */ - private final String eventId; + /** Automatically assigned UUID for {@link EventWithUuid} events. */ + private String eventId = null; - /** The event instance created by this publisher */ - private final T event; + /** The event instance created by this publisher. */ + private T event = null; - /** The listener object returned by the global event bus subscription */ + /** The listener returned by GlobalEventBus subscription. Used for unsubscription. */ private Object listener; - /** Flag indicating whether to unregister the listener after it is successfully triggered */ - private boolean unregisterAfterSuccess = false; + /** Flag indicating whether to automatically unsubscribe the listener after success. */ + private boolean unsubscribeAfterSuccess = false; - /** Results that came back from the subscribed event */ + /** Holds the results returned from the subscribed event, if any. */ private Map result = null; /** * Constructs a new EventPublisher by instantiating the given event class. - * A unique UUID is automatically generated and passed as the last constructor argument. + * For {@link EventWithUuid} events, a UUID is automatically generated and passed as + * the last constructor argument if not explicitly provided. * * @param postEventClass the class of the event to instantiate - * @param args constructor arguments for the event, excluding the UUID + * @param args constructor arguments for the event (UUID may be excluded) * @throws RuntimeException if instantiation fails */ public EventPublisher(Class postEventClass, Object... args) { - this.eventId = UUID.randomUUID().toString(); - try { boolean isUuidEvent = EventWithUuid.class.isAssignableFrom(postEventClass); @@ -71,43 +104,65 @@ public class EventPublisher { }); Object[] finalArgs; - if (isUuidEvent) { - // append UUID to args + int expectedParamCount = ctorHandle.type().parameterCount(); + if (isUuidEvent && args.length < expectedParamCount) { + this.eventId = UUID.randomUUID().toString(); finalArgs = new Object[args.length + 1]; System.arraycopy(args, 0, finalArgs, 0, args.length); finalArgs[args.length] = this.eventId; + } else if (isUuidEvent) { + this.eventId = (String) args[args.length - 1]; + finalArgs = args; } else { - // just forward args finalArgs = args; } @SuppressWarnings("unchecked") T instance = (T) ctorHandle.invokeWithArguments(finalArgs); this.event = instance; - } catch (Throwable e) { throw new RuntimeException("Failed to instantiate event", e); } } /** - * Subscribes a listener for a specific event type, but only triggers the listener - * if the incoming event's UUID matches this EventPublisher's UUID. + * Creates a new EventPublisher and immediately subscribes a listener for the event class. * * @param eventClass the class of the event to subscribe to - * @param action the action to execute when a matching event is received - * @param the type of the event to subscribe to; must extend EventWithUuid - * @return this EventPublisher instance, for chainable calls + * @param action the action to execute when an event of the given class is received + */ + public EventPublisher(Class eventClass, Consumer action) { + this.onEvent(eventClass, action); + } + + /** + * Creates a new EventPublisher and immediately subscribes a listener using runtime type inference. + * The event type is inferred at runtime. Wrong type casts are ignored silently. + * + * @param action the action to execute when a matching event is received + */ + public EventPublisher(Consumer action) { + this.onEvent(action); + } + + /** + * Subscribes a listener for a specific {@link EventWithUuid} event type. + * The listener is only triggered if the event UUID matches this publisher's UUID. + * + * @param eventClass the class of the event to subscribe to + * @param action the action to execute on a matching event + * @param type of event; must extend EventWithUuid + * @return this EventPublisher for chainable calls */ public EventPublisher onEventById( Class eventClass, Consumer action) { - this.listener = GlobalEventBus.subscribeAndRegister(eventClass, event -> { + this.listener = GlobalEventBus.subscribe(eventClass, event -> { if (event.eventId().equals(this.eventId)) { action.accept(event); - if (unregisterAfterSuccess && listener != null) { - GlobalEventBus.unregister(listener); + if (unsubscribeAfterSuccess && listener != null) { + GlobalEventBus.unsubscribe(listener); } this.result = event.result(); @@ -118,33 +173,29 @@ public class EventPublisher { } /** - * Subscribes a listener for a specific event type, but only triggers the listener - * if the incoming event's UUID matches this EventPublisher's UUID. + * Subscribes a listener for {@link EventWithUuid} events without specifying class explicitly. + * Only triggers for events whose UUID matches this publisher's UUID. * - * @param action the action (function) to execute when a matching event is received - * @param the type of the event to subscribe to; must extend EventWithUuid - * @return this EventPublisher instance, for chainable calls + * @param action the action to execute on a matching event + * @param type of event; must extend EventWithUuid + * @return this EventPublisher for chainable calls */ @SuppressWarnings("unchecked") - public EventPublisher onEventById( - Consumer action) { + public EventPublisher onEventById(Consumer action) { - this.listener = GlobalEventBus.subscribeAndRegister(event -> { - // Only process events that are EventWithUuid + this.listener = GlobalEventBus.subscribe(event -> { if (event instanceof EventWithUuid uuidEvent) { if (uuidEvent.eventId().equals(this.eventId)) { try { - TT typedEvent = (TT) uuidEvent; // unchecked cast + TT typedEvent = (TT) uuidEvent; action.accept(typedEvent); - if (unregisterAfterSuccess && listener != null) { - GlobalEventBus.unregister(listener); + if (unsubscribeAfterSuccess && listener != null) { + GlobalEventBus.unsubscribe(listener); } this.result = typedEvent.result(); - } catch (ClassCastException ignored) { - // TODO: Not the right type, ignore silently - } + } catch (ClassCastException ignored) {} } } }); @@ -153,76 +204,50 @@ public class EventPublisher { } /** - * Subscribes a listener for a specific event type. The listener will be invoked - * whenever an event of the given class is posted to the global event bus. - * - *

This overload provides type safety by requiring the event class explicitly - * and casting the incoming event before passing it to the provided action.

- * - *
{@code
-     * new EventPublisher<>(MyEvent.class)
-     *     .onEvent(MyEvent.class, e -> logger.info("Received: " + e))
-     *     .postEvent();
-     * }
+ * Subscribes a listener for a specific event type without UUID filtering. * * @param eventClass the class of the event to subscribe to - * @param action the action to execute when an event of the given class is received - * @param the type of the event to subscribe to - * @return this EventPublisher instance, for chainable calls + * @param action the action to execute on the event + * @param type of event; must implement IEvent + * @return this EventPublisher for chainable calls */ - public EventPublisher onEvent(Class eventClass, Consumer action) { - this.listener = GlobalEventBus.subscribeAndRegister(eventClass, event -> { + public EventPublisher onEvent(Class eventClass, Consumer action) { + this.listener = GlobalEventBus.subscribe(eventClass, event -> { action.accept(eventClass.cast(event)); - if (unregisterAfterSuccess && listener != null) { - GlobalEventBus.unregister(listener); + if (unsubscribeAfterSuccess && listener != null) { + GlobalEventBus.unsubscribe(listener); } }); return this; } /** - * Subscribes a listener for events without requiring the event class explicitly. - * The listener will attempt to cast each posted event to the expected type. - * If the cast fails, the event is ignored silently. - * - *

This overload provides more concise syntax, but relies on an unchecked cast - * at runtime. Use {@link #onEvent(Class, Consumer)} if you prefer explicit - * type safety.

- * - *
{@code
-     * new EventPublisher<>(MyEvent.class)
-     *     .onEvent((MyEvent e) -> logger.info("Received: " + e))
-     *     .postEvent();
-     * }
+ * Subscribes a listener using runtime type inference. Wrong type casts are ignored silently. * * @param action the action to execute when a matching event is received - * @param the type of the event to subscribe to - * @return this EventPublisher instance, for chainable calls + * @param type of event (inferred at runtime) + * @return this EventPublisher for chainable calls */ @SuppressWarnings("unchecked") public EventPublisher onEvent(Consumer action) { - this.listener = GlobalEventBus.subscribeAndRegister(event -> { + this.listener = GlobalEventBus.subscribe(event -> { try { - // unchecked cast – if wrong type, ClassCastException is caught TT typedEvent = (TT) event; action.accept(typedEvent); - if (unregisterAfterSuccess && listener != null) { - GlobalEventBus.unregister(listener); + if (unsubscribeAfterSuccess && listener != null) { + GlobalEventBus.unsubscribe(listener); } - } catch (ClassCastException ignored) { - // Ignore events of unrelated types - } + } catch (ClassCastException ignored) {} }); return this; } /** - * Posts the event to the global event bus. This should generally be the - * final call in the chain. + * Posts the event synchronously to {@link GlobalEventBus}. * - * @return this EventPublisher instance, for potential chaining + * @return this EventPublisher for chainable calls */ public EventPublisher postEvent() { GlobalEventBus.post(event); @@ -230,30 +255,45 @@ public class EventPublisher { } /** - * Configures the publisher so that any listener registered with - * {@link #onEventById(Class, Consumer)} is automatically unregistered - * after it is successfully triggered. + * Posts the event asynchronously to {@link GlobalEventBus}. * - * @return this EventPublisher instance, for chainable calls + * @return this EventPublisher for chainable calls */ - public EventPublisher unregisterAfterSuccess() { - this.unregisterAfterSuccess = true; + public EventPublisher asyncPostEvent() { + GlobalEventBus.postAsync(event); return this; } - public EventPublisher unregisterNow() { - if (unregisterAfterSuccess && listener != null) { - GlobalEventBus.unregister(listener); + /** + * Configures automatic unsubscription for listeners registered via onEventById + * after a successful trigger. + * + * @return this EventPublisher for chainable calls + */ + public EventPublisher unsubscribeAfterSuccess() { + this.unsubscribeAfterSuccess = true; + return this; + } + + /** + * Immediately unsubscribes the listener, if set. + * + * @return this EventPublisher for chainable calls + */ + public EventPublisher unsubscribeNow() { + if (unsubscribeAfterSuccess && listener != null) { + GlobalEventBus.unsubscribe(listener); } return this; } + /** + * Returns the results provided by the triggered event, if any. + * + * @return map of results, or null if none + */ public Map getResult() { - if (this.result != null) { - return this.result; - } - return null; - // TODO: Why check for null if return is null anyway? + return this.result; } /** @@ -266,9 +306,9 @@ public class EventPublisher { } /** - * Returns the UUID automatically assigned to this event. + * Returns the automatically assigned UUID for {@link EventWithUuid} events. * - * @return the UUID of the event + * @return the UUID string, or null for non-UUID events */ public String getEventId() { return eventId; diff --git a/src/main/java/org/toop/eventbus/GlobalEventBus.java b/src/main/java/org/toop/eventbus/GlobalEventBus.java index adc1ea7..98897ed 100644 --- a/src/main/java/org/toop/eventbus/GlobalEventBus.java +++ b/src/main/java/org/toop/eventbus/GlobalEventBus.java @@ -1,114 +1,203 @@ package org.toop.eventbus; -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; +import org.toop.eventbus.events.EventWithUuid; +import org.toop.eventbus.events.IEvent; +import java.util.Map; +import java.util.concurrent.*; import java.util.function.Consumer; -/** A singleton Event Bus to be used for creating, triggering and activating events. */ -public class GlobalEventBus { +/** + * GlobalEventBus is a high-throughput, thread-safe event bus for publishing and subscribing + * to events within the application. + * + *

It supports:

+ *
    + *
  • Type-specific subscriptions via {@link #subscribe(Class, Consumer)}
  • + *
  • UUID-specific subscriptions via {@link #subscribeById(Class, String, Consumer)}
  • + *
  • Asynchronous posting of events with automatic queueing and fallback
  • + *
+ * + *

Performance note: Directly using {@link GlobalEventBus} is possible, + * but for safer type handling, automatic UUID management, and easier unsubscription, + * it is recommended to use {@link EventPublisher} whenever possible.

+ * + *

The bus maintains a fixed pool of worker threads that continuously process queued events.

+ */ +public final class GlobalEventBus { - /** Singleton event bus. */ - private static EventBus eventBus = new EventBus("global-bus"); + /** Number of worker threads, set to the number of available CPU cores. */ + private static final int WORKERS = Runtime.getRuntime().availableProcessors(); + /** Queue for asynchronous event processing. */ + private static final BlockingQueue EVENT_QUEUE = new LinkedBlockingQueue<>(WORKERS * 1024); + + /** Map of event class to type-specific listeners. */ + private static final Map, CopyOnWriteArrayList>> LISTENERS = new ConcurrentHashMap<>(); + + /** Map of event class to UUID-specific listeners. */ + private static final Map, ConcurrentHashMap>> UUID_LISTENERS = new ConcurrentHashMap<>(); + + /** Thread pool for worker threads processing queued events. */ + private static final ExecutorService WORKER_POOL = Executors.newFixedThreadPool(WORKERS, r -> { + Thread t = new Thread(r, "EventBus-Worker-" + r.hashCode()); + t.setDaemon(true); + return t; + }); + + // Initialize worker threads + static { + for (int i = 0; i < WORKERS; i++) { + WORKER_POOL.submit(GlobalEventBus::workerLoop); + } + } + + /** Private constructor to prevent instantiation. */ private GlobalEventBus() {} - /** - * Wraps a Consumer into a Guava @Subscribe-compatible listener. - * - * @return Singleton Event Bus - */ - public static EventBus get() { - return eventBus; + /** Continuously processes events from the queue and dispatches them to listeners. */ + private static void workerLoop() { + try { + while (true) { + IEvent event = EVENT_QUEUE.take(); + dispatchEvent(event); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } /** - * ONLY USE FOR TESTING + * Subscribes a type-specific listener for all events of a given class. * - * @param newBus + * @param eventClass the class of events to subscribe to + * @param listener the action to execute when the event is posted + * @param the event type + * @return the provided listener for possible unsubscription */ - public static void set(EventBus newBus) { - eventBus = newBus; - } - - /** Reset back to the default global EventBus. */ - public static void reset() { - eventBus = new EventBus("global-bus"); + public static Consumer subscribe(Class eventClass, Consumer listener) { + CopyOnWriteArrayList> list = + LISTENERS.computeIfAbsent(eventClass, k -> new CopyOnWriteArrayList<>()); + list.add(event -> listener.accept(eventClass.cast(event))); + return listener; } /** - * Wraps a Consumer into a Guava @Subscribe-compatible listener. TODO + * Subscribes a generic listener for all events (no type filtering). * - * @param type The event to be used. (e.g. Events.ServerCommand.class) - * @param action The function, or lambda to run when fired. - * @return Object to be used for registering an event. + * @param listener the action to execute on any event + * @return the provided listener for possible unsubscription */ - public static Object subscribe(Class type, Consumer action) { - return new Object() { - @Subscribe - public void handle(Object event) { - if (type.isInstance(event)) { - action.accept(type.cast(event)); + public static Consumer subscribe(Consumer listener) { + LISTENERS.computeIfAbsent(Object.class, _ -> new CopyOnWriteArrayList<>()) + .add(listener); + return listener; + } + + /** + * Subscribes a listener for a specific {@link EventWithUuid} identified by its UUID. + * + * @param eventClass the class of the UUID event + * @param eventId the UUID of the event to listen for + * @param listener the action to execute when the event with the matching UUID is posted + * @param the event type extending EventWithUuid + */ + public static void subscribeById(Class eventClass, String eventId, Consumer listener) { + UUID_LISTENERS + .computeIfAbsent(eventClass, _ -> new ConcurrentHashMap<>()) + .put(eventId, listener); + } + + /** + * Unsubscribes a previously registered listener. + * + * @param listener the listener to remove + */ + public static void unsubscribe(Object listener) { + LISTENERS.values().forEach(list -> list.remove(listener)); + } + + /** + * Unsubscribes a UUID-specific listener. + * + * @param eventClass the class of the UUID event + * @param eventId the UUID of the listener to remove + * @param the event type extending EventWithUuid + */ + public static void unsubscribeById(Class eventClass, String eventId) { + Map> map = UUID_LISTENERS.get(eventClass); + if (map != null) map.remove(eventId); + } + + /** + * Posts an event synchronously to all subscribed listeners. + * + * @param event the event instance to post + * @param the event type + */ + public static void post(T event) { + dispatchEvent(event); + } + + /** + * Posts an event asynchronously by adding it to the internal queue. + * If the queue is full, the event is dispatched synchronously. + * + * @param event the event instance to post + * @param the event type + */ + public static void postAsync(T event) { + if (!EVENT_QUEUE.offer(event)) { + dispatchEvent(event); + } + } + + /** Dispatches an event to all type-specific, generic, and UUID-specific listeners. */ + @SuppressWarnings("unchecked") + private static void dispatchEvent(IEvent event) { + Class clazz = event.getClass(); + + CopyOnWriteArrayList> classListeners = LISTENERS.get(clazz); + if (classListeners != null) { + for (Consumer listener : classListeners) { + try { listener.accept(event); } catch (Throwable ignored) {} + } + } + + CopyOnWriteArrayList> genericListeners = LISTENERS.get(Object.class); + if (genericListeners != null) { + for (Consumer listener : genericListeners) { + try { listener.accept(event); } catch (Throwable ignored) {} + } + } + + if (event instanceof EventWithUuid uuidEvent) { + Map> map = UUID_LISTENERS.get(clazz); + if (map != null) { + Consumer listener = (Consumer) map.remove(uuidEvent.eventId()); + if (listener != null) { + try { listener.accept(uuidEvent); } catch (Throwable ignored) {} } } - }; - } - - @SuppressWarnings("unchecked") - public static Object subscribe(Consumer action) { - return new Object() { - @Subscribe - public void handle(Object event) { - try { - action.accept((T) event); // unchecked cast - } catch (ClassCastException ignored) {} - } - }; + } } /** - * Wraps a Consumer into a Guava @Subscribe-compatible listener and registers it. - * - * @param type The event to be used. (e.g. Events.ServerCommand.class) - * @param action The function, or lambda to run when fired. - * @return Object to be used for registering an event. + * Shuts down the bus immediately, clearing all listeners and queued events. + * Worker threads are stopped. */ - public static Object subscribeAndRegister(Class type, Consumer action) { - var listener = subscribe(type, action); - register(listener); - return listener; - } - - public static Object subscribeAndRegister(Consumer action) { - var listener = subscribe(action); - register(listener); - return listener; + public static void shutdown() { + WORKER_POOL.shutdownNow(); + LISTENERS.clear(); + UUID_LISTENERS.clear(); + EVENT_QUEUE.clear(); } /** - * Wrapper for registering a listener. - * - * @param listener The listener to register. + * Clears all listeners and UUID-specific subscriptions without stopping worker threads. */ - public static void register(Object listener) { - GlobalEventBus.get().register(listener); - } - - /** - * Wrapper for unregistering a listener. - * - * @param listener The listener to unregister. - */ - public static void unregister(Object listener) { - GlobalEventBus.get().unregister(listener); - } - - /** - * Wrapper for posting events. - * - * @param event The event to post. - */ - public static void post(T event) { - GlobalEventBus.get().post(event); + public static void reset() { + LISTENERS.clear(); + UUID_LISTENERS.clear(); } } diff --git a/src/main/java/org/toop/eventbus/events/EventWithUuid.java b/src/main/java/org/toop/eventbus/events/EventWithUuid.java index e420e66..236c1ce 100644 --- a/src/main/java/org/toop/eventbus/events/EventWithUuid.java +++ b/src/main/java/org/toop/eventbus/events/EventWithUuid.java @@ -2,7 +2,7 @@ package org.toop.eventbus.events; import java.util.Map; -public interface EventWithUuid { +public interface EventWithUuid extends IEvent { Map result(); String eventId(); } diff --git a/src/main/java/org/toop/eventbus/events/Events.java b/src/main/java/org/toop/eventbus/events/Events.java index b83c3b0..3bbba53 100644 --- a/src/main/java/org/toop/eventbus/events/Events.java +++ b/src/main/java/org/toop/eventbus/events/Events.java @@ -1,11 +1,11 @@ package org.toop.eventbus.events; +import org.apache.logging.log4j.core.jmx.Server; + import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.concurrent.CompletableFuture; -import org.toop.core.Window; - /** Events that are used in the GlobalEventBus class. */ public class Events implements IEvent { @@ -81,7 +81,7 @@ public class Events implements IEvent { public record RequestsAllServers(CompletableFuture future) {} /** Forces closing all active servers immediately. */ - public record ForceCloseAllServers() {} + public record ForceCloseAllServers() implements IEvent {} /** * Requests starting a server with a specific port and game type. @@ -89,7 +89,7 @@ public class Events implements IEvent { * @param port The port to open the server. * @param gameType Either "tictactoe" or ... */ - public record StartServer(int port, String gameType) {} + public record StartServer(int port, String gameType) implements IEvent {} /** * BLOCKING Requests starting a server with a specific port and game type, and returns a @@ -100,7 +100,7 @@ public class Events implements IEvent { * @param future The uuid of the server. */ public record StartServerRequest( - int port, String gameType, CompletableFuture future) {} + int port, String gameType, CompletableFuture future) implements IEvent{} /** * Represents a server that has successfully started. @@ -108,7 +108,7 @@ public class Events implements IEvent { * @param uuid The unique identifier of the server. * @param port The port the server is listening on. */ - public record ServerStarted(String uuid, int port) {} + public record ServerStarted(String uuid, int port) implements IEvent {} /** * BLOCKING Requests creation of a TicTacToe game on a specific server. @@ -122,7 +122,7 @@ public class Events implements IEvent { String serverUuid, String playerA, String playerB, - CompletableFuture future) {} + CompletableFuture future) implements IEvent {} /** * Requests running a TicTacToe game on a specific server. @@ -130,7 +130,7 @@ public class Events implements IEvent { * @param serverUuid The unique identifier of the server. * @param gameUuid The UUID of the game to run. */ - public record RunTicTacToeGame(String serverUuid, String gameUuid) {} + public record RunTicTacToeGame(String serverUuid, String gameUuid) implements IEvent {} /** * Requests ending a TicTacToe game on a specific server. @@ -138,7 +138,7 @@ public class Events implements IEvent { * @param serverUuid The UUID of the server the game is running on. * @param gameUuid The UUID of the game to end. */ - public record EndTicTacToeGame(String serverUuid, String gameUuid) {} + public record EndTicTacToeGame(String serverUuid, String gameUuid) implements IEvent {} // public record StartGameConnectionRequest(String ip, String port, // CompletableFuture future) {} @@ -165,13 +165,13 @@ public class Events implements IEvent { public static class WindowEvents { /** Triggers when the window wants to quit. */ - public record OnQuitRequested() {} + public record OnQuitRequested() implements IEvent {} /** Triggers when the window is resized. */ - public record OnResize(Window.Size size) {} +// public record OnResize(Window.Size size) {} /** Triggers when the mouse is moved within the window. */ - public record OnMouseMove(int x, int y) {} + public record OnMouseMove(int x, int y) implements IEvent {} /** Triggers when the mouse is clicked within the window. */ public record OnMouseClick(int button) {} diff --git a/src/main/java/org/toop/eventbus/events/NetworkEvents.java b/src/main/java/org/toop/eventbus/events/NetworkEvents.java index ae75805..1a3dd56 100644 --- a/src/main/java/org/toop/eventbus/events/NetworkEvents.java +++ b/src/main/java/org/toop/eventbus/events/NetworkEvents.java @@ -1,12 +1,12 @@ package org.toop.eventbus.events; -import com.google.common.base.Supplier; import org.toop.backend.tictactoe.TicTacToeServer; import org.toop.frontend.networking.NetworkingGameClientHandler; import java.lang.reflect.RecordComponent; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -18,14 +18,14 @@ public class NetworkEvents extends Events { * * @param future List of all connections in string form. */ - public record RequestsAllClients(CompletableFuture future) {} + public record RequestsAllClients(CompletableFuture future) implements IEvent {} /** Forces closing all active connections immediately. */ - public record ForceCloseAllClients() {} + public record ForceCloseAllClients() implements IEvent {} public record CloseClientRequest(CompletableFuture future) {} - public record CloseClient(String connectionId) {} + public record CloseClient(String connectionId) implements IEvent {} /** * Event to start a new client connection to a server. @@ -102,15 +102,14 @@ public class NetworkEvents extends Events { */ public record StartClientRequest( Supplier handlerFactory, - String ip, int port, CompletableFuture future) {} + String ip, int port, CompletableFuture future) implements IEvent {} /** - * BLOCKING Triggers starting a server connection and returns a future. * - * @param ip The IP address of the server to connect to. - * @param port The port of the server to connect to. + * @param clientId The ID of the client to be used in requests. + * @param eventId The eventID used in checking if event is for you. */ - public record StartClientSuccess(Object connectionId, String ip, int port, String eventId) + public record StartClientSuccess(String clientId, String eventId) implements EventWithUuid { @Override public Map result() { @@ -139,7 +138,7 @@ public class NetworkEvents extends Events { * @param connectionId The UUID of the connection to send the command on. * @param args The command arguments. */ - public record SendCommand(String connectionId, String... args) {} + public record SendCommand(String connectionId, String... args) implements IEvent {} /** * WIP Triggers when a command is sent to a server. @@ -165,7 +164,7 @@ public class NetworkEvents extends Events { * @param ConnectionUuid The UUID of the connection that received the message. * @param message The message received. */ - public record ReceivedMessage(String ConnectionUuid, String message) {} + public record ReceivedMessage(String ConnectionUuid, String message) implements IEvent {} /** * Triggers changing connection to a new address. diff --git a/src/main/java/org/toop/eventbus/events/ServerEvents.java b/src/main/java/org/toop/eventbus/events/ServerEvents.java new file mode 100644 index 0000000..ccf02c1 --- /dev/null +++ b/src/main/java/org/toop/eventbus/events/ServerEvents.java @@ -0,0 +1,4 @@ +package org.toop.eventbus.events; + +public class ServerEvents { +} diff --git a/src/main/java/org/toop/frontend/UI/RemoteGameSelector.java b/src/main/java/org/toop/frontend/UI/RemoteGameSelector.java index ae363f6..2184c34 100644 --- a/src/main/java/org/toop/frontend/UI/RemoteGameSelector.java +++ b/src/main/java/org/toop/frontend/UI/RemoteGameSelector.java @@ -85,23 +85,23 @@ public class RemoteGameSelector { throw new RuntimeException(ex); } // TODO: Better error handling to not crash the system. - GlobalEventBus.subscribeAndRegister( - NetworkEvents.ReceivedMessage.class, - event -> { - if (event.message().equalsIgnoreCase("ok")) { - logger.info("received ok from server."); - } else if (event.message().toLowerCase().startsWith("gameid")) { - String gameId = - event.message() - .toLowerCase() - .replace("gameid ", ""); - GlobalEventBus.post( - new NetworkEvents.SendCommand( - "start_game " + gameId)); - } else { - logger.info("{}", event.message()); - } - }); +// GlobalEventBus.subscribeAndRegister( +// NetworkEvents.ReceivedMessage.class, +// event -> { +// if (event.message().equalsIgnoreCase("ok")) { +// logger.info("received ok from server."); +// } else if (event.message().toLowerCase().startsWith("gameid")) { +// String gameId = +// event.message() +// .toLowerCase() +// .replace("gameid ", ""); +// GlobalEventBus.post( +// new NetworkEvents.SendCommand( +// "start_game " + gameId)); +// } else { +// logger.info("{}", event.message()); +// } +// }); GlobalEventBus.post( new NetworkEvents.SendCommand( diff --git a/src/main/java/org/toop/frontend/games/LocalTicTacToe.java b/src/main/java/org/toop/frontend/games/LocalTicTacToe.java index d14d0bb..fab148f 100644 --- a/src/main/java/org/toop/frontend/games/LocalTicTacToe.java +++ b/src/main/java/org/toop/frontend/games/LocalTicTacToe.java @@ -1,8 +1,11 @@ package org.toop.frontend.games; import java.util.concurrent.*; + +import jdk.jfr.Event; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.eventbus.EventPublisher; import org.toop.eventbus.events.Events; import org.toop.eventbus.GlobalEventBus; import org.toop.eventbus.events.NetworkEvents; @@ -11,6 +14,7 @@ import org.toop.frontend.networking.NetworkingGameClientHandler; import org.toop.game.tictactoe.GameBase; import org.toop.game.tictactoe.TicTacToe; import org.toop.game.tictactoe.ai.MinMaxTicTacToe; +import java.util.function.Supplier; /** * A representation of a local tic-tac-toe game. Calls are made to a server for information about @@ -66,9 +70,9 @@ public class LocalTicTacToe { // TODO: Implement runnable * @param port The port of the server to connect to. */ private LocalTicTacToe(String ip, int port) { - this.receivedMessageListener = - GlobalEventBus.subscribe(this::receiveMessageAction); - GlobalEventBus.register(this.receivedMessageListener); +// this.receivedMessageListener = +// GlobalEventBus.subscribe(this::receiveMessageAction); +// GlobalEventBus.subscribe(this.receivedMessageListener); this.connectionId = this.createConnection(ip, port); this.createGame("X", "O"); this.isLocal = false; @@ -100,8 +104,8 @@ public class LocalTicTacToe { // TODO: Implement runnable private String createServer(int port) { CompletableFuture serverIdFuture = new CompletableFuture<>(); - GlobalEventBus.post( - new Events.ServerEvents.StartServerRequest(port, "tictactoe", serverIdFuture)); + new EventPublisher<>(Events.ServerEvents.StartServerRequest.class, port, "tictactoe", serverIdFuture) + .postEvent(); try { return serverIdFuture.get(); } catch (Exception e) { @@ -112,12 +116,9 @@ public class LocalTicTacToe { // TODO: Implement runnable private String createConnection(String ip, int port) { CompletableFuture connectionIdFuture = new CompletableFuture<>(); - GlobalEventBus.post( - new NetworkEvents.StartClientRequest( - NetworkingGameClientHandler::new, - ip, - port, - connectionIdFuture)); // TODO: what if server couldn't be started with port. + new EventPublisher<>(NetworkEvents.StartClientRequest.class, + (Supplier) NetworkingGameClientHandler::new, + ip, port, connectionIdFuture).postEvent(); // TODO: what if server couldn't be started with port. try { return connectionIdFuture.get(); } catch (InterruptedException | ExecutionException e) { @@ -231,7 +232,7 @@ public class LocalTicTacToe { // TODO: Implement runnable private void endTheGame() { this.sendCommand("end_game", this.gameId); - this.endListeners(); +// this.endListeners(); } private void receiveMessageAction(NetworkEvents.ReceivedMessage receivedMessage) { @@ -249,12 +250,12 @@ public class LocalTicTacToe { // TODO: Implement runnable } private void sendCommand(String... args) { - GlobalEventBus.post(new NetworkEvents.SendCommand(this.connectionId, args)); + new EventPublisher<>(NetworkEvents.SendCommand.class, this.connectionId, args).postEvent(); } - private void endListeners() { - GlobalEventBus.unregister(this.receivedMessageListener); - } +// private void endListeners() { +// GlobalEventBus.unregister(this.receivedMessageListener); +// } TODO public void setUIReference(UIGameBoard uiGameBoard) { this.ui = uiGameBoard; diff --git a/src/main/java/org/toop/frontend/graphics/Renderer.java b/src/main/java/org/toop/frontend/graphics/Renderer.java index 2c4d7b8..f626494 100644 --- a/src/main/java/org/toop/frontend/graphics/Renderer.java +++ b/src/main/java/org/toop/frontend/graphics/Renderer.java @@ -1,50 +1,50 @@ -package org.toop.frontend.graphics; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.frontend.platform.graphics.opengl.OpenglRenderer; - -public abstract class Renderer { - public enum API { - NONE, - OPENGL, - }; - - protected static final Logger logger = LogManager.getLogger(Renderer.class); - - private static API api = API.NONE; - private static Renderer instance = null; - - public static Renderer setup(API api) { - if (instance != null) { - logger.warn("Renderer is already setup."); - return instance; - } - - switch (api) { - case OPENGL: - instance = new OpenglRenderer(); - break; - - default: - logger.fatal("No valid renderer api chosen"); - return null; - } - - Renderer.api = api; - return instance; - } - - public static API getApi() { - return api; - } - - public void cleanup() { - instance = null; - logger.info("Renderer cleanup."); - } - - public abstract void clear(); - - public abstract void render(); -} +//package org.toop.frontend.graphics; +// +//import org.apache.logging.log4j.LogManager; +//import org.apache.logging.log4j.Logger; +//import org.toop.frontend.platform.graphics.opengl.OpenglRenderer; +// +//public abstract class Renderer { +// public enum API { +// NONE, +// OPENGL, +// }; +// +// protected static final Logger logger = LogManager.getLogger(Renderer.class); +// +// private static API api = API.NONE; +// private static Renderer instance = null; +// +// public static Renderer setup(API api) { +// if (instance != null) { +// logger.warn("Renderer is already setup."); +// return instance; +// } +// +// switch (api) { +// case OPENGL: +// instance = new OpenglRenderer(); +// break; +// +// default: +// logger.fatal("No valid renderer api chosen"); +// return null; +// } +// +// Renderer.api = api; +// return instance; +// } +// +// public static API getApi() { +// return api; +// } +// +// public void cleanup() { +// instance = null; +// logger.info("Renderer cleanup."); +// } +// +// public abstract void clear(); +// +// public abstract void render(); +//} diff --git a/src/main/java/org/toop/frontend/graphics/Shader.java b/src/main/java/org/toop/frontend/graphics/Shader.java index cd8516f..e884151 100644 --- a/src/main/java/org/toop/frontend/graphics/Shader.java +++ b/src/main/java/org/toop/frontend/graphics/Shader.java @@ -1,27 +1,27 @@ -package org.toop.frontend.graphics; - -import org.toop.frontend.platform.graphics.opengl.OpenglShader; - -public abstract class Shader { - public static Shader create(String vertexPath, String fragmentPath) { - Shader shader = null; - - switch (Renderer.getApi()) { - case OPENGL: - shader = new OpenglShader(vertexPath, fragmentPath); - break; - - case NONE: - default: - break; - } - - return shader; - } - - public abstract void cleanup(); - - public abstract void start(); - - public abstract void stop(); -} +//package org.toop.frontend.graphics; +// +//import org.toop.frontend.platform.graphics.opengl.OpenglShader; +// +//public abstract class Shader { +// public static Shader create(String vertexPath, String fragmentPath) { +// Shader shader = null; +// +// switch (Renderer.getApi()) { +// case OPENGL: +// shader = new OpenglShader(vertexPath, fragmentPath); +// break; +// +// case NONE: +// default: +// break; +// } +// +// return shader; +// } +// +// public abstract void cleanup(); +// +// public abstract void start(); +// +// public abstract void stop(); +//} diff --git a/src/main/java/org/toop/frontend/graphics/node/NodeManager.java b/src/main/java/org/toop/frontend/graphics/node/NodeManager.java index 9bbef84..afc17e7 100644 --- a/src/main/java/org/toop/frontend/graphics/node/NodeManager.java +++ b/src/main/java/org/toop/frontend/graphics/node/NodeManager.java @@ -1,68 +1,68 @@ -package org.toop.frontend.graphics.node; - -import java.util.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.eventbus.*; -import org.toop.eventbus.events.Events; -import org.toop.frontend.graphics.Shader; - -public class NodeManager { - private static final Logger logger = LogManager.getLogger(NodeManager.class); - - private static NodeManager instance = null; - - public static NodeManager setup() { - if (instance != null) { - logger.warn("NodeManager is already setup."); - return instance; - } - - instance = new NodeManager(); - return instance; - } - - private Shader shader; - private ArrayList nodes; - private Node active; - - private NodeManager() { - shader = - Shader.create( - "src/main/resources/shaders/gui_vertex.glsl", - "src/main/resources/shaders/gui_fragment.glsl"); - - nodes = new ArrayList(); - - GlobalEventBus.subscribeAndRegister( - Events.WindowEvents.OnMouseMove.class, - event -> { - for (int i = 0; i < nodes.size(); i++) { - Node node = nodes.get(i); - - if (node.check(event.x(), event.y())) { - active = node; - node.hover(); - - break; - } - } - }); - - GlobalEventBus.subscribeAndRegister( - Events.WindowEvents.OnMouseClick.class, - event -> { - if (active != null) { - active.click(); - } - }); - } - - public void cleanup() {} - - public void add(Node node) { - nodes.add(node); - } - - public void render() {} -} +//package org.toop.frontend.graphics.node; +// +//import java.util.*; +//import org.apache.logging.log4j.LogManager; +//import org.apache.logging.log4j.Logger; +//import org.toop.eventbus.*; +//import org.toop.eventbus.events.Events; +//import org.toop.frontend.graphics.Shader; +// +//public class NodeManager { +// private static final Logger logger = LogManager.getLogger(NodeManager.class); +// +// private static NodeManager instance = null; +// +// public static NodeManager setup() { +// if (instance != null) { +// logger.warn("NodeManager is already setup."); +// return instance; +// } +// +// instance = new NodeManager(); +// return instance; +// } +// +// private Shader shader; +// private ArrayList nodes; +// private Node active; +// +// private NodeManager() { +// shader = +// Shader.create( +// "src/main/resources/shaders/gui_vertex.glsl", +// "src/main/resources/shaders/gui_fragment.glsl"); +// +// nodes = new ArrayList(); +// +// GlobalEventBus.subscribeAndRegister( +// Events.WindowEvents.OnMouseMove.class, +// event -> { +// for (int i = 0; i < nodes.size(); i++) { +// Node node = nodes.get(i); +// +// if (node.check(event.x(), event.y())) { +// active = node; +// node.hover(); +// +// break; +// } +// } +// }); +// +// GlobalEventBus.subscribeAndRegister( +// Events.WindowEvents.OnMouseClick.class, +// event -> { +// if (active != null) { +// active.click(); +// } +// }); +// } +// +// public void cleanup() {} +// +// public void add(Node node) { +// nodes.add(node); +// } +// +// public void render() {} +//} diff --git a/src/main/java/org/toop/frontend/networking/NetworkingClient.java b/src/main/java/org/toop/frontend/networking/NetworkingClient.java index 995ae86..4982053 100644 --- a/src/main/java/org/toop/frontend/networking/NetworkingClient.java +++ b/src/main/java/org/toop/frontend/networking/NetworkingClient.java @@ -1,6 +1,5 @@ package org.toop.frontend.networking; -import com.google.common.base.Supplier; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioIoHandler; @@ -11,6 +10,8 @@ import io.netty.handler.codec.string.StringDecoder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.function.Supplier; + public class NetworkingClient { private static final Logger logger = LogManager.getLogger(NetworkingClient.class); diff --git a/src/main/java/org/toop/frontend/networking/NetworkingClientManager.java b/src/main/java/org/toop/frontend/networking/NetworkingClientManager.java index 9610976..81c47e7 100644 --- a/src/main/java/org/toop/frontend/networking/NetworkingClientManager.java +++ b/src/main/java/org/toop/frontend/networking/NetworkingClientManager.java @@ -1,16 +1,12 @@ package org.toop.frontend.networking; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; -import com.google.common.base.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.eventbus.EventPublisher; -import org.toop.eventbus.events.Events; import org.toop.eventbus.GlobalEventBus; import org.toop.eventbus.events.NetworkEvents; @@ -23,21 +19,17 @@ public class NetworkingClientManager { /** Starts a connection manager, to manage, connections. */ public NetworkingClientManager() { - GlobalEventBus.subscribeAndRegister(this::handleStartClientRequest); - GlobalEventBus.subscribeAndRegister(this::handleStartClient); - GlobalEventBus.subscribeAndRegister(this::handleCommand); - GlobalEventBus.subscribeAndRegister(this::handleCloseClient); -// GlobalEventBus.subscribeAndRegister( -// Events.ServerEvents.Reconnect.class, this::handleReconnect); - // GlobalEventBus.subscribeAndRegister(Events.ServerEvents.ChangeConnection.class, - // this::handleChangeConnection); - GlobalEventBus.subscribeAndRegister(this::shutdownAll); - GlobalEventBus.subscribeAndRegister(this::getAllConnections); + new EventPublisher<>(NetworkEvents.StartClientRequest.class, this::handleStartClientRequest); + new EventPublisher<>(NetworkEvents.StartClient.class, this::handleStartClient); + new EventPublisher<>(NetworkEvents.SendCommand.class, this::handleCommand); + new EventPublisher<>(NetworkEvents.CloseClient.class, this::handleCloseClient); + new EventPublisher<>(NetworkEvents.RequestsAllClients.class, this::getAllConnections); + new EventPublisher<>(NetworkEvents.ForceCloseAllClients.class, this::shutdownAll); } - private String startConnectionRequest(Supplier handlerFactory, - String ip, - int port) { + private String startClientRequest(Supplier handlerFactory, + String ip, + int port) { String connectionUuid = UUID.randomUUID().toString(); try { NetworkingClient client = new NetworkingClient( @@ -48,29 +40,24 @@ public class NetworkingClientManager { } catch (Exception e) { logger.error(e); } + logger.info("Client {} started", connectionUuid); return connectionUuid; } private void handleStartClientRequest(NetworkEvents.StartClientRequest request) { request.future() .complete( - this.startConnectionRequest( + this.startClientRequest( request.handlerFactory(), request.ip(), request.port())); // TODO: Maybe post ConnectionEstablished event. } private void handleStartClient(NetworkEvents.StartClient event) { - GlobalEventBus.post( - new NetworkEvents.StartClientSuccess( - this.startConnectionRequest( - event.handlerFactory(), - event.ip(), - event.port()), - event.ip(), - event.port(), - event.eventId() - )); + String uuid = this.startClientRequest(event.handlerFactory(), event.ip(), event.port()); + new EventPublisher<>(NetworkEvents.StartClientSuccess.class, + uuid, event.eventId() + ).asyncPostEvent(); } private void handleCommand( diff --git a/src/main/java/org/toop/frontend/networking/NetworkingGameClientHandler.java b/src/main/java/org/toop/frontend/networking/NetworkingGameClientHandler.java index b7362fa..8decbd4 100644 --- a/src/main/java/org/toop/frontend/networking/NetworkingGameClientHandler.java +++ b/src/main/java/org/toop/frontend/networking/NetworkingGameClientHandler.java @@ -1,11 +1,9 @@ package org.toop.frontend.networking; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.Main; public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = LogManager.getLogger(NetworkingGameClientHandler.class); diff --git a/src/main/java/org/toop/frontend/networking/ServerConnection.java b/src/main/java/org/toop/frontend/networking/ServerConnection.java index 1f01075..820ed03 100644 --- a/src/main/java/org/toop/frontend/networking/ServerConnection.java +++ b/src/main/java/org/toop/frontend/networking/ServerConnection.java @@ -5,6 +5,7 @@ import java.net.InetAddress; import java.util.concurrent.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.toop.eventbus.EventPublisher; import org.toop.eventbus.events.Events; import org.toop.eventbus.GlobalEventBus; import org.toop.eventbus.events.NetworkEvents; @@ -85,9 +86,7 @@ public final class ServerConnection extends TcpClient implements Runnable { if (received != null) { logger.info("Connection: {} received: '{}'", this.uuid, received); // this.addReceivedMessageToQueue(received); // TODO: Will never go empty - GlobalEventBus.post( - new NetworkEvents.ReceivedMessage( - this.uuid, received)); // TODO: mb change + new EventPublisher<>(NetworkEvents.ReceivedMessage.class, this.uuid, received).postEvent(); } else { break; } diff --git a/src/main/java/org/toop/frontend/platform/core/glfw/GlfwWindow.java b/src/main/java/org/toop/frontend/platform/core/glfw/GlfwWindow.java index b83ed52..ac494a2 100644 --- a/src/main/java/org/toop/frontend/platform/core/glfw/GlfwWindow.java +++ b/src/main/java/org/toop/frontend/platform/core/glfw/GlfwWindow.java @@ -1,109 +1,109 @@ -package org.toop.frontend.platform.core.glfw; - -import org.lwjgl.glfw.*; -import org.lwjgl.system.*; -import org.toop.core.*; -import org.toop.eventbus.*; -import org.toop.eventbus.events.Events; - -public class GlfwWindow extends Window { - private long window; - - public GlfwWindow(String title, Size size) { - if (!GLFW.glfwInit()) { - logger.fatal("Failed to initialize glfw"); - return; - } - - GLFW.glfwDefaultWindowHints(); - GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); - GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE); - - GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()); - - int width = size.width(); - int height = size.height(); - - if (width <= 0 || height <= 0 || width > videoMode.width() || height > videoMode.height()) { - width = videoMode.width(); - height = videoMode.height(); - - GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, GLFW.GLFW_TRUE); - } - - long window = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL); - - if (window == MemoryUtil.NULL) { - GLFW.glfwTerminate(); - - logger.fatal("Failed to create glfw window"); - return; - } - - int[] widthBuffer = new int[1]; - int[] heightBuffer = new int[1]; - GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer); - - GLFW.glfwMakeContextCurrent(window); - GLFW.glfwSwapInterval(1); - - GLFW.glfwSetWindowCloseCallback( - window, - (lwindow) -> { - GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested()); - }); - - GLFW.glfwSetFramebufferSizeCallback( - window, - (lwindow, lwidth, lheight) -> { - GlobalEventBus.post( - new Events.WindowEvents.OnResize(new Size(lwidth, lheight))); - }); - - GLFW.glfwSetCursorPosCallback( - window, - (lwindow, lx, ly) -> { - GlobalEventBus.post(new Events.WindowEvents.OnMouseMove((int) lx, (int) ly)); - }); - - GLFW.glfwSetMouseButtonCallback( - window, - (lwindow, lbutton, laction, lmods) -> { - switch (laction) { - case GLFW.GLFW_PRESS: - GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton)); - break; - - case GLFW.GLFW_RELEASE: - GlobalEventBus.post(new Events.WindowEvents.OnMouseRelease(lbutton)); - break; - - default: - break; - } - }); - - this.window = window; - GLFW.glfwShowWindow(window); - - logger.info( - "Glfw window setup. Title: {}. Width: {}. Height: {}.", - title, - size.width(), - size.height()); - } - - @Override - public void cleanup() { - GLFW.glfwDestroyWindow(window); - GLFW.glfwTerminate(); - - super.cleanup(); - } - - @Override - public void update() { - GLFW.glfwSwapBuffers(window); - GLFW.glfwPollEvents(); - } -} +//package org.toop.frontend.platform.core.glfw; +// +//import org.lwjgl.glfw.*; +//import org.lwjgl.system.*; +//import org.toop.core.*; +//import org.toop.eventbus.*; +//import org.toop.eventbus.events.Events; +// +//public class GlfwWindow extends Window { +// private long window; +// +// public GlfwWindow(String title, Size size) { +// if (!GLFW.glfwInit()) { +// logger.fatal("Failed to initialize glfw"); +// return; +// } +// +// GLFW.glfwDefaultWindowHints(); +// GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); +// GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_TRUE); +// +// GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()); +// +// int width = size.width(); +// int height = size.height(); +// +// if (width <= 0 || height <= 0 || width > videoMode.width() || height > videoMode.height()) { +// width = videoMode.width(); +// height = videoMode.height(); +// +// GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, GLFW.GLFW_TRUE); +// } +// +// long window = GLFW.glfwCreateWindow(width, height, title, MemoryUtil.NULL, MemoryUtil.NULL); +// +// if (window == MemoryUtil.NULL) { +// GLFW.glfwTerminate(); +// +// logger.fatal("Failed to create glfw window"); +// return; +// } +// +// int[] widthBuffer = new int[1]; +// int[] heightBuffer = new int[1]; +// GLFW.glfwGetWindowSize(window, widthBuffer, heightBuffer); +// +// GLFW.glfwMakeContextCurrent(window); +// GLFW.glfwSwapInterval(1); +// +// GLFW.glfwSetWindowCloseCallback( +// window, +// (lwindow) -> { +// GlobalEventBus.post(new Events.WindowEvents.OnQuitRequested()); +// }); +// +// GLFW.glfwSetFramebufferSizeCallback( +// window, +// (lwindow, lwidth, lheight) -> { +// GlobalEventBus.post( +// new Events.WindowEvents.OnResize(new Size(lwidth, lheight))); +// }); +// +// GLFW.glfwSetCursorPosCallback( +// window, +// (lwindow, lx, ly) -> { +// GlobalEventBus.post(new Events.WindowEvents.OnMouseMove((int) lx, (int) ly)); +// }); +// +// GLFW.glfwSetMouseButtonCallback( +// window, +// (lwindow, lbutton, laction, lmods) -> { +// switch (laction) { +// case GLFW.GLFW_PRESS: +// GlobalEventBus.post(new Events.WindowEvents.OnMouseClick(lbutton)); +// break; +// +// case GLFW.GLFW_RELEASE: +// GlobalEventBus.post(new Events.WindowEvents.OnMouseRelease(lbutton)); +// break; +// +// default: +// break; +// } +// }); +// +// this.window = window; +// GLFW.glfwShowWindow(window); +// +// logger.info( +// "Glfw window setup. Title: {}. Width: {}. Height: {}.", +// title, +// size.width(), +// size.height()); +// } +// +// @Override +// public void cleanup() { +// GLFW.glfwDestroyWindow(window); +// GLFW.glfwTerminate(); +// +// super.cleanup(); +// } +// +// @Override +// public void update() { +// GLFW.glfwSwapBuffers(window); +// GLFW.glfwPollEvents(); +// } +//} diff --git a/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglRenderer.java b/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglRenderer.java index 911ec64..020ff22 100644 --- a/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglRenderer.java +++ b/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglRenderer.java @@ -1,79 +1,79 @@ -package org.toop.frontend.platform.graphics.opengl; - -import org.lwjgl.opengl.*; -import org.lwjgl.system.*; -import org.toop.eventbus.*; -import org.toop.eventbus.events.Events; -import org.toop.frontend.graphics.Renderer; -import org.toop.frontend.graphics.Shader; - -public class OpenglRenderer extends Renderer { - private Shader shader; - private int vao; - - public OpenglRenderer() { - GL.createCapabilities(); - GL45.glClearColor(0.65f, 0.9f, 0.65f, 1f); - - GlobalEventBus.subscribeAndRegister( - Events.WindowEvents.OnResize.class, - event -> { - GL45.glViewport(0, 0, event.size().width(), event.size().height()); - }); - - logger.info("Opengl renderer setup."); - - // Form here on, everything is temporary - float vertices[] = { - -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, - }; - - int indicies[] = { - 0, 1, 2, - 2, 3, 0, - }; - - vao = GL45.glCreateVertexArrays(); - GL45.glBindVertexArray(vao); - - int vbo = GL45.glCreateBuffers(); - GL45.glBindBuffer(GL45.GL_ARRAY_BUFFER, vbo); - GL45.glBufferData(GL45.GL_ARRAY_BUFFER, vertices, GL45.GL_STATIC_DRAW); - - GL45.glVertexAttribPointer(0, 2, GL45.GL_FLOAT, false, 5 * 4, 0); - GL45.glVertexAttribPointer(1, 3, GL45.GL_FLOAT, false, 5 * 4, 2 * 4); - - GL45.glEnableVertexAttribArray(0); - GL45.glEnableVertexAttribArray(1); - - int ib = GL45.glCreateBuffers(); - GL45.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib); - GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW); - - shader = - Shader.create( - "src/main/resources/shaders/gui_vertex.glsl", - "src/main/resources/shaders/gui_fragment.glsl"); - } - - @Override - public void cleanup() { - super.cleanup(); - } - - @Override - public void clear() { - GL45.glClear(GL45.GL_COLOR_BUFFER_BIT); - } - - @Override - public void render() { - // temporary - // shader.start(); - GL45.glBindVertexArray(vao); - GL45.glDrawElements(GL45.GL_TRIANGLES, 6, GL45.GL_UNSIGNED_INT, MemoryUtil.NULL); - } -} +//package org.toop.frontend.platform.graphics.opengl; +// +//import org.lwjgl.opengl.*; +//import org.lwjgl.system.*; +//import org.toop.eventbus.*; +//import org.toop.eventbus.events.Events; +//import org.toop.frontend.graphics.Renderer; +//import org.toop.frontend.graphics.Shader; +// +//public class OpenglRenderer extends Renderer { +// private Shader shader; +// private int vao; +// +// public OpenglRenderer() { +// GL.createCapabilities(); +// GL45.glClearColor(0.65f, 0.9f, 0.65f, 1f); +// +// GlobalEventBus.subscribeAndRegister( +// Events.WindowEvents.OnResize.class, +// event -> { +// GL45.glViewport(0, 0, event.size().width(), event.size().height()); +// }); +// +// logger.info("Opengl renderer setup."); +// +// // Form here on, everything is temporary +// float vertices[] = { +// -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, +// -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, +// 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, +// 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, +// }; +// +// int indicies[] = { +// 0, 1, 2, +// 2, 3, 0, +// }; +// +// vao = GL45.glCreateVertexArrays(); +// GL45.glBindVertexArray(vao); +// +// int vbo = GL45.glCreateBuffers(); +// GL45.glBindBuffer(GL45.GL_ARRAY_BUFFER, vbo); +// GL45.glBufferData(GL45.GL_ARRAY_BUFFER, vertices, GL45.GL_STATIC_DRAW); +// +// GL45.glVertexAttribPointer(0, 2, GL45.GL_FLOAT, false, 5 * 4, 0); +// GL45.glVertexAttribPointer(1, 3, GL45.GL_FLOAT, false, 5 * 4, 2 * 4); +// +// GL45.glEnableVertexAttribArray(0); +// GL45.glEnableVertexAttribArray(1); +// +// int ib = GL45.glCreateBuffers(); +// GL45.glBindBuffer(GL45.GL_ELEMENT_ARRAY_BUFFER, ib); +// GL45.glBufferData(GL45.GL_ELEMENT_ARRAY_BUFFER, indicies, GL45.GL_STATIC_DRAW); +// +// shader = +// Shader.create( +// "src/main/resources/shaders/gui_vertex.glsl", +// "src/main/resources/shaders/gui_fragment.glsl"); +// } +// +// @Override +// public void cleanup() { +// super.cleanup(); +// } +// +// @Override +// public void clear() { +// GL45.glClear(GL45.GL_COLOR_BUFFER_BIT); +// } +// +// @Override +// public void render() { +// // temporary +// // shader.start(); +// GL45.glBindVertexArray(vao); +// GL45.glDrawElements(GL45.GL_TRIANGLES, 6, GL45.GL_UNSIGNED_INT, MemoryUtil.NULL); +// } +//} diff --git a/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglShader.java b/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglShader.java index b295ec7..e288300 100644 --- a/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglShader.java +++ b/src/main/java/org/toop/frontend/platform/graphics/opengl/OpenglShader.java @@ -1,57 +1,57 @@ -package org.toop.frontend.platform.graphics.opengl; - -import org.lwjgl.opengl.*; -import org.toop.core.*; -import org.toop.frontend.graphics.Shader; - -public class OpenglShader extends Shader { - private int programID; - - public OpenglShader(String vertexPath, String fragmentPath) { - FileSystem.File vertexSource = FileSystem.read(vertexPath); - FileSystem.File fragmentSource = FileSystem.read(fragmentPath); - - if (vertexSource == null || fragmentPath == null) { - return; - } - - programID = GL45.glCreateProgram(); - - int vertexShader = GL45.glCreateShader(GL45.GL_VERTEX_SHADER); - int fragmentShader = GL45.glCreateShader(GL45.GL_FRAGMENT_SHADER); - - GL45.glShaderSource(vertexShader, vertexSource.buffer()); - GL45.glShaderSource(fragmentShader, fragmentSource.buffer()); - - GL45.glCompileShader(vertexShader); - GL45.glCompileShader(fragmentShader); - - GL45.glAttachShader(programID, vertexShader); - GL45.glAttachShader(programID, fragmentShader); - - GL45.glLinkProgram(programID); - GL45.glValidateProgram(programID); - - GL45.glDetachShader(programID, vertexShader); - GL45.glDetachShader(programID, fragmentShader); - - GL45.glDeleteShader(vertexShader); - GL45.glDeleteShader(fragmentShader); - } - - @Override - public void cleanup() { - stop(); - GL45.glDeleteProgram(programID); - } - - @Override - public void start() { - GL45.glUseProgram(programID); - } - - @Override - public void stop() { - GL45.glUseProgram(0); - } -} +//package org.toop.frontend.platform.graphics.opengl; +// +//import org.lwjgl.opengl.*; +//import org.toop.core.*; +//import org.toop.frontend.graphics.Shader; +// +//public class OpenglShader extends Shader { +// private int programID; +// +// public OpenglShader(String vertexPath, String fragmentPath) { +// FileSystem.File vertexSource = FileSystem.read(vertexPath); +// FileSystem.File fragmentSource = FileSystem.read(fragmentPath); +// +// if (vertexSource == null || fragmentPath == null) { +// return; +// } +// +// programID = GL45.glCreateProgram(); +// +// int vertexShader = GL45.glCreateShader(GL45.GL_VERTEX_SHADER); +// int fragmentShader = GL45.glCreateShader(GL45.GL_FRAGMENT_SHADER); +// +// GL45.glShaderSource(vertexShader, vertexSource.buffer()); +// GL45.glShaderSource(fragmentShader, fragmentSource.buffer()); +// +// GL45.glCompileShader(vertexShader); +// GL45.glCompileShader(fragmentShader); +// +// GL45.glAttachShader(programID, vertexShader); +// GL45.glAttachShader(programID, fragmentShader); +// +// GL45.glLinkProgram(programID); +// GL45.glValidateProgram(programID); +// +// GL45.glDetachShader(programID, vertexShader); +// GL45.glDetachShader(programID, fragmentShader); +// +// GL45.glDeleteShader(vertexShader); +// GL45.glDeleteShader(fragmentShader); +// } +// +// @Override +// public void cleanup() { +// stop(); +// GL45.glDeleteProgram(programID); +// } +// +// @Override +// public void start() { +// GL45.glUseProgram(programID); +// } +// +// @Override +// public void stop() { +// GL45.glUseProgram(0); +// } +//} diff --git a/src/test/java/org/toop/eventbus/EventPublisherSpeedTest.java b/src/test/java/org/toop/eventbus/EventPublisherSpeedTest.java index 85d2691..4f2d65d 100644 --- a/src/test/java/org/toop/eventbus/EventPublisherSpeedTest.java +++ b/src/test/java/org/toop/eventbus/EventPublisherSpeedTest.java @@ -34,10 +34,10 @@ class EventPublisherPerformanceTest { @Test void testEventPostSpeed() { - int iterations = 10_000; + int iterations = 100_000; AtomicInteger counter = new AtomicInteger(0); - GlobalEventBus.subscribeAndRegister(PerfEvent.class, e -> counter.incrementAndGet()); + GlobalEventBus.subscribe(PerfEvent.class, e -> counter.incrementAndGet()); long start = System.nanoTime(); @@ -59,7 +59,7 @@ class EventPublisherPerformanceTest { int eventsPerThread = 5_000; AtomicInteger counter = new AtomicInteger(0); - GlobalEventBus.subscribeAndRegister(PerfEvent.class, e -> counter.incrementAndGet()); + GlobalEventBus.subscribe(PerfEvent.class, e -> counter.incrementAndGet()); Thread[] workers = new Thread[threads]; diff --git a/src/test/java/org/toop/eventbus/EventPublisherStressTest.java b/src/test/java/org/toop/eventbus/EventPublisherStressTest.java index c825ea7..513fc66 100644 --- a/src/test/java/org/toop/eventbus/EventPublisherStressTest.java +++ b/src/test/java/org/toop/eventbus/EventPublisherStressTest.java @@ -5,32 +5,49 @@ import org.junit.jupiter.api.Test; import org.toop.eventbus.events.EventWithUuid; import java.math.BigInteger; +import java.util.UUID; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; import static org.junit.jupiter.api.Assertions.assertEquals; class EventPublisherStressTest { + /** Top-level record to ensure runtime type matches subscription */ public record HeavyEvent(String payload, String eventId) implements EventWithUuid { @Override public java.util.Map result() { return java.util.Map.of("payload", payload, "eventId", eventId); } + + @Override + public String eventId() { + return eventId; + } } - private static final int THREADS = 1; - private static final long EVENTS_PER_THREAD = 2_000_000_000; + public record HeavyEventSuccess(String payload, String eventId) implements EventWithUuid { + @Override + public java.util.Map result() { + return java.util.Map.of("payload", payload, "eventId", eventId); + } + + @Override + public String eventId() { + return eventId; + } + } + + private static final int THREADS = 16; + private static final long EVENTS_PER_THREAD = 1_000_000_000; @Tag("stress") @Test - void extremeConcurrencyTest_progressWithMemory() throws InterruptedException { - AtomicLong counter = new AtomicLong(0); // Big numbers safety + void extremeConcurrencySendTest_progressWithMemory() throws InterruptedException { + LongAdder counter = new LongAdder(); ExecutorService executor = Executors.newFixedThreadPool(THREADS); - GlobalEventBus.subscribeAndRegister(HeavyEvent.class, _ -> counter.incrementAndGet()); - BigInteger totalEvents = BigInteger.valueOf(THREADS) .multiply(BigInteger.valueOf(EVENTS_PER_THREAD)); @@ -39,25 +56,22 @@ class EventPublisherStressTest { // Monitor thread for EPS and memory Thread monitor = new Thread(() -> { long lastCount = 0; - long lastTime = startTime; - + long lastTime = System.currentTimeMillis(); Runtime runtime = Runtime.getRuntime(); - while (counter.get() < totalEvents.longValue()) { - try { Thread.sleep(1000); } catch (InterruptedException ignored) {} + while (counter.sum() < totalEvents.longValue()) { + try { Thread.sleep(200); } catch (InterruptedException ignored) {} long now = System.currentTimeMillis(); - long completed = counter.get(); - long eventsThisSecond = completed - lastCount; - double eps = eventsThisSecond / ((now - lastTime) / 1000.0); + long completed = counter.sum(); + long eventsThisPeriod = completed - lastCount; + double eps = eventsThisPeriod / ((now - lastTime) / 1000.0); - // Memory usage long usedMemory = runtime.totalMemory() - runtime.freeMemory(); - long maxMemory = runtime.maxMemory(); - double usedPercent = usedMemory * 100.0 / maxMemory; + double usedPercent = usedMemory * 100.0 / runtime.maxMemory(); System.out.printf( - "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)\n", + "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n", completed, totalEvents.longValue(), completed * 100.0 / totalEvents.doubleValue(), @@ -73,17 +87,22 @@ class EventPublisherStressTest { monitor.setDaemon(true); monitor.start(); - // Submit events + var listener = new EventPublisher<>(HeavyEvent.class, _ -> counter.increment()); + + // Submit events asynchronously for (int t = 0; t < THREADS; t++) { executor.submit(() -> { for (int i = 0; i < EVENTS_PER_THREAD; i++) { - new EventPublisher<>(HeavyEvent.class, "payload-" + i).postEvent(); + var _ = new EventPublisher<>(HeavyEvent.class, "payload-" + i) + .asyncPostEvent(); } }); } executor.shutdown(); - executor.awaitTermination(20, TimeUnit.MINUTES); // allow extra time for huge tests + executor.awaitTermination(10, TimeUnit.MINUTES); + + listener.getResult(); long endTime = System.currentTimeMillis(); double durationSeconds = (endTime - startTime) / 1000.0; @@ -92,13 +111,87 @@ class EventPublisherStressTest { double averageEps = totalEvents.doubleValue() / durationSeconds; System.out.printf("Average EPS: %.0f%n", averageEps); - assertEquals(totalEvents.longValue(), counter.get()); + assertEquals(totalEvents.longValue(), counter.sum()); } + @Tag("stress") + @Test + void extremeConcurrencySendAndReturnTest_progressWithMemory() throws InterruptedException { + LongAdder counter = new LongAdder(); + ExecutorService executor = Executors.newFixedThreadPool(THREADS); + + BigInteger totalEvents = BigInteger.valueOf(THREADS) + .multiply(BigInteger.valueOf(EVENTS_PER_THREAD)); + + long startTime = System.currentTimeMillis(); + + // Monitor thread for EPS and memory + Thread monitor = new Thread(() -> { + long lastCount = 0; + long lastTime = System.currentTimeMillis(); + Runtime runtime = Runtime.getRuntime(); + + while (counter.sum() < totalEvents.longValue()) { + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + + long now = System.currentTimeMillis(); + long completed = counter.sum(); + long eventsThisPeriod = completed - lastCount; + double eps = eventsThisPeriod / ((now - lastTime) / 1000.0); + + long usedMemory = runtime.totalMemory() - runtime.freeMemory(); + double usedPercent = usedMemory * 100.0 / runtime.maxMemory(); + + System.out.printf( + "Progress: %d/%d (%.2f%%), EPS: %.0f, Memory Used: %.2f MB (%.2f%%)%n", + completed, + totalEvents.longValue(), + completed * 100.0 / totalEvents.doubleValue(), + eps, + usedMemory / 1024.0 / 1024.0, + usedPercent + ); + + lastCount = completed; + lastTime = now; + } + }); + monitor.setDaemon(true); + monitor.start(); + + // Submit events asynchronously + for (int t = 0; t < THREADS; t++) { + executor.submit(() -> { + for (int i = 0; i < EVENTS_PER_THREAD; i++) { + var a = new EventPublisher<>(HeavyEvent.class, "payload-" + i) + .onEventById(HeavyEventSuccess.class, _ -> counter.increment()) + .unsubscribeAfterSuccess() + .asyncPostEvent(); + + new EventPublisher<>(HeavyEventSuccess.class, "payload-" + i, a.getEventId()) + .asyncPostEvent(); + } + }); + } + + executor.shutdown(); + executor.awaitTermination(10, TimeUnit.MINUTES); + + long endTime = System.currentTimeMillis(); + double durationSeconds = (endTime - startTime) / 1000.0; + + System.out.println("Posted " + totalEvents + " events in " + durationSeconds + " seconds"); + double averageEps = totalEvents.doubleValue() / durationSeconds; + System.out.printf("Average EPS: %.0f%n", averageEps); + + assertEquals(totalEvents.longValue(), counter.sum()); + } + + @Tag("stress") @Test void efficientExtremeConcurrencyTest() throws InterruptedException { - final int THREADS = Runtime.getRuntime().availableProcessors(); // threads ≈ CPU cores + final int THREADS = Runtime.getRuntime().availableProcessors(); final int EVENTS_PER_THREAD = 5000; ExecutorService executor = Executors.newFixedThreadPool(THREADS); @@ -128,11 +221,9 @@ class EventPublisherStressTest { System.out.printf("Posted %s events in %.3f seconds%n", totalEvents, durationSeconds); System.out.printf("Throughput: %.0f events/sec%n", eps); - // Memory snapshot Runtime rt = Runtime.getRuntime(); System.out.printf("Used memory: %.2f MB%n", (rt.totalMemory() - rt.freeMemory()) / 1024.0 / 1024.0); - // Ensure all events were processed assertEquals(totalEvents.intValue(), processedEvents.size()); } @@ -142,14 +233,12 @@ class EventPublisherStressTest { int iterations = 1_000_000; long startReflect = System.nanoTime(); for (int i = 0; i < iterations; i++) { - // Reflection every time HeavyEvent.class.getDeclaredConstructors()[0].newInstance("payload", "uuid-" + i); } long endReflect = System.nanoTime(); long startHandle = System.nanoTime(); for (int i = 0; i < iterations; i++) { - // Using cached MethodHandle EventPublisher ep = new EventPublisher<>(HeavyEvent.class, "payload-" + i); } long endHandle = System.nanoTime(); diff --git a/src/test/java/org/toop/eventbus/EventPublisherTest.java b/src/test/java/org/toop/eventbus/EventPublisherTest.java index cfba3f8..0fd37b1 100644 --- a/src/test/java/org/toop/eventbus/EventPublisherTest.java +++ b/src/test/java/org/toop/eventbus/EventPublisherTest.java @@ -1,14 +1,11 @@ package org.toop.eventbus; -import com.google.common.eventbus.EventBus; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.toop.eventbus.events.EventWithUuid; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; @@ -71,7 +68,7 @@ class EventPublisherTest { EventPublisher publisher = new EventPublisher<>(TestEvent.class, "event"); publisher.onEventById(TestEvent.class, event -> triggered.set(true)) - .unregisterAfterSuccess() + .unsubscribeAfterSuccess() .postEvent(); // Subscriber should have been removed after first trigger @@ -109,6 +106,13 @@ class EventPublisherTest { assertTrue(firstTriggered.get()); assertTrue(secondTriggered.get()); + + publisher.onEventById(TestEvent.class, e -> firstTriggered.set(true)) + .onEventById(TestEvent.class, e -> secondTriggered.set(true)) + .asyncPostEvent(); + + assertTrue(firstTriggered.get()); + assertTrue(secondTriggered.get()); } @Test diff --git a/src/test/java/org/toop/eventbus/GlobalEventBusTest.java b/src/test/java/org/toop/eventbus/GlobalEventBusTest.java index 9208453..4bc6b02 100644 --- a/src/test/java/org/toop/eventbus/GlobalEventBusTest.java +++ b/src/test/java/org/toop/eventbus/GlobalEventBusTest.java @@ -1,84 +1,110 @@ -package org.toop.eventbus; +//package org.toop.eventbus; +// +//import net.engio.mbassy.bus.publication.SyncAsyncPostCommand; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.Test; +//import org.toop.eventbus.events.IEvent; +// +//import java.util.concurrent.atomic.AtomicBoolean; +//import java.util.concurrent.atomic.AtomicReference; +// +//import static org.junit.jupiter.api.Assertions.*; +// +//class GlobalEventBusTest { +// +// // A simple test event +// static class TestEvent implements IEvent { +// private final String message; +// +// TestEvent(String message) { +// this.message = message; +// } +// +// String getMessage() { +// return message; +// } +// } +// +// @AfterEach +// void tearDown() { +// // Reset to avoid leaking subscribers between tests +// GlobalEventBus.reset(); +// } +// +// @Test +// void testSubscribeWithType() { +// AtomicReference result = new AtomicReference<>(); +// +// GlobalEventBus.subscribe(TestEvent.class, e -> result.set(e.getMessage())); +// +// GlobalEventBus.post(new TestEvent("hello")); +// +// assertEquals("hello", result.get()); +// } +// +// @Test +// void testSubscribeWithoutType() { +// AtomicReference result = new AtomicReference<>(); +// +// GlobalEventBus.subscribe((TestEvent e) -> result.set(e.getMessage())); +// +// GlobalEventBus.post(new TestEvent("world")); +// +// assertEquals("world", result.get()); +// } +// +// @Test +// void testUnsubscribeStopsReceivingEvents() { +// AtomicBoolean called = new AtomicBoolean(false); +// +// Object listener = GlobalEventBus.subscribe(TestEvent.class, e -> called.set(true)); +// +// // First event should trigger +// GlobalEventBus.post(new TestEvent("first")); +// assertTrue(called.get()); +// +// // Reset flag +// called.set(false); +// +// // Unsubscribe and post again +// GlobalEventBus.unsubscribe(listener); +// GlobalEventBus.post(new TestEvent("second")); +// +// assertFalse(called.get(), "Listener should not be called after unsubscribe"); +// } +// +// @Test +// void testResetClearsListeners() { +// AtomicBoolean called = new AtomicBoolean(false); +// +// GlobalEventBus.subscribe(TestEvent.class, e -> called.set(true)); +// +// GlobalEventBus.reset(); // should wipe subscriptions +// +// GlobalEventBus.post(new TestEvent("ignored")); +// +// assertFalse(called.get(), "Listener should not survive reset()"); +// } -import static org.junit.jupiter.api.Assertions.*; - -import com.google.common.eventbus.EventBus; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.*; - -class GlobalEventBusTest { - - @BeforeEach - void setup() { - // Reset the singleton before each test - GlobalEventBus.reset(); - } - - @AfterEach - void teardown() { - // Ensure reset after tests - GlobalEventBus.reset(); - } - - @Test - void testGet_returnsEventBus() { - EventBus bus = GlobalEventBus.get(); - assertNotNull(bus, "EventBus should not be null"); - assertEquals("global-bus", bus.identifier(), "EventBus name should match"); - } - - @Test - void testSet_replacesEventBus() { - EventBus newBus = new EventBus("new-bus"); - GlobalEventBus.set(newBus); - - assertEquals(newBus, GlobalEventBus.get(), "EventBus should be replaced"); - } - - @Test - void testSubscribe_wrapsConsumerAndHandlesEvent() { - AtomicBoolean called = new AtomicBoolean(false); - - var listener = GlobalEventBus.subscribe(String.class, _ -> called.set(true)); - GlobalEventBus.register(listener); - - GlobalEventBus.post("hello"); - - assertTrue(called.get(), "Consumer should have been called"); - } - - @Test - void testSubscribeAndRegister_registersListenerAutomatically() { - AtomicBoolean called = new AtomicBoolean(false); - - GlobalEventBus.subscribeAndRegister(String.class, _ -> called.set(true)); - GlobalEventBus.post("test-event"); - - assertTrue(called.get(), "Consumer should have been called"); - } - - @Test - void testUnregister_removesListener() { - AtomicBoolean called = new AtomicBoolean(false); - - var listener = GlobalEventBus.subscribe(String.class, _ -> called.set(true)); - GlobalEventBus.register(listener); - GlobalEventBus.unregister(listener); - - GlobalEventBus.post("hello"); - assertFalse(called.get(), "Consumer should not be called after unregister"); - } - - // @Test - // void testPost_storesEventInRegistry() { - // // Simple EventMeta check - // class MyEvent {} - // - // MyEvent event = new MyEvent(); - // GlobalEventBus.post(event); - // - // EventMeta stored = EventRegistry.getStoredEvent(MyEvent.class); - // assertNotNull(stored, "EventMeta should be stored"); - // assertEquals(event, stored.event(), "Stored event should match the posted one"); - // } -} +// @Test +// void testSetReplacesBus() { +// MBassadorMock mockBus = new MBassadorMock<>(); +// GlobalEventBus.set(mockBus); +// +// TestEvent event = new TestEvent("test"); +// GlobalEventBus.post(event); +// +// assertEquals(event, mockBus.lastPosted, "Custom bus should receive the event"); +// } +// +// // Minimal fake MBassador for verifying set() +// static class MBassadorMock extends net.engio.mbassy.bus.MBassador { +// T lastPosted; +// +// @Override +// public SyncAsyncPostCommand post(T message) { +// this.lastPosted = message; +// return super.post(message); +// } +// } +//}