Looking for lightweight signal and slot classes

Status
Not open for further replies.
Good to see that - now add this:

Code:
int getInt(int value)
{
  return value;
}

and use it to feed the signal, i.e.

Code:
intsigs(getInt(23));

The compiler should complain about not being able to bind the argument. I don't like that, but you can change the signal argument to const int& and it should work again. That's the most flexible method I have found so far.

I'll update the header later today, but with a small change to the interface.
 
I think there is a lingering bug in the Connection:~Connection() destructor.

Code:
    ~Connection()
    {
      if (signal_ != NULL)
      {
        signal_->disconnect(this);
      }
      delete delegate_; 
    }

The problem is that signal_ is never set to anything other than NULL. So when the Connection object destructs, it never de-registers itself from the signal. I've quoted one of the constructors below:

Code:
   /** template constructor for static member functions and free functions.
      allocates a new delegate on the heap **/
    template<typename ReturnType>
    Connection(Signal<args...>* signal, ReturnType (*Fn)(args...))
      : delegate_(new FnDelegate<ReturnType, args...>(Fn)),
      [B]signal_(NULL),[/B]
      next_(NULL)   
    {
      signal->connect(this);
    }

In the constructor, signal_ is set to NULL, and the argument signal is used for the connecting. But signal_ is never set.

You probably intended to initialize signal_ from signal:

Code:
   /** template constructor for static member functions and free functions.
      allocates a new delegate on the heap **/
    template<typename ReturnType>
    Connection(Signal<args...>* signal, ReturnType (*Fn)(args...))
      : delegate_(new FnDelegate<ReturnType, args...>(Fn)),
      [B]signal_(signal),[/B]
      next_(NULL)   
    {
      signal->connect(this);
    }
 
Signal sets the connection's signal property when a connection is added to a signal:
Code:
    /** connect to this signal **/
    void connect(connection_p p)
    {
      p->next_ = connections_;
      connections_ = p;
      p->signal_ = this;
    }
 
Signals.h is now on github: https://github.com/crteensy/Signals

The connect() functions now take a reference to a Signal object instead of a pointer. I'm thinking about adding block() and unblock() methods to the Connection classes so that connections can be temporarily blocked without disconnecting.
 
I'm always torn when it comes to reference vs pointer argument. The pointer argument often includes the "&" operator, which makes it clear that the value is passed by reference. OTOH, it seems cleaner to pass things by reference when that is what you want. I think I like the by-reference change.

At first I wasn't sure there would be much call for the block/unblock functionality here. But thinking about it more, I now think there would be a few practical use cases. For example when 3rd-party/main code might want to disable signals to a particular destination. Like perhaps a usb device while it is being reconfigured/initialized. The reporting device then would not need to know about usb reporting availability etc.

Along this line, it would perhaps also be useful to disable all and enable all again. I'm thinking this might permit 3rd party control over events without modifying the source of the events. For example this could allow a master on/off switch for a line of communication.
 
The "master switch" is a good idea and would add a kind of "symmetry" to the Signal/Connection combo. The master switch would block a signal and would be just as easy to implement as a connection block method.
 
So now the question is, when you enable all, do you "restore the enable state" that was, or simply turn on regardless of former state. What I mean is, if you disabled one connection, then disabled all, and then re-enabled all, would that disabled connection be enabled as well?

It could be done easily with two flags (per node), but in my mind is which is preferable? Perhaps we bloat the API one more level and provide an enable_all and a restore_all with those different semantics.
 
Signals and Connections would be blocked independently. Here's why:

A connection is blocked because the module (let's call it foo) that manages this connection says "no incoming events here, please". Now another module (called bar) disables a signal (no outgoing events) and later re-enables it. If the connection managed by foo now starts receiving unexpected events again, that might break foo.

A restore_all wouldn't be desirable because a blocking history would have to be stored.
 
A connection is blocked because the module (let's call it foo) that manages this connection says "no incoming events here, please". Now another module (called bar) disables a signal (no outgoing events) and later re-enables it. If the connection managed by foo now starts receiving unexpected events again, that might break foo.

Agreed, which is why I brought up the point (and glad that you see it that way). But the naming enable_all (say) might imply that it is now enabled. So perhaps the best solution is in the naming.
 
I think we still have a misunderstanding here:

I first suggested adding Connection::block() and Connection::unblock() methods. These block events received by a specific Connection.

The second thing that came in was a method that affects a set of Connections, which can mean either those connected to a Signal or those that connect to a specific receiver. I was talking about the former set, which can be blocked by a Signal::block() method: no connection associated with this signal will receive an event, because the Signal stops sending events. This would be implemented with a flag in the Signal object. We don't re-enable all Connections when unblocking the Signal, but the Signal itself. No Connection's state is affected by this.

A re-enable-all would work differently: It would iterate over the list of connections associated with a signal and unblock each and every one of them. That's probably rarely desired, unless we're talking about a Signal that says "update permanent configuration values, I want to store them on an SD card if you don't listen to me I'm screwed on next startup". Hmm...I want that.

The latter set of Connections (those that connect to a specific receiver) could also be blocked, by creating a Slot class that can be blocked. This would make the "gating" complete by being able to block a) single Connections, b) Signals, c) Slots.

The cool thing about template class methods is that we can add whatever we want and the compiler has a good chance of optimizing away what we don't need.
 
I think we still have a misunderstanding here:

I first suggested adding Connection::block() and Connection::unblock() methods. These block events received by a specific Connection.

You're right - for the master on/off I was thinking in terms of flagging currently connected Connections to a given signal. But I agree now that on/off at the Signal level is better. In this case, we wouldn't be affecting the state of individual connections as you've said. It would also have the advantage of preventing any newly added connections from receiving events until the Signal was enabled again (if the code should be structured that way).

The second thing that came in was a method that affects a set of Connections, which can mean either those connected to a Signal or those that connect to a specific receiver. I was talking about the former set, ...

I think we're on the same page there (the set connected to the Signal). But using the connection objects individually with appropriate block/unblock will be sufficient functionality I think. One could suggest that we should have iterators available from Signal so that we can iterate through all connected connections (blocked or not). But I'm not sure that is worth the trouble.

Thanks, I think I got it now.
 
Committed changes to repo. It's now possible to do stunts like this:
Code:
#include <Arduino.h>
#include <Signals.h>


Signal<> update;

template<uint8_t pin>
class Blink
{
  public:
    Blink()
    {
      pinMode(pin, OUTPUT);
      digitalWrite(pin, 1);
    }
    void update()
    {
      if (time_ > 250)
      {
        digitalWrite(pin, !digitalRead(pin));
        time_ = 0;
      }
    }
  private:
    elapsedMillis time_;
};

class Gate
{
  public:
    Gate()
    {
    }
    void update()
    {
      if (time_ > 3000)
      {
        time_ = 0;
        open();
      }
      else if (time_ > 2000)
      {
        close();
      }
    }
    Signal<> close;
    Signal<> open;
  private:
    elapsedMillis time_;
};

typedef Blink<13> Led;
Led led;
Gate gate;

void setup()
{
  auto connection_p = connect(update, led, &Led::update);
  connect(gate.close, *connection_p, &Connection<>::block);
  connect(gate.open, *connection_p, &Connection<>::unblock);
  connect(update, gate, &Gate::update);
}

void loop()
{
  update();
}
setup(): The first connection connects the LED to the global update signal which is called in the main loop. The other connections can be commented out to get an LED flashing at a constant 2 Hz. The second and third connections connect the Gate object's open and close signals to the led's update connection, resulting in a frequently disabled update. The third connection is necessary to update the Gate object.

The result is an LED that flashes 4 times, pauses for one second, and repeats that.

This result doesn't demonstrate blocking of signals, but it works the same way.
 
...Smalltalk-80, InterLisp-D/Loops, and Intellicorp's KEE were all development environments that had a notion of signals and slots. A signal was a message, but unlike a simple single-threaded function call with dispatch based on the receiver's type, a signal could be used for multi-threaded event-based programming or a network-transparent notification. A slot was a software concept analogous to a hardware port.
Kind of like BSD sockets are slots and datagrams are signals? You can address them, queue them, etc. Just need talker/listener to agree on address and port # (and IP protocol).
Yes?

Way back, people talked about a "software bus", in this ilk.
Or is it "buss"
 
Way back, people talked about a "software bus", in this ilk.
Or is it "buss"

This seems to be more like a "software bus", but not the kind that gstreamer uses. Some software busses (like gstreamer) queue up structures of data that are then passed along to other nodes (audio/video processing in that case).

The approach used here starts with a call on the Signal object, which then causes zero or more calls to registered interested parties. It still involves the caller (indirectly) calling each individual party in turn, but both ends need not otherwise be coded to do so directly (the Signal being an object containing a list of functions or methods to be called when an event happens).

I first saw this used in an early version of Qt, when they were getting started. It makes sense to leverage this concept in a GUI environment. Like the example shown by Christoph (a few posts back), it seems a natural fit in the embedded processing environment - bringing producers and consumers of events together.
 
Indeed I use this concet to pass events and small pieces of information between classes that are not directly related to each other. A widget that displays a float does not need to know about the fact that my hardware contains a GPS, and the GPS doesn't care about how a position is used (serial dump or display on screen). But I can
Code:
connect(gps.latChanged, latWidget, &FloatWidget::setValue);
It's also easy to change the widget type to something different (a DegreesWidget, for example, that applies a special formatting).

If one wanted to go the "software bus" route, it would be possible to create connection classes that manage a queue internally. Structures can be passed around already, but not in a queue-like manner.
 
Exactly! Every few years it gets reinvented with a new name and the sell sheets declare it the 'new' hotness.

Please be fair - I didn't reinvent the wheel, I just reimplemented it. I certainly didn't declare anything as "new", and I would have used an existing implementation if I had found one that fit my needs. Boost.Signals2 certainly would have, and it would have been magnitudes better than mine, but it didn't compile with the arduino interface in the way.
 
I've had enough SOA tossed at me to choke a horse. An embedded systems guy sees these as needless bloatware.
 
Hi Christoph: I've been putting the Signal class to work on a future project of mine, involving a MIDI dispatch to various Signal instances. What I am finding, is that I am getting compile errors (currently testing on OSX) from some of the signal calls:

Code:
Signals/signals.h:89:10: note: candidate function not viable: no known conversion from 'unsigned int' to 'unsigned int &&' for 1st argument
    void operator()(args&&... a) const
         ^

The problem appears related to the && reference in the declaration:

Code:
class Signal {
   ...
   void operator()(args&&... a) const

The instantiation of Signal in question here was:

Code:
Signal<unsigned>    sig_songpos;

When I remove "&&" from the Signals declaration above for "operator()", the problem disappears on the two compilers I've tried so far:

  • Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn) - Target: x86_64-apple-darwin13.1.0
  • g++ (GCC) 4.7.2

If you want to take this offline, email me at ve3wwg over at gmail.com.

I also had a similar issue, when my data type was unsigned char. For now I just changed them all to "unsigned" for now, but then ran into the issue reported above.
 
This is related to perfect forwarding with std::forward<>. The purpose of this forwarding is to avoid copies of the argument, which can be sometimes be impossible for non-copyable objects. Unfortunately, this fails for plain int, char, and so on. If you change the Signal declaration to the original one, but pass a const int& or a const char&, it should work. So instead of
Code:
Signal<int>
use
Code:
Signal<const int&>
 
Hm...I have encountered this problem before and usually solved it by using const int& instead of int, so I just suggested that above.

Apparently I cannot reproduce it any more. This might be related to the changes I made to the file when I implemented signal and connection blocking - can you please download the current file from github and try again? https://github.com/crteensy/Signals
 
Please post the exact line of code that results in the error you got. I must be related to the kind of expression that is passed as an argument to Signal:: operator()(...).
 
I know you can successfully work around that with const refs, but I find it rather irksome to be forced to.

There was no change (github said I was already working with the current version).

I do believe this is a compiler bug, since it was my understanding that this should always work (none of the examples I have seen about arg&& have suggested otherwise). Oddly enough, it didn't flag all of the similar calls with errors. The compilers consistent picked upon the same calls. But perhaps this has more to do with its error recovery.
 
Status
Not open for further replies.
Back
Top