views:

54

answers:

2

I'm making a GUI API (for games, not an OS) and would like to implement animated buttons. I'd like to be able to create timed events, but, within the class. example:

class TextBox
{
   void changeColor(int color);

    void createTimedEvent(func* or something, int ticks);
     void animate()
{

    createTimedEvent(changeColor(red),30);

}

};

So in this example, the timer would call the class instance's changeColor function, with argument red, after 30 ms. Is there a way to do this?

Basically, a function to call a function, which could be a function from a instancable class, wit n arguments, after a given interval has expired.

The precision of the timer is not a big deal for me.

Thanks

A: 

I'd welcome anybody showing otherwise, but as far as I know, you'd need to deal with this in steps. The first step is to create a bound function -- i.e., take the function you specify, and create an object that, when you invoke it, in turn invokes the specified function with the specified parameters. Using Boost/TR1/C++0x bind, that much would look something like this:

std::tr1::function<void (int)> func(std::tr1::bind(&TextBox::changColor, this, red));

That makes func an object that will invoke TextBox::changeColor(red) when it's called. There is one minor problem with this though: func is an object, not really a function. Syntactically, using it looks like calling a function, but that's an illusion created by the C++ compiler; trying to pass that object's address to something that will use it as the address of a function will fail (probably pretty spectacularly). Unfortunately, at least in Windows, there's no way to designate an arbitrary parameter that will be passed to a timer callback function (though you could probably manage to do it in the nIdEvent parameter with some really gross casting, something like:

void callback(HWND, UINT, UINT_PTR f, DWORD) { 
    typedef std::tr1::function<void (int)> function;
    function *func = reinterpret_cast<function *>(f);
    (*func)();
}

To make this a bit cleaner, instead of casting the address to an unsigned integer, I'd consider saving the address of the callback in an array, and passing its index in the array instead:

void callback(HWND, UINT, UINT_PTR f, DWORD) { 
    callback_functions[f]();
}

That leaves the really non-portable part: actually getting the system to invoke that function after the right length of time. Though most modern systems have one, each is still unique. Under Windows (for one example) you could do something like this:

callback_functions[++N] = func;
SetTimer(hWnd, N, 30, callback);

For such a simple idea, that's all too ugly and complex an answer, but I honestly don't know of anything much less complex that'll work. If you have almost any reasonable choice in the matter, I'd use something else. Also note that this is really a stream-of-consciousness sketch -- none of the code has been compiled, much less really tested. I can't see a good reason the general idea shouldn't work, but it might take a fair amount of effort to flesh it out to something that really does (e.g., I've mostly neglected management of the "callback_functions" array).

Jerry Coffin
While this is great, its winAPI specific
Milo
The last piece is, yes. On Linux one possibility would be to call `setitimer()` to start the timer, which will deliver a SIGALRM when the timer expires. Most of the rest (creating the bound function and such) would remain the same though. I'm pretty sure MacOS should support `setitimer` too, since it originally came from BSD.
Jerry Coffin
+1  A: 

I believe you could make this work portably using Boost.Asio - this is primarily designed for async I/O but I see no reason why the timer code cannot be used in other contexts. See this example for how to kick off a timer which calls back your code after expiry.

The only proviso I noticed is that you have to call ioservice::run in some thread with the ioservice instance you used here, or the callbacks will not happen.

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

void print(const boost::system::error_code& /*e*/)
{
  std::cout << "Hello, world!\n";
}

int main()
{
  boost::asio::io_service io;

  boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

  t.async_wait(print);

  // ensure we call io.run() from some thread or callbacks will not happen

  // other app logic

  return(0);
}

There is also a discussion of this very topic on MSDN blogs here by the author of the library.

Steve Townsend