Skip to content

Commit

Permalink
Merge pull request #1 from YumiProject/dev/filtered_events
Browse files Browse the repository at this point in the history
Filtered events
  • Loading branch information
LambdAurora authored Nov 26, 2024
2 parents 9af7a0f + 22572d3 commit 85afdf6
Show file tree
Hide file tree
Showing 12 changed files with 830 additions and 97 deletions.
2 changes: 1 addition & 1 deletion build_logic/src/main/kotlin/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ data class Developer(val name: String, val email: String)

object Constants {
const val GROUP = "dev.yumi.commons"
const val VERSION = "1.0.0-alpha.1"
const val VERSION = "1.0.0-alpha.2"
const val JAVA_VERSION = 17

const val PROJECT_NAME = "Yumi Commons"
Expand Down
82 changes: 58 additions & 24 deletions libraries/event/src/main/java/dev/yumi/commons/event/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
package dev.yumi.commons.event;

import dev.yumi.commons.collections.toposort.NodeSorting;
import dev.yumi.commons.collections.toposort.SortableNode;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

Expand All @@ -52,7 +54,7 @@
* of implementing an invoker and only allows listener implementations to be done by implementing an interface onto a
* class or extending a class.
* <p>
* An Event can have phases, each listener is attributed to a phase ({@link Event#getDefaultPhaseId()} if unspecified),
* An Event can have phases, each listener is attributed to a phase ({@link Event#defaultPhaseId()} if unspecified),
* and each phase can have a defined ordering. Each event phase is identified, ordering is done
* by explicitly stating that event phase {@code A} will run before event phase {@code B}, for example.
* See {@link Event#addPhaseOrdering(Comparable, Comparable)} for more information.
Expand Down Expand Up @@ -114,11 +116,13 @@
* }</pre>
*
* @param <I> the phase identifier type
* @param <T> the type of the invoker used to execute an event and the type of the listener
* @param <T> the type of the listeners, and the type of the invoker used to execute an event
* @version 1.0.0
* @since 1.0.0
*/
public final class Event<I extends Comparable<? super I>, T> {
public sealed class Event<I extends Comparable<? super I>, T>
implements InvokableEvent<T>
permits FilteredEvent {
/**
* The type of listener of this event.
*/
Expand All @@ -130,24 +134,24 @@ public final class Event<I extends Comparable<? super I>, T> {
/**
* The function used to generate the implementation of the invoker to call the listeners.
*/
private final Function<T[], T> invokerFactory;
private final Lock lock = new ReentrantLock();
final Function<T[], T> invokerFactory;
final Lock lock = new ReentrantLock();
/**
* The invoker to execute the callbacks.
*/
private volatile T invoker;
/**
* The registered listeners.
*/
private T[] listeners;
T[] listeners;
/**
* The registered event phases.
*/
private final Map<I, EventPhaseData<I, T>> phases = new LinkedHashMap<>();
final Map<I, PhaseData<I, T>> phases = new LinkedHashMap<>();
/**
* The event phases sorted in a way that satisfies dependencies.
*/
private final List<EventPhaseData<I, T>> sortedPhases = new ArrayList<>();
final List<PhaseData<I, T>> sortedPhases = new ArrayList<>();

@SuppressWarnings("unchecked")
Event(
Expand All @@ -170,15 +174,15 @@ public final class Event<I extends Comparable<? super I>, T> {
* {@return the class of the kind of listeners accepted by this event}
*/
@Contract(pure = true)
public @NotNull Class<? super T> getType() {
public @NotNull Class<? super T> type() {
return this.type;
}

/**
* {@return the default phase identifier of this event}
*/
@Contract(pure = true)
public @NotNull I getDefaultPhaseId() {
public @NotNull I defaultPhaseId() {
return this.defaultPhaseId;
}

Expand Down Expand Up @@ -235,44 +239,42 @@ public void addPhaseOrdering(@NotNull I firstPhase, @NotNull I secondPhase) {
var first = this.getOrCreatePhase(firstPhase, false);
var second = this.getOrCreatePhase(secondPhase, false);

EventPhaseData.link(first, second);
NodeSorting.sort(this.sortedPhases, "event phases");
PhaseData.link(first, second);
this.sortPhases();
this.rebuildInvoker(this.listeners.length);
} finally {
this.lock.unlock();
}
}

/**
* {@return the invoker instance used to execute this event}
* <p>
* The result of this method should not be stored since the invoker may become invalid
* at any time. Always call this method when you intend to execute an event.
*/
@Contract(pure = true)
@Override
public @NotNull T invoker() {
return this.invoker;
}

/* Implementation */

private EventPhaseData<I, T> getOrCreatePhase(I id, boolean sortIfCreate) {
PhaseData<I, T> getOrCreatePhase(@NotNull I id, boolean sortIfCreate) {
var phase = this.phases.get(id);

if (phase == null) {
phase = new EventPhaseData<>(id, this.type);
phase = new PhaseData<>(id, this.type);
this.phases.put(id, phase);
this.sortedPhases.add(phase);

if (sortIfCreate) {
NodeSorting.sort(this.sortedPhases, "event phases");
this.sortPhases();
}
}

return phase;
}

private void rebuildInvoker(int newLength) {
void sortPhases() {
NodeSorting.sort(this.sortedPhases, "event phases");
}

void rebuildInvoker(int newLength) {
if (this.sortedPhases.size() == 1) {
// There's a single phase, so we can directly use its listeners.
this.listeners = this.sortedPhases.get(0).listeners;
Expand All @@ -297,7 +299,7 @@ private void rebuildInvoker(int newLength) {
this.update();
}

private void update() {
void update() {
// Make a copy of the array given to the invoker factory so the entries cannot be mutated.
this.invoker = this.invokerFactory.apply(
Arrays.copyOf(this.listeners, this.listeners.length)
Expand All @@ -315,4 +317,36 @@ public String toString() {
", sortedPhases=" + this.sortedPhases +
'}';
}

/**
* Represents data for a specific event phase.
*
* @param <I> the phase identifier type
* @param <T> the type of the listeners
*/
@ApiStatus.Internal
static sealed class PhaseData<I, T> extends SortableNode<I, PhaseData<I, T>>
permits FilteredEvent.FilteredPhaseData {
private final I id;
T[] listeners;

@SuppressWarnings("unchecked")
PhaseData(@NotNull I id, @NotNull Class<? super T> listenerType) {
Objects.requireNonNull(id);

this.id = id;
this.listeners = (T[]) Array.newInstance(listenerType, 0);
}

@Override
public @NotNull I getId() {
return this.id;
}

void addListener(@NotNull T listener) {
int oldLength = this.listeners.length;
this.listeners = Arrays.copyOf(this.listeners, oldLength + 1);
this.listeners[oldLength] = listener;
}
}
}
Loading

0 comments on commit 85afdf6

Please sign in to comment.