EventSys has two modes of listener method invocation, first is generating a class which implements EventListener
,
example, if the listener is called MyListener
and the listener methods is listen
:
Obs: Classes was decompiled with IntelliJ Built-in FernFlower
public class listen$0 implements EventListener<MvEvent> {
private final MyListener $instance;
public listen$0(MyListener $instance) {
this.$instance = $instance;
}
@Override
public void onEvent(Event event, Object pluginContainer) {
this.$instance.listen((MvEvent)event);
}
@Override
public EventPriority getPriority() {
return EventPriority.NORMAL;
}
@Override
public int getPhase() {
return -1;
}
@Override
public boolean getIgnoreCancelled() {
return false;
}
}
For destruction, the class generator generates the destruction on onEvent
method:
public class listen$0 implements EventListener<MyGenericEvent<Integer>> {
private final MyListener $instance;
public listen$0(MyListener $instance) {
this.$instance = $instance;
}
@Override
public void onEvent(Event event, Object pluginContainer) {
MyListener var10000 = this.$instance;
MyGenericEvent var10001 = (MyGenericEvent)event;
GetterProperty var10002 = event.getGetterProperty(Integer.class, "obj");
if (var10002 != null) {
if (var10002 != null) {
var10000.listen2(var10001, (Integer)var10002.getValue());
}
}
}
@Override
public EventPriority getPriority() {
return EventPriority.NORMAL;
}
@Override
public int getPhase() {
return -1;
}
@Override
public boolean getIgnoreCancelled() {
return false;
}
}
Annotation properties are also inlined on the generated class, example, given following class:
@Listener(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void listen3(MyGenericEvent<Integer> event, @Name("obj") Integer i) {
System.out.println("Value: " + i);
}
Generator would generate a class like that:
public class listen$0 implements EventListener<MyGenericEvent<Integer>> {
private final MyListener $instance;
public listen$0(MyListener $instance) {
this.$instance = $instance;
}
@Override
public void onEvent(Event event, Object pluginContainer) {
MyListener var10000 = this.$instance;
MyGenericEvent var10001 = (MyGenericEvent)event;
GetterProperty var10002 = event.getGetterProperty(Integer.class, "obj");
if (var10002 != null) {
if (var10002 != null) {
var10000.listen3(var10001, (Integer)var10002.getValue());
}
}
}
@Override
public EventPriority getPriority() {
return EventPriority.HIGHEST;
}
@Override
public int getPhase() {
return -1;
}
@Override
public boolean getIgnoreCancelled() {
return true;
}
}
The second way is using Java 7 MethodHandle
(and yes, destruction is supported).
Given following event class:
public interface MyTestEvent extends Event {
@NotNullValue
String getName();
int getAmount();
void setAmount(int amount);
default void applyToAmount(IntUnaryOperator operator) {
this.setAmount(operator.applyAsInt(this.getAmount()));
}
}
Generator would generate an event class like that:
public class MyTestEventImpl implements MyTestEvent {
private int amount;
private final String name;
private final Map<String, Property> #properties = new HashMap();
private final Map<String, Property> immutable#properties;
public MyTestEventImpl(@Name("amount") int amount, @Name("name") String name) {
this.immutable#properties = Collections.unmodifiableMap(this.#properties);
Objects.requireNonNull(name);
this.amount = amount;
this.name = name;
this.#properties.put("amount", new IntGSProperty.Impl(this::getAmount, this::setAmount));
this.#properties.put("name", new GetterProperty.Impl(String.class, this::getName));
}
public int getAmount() {
return this.amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getName() {
return this.name;
}
public <T> T getExtension(Class<T> extensionClass) {
return null;
}
@Override
public Map<String, Property> getProperties() {
return this.immutable#properties;
}
// Note: A lot of bridge methods are generated to call DefaultImpls of PropertyHolder class
}
Note that EventGenerator
uses specialized versions of Property
for primitive types, and generates Objects.requireNonNull
for @NotNullValue
annotated properties.
For generic events, the type information of event should be provided to event class.
Given following event interface:
public interface MyGenericEvent<T> extends Event {
@NotNullValue
T getObj();
}
And following factory:
<T> MyGenericEvent<T> createMyGenericEvent(@TypeParam TypeInfo<MyGenericEvent<T>> type, @Name("obj") T obj);
Following event class will be generated to createMyGenericEvent(new AbstractTypeInfo<MyGenericEvent<String>>(){}, "A")
:
public class MyGenericEventImpl implements MyGenericEvent {
private final Object obj;
private final Map<String, Property> #properties = new HashMap();
private final Map<String, Property> immutable#properties;
private final TypeInfo<MyGenericEvent> eventTypeInfo;
public MyGenericEventImpl(@TypeParam @Name("eventTypeInfo") TypeInfo<MyGenericEvent> eventTypeInfo, @Name("obj") Object obj) {
this.immutable#properties = Collections.unmodifiableMap(this.#properties);
Objects.requireNonNull(obj);
Objects.requireNonNull(eventTypeInfo);
this.eventTypeInfo = eventTypeInfo;
this.obj = obj;
this.#properties.put("obj", new Impl(Object.class, this::getObj));
}
public Object getObj() {
return this.obj;
}
@Override
public TypeInfo<? extends Event> getEventTypeInfo() {
return this.eventTypeInfo;
}
// toString()
public <T> T getExtension(Class<T> extensionClass) {
return null;
}
@Override
public Map<String, Property> getProperties() {
return this.immutable#properties;
}
}
Yes, you need to provide a TypeInfo<...>
for event construction, you can also construct the event without the type information, but dispatch
methods will only dispatch for listeners which listen to event without the type or with bound type, for example, if you construct MyGenericEvent
without type information and dispatch
it without TypeInfo
, only listeners which listen to MyGenericEvent
and MyGenericEvent<Object>
will be invoked. If you dispatch
the event with TypeInfo
event will be also be dispatched correctly.
Since 1.3
If you use generic events, PropertyHolder.lookup
will be called instead of PropertyHolder.getGetterProperty
in event destruction.
Events can be also lazily generated by factories, this means that event classes will not be generated before the factory is generated, they will be generated on-demand, there are currently two modes of lazy generation:
- Reflection + PropertySorter
- InvokeDynamic + Bootstrap with fallback
The first call the EventGenerator
to generate event class and uses PropertySorter
class to sort event arguments to follow constructor property order and call it using Constructor
instance (Reflectively).
The second delegate the call to FactoryBootstrap
, which implements the generation and sorting logic (and a simple cache).
The second mode is the default mode
public MyGenericEvent createMyGenericEvent(TypeInfo eventTypeInfo, Object obj) {
Class eventClass = this.eventGenerator.createEventClass(TypeInfo.of(MyGenericEvent.class), Collections3.listOf(new Object[0]), Collections3.listOf(new Object[0]));
Constructor ctr = eventClass.getDeclaredConstructors()[0];
Object[] sorted = PropertiesSort.sort(ctr, new String[]{"eventTypeInfo", "obj"}, new Object[]{(Object)eventTypeInfo, obj});
return (MyGenericEvent)ctr.newInstance(sorted);
}
Bytecode:
public com.github.koresframework.eventsys.test.event.MyGenericEvent createMyGenericEvent(com.github.jonathanxd.iutils.type.TypeInfo, java.lang.Object);
descriptor: (Lcom/github/jonathanxd/iutils/type/TypeInfo;Ljava/lang/Object;)Lcom/github/projectsandstone/eventsys/test/event/MyGenericEvent;
flags: ACC_PUBLIC
Code:
stack=6, locals=6, args_size=3
0: aload_0
1: getfield #16 // Field eventGenerator:Lcom/github/projectsandstone/eventsys/gen/event/EventGenerator;
4: ldc #33 // class com/github/projectsandstone/eventsys/test/event/MyGenericEvent
6: invokestatic #39 // Method com/github/jonathanxd/iutils/type/TypeInfo.of:(Ljava/lang/Class;)Lcom/github/jonathanxd/iutils/type/TypeInfo;
9: iconst_0
10: anewarray #4 // class java/lang/Object
13: invokestatic #45 // Method com/github/jonathanxd/iutils/collection/Collections3.listOf:([Ljava/lang/Object;)Ljava/util/List;
16: iconst_0
17: anewarray #4 // class java/lang/Object
20: invokestatic #45 // Method com/github/jonathanxd/iutils/collection/Collections3.listOf:([Ljava/lang/Object;)Ljava/util/List;
23: invokeinterface #51, 4 // InterfaceMethod com/github/projectsandstone/eventsys/gen/event/EventGenerator.createEventClass:(Lcom/github/jonathanxd/iutils/type/TypeInfo;Ljava/util/List;Ljava/util/List;)Ljava/lang/Class;
28: astore_3
29: aload_3
30: invokevirtual #57 // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
33: iconst_0
34: aaload
35: astore 4
37: aload 4
39: iconst_2
40: anewarray #59 // class java/lang/String
43: dup
44: iconst_0
45: ldc #60 // String eventTypeInfo
47: aastore
48: dup
49: iconst_1
50: ldc #61 // String obj
52: aastore
53: iconst_2
54: anewarray #4 // class java/lang/Object
57: dup
58: iconst_0
59: aload_1
60: checkcast #4 // class java/lang/Object
63: aastore
64: dup
65: iconst_1
66: aload_2
67: aastore
68: invokestatic #67 // Method com/github/projectsandstone/eventsys/reflect/PropertiesSort.sort:(Ljava/lang/reflect/Constructor;[Ljava/lang/String;[Ljava/lang/Object;)[Ljava/lang/Object;
71: astore 5
73: aload 4
75: aload 5
77: invokevirtual #73 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;
80: areturn
LocalVariableTable:
Start Length Slot Name Signature
71 10 5 sorted [Ljava/lang/Object;
35 46 4 ctr Ljava/lang/reflect/Constructor;
28 53 3 eventClass Ljava/lang/Class;
0 81 2 obj Ljava/lang/Object;
0 81 1 eventTypeInfo Lcom/github/jonathanxd/iutils/type/TypeInfo;
0 81 0 this Lcom/github/projectsandstone/eventsys/test/factory/MyFactory$Impl;
LineNumberTable:
line 3: 0
line 4: 29
line 5: 37
line 6: 73
MethodParameters:
Name Flags
eventTypeInfo
obj
public MyGenericEvent createMyGenericEvent(TypeInfo eventTypeInfo, Object obj) {
return this.eventGenerator.create<invokedynamic>(this.eventGenerator, TypeInfo.of(MyGenericEvent.class), Collections3.listOf(new Object[0]), Collections3.listOf(new Object[0]), new String[]{"eventTypeInfo", "obj"}, new Object[]{(Object)eventTypeInfo, obj});
}
Bytecode:
public com.github.koresframework.eventsys.test.event.MyGenericEvent createMyGenericEvent(com.github.jonathanxd.iutils.type.TypeInfo, java.lang.Object);
descriptor: (Lcom/github/jonathanxd/iutils/type/TypeInfo;Ljava/lang/Object;)Lcom/github/projectsandstone/eventsys/test/event/MyGenericEvent;
flags: ACC_PUBLIC
Code:
stack=9, locals=3, args_size=3
0: aload_0
1: getfield #16 // Field eventGenerator:Lcom/github/projectsandstone/eventsys/gen/event/EventGenerator;
4: ldc #24 // class com/github/projectsandstone/eventsys/test/event/MyGenericEvent
6: invokestatic #30 // Method com/github/jonathanxd/iutils/type/TypeInfo.of:(Ljava/lang/Class;)Lcom/github/jonathanxd/iutils/type/TypeInfo;
9: iconst_0
10: anewarray #4 // class java/lang/Object
13: invokestatic #36 // Method com/github/jonathanxd/iutils/collection/Collections3.listOf:([Ljava/lang/Object;)Ljava/util/List;
16: iconst_0
17: anewarray #4 // class java/lang/Object
20: invokestatic #36 // Method com/github/jonathanxd/iutils/collection/Collections3.listOf:([Ljava/lang/Object;)Ljava/util/List;
23: iconst_2
24: anewarray #38 // class java/lang/String
27: dup
28: iconst_0
29: ldc #39 // String eventTypeInfo
31: aastore
32: dup
33: iconst_1
34: ldc #40 // String obj
36: aastore
37: iconst_2
38: anewarray #4 // class java/lang/Object
41: dup
42: iconst_0
43: aload_1
44: checkcast #4 // class java/lang/Object
47: aastore
48: dup
49: iconst_1
50: aload_2
51: aastore
52: invokedynamic #51, 0 // InvokeDynamic #0:create:(Lcom/github/projectsandstone/eventsys/gen/event/EventGenerator;Lcom/github/jonathanxd/iutils/type/TypeInfo;Ljava/util/List;Ljava/util/List;[Ljava/lang/String;[Ljava/lang/Object;)Lcom/github/projectsandstone/eventsys/test/event/MyGenericEvent;
57: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 58 2 obj Ljava/lang/Object;
0 58 1 eventTypeInfo Lcom/github/jonathanxd/iutils/type/TypeInfo;
0 58 0 this Lcom/github/projectsandstone/eventsys/test/factory/MyFactory$Impl;
LineNumberTable:
line 2: 0
MethodParameters:
Name Flags
eventTypeInfo
obj
BootstrapMethods:
0: #87 invokestatic com/github/projectsandstone/eventsys/bootstrap/FactoryBootstrap.factoryBootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
Both have similar performance (and is very good).