Skip to content

Latest commit

 

History

History
260 lines (226 loc) · 11.6 KB

README.md

File metadata and controls

260 lines (226 loc) · 11.6 KB

LambdaEvents

Fast and modular event library for Java.

Releases

To use LambdaEvents with Gradle/Maven you can follow the instructions on maven central or my maven server.

Gradle Template

repositories {
    mavenCentral()
}

dependencies {
    implementation "net.lenni0451:LambdaEvents:x.x.x"
}

Maven Template

<dependency>
    <groupId>net.lenni0451</groupId>
    <artifactId>LambdaEvents</artifactId>
    <version>x.x.x</version>
</dependency>

You should check maven central or my maven server for the latest version.

Jar File

You can download the jar file from my Jenkins server.
Since LambdaEvents has no dependencies you don't need to download any other jar files.

Usage

LambdaManager

To use LambdaEvents you need to create an instance of the LambdaManager class.
It is the main class of the library used to register and call events.

LambdaManager eventManager = LambdaManager.basic(generator);

Or for a thread safe version:

LambdaManager eventManager = LambdaManager.threadSafe(generator);

There is no global instance to prevent event conflicts.

Generator

Because of the dynamic nature of LambdaEvents you need to provide an IGenerator implementation.
It is used to generate the caller which calls the event listener.
The following implementations are provided:

  • ReflectionGenerator
  • MethodHandleGenerator
  • LambdaMetaFactoryGenerator
  • ASMGenerator (Requires Reflect and ASM)

Check out the JMH Benchmark section for performance comparisons.

The MethodHandleGenerator and the LambdaMetaFactoryGenerator have an optional MethodHandles.Lookup parameter.

To create your own implementation you need to implement the IGenerator interface.
The generate method is used to generate a caller for handler which take the event as a parameter.
The generateVirtual method is used to generate a caller for handler which don't take the event as a parameter.

Events

In LambdaEvents there is no need to implement an interface or extend a class to create an event.
You can pass any object as an event. Remember that the class is used to identify the event type. Inheritance is not checked and treated as a different event type.

Registering

LambdaEvents has 6 different ways to listen to events:

Type Description
Static methods A static method annotated with @EventHandler
Virtual methods A virtual/non-static method annotated with @EventHandler
(static) Runnable field A runnable field annotated with @EventHandler
(static) Consumer field A consumer field annotated with @EventHandler
Independent Runnable A runnable which is passed to the register method
Independent Consumer A consumer which is passed to the register method

All register/unregister methods have an optional event class parameter to specify the type of the event to register/unregister.
This parameter is required when registering an independent event handler.

Runnable fields or Consumer fields without type parameters require the event type(s) to be added to the @EventHandler(events = {Event.class}) annotation.
Methods are required to not have any parameters or to have the event type as a parameter. Methods without parameters require the event type(s) to be added to the @EventHandler(events = {Event.class}) annotation.
Independent Runnables or Consumers require the event type(s) to be passed to the register method.

Static event handler

To register static event handler (methods and fields) you need to call the register method of the LambdaManager instance passing the owner class.

eventManager.register(Example.class);

Virtual event handler

To register virtual event handler (methods and fields) you need to call the register method of the LambdaManager instance passing the owner object.

eventManager.register(new Example());

Independent event handler

To register independent event handler (runnables and consumers) you need to call the register method of the LambdaManager instance passing the handler, priority and event type(s).

//Runnable
Runnable handler = () -> System.out.println("called");
eventManager.register(handler, 0, Event.class);

//Consumer
Consumer<Event> handler = e -> System.out.println("called " + e);
eventManager.register(handler, 0, Event.class);

Unregistering

To unregister event handler you have to call the respective unregister method of the LambdaManager instance in the same way you registered them.

//Static
eventManager.unregister(Example.class);

//Virtual
eventManager.unregister(oldExampleInstance);

//Independent
eventManager.unregister(handler, Event.class);

You can also unregister all event handlers for a specific event type by calling the unregisterAll method.

