Sign in to follow this  

Functors and Callbacks: Problems with Timer and Alarms

This topic is 4306 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

// Simple example of using constants with templates.
#include <iostream>

template <int N>
int factorial() {
return N * factorial<N-1>();
}

template <>
int factorial<0> {
return 1;
}

int main() {
std::cout << factorial<5>() << std::endl;
}
// The compiler is forced to generate several specialized versions of the
// factorial function; after that it will likely inline all the calls, and
// observe that it can do constant folding - so that the emitted code is
// equivalent to code for just "cout << 120 << endl". It probably wouldn't do
// that for a runtime call, even for very simple recursive functions.
// But this trades off against your ability to decide at runtime which
// factorial you want, and none of this optimization is guaranteed to happen
// either - plus the compiler/linker could leave specialized versions of
// functions around (if it's smart it can minimize the impact with template-
// and-hook tricks), bloating the executable.

Share this post


Link to post
Share on other sites

This topic is 4306 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this