Update ObservableEventDispatcher.java, ProfilingEventDispatcher.java, and 5 more files...
This commit is contained in:
parent
d83bd72911
commit
cff157a0cd
280
eventflow/ObservableEventDispatcher.java
Executable file
280
eventflow/ObservableEventDispatcher.java
Executable file
@ -0,0 +1,280 @@
|
|||||||
|
package de.midlane_illaoi.eventflow;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import de.midlane_illaoi.eventflow.event.ObservableEvent;
|
||||||
|
import de.midlane_illaoi.eventflow.event.ObserverSortingEvent;
|
||||||
|
|
||||||
|
public class ObservableEventDispatcher
|
||||||
|
{
|
||||||
|
private Map<Class<? extends ObservableEvent<?>>, Collection<?>> observersMap;
|
||||||
|
|
||||||
|
private List<QueuedModification<?>> queuedModifications;
|
||||||
|
|
||||||
|
class QueuedModification<T>{
|
||||||
|
private Class<? extends ObservableEvent<T>> eventType;
|
||||||
|
private T observer;
|
||||||
|
private boolean isSubscription;
|
||||||
|
|
||||||
|
public QueuedModification(Class<? extends ObservableEvent<T>> eventType, T observer, boolean isSubscription) {
|
||||||
|
super();
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.observer = observer;
|
||||||
|
this.isSubscription = isSubscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends ObservableEvent<T>> getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getObserver() {
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSubscription() {
|
||||||
|
return isSubscription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This variable serves as a recursion counter for nested event dispatching.
|
||||||
|
* */
|
||||||
|
private int isDispatching;
|
||||||
|
|
||||||
|
public ObservableEventDispatcher() {
|
||||||
|
isDispatching = 0;
|
||||||
|
observersMap = new HashMap<>();
|
||||||
|
queuedModifications = new LinkedList<QueuedModification<?>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently this dispacther favors performance over functionality.
|
||||||
|
* It is currently not possible to change active listeners with a listener for the currently dispatched event.
|
||||||
|
* Anyways it is possible to change subscribed listener with a listener.
|
||||||
|
* The changes will become active after the dispatching of the currently dispatched event and dispatching of all events that caused the currently dispatched event stops.
|
||||||
|
* */
|
||||||
|
public <T> void subscribe(Class<? extends ObservableEvent<T>> eventType, T observer) {
|
||||||
|
|
||||||
|
Collection<T> computeIfAbsent;
|
||||||
|
if( isDispatching > 0 ) {
|
||||||
|
queuedModifications.add( new QueuedModification<T>(eventType, observer, true) );
|
||||||
|
}else {
|
||||||
|
computeIfAbsent = getOrCreateListenerSet(eventType);
|
||||||
|
computeIfAbsent.add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently this dispacther favors performance over functionality.
|
||||||
|
* It is currently not possible to change active listeners with a listener for the currently dispatched event.
|
||||||
|
* Anyways it is possible to change subscribed listener with a listener.
|
||||||
|
* The changes will become active after the dispatching of the currently dispatched event stops.
|
||||||
|
*
|
||||||
|
* @return true if the listener was currently subscribed and removed. false other wise.
|
||||||
|
* false is also returned in case the dispatcher is currently dispatching and queues the removal.
|
||||||
|
* */
|
||||||
|
public <T> boolean unsubscribe(Class<? extends ObservableEvent<T>> eventType, T observer) {
|
||||||
|
|
||||||
|
Collection<T> computeIfAbsent;
|
||||||
|
if( isDispatching > 0 ) {
|
||||||
|
queuedModifications.add( new QueuedModification<T>(eventType, observer, false) );
|
||||||
|
return false;
|
||||||
|
}else {
|
||||||
|
computeIfAbsent = getOrCreateListenerSet(eventType);
|
||||||
|
return computeIfAbsent.remove(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked"})
|
||||||
|
private <T> Collection<T> getOrCreateListenerSet(Class<? extends ObservableEvent<T>> eventType)
|
||||||
|
{
|
||||||
|
|
||||||
|
Collection<?> computeIfAbsent = observersMap.computeIfAbsent(eventType, key -> {
|
||||||
|
if( ObserverSortingEvent.class.isAssignableFrom(eventType) ) {
|
||||||
|
//ObserverSortingEvent eventInstance = getSortableEventTypeInstance((Class<ObserverSortingEvent>) eventType);
|
||||||
|
ObserverSortingEvent eventInstance = getSortableEventTypeInstance(castObservableEventClassToObservableEventClass(eventType));
|
||||||
|
if( eventInstance.isComparatorUsedForAllEventsOfType() ) {
|
||||||
|
Comparator<T> observerComparator = (Comparator<T>) eventInstance.getObserverComparator();
|
||||||
|
return new TreeSet<T>( observerComparator );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<T>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (Collection<T>) computeIfAbsent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T extends ObserverSortingEvent> Class<T> castObservableEventClassToObservableEventClass(Class<? extends ObservableEvent<?>> eventType){
|
||||||
|
return (Class<T>) eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends ObserverSortingEvent> T getSortableEventTypeInstance( Class<T> eventType )
|
||||||
|
{
|
||||||
|
Constructor<T> constructor = null;
|
||||||
|
Boolean accessibility = null;
|
||||||
|
try {
|
||||||
|
// Get the (private) default constructor
|
||||||
|
constructor = eventType.getDeclaredConstructor();
|
||||||
|
accessibility = constructor.isAccessible();
|
||||||
|
|
||||||
|
// Set the accessibility to true
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
|
||||||
|
// Create an instance using the private constructor
|
||||||
|
|
||||||
|
return (T) constructor.newInstance();
|
||||||
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||||
|
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
||||||
|
String errorMessage = String.format("Cannot subscribe to events of type: %s. Events that implement the %s interface must implement a (private) default constructor.", eventType.getName(), ObserverSortingEvent.class.getName());
|
||||||
|
throw new RuntimeException( errorMessage, e);
|
||||||
|
}finally {
|
||||||
|
if( accessibility != null && constructor != null) {
|
||||||
|
constructor.setAccessible(accessibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public <T> void dispatchEvent(ObservableEvent<T> event)
|
||||||
|
{
|
||||||
|
isDispatching++;
|
||||||
|
try {
|
||||||
|
List<Class<?>> eventTypes = getAllEventSuperclasses(event.getClass());
|
||||||
|
|
||||||
|
|
||||||
|
for (Class<?> eventType : eventTypes) {
|
||||||
|
if(!observersMap.containsKey(eventType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//List<T> observers = (List<T>) observersMap.get(eventType);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a copy of the observers list.
|
||||||
|
*
|
||||||
|
* Observers can now unsubscribe while being notified.
|
||||||
|
*
|
||||||
|
* Alternative solution: Queuing Unsubscribe Calls:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Pros:
|
||||||
|
* Lower cpu usage
|
||||||
|
* Lower Memory Usage: There's no need to create copies of observer lists, so memory usage remains low.
|
||||||
|
* Deferred Unsubscription: Unsubscribe calls are deferred until after event dispatch, allowing observers to continue receiving events during the current dispatch cycle.
|
||||||
|
*
|
||||||
|
* Cons:
|
||||||
|
* Complexity: Managing a queue of unsubscribe commands adds complexity to the implementation.
|
||||||
|
* Potential Delay: Unsubscribed observers may receive events during the current dispatch cycle.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @SuppressWarnings("unchecked")
|
||||||
|
* List<T> observers = new ArrayList<>( (Set<T>) observersMap.get(eventType) );
|
||||||
|
*/
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<T> observers = (Collection<T>) observersMap.get(eventType);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* currently for a ObserverSortingEvent all listeners
|
||||||
|
* get sorted for every dispatched event.
|
||||||
|
*
|
||||||
|
* This may slow down execution.
|
||||||
|
* An alternative is to keep the observers for such event types
|
||||||
|
* in a sorted list
|
||||||
|
* */
|
||||||
|
if (event instanceof ObserverSortingEvent) {
|
||||||
|
ObserverSortingEvent sortingEvent = (ObserverSortingEvent) event;
|
||||||
|
|
||||||
|
if( !sortingEvent.isComparatorUsedForAllEventsOfType() ) {
|
||||||
|
observers = new ArrayList<>( observers );
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Comparator<? super T> observerComparator = (Comparator<? super T>) sortingEvent.getObserverComparator();
|
||||||
|
((ArrayList)observers).sort( observerComparator );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observers.forEach(observer -> {
|
||||||
|
event.accept(observer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Class<? extends ObservableEvent<T>> eventType = getEventType(event);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<T> observers = (List<T>) observersMap.computeIfAbsent(eventType, key -> new ArrayList<T>());
|
||||||
|
observers.forEach(observer -> {
|
||||||
|
event.accept(observer);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}catch (Exception e){
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isDispatching--;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* code changed at 15.07.2024
|
||||||
|
* old code did not check for
|
||||||
|
* if( isDispatching == 0 ) {
|
||||||
|
* */
|
||||||
|
if( isDispatching == 0 ) {
|
||||||
|
for( QueuedModification<?> queuedModification : queuedModifications ) {
|
||||||
|
if( queuedModification.isSubscription() ) {
|
||||||
|
subscribeHelper(queuedModification);
|
||||||
|
}else {
|
||||||
|
unsubscribeHelper(queuedModification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queuedModifications.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private <T> void subscribeHelper(QueuedModification<T> queuedModification) {
|
||||||
|
subscribe(queuedModification.getEventType(), queuedModification.getObserver());
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void unsubscribeHelper(QueuedModification<T> queuedModification) {
|
||||||
|
unsubscribe(queuedModification.getEventType(), queuedModification.getObserver());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Class<?>> getAllEventSuperclasses(Class<?> eventType)
|
||||||
|
{
|
||||||
|
List<Class<?>> superclasses = new ArrayList<>();
|
||||||
|
while (eventType != null && ObservableEvent.class.isAssignableFrom(eventType)) {
|
||||||
|
superclasses.add(eventType);
|
||||||
|
eventType = eventType.getSuperclass();
|
||||||
|
}
|
||||||
|
return superclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "unused" })
|
||||||
|
private <T> Class<? extends ObservableEvent<T>> getEventType(ObservableEvent<T> event) {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Class<? extends ObservableEvent> eventClass = event.getClass();
|
||||||
|
return (Class<? extends ObservableEvent<T>>) eventClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
eventflow/ProfilingEventDispatcher.java
Executable file
29
eventflow/ProfilingEventDispatcher.java
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
package de.midlane_illaoi.eventflow;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import de.midlane_illaoi.eventflow.event.ObservableEvent;
|
||||||
|
|
||||||
|
public class ProfilingEventDispatcher extends ObservableEventDispatcher{
|
||||||
|
|
||||||
|
private Map<Class<?>, Integer> eventTypeCounter;
|
||||||
|
|
||||||
|
public ProfilingEventDispatcher() {
|
||||||
|
eventTypeCounter = new HashMap<Class<?>, Integer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void dispatchEvent(ObservableEvent<T> event) {
|
||||||
|
super.dispatchEvent(event);
|
||||||
|
int count = eventTypeCounter.computeIfAbsent(event.getClass(), c -> 0);
|
||||||
|
eventTypeCounter.put(event.getClass(), count+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printEventsCount() {
|
||||||
|
for (Map.Entry<Class<?>, Integer> entry : eventTypeCounter.entrySet()) {
|
||||||
|
System.out.println("Event type: " + entry.getKey().getSimpleName() + ",\t\t\t\tEvents count:: " + entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
eventflow/event/BasicEvent.java
Executable file
10
eventflow/event/BasicEvent.java
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
package de.midlane_illaoi.eventflow.event;
|
||||||
|
|
||||||
|
import de.midlane_illaoi.eventflow.listener.BasicEventListener;
|
||||||
|
|
||||||
|
public abstract class BasicEvent<T extends BasicEvent<T>> implements ObservableEvent<BasicEventListener<T>> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void accept(BasicEventListener<T> eventlistener) {
|
||||||
|
eventlistener.onEvent((T) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
eventflow/event/ObservableEvent.java
Executable file
6
eventflow/event/ObservableEvent.java
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
package de.midlane_illaoi.eventflow.event;
|
||||||
|
|
||||||
|
public interface ObservableEvent<T>
|
||||||
|
{
|
||||||
|
public void accept(T eventlistener);
|
||||||
|
}
|
||||||
81
eventflow/event/ObserverSortingEvent.java
Executable file
81
eventflow/event/ObserverSortingEvent.java
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
package de.midlane_illaoi.eventflow.event;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import de.midlane_illaoi.eventflow.listener.SortableEventListener;
|
||||||
|
|
||||||
|
public interface ObserverSortingEvent
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO write library to handle bitflags
|
||||||
|
* refactor this code & AbstractBuff
|
||||||
|
public static final int ON_UNCOMPARABLE_LISTENERS_THROW_EXCEPTION = 1;
|
||||||
|
public static final int EXECUTE_UNCOMPARABLE_LISTENERS_FIRST = 2;
|
||||||
|
public static final int EXECUTE_UNCOMPARABLE_LISTENERS_LAST = 4;
|
||||||
|
public static final int COMPARE_UNCAMPARABLE_LISTENERS_BY_HASHCODE = 8;
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
enum SortingMode {
|
||||||
|
ON_UNCOMPARABLE_LISTENERS_THROW_EXCEPTION,
|
||||||
|
EXECUTE_UNCOMPARABLE_LISTENERS_FIRST,
|
||||||
|
EXECUTE_UNCOMPARABLE_LISTENERS_LAST,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Comparator<? extends Object> getObserverComparator();
|
||||||
|
|
||||||
|
default public boolean isComparatorUsedForAllEventsOfType() { return true; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The comparator compares all objects,
|
||||||
|
* but treats every object that does
|
||||||
|
* not implement the SortableEventListener interface as equal
|
||||||
|
* */
|
||||||
|
default Comparator<? extends Object> createObserverComparator(SortingMode sortingMode){
|
||||||
|
return new Comparator<Object>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Object o1, Object o2) {
|
||||||
|
boolean isO1Comparable = o1 instanceof SortableEventListener;
|
||||||
|
boolean isO2Comparable = o2 instanceof SortableEventListener;
|
||||||
|
|
||||||
|
if( sortingMode == SortingMode.ON_UNCOMPARABLE_LISTENERS_THROW_EXCEPTION && isO1Comparable == isO2Comparable ) {
|
||||||
|
throw new RuntimeException("All listeners for this event must implement the SortableEventListener interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
if( isO1Comparable && isO2Comparable ) {
|
||||||
|
SortableEventListener o1Sortable = (SortableEventListener) o1;
|
||||||
|
SortableEventListener o2Sortable = (SortableEventListener) o2;
|
||||||
|
/*
|
||||||
|
* sort by priority in an ascending order
|
||||||
|
* */
|
||||||
|
return Integer.compare(o2Sortable.getPriority(), o1Sortable.getPriority());
|
||||||
|
}else if( !( isO1Comparable || isO2Comparable) ) {
|
||||||
|
/*two uncomparable objects are treated as equal*/
|
||||||
|
/* using has code here for comparison instead of returning 0
|
||||||
|
* slows down sorting but allows to use this comparator for a tree set
|
||||||
|
* */
|
||||||
|
return Integer.compare(o1.hashCode(), o2.hashCode());
|
||||||
|
}else if(isO1Comparable){
|
||||||
|
/*
|
||||||
|
* o1 is comparable but o2 is not
|
||||||
|
* */
|
||||||
|
if(sortingMode == SortingMode.EXECUTE_UNCOMPARABLE_LISTENERS_FIRST) {
|
||||||
|
return -1;
|
||||||
|
}else /*sortingMode == SortingMode.EXECUTE_UNCOMPARABLE_LISTENERS_LAST*/{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}else {
|
||||||
|
/*o2 is comparable but o1 is not*/
|
||||||
|
if(sortingMode == SortingMode.EXECUTE_UNCOMPARABLE_LISTENERS_FIRST) {
|
||||||
|
return 1;
|
||||||
|
}else /*sortingMode == SortingMode.EXECUTE_UNCOMPARABLE_LISTENERS_LAST*/{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
5
eventflow/listener/BasicEventListener.java
Executable file
5
eventflow/listener/BasicEventListener.java
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
package de.midlane_illaoi.eventflow.listener;
|
||||||
|
|
||||||
|
public interface BasicEventListener<T> {
|
||||||
|
public void onEvent(T event);
|
||||||
|
}
|
||||||
17
eventflow/listener/SortableEventListener.java
Executable file
17
eventflow/listener/SortableEventListener.java
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
package de.midlane_illaoi.eventflow.listener;
|
||||||
|
|
||||||
|
import de.midlane_illaoi.eventflow.ObservableEventDispatcher;
|
||||||
|
import de.midlane_illaoi.eventflow.event.ObserverSortingEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interfaces is used by the {@link ObserverSortingEvent} to sort event listeners.
|
||||||
|
* A higher priority value will lead to an earlier execution when using the {@link ObservableEventDispatcher}
|
||||||
|
* */
|
||||||
|
public interface SortableEventListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* a higher priority indicates an earlier execution.
|
||||||
|
* negative priority values are allowed
|
||||||
|
* */
|
||||||
|
public int getPriority();
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user