A timer is a mechanism by which you can have an event repeat based on a specified
Each timer has the same functionality (i.e. the same member functions) and utilizes an
If we have a timer object with a tick interval of 1 second (1000 ms) the
Asynchronous Timer
The
As such, any delegates attached to the async timer must be re-entrant or protect any shared data with a synchronization primitive like a mutex or binary semaphore. The
Synchronous Timer
The
This blocking nature will delay the
Queue Timer
The
If the queue is empty after the tick event occurs then the tick thread dies and a new one will be spawned on the next tick event; if the queue is not empty, the tick thread will pop off each event in the queue, executing in the order it was added regardless of tick interval. In this manner, it is possible to have multiple tick events occur in quick succession if the queue has any items in it due to a tick event taking longer than the interval.
Drop Timer
The
Like the other timer types the
Why Separate Timers?
Each timer has a separate idiom to it to allow a greater flexibility in how you design your code. The timer types are separated out instead of having 1 class that could "do it all" because merging the capabilities of each timer type into one class would bloat the size of the class needlessly if you only ever needed 1 timer type over another (i.e. an
Example
time interval. After the specified interval has lapsed, the tick event will be raised and any omni::chrono::timer_delegate delegates attached will be invoked. There are 4 types of timer classes to utilize within the library:omni::chrono::async_timeromni::chrono::sync_timeromni::chrono::queue_timeromni::chrono::drop_timerEach timer has the same functionality (i.e. the same member functions) and utilizes an
omni::sync::basic_thread that raises a tick event after a specified interval has passed. What differentiates the timer types is how they handle any attached delegates to the tick event that take longer than the specified tick time and could cause an overlap in ticks. As an example, take the following timer tick functions:void timer_tick_short(omni::chrono::tick_t tick_time, const omni::generic_ptr& state_obj) { std::cout << "In tick!" << std::endl; std::cout << "Leaving tick!" << std::endl; } void timer_tick_long(omni::chrono::tick_t tick_time, const omni::generic_ptr& state_obj) { std::cout << "In tick!" << std::endl; omni::sync::sleep(2000); std::cout << "Leaving tick!" << std::endl; } omni::chrono::async_timer tobj(1000); // 1 second tobj.tick += timer_tick_short; tobj.tick += timer_tick_long;
timer_tick_short function will execute and return with no delays yet the timer_tick_long function will take longer to execute than the interval between ticks, and thus an overlap in ticks can occur. It is this overlapping of ticks where each timer has a different approach.Asynchronous Timer
The
omni::chrono::async_timer spawns a thread to preform the tick event when the specified interval has passed.As such, any delegates attached to the async timer must be re-entrant or protect any shared data with a synchronization primitive like a mutex or binary semaphore. The
async_timer utilizes a separate thread to guarantee the tick event is not blocked by attached delegates that might take longer than the specified interval, this ensures that no matter how long an attached delegate might take the tick event will occur after the specified interval has lapsed.Synchronous Timer
The
omni::chrono::sync_timer does not spawn a thread to do the tick event and instead calls the tick event directly, immediately following the interval lapse.This blocking nature will delay the
time the next tick occurs by the time it takes for each attached function to execute; so if a sync_timer were created with a tick interval of 1000 milliseconds and timer_tick_long were attached to it, then the tick will occur after 1 second and the next tick will not occur for another 2 seconds afterwards (instead of 1 second) because of the omni::sync::sleep in the timer_tick_long function, totalling to 3 seconds for the next tick (versus the 1 second interval).Queue Timer
The
omni::chrono::queue_timer will spawn a thread to do the tick event on (similar to the async_timer), but when a tick event occurs on the queue_timer the time it ticked and state object are added to a queue if the tick event thread is still executing.If the queue is empty after the tick event occurs then the tick thread dies and a new one will be spawned on the next tick event; if the queue is not empty, the tick thread will pop off each event in the queue, executing in the order it was added regardless of tick interval. In this manner, it is possible to have multiple tick events occur in quick succession if the queue has any items in it due to a tick event taking longer than the interval.
Drop Timer
The
omni::chrono::drop_timer differs from the other types in that it will ignore any ticks that occur if a tick event is currently happening.Like the other timer types the
drop_timer executes the tick event on a separate thread, but if the next tick occurs when the current tick thread is still active then it is dropped (ignored) and the timer continues in this fashion. There are no alerts to the user if a tick event was dropped.Why Separate Timers?
Each timer has a separate idiom to it to allow a greater flexibility in how you design your code. The timer types are separated out instead of having 1 class that could "do it all" because merging the capabilities of each timer type into one class would bloat the size of the class needlessly if you only ever needed 1 timer type over another (i.e. an
async_timer over a queue_timer type).Example
#include <omnilib> static omni::sync::basic_lock mtx; static std::map<int, std::string> vals; static std::map<int, int> tcount; void print_status(int val, const std::string& el) { mtx.lock(); std::cout << "Thread #" << omni::sync::thread_id() << " (" << vals[val] << ") " << el << std::endl; mtx.unlock(); } void timer_tick(omni::chrono::tick_t tick_time, const omni::generic_ptr& sobj) { int idx = (*static_cast<int*>(sobj)); tcount[idx] = tcount[idx] + 1; print_status(idx, "enter"); omni::sync::sleep(1500); // sleep 1.5s print_status(idx, "leaving"); } int main(int argc, char* argv[]) { int idx[] = { 1, 2, 3, 4 }; vals[1] = "async_timer"; vals[2] = "sync_timer"; vals[3] = "queue_timer"; vals[4] = "drop_timer"; omni::chrono::async_timer atimer(1000, &timer_tick); atimer.state_object = &idx[0]; omni::chrono::sync_timer stimer(1000); omni::chrono::queue_timer qtimer(1000); omni::chrono::drop_timer dtimer(1000); stimer.tick += &timer_tick; stimer.state_object = &idx[1]; qtimer.tick += &timer_tick; qtimer.state_object = &idx[2]; dtimer.tick += &timer_tick; dtimer.state_object = &idx[3]; std::cout << "starting timers" << std::endl; atimer.start(); // 6 ticks stimer.start(); // approximately 3 ticks (delay) qtimer.start(); // 6 ticks (deferred) dtimer.start(); // approximately 5 ticks std::cout << "sleeping 6 seconds" << std::endl; omni::sync::sleep(6000); // sleep 6s std::cout << "stopping timers" << std::endl; atimer.stop(); stimer.stop(); qtimer.stop(); dtimer.stop(); std::cout << "sleeping 3 seconds to wait the other ticks" << std::endl; omni::sync::sleep(3000); // sleep 6s for (int i = 1; i <= 4; ++i) { std::cout << vals[i] << " tick count: " << tcount[i] << std::endl; } return 0; }