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