Thread overview
Improve a simple event handler
Jan 15, 2022
JN
Jan 16, 2022
JN
Jan 17, 2022
Sebastiaan Koppe
Jan 19, 2022
Hipreme
Jan 18, 2022
Salih Dincer
January 15, 2022

I am writing a simple event handler object for observer pattern.

https://gist.github.com/run-dlang/d58d084752a1f65148b33c796535a4e2

(note: the final implementation will use an array of listeners, but I want to keep it simple for now and have only one handler per event).

Is there some way I could improve this with some D features? My main gripes with it are:

  1. generic typing. I wish event handler methods such as onResize and onWindowTitle could take concrete types like WindowResize and WindowTitle rather than the generic Event struct.

  2. same when emitting the event. It feels a bit verbose.

I don't want to use classes or polymorphism. I was thinking perhaps std.variant could be useful here, although I am not exactly sure how it would work in practice.

January 16, 2022

On Saturday, 15 January 2022 at 23:15:16 UTC, JN wrote:

>

Is there some way I could improve this with some D features? My main gripes with it are:

Managed to dramatically simplify it to 10 lines of code with variadic templates.

import std.stdio;

struct Event(T...)
{
    void function(T)[] listeners;

    void addListener(void function(T) handler)
    {
        listeners ~= handler;
    }

    void emit(T args)
    {
        foreach (listener; listeners)
        {
            listener(args);
        }
    }
}

void onResize(uint newWidth, uint newHeight)
{
    writefln("Resized: %d %d", newWidth, newHeight);
}

void main()
{
    Event!(uint, uint) windowResizeEvent;
    windowResizeEvent.addListener(&onResize);

    windowResizeEvent.emit(1000, 2000);
}

I am very happy with this solution.

January 17, 2022

On Sunday, 16 January 2022 at 20:01:09 UTC, JN wrote:

>

On Saturday, 15 January 2022 at 23:15:16 UTC, JN wrote:

>

Is there some way I could improve this with some D features? My main gripes with it are:

Managed to dramatically simplify it to 10 lines of code with variadic templates.

import std.stdio;

struct Event(T...)
{
    void function(T)[] listeners;

    void addListener(void function(T) handler)
    {
        listeners ~= handler;
    }

    void emit(T args)
    {
        foreach (listener; listeners)
        {
            listener(args);
        }
    }
}

void onResize(uint newWidth, uint newHeight)
{
    writefln("Resized: %d %d", newWidth, newHeight);
}

void main()
{
    Event!(uint, uint) windowResizeEvent;
    windowResizeEvent.addListener(&onResize);

    windowResizeEvent.emit(1000, 2000);
}

I am very happy with this solution.

Looks good. But do note that with larger applications it inevitably becomes a ball of spaghetti, making it hard to understand why a certain widget behaves the way it does (or doesn't).

The other problem - already apparent in this small example - is the absence of removeListener. It is a very crucial and often overlooked part that often only gets written afterwards. The problem is that it ties into lifetime which is hard to bolt on.

For small things though, it works wonderfully.

January 18, 2022

On Saturday, 15 January 2022 at 23:15:16 UTC, JN wrote:

>

I am writing a simple event handler object for observer pattern.

https://gist.github.com/run-dlang/d58d084752a1f65148b33c796535a4e2

(note: the final implementation will use an array of listeners,

Did you especially make an effort not to use money DI (Dependency injection)? Your codes will be more delicious if you implement DI. You can find its simple implementation here.

If you get the ConnectionSetup working, you can setup without knowing the service parameters.

Salih

January 19, 2022

On Sunday, 16 January 2022 at 20:01:09 UTC, JN wrote:

>

On Saturday, 15 January 2022 at 23:15:16 UTC, JN wrote:

>

Is there some way I could improve this with some D features? My main gripes with it are:

Managed to dramatically simplify it to 10 lines of code with variadic templates.

import std.stdio;

struct Event(T...)
{
    void function(T)[] listeners;

    void addListener(void function(T) handler)
    {
        listeners ~= handler;
    }

    void emit(T args)
    {
        foreach (listener; listeners)
        {
            listener(args);
        }
    }
}

void onResize(uint newWidth, uint newHeight)
{
    writefln("Resized: %d %d", newWidth, newHeight);
}

void main()
{
    Event!(uint, uint) windowResizeEvent;
    windowResizeEvent.addListener(&onResize);

    windowResizeEvent.emit(1000, 2000);
}

I am very happy with this solution.

My advice is to make your listeners return a boolean. This boolean is used to basically stop propagating the event. And debugging it seems really hard to be honest, so, incrementing it a little, I would do:


struct ListenerInfo
{
  string name;
  string file;
  uint line;
  string registeredAt;
}
struct Event(T...)
{
  bool function(T)[] listeners;
  ListenerInfo[] info;
  void addListener(bool function(T) handler, string name, string file = __FILE__, uint line = __LINE__, string registeredAt = __PRETTY_FUNCTION__)
  {
     listeners~= handler;
     info~= ListenerInfo(name, file, line, registeredAt);
  }

  void emit(T args)
  {
    foreach(i, l; listeners)
    {
      if(l(args))
      {
        writeln(info[i]);
        break;
      }
    }
  }
}

You could even extend this concept further by making it return an enum value for actually removing the listener when executed, stopping propagation. Or you could have access to the listener info inside the arguments too.