I'm messing around in the editor code. What I'm trying to do is something like this:

  1. Class A implements a signal "some_signal_called".
  2. Class B and C connect to the signal.
  3. Class A uses get_signal_connection_list("some_signal_called", &conn_list).
  4. It then iterates through the connections in a loop and invokes their Callables one by one, sequentially and synchronously.
  5. It gets some info back whether each callable successfully handled the signal or not.
  6. If the signal has been successfully handled, it exits the loop. It doesn't need to invoke any more Callables.
  7. If the signal has not been successfully handled by the end of the loop, some fallback code is used.

There might of course be a bunch of problems with this implementation.

  • Signals are not intended to be invoked in this manner. Invoking the callable directly bypasses a ton of important code in Object::emit_signalp. I'm not even sure it would work.
  • There is no reliable order for B and C to be called, and so there can be no priority of responsibility.
  • Obviously signals don't return anything. I would need to pass a reference to an object with a flag for B or C to set if they have handled the signal. This would get messy.

Is there a better idea than this? Is there a better approach for the chain of responsibility pattern somewhere already in the engine? I would like to keep the kind of decoupling which signals provide, if possible. I'd also like to avoid adding a ton of boilerplate just to get this pattern to work.

Thanks!

    Hard to say whether this would be a good solution without understanding the use case for the signal (and I'm not sure why you would do this in C++ rather than GD Script or C#), but one idea that occurs to me is to use a singleton class as broker. So classes A, B, and C all register with the broker, when A wants to send its signal it calls a method on the broker, and the broker can then either call the callables on A's behalf and return the result, or just give A references to B and C which A can then interact with directly.

    An even simpler option would be to add B and C to a group, and A could call get_tree().get_nodes_in_group() without the need for a broker, although that would still have the problem you mention of the order being unpredictable.

      soundgnome (and I'm not sure why you would do this in C++ rather than GD Script or C#)


      award I'm messing around in the editor code.

      Which is C++.

      I’m confused, are you doing a homework assignment where you’re tasked with implementing chain of responsibility somewhere? That pattern might be more useful with higher level constructs.

      Also confused :-) Must remind myself that it is not about signals from the OS.

      award

      • Signals are not intended to be invoked in this manner. Invoking the callable directly bypasses a ton of important code in Object::emit_signalp. I'm not even sure it would work.
      • There is no reliable order for B and C to be called, and so there can be no priority of responsibility.
      • Obviously signals don't return anything. I would need to pass a reference to an object with a flag for B or C to set if they have handled the signal. This would get messy.

      Jumping around in the code is always a questionable thing. Affects performance and complicates program flow which is bad especially in a multi-threaded environment.
      A reliable order could be achieved by sorting and serialising the callables. There are many ways to express this in C++, but I am not sure if I would go that way when performance matters. Also, a world of problems may arise when users can provide signal handling code while the program waits.
      I don't understand: Why don't those signals, if they are callables of some kind, not return anything ? Maybe Godot's signals don't support this, but what you describe seems something independent, more general from that.

      Can you explain what the use case is? As far as I understand, this is beyond the functionality of what signals can provide.

      But if you give some detail of what this will be used for, maybe there is a simpler way, or maybe it will have to be a custom thing.

      But I will say that when I was working on my Vulkan engine, I created this super clever but ultimately overly complex event system.

      The event system was templated, so neither classes had to know of each other, can could pass messages back and forth. It was actually pretty clever C++ code.

      However, it was too complex and I based the whole system on it. Eventually it just caused slowdown and problems, I would have been better off just calling functions directly.

      I had to abandon the project mostly because of that poor architecture choice.