genaray/Arch

Event handling via generics instead of conditional compilation

stanoddly opened this issue · 3 comments

I think that there is a better approach to optionally handle events than conditional compilation. .NET does some smart optimizations based on generics and struct generic argument, for example:

public interface IEventsHandler
{
    // ...
}

// a struct for compiler optimization
public struct NullEventsHandler: IEventsHandler
{
    // empty implementations with aggressively inlined methods
}

// World with generic TEventsHandler, which would have nicely optimized calls for NullEventsHandler
public class World<TEventsHandler> where TA: IEventsHandler
{
    // ...
}

public static class World
{
    public World<NullEventsHandler> Create() { /* ... */}
    public World<TEventsHandler> Create(TEventsHandler handler) where TEventsHandler: IEventsHandler  { /* ... */}
}

The only issue is that a generic World is returned. If that's a problem, an interface of IWorld could be created and returned instead. If an only one class exists for such interface, that would be optimized too (devirtualization).

The approach with generics has been successfully utilized in pebuphysics2:
https://github.com/bepu/bepuphysics2/blob/a763813/Demos/Demos/SimpleSelfContainedDemo.cs

Specifically it's described here:
https://github.com/bepu/bepuphysics2/blob/a763813/Demos/Demos/SimpleSelfContainedDemo.cs#L20

I've just realized that World can just inherit from World<NullEventsHandler>, and the Arch API would stay compatible, e.g.:

public class World: World<NullEventsHandler>
{
    // shadows World<NullEventsHandler>.Create
    public static World Create(/*...*/)
    {
        // ...
    }
}

So, if anyone wants to use events, they may simply use World<MyEventsHandler>.Create instead of World.Create.

This is definitely worth considering. This could simplify some things and it has the advantage that the performance remains stable. Unfortunately I'm currently working on some other features, but that would also be worth considering ^^

How would you then listen for individual component changes? That doesn't really make sense to me right now.

E.g.
world.OnAdded<Velocity>(() => {});

So how would this generic world variant implement this?