How to make cancellable events?

I’m learning how to create events and fire them from my plugin, so other plugins or mods can listen to those events and do stuff. I think I’ve already learned how to make a simple event, since what I tried is this:
PluginA implements two classes, a base class that implements the Event interface

public abstract class EventBase implements Event {

    private final Object source;

    public EventBase(Object source) {
        this.source = source;
        execute();
    }

    protected void execute() {
        handle();
    }

    protected abstract void handle();

    @Override
    public Object getSource() {
        return this.source;
    }

    @Override
    public EventContext getContext() {
        return EventContext.builder().build();
    }

    @Override
    public Cause getCause() {
        Cause.Builder causeBuilder = Cause.builder()
                .append(UniverseGuard.getInstance().getPluginContainer());
        if(getSource() != null) {
            causeBuilder.append(getSource());
        }
        return causeBuilder.build(getContext());
    }
}

and a derived class that implements the Cancellable interface

public abstract class CancellableEvent extends EventBase implements Cancellable {

    private boolean isCancelled;

    public CancellableEvent(Object source) {
        super(source);
    }

    @Override
    public boolean isCancelled() {
        return this.isCancelled;
    }

    @Override
    public void setCancelled(boolean cancelled) {
        this.isCancelled = cancelled;
    }

    @Override
    protected void execute() {
        if(!this.isCancelled()) {
            super.execute();
        }
    }
}

Then there is a third class, a Test Event class

public class TestEvent extends CancellableEvent {

    public TestEvent() {
        super();
    }

    @Override
    protected void handle() {
        LogUtils.info("Test Event");
    }
}

where LogUtils is just a class to log stuff.
Finally, PluginA fires the event during the GameStartedServerEvent

@Listener
public final void onServerStart(final GameStartedServerEvent event) {
    Sponge.getEventManager().post(new TestEvent());
}

At this point, PluginB defines it’s own listener for that event

public class TestCancellable {

    @Listener
    public void onTestEvent(TestEvent event) {
        LogApi.info("Handling test event");
        event.setCancelled(true);
    }
}

and register it in the GameInitializationEvent

@Listener
public void onGameInitialization(GameInitializationEvent event) {
   Sponge.getEventManager().registerListeners(this, new TestCancellable());
}

By doing this, the listener catches the event but of course can’t cancel it.
So, what I’m trying to achieve is that when PluginA fires the event, something will occur, UNLESS PluginB cancels this. Is this possible?

So when a plugin fires an event, that instance of the event is used, therefore all modifications done in handlers are applied to that same instance of that event. Therefore you can detect if the event has been cancelled or not like so

@Listener
public final void onServerStart(final GameStartedServerEvent event) {
    TestEvent event = new TestEvent();
    Sponge.getEventManager().post(event);
    if(event.isCancelled()){
       //event was cancelled so handle the cancel
    }
}

Yes, but the code inside the handle function of the event is executed before, when the instance is created by calling the constructor. From what I understand reading also the doc, I should move that code totally outside of the event class, since an event class is just used to notify that “ehy, something is happening or happened!” but the event class itself shouldn’t do anything

Yeah, the handling of event should occur outside the event class, as (as you picked up on) the event class should just be the event data and functions related to the event.

For example

public class CancelEvent {

    boolean cancelled;

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

    @Override
    public void setCancelled(boolean check){
        this.cancelled = check;
    }
}

The event

public class Handle {
    public void onEvent(){
        CancelEvent event = new CancelEvent();
        Sponge.getEventManager().registerListeners(this, event);
        if (event.isCancelled()){
            return;
        }
        System.out.println("Event was not cancelled");
    }
}
1 Like