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#include "SDL_timer.h"
25#include "SDL_timer_c.h"
26#include "SDL_mutex.h"
27#include "SDL_systimer.h"
28
29/* #define DEBUG_TIMERS */
30
31int SDL_timer_started = 0;
32int SDL_timer_running = 0;
33
34/* Data to handle a single periodic alarm */
35Uint32 SDL_alarm_interval = 0;
36SDL_TimerCallback SDL_alarm_callback;
37
38/* Data used for a thread-based timer */
39static int SDL_timer_threaded = 0;
40
41struct _SDL_TimerID {
42	Uint32 interval;
43	SDL_NewTimerCallback cb;
44	void *param;
45	Uint32 last_alarm;
46	struct _SDL_TimerID *next;
47};
48
49static SDL_TimerID SDL_timers = NULL;
50static SDL_mutex *SDL_timer_mutex;
51static volatile SDL_bool list_changed = SDL_FALSE;
52
53/* Set whether or not the timer should use a thread.
54   This should not be called while the timer subsystem is running.
55*/
56int SDL_SetTimerThreaded(int value)
57{
58	int retval;
59
60	if ( SDL_timer_started ) {
61		SDL_SetError("Timer already initialized");
62		retval = -1;
63	} else {
64		retval = 0;
65		SDL_timer_threaded = value;
66	}
67	return retval;
68}
69
70int SDL_TimerInit(void)
71{
72	int retval;
73
74	retval = 0;
75	if ( SDL_timer_started ) {
76		SDL_TimerQuit();
77	}
78	if ( ! SDL_timer_threaded ) {
79		retval = SDL_SYS_TimerInit();
80	}
81	if ( SDL_timer_threaded ) {
82		SDL_timer_mutex = SDL_CreateMutex();
83	}
84	if ( retval == 0 ) {
85		SDL_timer_started = 1;
86	}
87	return(retval);
88}
89
90void SDL_TimerQuit(void)
91{
92	SDL_SetTimer(0, NULL);
93	if ( SDL_timer_threaded < 2 ) {
94		SDL_SYS_TimerQuit();
95	}
96	if ( SDL_timer_threaded ) {
97		SDL_DestroyMutex(SDL_timer_mutex);
98		SDL_timer_mutex = NULL;
99	}
100	SDL_timer_started = 0;
101	SDL_timer_threaded = 0;
102}
103
104void SDL_ThreadedTimerCheck(void)
105{
106	Uint32 now, ms;
107	SDL_TimerID t, prev, next;
108	SDL_bool removed;
109
110	SDL_mutexP(SDL_timer_mutex);
111	list_changed = SDL_FALSE;
112	now = SDL_GetTicks();
113	for ( prev = NULL, t = SDL_timers; t; t = next ) {
114		removed = SDL_FALSE;
115		ms = t->interval - SDL_TIMESLICE;
116		next = t->next;
117		if ( (int)(now - t->last_alarm) > (int)ms ) {
118			struct _SDL_TimerID timer;
119
120			if ( (now - t->last_alarm) < t->interval ) {
121				t->last_alarm += t->interval;
122			} else {
123				t->last_alarm = now;
124			}
125#ifdef DEBUG_TIMERS
126			printf("Executing timer %p (thread = %d)\n",
127				t, SDL_ThreadID());
128#endif
129			timer = *t;
130			SDL_mutexV(SDL_timer_mutex);
131			ms = timer.cb(timer.interval, timer.param);
132			SDL_mutexP(SDL_timer_mutex);
133			if ( list_changed ) {
134				/* Abort, list of timers modified */
135				/* FIXME: what if ms was changed? */
136				break;
137			}
138			if ( ms != t->interval ) {
139				if ( ms ) {
140					t->interval = ROUND_RESOLUTION(ms);
141				} else {
142					/* Remove timer from the list */
143#ifdef DEBUG_TIMERS
144					printf("SDL: Removing timer %p\n", t);
145#endif
146					if ( prev ) {
147						prev->next = next;
148					} else {
149						SDL_timers = next;
150					}
151					SDL_free(t);
152					--SDL_timer_running;
153					removed = SDL_TRUE;
154				}
155			}
156		}
157		/* Don't update prev if the timer has disappeared */
158		if ( ! removed ) {
159			prev = t;
160		}
161	}
162	SDL_mutexV(SDL_timer_mutex);
163}
164
165static SDL_TimerID SDL_AddTimerInternal(Uint32 interval, SDL_NewTimerCallback callback, void *param)
166{
167	SDL_TimerID t;
168	t = (SDL_TimerID) SDL_malloc(sizeof(struct _SDL_TimerID));
169	if ( t ) {
170		t->interval = ROUND_RESOLUTION(interval);
171		t->cb = callback;
172		t->param = param;
173		t->last_alarm = SDL_GetTicks();
174		t->next = SDL_timers;
175		SDL_timers = t;
176		++SDL_timer_running;
177		list_changed = SDL_TRUE;
178	}
179#ifdef DEBUG_TIMERS
180	printf("SDL_AddTimer(%d) = %08x num_timers = %d\n", interval, (Uint32)t, SDL_timer_running);
181#endif
182	return t;
183}
184
185SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param)
186{
187	SDL_TimerID t;
188	if ( ! SDL_timer_mutex ) {
189		if ( SDL_timer_started ) {
190			SDL_SetError("This platform doesn't support multiple timers");
191		} else {
192			SDL_SetError("You must call SDL_Init(SDL_INIT_TIMER) first");
193		}
194		return NULL;
195	}
196	if ( ! SDL_timer_threaded ) {
197		SDL_SetError("Multiple timers require threaded events!");
198		return NULL;
199	}
200	SDL_mutexP(SDL_timer_mutex);
201	t = SDL_AddTimerInternal(interval, callback, param);
202	SDL_mutexV(SDL_timer_mutex);
203	return t;
204}
205
206SDL_bool SDL_RemoveTimer(SDL_TimerID id)
207{
208	SDL_TimerID t, prev = NULL;
209	SDL_bool removed;
210
211	removed = SDL_FALSE;
212	SDL_mutexP(SDL_timer_mutex);
213	/* Look for id in the linked list of timers */
214	for (t = SDL_timers; t; prev=t, t = t->next ) {
215		if ( t == id ) {
216			if(prev) {
217				prev->next = t->next;
218			} else {
219				SDL_timers = t->next;
220			}
221			SDL_free(t);
222			--SDL_timer_running;
223			removed = SDL_TRUE;
224			list_changed = SDL_TRUE;
225			break;
226		}
227	}
228#ifdef DEBUG_TIMERS
229	printf("SDL_RemoveTimer(%08x) = %d num_timers = %d thread = %d\n", (Uint32)id, removed, SDL_timer_running, SDL_ThreadID());
230#endif
231	SDL_mutexV(SDL_timer_mutex);
232	return removed;
233}
234
235/* Old style callback functions are wrapped through this */
236static Uint32 SDLCALL callback_wrapper(Uint32 ms, void *param)
237{
238	SDL_TimerCallback func = (SDL_TimerCallback) param;
239	return (*func)(ms);
240}
241
242int SDL_SetTimer(Uint32 ms, SDL_TimerCallback callback)
243{
244	int retval;
245
246#ifdef DEBUG_TIMERS
247	printf("SDL_SetTimer(%d)\n", ms);
248#endif
249	retval = 0;
250
251	if ( SDL_timer_threaded ) {
252		SDL_mutexP(SDL_timer_mutex);
253	}
254	if ( SDL_timer_running ) {	/* Stop any currently running timer */
255		if ( SDL_timer_threaded ) {
256			while ( SDL_timers ) {
257				SDL_TimerID freeme = SDL_timers;
258				SDL_timers = SDL_timers->next;
259				SDL_free(freeme);
260			}
261			SDL_timer_running = 0;
262			list_changed = SDL_TRUE;
263		} else {
264			SDL_SYS_StopTimer();
265			SDL_timer_running = 0;
266		}
267	}
268	if ( ms ) {
269		if ( SDL_timer_threaded ) {
270			if ( SDL_AddTimerInternal(ms, callback_wrapper, (void *)callback) == NULL ) {
271				retval = -1;
272			}
273		} else {
274			SDL_timer_running = 1;
275			SDL_alarm_interval = ms;
276			SDL_alarm_callback = callback;
277			retval = SDL_SYS_StartTimer();
278		}
279	}
280	if ( SDL_timer_threaded ) {
281		SDL_mutexV(SDL_timer_mutex);
282	}
283
284	return retval;
285}
286