MultiTimer and lambdas


#1

Hey Jules

Some while ago (like 3 years or so) you said you would implement the MultiTimer features using lambdas. I could not find anything new on that topic in the forum nor in the code. DId I miss something ? I would like to use some other mechanism than MultiTimer to handle several timers at once and if you already did it I'd rather use what you've done, obviously...


#2

Yeah, we still never got around to that yet! Not sure why, it's not a big thing to implement..

(Actually, I think my main problem with adding a class for this would be finding a good name for it.. "Timer" would be perfect, but to avoid a name clash it'd have to be "TimerCallback" or something which isn't quite as satisfying)


#3

(Actually, I think my main problem with adding a class for this would be finding a good name for it...)

Hahaha ! how about "PolyChronicMeterLambda"

:))


#4

Could someone give a minimal code example of what is being proposed?

What would the syntax look like?

int id = Timer::addCallback( // runAfterDelay maybe?
   300 /*interval ms*/, 
   [&]->bool{ 
      :
      return true // repeat?
   });

Timer::destroy(id);

Or maybe...

Timer::callbacks += [&]->bool{ /*... return ms repeat, 0 to terminate*/ } )

π


#5

No. It'd be more like this:

class MyClass
{
    MyClass()
    {
         timer.start ([this]() { this->doSomething(); });
    }

    void doSomething()
    {
        ...
    }

    LambdaTimer timer;
};

..because a huge problem with just flinging off a lambda to get called later is that you almost always need to stop it using objects that may have been deleted before it gets delivered. By tying the lambda to an object it means you can write code like the above, which would mean that it could never call doSomething() on an object that has been deleted.

Also it makes more practical sense because this would also let you call stop() etc on the timer object to control it.


#6

I see. That's nice.

I suppose there is a possible threading issue -- if one thread is deleting the object while another thread is inside the callback. But Timer could easily acquire a lock before invoking its callback, and the destructor could wait for it.

You might want to pass the millisecond interval as a first parameter...

How about returning true/false to determine whether the lambda destroys itself after completion, or queues itself to fire again?

That would mean it can be stopped both internally and externally.

Another idea might be that it returns the number of milliseconds until next fire, 0 would terminate.

 Another model might be:

class Foo : CallbackTimer<Foo>  // CRTP
{
    int optionalId;
    void bar() {
        optionalId = startTimer( 30ms, [this]{ ... } );
        // or...
        optionalId = startTimer( 30ms, quux ); // <-- need CRTP for this syntax
    }

    bool quux() { ... }

This would mean that startTimer could be overloaded to take a method of Foo, or a lambda (or a free function).

 It could return an id that could be used to terminate it. Alternatively it might want to terminate itself (by the lambda/method/freefunc returning false) in which case the id return value could be discarded.

π


#7

You're over-complicating things.

Inheriting from a timer class is one of the main things we'd be trying to avoid - my suggestion above is all about preferring composition over inheritance.

And the timer can of course stop itself internally without needing to mess around returning things. All it needs it to do is call stop/start on the timer object during its callback, like we already do with juce::Timers. And it will be able to easily lambda-capture a reference to that timer object because it'll almost always be in scope at the point where you create the lambda and call the start method with it.


#8

Definitely the way to go Jules. Please please please make it on your high-priority list ! You said it yourself: 

 not a big thing to implement..

;)


#9

+1

 

and do the same for AsyncUpdater ^^


#10

+1 add lambda to popup-menu items, so that no additonally callback-function is needed

popup.addItem ("do Something",[this]() { this->doSomething(); });
popup.addItem ("do another thing",[this]() { this->doAnotherThing(); });