1/*-------------------------------------------------------------------------
2 * drawElements Utility Library
3 * ----------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Periodic timer.
22 *//*--------------------------------------------------------------------*/
23
24#include "deTimer.h"
25#include "deMemory.h"
26#include "deThread.h"
27
28#if (DE_OS == DE_OS_WIN32)
29
30#define VC_EXTRALEAN
31#define WIN32_LEAN_AND_MEAN
32#include <windows.h>
33
34struct deTimer_s
35{
36	deTimerCallback		callback;
37	void*				callbackArg;
38
39	HANDLE				timer;
40};
41
42static void CALLBACK timerCallback (PVOID lpParameter, BOOLEAN timerOrWaitFired)
43{
44	const deTimer* timer = (const deTimer*)lpParameter;
45	DE_UNREF(timerOrWaitFired);
46
47	timer->callback(timer->callbackArg);
48}
49
50deTimer* deTimer_create (deTimerCallback callback, void* arg)
51{
52	deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));
53
54	if (!timer)
55		return DE_NULL;
56
57	timer->callback		= callback;
58	timer->callbackArg	= arg;
59	timer->timer		= 0;
60
61	return timer;
62}
63
64void deTimer_destroy (deTimer* timer)
65{
66	DE_ASSERT(timer);
67
68	if (deTimer_isActive(timer))
69		deTimer_disable(timer);
70
71	deFree(timer);
72}
73
74deBool deTimer_isActive (const deTimer* timer)
75{
76	return timer->timer != 0;
77}
78
79deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
80{
81	BOOL ret;
82
83	DE_ASSERT(timer && milliseconds > 0);
84
85	if (deTimer_isActive(timer))
86		return DE_FALSE;
87
88	ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, 0, WT_EXECUTEDEFAULT);
89
90	if (!ret)
91	{
92		DE_ASSERT(!timer->timer);
93		return DE_FALSE;
94	}
95
96	return DE_TRUE;
97}
98
99deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
100{
101	BOOL ret;
102
103	DE_ASSERT(timer && milliseconds > 0);
104
105	if (deTimer_isActive(timer))
106		return DE_FALSE;
107
108	ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, (DWORD)milliseconds, WT_EXECUTEDEFAULT);
109
110	if (!ret)
111	{
112		DE_ASSERT(!timer->timer);
113		return DE_FALSE;
114	}
115
116	return DE_TRUE;
117}
118
119void deTimer_disable (deTimer* timer)
120{
121	if (timer->timer)
122	{
123		const int	maxTries	= 100;
124		HANDLE		waitEvent	= CreateEvent(NULL, FALSE, FALSE, NULL);
125		int			tryNdx		= 0;
126		DE_ASSERT(waitEvent);
127
128		for (tryNdx = 0; tryNdx < maxTries; tryNdx++)
129		{
130			BOOL success = DeleteTimerQueueTimer(NULL, timer->timer, waitEvent);
131			if (success)
132			{
133				/* Wait for all callbacks to complete. */
134				DWORD res = WaitForSingleObject(waitEvent, INFINITE);
135				DE_ASSERT(res == WAIT_OBJECT_0);
136				DE_UNREF(res);
137				break;
138			}
139			else
140			{
141				DWORD err = GetLastError();
142				if (err == ERROR_IO_PENDING)
143					break; /* \todo [2013-03-21 pyry] Does this mean that callback is still in progress? */
144				deYield();
145			}
146		}
147
148		DE_ASSERT(tryNdx < maxTries);
149
150		CloseHandle(waitEvent);
151		timer->timer = 0;
152	}
153}
154
155#elif (DE_OS == DE_OS_UNIX || DE_OS == DE_OS_ANDROID || DE_OS == DE_OS_SYMBIAN)
156
157#include <signal.h>
158#include <time.h>
159
160struct deTimer_s
161{
162	deTimerCallback		callback;
163	void*				callbackArg;
164
165	timer_t				timer;
166
167	deBool				isActive;
168};
169
170static void timerCallback (union sigval val)
171{
172	const deTimer* timer = (const deTimer*)val.sival_ptr;
173	timer->callback(timer->callbackArg);
174}
175
176deTimer* deTimer_create (deTimerCallback callback, void* arg)
177{
178	deTimer*		timer = (deTimer*)deCalloc(sizeof(deTimer));
179	struct sigevent	sevp;
180
181	if (!timer)
182		return DE_NULL;
183
184	deMemset(&sevp, 0, sizeof(sevp));
185	sevp.sigev_notify			= SIGEV_THREAD;
186	sevp.sigev_value.sival_ptr	= timer;
187	sevp.sigev_notify_function	= timerCallback;
188
189	if (timer_create(CLOCK_REALTIME, &sevp, &timer->timer) != 0)
190	{
191		deFree(timer);
192		return DE_NULL;
193	}
194
195	timer->callback		= callback;
196	timer->callbackArg	= arg;
197	timer->isActive		= DE_FALSE;
198
199	return timer;
200}
201
202void deTimer_destroy (deTimer* timer)
203{
204	DE_ASSERT(timer);
205
206	timer_delete(timer->timer);
207	deFree(timer);
208}
209
210deBool deTimer_isActive (const deTimer* timer)
211{
212	return timer->isActive;
213}
214
215deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
216{
217	struct itimerspec tspec;
218
219	DE_ASSERT(timer && milliseconds > 0);
220
221	if (timer->isActive)
222		return DE_FALSE;
223
224	tspec.it_value.tv_sec		= milliseconds / 1000;
225	tspec.it_value.tv_nsec		= (milliseconds % 1000) * 1000;
226	tspec.it_interval.tv_sec	= 0;
227	tspec.it_interval.tv_nsec	= 0;
228
229	if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
230		return DE_FALSE;
231
232	timer->isActive = DE_TRUE;
233	return DE_TRUE;
234}
235
236deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
237{
238	struct itimerspec tspec;
239
240	DE_ASSERT(timer && milliseconds > 0);
241
242	if (timer->isActive)
243		return DE_FALSE;
244
245	tspec.it_value.tv_sec		= milliseconds / 1000;
246	tspec.it_value.tv_nsec		= (milliseconds % 1000) * 1000;
247	tspec.it_interval.tv_sec	= tspec.it_value.tv_sec;
248	tspec.it_interval.tv_nsec	= tspec.it_value.tv_nsec;
249
250	if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
251		return DE_FALSE;
252
253	timer->isActive = DE_TRUE;
254	return DE_TRUE;
255}
256
257void deTimer_disable (deTimer* timer)
258{
259	struct itimerspec tspec;
260
261	DE_ASSERT(timer);
262
263	tspec.it_value.tv_sec		= 0;
264	tspec.it_value.tv_nsec		= 0;
265	tspec.it_interval.tv_sec	= 0;
266	tspec.it_interval.tv_nsec	= 0;
267
268	timer_settime(timer->timer, 0, &tspec, DE_NULL);
269
270	/* \todo [2012-07-10 pyry] How to wait until all pending callbacks have finished? */
271
272	timer->isActive = DE_FALSE;
273}
274
275#else
276
277/* Generic thread-based implementation for OSes that lack proper timers. */
278
279#include "deThread.h"
280#include "deMutex.h"
281#include "deClock.h"
282
283typedef enum TimerState_e
284{
285	TIMERSTATE_INTERVAL = 0,	/*!< Active interval timer.		*/
286	TIMERSTATE_SINGLE,			/*!< Single callback timer.		*/
287	TIMERSTATE_DISABLED,		/*!< Disabled timer.			*/
288
289	TIMERSTATE_LAST
290} TimerState;
291
292typedef struct deTimerThread_s
293{
294	deTimerCallback		callback;		/*!< Callback function.		*/
295	void*				callbackArg;	/*!< User pointer.			*/
296
297	deThread			thread;			/*!< Thread.				*/
298	int					interval;		/*!< Timer interval.		*/
299
300	deMutex				lock;			/*!< State lock.			*/
301	volatile TimerState	state;			/*!< Timer state.			*/
302} deTimerThread;
303
304struct deTimer_s
305{
306	deTimerCallback		callback;		/*!< Callback function.		*/
307	void*				callbackArg;	/*!< User pointer.			*/
308	deTimerThread*		curThread;		/*!< Current timer thread.	*/
309};
310
311static void timerThread (void* arg)
312{
313	deTimerThread*	thread			= (deTimerThread*)arg;
314	int				numCallbacks	= 0;
315	deBool			destroy			= DE_TRUE;
316	deInt64			lastCallback	= (deInt64)deGetMicroseconds();
317
318	for (;;)
319	{
320		int sleepTime = 0;
321
322		deMutex_lock(thread->lock);
323
324		if (thread->state == TIMERSTATE_SINGLE && numCallbacks > 0)
325		{
326			destroy = DE_FALSE; /* Will be destroyed by deTimer_disable(). */
327			thread->state = TIMERSTATE_DISABLED;
328			break;
329		}
330		else if (thread->state == TIMERSTATE_DISABLED)
331			break;
332
333		deMutex_unlock(thread->lock);
334
335		sleepTime = thread->interval - (int)(((deInt64)deGetMicroseconds()-lastCallback)/1000);
336		if (sleepTime > 0)
337			deSleep(sleepTime);
338
339		lastCallback = (deInt64)deGetMicroseconds();
340		thread->callback(thread->callbackArg);
341		numCallbacks += 1;
342	}
343
344	/* State lock is held when loop is exited. */
345	deMutex_unlock(thread->lock);
346
347	if (destroy)
348	{
349		/* Destroy thread except thread->thread. */
350		deMutex_destroy(thread->lock);
351		deFree(thread);
352	}
353}
354
355static deTimerThread* deTimerThread_create (deTimerCallback callback, void* arg, int interval, TimerState state)
356{
357	deTimerThread* thread = (deTimerThread*)deCalloc(sizeof(deTimerThread));
358
359	DE_ASSERT(state == TIMERSTATE_INTERVAL || state == TIMERSTATE_SINGLE);
360
361	if (!thread)
362		return DE_NULL;
363
364	thread->callback	= callback;
365	thread->callbackArg	= arg;
366	thread->interval	= interval;
367	thread->lock		= deMutex_create(DE_NULL);
368	thread->state		= state;
369
370	thread->thread		= deThread_create(timerThread, thread, DE_NULL);
371	if (!thread->thread)
372	{
373		deMutex_destroy(thread->lock);
374		deFree(thread);
375		return DE_NULL;
376	}
377
378	return thread;
379}
380
381deTimer* deTimer_create (deTimerCallback callback, void* arg)
382{
383	deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));
384
385	if (!timer)
386		return DE_NULL;
387
388	timer->callback		= callback;
389	timer->callbackArg	= arg;
390
391	return timer;
392}
393
394void deTimer_destroy (deTimer* timer)
395{
396	if (timer->curThread)
397		deTimer_disable(timer);
398	deFree(timer);
399}
400
401deBool deTimer_isActive (const deTimer* timer)
402{
403	if (timer->curThread)
404	{
405		deBool isActive = DE_FALSE;
406
407		deMutex_lock(timer->curThread->lock);
408		isActive = timer->curThread->state != TIMERSTATE_LAST;
409		deMutex_unlock(timer->curThread->lock);
410
411		return isActive;
412	}
413	else
414		return DE_FALSE;
415}
416
417deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
418{
419	if (timer->curThread)
420		deTimer_disable(timer);
421
422	DE_ASSERT(!timer->curThread);
423	timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_SINGLE);
424
425	return timer->curThread != DE_NULL;
426}
427
428deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
429{
430	if (timer->curThread)
431		deTimer_disable(timer);
432
433	DE_ASSERT(!timer->curThread);
434	timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_INTERVAL);
435
436	return timer->curThread != DE_NULL;
437}
438
439void deTimer_disable (deTimer* timer)
440{
441	if (!timer->curThread)
442		return;
443
444	deMutex_lock(timer->curThread->lock);
445
446	if (timer->curThread->state != TIMERSTATE_DISABLED)
447	{
448		/* Just set state to disabled and destroy thread handle. */
449		/* \note Assumes that deThread_destroy() can be called while thread is still running
450		 *       and it will not terminate the thread.
451		 */
452		timer->curThread->state = TIMERSTATE_DISABLED;
453		deThread_destroy(timer->curThread->thread);
454		timer->curThread->thread = 0;
455		deMutex_unlock(timer->curThread->lock);
456
457		/* Thread will destroy timer->curThread. */
458	}
459	else
460	{
461		/* Single timer has expired - we must destroy whole thread structure. */
462		deMutex_unlock(timer->curThread->lock);
463		deThread_destroy(timer->curThread->thread);
464		deMutex_destroy(timer->curThread->lock);
465		deFree(timer->curThread);
466	}
467
468	timer->curThread = DE_NULL;
469}
470
471#endif
472