Skip to main content
gamedev.net
🔒 Locked

Timer class

Started by mb108 3 replies 1.1k views
Original Post
mb108
mb108
Happy Thanksgiving! Design Question: I have an EventTimer class that stores a priority queue of TimedEvent objects sorted by the time they are scheduled to be triggered. It spawns a thread in the constructor that sends the events off to the main handler, sleeping in between scheduled events. I originally intended this class to be a monolithic, "All your timers are belong to me", sort of object. And it is. But there's some problems with it. Recurring events will go on for the life of the timer, and if an Event is added while the monster is sleeping, it will be queued but ignored until the thread wakes. (imagine it's sleeping for the next event in 5s, and you add something that's supposed to go off in 1s). So. Is there any way to salvage this beast? My thoughts: Yes, but it's complicated. Recurring events could be issued IDs that can be used to cancel them later on. I could do some sort of fancy blocking scheme where the first thread blocks on a conditional, a 2nd thread sleeps until the next known event, and then either the addition of a 'sooner' TimedEvent -or- the 2nd thread waking will unblock the 1st thread. In short, NO. I will probably strip out the queue, and have a timer object track 1 event in 1 thread. Each timer will be local, be destroyed with it's owner, and the lifespan of recurring events will be naturally limited. No conflicts over sleeping threads. Your thoughts: Would love to hear them. Source is GPLv3 and WIP, check it out here if you like. Event.h Event.cpp (scroll to the bottom to find the EventTimer class) Cheers!
ville-v
ville-v
Why can't you only sleep in periods of 100ms or so and check queue every time your thread wakes up?
mb108
mb108
That actually had not occurred to me, and is a very simple solution. In the purely academic sense, it is a form of busy-waiting (albeit a very slow kind of busy), but I think practically speaking it would be fine.

Another option would be to switch from the feature-scarce GLFW thread functions to boost::thread. I would get thread::interrupt(), and also some shared locking stuff that could simplify other parts of the code.

The createTimer function would interrupt the timer thread if it's sleeping on an event that happens later than the new event. The handling loop would have to catch a boost::thread_interrupted exception and act accordingly.
samoth
samoth
Or, you could wait on a condition variable, specifying a timeout equal to the closest scheduled event in the future (which may be 1 second, or 5 seconds, or whatever).
When another thread adds an event, it signals the condition variable, so you can recalculate the next closest event, possibly do something, and go to sleep again.

Actually a semaphore would do too for this application... but unluckily, GLFW doesn't have semaphores, so you'll need a GLFWCond.
Antheus
Antheus
You might not want to mess with threads if you can help it. The solution becomes *much* easier with respect to many other issues, and also much more deterministic:
while (running) {  time t = now();  handleInput();  handleExpiredTimers(t);  updateLogic(t);  render();};
In handleExpiredTimers you check, duh, which timers expired and notify interested parties.

This makes timing somewhat more predictable as well, since times are no longer bound to system time but actual logic time.

Always remember that threads are non-deterministic. They may be given processing time, or they might not. They make wake when desired, or they might not. This becomes a problem as soon as the CPU is overloaded and threads start to compete for resources. Not to mention that threaded timer invocations require you to lock the state.

And never use Sleep() for anything that resembles a timer based on real time. Sleeping means to sleep at least that long, but it may sleep forever or for 3 hours. If you need a periodic event that needs to do something every once in a while, the Sleep is ok. But emphasis is on "once in a while".
mb108
mb108
samoth, this is basically the solution that was winning out in my mind. I will probably switch to boost::thread at some point, but I just double checked and boost doesn't have a semaphore either. So it would still be a similar situation.

Antheus, point well taken. The EventHandler class above it is *pervasively* multithreaded, handing off tasks to a thread pool, so I have already perpetrated many crimes against determinism. However, that class also provides a no-thread/stepping solution, so I should probably do something similar for EventTimer.

This has been a learning and portfolio-building project, so some of the design is more rooted in curiosity that it is in practicality. \o/

Thanks for the replies.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.