From 314cce0b18ea4b3dcdd3390f63c38aa5d59a8827 Mon Sep 17 00:00:00 2001 From: RealMangoRage <64402114+RealMangorage@users.noreply.github.com> Date: Wed, 22 May 2024 11:36:59 -0700 Subject: [PATCH] Minor Optimizations for MangoBot --- .../org/mangorage/basicutils/misc/Lazy.java | 105 ++++++++++++++++++ .../mangorage/mboteventbus/base/EventBus.java | 7 +- .../mboteventbus/base/ListenerListImpl.java | 53 ++++++++- .../mboteventbus/impl/IListenerList.java | 8 +- .../mangorage/mangobot/test/EventBusTest.java | 62 +++++++++++ .../mangobot/test/misc/TimedTest.java | 35 ++++++ 6 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/mangorage/basicutils/misc/Lazy.java create mode 100644 src/test/java/org/mangorage/mangobot/test/EventBusTest.java create mode 100644 src/test/java/org/mangorage/mangobot/test/misc/TimedTest.java diff --git a/src/main/java/org/mangorage/basicutils/misc/Lazy.java b/src/main/java/org/mangorage/basicutils/misc/Lazy.java new file mode 100644 index 00000000..8a32a714 --- /dev/null +++ b/src/main/java/org/mangorage/basicutils/misc/Lazy.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024. MangoRage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.mangorage.basicutils.misc; + +import java.util.function.Supplier; + +public interface Lazy extends Supplier { + void invalidate(); + + /** + * Constructs a lazy-initialized object + * + * @param supplier The supplier for the value, to be called the first time the value is needed. + */ + static Lazy of(Supplier supplier) { + return new Fast(supplier); + } + + /** + * Constructs a thread-safe lazy-initialized object + * + * @param supplier The supplier for the value, to be called the first time the value is needed. + */ + static Lazy concurrentOf(Supplier supplier) { + return new Concurrent(supplier); + } + + /** + * Non-thread-safe implementation. + */ + final class Fast implements Lazy { + private final Supplier supplier; + private T instance; + + private Fast(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public final T get() { + if (instance == null) { + instance = supplier.get(); + } + return instance; + } + + @Override + public void invalidate() { + this.instance = null; + } + } + + /** + * Thread-safe implementation. + */ + final class Concurrent implements Lazy { + private final Object lock = new Object(); + private final Supplier supplier; + private volatile T instance; + + private Concurrent(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public final T get() { + var ret = instance; + if (ret == null) { + synchronized (lock) { + if (instance == null) { + return instance = supplier.get(); + } + } + } + return ret; + } + + @Override + public void invalidate() { + synchronized (lock) { + this.instance = null; + } + } + } +} diff --git a/src/main/java/org/mangorage/mboteventbus/base/EventBus.java b/src/main/java/org/mangorage/mboteventbus/base/EventBus.java index 4e6cfd87..350786a2 100644 --- a/src/main/java/org/mangorage/mboteventbus/base/EventBus.java +++ b/src/main/java/org/mangorage/mboteventbus/base/EventBus.java @@ -40,13 +40,19 @@ private record ListenerKey(Class eventClass, Class genericClass) { private final Map> LISTENERS = new ConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") public void addListener(int priority, Class eventClass, Consumer eventConsumer) { IListenerList list = (IListenerList) getListenerList(eventClass, null); + if (list == null) return; list.register(eventConsumer, "default", priority); } + + @SuppressWarnings("unchecked") public , G> void addGenericListener(int priority, Class genericClassFilter, Class eventType, Consumer genericEventListener) { IListenerList list = (IListenerList) getListenerList(eventType, genericClassFilter); + if (list == null) return; list.register(genericEventListener, "default", priority); } @@ -60,7 +66,6 @@ public void post(Event event) { listeners.accept(event); } - @SuppressWarnings("unchecked") private IListenerList getListenerList(Class eventClass, Class genericClass) { var key = new ListenerKey(eventClass, genericClass); var list = LISTENERS.get(key); diff --git a/src/main/java/org/mangorage/mboteventbus/base/ListenerListImpl.java b/src/main/java/org/mangorage/mboteventbus/base/ListenerListImpl.java index fbc20827..b1334a47 100644 --- a/src/main/java/org/mangorage/mboteventbus/base/ListenerListImpl.java +++ b/src/main/java/org/mangorage/mboteventbus/base/ListenerListImpl.java @@ -22,18 +22,44 @@ package org.mangorage.mboteventbus.base; +import org.mangorage.basicutils.misc.Lazy; import org.mangorage.mboteventbus.impl.IListenerList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Stream; public class ListenerListImpl implements IListenerList { private final List> backingList; private final IListenerList parent; + private final Set> children = new HashSet<>(); + private final Lazy[]> listeners; public ListenerListImpl(List> backingList, IListenerList parent) { this.backingList = backingList; this.parent = parent; + if (parent != null) parent.addChild(this); + + this.listeners = Lazy.concurrentOf(() -> { + if (parent != null) { + return Stream.of(backingList, List.of(parent.getListeners())) + .flatMap(List::stream) + .sorted() + .toArray(Listener[]::new); + } + + return backingList.toArray(Listener[]::new); + }); + } + + /** + * @param child + */ + @Override + public void addChild(IListenerList child) { + children.add(child); } /** @@ -41,10 +67,9 @@ public ListenerListImpl(List> backingList, IListenerList parent) */ @Override public void accept(Event event) { - backingList.stream() - .sorted() - .forEach(e -> e.consumer().accept((T) event)); - if (parent != null) parent.accept(event); + for (Listener listener : getListeners()) { + listener.consumer().accept((T) event); + } } /** @@ -55,5 +80,25 @@ public void accept(Event event) { @Override public void register(Consumer eventConsumer, String name, int priority) { backingList.add(new Listener<>(priority, name, eventConsumer)); + if (parent != null) parent.invalidate(); + children.forEach(IListenerList::invalidate); } + + /** + * @return + */ + @Override + public Listener[] getListeners() { + return listeners.get(); + } + + /** + * + */ + @Override + public void invalidate() { + listeners.invalidate(); + } + + } diff --git a/src/main/java/org/mangorage/mboteventbus/impl/IListenerList.java b/src/main/java/org/mangorage/mboteventbus/impl/IListenerList.java index 583d9e2c..58bf917f 100644 --- a/src/main/java/org/mangorage/mboteventbus/impl/IListenerList.java +++ b/src/main/java/org/mangorage/mboteventbus/impl/IListenerList.java @@ -23,11 +23,17 @@ package org.mangorage.mboteventbus.impl; import org.mangorage.mboteventbus.base.Event; +import org.mangorage.mboteventbus.base.Listener; import java.util.function.Consumer; public interface IListenerList { void accept(Event event); - void register(Consumer eventConsumer, String name, int priority); + + Listener[] getListeners(); + + void invalidate(); + + void addChild(IListenerList child); } diff --git a/src/test/java/org/mangorage/mangobot/test/EventBusTest.java b/src/test/java/org/mangorage/mangobot/test/EventBusTest.java new file mode 100644 index 00000000..7a9faaa7 --- /dev/null +++ b/src/test/java/org/mangorage/mangobot/test/EventBusTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024. MangoRage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.mangorage.mangobot.test; + +import org.mangorage.mangobot.test.misc.TimedTest; +import org.mangorage.mboteventbus.base.Event; +import org.mangorage.mboteventbus.base.EventBus; + +public class EventBusTest { + + public static class MyEvent extends Event { + } + + + public static void main(String[] args) { + var bus = EventBus.create(); + + var registration = TimedTest.of(() -> { + for (int i = 0; i < 10000; i++) { + bus.addListener(10, Event.class, e -> { + }); + bus.addListener(10, MyEvent.class, e -> { + }); + } + }); + + var post = TimedTest.of(() -> { + bus.post(new MyEvent()); + }); + + System.out.println("Registration time for 1 listener -> " + registration.getResult()); + long total = 0; + long amount = 10000; + + for (int i = 0; i < amount; i++) { + var result = post.getResult(); + System.out.println("Post time for Post #%s -> ".formatted(i) + result); + total += result; + } + System.out.println("AVG -> " + total / amount); + } +} diff --git a/src/test/java/org/mangorage/mangobot/test/misc/TimedTest.java b/src/test/java/org/mangorage/mangobot/test/misc/TimedTest.java new file mode 100644 index 00000000..a5c19aa4 --- /dev/null +++ b/src/test/java/org/mangorage/mangobot/test/misc/TimedTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024. MangoRage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.mangorage.mangobot.test.misc; + +public record TimedTest(Runnable runnable) { + public static TimedTest of(Runnable runnable) { + return new TimedTest(runnable); + } + + public long getResult() { + var before = System.nanoTime(); + runnable.run(); + return System.nanoTime() - before; + } +}