mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 00:11:04 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@15964 72102866-910b-0410-8b05-ffd578937521
2266 lines
53 KiB
Objective-C
2266 lines
53 KiB
Objective-C
/** Implementation of object for waiting on several input sources
|
||
NSRunLoop.m
|
||
|
||
Copyright (C) 1996-1999 Free Software Foundation, Inc.
|
||
|
||
Original by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
|
||
Created: March 1996
|
||
OPENSTEP version by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||
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 Library 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 Library General Public
|
||
License along with this library; if not, write to the Free
|
||
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
||
|
||
<title>NSRunLoop class reference</title>
|
||
$Date$ $Revision$
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <base/preface.h>
|
||
#include <Foundation/NSMapTable.h>
|
||
#include <Foundation/NSDate.h>
|
||
#include <Foundation/NSValue.h>
|
||
#include <Foundation/NSAutoreleasePool.h>
|
||
#include <Foundation/NSPort.h>
|
||
#include <Foundation/NSTimer.h>
|
||
#include <Foundation/NSNotificationQueue.h>
|
||
#include <Foundation/NSRunLoop.h>
|
||
#include <Foundation/NSThread.h>
|
||
#include <Foundation/NSDebug.h>
|
||
|
||
#ifdef HAVE_SYS_TYPES_H
|
||
#include <sys/types.h>
|
||
#endif
|
||
#ifdef HAVE_SYS_TIME_H
|
||
#include <sys/time.h>
|
||
#endif
|
||
#ifdef HAVE_POLL_H
|
||
#include <poll.h>
|
||
#endif
|
||
#ifdef HAVE_UNISTD_H
|
||
#include <unistd.h>
|
||
#endif
|
||
#include <time.h>
|
||
#include <limits.h>
|
||
#include <string.h> /* for memset() */
|
||
|
||
|
||
NSString * const NSDefaultRunLoopMode = @"NSDefaultRunLoopMode";
|
||
|
||
static NSDate *theFuture = nil;
|
||
|
||
extern BOOL GSCheckTasks();
|
||
|
||
#ifdef HAVE_POLL
|
||
typedef struct {
|
||
int limit;
|
||
short *index;
|
||
} pollextra;
|
||
#endif
|
||
|
||
|
||
/*
|
||
* The 'GSRunLoopWatcher' class was written to permit the (relatively)
|
||
* easy addition of new events to be watched for in the runloop.
|
||
*
|
||
* To add a new type of event, the 'RunLoopEventType' enumeration must be
|
||
* extended, and the methods must be modified to handle the new type.
|
||
*
|
||
* The internal variables if the GSRunLoopWatcher are used as follows -
|
||
*
|
||
* The '_date' variable contains a date after which the event is useless
|
||
* and the watcher can be removed from the runloop.
|
||
*
|
||
* If '_invalidated' is set, the watcher should be disabled and should
|
||
* be removed from the runloop when next encountered.
|
||
*
|
||
* The 'data' variable is used to identify the resource/event that the
|
||
* watcher is interested in.
|
||
*
|
||
* The 'receiver' is the object which should be told when the event
|
||
* occurs. This object is retained so that we know it will continue
|
||
* to exist and can handle a callback.
|
||
*
|
||
* The 'type' variable indentifies the type of event watched for.
|
||
* NSRunLoops [-acceptInputForMode: beforeDate: ] method MUST contain
|
||
* code to watch for events of each type.
|
||
*
|
||
* To set this variable, the method adding the GSRunLoopWatcher to the
|
||
* runloop must ask the 'receiver' (or its delegate) to supply a date
|
||
* using the '[-limitDateForMode: ]' message.
|
||
*
|
||
* NB. This class is private to NSRunLoop and must not be subclassed.
|
||
*/
|
||
|
||
static SEL eventSel; /* Initialized in [NSRunLoop +initialize] */
|
||
|
||
@interface GSRunLoopWatcher: NSObject
|
||
{
|
||
@public
|
||
NSDate *_date; /* First to match layout of NSTimer */
|
||
BOOL _invalidated; /* 2nd to match layout of NSTimer */
|
||
IMP handleEvent; /* New-style event handling */
|
||
void *data;
|
||
id receiver;
|
||
RunLoopEventType type;
|
||
unsigned count;
|
||
}
|
||
- (id) initWithType: (RunLoopEventType)type
|
||
receiver: (id)anObj
|
||
data: (void*)data;
|
||
@end
|
||
|
||
@implementation GSRunLoopWatcher
|
||
|
||
- (void) dealloc
|
||
{
|
||
RELEASE(_date);
|
||
[super dealloc];
|
||
}
|
||
|
||
- (id) initWithType: (RunLoopEventType)aType
|
||
receiver: (id)anObj
|
||
data: (void*)item
|
||
{
|
||
_invalidated = NO;
|
||
|
||
switch (aType)
|
||
{
|
||
case ET_EDESC: type = aType; break;
|
||
case ET_RDESC: type = aType; break;
|
||
case ET_WDESC: type = aType; break;
|
||
case ET_RPORT: type = aType; break;
|
||
default:
|
||
[NSException raise: NSInvalidArgumentException
|
||
format: @"NSRunLoop - unknown event type"];
|
||
}
|
||
receiver = anObj;
|
||
if ([receiver respondsToSelector: eventSel] == YES)
|
||
handleEvent = [receiver methodForSelector: eventSel];
|
||
else
|
||
[NSException raise: NSInvalidArgumentException
|
||
format: @"RunLoop listener has no event handling method"];
|
||
data = item;
|
||
return self;
|
||
}
|
||
|
||
@end
|
||
|
||
/*
|
||
* Two optimisation functions that depend on a hack that the layout of
|
||
* the NSTimer class is known to be the same as GSRunLoopWatcher for the
|
||
* first two elements.
|
||
*/
|
||
static inline NSDate* timerDate(NSTimer* timer)
|
||
{
|
||
return ((GSRunLoopWatcher*)timer)->_date;
|
||
}
|
||
|
||
static inline BOOL timerInvalidated(NSTimer* timer)
|
||
{
|
||
return ((GSRunLoopWatcher*)timer)->_invalidated;
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
* 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: (unsigned int)order;
|
||
@end
|
||
|
||
@implementation GSRunLoopPerformer
|
||
|
||
- (void) fire
|
||
{
|
||
[target performSelector: selector withObject: argument];
|
||
}
|
||
|
||
- (id) initWithSelector: (SEL)aSelector
|
||
target: (id)aTarget
|
||
argument: (id)anArgument
|
||
order: (unsigned int)theOrder
|
||
{
|
||
self = [super init];
|
||
if (self)
|
||
{
|
||
selector = aSelector;
|
||
target = aTarget;
|
||
argument = 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 <GCFinalization>
|
||
{
|
||
@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 gcFinalize];
|
||
TEST_RELEASE(timer);
|
||
RELEASE(target);
|
||
RELEASE(argument);
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void) fire
|
||
{
|
||
DESTROY(timer);
|
||
[target performSelector: selector withObject: argument];
|
||
[[[NSRunLoop currentRunLoop] _timedPerformers]
|
||
removeObjectIdenticalTo: self];
|
||
}
|
||
|
||
- (void) gcFinalize
|
||
{
|
||
[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.
|
||
*/
|
||
|
||
#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 <base/GSIArray.h>
|
||
|
||
static NSComparisonResult aSort(GSIArrayItem i0, GSIArrayItem i1)
|
||
{
|
||
return [((GSRunLoopWatcher *)(i0.obj))->_date
|
||
compare: ((GSRunLoopWatcher *)(i1.obj))->_date];
|
||
}
|
||
|
||
#if GS_WITH_GC == 0
|
||
static SEL wRelSel;
|
||
static SEL wRetSel;
|
||
static IMP wRelImp;
|
||
static IMP wRetImp;
|
||
|
||
static void
|
||
wRelease(NSMapTable* t, void* w)
|
||
{
|
||
(*wRelImp)((id)w, wRelSel);
|
||
}
|
||
|
||
static void
|
||
wRetain(NSMapTable* t, const void* w)
|
||
{
|
||
(*wRetImp)((id)w, wRetSel);
|
||
}
|
||
|
||
static const NSMapTableValueCallBacks WatcherMapValueCallBacks =
|
||
{
|
||
wRetain,
|
||
wRelease,
|
||
0
|
||
};
|
||
#else
|
||
#define WatcherMapValueCallBacks NSOwnedPointerMapValueCallBacks
|
||
#endif
|
||
|
||
|
||
|
||
/**
|
||
* The GSRunLoopCtxt stores context information to handle polling for
|
||
* events. This information is associated with a particular runloop
|
||
* mode, and persists throughout the life of the runloop instance.
|
||
*
|
||
* NB. This class is private to NSRunLoop and must not be subclassed.
|
||
*/
|
||
@interface GSRunLoopCtxt : NSObject
|
||
{
|
||
@public
|
||
void *extra; /** Copy of the RunLoop ivar. */
|
||
NSString *mode; /** The mode for this context. */
|
||
GSIArray performers; /** The actions to perform regularly. */
|
||
GSIArray timers; /** The timers set for the runloop mode */
|
||
GSIArray watchers; /** The inputs set for the runloop mode */
|
||
@private
|
||
NSMapTable *_efdMap;
|
||
NSMapTable *_rfdMap;
|
||
NSMapTable *_wfdMap;
|
||
int fairStart; // For trying to ensure fair handling.
|
||
BOOL completed; // To mark operation as completed.
|
||
#ifdef HAVE_POLL
|
||
unsigned int pollfds_capacity;
|
||
unsigned int pollfds_count;
|
||
struct pollfd *pollfds;
|
||
#endif
|
||
}
|
||
- (void) endEvent: (void*)data
|
||
type: (RunLoopEventType)type;
|
||
- (void) endPoll;
|
||
- (id) initWithMode: (NSString*)theMode extra: (void*)e;
|
||
- (BOOL) pollUntil: (int)milliseconds within: (NSArray*)contexts;
|
||
@end
|
||
|
||
@implementation GSRunLoopCtxt
|
||
- (void) dealloc
|
||
{
|
||
RELEASE(mode);
|
||
GSIArrayEmpty(performers);
|
||
NSZoneFree(performers->zone, (void*)performers);
|
||
GSIArrayEmpty(timers);
|
||
NSZoneFree(timers->zone, (void*)timers);
|
||
GSIArrayEmpty(watchers);
|
||
NSZoneFree(watchers->zone, (void*)watchers);
|
||
if (_efdMap != 0)
|
||
{
|
||
NSFreeMapTable(_efdMap);
|
||
}
|
||
if (_rfdMap != 0)
|
||
{
|
||
NSFreeMapTable(_rfdMap);
|
||
}
|
||
if (_wfdMap != 0)
|
||
{
|
||
NSFreeMapTable(_wfdMap);
|
||
}
|
||
#ifdef HAVE_POLL
|
||
if (pollfds != 0)
|
||
{
|
||
objc_free(pollfds);
|
||
}
|
||
#endif
|
||
[super dealloc];
|
||
}
|
||
|
||
/**
|
||
* Remove any callback for the specified event which is set for an
|
||
* uncompleted poll operation.<br />
|
||
* This is called by nested event loops on contexts in outer loops
|
||
* when they handle an event ... removing the event from the outer
|
||
* loop ensures that it won't get handled twice, once by the inner
|
||
* loop and once by the outer one.
|
||
*/
|
||
- (void) endEvent: (void*)data
|
||
type: (RunLoopEventType)type
|
||
{
|
||
if (completed == NO)
|
||
{
|
||
switch (type)
|
||
{
|
||
case ET_RDESC:
|
||
NSMapRemove(_rfdMap, data);
|
||
break;
|
||
case ET_WDESC:
|
||
NSMapRemove(_wfdMap, data);
|
||
break;
|
||
case ET_EDESC:
|
||
NSMapRemove(_efdMap, data);
|
||
break;
|
||
default:
|
||
NSLog(@"Ending an event of unkown type (%d)", type);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Mark this poll context as having completed, so that if we are
|
||
* executing a re-entrant poll, the enclosing poll operations
|
||
* know they can stop what they are doing because an inner
|
||
* operation has done the job.
|
||
*/
|
||
- (void) endPoll
|
||
{
|
||
completed = YES;
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"-init may not be called for GSRunLoopCtxt"];
|
||
return nil;
|
||
}
|
||
|
||
- (id) initWithMode: (NSString*)theMode extra: (void*)e
|
||
{
|
||
self = [super init];
|
||
if (self != nil)
|
||
{
|
||
NSZone *z = [self zone];
|
||
|
||
mode = [theMode copy];
|
||
extra = e;
|
||
performers = NSZoneMalloc(z, sizeof(GSIArray_t));
|
||
GSIArrayInitWithZoneAndCapacity(performers, z, 8);
|
||
timers = NSZoneMalloc(z, sizeof(GSIArray_t));
|
||
GSIArrayInitWithZoneAndCapacity(timers, z, 8);
|
||
watchers = NSZoneMalloc(z, sizeof(GSIArray_t));
|
||
GSIArrayInitWithZoneAndCapacity(watchers, z, 8);
|
||
|
||
_efdMap = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
WatcherMapValueCallBacks, 0);
|
||
_rfdMap = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
WatcherMapValueCallBacks, 0);
|
||
_wfdMap = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
WatcherMapValueCallBacks, 0);
|
||
}
|
||
return self;
|
||
}
|
||
|
||
#ifdef HAVE_POLL
|
||
|
||
static void setPollfd(int fd, int event, GSRunLoopCtxt *ctxt)
|
||
{
|
||
int index;
|
||
struct pollfd *pollfds = ctxt->pollfds;
|
||
pollextra *pe = (pollextra*)ctxt->extra;
|
||
|
||
if (fd >= pe->limit)
|
||
{
|
||
int oldfd_limit = pe->limit;
|
||
|
||
pe->limit = fd + 1;
|
||
if (pe->index == 0)
|
||
{
|
||
pe->index = objc_malloc(pe->limit * sizeof(*(pe->index)));
|
||
}
|
||
else
|
||
{
|
||
pe->index = objc_realloc(pe->index, pe->limit * sizeof(*(pe->index)));
|
||
}
|
||
do
|
||
{
|
||
pe->index[oldfd_limit++] = -1;
|
||
}
|
||
while (oldfd_limit < pe->limit);
|
||
}
|
||
index = pe->index[fd];
|
||
if (index == -1)
|
||
{
|
||
if (ctxt->pollfds_count >= ctxt->pollfds_capacity)
|
||
{
|
||
ctxt->pollfds_capacity += 8;
|
||
pollfds =
|
||
objc_realloc(pollfds, ctxt->pollfds_capacity * sizeof (*pollfds));
|
||
ctxt->pollfds = pollfds;
|
||
}
|
||
index = ctxt->pollfds_count++;
|
||
pe->index[fd] = index;
|
||
pollfds[index].fd = fd;
|
||
pollfds[index].events = 0;
|
||
pollfds[index].revents = 0;
|
||
}
|
||
pollfds[index].events |= event;
|
||
}
|
||
|
||
/**
|
||
* Perform a poll for the specified runloop context.
|
||
* If the method has been called re-entrantly, the contexts stack
|
||
* will list all the contexts with polls in progress
|
||
* and this method must tell those outer contexts not to handle events
|
||
* which are handled by this context.
|
||
*/
|
||
- (BOOL) pollUntil: (int)milliseconds within: (NSArray*)contexts
|
||
{
|
||
int poll_return;
|
||
int fdEnd; /* Number of descriptors being monitored. */
|
||
int fdIndex;
|
||
int fdFinish;
|
||
unsigned int i;
|
||
|
||
i = GSIArrayCount(watchers);
|
||
|
||
/*
|
||
* Get ready to listen to file descriptors.
|
||
* The maps will not have been emptied by any previous call.
|
||
*/
|
||
NSResetMapTable(_efdMap);
|
||
NSResetMapTable(_rfdMap);
|
||
NSResetMapTable(_wfdMap);
|
||
|
||
/*
|
||
* Do the pre-listening set-up for the file descriptors of this mode.
|
||
*/
|
||
if (pollfds_capacity < i + 1)
|
||
{
|
||
pollfds_capacity = i + 1;
|
||
if (pollfds == 0)
|
||
{
|
||
pollfds = objc_malloc(pollfds_capacity * sizeof(*pollfds));
|
||
}
|
||
else
|
||
{
|
||
pollfds = objc_realloc(pollfds, pollfds_capacity * sizeof(*pollfds));
|
||
}
|
||
}
|
||
pollfds_count = 0;
|
||
((pollextra*)extra)->limit = 0;
|
||
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopWatcher *info;
|
||
int fd;
|
||
|
||
info = GSIArrayItemAtIndex(watchers, i).obj;
|
||
if (info->_invalidated == YES)
|
||
{
|
||
GSIArrayRemoveItemAtIndex(watchers, i);
|
||
continue;
|
||
}
|
||
|
||
switch (info->type)
|
||
{
|
||
case ET_EDESC:
|
||
fd = (int)info->data;
|
||
setPollfd(fd, POLLPRI, self);
|
||
NSMapInsert(_efdMap, (void*)fd, info);
|
||
break;
|
||
|
||
case ET_RDESC:
|
||
fd = (int)info->data;
|
||
setPollfd(fd, POLLIN, self);
|
||
NSMapInsert(_rfdMap, (void*)fd, info);
|
||
break;
|
||
|
||
case ET_WDESC:
|
||
fd = (int)info->data;
|
||
setPollfd(fd, POLLOUT, self);
|
||
NSMapInsert(_wfdMap, (void*)fd, info);
|
||
break;
|
||
|
||
case ET_RPORT:
|
||
if ([info->receiver isValid] == NO)
|
||
{
|
||
/*
|
||
* We must remove an invalidated port.
|
||
*/
|
||
info->_invalidated = YES;
|
||
GSIArrayRemoveItemAtIndex(watchers, i);
|
||
}
|
||
else
|
||
{
|
||
id port = info->receiver;
|
||
int port_fd_count = 128; // FIXME
|
||
int port_fd_array[port_fd_count];
|
||
|
||
if ([port respondsToSelector:
|
||
@selector(getFds:count:)])
|
||
{
|
||
[port getFds: port_fd_array
|
||
count: &port_fd_count];
|
||
}
|
||
NSDebugMLLog(@"NSRunLoop",
|
||
@"listening to %d port handles\n", port_fd_count);
|
||
while (port_fd_count--)
|
||
{
|
||
fd = port_fd_array[port_fd_count];
|
||
setPollfd(fd, POLLIN, self);
|
||
NSMapInsert(_rfdMap,
|
||
(void*)port_fd_array[port_fd_count], info);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* If there are notifications in the 'idle' queue, we try an
|
||
* instantaneous select so that, if there is no input pending,
|
||
* we can service the queue. Similarly, if a task has completed,
|
||
* we need to deliver its notifications.
|
||
*/
|
||
if (GSCheckTasks() || GSNotifyMore())
|
||
{
|
||
milliseconds = 0;
|
||
}
|
||
|
||
#if 0
|
||
{
|
||
unsigned int i;
|
||
fprintf(stderr, "poll %d %d:", milliseconds, pollfds_count);
|
||
for (i = 0; i < pollfds_count; i++)
|
||
fprintf(stderr, " %d,%x", pollfds[i].fd, pollfds[i].events);
|
||
fprintf(stderr, "\n");
|
||
}
|
||
#endif
|
||
poll_return = poll (pollfds, pollfds_count, milliseconds);
|
||
#if 0
|
||
{
|
||
unsigned int i;
|
||
fprintf(stderr, "ret %d %d:", poll_return, pollfds_count);
|
||
for (i = 0; i < pollfds_count; i++)
|
||
fprintf(stderr, " %d,%x", pollfds[i].fd, pollfds[i].revents);
|
||
fprintf(stderr, "\n");
|
||
}
|
||
#endif
|
||
|
||
NSDebugMLLog(@"NSRunLoop", @"poll returned %d\n", poll_return);
|
||
|
||
if (poll_return < 0)
|
||
{
|
||
if (errno == EINTR)
|
||
{
|
||
GSCheckTasks();
|
||
poll_return = 0;
|
||
}
|
||
#ifdef __MINGW__
|
||
else if (errno == 0)
|
||
{
|
||
/* MinGW often returns an errno == 0. Not sure why */
|
||
poll_return = 0;
|
||
}
|
||
#endif
|
||
else
|
||
{
|
||
/* Some exceptional condition happened. */
|
||
/* xxx We can do something with exception_fds, instead of
|
||
aborting here. */
|
||
NSLog (@"poll() error in -acceptInputForMode:beforeDate: '%s'",
|
||
GSLastErrorStr(errno));
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
if (poll_return == 0)
|
||
{
|
||
completed = YES;
|
||
return NO;
|
||
}
|
||
|
||
/*
|
||
* Look at all the file descriptors select() says are ready for action;
|
||
* notify the corresponding object for each of the ready fd's.
|
||
* NB. It is possible for a watcher to be missing from the map - if
|
||
* the event handler of a previous watcher has 'run' the loop again
|
||
* before returning.
|
||
* NB. Each time this loop is entered, the starting position (fairStart)
|
||
* is incremented - this is to ensure a fair distribion over all
|
||
* inputs where multiple inputs are in use. Note - fairStart can be
|
||
* modified while we are in the loop (by recursive calls).
|
||
*/
|
||
fdEnd = pollfds_count;
|
||
if (++fairStart >= fdEnd)
|
||
{
|
||
fairStart = 0;
|
||
fdIndex = 0;
|
||
fdFinish = 0;
|
||
}
|
||
else
|
||
{
|
||
fdIndex = fairStart;
|
||
fdFinish = fairStart;
|
||
}
|
||
completed = NO;
|
||
while (completed == NO)
|
||
{
|
||
if (pollfds[fdIndex].revents != 0)
|
||
{
|
||
int fd = pollfds[fdIndex].fd;
|
||
GSRunLoopWatcher *watcher;
|
||
BOOL found = NO;
|
||
|
||
/*
|
||
* The poll() call supports various error conditions - all
|
||
* errors should be handled by any available handler.
|
||
* The ET_EDSEC handler is the primary handler for exceptions
|
||
* though it is more generally used to deal with out-of-band data.
|
||
*/
|
||
if (pollfds[fdIndex].revents & (POLLPRI|POLLERR|POLLHUP|POLLNVAL))
|
||
{
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_efdMap, (void*)fd);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fd type: ET_EDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its
|
||
* receivers event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fd, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break; // A nested poll has done the job.
|
||
}
|
||
found = YES;
|
||
}
|
||
if (pollfds[fdIndex].revents & (POLLOUT|POLLERR|POLLHUP|POLLNVAL))
|
||
{
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_wfdMap, (void*)fd);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fd type: ET_WDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its
|
||
* receivers event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fd, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break; // A nested poll has done the job.
|
||
}
|
||
found = YES;
|
||
}
|
||
if (pollfds[fdIndex].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL))
|
||
{
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_rfdMap, (void*)fd);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fd type: ET_RDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its
|
||
* receivers event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fd, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break; // A nested poll has done the job.
|
||
}
|
||
found = YES;
|
||
}
|
||
if (found == YES && --poll_return == 0)
|
||
{
|
||
completed = YES;
|
||
}
|
||
}
|
||
if (++fdIndex >= fdEnd)
|
||
{
|
||
fdIndex = 0;
|
||
}
|
||
if (fdIndex == fdFinish)
|
||
{
|
||
completed = YES;
|
||
}
|
||
}
|
||
completed = YES;
|
||
return YES;
|
||
}
|
||
|
||
#else
|
||
|
||
- (BOOL) pollUntil: (int)milliseconds within: (NSArray*)contexts
|
||
{
|
||
struct timeval timeout;
|
||
void *select_timeout;
|
||
int select_return;
|
||
int fdIndex;
|
||
int fdFinish;
|
||
fd_set read_fds; // Mask for read-ready fds.
|
||
fd_set exception_fds; // Mask for exception fds.
|
||
fd_set write_fds; // Mask for write-ready fds.
|
||
int num_inputs = 0;
|
||
int fdEnd = -1;
|
||
unsigned i;
|
||
|
||
i = GSIArrayCount(watchers);
|
||
|
||
/* Find out how much time we should wait, and set SELECT_TIMEOUT. */
|
||
if (milliseconds == 0)
|
||
{
|
||
/* Don't wait at all. */
|
||
timeout.tv_sec = 0;
|
||
timeout.tv_usec = 0;
|
||
select_timeout = &timeout;
|
||
}
|
||
else if (milliseconds > 0)
|
||
{
|
||
timeout.tv_sec = milliseconds/1000;
|
||
timeout.tv_usec = (milliseconds - 1000 * timeout.tv_sec) * 1000;
|
||
select_timeout = &timeout;
|
||
}
|
||
else
|
||
{
|
||
timeout.tv_sec = -1;
|
||
timeout.tv_usec = -1;
|
||
select_timeout = NULL;
|
||
}
|
||
|
||
/*
|
||
* Get ready to listen to file descriptors.
|
||
* Initialize the set of FDS we'll pass to select(), and make sure we
|
||
* have empty maps for keeping track of which watcher is associated
|
||
* with which file descriptor.
|
||
* The maps may not have been emptied if a previous call to this
|
||
* method was terminated by an exception.
|
||
*/
|
||
memset(&exception_fds, '\0', sizeof(exception_fds));
|
||
memset(&read_fds, '\0', sizeof(read_fds));
|
||
memset(&write_fds, '\0', sizeof(write_fds));
|
||
NSResetMapTable(_efdMap);
|
||
NSResetMapTable(_rfdMap);
|
||
NSResetMapTable(_wfdMap);
|
||
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopWatcher *info;
|
||
int fd;
|
||
|
||
info = GSIArrayItemAtIndex(watchers, i).obj;
|
||
if (info->_invalidated == YES)
|
||
{
|
||
GSIArrayRemoveItemAtIndex(watchers, i);
|
||
continue;
|
||
}
|
||
switch (info->type)
|
||
{
|
||
case ET_EDESC:
|
||
fd = (int)info->data;
|
||
if (fd > fdEnd)
|
||
fdEnd = fd;
|
||
FD_SET (fd, &exception_fds);
|
||
NSMapInsert(_efdMap, (void*)fd, info);
|
||
num_inputs++;
|
||
break;
|
||
|
||
case ET_RDESC:
|
||
fd = (int)info->data;
|
||
if (fd > fdEnd)
|
||
fdEnd = fd;
|
||
FD_SET (fd, &read_fds);
|
||
NSMapInsert(_rfdMap, (void*)fd, info);
|
||
num_inputs++;
|
||
break;
|
||
|
||
case ET_WDESC:
|
||
fd = (int)info->data;
|
||
if (fd > fdEnd)
|
||
fdEnd = fd;
|
||
FD_SET (fd, &write_fds);
|
||
NSMapInsert(_wfdMap, (void*)fd, info);
|
||
num_inputs++;
|
||
break;
|
||
|
||
case ET_RPORT:
|
||
if ([info->receiver isValid] == NO)
|
||
{
|
||
/*
|
||
* We must remove an invalidated port.
|
||
*/
|
||
info->_invalidated = YES;
|
||
GSIArrayRemoveItemAtIndex(watchers, i);
|
||
}
|
||
else
|
||
{
|
||
id port = info->receiver;
|
||
int port_fd_count = 128; // xxx #define this constant
|
||
int port_fd_array[port_fd_count];
|
||
|
||
if ([port respondsToSelector:
|
||
@selector(getFds:count:)])
|
||
{
|
||
[port getFds: port_fd_array
|
||
count: &port_fd_count];
|
||
}
|
||
NSDebugMLLog(@"NSRunLoop", @"listening to %d port sockets",
|
||
port_fd_count);
|
||
while (port_fd_count--)
|
||
{
|
||
fd = port_fd_array[port_fd_count];
|
||
FD_SET (port_fd_array[port_fd_count], &read_fds);
|
||
if (fd > fdEnd)
|
||
fdEnd = fd;
|
||
NSMapInsert(_rfdMap,
|
||
(void*)port_fd_array[port_fd_count], info);
|
||
num_inputs++;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
fdEnd++;
|
||
|
||
/*
|
||
* If there are notifications in the 'idle' queue, we try an
|
||
* instantaneous select so that, if there is no input pending,
|
||
* we can service the queue. Similarly, if a task has completed,
|
||
* we need to deliver its notifications.
|
||
*/
|
||
if (GSCheckTasks() || GSNotifyMore())
|
||
{
|
||
timeout.tv_sec = 0;
|
||
timeout.tv_usec = 0;
|
||
select_timeout = &timeout;
|
||
}
|
||
|
||
// NSDebugMLLog(@"NSRunLoop", @"select timeout %d,%d", timeout.tv_sec, timeout.tv_usec);
|
||
|
||
select_return = select (fdEnd, &read_fds, &write_fds,
|
||
&exception_fds, select_timeout);
|
||
|
||
NSDebugMLLog(@"NSRunLoop", @"select returned %d", select_return);
|
||
|
||
if (select_return < 0)
|
||
{
|
||
if (errno == EINTR)
|
||
{
|
||
GSCheckTasks();
|
||
select_return = 0;
|
||
}
|
||
#ifdef __MINGW__
|
||
else if (errno == 0)
|
||
{
|
||
/* MinGW often returns an errno == 0. Not sure why */
|
||
select_return = 0;
|
||
}
|
||
#endif
|
||
else
|
||
{
|
||
/* Some exceptional condition happened. */
|
||
/* xxx We can do something with exception_fds, instead of
|
||
aborting here. */
|
||
NSLog (@"select() error in -acceptInputForMode:beforeDate: '%s'",
|
||
GSLastErrorStr(errno));
|
||
abort ();
|
||
}
|
||
}
|
||
if (select_return == 0)
|
||
{
|
||
completed = YES;
|
||
return NO;
|
||
}
|
||
|
||
/*
|
||
* Look at all the file descriptors select() says are ready for action;
|
||
* notify the corresponding object for each of the ready fd's.
|
||
* NB. Each time this roop is entered, the starting position (fairStart)
|
||
* is incremented - this is to ensure a fair distribtion over all
|
||
* inputs where multiple inputs are in use. Note - fairStart can be
|
||
* modified while we are in the loop (by recursive calls).
|
||
*/
|
||
if (++fairStart >= fdEnd)
|
||
{
|
||
fairStart = 0;
|
||
fdIndex = 0;
|
||
fdFinish = 0;
|
||
}
|
||
else
|
||
{
|
||
fdIndex = fairStart;
|
||
fdFinish = fairStart;
|
||
}
|
||
completed = NO;
|
||
while (completed == NO)
|
||
{
|
||
BOOL found = NO;
|
||
|
||
if (FD_ISSET (fdIndex, &exception_fds))
|
||
{
|
||
GSRunLoopWatcher *watcher;
|
||
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_efdMap, (void*)fdIndex);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fdIndex type: ET_EDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its receivers
|
||
* event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fdIndex, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break;
|
||
}
|
||
found = YES;
|
||
}
|
||
if (FD_ISSET (fdIndex, &write_fds))
|
||
{
|
||
GSRunLoopWatcher *watcher;
|
||
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_wfdMap, (void*)fdIndex);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fdIndex type: ET_WDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its receivers
|
||
* event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fdIndex, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break;
|
||
}
|
||
found = YES;
|
||
}
|
||
if (FD_ISSET (fdIndex, &read_fds))
|
||
{
|
||
GSRunLoopWatcher *watcher;
|
||
|
||
watcher = (GSRunLoopWatcher*)NSMapGet(_rfdMap, (void*)fdIndex);
|
||
if (watcher != nil && watcher->_invalidated == NO)
|
||
{
|
||
i = [contexts count];
|
||
while (i-- > 0)
|
||
{
|
||
GSRunLoopCtxt *c = [contexts objectAtIndex: i];
|
||
|
||
if (c != self) [c endEvent: (void*)fdIndex type: ET_RDESC];
|
||
}
|
||
/*
|
||
* The watcher is still valid - so call its receivers
|
||
* event handling method.
|
||
*/
|
||
(*watcher->handleEvent)(watcher->receiver,
|
||
eventSel, watcher->data, watcher->type,
|
||
(void*)(gsaddr)fdIndex, mode);
|
||
}
|
||
GSNotifyASAP();
|
||
if (completed == YES)
|
||
{
|
||
break;
|
||
}
|
||
found = YES;
|
||
}
|
||
if (found == YES && --select_return == 0)
|
||
{
|
||
completed = YES;
|
||
}
|
||
if (++fdIndex >= fdEnd)
|
||
{
|
||
fdIndex = 0;
|
||
}
|
||
if (fdIndex == fdFinish)
|
||
{
|
||
completed = YES;
|
||
}
|
||
}
|
||
completed = YES;
|
||
return YES;
|
||
}
|
||
|
||
#endif
|
||
@end
|
||
|
||
|
||
|
||
@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.<br />
|
||
* 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_eq(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);
|
||
[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;
|
||
- (void) _checkPerformers: (GSRunLoopCtxt*)context;
|
||
- (GSRunLoopWatcher*) _getWatcher: (void*)data
|
||
type: (RunLoopEventType)type
|
||
forMode: (NSString*)mode;
|
||
- (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;
|
||
id obj;
|
||
|
||
context = NSMapGet(_contextMap, mode);
|
||
if (context == nil)
|
||
{
|
||
context = [[GSRunLoopCtxt alloc] initWithMode: mode extra: _extra];
|
||
NSMapInsert(_contextMap, context->mode, context);
|
||
RELEASE(context);
|
||
}
|
||
watchers = context->watchers;
|
||
|
||
/*
|
||
* If the receiver or its delegate (if any) respond to
|
||
* 'limitDateForMode: ' then we ask them for the limit date for
|
||
* this watcher.
|
||
*/
|
||
obj = item->receiver;
|
||
if ([obj respondsToSelector: @selector(limitDateForMode:)])
|
||
{
|
||
NSDate *d = [obj limitDateForMode: mode];
|
||
|
||
item->_date = RETAIN(d);
|
||
}
|
||
else if ([obj respondsToSelector: @selector(delegate)])
|
||
{
|
||
obj = [obj delegate];
|
||
if (obj != nil && [obj respondsToSelector: @selector(limitDateForMode:)])
|
||
{
|
||
NSDate *d = [obj limitDateForMode: mode];
|
||
|
||
item->_date = RETAIN(d);
|
||
}
|
||
else
|
||
{
|
||
item->_date = RETAIN(theFuture);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
item->_date = RETAIN(theFuture);
|
||
}
|
||
GSIArrayInsertSorted(watchers, (GSIArrayItem)item, aSort);
|
||
}
|
||
|
||
- (void) _checkPerformers: (GSRunLoopCtxt*)context
|
||
{
|
||
if (context != nil)
|
||
{
|
||
GSIArray performers = context->performers;
|
||
unsigned count = GSIArrayCount(performers);
|
||
|
||
if (count > 0)
|
||
{
|
||
GSRunLoopPerformer *array[count];
|
||
NSMapEnumerator enumerator;
|
||
GSRunLoopCtxt *context;
|
||
void *mode;
|
||
unsigned i;
|
||
|
||
/*
|
||
* Copy the array - because we have to cancel the requests
|
||
* before firing.
|
||
*/
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
array[i] = RETAIN(GSIArrayItemAtIndex(performers, i).obj);
|
||
}
|
||
|
||
/*
|
||
* Remove the requests that we are about to fire from all modes.
|
||
*/
|
||
enumerator = NSEnumerateMapTable(_contextMap);
|
||
while (NSNextMapEnumeratorPair(&enumerator, &mode, (void**)&context))
|
||
{
|
||
if (context != nil)
|
||
{
|
||
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.
|
||
*/
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
[array[i] fire];
|
||
RELEASE(array[i]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
|
||
/**
|
||
* 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)
|
||
|
||
/**
|
||
* Adds 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) addEvent: (void*)data
|
||
type: (RunLoopEventType)type
|
||
watcher: (id<RunLoopEvents>)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 && 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. */
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
* The additional removeAll flag may be used to remove all instances of
|
||
* the watcher rather than just a single one.
|
||
*/
|
||
- (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
|
||
|
||
|
||
|
||
@implementation NSRunLoop
|
||
|
||
+ (void) initialize
|
||
{
|
||
if (self == [NSRunLoop class])
|
||
{
|
||
[self currentRunLoop];
|
||
theFuture = RETAIN([NSDate distantFuture]);
|
||
eventSel = @selector(receivedEvent:type:extra:forMode:);
|
||
#if GS_WITH_GC == 0
|
||
wRelSel = @selector(release);
|
||
wRetSel = @selector(retain);
|
||
wRelImp = [[GSRunLoopWatcher class] instanceMethodForSelector: wRelSel];
|
||
wRetImp = [[GSRunLoopWatcher class] instanceMethodForSelector: wRetSel];
|
||
#endif
|
||
}
|
||
}
|
||
|
||
+ (NSRunLoop*) currentRunLoop
|
||
{
|
||
extern NSRunLoop *GSRunLoopForThread();
|
||
|
||
return GSRunLoopForThread(nil);
|
||
}
|
||
|
||
/* This is the designated initializer. */
|
||
- (id) init
|
||
{
|
||
self = [super init];
|
||
if (self != nil)
|
||
{
|
||
_contextStack = [NSMutableArray new];
|
||
_contextMap = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
_timedPerformers = [[NSMutableArray alloc] initWithCapacity: 8];
|
||
#ifdef HAVE_POLL
|
||
_extra = objc_malloc(sizeof(pollextra));
|
||
memset(_extra, '\0', sizeof(pollextra));
|
||
#endif
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
[self gcFinalize];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void) gcFinalize
|
||
{
|
||
#ifdef HAVE_POLL
|
||
if (_extra != 0)
|
||
{
|
||
pollextra *e = (pollextra*)_extra;
|
||
|
||
if (e->index != 0)
|
||
objc_free(e->index);
|
||
objc_free(e);
|
||
}
|
||
#endif
|
||
RELEASE(_contextStack);
|
||
if (_contextMap != 0)
|
||
{
|
||
NSFreeMapTable(_contextMap);
|
||
}
|
||
RELEASE(_timedPerformers);
|
||
}
|
||
|
||
/**
|
||
* 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.<br />
|
||
* Timers are removed automatically when they are invalid.<br />
|
||
*/
|
||
- (void) addTimer: (NSTimer*)timer
|
||
forMode: (NSString*)mode
|
||
{
|
||
GSRunLoopCtxt *context;
|
||
GSIArray timers;
|
||
|
||
context = NSMapGet(_contextMap, mode);
|
||
if (context == nil)
|
||
{
|
||
context = [[GSRunLoopCtxt alloc] initWithMode: mode extra: _extra];
|
||
NSMapInsert(_contextMap, context->mode, context);
|
||
RELEASE(context);
|
||
}
|
||
timers = context->timers;
|
||
GSIArrayInsertSorted(timers, (GSIArrayItem)timer, aSort);
|
||
}
|
||
|
||
|
||
/**
|
||
* Fire appropriate timers and determine the earliest time that anything
|
||
* watched for becomes useless.
|
||
*/
|
||
- (NSDate*) limitDateForMode: (NSString*)mode
|
||
{
|
||
GSRunLoopCtxt *context = NSMapGet(_contextMap, mode);
|
||
NSDate *when = nil;
|
||
|
||
if (context != nil)
|
||
{
|
||
NSTimer *min_timer = nil;
|
||
GSRunLoopWatcher *min_watcher = nil;
|
||
NSString *savedMode = _currentMode;
|
||
CREATE_AUTORELEASE_POOL(arp);
|
||
|
||
_currentMode = mode;
|
||
NS_DURING
|
||
{
|
||
GSIArray timers = context->timers;
|
||
GSIArray watchers = context->watchers;
|
||
|
||
while (GSIArrayCount(timers) != 0)
|
||
{
|
||
min_timer = GSIArrayItemAtIndex(timers, 0).obj;
|
||
if (timerInvalidated(min_timer) == YES)
|
||
{
|
||
GSIArrayRemoveItemAtIndex(timers, 0);
|
||
min_timer = nil;
|
||
continue;
|
||
}
|
||
|
||
if ([timerDate(min_timer) timeIntervalSinceNow] > 0)
|
||
{
|
||
break;
|
||
}
|
||
|
||
GSIArrayRemoveItemAtIndexNoRelease(timers, 0);
|
||
/* Firing will also increment its fireDate, if it is repeating. */
|
||
[min_timer fire];
|
||
if (timerInvalidated(min_timer) == NO)
|
||
{
|
||
GSIArrayInsertSortedNoRetain(timers,
|
||
(GSIArrayItem)min_timer, aSort);
|
||
}
|
||
else
|
||
{
|
||
RELEASE(min_timer);
|
||
}
|
||
min_timer = nil;
|
||
GSNotifyASAP(); /* Post notifications. */
|
||
}
|
||
|
||
/* Is this right? At the moment we invalidate and discard watchers
|
||
whose limit-dates have passed. */
|
||
while (GSIArrayCount(watchers) != 0)
|
||
{
|
||
min_watcher = GSIArrayItemAtIndex(watchers, 0).obj;
|
||
|
||
if (min_watcher->_invalidated == YES)
|
||
{
|
||
GSIArrayRemoveItemAtIndex(watchers, 0);
|
||
min_watcher = nil;
|
||
continue;
|
||
}
|
||
|
||
if ([min_watcher->_date timeIntervalSinceNow] > 0)
|
||
{
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
id obj;
|
||
NSDate *nxt = nil;
|
||
|
||
/*
|
||
* If the receiver or its delegate wants to know about
|
||
* timeouts - inform it and give it a chance to set a
|
||
* revised limit date.
|
||
*/
|
||
GSIArrayRemoveItemAtIndexNoRelease(watchers, 0);
|
||
obj = min_watcher->receiver;
|
||
if ([obj respondsToSelector:
|
||
@selector(timedOutEvent:type:forMode:)])
|
||
{
|
||
nxt = [obj timedOutEvent: min_watcher->data
|
||
type: min_watcher->type
|
||
forMode: mode];
|
||
}
|
||
else if ([obj respondsToSelector: @selector(delegate)])
|
||
{
|
||
obj = [obj delegate];
|
||
if (obj != nil && [obj respondsToSelector:
|
||
@selector(timedOutEvent:type:forMode:)])
|
||
{
|
||
nxt = [obj timedOutEvent: min_watcher->data
|
||
type: min_watcher->type
|
||
forMode: mode];
|
||
}
|
||
}
|
||
if (nxt && [nxt timeIntervalSinceNow] > 0.0)
|
||
{
|
||
/*
|
||
* If the watcher has been given a revised limit date -
|
||
* re-insert it into the queue in the correct place.
|
||
*/
|
||
ASSIGN(min_watcher->_date, nxt);
|
||
GSIArrayInsertSortedNoRetain(watchers,
|
||
(GSIArrayItem)min_watcher, aSort);
|
||
}
|
||
else
|
||
{
|
||
/*
|
||
* If the watcher is now useless - invalidate and
|
||
* release it.
|
||
*/
|
||
min_watcher->_invalidated = YES;
|
||
RELEASE(min_watcher);
|
||
}
|
||
min_watcher = nil;
|
||
}
|
||
}
|
||
_currentMode = savedMode;
|
||
}
|
||
NS_HANDLER
|
||
{
|
||
_currentMode = savedMode;
|
||
[localException raise];
|
||
}
|
||
NS_ENDHANDLER
|
||
|
||
RELEASE(arp);
|
||
|
||
/*
|
||
* If there are timers - set limit date to the earliest of them.
|
||
* If there are watchers, set the limit date to that of the earliest
|
||
* watcher (or leave it as the date of the earliest timer if that is
|
||
* before the watchers limit).
|
||
*/
|
||
if (min_timer != nil)
|
||
{
|
||
when = timerDate(min_timer);
|
||
if (min_watcher != nil
|
||
&& [min_watcher->_date compare: when] == NSOrderedAscending)
|
||
{
|
||
when = min_watcher->_date;
|
||
}
|
||
}
|
||
else if (min_watcher != nil)
|
||
{
|
||
when = min_watcher->_date;
|
||
}
|
||
else
|
||
{
|
||
return nil; /* Nothing waiting to be done. */
|
||
}
|
||
|
||
NSDebugMLLog(@"NSRunLoop", @"limit date %f",
|
||
[when timeIntervalSinceReferenceDate]);
|
||
}
|
||
return when;
|
||
}
|
||
|
||
/**
|
||
* Listen to input sources.<br />
|
||
* 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).<br />
|
||
* If the supplied mode is nil, uses NSDefaultRunLoopMode.
|
||
*/
|
||
- (void) acceptInputForMode: (NSString*)mode
|
||
beforeDate: (NSDate*)limit_date
|
||
{
|
||
GSRunLoopCtxt *context;
|
||
NSTimeInterval ti;
|
||
int timeout_ms;
|
||
NSString *savedMode = _currentMode;
|
||
CREATE_AUTORELEASE_POOL(arp);
|
||
|
||
NSAssert(mode, NSInvalidArgumentException);
|
||
if (mode == nil)
|
||
{
|
||
mode = NSDefaultRunLoopMode;
|
||
}
|
||
_currentMode = mode;
|
||
context = NSMapGet(_contextMap, mode);
|
||
|
||
[self _checkPerformers: context];
|
||
|
||
NS_DURING
|
||
{
|
||
GSIArray watchers;
|
||
unsigned i;
|
||
|
||
if (context == nil || (watchers = context->watchers) == 0
|
||
|| (i = GSIArrayCount(watchers)) == 0)
|
||
{
|
||
NSDebugMLLog(@"NSRunLoop", @"no inputs in mode %@", mode);
|
||
GSNotifyASAP();
|
||
GSNotifyIdle();
|
||
ti = [limit_date timeIntervalSinceNow];
|
||
/*
|
||
* Pause for as long as possible (up to the limit date)
|
||
*/
|
||
if (ti > 0.0)
|
||
{
|
||
#if defined(HAVE_USLEEP)
|
||
if (ti >= INT_MAX / 1000000)
|
||
{
|
||
ti = INT_MAX;
|
||
}
|
||
else
|
||
{
|
||
ti *= 1000000;
|
||
}
|
||
usleep (ti);
|
||
#elif defined(__MINGW__)
|
||
if (ti >= INT_MAX / 1000)
|
||
{
|
||
ti = INT_MAX;
|
||
}
|
||
else
|
||
{
|
||
ti *= 1000;
|
||
}
|
||
Sleep (ti);
|
||
#else
|
||
sleep (ti);
|
||
#endif
|
||
}
|
||
GSCheckTasks();
|
||
if (context != nil)
|
||
{
|
||
[self _checkPerformers: context];
|
||
}
|
||
GSNotifyASAP();
|
||
_currentMode = savedMode;
|
||
RELEASE(arp);
|
||
NS_VOIDRETURN;
|
||
}
|
||
|
||
/* Find out how much time we should wait, and set SELECT_TIMEOUT. */
|
||
if (!limit_date)
|
||
{
|
||
/* Don't wait at all. */
|
||
timeout_ms = 0;
|
||
}
|
||
else if ((ti = [limit_date timeIntervalSinceNow]) > 0.0)
|
||
{
|
||
/* Wait until the LIMIT_DATE. */
|
||
NSDebugMLLog(@"NSRunLoop", @"accept I/P before %f (sec from now %f)",
|
||
[limit_date timeIntervalSinceReferenceDate], ti);
|
||
if (ti >= INT_MAX / 1000)
|
||
{
|
||
timeout_ms = INT_MAX; // Far future.
|
||
}
|
||
else
|
||
{
|
||
timeout_ms = ti * 1000;
|
||
}
|
||
}
|
||
else if (ti <= 0.0)
|
||
{
|
||
/* The LIMIT_DATE has already past; return immediately without
|
||
polling any inputs. */
|
||
GSCheckTasks();
|
||
[self _checkPerformers: context];
|
||
GSNotifyASAP();
|
||
NSDebugMLLog(@"NSRunLoop", @"limit date past, returning");
|
||
_currentMode = savedMode;
|
||
RELEASE(arp);
|
||
NS_VOIDRETURN;
|
||
}
|
||
else
|
||
{
|
||
/* Wait forever. */
|
||
NSDebugMLLog(@"NSRunLoop", @"accept input waiting forever");
|
||
timeout_ms = -1;
|
||
}
|
||
|
||
if ([_contextStack indexOfObjectIdenticalTo: context] == NSNotFound)
|
||
{
|
||
[_contextStack addObject: context];
|
||
}
|
||
if ([context pollUntil: timeout_ms within: _contextStack] == NO)
|
||
{
|
||
GSNotifyIdle();
|
||
}
|
||
[self _checkPerformers: context];
|
||
GSNotifyASAP();
|
||
_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];
|
||
}
|
||
NS_HANDLER
|
||
{
|
||
_currentMode = savedMode;
|
||
[context endPoll];
|
||
[_contextStack removeObjectIdenticalTo: context];
|
||
[localException raise];
|
||
}
|
||
NS_ENDHANDLER
|
||
RELEASE(arp);
|
||
}
|
||
|
||
/**
|
||
* Calls -acceptInputForMode:beforeDate: to run the loop once.<br />
|
||
* The specified date may be nil ... in which case the loop runs
|
||
* until the first input or timeout.<br />
|
||
* If the limit dates for all of mode's input sources have passed,
|
||
* returns NO without running the loop, otherwise returns YES.
|
||
*/
|
||
- (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date
|
||
{
|
||
id d;
|
||
|
||
NSAssert(mode != nil, NSInvalidArgumentException);
|
||
/* If date has already passed, simply return. */
|
||
if (date != nil && [date timeIntervalSinceNow] < 0)
|
||
{
|
||
NSDebugMLLog(@"NSRunLoop", @"run mode with date already past");
|
||
/*
|
||
* Notify if any tasks have completed.
|
||
*/
|
||
if (GSCheckTasks() == YES)
|
||
{
|
||
GSNotifyASAP();
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
/* Find out how long we can wait before first limit date. */
|
||
d = [self limitDateForMode: mode];
|
||
if (d == nil)
|
||
{
|
||
NSDebugMLLog(@"NSRunLoop", @"run mode with nothing to do");
|
||
/*
|
||
* Notify if any tasks have completed.
|
||
*/
|
||
if (GSCheckTasks() == YES)
|
||
{
|
||
GSNotifyASAP();
|
||
}
|
||
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];
|
||
}
|
||
IF_NO_GC(RETAIN(d));
|
||
|
||
/* Wait, listening to our input sources. */
|
||
[self acceptInputForMode: mode beforeDate: d];
|
||
|
||
RELEASE(d);
|
||
|
||
return YES;
|
||
}
|
||
|
||
- (void) run
|
||
{
|
||
[self runUntilDate: theFuture];
|
||
}
|
||
|
||
- (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
|
||
|
||
|
||
|
||
@implementation NSRunLoop (OPENSTEP)
|
||
|
||
- (void) addPort: (NSPort*)port
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self addEvent: (void*)port
|
||
type: ET_RPORT
|
||
watcher: (id<RunLoopEvents>)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.<br />
|
||
* 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_eq(p->selector, aSelector)
|
||
&& (p->argument == argument || [p->argument isEqual: argument]))
|
||
{
|
||
GSIArrayRemoveItemAtIndex(performers, count);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
NSEndMapTableEnumeration(&enumerator);
|
||
}
|
||
|
||
- (void) configureAsServer
|
||
{
|
||
/* Nothing to do here */
|
||
}
|
||
|
||
/**
|
||
* Sets up sending of aSelector to target with argument.<br />
|
||
* The selector is sent before the next runloop iteration (unless
|
||
* cancelled before then).<br />
|
||
* The target and argument objects are <em>not</em> retained.<br />
|
||
* 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.
|
||
*/
|
||
- (void) performSelector: (SEL)aSelector
|
||
target: (id)target
|
||
argument: (id)argument
|
||
order: (unsigned int)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];
|
||
|
||
[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)item, i);
|
||
break;
|
||
}
|
||
}
|
||
if (i == end)
|
||
{
|
||
GSIArrayInsertItem(performers, (GSIArrayItem)item, i);
|
||
}
|
||
}
|
||
RELEASE(item);
|
||
}
|
||
}
|
||
|
||
- (void) removePort: (NSPort*)port
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self removeEvent: (void*)port type: ET_RPORT forMode: mode all: NO];
|
||
}
|
||
|
||
@end
|
||
|