Functors and Callbacks: Problems with Timer and Alarms

Started by
9 comments, last by Zahlman 18 years, 1 month ago
Hi. I have a question about using functors and callbacks for timing. My SDL game engine has a timing class that lets me set a timed alarm giving it the number of milliseconds to wait and a callback function to call when the alarm has expired. This works just fine, but I've wanted to use this alarm system inside of other classes in the engine. The problem is, my timer can only call static methods of objects using this alarm system (since methods are not really the same as global functions in C++). I also have a templated functor class, but I'm not sure how to use it in this context. Because it's a template, I can't use a functor as arguments or data types in alarms when I don't already know the type of object that will be using it. Is there a way to get around these problems? To set an alarm, I use the XLib::XTimer::SetAlarm() method, which takes two parameters: the time to wait in milliseconds, and a pointer to a void function that takes no arguments. I'd like to be able to write something like this, if it's possible.
class A
{
public:
    A()
    {
        XApplication::GetApp()->GetTimer()->SetAlarm(5000, SomeMethod);
    }
    ~A();
    void SomeMethod();
};




Here, GetTimer() is just getting a pointer to the application's timer instance. Is something like this possible for any type of object? Thanks! EDIT: Also, is it possible to make a template argument variable? For instance, with my functor class, rather than writing something like...
XFunctor<SomeClass> Functor;
...could I instead write something like...
XFunctor<VarThatContainsTheClassName> Functor;
...? I'm pretty sure this isn't possible, but I'm just wondering. It'd be nice to know. :-) Thanks agian.
Advertisement
Quote:and a pointer to a void function that takes no arguments.


If the callback function takes no arguments, there is no easy way to do it. Some callback systems allow you to pass an extra void* parameter when registering the callback, and that void* gets passed to the callback functions, thus allowing you to specify which instance to use.

Quote:Is something like this possible for any type of object?


No, as you pointed out, non-static member functions are not the same things as free or static member functions. You can't pass a pointer to a non-static member function where an 'ordinary' function pointer is expected.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
The alarm function you are provided lacks a void*. This weakens it significantly.

To fix this, create your own alarm system that piggybacks on the existing one. Your alarm system is the only code that accesses the SDL alarm system.

It keeps an ordered queue of {callback, time} pairs. When it is woken up by the SDL alarm, it pops off the front of the queue every {callback, time} pair whose time has already passed. After doing so, it scheduals a new a new SDL alarm to wake it up in time for the next callback on it's queue.

When a callback is added, if it is added to the front, a SDL alarm request is also filed.

Callbacks in your system are function+void pointer pairs. Write helper functions to bundle class+methods+arguements into the function+void pointers.
The SDL timers support parameters. Just make it so you use them...
I'll have to try tapping into SDL for my alarm functions. I implemented this alarm functionality (and the entire timer) myself; the only help from SDL are periodic calls to SDL_GetTicks().

I'm sure it'd be better and more accurate to use SDL's system, huh? (My implementation is only accurate to within 10 milliseconds, I think.) Actually, I'd like to implement the alarm system myself. How exactly does adding a void* parameter help? Can I pass in a pointer to an object or something?

Thanks for the replies!
One possibility is to create a TimerCallback virtual class:
class TimerCallback{public:  virtual void OnTimer(void *pData)=0;};

And then have the classes you want to receive the call inherit from that class. Then, when setting up a new timer, you can just store a TimerCallback pointer to the recipient.

Some people like this approach, some do not. I use it extensively for callback-like functionality, such as UI input events, etc., and I've had good success with it.
Quote:Actually, I'd like to implement the alarm system myself. How exactly does adding a void* parameter help? Can I pass in a pointer to an object or something?


It doesn't have to be a void*, but as it happens a void* works.

A function pointer is compile-time information only. If you want to be able to have run-time information, you need something analagous to a void*. This can be in the form of a virtual class pointer if required.

With a standard callback (typedef void callback(void*)) and a void*, you can wrap up the unsafe code and do anything required. As an example:

struct TimeData; // structure that stores "when I should be called back" parameters.void InstallCallBack(TimeData, callback, void*); // the void* only callback function.// what I'm actually implementing:template<typename T, typename M>void install_class_callback(TimeData, T* any_class, M* any_trivial_method);template<typename T, typename M>struct class_callback_helper {  static void callback_wrapper(void* d) {    class_callback_helper* data = (class_callback_helper*)d;    data->do_work();  }  M* mem_func;  T* class_ptr;  class_callback_helper(T* class_ptr_, M* mem_func_): class_ptr(class_ptr_), mem_func(mem_func_) {};  void do_work() {    class_ptr->mem_func();    delete this;  }};template<typename T, typename M>void install_class_callback(TimeData, T* any_class, M* any_trivial_method);{  class_callback_helper<T, M>* helper = new class_callback_helper<T, M>(any_class, any_trivial_method);  InstallCallBack(TimeData, class_callback_helper<T, M>::callback_helper, (void*)helper);}


All of the type-unsafe code is distilled into the struct class_callback_helper.

(the above code assumes a given callback is called only once. Repeating callbacks need more work (and the callback code needs to be able to figure out that they will never be called again, so the "void*" can be cleaned up)).

HTH.
Thanks for the help. I like the virtual class idea. NotAYakk's idea looks interesting too. For now, I'll try inherenting from a callback class. My engine includes a GUI namespace I've been working on and that's a very helpful solution for some event handling I wanted to do.

Another messy method I tried before was having an overloaded version of XLib::XTimer::SetAlarm() that took three parameters: the time to wait in milliseconds, a pointer to the object setting the alarm, and a pointer to a static method of that object. It worked, but having to create a static method for any objects wanting to receive alarm events didn't seem very... elegant. :-P

I appreciate all of the ideas.

Oh! And does anyone know about my question about template arguments...? I really doubt you can make them variable... but maybe there's a way?
Quote:Oh! And does anyone know about my question about template arguments...? I really doubt you can make them variable... but maybe there's a way?


Template parameters are only used at compile time. If the parameter is type, you can use a typedef. If the parameter is not a type, you can use a constant.

What you may not use, is a run-time variable.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by GenuineXP
Oh! And does anyone know about my question about template arguments...? I really doubt you can make them variable... but maybe there's a way?


This is not legal:
XFunctor<VarThatContainsTheClassName> Functor;

all template work is done at compile-time. Think of templates as a fancy pre-processor. The values of variables are run-time data, not compile-time data, so they cannot be used as template parameters inside the waka-brackets.

This topic is closed to new replies.

Advertisement