An event system for Processing 4!

I am back with a better version of event bindings for Processing. This system can be used with Processing 4 and Eclipse. Unfortunately, it cannot be used with Processing 3 because the library requires Java version 8+ for lambdas () -> {} and the double colon operator ::

Usage

  • create a final Event.PX<...> onEvent; replace the ... with the parameter types you would like to pass in your event. Now replace the X in PX by the number of arguments you passed, for instance: final Event.P2<Integer, String> onEvent;. To initialize an event use = Event.PX.create() or alternatively = new Event.PX<>() with the X being replaced same as above.
  • create a listener final Listener.PX listener = Object::method; the :: operator is called the method reference operator. You can reference any method you wish to execute when the event triggers. You can also alternetively specify a lambda also called a functional interface () -> {}.
  • bind the listener to the event: onEvent.bind(listener)
  • onEvent.trigger(...) whenever you are are ready

Those are the basics. An example is also introduced to really visualize how it works.

Features

  • Reflectionless implementation of events using functional interfaces and generics
  • Support for double colon operator :: for example this::onSetup
  • Error handeling, for instance parameter check and listener method reference
  • trigger(...)
  • bind(method) / unbind(method) / unbind()
  • bind(method, priority, ignoreCancelled) listener priority and cancel handeling
  • bound(method)
  • cancel() / setCancelled(cancelled) / isCancelled()
  • Event.flush(object) failsafe measure in uncontrolled cases to prevent memory leaks
  • Variable<T> implementation to modify the impact of events
  • Events can pass from 0 to 9 parameters by default, generate more at your needs

Example

In this example, there is a world listening and a player emitting events.

public void setup() {
	World world = new World();
}

// this object host events
// note that it could also listen to events
public static class Player {
	
	public final Event.P0 onJump;
	public final Event.P1<Variable<Integer>> onCollect;
	
	int x, y, points;
	
	public Player() {
		onJump = Event.P0.create();
		onCollect = Event.P1.create(true);
	}
	
	public void jump() {
		y += 10;
		
		// event
		onJump.trigger();
	}
	
	public void collect(int points) {
		Variable<Integer> variable = Variable.of(points);
		
		// event
		onCollect.trigger(variable);
		
		// check if event was cancelled, if not continue the process
		if(!onCollect.isCancelled() && !variable.isNull())
			this.points += points;
	}
}

// this object listens to the player's events
public static class World {
	
	public final Listener.P0 onPlayerJump = this::onPlayerJump;
	public final Listener.P1<Variable<Integer>> onPlayerCollect = this::onPlayerCollect;
	public final Listener.P1<Variable<Integer>> onPlayerCollected = this::onPlayerCollected;
	
	Player player;
	
	boolean doublePoints;
	boolean noPoints;
	boolean test;
	
	public World() {
		player = new Player();
		player.onJump.bind(onPlayerJump);
		player.onCollect.bind(onPlayerCollect, 0);
		player.onCollect.bind(onPlayerCollected, 1, true);
		
		// --- playground ---
		
		player.jump();
		
		player.collect(100);
		
		doublePoints = true;
		
		player.collect(100);
		
		noPoints = true;
		
		player.collect(1000);
		
		player.onJump.unbind(onPlayerJump);
		
		player.jump();
	}
	
	// feedback for when the player jumps
	public void onPlayerJump() {
		System.out.println("Jump! :D");
		
		// WARNING: this is not allowed. cancel() can only be called:
		// #1 on events that are cancellable: 'Event.PX.create(true)' 
		// #2 inside a listener method, othwerwise will throw an exception
		if(test) player.onJump.cancel();
	}
	
	// apply no points and double points modifiers to points collected by player
	public void onPlayerCollect(Variable<Integer> points) {
		if(noPoints)
			player.onCollect.cancel();
		else
			points.apply((p) -> doublePoints? p * 2 : p);
		
		System.out.println("[info] applied points modifier");
	}
	
	// feedback for when the player collects points.
	// two listeners are bound to the same event, but this one
	// has a higher priority so it is executed last showing the final
	// points collected. This will not execute if ignoreCancelled is
	// set to true during binding.
	public void onPlayerCollected(Variable<Integer> points) {
		System.out.println("Player collected " + points.get() + " points!");
	}
	
	public void flush() {
		
		// WARNING: will not unbind, the operator :: creates a new immutable reference
		player.onJump.unbind(this::onPlayerJump);
		
		// that's why we kept a final reference Listener for each
		player.onJump.unbind(onPlayerJump);
		player.onCollect.unbind(onPlayerCollect);
		
		// WARNING: since binding has a reference to the listener's container class method
		// this can cause memory leaks if not used properly
		// in uncontrolled scenarios, this method can be used as a failsafe when you are
		// done with an object.
		Event.flush(this);
	}
}

Source code

This library is hosted on GitHub, add the files Event.java, Listener.java and Variable.java to your project to use the library.

If you require events that need more than 9 variables, you can execute Generator.java to expand beyond the limit at your needs.

Good things about this

  • It is not interface based, which almost eliminates boilerplate code
  • It is not reflection based, which makes it a little bit more performant than v1. It does include an alternative reflectionTrigger(...) if needed.
  • You can listen to an event and host an event from anywhere: the main class, in no class, in the same class…
  • Error handling is probably the #1 reason why I decided to make a version 2. Parameters are all checked and listener methods are referenced
  • Added event priority and event cancellation

This is the GitHub repository for the library

4 Likes