Skip to content

Conversation

@PaintNinja
Copy link
Contributor

#69 mentioned the following possible future enhancement:

Direct pass-through of sealed inheritable events that only have one subclass to save memory and reduce startup time.

Consider the following example:

// in a public exported API package
public sealed interface MyEvent extends Cancellable, InheritableEvent permits MyEventImpl {
    CancellableEventBus<MyEvent> BUS = CancellableEventBus.create(MyEvent.class);

    String message();
}

// in an internal package
public record MyEventImpl(String message) implements MyEvent {
    public static final CancellableEventBus<MyEventImpl> BUS = CancellableEventBus.create(MyEventImpl.class);
}

The BUS fields currently refer to different EventBus instances, although they effectively do the same thing - regardless of whether you add a listener to the parent or the child, and regardless of whether you post the MyEventImpl instance on the parent's bus or the child's.

This PR makes the CancellableEventBus.create(MyEventImpl.class); call return the bus of the parent instead of creating a new instance, reducing memory usage by sharing the same instance across both classes.

Direct pass-through of sealed inheritable events that only have one subclass to save memory and reduce startup time.
@PaintNinja PaintNinja added the enhancement New feature or request label Sep 19, 2025
@PaintNinja PaintNinja marked this pull request as ready for review September 19, 2025 15:24
@LexManos
Copy link
Member

Honestly I'd be more inclined to have MyEventImpl fire on MyEvent.BUS, not even using its own BUS field.
However, on the technical side I just want to be sure that cases such as the following still work.
Sealed class with one permits, final class with separate bus, both events fired, Parent called twice, child called once.

// in a public exported API package
public sealed class DogEvent extends Cancellable, InheritableEvent permits LabradorEvent {
    CancellableEventBus<DogEvent> BUS = CancellableEventBus.create(DogEvent.class);
}

public final class LabradorEvent extends DogEvent {
    public static final CancellableEventBus<LabradorEvent> BUS = CancellableEventBus.create(LabradorEvent.class);

    private final String type;
    public LabradorEvent(String type) {
        this.type = type;
    }    
    
    public String getType() { return this.type; }
}

new DogEvent().post();
new LabradorEvent("golden").post();

@Jonathing
Copy link
Member

The internal code is a little hard to grasp, but I understand what's going on here. So to put it in list form:

If:

  • An event is inheritable
  • It is sealed
  • It only permits a single subtype

Then:

  • Delegate to only using the parent's bus if it exists.

But I agree with what Lex is saying here as well. It should be better to just point to the parent's event bus. However, if and only if it has access to it. I can see the case where you want to hide the bus from consumers by grabbing the parent instead.

So I'm unsure about the practicality of this. But the internal code looks sane to me.

@PaintNinja
Copy link
Contributor Author

So long story short I wrote this PR while tired and forgot that you could just... post the child instance on the interface bus (e.g. MyEvent.BUS.post(new MyEventImpl("")), so the idea was to save some memory by internally redirecting calls of the child bus directly to the parent's.

There may be edge-cases crossing module boundaries and indirectly posting an event multiple inheritance hierarchies deep where you couldn't directly post the child event instance on the parent interface's bus but these are very rare and wouldn't be covered by this optimisation anyway, so closing the PR.

@PaintNinja PaintNinja closed this Nov 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants