[suggestions] More general listeners


#1

I used many of the facilities of Juce to write my program, even when I had to write adaptors to other libraries to do so and while I have my quibbles it worked out well for me every time.

There is, however, one facility that I instantly decided to roll my own, and that was the whole callback/update/listener mechanism. The simple reason is type safety - I like to be very type-safe in my code, I save my sloppy ass all the time that way without paying attention, and I had repeated troubles not making mistakes until I went to a fully typed callback.

Here’s an example, distilled from real code. Consider that juce::AudioTransportSource inherits from both juce::ChangeBroadcaster and juce::PositionableAudioSource, you might be tempted to attach the following listener to it:virtual void changeCallback(ChangeBroadcaster* objectThatChanged) { PositionableAudioSource* source = (PositionableAudioSource*) objectThatChanged; }

But you’d be wrong - like I was (in my defense, the cast came at another place) and you’d likely experience a SEGV or the like. The reason (more or less - details will vary on individual compilers) is that objectThatChanged points to the base of the vtable of ChangeBroadcaster which is almost certainly not the base of the vtable of PositionableAudioSource.

A temporary hack that works in that case would be casting lower, to the actual class AudioTransportSource. I’m pretty sure that compiler is then able to work its magic correctly…

…but that’s a bad solution. I’m not convinced that the compiler has to do a good job at this - it didn’t on the original code, that looks so similar - and I can still later attach a listener to some other class that’s both a ChangeBroadcaster and a PositionableAudioSource (and there are several!) and again get a run-time signal.

Even before I found that bug, I was already using my own generic listeners almost everywhere between my compilation units - simply because it was far more convenient:template <typename Type> class Listener { public: virtual void operator()(Type x) = 0; virtual ~Listener() {} };

(And of course there’s a corresponding broadcaster…)

This is lighter and more flexible - because it uses call by value. Of course, that “value” can be a pointer or reference, I have tons of interfaces like Listener<PositionableAudioSource*> and Listener<const Preferences&> and things like that - but you can have things like Listener or Listener<const char*>.

As an example, everything in my code that listeners to the current time, like cursors or clocks, is a Listener. There’s no object that needs to be passed around, no locking… I get just the time delivered to my method.

And I love typesafety so it’s likely that “after the rush” I’m going to create this tiny struct:struct Time { float time_; Time(float time) : time_(time) {} operator float() const { return time_; } }; and then have those time-listeners become Listener - at no additional CPU or memory cost (in fact, if I make the change correctly it might be that the code is byte-for-byte the same…).

Note that I use operator() here! This might be controversial… and in fact, you can use the generic listeners without having to use operator() as the name of the single virtual function.

So if you don’t like operator(), consider strongly using generic listeners, but with a named virtual function.

But operator() is so extremely convenient in generic code because you can intermix pure functions and objects, and once you’re used to it, it makes the code shorter and clearer.

It means, for example, that a generic callback in your code can either be a function, or a class with an operator() function - and that’s so darned convenient! And it interoperates well as a result with a lot of other generic packages, like STL.


#2

I read your message and the first things that pop into my head are “boost::function” and “boost::bind”.


#3

Those old C-style casts can be treacherous. Avoid them at all cost! If you’d written:

virtual void changeCallback(ChangeBroadcaster* objectThatChanged) { PositionableAudioSource* source = dynamic_cast <PositionableAudioSource*> (objectThatChanged); }

…then it would have been ok.

And if you’d tried to do a static_cast <PositionableAudioSource*>, then the compiler would have given an error and alerted you to your blooper.


#4

[boost]

Absolutely, I had no doubt when I wrote this that boost had something like this! But the hugeness of boost makes it hard to use for anyone, and impossible for Jules to use in his system. I mean, he doesn’t even use STL! :stuck_out_tongue:

Those old C-style casts can be treacherous.

Yes, it’s very true! I knew it when I did it, and I did it anyway :frowning: because I was trying several ideas fast. Bad move! But it at least crashed the first time.

But I don’t think that your claimed code fix will in fact work :wink: though I haven’t tried it. Let’s look at that line:PositionableAudioSource* source = dynamic_cast <PositionableAudioSource*> (objectThatChanged);

Unfortunately, it is not PositionableAudioSource* that is the ChangeBroadcaster - it’s the common derived class AudioTransportSource which is. AudioTransportSource contains the vtables of both ChangeBroadcaster and of PositionableAudioSource, but I do not believe that the compiler has enough information to translate one to the other without knowing that this is fact an instance of AudioTransportSource.

I don’t believe that that dynamic_cast would in fact compile, and that that would be the correct behaviour. As you point out, that’s why you use dynamic_cast (to only cast when that actually makes sense and refuse to compile otherwise).

The advantage of the generic listener is that I get exactly the right type at the other end - so I don’t have any casts at all. Often, my Broadcaster will simply broadcast(this) or broadcast(*this). I worried that I’d have issues with the type being too specific or general, but that never happened in practice.

It’s nice, too, because one class can broadcast more than one sort of thing, or listen to more than one sort of thing, or both. For example, I have a little player class that broadcasts the current play time, periodically, and also itself, when its status changes. Everyone talks to it, it’s very useful and central - and yet it’s only a page of code.

And it’s nice, because making adaptors from these listeners is really easy.

Just a plug for this idea because it worked well for me!


#5

I do like your listener idea, I’ll bear it in mind as I’m working on things, and might find a use for it somewhere, cheers!


#6

Tom, I agree with you. Even if the dynamic_cast code compiled, it would SEGV on GCC/linux.
I had experienced this numerous time in the past. Dynamic_cast in reality does a if (*(voidPtrObject + address of typeid) == typeid(CastedObject)).

Since the object to pass it can be from anywhere, the first part of the equation can crash if the computed address is out the allocated page.

It’s easy to fix by the way, the good fix would be to remove that signature and put some class in there instead of void *.
A temporal fix is to do like you’re saying, making a kind of general base class all broadcaster must implement, and make sure the parameter instance is always a child of this base class.


#7

I have such an implementation, after trying many other solutions, and I can’t belive how powerfull boost::function and boost::bind are
By wrapping “boost::function”, you can have :

  • dynamic binding between component and “core” classes
  • type safety all allong the way
  • total separation between the “model” and the view

Basicaly you can implement QT’s signal/slot paradigm, except that you don’t need QT and it’s meta compiler.

And performance wise, boost::function is slow when copying functions objects, but not when calling them, so you have to make sure your wrapper objects are not copiable (you don’t need it anyway)

Boost function is very powerful. Basically, it allows you to store function calls as objects, so for example, you can implement a FIFO of your boost::function (or a wrapper), so you can call a function from one thread, and the function is ran in another thread !
Finally boost::function and boost::bind are now part of the standard (TR1).

I stongly suggest you have a look at that :slight_smile:


#8

And that’s exactly the technique I used in my DspDemo application to talk to the audio callback for changing settings without requiring a critical section on the data. Unfortunately I had to re-invent my own small version of boost::bind (people might not have tr1 extensions, and I dont want to require my users to also install boost). You can see the queue here:

http://code.google.com/p/dspfilterscpp/source/browse/trunk/demo/ThreadQueue.h

http://code.google.com/p/dspfilterscpp/source/browse/trunk/demo/ThreadQueue.cpp


#9

Powerful, thread-aware listeners built on top of blazingly fast function call queues are in the vf_concurrent module in VFLib:


#10

[quote=“TheVinn”]Powerful, thread-aware listeners built on top of blazingly fast function call queues are in the vf_concurrent module in VFLib:

https://github.com/vinniefalco/VFLib[/quote]

Looks really good ! I’m gonna have a look at the code to see how you got rid of boost::function and boost::bind though. Is your lib gpl, or mit ?


#11

GPL at the moment although I think will likely re-evaluate when I have an actual release in a week or two (I hope).

I have cobbled together my own version of boost::function that puts a fixed limit on the size of the arguments.

For bind, you still need it but it comes from your development environment instead of boost (bind is part of c++ now in some way depending on your platform).

I call bind for you so you don’t need to use it (unless you really want to). My routines all come in 9 versions, to support automatic bind from 0 to 8 parameters.


#12

Even more amazing ! Yep bind is part of tr1 I think. What’s the advantage of your Function class compared to boost’s ?


#13

It’s…that you don’t need to include boost! LOL

But seriously, the only real difference is that it uses a fixed size buffer instead of dynamically allocating memory. So you can use it in the AudioDeviceIOCallback. See vf_CatchAny.h for an example of usage.

I’m using CatchAny() to wrap my calls to external VST plugins in case they screw up.


#14

But seriously, the only real difference is that it uses a fixed size buffer instead of dynamically allocating memory. [/quote]

Love it !