Skip to content

Commit

Permalink
Minor Optimizations for MangoBot
Browse files Browse the repository at this point in the history
  • Loading branch information
RealMangorage committed May 22, 2024
1 parent fecf73a commit 314cce0
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 6 deletions.
105 changes: 105 additions & 0 deletions src/main/java/org/mangorage/basicutils/misc/Lazy.java
Original file line number Diff line number Diff line change
@@ -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<T> extends Supplier<T> {
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 <T> Lazy<T> of(Supplier<T> supplier) {
return new Fast<T>(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 <T> Lazy<T> concurrentOf(Supplier<T> supplier) {
return new Concurrent<T>(supplier);
}

/**
* Non-thread-safe implementation.
*/
final class Fast<T> implements Lazy<T> {
private final Supplier<T> supplier;
private T instance;

private Fast(Supplier<T> 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<T> implements Lazy<T> {
private final Object lock = new Object();
private final Supplier<T> supplier;
private volatile T instance;

private Concurrent(Supplier<T> 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;
}
}
}
}
7 changes: 6 additions & 1 deletion src/main/java/org/mangorage/mboteventbus/base/EventBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ private record ListenerKey(Class<?> eventClass, Class<?> genericClass) {

private final Map<ListenerKey, IListenerList<? extends Event>> LISTENERS = new ConcurrentHashMap<>();


@SuppressWarnings("unchecked")
public <T extends Event> void addListener(int priority, Class<T> eventClass, Consumer<T> eventConsumer) {
IListenerList<T> list = (IListenerList<T>) getListenerList(eventClass, null);
if (list == null) return;
list.register(eventConsumer, "default", priority);
}


@SuppressWarnings("unchecked")
public <T extends GenericEvent<? extends G>, G> void addGenericListener(int priority, Class<G> genericClassFilter, Class<T> eventType, Consumer<T> genericEventListener) {
IListenerList<T> list = (IListenerList<T>) getListenerList(eventType, genericClassFilter);
if (list == null) return;
list.register(genericEventListener, "default", priority);
}

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,54 @@

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<T> implements IListenerList<T> {
private final List<Listener<T>> backingList;
private final IListenerList<?> parent;
private final Set<IListenerList<?>> children = new HashSet<>();
private final Lazy<Listener<T>[]> listeners;

public ListenerListImpl(List<Listener<T>> 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);
}

/**
* @param event
*/
@Override
public void accept(Event event) {
backingList.stream()
.sorted()
.forEach(e -> e.consumer().accept((T) event));
if (parent != null) parent.accept(event);
for (Listener<T> listener : getListeners()) {
listener.consumer().accept((T) event);
}
}

/**
Expand All @@ -55,5 +80,25 @@ public void accept(Event event) {
@Override
public void register(Consumer<T> 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<T>[] getListeners() {
return listeners.get();
}

/**
*
*/
@Override
public void invalidate() {
listeners.invalidate();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
void accept(Event event);

void register(Consumer<T> eventConsumer, String name, int priority);

Listener<T>[] getListeners();

void invalidate();

void addChild(IListenerList<?> child);
}
62 changes: 62 additions & 0 deletions src/test/java/org/mangorage/mangobot/test/EventBusTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
35 changes: 35 additions & 0 deletions src/test/java/org/mangorage/mangobot/test/misc/TimedTest.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 314cce0

Please sign in to comment.