1 /*
2  * Copyright (c) 2004-2012 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * Timer interface.
28  */
29 
30 #include <agar/core/core.h>
31 
32 struct ag_objectq agTimerObjQ = TAILQ_HEAD_INITIALIZER(agTimerObjQ);
33 Uint              agTimerCount = 0;
34 AG_Object         agTimerMgr;
35 AG_Mutex          agTimerLock;
36 
37 void
AG_InitTimers(void)38 AG_InitTimers(void)
39 {
40 	AG_MutexInitRecursive(&agTimerLock);
41 	AG_ObjectInitStatic(&agTimerMgr, NULL);
42 }
43 
44 void
AG_DestroyTimers(void)45 AG_DestroyTimers(void)
46 {
47 	AG_ObjectDestroy(&agTimerMgr);
48 	AG_MutexDestroy(&agTimerLock);
49 }
50 
51 /*
52  * Attach a timer to an object (or &agTimerMgr if object argument is NULL),
53  * and schedule the execution of a timer callback routine fn, in ival ticks.
54  *
55  * The AG_Timer structure should have been previously initialized with
56  * AG_InitTimer(). If the timer is already attached/scheduled, this function
57  * may be used to change the interval or the callback function/arguments.
58  */
59 int
AG_AddTimer(void * p,AG_Timer * to,Uint32 ival,AG_TimerFn fn,const char * fmt,...)60 AG_AddTimer(void *p, AG_Timer *to, Uint32 ival, AG_TimerFn fn,
61     const char *fmt, ...)
62 {
63 	AG_EventSource *src = AG_GetEventSource();
64 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
65 	AG_Timer *toOther;
66 	int newTimer = 0;
67 	AG_Event *ev;
68 
69 	AG_LockTimers(ob);
70 
71 	if (src->caps[AG_SINK_TIMER]) {		/* Unordered list */
72 		if (to->obj == NULL) {
73 			if (TAILQ_EMPTY(&ob->timers)) {
74 				TAILQ_INSERT_TAIL(&agTimerObjQ, ob, tobjs);
75 			}
76 			TAILQ_INSERT_TAIL(&ob->timers, to, timers);
77 			newTimer = 1;
78 			to->obj = ob;
79 			to->tSched = 0;
80 		} else if (to->obj != ob) {
81 			AG_FatalError("Timer is in a different object (%s != %s)",
82 			    OBJECT(to->obj)->name, ob->name);
83 		}
84 	} else {				/* Ordered timing wheel */
85 		if (to->obj == NULL) {
86 			newTimer = 1;
87 			to->obj = ob;
88 		} else if (to->obj != ob) {
89 			AG_FatalError("Timer is in a different object (%s != %s)",
90 			    OBJECT(to->obj)->name, ob->name);
91 		}
92 		if (TAILQ_EMPTY(&ob->timers)) {
93 			TAILQ_INSERT_TAIL(&agTimerObjQ, ob, tobjs);
94 		}
95 		to->tSched = AG_GetTicks()+ival;
96 reinsert:
97 		TAILQ_FOREACH(toOther, &ob->timers, timers) {
98 			if (toOther == to) {
99 				newTimer = 0;
100 				TAILQ_REMOVE(&ob->timers, to, timers);
101 				goto reinsert;
102 			}
103 			if (to->tSched < toOther->tSched) {
104 				TAILQ_INSERT_BEFORE(toOther, to, timers);
105 				break;
106 			}
107 		}
108 		if (toOther == TAILQ_END(&ob->timers)) {
109 			TAILQ_INSERT_TAIL(&ob->timers, to, timers);
110 		}
111 		to->ival = ival;
112 		to->id = 0;				/* Not needed */
113 	}
114 
115 	to->fn = fn;
116 	ev = &to->fnEvent;
117 	AG_EventInit(ev);
118 	ev->argv[0].data.p = ob;
119 	AG_EVENT_GET_ARGS(ev, fmt);
120 	ev->argc0 = ev->argc;
121 
122 	if (src->addTimerFn != NULL &&
123 	    src->addTimerFn(to, ival, newTimer) == -1) {
124 		goto fail;
125 	}
126 	AG_UnlockTimers(ob);
127 	return (0);
128 fail:
129 	to->obj = NULL;
130 	TAILQ_REMOVE(&ob->timers, to, timers);
131 	if (TAILQ_EMPTY(&ob->timers)) { TAILQ_REMOVE(&agTimerObjQ, ob, tobjs); }
132 	AG_UnlockTimers(ob);
133 	return (-1);
134 }
135 
136 /* Initialize a timer structure */
137 void
AG_InitTimer(AG_Timer * to,const char * name,Uint flags)138 AG_InitTimer(AG_Timer *to, const char *name, Uint flags)
139 {
140 	if (name == NULL) {
141 		to->name[0] = '\0';
142 	} else {
143 		Strlcpy(to->name, name, sizeof(to->name));
144 	}
145 	to->id = -1;
146 	to->obj = NULL;
147 	to->flags = flags;
148 	to->ival = 0;
149 	to->tSched = 0;
150 	to->fn = NULL;
151 }
152 
153 /*
154  * Variant of AG_AddTimer() for an auto-allocated, anonymous timer. The
155  * timer structure will be freed upon cancellation.
156  *
157  * The returned pointer is only safe to access as long as AG_LockTimers()
158  * is in effect.
159  */
160 AG_Timer *
AG_AddTimerAuto(void * p,Uint32 ival,AG_TimerFn fn,const char * fmt,...)161 AG_AddTimerAuto(void *p, Uint32 ival, AG_TimerFn fn, const char *fmt, ...)
162 {
163 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
164 	AG_Timer *to;
165 	AG_Event *ev;
166 
167 	if ((to = TryMalloc(sizeof(AG_Timer))) == NULL) {
168 		return (NULL);
169 	}
170 	AG_InitTimer(to, "auto", AG_TIMER_AUTO_FREE);
171 	AG_LockTimers(ob);
172 	if (AG_AddTimer(ob, to, ival, fn, NULL) == -1) {
173 		goto fail;
174 	}
175 	ev = &to->fnEvent;
176 	AG_EVENT_GET_ARGS(ev, fmt);
177 	ev->argc0 = ev->argc;
178 	AG_UnlockTimers(ob);
179 	return (to);
180 fail:
181 	AG_UnlockTimers(ob);
182 	free(to);
183 	return (NULL);
184 }
185 
186 /*
187  * Change the interval of a timer. The timer must be running.
188  * This is called whenever a timer callback returns a new interval.
189  */
190 int
AG_ResetTimer(void * p,AG_Timer * to,Uint32 ival)191 AG_ResetTimer(void *p, AG_Timer *to, Uint32 ival)
192 {
193 	AG_EventSource *src = AG_GetEventSource();
194 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
195 	AG_Timer *toOther;
196 	int rv = 0;
197 
198 	AG_LockTimers(ob);
199 	if (src->addTimerFn != NULL &&
200 	    src->addTimerFn(to, ival, 0) == -1) {
201 		rv = -1;
202 		goto out;
203 	}
204 	if (!src->caps[AG_SINK_TIMER]) {	/* Ordered timing wheel */
205 		to->tSched = AG_GetTicks()+ival;
206 		TAILQ_REMOVE(&ob->timers, to, timers);
207 		TAILQ_FOREACH(toOther, &ob->timers, timers) {
208 			if (to->tSched < toOther->tSched) {
209 				TAILQ_INSERT_BEFORE(toOther, to, timers);
210 				break;
211 			}
212 		}
213 		if (toOther == TAILQ_END(&ob->timers))
214 			TAILQ_INSERT_TAIL(&ob->timers, to, timers);
215 	}
216 	to->ival = ival;
217 out:
218 	AG_UnlockTimers(ob);
219 	return (rv);
220 }
221 
222 /* Cancel the given timeout if it is scheduled for execution. */
223 void
AG_DelTimer(void * p,AG_Timer * to)224 AG_DelTimer(void *p, AG_Timer *to)
225 {
226 	AG_EventSource *src = AG_GetEventSource();
227 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
228 	AG_Timer *toOther;
229 
230 	AG_LockTimers(ob);
231 
232 	TAILQ_FOREACH(toOther, &ob->timers, timers) {
233 		if (toOther == to)
234 			break;
235 	}
236 	if (toOther == NULL) 		/* Timer is not active */
237 		goto out;
238 
239 	if (src->delTimerFn != NULL) {
240 		src->delTimerFn(to);
241 	}
242 	to->id = -1;
243 	to->obj = NULL;
244 
245 	TAILQ_REMOVE(&ob->timers, to, timers);
246 	if (TAILQ_EMPTY(&ob->timers))
247 		TAILQ_REMOVE(&agTimerObjQ, ob, tobjs);
248 
249 	if (to->flags & AG_TIMER_AUTO_FREE)
250 		free(to);
251 out:
252 	AG_UnlockTimers(ob);
253 }
254 
255 /*
256  * Evaluate whether a timer is running.
257  * The caller should use AG_LockTimers().
258  */
259 int
AG_TimerIsRunning(void * p,AG_Timer * to)260 AG_TimerIsRunning(void *p, AG_Timer *to)
261 {
262 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
263 	AG_Timer *toRunning;
264 
265 	TAILQ_FOREACH(toRunning, &ob->timers, timers) {
266 		if (toRunning == to)
267 			break;
268 	}
269 	return (toRunning != NULL);
270 }
271 
272 /*
273  * Block the calling thread until the given timer expires (and is not
274  * immediately restarted), or the given delay (in ticks) is exceeded.
275  *
276  * XXX TODO use a condition variable instead of a delay loop.
277  */
278 int
AG_TimerWait(void * p,AG_Timer * to,Uint32 timeout)279 AG_TimerWait(void *p, AG_Timer *to, Uint32 timeout)
280 {
281 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
282 	Uint32 elapsed = 0;
283 
284 	for (;;) {
285 		if (timeout > 0 && ++elapsed >= timeout) {
286 			return (-1);
287 		}
288 		if (!AG_TimerIsRunning(ob, to)) {
289 			break;
290 		}
291 		AG_Delay(1);
292 	}
293 	return (0);
294 }
295 
296 /*
297  * Execute the callback routines of expired timers using AG_GetTicks()
298  * as a time source. This is used on platforms where system timers are not
299  * available and delay loops are the only option.
300  *
301  * Applications calling this routine explicitely must pass AG_SOFT_TIMERS to
302  * AG_InitCore().
303  */
304 void
AG_ProcessTimeouts(Uint32 t)305 AG_ProcessTimeouts(Uint32 t)
306 {
307 	AG_Timer *to, *toNext;
308 	AG_Object *ob, *obNext;
309 	Uint32 rv;
310 
311 	AG_LockTiming();
312 
313 	for (ob = TAILQ_FIRST(&agTimerObjQ);
314 	     ob != TAILQ_END(&agTimerObjQ);
315 	     ob = obNext) {
316 		obNext = TAILQ_NEXT(ob, tobjs);
317 		AG_ObjectLock(ob);
318 rescan:
319 		for (to = TAILQ_FIRST(&ob->timers);
320 		     to != TAILQ_END(&ob->timers);
321 		     to = toNext) {
322 			toNext = TAILQ_NEXT(to, timers);
323 
324 			if ((int)(to->tSched - t) > 0) {
325 				continue;
326 			}
327 			rv = to->fn(to, &to->fnEvent);
328 			if (rv > 0) {				/* Restart */
329 				(void)AG_ResetTimer(ob, to, rv);
330 				goto rescan;
331 			} else {				/* Cancel */
332 				AG_DelTimer(ob, to);
333 			}
334 		}
335 		AG_ObjectUnlock(ob);
336 	}
337 	AG_UnlockTiming();
338 }
339 
340 #ifdef AG_LEGACY
341 static Uint32
LegacyTimerCallback(AG_Timer * to,AG_Event * event)342 LegacyTimerCallback(AG_Timer *to, AG_Event *event)
343 {
344 	return to->fnLegacy(to->obj, to->ival, to->argLegacy);
345 }
346 void
AG_SetTimeout(AG_Timeout * to,Uint32 (* fn)(void *,Uint32,void *),void * arg,Uint flags)347 AG_SetTimeout(AG_Timeout *to, Uint32 (*fn)(void *, Uint32, void *), void *arg,
348     Uint flags)
349 {
350 	AG_InitTimer((AG_Timer *)to, "legacy", 0);
351 	to->fnLegacy = fn;
352 	to->argLegacy = arg;
353 }
354 void
AG_ScheduleTimeout(void * p,AG_Timeout * to,Uint32 ival)355 AG_ScheduleTimeout(void *p, AG_Timeout *to, Uint32 ival)
356 {
357 	AG_Object *ob = (p != NULL) ? p : &agTimerMgr;
358 
359 	if (AG_AddTimer(ob, to, ival, LegacyTimerCallback, NULL) == -1)
360 		AG_FatalError("ScheduleTimeout: %s", AG_GetError());
361 }
362 #endif /* AG_LEGACY */
363