/* * Copyright (c) 2004-2009 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Implementation of simple timers. Timers are usually associated with an * AG_Object for management purposes. */ #include struct ag_objectq agTimeoutObjQ = TAILQ_HEAD_INITIALIZER(agTimeoutObjQ); AG_Object agTimeoutMgr; #ifdef AG_THREADS AG_Mutex agTimingLock; #endif void AG_InitTimeouts(void) { AG_MutexInitRecursive(&agTimingLock); AG_ObjectInitStatic(&agTimeoutMgr, NULL); } void AG_DestroyTimeouts(void) { AG_ObjectDestroy(&agTimeoutMgr); AG_MutexDestroy(&agTimingLock); } /* Initialize a timeout structure. */ void AG_SetTimeout(AG_Timeout *to, Uint32 (*fn)(void *, Uint32, void *), void *arg, Uint flags) { to->fn = fn; to->arg = arg; to->ticks = 0; to->flags = flags; } /* Schedule (or re-schedule) the timeout to occur in dt ticks. */ void AG_ScheduleTimeout(void *p, AG_Timeout *to, Uint32 dt) { AG_Object *ob = (p != NULL) ? p : &agTimeoutMgr; AG_Timeout *toAfter; Uint32 t = AG_GetTicks()+dt; int listEmpty; AG_ObjectLock(ob); listEmpty = TAILQ_EMPTY(&ob->timeouts); if (!listEmpty && (to->flags & AG_TIMEOUT_QUEUED)) { TAILQ_REMOVE(&ob->timeouts, to, timeouts); } TAILQ_FOREACH(toAfter, &ob->timeouts, timeouts) { if (dt < toAfter->ticks) { TAILQ_INSERT_BEFORE(toAfter, to, timeouts); break; } } if (toAfter == TAILQ_END(&ob->timeouts)) { TAILQ_INSERT_HEAD(&ob->timeouts, to, timeouts); } to->ticks = t; to->ival = dt; to->flags |= AG_TIMEOUT_QUEUED; if (listEmpty) { AG_LockTiming(); TAILQ_INSERT_TAIL(&agTimeoutObjQ, ob, tobjs); AG_UnlockTiming(); } AG_ObjectUnlock(ob); } /* Cancel the given timeout if it is scheduled for execution. */ void AG_DelTimeout(void *p, AG_Timeout *to) { AG_Object *ob = (p != NULL) ? p : &agTimeoutMgr; AG_LockTimeouts(ob); if (to->flags & AG_TIMEOUT_QUEUED) { to->flags &= ~(AG_TIMEOUT_QUEUED); TAILQ_REMOVE(&ob->timeouts, to, timeouts); if (TAILQ_EMPTY(&ob->timeouts)) TAILQ_REMOVE(&agTimeoutObjQ, ob, tobjs); } AG_UnlockTimeouts(ob); } /* * Block the calling thread until the given timeout executes (and is not * immediately rescheduled), or the given delay (given in milliseconds) * is exceeded. */ int AG_TimeoutWait(void *p, AG_Timeout *to, Uint32 timeout) { AG_Object *ob = (p != NULL) ? p : &agTimeoutMgr; Uint32 elapsed = 0; wait: if (timeout > 0 && ++elapsed >= timeout) { AG_SetError(_("Timeout after %u ticks"), (Uint)elapsed); return (-1); } AG_Delay(1); AG_LockTimeouts(ob); if (to->flags & AG_TIMEOUT_QUEUED) { AG_UnlockTimeouts(ob); goto wait; } AG_UnlockTimeouts(ob); return (0); } void AG_ProcessTimeouts(Uint32 t) { AG_Timeout *to; AG_Object *ob; Uint32 rv; AG_LockTiming(); TAILQ_FOREACH(ob, &agTimeoutObjQ, tobjs) { AG_ObjectLock(ob); /* * Loop comparing the timestamp of the first element with * the current time for as long as the timestamp is in * the past. */ pop: if (!TAILQ_EMPTY(&ob->timeouts)) { to = TAILQ_FIRST(&ob->timeouts); if ((int)(to->ticks - t) <= 0) { TAILQ_REMOVE(&ob->timeouts, to, timeouts); if (TAILQ_EMPTY(&ob->timeouts)) { TAILQ_REMOVE(&agTimeoutObjQ, ob, tobjs); } to->flags &= ~(AG_TIMEOUT_QUEUED); rv = to->fn(ob, to->ival, to->arg); if (rv > 0) { to->ival = rv; AG_ScheduleTimeout(ob, to, rv); } goto pop; } } AG_ObjectUnlock(ob); } AG_UnlockTiming(); AG_Delay(1); }