//Unregister all event handlers for the event type
//This includes static and virtual event handlers
eventManager.unregisterAll(Event.class);

//Unregister all event handler for the event type and the given class predicate
//In this case all handlers which extend AbstractVirtualHandler are unregistered
//This includes static and virtual event handlers
eventManager.unregisterAll(Event.class, AbstractVirtualHandler.class::isAssignableFrom);

//The unregisterAll method also has an optional boolean to only unregister static/virtual event handlers
eventManager.unregisterAll(event, predicate, true /*static*/);

Calling

To call an event you need to call the call method of the LambdaManager instance passing the event object.

eventManager.call(new Event());

Priority

LambdaEvents supports priorities for event handlers which is used to determine the execution order.
The higher the priority is, the earlier the event handler is called.

Cancelling

Events

Since there is no requirement for an event to implement an interface or extend a class you have to implement the cancellation yourself.
Even though it's not a requirement, it is recommended to implement the ICancellableEvent interface.
Example:

public class Event implements ICancellableEvent {
    private boolean cancelled = false;

    public boolean isCancelled() {
        return this.cancelled;
    }

    public void setCancelled(final boolean cancelled) {
        this.cancelled = cancelled;
    }
}

public class Caller {
    public static void callEvent(final LambdaManager eventManager) {
        Event event = eventManager.call(new Event());
        if (event.isCancelled()) {
            return;
        }
        //Continue the method
        //...
    }
}

The ICancellableEvent interface is used for determining if an event handler should handle a cancelled event.
The handler can specify this by adding handleCancelled (default true) to the @EventHandler annotation.
Example:

public class Handler {
    @EventHandler
    public void handleEvent(final Event event) {
        //This handler will be called even if the event is cancelled
    }

    @EventHandler(handleCancelled = false)
    public void handleEvent(final Event event) {
        //This handler will not be called if the event is cancelled
    }
}

Call chain

To cancel the event call chain and prevent following event handlers from being executed you can throw the StopCall.INSTANCE exception.

Exception handling

Registration

If an exception is thrown during the registration process, the exception will be thrown to the caller.

Calling

If an exception is thrown by an event handler, the ExceptionHandler of the LambdaManager instance will be called.
It receives the handler, the event and the thrown exception as parameters.
By default the ExceptionHandler will print the stack trace of the exception to the console (System.err).

Event Filter

To make sure the LambdaManager only registers only the correct event types you can use the IEventFilter.
It is called with the event type and a type from where the filter was called.
The filter can return true to allow the registration/calling of the event or false to skip it. An exception can be thrown to notify the caller that the event type is not allowed.
Check types:

Type Description
CALL The filter was called from the call or the callParents method
REGISTER The filter was called from the register method without an explicit event type (wildcard registration)
EXPLICIT_REGISTER The filter was called from the register method with an explicitly specified event type

The unregister method does not call the filter.
Example filter:

public boolean check(final Class event, final CheckType checkType) {
    if (event instanceof EventBase) return true;
    if (CheckType.CALL.equals(checkType)) throw new IllegalArgumentException();
    if (CheckType.EXPLICIT_REGISTER.equals(checkType)) throw new IllegalArgumentException();
    return false;
}

This filter will allow any event which extends EventBase.
If an invalid event is passed to the call method an IllegalArgumentException will be thrown.
If an event is registered explicitly an IllegalArgumentException will be thrown.
Any other event will be blocked without throwing an exception (silent block).

JMH Benchmark

The Benchmark shows the average time it takes to call an event 100_000 times.
The lower the time is, the better the call performance of the generator is.
The tests were run using Java 17 and may vary on other Java versions.

Benchmark Mode Cnt Score Error Units
CallBenchmark.callASM avgt 4 1253339,286 81976,516 ns/op
CallBenchmark.callLambdaMetaFactory avgt 4 1270657,312 133794,400 ns/op
CallBenchmark.callMethodHandles avgt 4 1893870,724 569223,318 ns/op
CallBenchmark.callReflection avgt 4 1466385,654 256169,146 ns/op