/** Implementation of object for waiting on several input sources NSRunLoop.m Copyright (C) 1996-1999 Free Software Foundation, Inc. Original by: Andrew Kachites McCallum Created: March 1996 OPENSTEP version by: Richard Frith-Macdonald Created: August 1997 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. NSRunLoop class reference $Date$ $Revision$ */ #import "common.h" #define EXPOSE_NSRunLoop_IVARS 1 #define EXPOSE_NSTimer_IVARS 1 #import "Foundation/NSMapTable.h" #import "Foundation/NSDate.h" #import "Foundation/NSValue.h" #import "Foundation/NSAutoreleasePool.h" #import "Foundation/NSPort.h" #import "Foundation/NSTimer.h" #import "Foundation/NSNotification.h" #import "Foundation/NSNotificationQueue.h" #import "Foundation/NSRunLoop.h" #import "Foundation/NSStream.h" #import "Foundation/NSThread.h" #import "Foundation/NSInvocation.h" #import "GSRunLoopCtxt.h" #import "GSRunLoopWatcher.h" #import "GSStream.h" #import "GSPrivate.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_POLL_F #include #endif #include #include NSString * const NSDefaultRunLoopMode = @"NSDefaultRunLoopMode"; static NSDate *theFuture = nil; @interface NSObject (OptionalPortRunLoop) - (void) getFds: (NSInteger*)fds count: (NSInteger*)count; @end /* * The GSRunLoopPerformer class is used to hold information about * messages which are due to be sent to objects once each runloop * iteration has passed. */ @interface GSRunLoopPerformer: NSObject { @public SEL selector; id target; id argument; unsigned order; } - (void) fire; - (id) initWithSelector: (SEL)aSelector target: (id)target argument: (id)argument order: (NSUInteger)order; @end @implementation GSRunLoopPerformer - (void) dealloc { RELEASE(target); RELEASE(argument); [super dealloc]; } - (void) fire { NS_DURING { [target performSelector: selector withObject: argument]; } NS_HANDLER { NSLog(@"*** NSRunLoop ignoring exception '%@' (reason '%@') " @"raised during performSelector... with target %p " @"and selector '%@'", [localException name], [localException reason], target, NSStringFromSelector([target selector])); } NS_ENDHANDLER } - (id) initWithSelector: (SEL)aSelector target: (id)aTarget argument: (id)anArgument order: (NSUInteger)theOrder { self = [super init]; if (self) { selector = aSelector; target = RETAIN(aTarget); argument = RETAIN(anArgument); order = theOrder; } return self; } @end @interface NSRunLoop (TimedPerformers) - (NSMutableArray*) _timedPerformers; @end @implementation NSRunLoop (TimedPerformers) - (NSMutableArray*) _timedPerformers { return _timedPerformers; } @end /* * The GSTimedPerformer class is used to hold information about * messages which are due to be sent to objects at a particular time. */ @interface GSTimedPerformer: NSObject { @public SEL selector; id target; id argument; NSTimer *timer; } - (void) fire; - (id) initWithSelector: (SEL)aSelector target: (id)target argument: (id)argument delay: (NSTimeInterval)delay; - (void) invalidate; @end @implementation GSTimedPerformer - (void) dealloc { [self finalize]; TEST_RELEASE(timer); RELEASE(target); RELEASE(argument); [super dealloc]; } - (void) fire { DESTROY(timer); [target performSelector: selector withObject: argument]; [[[NSRunLoop currentRunLoop] _timedPerformers] removeObjectIdenticalTo: self]; } - (void) finalize { [self invalidate]; } - (id) initWithSelector: (SEL)aSelector target: (id)aTarget argument: (id)anArgument delay: (NSTimeInterval)delay { self = [super init]; if (self != nil) { selector = aSelector; target = RETAIN(aTarget); argument = RETAIN(anArgument); timer = [[NSTimer allocWithZone: NSDefaultMallocZone()] initWithFireDate: nil interval: delay target: self selector: @selector(fire) userInfo: nil repeats: NO]; } return self; } - (void) invalidate { if (timer != nil) { [timer invalidate]; DESTROY(timer); } } @end /* * Setup for inline operation of arrays. */ #ifndef GSI_ARRAY_TYPES #define GSI_ARRAY_TYPES GSUNION_OBJ #if GS_WITH_GC == 0 #define GSI_ARRAY_RELEASE(A, X) [(X).obj release] #define GSI_ARRAY_RETAIN(A, X) [(X).obj retain] #else #define GSI_ARRAY_RELEASE(A, X) #define GSI_ARRAY_RETAIN(A, X) #endif #include "GNUstepBase/GSIArray.h" #endif static inline NSDate *timerDate(NSTimer *t) { return t->_date; } static inline BOOL timerInvalidated(NSTimer *t) { return t->_invalidated; } @implementation NSObject (TimedPerformers) /* * Cancels any perform operations set up for the specified target * in the current run loop. */ + (void) cancelPreviousPerformRequestsWithTarget: (id)target { NSMutableArray *perf = [[NSRunLoop currentRunLoop] _timedPerformers]; unsigned count = [perf count]; if (count > 0) { GSTimedPerformer *array[count]; IF_NO_GC(RETAIN(target)); [perf getObjects: array]; while (count-- > 0) { GSTimedPerformer *p = array[count]; if (p->target == target) { [p invalidate]; [perf removeObjectAtIndex: count]; } } RELEASE(target); } } /* * Cancels any perform operations set up for the specified target * in the current loop, but only if the value of aSelector and argument * with which the performs were set up match those supplied.
* Matching of the argument may be either by pointer equality or by * use of the [NSObject-isEqual:] method. */ + (void) cancelPreviousPerformRequestsWithTarget: (id)target selector: (SEL)aSelector object: (id)arg { NSMutableArray *perf = [[NSRunLoop currentRunLoop] _timedPerformers]; unsigned count = [perf count]; if (count > 0) { GSTimedPerformer *array[count]; IF_NO_GC(RETAIN(target)); IF_NO_GC(RETAIN(arg)); [perf getObjects: array]; while (count-- > 0) { GSTimedPerformer *p = array[count]; if (p->target == target && sel_isEqual(p->selector, aSelector) && (p->argument == arg || [p->argument isEqual: arg])) { [p invalidate]; [perf removeObjectAtIndex: count]; } } RELEASE(arg); RELEASE(target); } } - (void) performSelector: (SEL)aSelector withObject: (id)argument afterDelay: (NSTimeInterval)seconds { NSRunLoop *loop = [NSRunLoop currentRunLoop]; GSTimedPerformer *item; item = [[GSTimedPerformer alloc] initWithSelector: aSelector target: self argument: argument delay: seconds]; [[loop _timedPerformers] addObject: item]; RELEASE(item); [loop addTimer: item->timer forMode: NSDefaultRunLoopMode]; } - (void) performSelector: (SEL)aSelector withObject: (id)argument afterDelay: (NSTimeInterval)seconds inModes: (NSArray*)modes { unsigned count = [modes count]; if (count > 0) { NSRunLoop *loop = [NSRunLoop currentRunLoop]; NSString *marray[count]; GSTimedPerformer *item; unsigned i; item = [[GSTimedPerformer alloc] initWithSelector: aSelector target: self argument: argument delay: seconds]; [[loop _timedPerformers] addObject: item]; RELEASE(item); if ([modes isProxy]) { for (i = 0; i < count; i++) { marray[i] = [modes objectAtIndex: i]; } } else { [modes getObjects: marray]; } for (i = 0; i < count; i++) { [loop addTimer: item->timer forMode: marray[i]]; } } } @end @interface NSRunLoop (Private) - (void) _addWatcher: (GSRunLoopWatcher*)item forMode: (NSString*)mode; - (BOOL) _checkPerformers: (GSRunLoopCtxt*)context; - (GSRunLoopWatcher*) _getWatcher: (void*)data type: (RunLoopEventType)type forMode: (NSString*)mode; - (id) _init; - (void) _removeWatcher: (void*)data type: (RunLoopEventType)type forMode: (NSString*)mode; @end @implementation NSRunLoop (Private) /* Add a watcher to the list for the specified mode. Keep the list in limit-date order. */ - (void) _addWatcher: (GSRunLoopWatcher*) item forMode: (NSString*)mode { GSRunLoopCtxt *context; GSIArray watchers; unsigned i; context = NSMapGet(_contextMap, mode); if (context == nil) { context = [[GSRunLoopCtxt alloc] initWithMode: mode extra: _extra]; NSMapInsert(_contextMap, context->mode, context); RELEASE(context); } watchers = context->watchers; GSIArrayAddItem(watchers, (GSIArrayItem)((id)item)); i = GSIArrayCount(watchers); if (i % 1000 == 0 && i > context->maxWatchers) { context->maxWatchers = i; NSLog(@"WARNING ... there are %u watchers scheduled in mode %@ of %@", i, mode, self); } } - (BOOL) _checkPerformers: (GSRunLoopCtxt*)context { BOOL found = NO; if (context != nil) { GSIArray performers = context->performers; unsigned count = GSIArrayCount(performers); if (count > 0) { NSAutoreleasePool *arp = [NSAutoreleasePool new]; GSRunLoopPerformer *array[count]; NSMapEnumerator enumerator; GSRunLoopCtxt *original; void *mode; unsigned i; found = YES; /* We have to remove the performers before firing, so we copy * the pointers without releasing the objects, and then set * the performers to be empty. The copied objects in 'array' * will be released later. */ for (i = 0; i < count; i++) { array[i] = GSIArrayItemAtIndex(performers, i).obj; } performers->count = 0; /* Remove the requests that we are about to fire from all modes. */ original = context; enumerator = NSEnumerateMapTable(_contextMap); while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context)) { if (context != nil && context != original) { GSIArray performers = context->performers; unsigned tmpCount = GSIArrayCount(performers); while (tmpCount--) { GSRunLoopPerformer *p; p = GSIArrayItemAtIndex(performers, tmpCount).obj; for (i = 0; i < count; i++) { if (p == array[i]) { GSIArrayRemoveItemAtIndex(performers, tmpCount); } } } } } NSEndMapTableEnumeration(&enumerator); /* Finally, fire the requests ands release them. */ for (i = 0; i < count; i++) { [array[i] fire]; RELEASE(array[i]); IF_NO_GC([arp emptyPool];) } [arp drain]; } } return found; } /** * Locates a runloop watcher matching the specified data and type in this * runloop. If the mode is nil, either the currentMode is used (if the * loop is running) or NSDefaultRunLoopMode is used. */ - (GSRunLoopWatcher*) _getWatcher: (void*)data type: (RunLoopEventType)type forMode: (NSString*)mode { GSRunLoopCtxt *context; if (mode == nil) { mode = [self currentMode]; if (mode == nil) { mode = NSDefaultRunLoopMode; } } context = NSMapGet(_contextMap, mode); if (context != nil) { GSIArray watchers = context->watchers; unsigned i = GSIArrayCount(watchers); while (i-- > 0) { GSRunLoopWatcher *info; info = GSIArrayItemAtIndex(watchers, i).obj; if (info->type == type && info->data == data) { return info; } } } return nil; } - (id) _init { self = [super init]; if (self != nil) { _contextStack = [NSMutableArray new]; _contextMap = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 0); _timedPerformers = [[NSMutableArray alloc] initWithCapacity: 8]; #ifdef HAVE_POLL_F #if GS_WITH_GC _extra = NSAllocateCollectable(sizeof(pollextra), NSScannedOption); #else _extra = NSZoneMalloc(NSDefaultMallocZone(), sizeof(pollextra)); memset(_extra, '\0', sizeof(pollextra)); #endif #endif } return self; } /** * Removes a runloop watcher matching the specified data and type in this * runloop. If the mode is nil, either the currentMode is used (if the * loop is running) or NSDefaultRunLoopMode is used. */ - (void) _removeWatcher: (void*)data type: (RunLoopEventType)type forMode: (NSString*)mode { GSRunLoopCtxt *context; if (mode == nil) { mode = [self currentMode]; if (mode == nil) { mode = NSDefaultRunLoopMode; } } context = NSMapGet(_contextMap, mode); if (context != nil) { GSIArray watchers = context->watchers; unsigned i = GSIArrayCount(watchers); while (i-- > 0) { GSRunLoopWatcher *info; info = GSIArrayItemAtIndex(watchers, i).obj; if (info->type == type && info->data == data) { info->_invalidated = YES; GSIArrayRemoveItemAtIndex(watchers, i); } } } } @end @implementation NSRunLoop(GNUstepExtensions) - (void) addEvent: (void*)data type: (RunLoopEventType)type watcher: (id)watcher forMode: (NSString*)mode { GSRunLoopWatcher *info; if (mode == nil) { mode = [self currentMode]; if (mode == nil) { mode = NSDefaultRunLoopMode; } } info = [self _getWatcher: data type: type forMode: mode]; if (info != nil && (id)info->receiver == (id)watcher) { /* Increment usage count for this watcher. */ info->count++; } else { /* Remove any existing handler for another watcher. */ [self _removeWatcher: data type: type forMode: mode]; /* Create new object to hold information. */ info = [[GSRunLoopWatcher alloc] initWithType: type receiver: watcher data: data]; /* Add the object to the array for the mode. */ [self _addWatcher: info forMode: mode]; RELEASE(info); /* Now held in array. */ } } - (void) removeEvent: (void*)data type: (RunLoopEventType)type forMode: (NSString*)mode all: (BOOL)removeAll { if (mode == nil) { mode = [self currentMode]; if (mode == nil) { mode = NSDefaultRunLoopMode; } } if (removeAll) { [self _removeWatcher: data type: type forMode: mode]; } else { GSRunLoopWatcher *info; info = [self _getWatcher: data type: type forMode: mode]; if (info) { if (info->count == 0) { [self _removeWatcher: data type: type forMode: mode]; } else { info->count--; } } } } @end /** *

NSRunLoop instances handle various utility tasks that must * be performed repetitively in an application, such as processing input * events, listening for distributed objects communications, firing * [NSTimer]s, and sending notifications and other messages * asynchronously.

* *

There is one run loop per thread in an application, which * may always be obtained through the +currentRunLoop method * (you cannot use -init or +new), * however unless you are using the AppKit and the [NSApplication] class, the * run loop will not be started unless you explicitly send it a * -run message.

* *

At any given point, a run loop operates in a single mode, usually * NSDefaultRunLoopMode. Other options include * NSConnectionReplyMode, and certain modes used by the AppKit.

*/ @implementation NSRunLoop + (void) initialize { if (self == [NSRunLoop class]) { [self currentRunLoop]; theFuture = RETAIN([NSDate distantFuture]); [[NSObject leakAt: &theFuture] release]; } } + (NSRunLoop*) _runLoopForThread: (NSThread*) aThread { GSRunLoopThreadInfo *info = GSRunLoopInfoForThread(aThread); NSRunLoop *current = info->loop; if (nil == current) { current = info->loop = [[self alloc] _init]; /* If this is the main thread, set up a housekeeping timer. */ if (nil != current && [GSCurrentThread() isMainThread] == YES) { NSAutoreleasePool *arp = [NSAutoreleasePool new]; GSRunLoopCtxt *context; NSNotificationCenter *ctr; NSNotification *not; NSInvocation *inv; NSTimer *timer; SEL sel; ctr = [NSNotificationCenter defaultCenter]; not = [NSNotification notificationWithName: @"GSHousekeeping" object: nil userInfo: nil]; sel = @selector(postNotification:); inv = [NSInvocation invocationWithMethodSignature: [ctr methodSignatureForSelector: sel]]; [inv setTarget: ctr]; [inv setSelector: sel]; [inv setArgument: ¬ atIndex: 2]; [inv retainArguments]; context = NSMapGet(current->_contextMap, NSDefaultRunLoopMode); if (context == nil) { context = [GSRunLoopCtxt alloc]; context = [context initWithMode: NSDefaultRunLoopMode extra: current->_extra]; NSMapInsert(current->_contextMap, context->mode, context); RELEASE(context); } if (context->housekeeper != nil) { [context->housekeeper invalidate]; DESTROY(context->housekeeper); } timer = [[NSTimer alloc] initWithFireDate: nil interval: 30.0 target: inv selector: NULL userInfo: nil repeats: YES]; context->housekeeper = timer; [arp drain]; } } return current; } + (NSRunLoop*) currentRunLoop { return [self _runLoopForThread: nil]; } + (NSRunLoop*) mainRunLoop { return [self _runLoopForThread: [NSThread mainThread]]; } - (id) init { DESTROY(self); return nil; } - (void) dealloc { #ifdef HAVE_POLL_F if (_extra != 0) { pollextra *e = (pollextra*)_extra; if (e->index != 0) NSZoneFree(NSDefaultMallocZone(), e->index); NSZoneFree(NSDefaultMallocZone(), e); } #endif RELEASE(_contextStack); if (_contextMap != 0) { NSFreeMapTable(_contextMap); } RELEASE(_timedPerformers); [super dealloc]; } /** * Returns the current mode of this runloop. If the runloop is not running * then this method returns nil. */ - (NSString*) currentMode { return _currentMode; } /** * Adds a timer to the loop in the specified mode.
* Timers are removed automatically when they are invalid.
*/ - (void) addTimer: (NSTimer*)timer forMode: (NSString*)mode { GSRunLoopCtxt *context; GSIArray timers; unsigned i; if ([timer isKindOfClass: [NSTimer class]] == NO || [timer isProxy] == YES) { [NSException raise: NSInvalidArgumentException format: @"[%@-%@] not a valid timer", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } if ([mode isKindOfClass: [NSString class]] == NO) { [NSException raise: NSInvalidArgumentException format: @"[%@-%@] not a valid mode", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } NSDebugMLLog(@"NSRunLoop", @"add timer for %f in %@", [[timer fireDate] timeIntervalSinceReferenceDate], mode); context = NSMapGet(_contextMap, mode); if (context == nil) { context = [[GSRunLoopCtxt alloc] initWithMode: mode extra: _extra]; NSMapInsert(_contextMap, context->mode, context); RELEASE(context); } timers = context->timers; i = GSIArrayCount(timers); while (i-- > 0) { if (timer == GSIArrayItemAtIndex(timers, i).obj) { return; /* Timer already present */ } } /* * NB. A previous version of the timer code maintained an ordered * array on the theory that we could improve performance by only * checking the first few timers (up to the first one whose fire * date is in the future) each time -limitDateForMode: is called. * The problem with this was that it's possible for one timer to * be added in multiple modes (or to different run loops) and for * a repeated timer this could mean that the firing of the timer * in one mode/loop adjusts its date ... without changing the * ordering of the timers in the other modes/loops which contain * the timer. When the ordering of timers in an array was broken * we could get delays in processing timeouts, so we reverted to * simply having timers in an unordered array and checking them * all each time -limitDateForMode: is called. */ GSIArrayAddItem(timers, (GSIArrayItem)((id)timer)); i = GSIArrayCount(timers); if (i % 1000 == 0 && i > context->maxTimers) { context->maxTimers = i; NSLog(@"WARNING ... there are %u timers scheduled in mode %@ of %@", i, mode, self); } } /* Ensure that the fire date has been updated either by the timeout handler * updating it or by incrementing it ourselves.
* Return YES if it was updated, NO if it was invalidated. */ static BOOL updateTimer(NSTimer *t, NSDate *d, NSTimeInterval now) { if (timerInvalidated(t) == YES) { return NO; } if (timerDate(t) == d) { NSTimeInterval ti = [d timeIntervalSinceReferenceDate]; NSTimeInterval increment = [t timeInterval]; if (increment <= 0.0) { /* Should never get here ... unless a subclass is returning * a bad interval ... we return NO so that the timer gets * removed from the loop. */ NSLog(@"WARNING timer %@ had bad interval ... removed", t); return NO; } ti += increment; // Hopefully a single increment will do. if (ti < now) { NSTimeInterval add; /* Just incrementing the date was insufficieint to bring it to * the current time, so we must have missed one or more fire * opportunities, or the fire date has been set on the timer. * If a fire date long ago has been set and the increment value * is really small, we might need to increment very many times * to get the new fire date. To avoid looping for ages, we * calculate the number of increments needed and do them in one * go. */ add = floor((now - ti) / increment); ti += (increment * add); if (ti < now) { ti += increment; } } d = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate: ti]; [t setFireDate: d]; RELEASE(d); } return YES; } /** * Fires timers whose fire date has passed, and checks timers and limit dates * for input sources, determining the earliest time that any future timeout * becomes due. Returns that date/time.
* Returns distant future if the loop contains no timers, just input sources * without timeouts.
* Returns nil if the loop contains neither timers nor input sources. */ - (NSDate*) limitDateForMode: (NSString*)mode { GSRunLoopCtxt *context; NSDate *when = nil; context = NSMapGet(_contextMap, mode); if (context != nil) { NSString *savedMode = _currentMode; NSAutoreleasePool *arp = [NSAutoreleasePool new]; _currentMode = mode; NS_DURING { GSIArray timers = context->timers; NSTimeInterval now; NSDate *earliest; NSDate *d; NSTimer *t; NSTimeInterval ti; NSTimeInterval ei; unsigned c; unsigned i; ei = 0.0; // Only needed to avoid compiler warning /* * Save current time so we don't keep redoing system call to * get it and so that we check timer fire dates against a known * value at the point when the method was called. * If we refetched the date after firing each timer, the time * taken in firing the timer could be large enough so we would * just keep firing the timer repeatedly and never return from * this method. */ now = GSPrivateTimeNow(); /* Fire housekeeping timer as necessary */ if ((t = context->housekeeper) != nil) { if (timerInvalidated(t)) { DESTROY(context->housekeeper); } else if ([(d=timerDate(t)) timeIntervalSinceReferenceDate] <= now) { [t fire]; GSPrivateNotifyASAP(_currentMode); IF_NO_GC([arp emptyPool];) updateTimer(t, d, now); } } /* Fire the oldest/first valid timer whose fire date has passed * and fire it. * We fire timers in the order in which they were added to the * run loop rather than in date order. This prevents code * from blocking other timers by adding timers whose fire date * is some time in the past... we guarantee fair handling. */ c = GSIArrayCount(timers); for (i = 0; i < c; i++) { t = GSIArrayItemAtIndex(timers, i).obj; if (timerInvalidated(t) == NO) { d = timerDate(t); ti = [d timeIntervalSinceReferenceDate]; if (ti < now) { GSIArrayRemoveItemAtIndexNoRelease(timers, i); [t fire]; GSPrivateNotifyASAP(_currentMode); IF_NO_GC([arp emptyPool];) if (updateTimer(t, d, now) == YES) { /* Updated ... replace in array. */ GSIArrayAddItemNoRetain(timers, (GSIArrayItem)((id)t)); } else { /* The timer was invalidated, so we can * release it as we aren't putting it back * in the array. */ RELEASE(t); } break; } } } /* Now, find the earliest remaining timer date while removing * any invalidated timers. We iterate from the end of the * array to minimise the amount of array alteration needed. */ earliest = nil; i = GSIArrayCount(timers); while (i-- > 0) { t = GSIArrayItemAtIndex(timers, i).obj; if (timerInvalidated(t) == YES) { GSIArrayRemoveItemAtIndex(timers, i); } else { d = timerDate(t); ti = [d timeIntervalSinceReferenceDate]; if (earliest == nil || ti < ei) { earliest = d; ei = ti; } } } /* The earliest date of a valid timeout is copied into 'when' * and used as our limit date. */ if (earliest != nil) { when = [earliest copy]; } _currentMode = savedMode; } NS_HANDLER { _currentMode = savedMode; [localException raise]; } NS_ENDHANDLER [arp release]; if (when == nil) { GSIArray watchers = context->watchers; unsigned i = GSIArrayCount(watchers); while (i-- > 0) { GSRunLoopWatcher *w = GSIArrayItemAtIndex(watchers, i).obj; if (w->_invalidated == YES) { GSIArrayRemoveItemAtIndex(watchers, i); } } if (GSIArrayCount(context->watchers) > 0) { when = theFuture; } } #if !GS_WITH_GC else { AUTORELEASE(when); } #endif NSDebugMLLog(@"NSRunLoop", @"limit date %f in %@", nil == when ? 0.0 : [when timeIntervalSinceReferenceDate], mode); } return when; } /** * Listen for events from input sources.
* If limit_date is nil or in the past, then don't wait; * just poll inputs and return, * otherwise block until input is available or until the * earliest limit date has passed (whichever comes first).
* If the supplied mode is nil, uses NSDefaultRunLoopMode.
* If there are no input sources or timers in the mode, returns immediately. */ - (void) acceptInputForMode: (NSString*)mode beforeDate: (NSDate*)limit_date { GSRunLoopCtxt *context; NSTimeInterval ti = 0; int timeout_ms; NSString *savedMode = _currentMode; NSAutoreleasePool *arp = [NSAutoreleasePool new]; NSAssert(mode, NSInvalidArgumentException); if (mode == nil) { mode = NSDefaultRunLoopMode; } _currentMode = mode; context = NSMapGet(_contextMap, mode); [self _checkPerformers: context]; NS_DURING { /* * If we have a housekeeping timer, and it is earlier than the * limit date we have been given, we use the date of the housekeeper * to determine when to stop. */ if (limit_date != nil && context != nil && context->housekeeper != nil && [timerDate(context->housekeeper) timeIntervalSinceReferenceDate] < [limit_date timeIntervalSinceReferenceDate]) { limit_date = timerDate(context->housekeeper); } if (context == nil || (GSIArrayCount(context->watchers) == 0 && GSIArrayCount(context->timers) == 0)) { NSDebugMLLog(@"NSRunLoop", @"no inputs or timers in mode %@", mode); GSPrivateNotifyASAP(_currentMode); GSPrivateNotifyIdle(_currentMode); /* Pause until the limit date or until we might have * a method to perform in this thread. */ [GSRunLoopCtxt awakenedBefore: nil]; GSPrivateCheckTasks(); if (context != nil) { [self _checkPerformers: context]; } GSPrivateNotifyASAP(_currentMode); _currentMode = savedMode; [arp drain]; NS_VOIDRETURN; } /* Find out how much time we should wait, and set SELECT_TIMEOUT. */ if (limit_date == nil || (ti = [limit_date timeIntervalSinceNow]) <= 0.0) { /* Don't wait at all. */ timeout_ms = 0; } else { /* Wait until the LIMIT_DATE. */ if (ti >= INT_MAX / 1000) { timeout_ms = INT_MAX; // Far future. } else { timeout_ms = (ti * 1000.0); } } NSDebugMLLog(@"NSRunLoop", @"accept I/P before %d millisec from now in %@", timeout_ms, mode); if ([_contextStack indexOfObjectIdenticalTo: context] == NSNotFound) { [_contextStack addObject: context]; } if ([context pollUntil: timeout_ms within: _contextStack] == NO) { GSPrivateNotifyIdle(_currentMode); } [self _checkPerformers: context]; GSPrivateNotifyASAP(_currentMode); _currentMode = savedMode; /* Once a poll has been completed on a context, we can remove that * context from the stack even if it actually polling at an outer * level of re-entrancy ... since the poll we have just done will * have handled any events that the outer levels would have wanted * to handle, and the polling for this context will be marked as ended. */ [context endPoll]; [_contextStack removeObjectIdenticalTo: context]; NSDebugMLLog(@"NSRunLoop", @"accept I/P completed in %@", mode); } NS_HANDLER { _currentMode = savedMode; [context endPoll]; [_contextStack removeObjectIdenticalTo: context]; [localException raise]; } NS_ENDHANDLER [arp drain]; } - (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date { NSAutoreleasePool *arp = [NSAutoreleasePool new]; NSString *savedMode = _currentMode; GSRunLoopCtxt *context; NSDate *d; NSAssert(mode != nil, NSInvalidArgumentException); /* Process any pending notifications. */ GSPrivateCheckTasks(); GSPrivateNotifyASAP(mode); /* And process any performers scheduled in the loop (eg something from * another thread. */ _currentMode = mode; context = NSMapGet(_contextMap, mode); [self _checkPerformers: context]; _currentMode = savedMode; /* Find out how long we can wait before first limit date. */ d = [self limitDateForMode: mode]; if (d == nil) { [arp drain]; return NO; } /* Use the earlier of the two dates we have. * Retain the date in case the firing of a timer (or some other event) * releases it. */ if (date != nil) { d = [d earlierDate: date]; } [d retain]; /* Wait, listening to our input sources. */ [self acceptInputForMode: mode beforeDate: d]; [d release]; [arp drain]; return YES; } /** * Runs the loop in NSDefaultRunLoopMode by repeated calls to * -runMode:beforeDate: while there are still input sources. Exits when no * more input sources remain. */ - (void) run { [self runUntilDate: theFuture]; } /** * Runs the loop in NSDefaultRunLoopMode by repeated calls to * -runMode:beforeDate: while there are still input sources. Exits when no * more input sources remain, or date is reached, whichever occurs first. */ - (void) runUntilDate: (NSDate*)date { double ti = [date timeIntervalSinceNow]; BOOL mayDoMore = YES; /* Positive values are in the future. */ while (ti > 0 && mayDoMore == YES) { NSDebugMLLog(@"NSRunLoop", @"run until date %f seconds from now", ti); mayDoMore = [self runMode: NSDefaultRunLoopMode beforeDate: date]; ti = [date timeIntervalSinceNow]; } } @end /** * OpenStep-compatibility methods for [NSRunLoop]. These methods are also * all in OS X. */ @implementation NSRunLoop (OPENSTEP) /** * Adds port to be monitored in given mode. */ - (void) addPort: (NSPort*)port forMode: (NSString*)mode { [self addEvent: (void*)port type: ET_RPORT watcher: (id)port forMode: (NSString*)mode]; } /** * Cancels any perform operations set up for the specified target * in the receiver. */ - (void) cancelPerformSelectorsWithTarget: (id) target { NSMapEnumerator enumerator; GSRunLoopCtxt *context; void *mode; enumerator = NSEnumerateMapTable(_contextMap); while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context)) { if (context != nil) { GSIArray performers = context->performers; unsigned count = GSIArrayCount(performers); while (count--) { GSRunLoopPerformer *p; p = GSIArrayItemAtIndex(performers, count).obj; if (p->target == target) { GSIArrayRemoveItemAtIndex(performers, count); } } } } NSEndMapTableEnumeration(&enumerator); } /** * Cancels any perform operations set up for the specified target * in the receiver, but only if the value of aSelector and argument * with which the performs were set up match those supplied.
* Matching of the argument may be either by pointer equality or by * use of the [NSObject-isEqual:] method. */ - (void) cancelPerformSelector: (SEL)aSelector target: (id) target argument: (id) argument { NSMapEnumerator enumerator; GSRunLoopCtxt *context; void *mode; enumerator = NSEnumerateMapTable(_contextMap); while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context)) { if (context != nil) { GSIArray performers = context->performers; unsigned count = GSIArrayCount(performers); while (count--) { GSRunLoopPerformer *p; p = GSIArrayItemAtIndex(performers, count).obj; if (p->target == target && sel_isEqual(p->selector, aSelector) && (p->argument == argument || [p->argument isEqual: argument])) { GSIArrayRemoveItemAtIndex(performers, count); } } } } NSEndMapTableEnumeration(&enumerator); } /** * Configure event processing for acting as a server process for distributed * objects. (In the current implementation this is a no-op.) */ - (void) configureAsServer { return; /* Nothing to do here */ } /** * Sets up sending of aSelector to target with argument.
* The selector is sent before the next runloop iteration (unless * cancelled before then) in any of the specified modes.
* The target and argument objects are retained.
* The order value is used to determine the order in which messages * are sent if multiple messages have been set up. Messages with a lower * order value are sent first.
* If the modes array is empty, this method has no effect. */ - (void) performSelector: (SEL)aSelector target: (id)target argument: (id)argument order: (NSUInteger)order modes: (NSArray*)modes { unsigned count = [modes count]; if (count > 0) { NSString *array[count]; GSRunLoopPerformer *item; item = [[GSRunLoopPerformer alloc] initWithSelector: aSelector target: target argument: argument order: order]; if ([modes isProxy]) { unsigned i; for (i = 0; i < count; i++) { array[i] = [modes objectAtIndex: i]; } } else { [modes getObjects: array]; } while (count-- > 0) { NSString *mode = array[count]; unsigned end; unsigned i; GSRunLoopCtxt *context; GSIArray performers; context = NSMapGet(_contextMap, mode); if (context == nil) { context = [[GSRunLoopCtxt alloc] initWithMode: mode extra: _extra]; NSMapInsert(_contextMap, context->mode, context); RELEASE(context); } performers = context->performers; end = GSIArrayCount(performers); for (i = 0; i < end; i++) { GSRunLoopPerformer *p; p = GSIArrayItemAtIndex(performers, i).obj; if (p->order > order) { GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i); break; } } if (i == end) { GSIArrayInsertItem(performers, (GSIArrayItem)((id)item), i); } i = GSIArrayCount(performers); if (i % 1000 == 0 && i > context->maxPerformers) { context->maxPerformers = i; NSLog(@"WARNING ... there are %u performers scheduled" @" in mode %@ of %@\n(Latest: [%@ %@])", i, mode, self, NSStringFromClass([target class]), NSStringFromSelector(aSelector)); } } RELEASE(item); } } /** * Removes port to be monitored from given mode. * Ports are also removed if they are detected to be invalid. */ - (void) removePort: (NSPort*)port forMode: (NSString*)mode { [self removeEvent: (void*)port type: ET_RPORT forMode: mode all: NO]; } @end