Skip to content

Commit

Permalink
Update license years, add BiConsumerEvent, clarify javadocs, add clear()
Browse files Browse the repository at this point in the history
  • Loading branch information
falkreon committed Mar 30, 2023
1 parent bbcddf8 commit 51346dd
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 31 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Falkreon (Isaac Ellingson)
Copyright (c) 2021-2023 Falkreon (Isaac Ellingson)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
153 changes: 153 additions & 0 deletions src/main/java/blue/endless/tinyevents/BiConsumerEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* MIT License
*
* Copyright (c) 2021-2023 Falkreon (Isaac Ellingson)
*
* 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 blue.endless.tinyevents;

import java.util.concurrent.Executor;
import java.util.function.BiConsumer;

/**
* Represents an Event where event-responders receive two pieces of information.
*
* <p>To respond to an Event of this type, you need a BiConsumer that accepts the data this event produces - so if it's
* a {@code ConsumerEvent<Integer, String>}, you need to register a {@code BiConsumer<Integer,String>}. You can also
* optionally supply a registration key that can be used to unregister the event, and/or an Executor to defer event
* handler execution to.
*
* <p>To "provide" a new BiConsumerEvent, you might do something like,
*
* <pre>
* public class Foo {
* private final ConsumerEvent&lt;String, String&gt; onNameChanged = ConsumerEvent.create();
* private String name = "default";
*
* public ConsumerEvent&lt;String, String&gt; onNameChanged() {
* return onNameChanged;
* }
*
* public void changeName(String name) {
* String oldName = this.name;
* this.name = name;
*
* onNameChange.fire(oldName, name);
* }
* }
* </pre>
*
* Note that generally Events fire after their corresponding logic, and their verb names are past tense, since they're
* best used for situations where you can't change the triggering event, but you're responding to it.
*
* See {@link #register(BiConsumer)} for more info on how to register and respond to events of this type.
*/
public interface BiConsumerEvent<T, U> {
/**
* Fire this event, causing all the registered handlers to be called. How the handlers
* are scheduled, when they will run, and on what thread, depends on how they registered
* and, if applicable, the Executor they were registered with.
* @param t the first argument the BiConsumer event responders will accept
* @param u the second argument the BiConsumer event responders will accept
*/
void fire(T t, U u);

/**
* Registers an event handler to be run when this Event is fired.
*
* <p>Note: Because of the semantics regarding lambdas, the following will not work:
* <pre>
* public void throwSomething(int a, int b) {
* assert(false);
* }
*
* public static void main(String[] args) {
* BiConsumerEvent&lt;Integer, Integer&gt; onRun = ConsumerEvent&lt;Integer, Integer&gt;.create();
* onRun.register(this::throwSomething);
* onRun.unregister(this::throwSomething);
* onRun.fire(1, 1);
* }
* </pre>
*
* <p>This is because the lambda which {@code this::throwSomething} is implicitly cast
* to, (causing the lambda to be generated by lambdaMetafactory) may be different for
* each capture site. This was done to make lambdas extremely fast, but is inconvenient
* for event systems. A workaround is to say instead,
*
* <pre>
* public void throwSomething(int a, int b) {
* assert(false);
* }
*
* public static void main(String[] args) {
* ConsumerEvent&lt;Integer, Integer&gt; onRun = ConsumerEvent.create();
*
* Consumer&lt;Integer, Integer&gt; handler = this::throwSomething; //handler is always reference-equal to handler
*
* onRun.register(handler);
* onRun.unregister(handler);
* onRun.fire(1, 1);
* }
* </pre>
*
* or to supply a registration key where reference equality can be guaranteed.
*
* @param handler
*/
void register(BiConsumer<T, U> handler);

/**
*Registers an event handler to be run when this Event is fired.
*
* @param handler The event handler to be invoked when {@link #fire(T, U)} is called.
* @param key A key which can be later used to unregister the event handler
*/
void register(BiConsumer<T, U> handler, Object key);

/**
* Registers an event handler to be run when this Event is fired.
* @param handler The event handler to be invoked when {@link #fire(T, U)} is called.
* @param executor The executor to run the handler on
*/
void register(BiConsumer<T, U> handler, Executor executor);

/**
* Registers an event handler to be run when this Event is fired.
* @param handler The event handler to be invoked when {@link #fire(T, U)} is called.
* @param executor The executor to run the handler on
* @param key A key which can later be used to unregister the event handler
*/
void register(BiConsumer<T, U> handler, Executor executor, Object key);

/**
* Unregisters and event handler. The object will be <em>reference-compared</em> (==) with
* the key of each existing event handler to determine whether it should be unregistered.
* If multiple event handlers were registered with this key, all of them will be removed.
* @param key
*/
void unregister(Object key);

/**
* Unregisters ALL event handlers attached to this event. If event handlers are currently firing, they may continue
* to fire until the event is complete.
*/
void clear();
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2021 Falkreon (Isaac Ellingson)
* Copyright (c) 2021-2023 Falkreon (Isaac Ellingson)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -22,33 +22,40 @@
* SOFTWARE.
*/

package blue.endless.tinyevents.impl;
package blue.endless.tinyevents;

import java.util.concurrent.Executor;
import java.util.function.Consumer;

import blue.endless.tinyevents.impl.ConsumerEventListImpl;

/**
* Implementers of this type represent an event where handlers receive a single Object.
* Handlers for the event are registered as {@link java.util.function.Consumer Consumer}s, hence the name of this
* interface. To "provide" a ConsumerEvent, you might do something like,
* Represents an Event where event-responders receive one piece of information, such as an elapsed time or an event
* source.
*
* <p>To respond to an Event of this type, you need a Consumer that accepts the data this event produces - so if it's
* a {@code ConsumerEvent<Integer>}, you need to register a {@code Consumer<Integer>}. You can also optionally supply a
* registration key that can be used to unregister the event, and/or an Executor to defer event handler execution to.
*
* <p>To "provide" a new ConsumerEvent, you might do something like,
*
* <pre>
* public class Foo {
* private ConsumerEvent&lt;Integer&gt; onFoo = ConsumerEvent.create();
* private final ConsumerEvent&lt;Integer&gt; onSubmitted = ConsumerEvent.create();
*
* public ConsumerEvent&lt;Integer&gt; onFoo() {
* return onFoo;
* public ConsumerEvent&lt;Integer&gt; onSubmitted() {
* return onSubmitted;
* }
*
* public void foo(int value) {
* // (foo logic here)
* onFoo.fire(value);
* public void submit(int value) {
* // (submit logic here)
* onSubmitted.fire(value);
* }
* }
* </pre>
*
* Note that generally Events fire after their corresponding logic, since they're best used for a
* fait accomplis - where you can't change the triggering event, but you're responding to it.
* Note that generally Events fire after their corresponding logic, and their verb names are past tense, since they're
* best used for situations where you can't change the triggering event, but you're responding to it.
*
* See {@link #register(Consumer)} for more info on how to register and respond to events of this type.
*/
Expand Down Expand Up @@ -82,7 +89,7 @@ public interface ConsumerEvent<T> {
*
* <p>This is because the lambda which {@code this::throwSomething} is implicitly cast
* to, (causing the lambda to be generated by lambdaMetafactory) may be different for
* each capture site. This was done to make lambdas extremelyfast, but is inconvenient
* each capture site. This was done to make lambdas extremely fast, but is inconvenient
* for event systems. A workaround is to say instead,
*
* <pre>
Expand All @@ -101,6 +108,8 @@ public interface ConsumerEvent<T> {
* }
* </pre>
*
* or to supply a registration key where reference equality can be guaranteed.
*
* @param handler
*/
void register(Consumer<T> handler);
Expand Down Expand Up @@ -138,6 +147,11 @@ public interface ConsumerEvent<T> {
*/
void unregister(Object key);

/**
* Unregisters ALL event handlers attached to this event. If event handlers are currently firing, they may continue
* to fire until the event is complete.
*/
void clear();


public static <T> ConsumerEvent<T> create() {
Expand Down
39 changes: 25 additions & 14 deletions src/main/java/blue/endless/tinyevents/RunnableEvent.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2021 Falkreon (Isaac Ellingson)
* Copyright (c) 2021-2023 Falkreon (Isaac Ellingson)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -29,27 +29,32 @@
import blue.endless.tinyevents.impl.RunnableEventListImpl;

/**
* Implementers of this type represent an event which requires no information to respond to. Handlers
* for the event are registered as {@link java.lang.Runnable Runnable}s, hence the name of this
* interface. To "provide" a RunnableEvent, you might do something like,
* Represents an event where no information is communicated to event responders beyond the fact that the event has
* triggered.
*
* <p>To respond to an Event of this type, you need a Runnable, or a lambda that can be cast to a Runnable. You can also
* optionally supply a registration key that can be used to unregister the event, and/or an Executor to defer event
* handler execution to.
*
* <p>To "provide" a new RunnableEvent, you might do something like,
*
* <pre><code>
* public class Foo {
* private RunnableEvent onFoo = RunnableEvent.create();
* private final RunnableEvent onUpdated = RunnableEvent.create();
*
* public RunnableEvent onFoo() {
* return onFoo;
* public RunnableEvent onUpdated() {
* return onUpdated;
* }
*
* public void foo() {
* // (foo logic here)
* onFoo.fire();
* public void update() {
* // (update logic here)
* onUpdated.fire();
* }
* }
* </code></pre>
*
* Note that generally Events fire after their corresponding logic, since they're best used for a
* fait accomplis - where you can't change the triggering event, but you're responding to it.
* Note that generally Events fire after their corresponding logic, and their verb names are past tense, since they're
* best used for situations where you can't change the triggering event, but you're responding to it.
*
* See {@link #register(Runnable)} for more info on how to register and respond to events of this type.
*/
Expand Down Expand Up @@ -81,7 +86,7 @@ public interface RunnableEvent {
*
* <p>This is because the lambda which {@code this::throwSomething} is implicitly cast
* to, (causing the lambda to be generated by lambdaMetafactory) may be different for
* each capture site. This was done to make lambdas extremelyfast, but is inconvenient
* each capture site. This was done to make lambdas extremely fast, but is inconvenient
* for event systems. A workaround is to say instead,
*
* <pre><code>
Expand All @@ -100,6 +105,8 @@ public interface RunnableEvent {
* }
* </code></pre>
*
* or to supply a registration key where reference equality can be guaranteed.
*
* @param handler The event handler to be invoked when {@link #fire()} is called.
*/
void register(Runnable handler);
Expand Down Expand Up @@ -137,7 +144,11 @@ public interface RunnableEvent {
*/
void unregister(Object key);


/**
* Unregisters ALL event handlers attached to this event. If event handlers are currently firing, they may continue
* to fire until the event is complete.
*/
void clear();

public static RunnableEvent create() {
return new RunnableEventListImpl();
Expand Down
Loading

0 comments on commit 51346dd

Please sign in to comment.