1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#ifdef SDL_TIMER_UNIX
25
26#include <stdio.h>
27#include <sys/time.h>
28#include <signal.h>
29#include <unistd.h>
30#include <string.h>
31#include <errno.h>
32
33#include "SDL_timer.h"
34#include "../SDL_timer_c.h"
35
36/* The clock_gettime provides monotonous time, so we should use it if
37   it's available. The clock_gettime function is behind ifdef
38   for __USE_POSIX199309
39   Tommi Kyntola (tommi.kyntola@ray.fi) 27/09/2005
40*/
41#if HAVE_NANOSLEEP || HAVE_CLOCK_GETTIME
42#include <time.h>
43#endif
44
45#if SDL_THREAD_PTH
46#include <pth.h>
47#endif
48
49#if SDL_THREADS_DISABLED
50#define USE_ITIMER
51#endif
52
53/* The first ticks value of the application */
54#ifdef HAVE_CLOCK_GETTIME
55static struct timespec start;
56#else
57static struct timeval start;
58#endif /* HAVE_CLOCK_GETTIME */
59
60
61void SDL_StartTicks(void)
62{
63	/* Set first ticks value */
64#if HAVE_CLOCK_GETTIME
65	clock_gettime(CLOCK_MONOTONIC,&start);
66#else
67	gettimeofday(&start, NULL);
68#endif
69}
70
71Uint32 SDL_GetTicks (void)
72{
73#if HAVE_CLOCK_GETTIME
74	Uint32 ticks;
75	struct timespec now;
76	clock_gettime(CLOCK_MONOTONIC,&now);
77	ticks=(now.tv_sec-start.tv_sec)*1000+(now.tv_nsec-start.tv_nsec)/1000000;
78	return(ticks);
79#else
80	Uint32 ticks;
81	struct timeval now;
82	gettimeofday(&now, NULL);
83	ticks=(now.tv_sec-start.tv_sec)*1000+(now.tv_usec-start.tv_usec)/1000;
84	return(ticks);
85#endif
86}
87
88void SDL_Delay (Uint32 ms)
89{
90#if SDL_THREAD_PTH
91	pth_time_t tv;
92	tv.tv_sec  =  ms/1000;
93	tv.tv_usec = (ms%1000)*1000;
94	pth_nap(tv);
95#else
96	int was_error;
97
98#if HAVE_NANOSLEEP
99	struct timespec elapsed, tv;
100#else
101	struct timeval tv;
102	Uint32 then, now, elapsed;
103#endif
104
105	/* Set the timeout interval */
106#if HAVE_NANOSLEEP
107	elapsed.tv_sec = ms/1000;
108	elapsed.tv_nsec = (ms%1000)*1000000;
109#else
110	then = SDL_GetTicks();
111#endif
112	do {
113		errno = 0;
114
115#if HAVE_NANOSLEEP
116		tv.tv_sec = elapsed.tv_sec;
117		tv.tv_nsec = elapsed.tv_nsec;
118		was_error = nanosleep(&tv, &elapsed);
119#else
120		/* Calculate the time interval left (in case of interrupt) */
121		now = SDL_GetTicks();
122		elapsed = (now-then);
123		then = now;
124		if ( elapsed >= ms ) {
125			break;
126		}
127		ms -= elapsed;
128		tv.tv_sec = ms/1000;
129		tv.tv_usec = (ms%1000)*1000;
130
131		was_error = select(0, NULL, NULL, NULL, &tv);
132#endif /* HAVE_NANOSLEEP */
133	} while ( was_error && (errno == EINTR) );
134#endif /* SDL_THREAD_PTH */
135}
136
137#ifdef USE_ITIMER
138
139static void HandleAlarm(int sig)
140{
141	Uint32 ms;
142
143	if ( SDL_alarm_callback ) {
144		ms = (*SDL_alarm_callback)(SDL_alarm_interval);
145		if ( ms != SDL_alarm_interval ) {
146			SDL_SetTimer(ms, SDL_alarm_callback);
147		}
148	}
149}
150
151int SDL_SYS_TimerInit(void)
152{
153	struct sigaction action;
154
155	/* Set the alarm handler (Linux specific) */
156	SDL_memset(&action, 0, sizeof(action));
157	action.sa_handler = HandleAlarm;
158	action.sa_flags = SA_RESTART;
159	sigemptyset(&action.sa_mask);
160	sigaction(SIGALRM, &action, NULL);
161	return(0);
162}
163
164void SDL_SYS_TimerQuit(void)
165{
166	SDL_SetTimer(0, NULL);
167}
168
169int SDL_SYS_StartTimer(void)
170{
171	struct itimerval timer;
172
173	timer.it_value.tv_sec = (SDL_alarm_interval/1000);
174	timer.it_value.tv_usec = (SDL_alarm_interval%1000)*1000;
175	timer.it_interval.tv_sec = (SDL_alarm_interval/1000);
176	timer.it_interval.tv_usec = (SDL_alarm_interval%1000)*1000;
177	setitimer(ITIMER_REAL, &timer, NULL);
178	return(0);
179}
180
181void SDL_SYS_StopTimer(void)
182{
183	struct itimerval timer;
184
185	SDL_memset(&timer, 0, (sizeof timer));
186	setitimer(ITIMER_REAL, &timer, NULL);
187}
188
189#else /* USE_ITIMER */
190
191#include "SDL_thread.h"
192
193/* Data to handle a single periodic alarm */
194static int timer_alive = 0;
195static SDL_Thread *timer = NULL;
196
197static int RunTimer(void *unused)
198{
199	while ( timer_alive ) {
200		if ( SDL_timer_running ) {
201			SDL_ThreadedTimerCheck();
202		}
203		SDL_Delay(1);
204	}
205	return(0);
206}
207
208/* This is only called if the event thread is not running */
209int SDL_SYS_TimerInit(void)
210{
211	timer_alive = 1;
212	timer = SDL_CreateThread(RunTimer, NULL);
213	if ( timer == NULL )
214		return(-1);
215	return(SDL_SetTimerThreaded(1));
216}
217
218void SDL_SYS_TimerQuit(void)
219{
220	timer_alive = 0;
221	if ( timer ) {
222		SDL_WaitThread(timer, NULL);
223		timer = NULL;
224	}
225}
226
227int SDL_SYS_StartTimer(void)
228{
229	SDL_SetError("Internal logic error: Linux uses threaded timer");
230	return(-1);
231}
232
233void SDL_SYS_StopTimer(void)
234{
235	return;
236}
237
238#endif /* USE_ITIMER */
239
240#endif /* SDL_TIMER_UNIX */
241