Event binding for Processing!

This is a simple but awesome library! It provides events for your processing sketches. These events could be created in your classes or simply globally. A couple of examples: onLoaded, onOverlap, onHit, onMove, onOpen, onDestroy, onThreadTerminate

Usage

  • create an Event in the class that triggers the event. Although, events could also be triggered somewhere else.
  • pass in the Event(...) constructor the parameter classes that will be passed to the listener functions
  • call Event::trigger(...) and pass in the parameters of the event. Must match the types passed in the constructor
  • to add listeners create a function with the matching parameters of the specific Event. Then call bind(Object listener, String functionName)

Example

In this example, there is a world object listening to the player’s events. The player is initialized before the world because the world needs the player to exists. Alternatively, the bindings could of took place in a function executed after everyone is initialized.

World world;
Player player;

public void setup() {
    
    // Make sure you bind to an object that already exists!
    player = new Player();
    world = new World();
    
    // Initialize player stuff
    player.start();
    
    // Walk after 1sec
    delay(1000);
    player.walk();
    
    // Jump after 1sec
    delay(1000);
    player.jump();
    
    println("bound? " + player.onWalk.bound(world, "onPlayerWalk"));
    
    // unbind onWalk, next walk() should not print to console
    player.onWalk.unbind(world, "onPlayerWalk");
    
    println("bound? " + player.onWalk.bound(world, "onPlayerWalk"));
    
    // Walk after 1sec
    delay(1000);
    player.walk();
}

public void draw() {
    // empty
}

public class Player {
    public String name;
    
    // events
    public Event onReady;
    public Event onJump;
    public Event onWalk;
    
    public Player() {
        onReady = new Event(Player.class);
        onJump = new Event(Player.class, PVector.class);
        onWalk = new Event(Player.class, float.class, int.class);
        
        name = "Stephcraft";
    }
    
    public void start() {
        // ... resource loading code
        // ... setup code
        
        // trigger event
        onReady.trigger(this);
    }
    
    public void walk() {
        float speed = 100.0f;
        int direction = speed > 0 ? RIGHT : LEFT;
        
        // ... walk logic
        
        // trigger event
        onWalk.trigger(this, speed, direction);
    }
    
    public void jump() {
        PVector velocity = new PVector(0, -10);
        
        // ... jump logic
        
        // trigger event
        onJump.trigger(this, velocity);
    }
}

public class World {
    
    public World() {
        player.onReady.bind(this, "onPlayerReady");
        player.onWalk.bind(this, "onPlayerWalk");
        player.onJump.bind(this, "onPlayerJump");
    }
    
    public void onPlayerReady(Player player) {
        println("<Event> Player \""+ player.name +"\" is ready!");
    }
    
    public void onPlayerWalk(Player player, float speed, int direction) {
        println("<Event> Player is walking!");
    }
    
    public void onPlayerJump(Player player, PVector velocity) {
        println("<Event> Player jumped!");
    }
}

Documentation

  • class Event
    • constructor() defines what are the parameter types of the event
    • bind() will bind a function listener to the event
    • unbind() will unbind a function listener to the event
    • bound() returns a boolean representing if the function listener is already bound to the event or not

Source code

This library was made possible thanks to java reflect.

import java.lang.reflect.Method;
import java.util.Arrays;

public class Event {
    private Class<?>[] args;
    private ArrayList<EventListener> eventListeners;
    
    private class EventListener {
        public Method method;
        public Object listener;
        
        public EventListener(Object listener, Method method) {
            this.listener = listener;
            this.method = method;
        }
        
        public void invoke(Object[] arguments) {
            try {
                method.invoke(listener, arguments);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        @Override
        public boolean equals(Object obj) {
            if(obj instanceof EventListener) {
                EventListener other = (EventListener)obj;
                return other.listener.equals(listener) && other.method.equals(method);
            }
            
            return false;
        }
        
        @Override
        public int hashCode() {
            int className = method.getDeclaringClass().getName().hashCode();
            int functionName = method.getName().hashCode();
            int parameters = method.getParameterTypes().hashCode();
            
            return className ^ functionName ^ parameters;
        }
    }
    
    public Event(Class<?>... args) {
        this.args = args;
        this.eventListeners = new ArrayList<EventListener>();
    }
    
    public void trigger(Object... arguments) {
        for(EventListener eventListener : eventListeners) {
            try {
                eventListener.invoke(arguments);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public void bind(Object listener, String name) {
        Class<?> c = listener.getClass();
        
        try {
            Method method = c.getMethod(name, args);
            EventListener eventListener = new EventListener(listener, method);
            
            if(!eventListeners.contains(eventListener)) {
                eventListeners.add(new EventListener(listener, method));
            }
            else {
                System.err.println("<Error> could not bind event listener \""+name+"\". It is already bound!");
            }
        }
        catch(Exception e) {
            System.err.println("<Error> could not bind event listener \""+name+"\". Make sure the listener and the function is valid. The parameter types must match the ones provided in the Event constructor");
        }
    }
    
    public void unbind(Object listener, String name) {
         Class<?> c = listener.getClass();
         
         try {
             Method method = c.getMethod(name, args);
             EventListener eventListener = new EventListener(listener, method);
             
             if(eventListeners.contains(eventListener)) {
                 eventListeners.remove(eventListener);
             }
             else {
                 System.err.println("<Error> could not unbind event listener \""+name+"\". Make sure the listener is already bound");
             }
         }
         catch(Exception e) {
            System.err.println("<Error> could not unbind event listener \""+name+"\". Make sure the listener and the function is valid. The parameter types must match the ones provided in the Event constructor");
        }
    }
    
    public boolean bound(Object listener, String name) {
        Class<?> c = listener.getClass();
         
         try {
             Method method = c.getMethod(name, args);
             EventListener eventListener = new EventListener(listener, method);
             
             return eventListeners.contains(eventListener);
         }
         catch(Exception e) {
            System.err.println("<Error> Event : invalid function \""+name+"\" provided in bound()");
        }
        
        return false;
    }
}

Advantages of Event library

  • You do not need to implement an interface for each classes that need to listen to the event
  • You do not need to create an interface per event
  • You do not need to create all you function listener “templates” with the keyword default in a centralized interface
  • This library has nothing to do with interface
  • You can listen to an event and host an event in the main class

Inspiration

  • This post inspired me to make this.
  • The functionnality of this library is the same concept as the built in registerMethod processing function see here

This library will be used for the open source video game made with processing known as Project 16x16.

Code hosted on GitHub here.

8 Likes

@Stephcraft – thanks so much for sharing this.

Have you considered packaging this as a Processing Library and submitting it for inclusion on the libraries page / distribution through the Processing Contributions Manager?

https://processing.org/reference/libraries/

Very neat and I suspect many sketchers will find it useful.

I suggest that you rename the bind() and unbind() methods to addListener() and removeListener() because JavaFX already uses these method names to bind / unbind properties which is different from event handling.

2 Likes