/* * Copyright (c) 2001-2008 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 the generic event system for AG_Object. */ #include #include #include #include #include static void PropagateEvent(AG_Object *, AG_Object *, AG_Event *); /* Execute a scheduled event invocation. */ static Uint32 SchedEventTimeout(void *p, Uint32 ival, void *arg) { AG_Object *ob = p; AG_Event *ev = arg; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(ob, "Event <%s> timeout (%u ticks)\n", ev->name, (Uint)ival); #endif ev->flags &= ~(AG_EVENT_SCHEDULED); /* Propagate event to children. */ if (ev->flags & AG_EVENT_PROPAGATE) { AG_Object *child; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(ob, "Propagate <%s> (timeout)\n", ev->name); #endif AG_LockVFS(ob); OBJECT_FOREACH_CHILD(child, ob, ag_object) { PropagateEvent(ob, child, ev); } AG_UnlockVFS(ob); } /* Invoke the event handler routine. */ if (ev->handler != NULL) { ev->handler(ev); } return (0); } static __inline__ void SetEventName(AG_Event *ev, AG_Object *ob, const char *name) { if (name == NULL) { if (ob != NULL) { /* XXX snprintf is too slow */ Snprintf(ev->name, sizeof(ev->name), "__%u", ob->nevents); } else { Strlcpy(ev->name, "noname", sizeof(ev->name)); } } else { Strlcpy(ev->name, name, sizeof(ev->name)); } } static __inline__ void InitPointer(AG_Variable *V, const char *name, AG_Object *ob) { V->type = AG_VARIABLE_POINTER; V->fn.fnVoid = NULL; V->mutex = NULL; V->data.p = ob; Strlcpy(V->name, name, sizeof(V->name)); } static __inline__ void InitEvent(AG_Event *ev, AG_Object *ob) { ev->flags = 0; ev->argc = 1; ev->argc0 = 1; ev->handler = NULL; InitPointer(&ev->argv[0], "_self", ob); AG_SetTimeout(&ev->timeout, SchedEventTimeout, ev, 0); } void AG_EventInit(AG_Event *ev) { InitEvent(ev, NULL); } /* Construct an Event structure from the given arguments only. */ void AG_EventArgs(AG_Event *ev, const char *fmt, ...) { InitEvent(ev, NULL); AG_EVENT_GET_ARGS(ev, fmt); } /* Set or update the handler function for the given event. */ AG_Event * AG_SetEvent(void *p, const char *name, AG_EventFn fn, const char *fmt, ...) { AG_Object *ob = p; AG_Event *ev; AG_ObjectLock(ob); if (name != NULL) { TAILQ_FOREACH(ev, &ob->events, events) if (strcmp(ev->name, name) == 0) break; } else { ev = NULL; } if (ev == NULL) { ev = Malloc(sizeof(AG_Event)); InitEvent(ev, ob); SetEventName(ev, ob, name); TAILQ_INSERT_TAIL(&ob->events, ev, events); ob->nevents++; } else { InitEvent(ev, ob); } InitPointer(&ev->argv[0], "_self", ob); ev->handler = fn; AG_EVENT_GET_ARGS(ev, fmt); ev->argc0 = ev->argc; AG_ObjectUnlock(ob); return (ev); } /* Configure an additional handler function for the specified event. */ AG_Event * AG_AddEvent(void *p, const char *name, AG_EventFn fn, const char *fmt, ...) { AG_Object *ob = p; AG_Event *ev; AG_ObjectLock(ob); ev = Malloc(sizeof(AG_Event)); InitEvent(ev, ob); SetEventName(ev, ob, name); ev->handler = fn; AG_EVENT_GET_ARGS(ev, fmt); ev->argc0 = ev->argc; TAILQ_INSERT_TAIL(&ob->events, ev, events); ob->nevents++; AG_ObjectUnlock(ob); return (ev); } /* Remove the named event handler and cancel any scheduled execution. */ void AG_UnsetEvent(void *p, const char *name) { AG_Object *ob = p; AG_Event *ev; AG_ObjectLock(ob); TAILQ_FOREACH(ev, &ob->events, events) { if (strcmp(name, ev->name) == 0) break; } if (ev == NULL) { goto out; } if (ev->flags & AG_EVENT_SCHEDULED) { /* XXX concurrent */ AG_DelTimeout(ob, &ev->timeout); } TAILQ_REMOVE(&ob->events, ev, events); ob->nevents--; Free(ev); out: AG_ObjectUnlock(ob); } /* Return the Event structure for the named event. */ AG_Event * AG_FindEventHandler(void *p, const char *name) { AG_Object *ob = p; AG_Event *ev; AG_ObjectLock(ob); TAILQ_FOREACH(ev, &ob->events, events) { if (strcmp(name, ev->name) == 0) break; } AG_ObjectUnlock(ob); return (ev); } /* Forward an event to an object's descendents. */ static void PropagateEvent(AG_Object *sndr, AG_Object *rcvr, AG_Event *ev) { AG_Object *chld; OBJECT_FOREACH_CHILD(chld, rcvr, ag_object) { PropagateEvent(rcvr, chld, ev); } AG_ForwardEvent(sndr, rcvr, ev); } #ifdef AG_THREADS /* Invoke an event handler routine asynchronously. */ static void * EventThread(void *p) { AG_Event *eev = p; AG_Object *rcvr = eev->argv[0].data.p; AG_Object *chld; if (eev->flags & AG_EVENT_PROPAGATE) { #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Propagate <%s> (async)\n", eev->name); #endif AG_LockVFS(rcvr); OBJECT_FOREACH_CHILD(chld, rcvr, ag_object) { PropagateEvent(rcvr, chld, eev); } AG_UnlockVFS(rcvr); } #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "BEGIN event thread for <%s>\n", eev->name); #endif if (eev->handler != NULL) { eev->handler(eev); } #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "CLOSE event thread for <%s>\n", eev->name); #endif Free(eev); return (NULL); } #endif /* AG_THREADS */ /* * Execute the event handler routine for the given event. The given arguments * are appended to the end of the argument vector. * * Event handler invocations may be nested. However, the event handler table * of the object should not be modified while in event context (XXX) */ void AG_PostEvent(void *sp, void *rp, const char *evname, const char *fmt, ...) { AG_Object *sndr = sp; AG_Object *rcvr = rp; AG_Event *ev; AG_Object *chld; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Event <%s> posted from %s\n", evname, sndr?sndr->name:"NULL"); #endif AG_ObjectLock(rcvr); TAILQ_FOREACH(ev, &rcvr->events, events) { if (strcmp(evname, ev->name) != 0) continue; #ifdef AG_THREADS if (ev->flags & AG_EVENT_ASYNC) { AG_Thread th; AG_Event *evNew; /* TODO allocate from an per-object pool */ evNew = Malloc(sizeof(AG_Event)); memcpy(evNew, ev, sizeof(AG_Event)); AG_EVENT_GET_ARGS(evNew, fmt); InitPointer(&evNew->argv[evNew->argc], "_sender", sndr); AG_ThreadCreate(&th, EventThread, evNew); } else #endif /* AG_THREADS */ { AG_Event tmpev; memcpy(&tmpev, ev, sizeof(AG_Event)); AG_EVENT_GET_ARGS(&tmpev, fmt); InitPointer(&tmpev.argv[tmpev.argc], "_sender", sndr); if (tmpev.flags & AG_EVENT_PROPAGATE) { #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Propagate <%s> (post)\n", evname); #endif AG_LockVFS(rcvr); OBJECT_FOREACH_CHILD(chld, rcvr, ag_object) { PropagateEvent(rcvr, chld, &tmpev); } AG_UnlockVFS(rcvr); } if (tmpev.handler != NULL) tmpev.handler(&tmpev); } } AG_ObjectUnlock(rcvr); } /* * Schedule the execution of the given event in the given number of * ticks. The given arguments are appended to the argument vector. */ int AG_SchedEvent(void *sp, void *rp, Uint32 ticks, const char *evname, const char *fmt, ...) { AG_Object *sndr = sp; AG_Object *rcvr = rp; AG_Event *ev; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Schedule <%s> in %u ticks\n", evname, (Uint)ticks); #endif AG_LockTiming(); AG_ObjectLock(rcvr); TAILQ_FOREACH(ev, &rcvr->events, events) { if (strcmp(evname, ev->name) == 0) break; } if (ev == NULL) { goto fail; } if (ev->flags & AG_EVENT_SCHEDULED) { #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Reschedule <%s>\n", evname); #endif AG_DelTimeout(rcvr, &ev->timeout); } ev->argc = ev->argc0; AG_EVENT_GET_ARGS(ev, fmt); InitPointer(&ev->argv[ev->argc], "_sender", sndr); ev->flags |= AG_EVENT_SCHEDULED; AG_ScheduleTimeout(rcvr, &ev->timeout, ticks); AG_UnlockTiming(); AG_ObjectUnlock(rcvr); return (0); fail: AG_UnlockTiming(); AG_ObjectUnlock(rcvr); return (-1); } int AG_ReschedEvent(void *p, const char *evname, Uint32 ticks) { AG_Object *ob = p; AG_Event *ev; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(ob, "Reschedule <%s> in %u ticks\n", evname, (Uint)ticks); #endif AG_LockTiming(); AG_ObjectLock(ob); TAILQ_FOREACH(ev, &ob->events, events) { if (strcmp(evname, ev->name) == 0) break; } if (ev == NULL) { goto fail; } if (ev->flags & AG_EVENT_SCHEDULED) { AG_DelTimeout(ob, &ev->timeout); } ev->flags |= AG_EVENT_SCHEDULED; AG_ScheduleTimeout(ob, &ev->timeout, ticks); AG_ObjectUnlock(ob); AG_UnlockTiming(); return (0); fail: AG_ObjectUnlock(ob); AG_UnlockTiming(); return (-1); } /* Cancel any future execution of the given event. */ int AG_CancelEvent(void *p, const char *evname) { AG_Object *ob = p; AG_Event *ev; int rv = 0; AG_LockTiming(); AG_ObjectLock(ob); TAILQ_FOREACH(ev, &ob->events, events) { if (strcmp(ev->name, evname) == 0) break; } if (ev == NULL) { goto fail; } if (ev->flags & AG_EVENT_SCHEDULED) { #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(ob, "Cancelled timeout <%s> (cancel)\n", evname); #endif AG_DelTimeout(ob, &ev->timeout); rv++; ev->flags &= ~(AG_EVENT_SCHEDULED); } /* XXX concurrent */ AG_ObjectUnlock(ob); AG_UnlockTiming(); return (rv); fail: AG_ObjectUnlock(ob); AG_UnlockTiming(); return (-1); } /* * Forward an event, without modifying the original event structure, except * for the sender and receiver pointers. */ void AG_ForwardEvent(void *pSndr, void *pRcvr, AG_Event *event) { AG_Object *sndr = pSndr; AG_Object *rcvr = pRcvr; AG_Object *chld; AG_Event *ev; #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Event <%s> forwarded from %s\n", event->name, sndr?sndr->name:"NULL"); #endif AG_ObjectLock(rcvr); TAILQ_FOREACH(ev, &rcvr->events, events) { if (strcmp(event->name, ev->name) == 0) break; } if (ev == NULL) goto out; #ifdef AG_THREADS if (ev->flags & AG_EVENT_ASYNC) { AG_Thread th; AG_Event *evNew; /* TODO allocate from an per-object pool */ evNew = Malloc(sizeof(AG_Event)); memcpy(evNew, ev, sizeof(AG_Event)); InitPointer(&evNew->argv[0], "_self", rcvr); InitPointer(&evNew->argv[evNew->argc], "_sender", sndr); AG_ThreadCreate(&th, EventThread, evNew); } else #endif /* AG_THREADS */ { AG_Event tmpev; memcpy(&tmpev, event, sizeof(AG_Event)); InitPointer(&tmpev.argv[0], "_self", rcvr); InitPointer(&tmpev.argv[tmpev.argc], "_sender", sndr); if (ev->flags & AG_EVENT_PROPAGATE) { #ifdef AG_EVENTDEBUG if (agDebugLvl >= 5) Debug(rcvr, "Propagate <%s> (forward)\n", event->name); #endif AG_LockVFS(rcvr); OBJECT_FOREACH_CHILD(chld, rcvr, ag_object) { PropagateEvent(rcvr, chld, ev); } AG_UnlockVFS(rcvr); } /* XXX AG_EVENT_ASYNC.. */ if (ev->handler != NULL) ev->handler(&tmpev); } out: AG_ObjectUnlock(rcvr); }