mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-25 09:41:15 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@3496 72102866-910b-0410-8b05-ffd578937521
1370 lines
31 KiB
Objective-C
1370 lines
31 KiB
Objective-C
/* Implementation of object for waiting on several input sources
|
||
Copyright (C) 1996, 1997 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
*/
|
||
|
||
/* October 1996 - extensions to permit file descriptors to be watched
|
||
for being readable or writable added by Richard Frith-Macdonald
|
||
(richard@brainstorm.co.uk) */
|
||
|
||
/* Andrews original comments - may still be valid even now -
|
||
|
||
Does it strike anyone else that NSNotificationCenter,
|
||
NSNotificationQueue, NSNotification, NSRunLoop, the "notifications"
|
||
a run loop sends the objects on which it is listening, NSEvent, and
|
||
the event queue maintained by NSApplication are all much more
|
||
intertwined/similar than OpenStep gives them credit for?
|
||
|
||
I wonder if these classes could be re-organized a little to make a
|
||
more uniform, "grand-unified" mechanism for: events,
|
||
event-listening, event-queuing, and event-distributing. It could
|
||
be quite pretty.
|
||
|
||
(GNUstep would definitely provide classes that were compatible with
|
||
all these OpenStep classes, but those classes could be wrappers
|
||
around fundamentally cleaner GNU classes. RMS has advised using an
|
||
underlying organization/implementation different from NeXT's
|
||
whenever that makes sense---it helps legally distinguish our work.)
|
||
|
||
Thoughts and insights, anyone?
|
||
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <base/preface.h>
|
||
#include <base/Heap.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>
|
||
|
||
#include <sys/types.h>
|
||
#ifndef __WIN32__
|
||
#include <time.h>
|
||
#include <sys/time.h>
|
||
#endif /* !__WIN32__ */
|
||
#include <limits.h>
|
||
#include <string.h> /* for memset() */
|
||
|
||
/* On some systems FD_ZERO is a macro that uses bzero().
|
||
Just define it to use memset(). */
|
||
#define bzero(PTR, LEN) memset (PTR, 0, LEN)
|
||
|
||
static int debug_run_loop = 0;
|
||
|
||
/*
|
||
* The 'RunLoopWatcher' 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 RunLoopWatcher are used as follows -
|
||
* If 'invalidated' is set, the wather 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.
|
||
*
|
||
* The 'limit' variable contains a date after which the event is useless
|
||
* and the watcher can be removed from the runloop. If this is nil
|
||
* then the watcher will only be removed if explicitly requested.
|
||
*
|
||
* To set this variable, the method adding the RunLoopWatcher 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.
|
||
*/
|
||
|
||
@interface RunLoopWatcher: NSObject
|
||
{
|
||
@public
|
||
BOOL invalidated;
|
||
void *data;
|
||
id receiver;
|
||
RunLoopEventType type;
|
||
NSDate *limit;
|
||
unsigned count;
|
||
}
|
||
- (void) eventFor: (void*)info
|
||
mode: (NSString*)mode;
|
||
- initWithType: (RunLoopEventType)type
|
||
receiver: (id)anObj
|
||
data: (void*)data;
|
||
- (void) invalidate;
|
||
- (BOOL) isValid;
|
||
@end
|
||
|
||
@implementation RunLoopWatcher
|
||
|
||
- (void) dealloc
|
||
{
|
||
[self invalidate];
|
||
[limit release];
|
||
[receiver release];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void) eventFor: (void*)info
|
||
mode: (NSString*)mode
|
||
{
|
||
if ([self isValid] == NO)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ([receiver respondsToSelector:
|
||
@selector(receivedEvent:type:extra:forMode:)])
|
||
{
|
||
[receiver receivedEvent: data type: type extra: info forMode: mode];
|
||
}
|
||
else
|
||
{
|
||
switch (type)
|
||
{
|
||
case ET_RDESC:
|
||
case ET_RPORT:
|
||
[receiver readyForReadingOnFileDescriptor: (int)info];
|
||
break;
|
||
|
||
case ET_WDESC:
|
||
[receiver readyForWritingOnFileDescriptor: (int)info];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
- initWithType: (RunLoopEventType)aType
|
||
receiver: (id)anObj
|
||
data: (void*)item
|
||
{
|
||
invalidated = NO;
|
||
switch (aType)
|
||
{
|
||
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 retain];
|
||
data = item;
|
||
return self;
|
||
}
|
||
|
||
- (void) invalidate
|
||
{
|
||
invalidated = YES;
|
||
}
|
||
|
||
- (BOOL) isValid
|
||
{
|
||
if (invalidated == YES)
|
||
{
|
||
return NO;
|
||
}
|
||
if ([receiver respondsToSelector: @selector(isValid)] &&
|
||
[receiver isValid] == NO)
|
||
{
|
||
[self invalidate];
|
||
return NO;
|
||
}
|
||
return YES;
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@interface NSRunLoop (TimedPerformers)
|
||
- (NSMutableArray*) _timedPerformers;
|
||
@end
|
||
|
||
@implementation NSRunLoop (TimedPerformers)
|
||
- (NSMutableArray*) _timedPerformers
|
||
{
|
||
return _timedPerformers;
|
||
}
|
||
@end
|
||
|
||
/*
|
||
* The RunLoopPerformer class is used to hold information about
|
||
* messages which are due to be sent to objects once a particular
|
||
* runloop iteration has passed.
|
||
*/
|
||
@interface RunLoopPerformer: NSObject
|
||
{
|
||
SEL selector;
|
||
id target;
|
||
id argument;
|
||
unsigned order;
|
||
NSArray *modes;
|
||
NSTimer *timer;
|
||
}
|
||
|
||
- (void) fire;
|
||
- initWithSelector: (SEL)aSelector
|
||
target: (id)target
|
||
argument: (id)argument
|
||
order: (unsigned int)order
|
||
modes: (NSArray*)modes;
|
||
- (BOOL) matchesSelector: (SEL)aSelector
|
||
target: (id)aTarget
|
||
argument: (id)anArgument;
|
||
- (NSArray*) modes;
|
||
- (unsigned int) order;
|
||
- (void) setTimer: (NSTimer*)timer;
|
||
@end
|
||
|
||
@implementation RunLoopPerformer
|
||
|
||
- (void) dealloc
|
||
{
|
||
[timer invalidate];
|
||
[target release];
|
||
[argument release];
|
||
[modes release];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void) fire
|
||
{
|
||
if (timer != nil)
|
||
{
|
||
timer = nil;
|
||
[[self retain] autorelease];
|
||
[[[NSRunLoop currentInstance] _timedPerformers]
|
||
removeObjectIdenticalTo: self];
|
||
}
|
||
[target performSelector: selector withObject: argument];
|
||
}
|
||
|
||
- initWithSelector: (SEL)aSelector
|
||
target: (id)aTarget
|
||
argument: (id)anArgument
|
||
order: (unsigned int)theOrder
|
||
modes: (NSArray*)theModes
|
||
{
|
||
self = [super init];
|
||
if (self)
|
||
{
|
||
selector = aSelector;
|
||
target = [aTarget retain];
|
||
argument = [anArgument retain];
|
||
order = theOrder;
|
||
modes = [theModes copy];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (BOOL) matchesSelector: (SEL)aSelector
|
||
target: (id)aTarget
|
||
argument: (id)anArgument
|
||
{
|
||
if (selector == aSelector)
|
||
{
|
||
if (target == aTarget)
|
||
{
|
||
if ([argument isEqual:anArgument])
|
||
{
|
||
return YES;
|
||
}
|
||
}
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
- (NSArray*) modes
|
||
{
|
||
return modes;
|
||
}
|
||
|
||
- (unsigned int) order
|
||
{
|
||
return order;
|
||
}
|
||
|
||
- (void) setTimer: (NSTimer*)t
|
||
{
|
||
timer = t;
|
||
}
|
||
@end
|
||
|
||
@implementation NSObject (TimedPerformers)
|
||
|
||
+ (void) cancelPreviousPerformRequestsWithTarget: (id)target
|
||
selector: (SEL)aSelector
|
||
object: (id)arg
|
||
{
|
||
NSMutableArray *array;
|
||
int i;
|
||
|
||
[target retain];
|
||
[arg retain];
|
||
array = [[NSRunLoop currentInstance] _timedPerformers];
|
||
for (i = [array count]; i > 0; i--)
|
||
{
|
||
if ([[array objectAtIndex: i-1] matchesSelector: aSelector
|
||
target: target
|
||
argument: arg])
|
||
{
|
||
[array removeObjectAtIndex: i-1];
|
||
}
|
||
}
|
||
[arg release];
|
||
[target release];
|
||
}
|
||
|
||
- (void) performSelector: (SEL)aSelector
|
||
withObject: (id)argument
|
||
afterDelay: (NSTimeInterval)seconds
|
||
{
|
||
NSMutableArray *array;
|
||
RunLoopPerformer *item;
|
||
|
||
array = [[NSRunLoop currentInstance] _timedPerformers];
|
||
item = [[RunLoopPerformer alloc] initWithSelector: aSelector
|
||
target: self
|
||
argument: argument
|
||
order: 0
|
||
modes: nil];
|
||
[array addObject: item];
|
||
[item setTimer: [NSTimer scheduledTimerWithTimeInterval: seconds
|
||
target: item
|
||
selector: @selector(fire)
|
||
userInfo: nil
|
||
repeats: NO]];
|
||
[item release];
|
||
}
|
||
|
||
- (void) performSelector: (SEL)aSelector
|
||
withObject: (id)argument
|
||
afterDelay: (NSTimeInterval)seconds
|
||
inModes: (NSArray*)modes
|
||
{
|
||
NSRunLoop *loop;
|
||
NSMutableArray *array;
|
||
RunLoopPerformer *item;
|
||
NSTimer *timer;
|
||
int i;
|
||
|
||
if (modes == nil || [modes count] == 0)
|
||
{
|
||
return;
|
||
}
|
||
loop = [NSRunLoop currentInstance];
|
||
array = [loop _timedPerformers];
|
||
item = [[RunLoopPerformer alloc] initWithSelector: aSelector
|
||
target: self
|
||
argument: argument
|
||
order: 0
|
||
modes: nil];
|
||
[array addObject: item];
|
||
timer = [NSTimer timerWithTimeInterval: seconds
|
||
target: item
|
||
selector: @selector(fire)
|
||
userInfo: nil
|
||
repeats: NO];
|
||
[item setTimer: timer];
|
||
[item release];
|
||
for (i = 0; i < [modes count]; i++)
|
||
{
|
||
[loop addTimer: timer forMode: [modes objectAtIndex: i]];
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
@interface NSRunLoop (Private)
|
||
|
||
- (void) _addWatcher: (RunLoopWatcher*)item
|
||
forMode: (NSString*)mode;
|
||
- (void) _checkPerformers;
|
||
- (RunLoopWatcher*) _getWatcher: (void*)data
|
||
type: (RunLoopEventType)type
|
||
forMode: (NSString*)mode;
|
||
- (NSMutableArray*) _performers;
|
||
- (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: (RunLoopWatcher*) item forMode: (NSString*)mode
|
||
{
|
||
NSMutableArray *watchers;
|
||
id obj;
|
||
NSDate *limit;
|
||
int count;
|
||
|
||
watchers = NSMapGet (_mode_2_watchers, mode);
|
||
if (watchers == nil)
|
||
{
|
||
watchers = [NSMutableArray new];
|
||
NSMapInsert (_mode_2_watchers, mode, watchers);
|
||
[watchers release];
|
||
count = 0;
|
||
}
|
||
else
|
||
{
|
||
count = [watchers count];
|
||
}
|
||
|
||
/*
|
||
* 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];
|
||
|
||
ASSIGN(item->limit, d);
|
||
}
|
||
else if ([obj respondsToSelector: @selector(delegate)])
|
||
{
|
||
obj = [obj delegate];
|
||
if ([obj respondsToSelector: @selector(limitDateForMode:)])
|
||
{
|
||
NSDate *d = [obj limitDateForMode: mode];
|
||
|
||
ASSIGN(item->limit, d);
|
||
}
|
||
}
|
||
limit = item->limit;
|
||
|
||
/*
|
||
* Make sure that the items in the watchers list are ordered.
|
||
*/
|
||
if (limit == nil || count == 0)
|
||
{
|
||
[watchers addObject:item];
|
||
}
|
||
else
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
RunLoopWatcher *watcher = [watchers objectAtIndex: i];
|
||
NSDate *when = watcher->limit;
|
||
|
||
if (when == nil || [limit earlierDate:when] == when)
|
||
{
|
||
[watchers insertObject:item atIndex:i];
|
||
break;
|
||
}
|
||
}
|
||
if (i == count)
|
||
{
|
||
[watchers addObject:item];
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void) _checkPerformers
|
||
{
|
||
RunLoopPerformer *item;
|
||
NSArray *array = [NSArray arrayWithArray: _performers];
|
||
int count = [array count];
|
||
unsigned pos;
|
||
int i;
|
||
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
item = (RunLoopPerformer*)[array objectAtIndex: i];
|
||
|
||
pos = [_performers indexOfObjectIdenticalTo: item];
|
||
if (pos != NSNotFound)
|
||
{
|
||
if ([[item modes] containsObject: _current_mode])
|
||
{
|
||
[_performers removeObjectAtIndex: pos];
|
||
[item fire];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
@implementation NSRunLoop(GNUstepExtensions)
|
||
|
||
+ currentInstance
|
||
{
|
||
return [self currentRunLoop];
|
||
}
|
||
|
||
+ (NSString*) currentMode
|
||
{
|
||
return [[NSRunLoop currentRunLoop] currentMode];
|
||
}
|
||
|
||
+ (void) run
|
||
{
|
||
[[NSRunLoop currentRunLoop] run];
|
||
}
|
||
|
||
+ (void) runUntilDate: date
|
||
{
|
||
[[NSRunLoop currentRunLoop] runUntilDate: date];
|
||
}
|
||
|
||
+ (void) runUntilDate: date forMode: (NSString*)mode
|
||
{
|
||
[[NSRunLoop currentRunLoop] runUntilDate: date forMode: mode];
|
||
}
|
||
|
||
+ (BOOL) runOnceBeforeDate: date
|
||
{
|
||
return [[NSRunLoop currentRunLoop] runOnceBeforeDate: date];
|
||
}
|
||
|
||
+ (BOOL) runOnceBeforeDate: date forMode: (NSString*)mode
|
||
{
|
||
return [[NSRunLoop currentRunLoop] runOnceBeforeDate: date forMode: mode];
|
||
}
|
||
|
||
- (void) addEvent: (void*)data
|
||
type: (RunLoopEventType)type
|
||
watcher: (id<RunLoopEvents>)watcher
|
||
forMode: (NSString*)mode
|
||
{
|
||
RunLoopWatcher *info;
|
||
|
||
if (mode == nil)
|
||
{
|
||
mode = _current_mode;
|
||
}
|
||
|
||
info = [self _getWatcher: data type: type forMode: mode];
|
||
|
||
if (info && 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 = [[RunLoopWatcher alloc] initWithType: type
|
||
receiver: watcher
|
||
data: data];
|
||
/* Add the object to the array for the mode. */
|
||
[self _addWatcher:info forMode:mode];
|
||
[info release]; /* Now held in array. */
|
||
}
|
||
}
|
||
|
||
- (void) addReadDescriptor: (int)fd
|
||
object: (id <FdListening>)listener
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self addEvent: (void*)fd
|
||
type: ET_RDESC
|
||
watcher: (id<RunLoopEvents>)listener
|
||
forMode: mode];
|
||
}
|
||
|
||
/* Add our new handler information to the array. */
|
||
- (void) addWriteDescriptor: (int)fd
|
||
object: (id <FdSpeaking>)speaker
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self addEvent: (void*)fd
|
||
type: ET_WDESC
|
||
watcher: (id<RunLoopEvents>)speaker
|
||
forMode: mode];
|
||
}
|
||
|
||
- (void) removeEvent: (void*)data
|
||
type: (RunLoopEventType)type
|
||
forMode: (NSString*)mode
|
||
all: (BOOL)removeAll
|
||
{
|
||
if (mode == nil)
|
||
{
|
||
mode = _current_mode;
|
||
}
|
||
if (removeAll)
|
||
{
|
||
[self _removeWatcher: data type: type forMode: mode];
|
||
}
|
||
else
|
||
{
|
||
RunLoopWatcher *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--;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void) removeReadDescriptor: (int)fd
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self removeEvent:(void*)fd type: ET_RDESC forMode:mode all:NO];
|
||
}
|
||
|
||
- (void) removeWriteDescriptor: (int)fd
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self removeEvent:(void*)fd type: ET_WDESC forMode:mode all:NO];
|
||
}
|
||
|
||
- (BOOL) runOnceBeforeDate: date
|
||
{
|
||
return [self runOnceBeforeDate: date forMode: _current_mode];
|
||
}
|
||
|
||
/* Running the run loop once through for timers and input listening. */
|
||
|
||
- (BOOL) runOnceBeforeDate: date forMode: (NSString*)mode
|
||
{
|
||
return [self runMode:mode beforeDate:date];
|
||
}
|
||
|
||
- (void) runUntilDate: date forMode: (NSString*)mode
|
||
{
|
||
volatile double ti;
|
||
BOOL mayDoMore = YES;
|
||
|
||
ti = [date timeIntervalSinceNow];
|
||
/* Positive values are in the future. */
|
||
while (ti > 0 && mayDoMore == YES)
|
||
{
|
||
id arp = [NSAutoreleasePool new];
|
||
if (debug_run_loop)
|
||
printf ("\tNSRunLoop run until date %f seconds from now\n", ti);
|
||
mayDoMore = [self runMode: mode beforeDate: date];
|
||
[arp release];
|
||
ti = [date timeIntervalSinceNow];
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation NSRunLoop
|
||
|
||
+ currentRunLoop
|
||
{
|
||
static NSString *key = @"NSRunLoopThreadKey";
|
||
NSRunLoop* r;
|
||
NSThread* t;
|
||
|
||
t = [NSThread currentThread];
|
||
r = [[t threadDictionary] objectForKey: key];
|
||
if (r == nil)
|
||
{
|
||
r = [NSRunLoop new];
|
||
[[t threadDictionary] setObject: r forKey: key];
|
||
[r release];
|
||
}
|
||
return r;
|
||
}
|
||
|
||
+ (void) initialize
|
||
{
|
||
if (self == [NSRunLoop class])
|
||
[self currentRunLoop];
|
||
}
|
||
|
||
/* This is the designated initializer. */
|
||
- init
|
||
{
|
||
[super init];
|
||
_current_mode = NSDefaultRunLoopMode;
|
||
_mode_2_timers = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
_mode_2_watchers = NSCreateMapTable (NSObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
_performers = [[NSMutableArray alloc] initWithCapacity:8];
|
||
_timedPerformers = [[NSMutableArray alloc] initWithCapacity:8];
|
||
return self;
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
NSFreeMapTable(_mode_2_timers);
|
||
NSFreeMapTable(_mode_2_watchers);
|
||
[_performers release];
|
||
[_timedPerformers release];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (NSString*) currentMode
|
||
{
|
||
return _current_mode;
|
||
}
|
||
|
||
|
||
/* Adding timers. They are removed when they are invalid. */
|
||
|
||
- (void) addTimer: timer
|
||
forMode: (NSString*)mode
|
||
{
|
||
Heap *timers;
|
||
|
||
timers = NSMapGet (_mode_2_timers, mode);
|
||
if (!timers)
|
||
{
|
||
timers = [Heap new];
|
||
NSMapInsert (_mode_2_timers, mode, timers);
|
||
[timers release];
|
||
}
|
||
/* xxx Should we make sure it isn't already there? */
|
||
[timers addObject: timer];
|
||
}
|
||
|
||
|
||
/* Fire appropriate timers and determine the earliest time that anything
|
||
watched for becomes useless. */
|
||
|
||
- limitDateForMode: (NSString*)mode
|
||
{
|
||
id saved_mode;
|
||
Heap *timers;
|
||
NSTimer *min_timer = nil;
|
||
RunLoopWatcher *min_watcher = nil;
|
||
NSMutableArray *watchers;
|
||
NSDate *when;
|
||
|
||
saved_mode = _current_mode;
|
||
_current_mode = mode;
|
||
|
||
timers = NSMapGet(_mode_2_timers, mode);
|
||
if (timers)
|
||
{
|
||
while ((min_timer = [timers minObject]) != nil)
|
||
{
|
||
if (![min_timer isValid])
|
||
{
|
||
[timers removeFirstObject];
|
||
min_timer = nil;
|
||
continue;
|
||
}
|
||
|
||
if ([[min_timer fireDate] timeIntervalSinceNow] > 0)
|
||
{
|
||
break;
|
||
}
|
||
|
||
[min_timer retain];
|
||
[timers removeFirstObject];
|
||
/* Firing will also increment its fireDate, if it is repeating. */
|
||
[min_timer fire];
|
||
if ([min_timer isValid])
|
||
{
|
||
[timers addObject: min_timer];
|
||
}
|
||
[min_timer release];
|
||
min_timer = nil;
|
||
[NSNotificationQueue runLoopASAP]; /* Post notifications. */
|
||
}
|
||
}
|
||
|
||
/* Is this right? At the moment we invalidate and discard watchers
|
||
whose limit-dates have passed. */
|
||
watchers = NSMapGet(_mode_2_watchers, mode);
|
||
if (watchers)
|
||
{
|
||
while ([watchers count] > 0)
|
||
{
|
||
min_watcher = (RunLoopWatcher*)[watchers objectAtIndex:0];
|
||
|
||
if (![min_watcher isValid])
|
||
{
|
||
[watchers removeObjectAtIndex: 0];
|
||
min_watcher = nil;
|
||
continue;
|
||
}
|
||
|
||
when = min_watcher->limit;
|
||
if (when == nil || [when 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.
|
||
*/
|
||
obj = min_watcher->receiver;
|
||
if ([obj respondsToSelector:
|
||
@selector(timedOutEvent:type:forMode:)])
|
||
{
|
||
nxt = [obj timedOutEvent: min_watcher->data
|
||
type: min_watcher->type
|
||
forMode: _current_mode];
|
||
}
|
||
else if ([obj respondsToSelector:@selector(delegate)])
|
||
{
|
||
obj = [obj delegate];
|
||
if ([obj respondsToSelector:
|
||
@selector(timedOutEvent:type:forMode:)])
|
||
{
|
||
nxt = [obj timedOutEvent: min_watcher->data
|
||
type: min_watcher->type
|
||
forMode: _current_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.
|
||
*/
|
||
[min_watcher retain];
|
||
ASSIGN(min_watcher->limit, nxt);
|
||
[watchers removeObjectAtIndex: 0];
|
||
[self _addWatcher: min_watcher forMode: mode];
|
||
[min_watcher release];
|
||
}
|
||
else
|
||
{
|
||
/*
|
||
* If the watcher is now useless - invalidate it and
|
||
* remove it from the queue so that we don't need to
|
||
* check it again.
|
||
*/
|
||
[min_watcher invalidate];
|
||
[watchers removeObjectAtIndex:0];
|
||
}
|
||
min_watcher = nil;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* If there are timers - set limit date to the earliest of them.
|
||
*/
|
||
if (min_timer)
|
||
{
|
||
when = [min_timer fireDate];
|
||
}
|
||
else
|
||
{
|
||
when = nil;
|
||
}
|
||
|
||
/*
|
||
* 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).
|
||
* NB. A watcher without a limit date watches forever - so it's limit
|
||
* is effectively some time in the distant future.
|
||
*/
|
||
if (min_watcher)
|
||
{
|
||
NSDate* lim;
|
||
|
||
if (min_watcher->limit == nil) /* No limit for watcher */
|
||
{
|
||
lim = [NSDate distantFuture]; /* - watches forever. */
|
||
}
|
||
else
|
||
{
|
||
lim = min_watcher->limit;
|
||
}
|
||
|
||
if (when == nil)
|
||
{
|
||
when = lim;
|
||
}
|
||
else
|
||
{
|
||
when = [when earlierDate:lim];
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 'when' will only be nil if there are neither timers nor watchers
|
||
* outstanding.
|
||
*/
|
||
if (when && debug_run_loop)
|
||
{
|
||
printf ("\tNSRunLoop limit date %f\n",
|
||
[when timeIntervalSinceReferenceDate]);
|
||
}
|
||
_current_mode = saved_mode;
|
||
|
||
return when;
|
||
}
|
||
|
||
- (RunLoopWatcher*) _getWatcher: (void*)data
|
||
type: (RunLoopEventType)type
|
||
forMode: (NSString*)mode
|
||
{
|
||
NSArray *watchers;
|
||
RunLoopWatcher *info;
|
||
int count;
|
||
|
||
if (mode == nil)
|
||
{
|
||
mode = _current_mode;
|
||
}
|
||
|
||
watchers = NSMapGet (_mode_2_watchers, mode);
|
||
if (watchers == nil)
|
||
{
|
||
return nil;
|
||
}
|
||
for (count = 0; count < [watchers count]; count++)
|
||
{
|
||
info = [watchers objectAtIndex: count];
|
||
|
||
if (info->type == type)
|
||
{
|
||
if (info->data == data)
|
||
{
|
||
return info;
|
||
}
|
||
}
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
- (void) _removeWatcher: (void*)data
|
||
type: (RunLoopEventType)type
|
||
forMode: (NSString*)mode
|
||
{
|
||
NSMutableArray *watchers;
|
||
|
||
if (mode == nil)
|
||
{
|
||
mode = _current_mode;
|
||
}
|
||
|
||
watchers = NSMapGet (_mode_2_watchers, mode);
|
||
if (watchers)
|
||
{
|
||
int i;
|
||
|
||
for (i = [watchers count]; i > 0; i--)
|
||
{
|
||
RunLoopWatcher* info;
|
||
|
||
info = (RunLoopWatcher*)[watchers objectAtIndex:(i-1)];
|
||
if (info->type == type && info->data == data)
|
||
{
|
||
[info invalidate];
|
||
[watchers removeObject: info];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/* Listen to input sources.
|
||
If LIMIT_DATE is nil, then don't wait; i.e. call select() with 0 timeout */
|
||
|
||
- (void) acceptInputForMode: (NSString*)mode
|
||
beforeDate: limit_date
|
||
{
|
||
NSTimeInterval ti;
|
||
struct timeval timeout;
|
||
void *select_timeout;
|
||
fd_set fds; /* The file descriptors we will listen to. */
|
||
fd_set read_fds; /* Copy for listening to read-ready fds. */
|
||
fd_set exception_fds; /* Copy for listening to exception fds. */
|
||
fd_set write_fds; /* Copy for listening for write-ready fds. */
|
||
int select_return;
|
||
int fd_index;
|
||
NSMapTable *rfd_2_object;
|
||
NSMapTable *wfd_2_object;
|
||
id saved_mode;
|
||
int num_inputs = 0;
|
||
|
||
NSAssert(mode, NSInvalidArgumentException);
|
||
saved_mode = _current_mode;
|
||
_current_mode = mode;
|
||
|
||
/* Find out how much time we should wait, and set SELECT_TIMEOUT. */
|
||
if (!limit_date)
|
||
{
|
||
/* Don't wait at all. */
|
||
timeout.tv_sec = 0;
|
||
timeout.tv_usec = 0;
|
||
select_timeout = &timeout;
|
||
}
|
||
else if ((ti = [limit_date timeIntervalSinceNow]) < LONG_MAX
|
||
&& ti > 0.0)
|
||
{
|
||
/* Wait until the LIMIT_DATE. */
|
||
if (debug_run_loop)
|
||
printf ("\tNSRunLoop accept input before %f (seconds from now %f)\n",
|
||
[limit_date timeIntervalSinceReferenceDate], ti);
|
||
/* If LIMIT_DATE has already past, return immediately. */
|
||
if (ti < 0)
|
||
{
|
||
[self _checkPerformers];
|
||
if (debug_run_loop)
|
||
printf ("\tNSRunLoop limit date past, returning\n");
|
||
_current_mode = saved_mode;
|
||
return;
|
||
}
|
||
timeout.tv_sec = ti;
|
||
timeout.tv_usec = (ti - timeout.tv_sec) * 1000000.0;
|
||
select_timeout = &timeout;
|
||
}
|
||
else if (ti <= 0.0)
|
||
{
|
||
/* The LIMIT_DATE has already past; return immediately without
|
||
polling any inputs. */
|
||
_current_mode = saved_mode;
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
/* Wait forever. */
|
||
if (debug_run_loop)
|
||
printf ("\tNSRunLoop accept input waiting forever\n");
|
||
select_timeout = NULL;
|
||
}
|
||
|
||
/* Get ready to listen to file descriptors.
|
||
Initialize the set of FDS we'll pass to select(), and create
|
||
an empty map for keeping track of which object is associated
|
||
with which file descriptor. */
|
||
FD_ZERO (&fds);
|
||
FD_ZERO (&write_fds);
|
||
rfd_2_object = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
wfd_2_object = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
|
||
/* Do the pre-listening set-up for the file descriptors of this mode. */
|
||
{
|
||
NSMutableArray *watchers;
|
||
|
||
watchers = NSMapGet (_mode_2_watchers, mode);
|
||
if (watchers) {
|
||
int i;
|
||
|
||
for (i = [watchers count]; i > 0; i--) {
|
||
RunLoopWatcher* info = [watchers objectAtIndex:(i-1)];
|
||
int fd;
|
||
|
||
if ([info isValid] == NO) {
|
||
[watchers removeObjectAtIndex: (i-1)];
|
||
continue;
|
||
}
|
||
switch (info->type) {
|
||
case ET_WDESC:
|
||
fd = (int)info->data;
|
||
FD_SET (fd, &write_fds);
|
||
NSMapInsert (wfd_2_object, (void*)fd, info);
|
||
num_inputs++;
|
||
break;
|
||
|
||
case ET_RDESC:
|
||
fd = (int)info->data;
|
||
FD_SET (fd, &fds);
|
||
NSMapInsert (rfd_2_object, (void*)fd, info);
|
||
num_inputs++;
|
||
break;
|
||
|
||
case ET_RPORT:
|
||
{
|
||
id port = info->receiver;
|
||
int port_fd_count = 128; // xxx #define this constant
|
||
int port_fd_array[port_fd_count];
|
||
|
||
if ([port respondsTo: @selector(getFds:count:)])
|
||
[port getFds: port_fd_array count: &port_fd_count];
|
||
if (debug_run_loop)
|
||
printf("\tNSRunLoop listening to %d sockets\n",
|
||
port_fd_count);
|
||
|
||
while (port_fd_count--)
|
||
{
|
||
FD_SET (port_fd_array[port_fd_count], &fds);
|
||
NSMapInsert (rfd_2_object,
|
||
(void*)port_fd_array[port_fd_count],
|
||
info);
|
||
num_inputs++;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Wait for incoming data, listening to the file descriptors in _FDS. */
|
||
read_fds = fds;
|
||
exception_fds = fds;
|
||
|
||
/* Detect if the NSRunLoop is idle, and if necessary - dispatch the
|
||
notifications from NSNotificationQueue's idle queue? */
|
||
if (num_inputs == 0 && [NSNotificationQueue runLoopMore])
|
||
{
|
||
timeout.tv_sec = 0;
|
||
timeout.tv_usec = 0;
|
||
select_timeout = &timeout;
|
||
select_return = select (FD_SETSIZE, &read_fds, &write_fds, &exception_fds,
|
||
select_timeout);
|
||
}
|
||
else
|
||
select_return = select (FD_SETSIZE, &read_fds, &write_fds, &exception_fds,
|
||
select_timeout);
|
||
|
||
if (debug_run_loop)
|
||
printf ("\tNSRunLoop select returned %d\n", select_return);
|
||
|
||
if (select_return < 0)
|
||
{
|
||
if (errno == EINTR)
|
||
{
|
||
select_return = 0;
|
||
}
|
||
else
|
||
{
|
||
/* Some exceptional condition happened. */
|
||
/* xxx We can do something with exception_fds, instead of
|
||
aborting here. */
|
||
perror ("[NSRunLoop acceptInputForMode:beforeDate:] select()");
|
||
abort ();
|
||
}
|
||
}
|
||
if (select_return == 0)
|
||
{
|
||
NSFreeMapTable (rfd_2_object);
|
||
NSFreeMapTable (wfd_2_object);
|
||
[NSNotificationQueue runLoopIdle];
|
||
[self _checkPerformers];
|
||
_current_mode = saved_mode;
|
||
return;
|
||
}
|
||
|
||
/* Look at all the file descriptors select() says are ready for reading;
|
||
notify the corresponding object for each of the ready fd's. */
|
||
for (fd_index = 0; fd_index < FD_SETSIZE; fd_index++)
|
||
{
|
||
if (FD_ISSET (fd_index, &write_fds))
|
||
{
|
||
id watcher = (id) NSMapGet (wfd_2_object, (void*)fd_index);
|
||
NSAssert(watcher, NSInternalInconsistencyException);
|
||
[watcher eventFor:(void*)fd_index mode:_current_mode];
|
||
[NSNotificationQueue runLoopASAP];
|
||
}
|
||
if (FD_ISSET (fd_index, &read_fds))
|
||
{
|
||
id watcher = (id) NSMapGet (rfd_2_object, (void*)fd_index);
|
||
NSAssert(watcher, NSInternalInconsistencyException);
|
||
[watcher eventFor:(void*)fd_index mode:_current_mode];
|
||
[NSNotificationQueue runLoopASAP];
|
||
}
|
||
}
|
||
/* Clean up before returning. */
|
||
NSFreeMapTable (rfd_2_object);
|
||
NSFreeMapTable (wfd_2_object);
|
||
|
||
[self _checkPerformers];
|
||
[NSNotificationQueue runLoopASAP];
|
||
_current_mode = saved_mode;
|
||
}
|
||
|
||
- (BOOL) runMode: (NSString*)mode beforeDate: date
|
||
{
|
||
id d;
|
||
|
||
/* If date has already passed, simply return. */
|
||
if ([date timeIntervalSinceNow] < 0)
|
||
{
|
||
if (debug_run_loop)
|
||
{
|
||
printf ("\tNSRunLoop run mode with date already past\n");
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
/* Find out how long we can wait before first limit date. */
|
||
d = [self limitDateForMode: mode];
|
||
if (d == nil)
|
||
{
|
||
if (debug_run_loop)
|
||
{
|
||
printf ("\tNSRunLoop run mode with nothing to do\n");
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
/* Use the earlier of the two dates we have. */
|
||
d = [[d earlierDate:date] retain];
|
||
|
||
/* Wait, listening to our input sources. */
|
||
[self acceptInputForMode: mode beforeDate: d];
|
||
|
||
[d release];
|
||
return YES;
|
||
}
|
||
|
||
- (void) run
|
||
{
|
||
[self runUntilDate: [NSDate distantFuture]];
|
||
}
|
||
|
||
- (void) runUntilDate: date
|
||
{
|
||
[self runUntilDate: date forMode: _current_mode];
|
||
}
|
||
|
||
|
||
/* NSRunLoop mode strings. */
|
||
|
||
id NSDefaultRunLoopMode = @"NSDefaultRunLoopMode";
|
||
|
||
@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];
|
||
}
|
||
|
||
- (void) cancelPerformSelector: (SEL)aSelector
|
||
target: target
|
||
argument: argument
|
||
{
|
||
RunLoopPerformer *item;
|
||
int count = [_performers count];
|
||
int i;
|
||
|
||
[target retain];
|
||
[argument retain];
|
||
for (i = count; i > 0; i--)
|
||
{
|
||
item = (RunLoopPerformer*)[_performers objectAtIndex:(i-1)];
|
||
|
||
if ([item matchesSelector:aSelector target:target argument:argument])
|
||
{
|
||
[_performers removeObjectAtIndex:(i-1)];
|
||
}
|
||
}
|
||
[argument release];
|
||
[target release];
|
||
}
|
||
|
||
- (void) configureAsServer
|
||
{
|
||
/* Nothing to do here */
|
||
}
|
||
|
||
- (void) performSelector: (SEL)aSelector
|
||
target: target
|
||
argument: argument
|
||
order: (unsigned int)order
|
||
modes: (NSArray*)modes
|
||
{
|
||
RunLoopPerformer *item;
|
||
int count = [_performers count];
|
||
|
||
item = [[RunLoopPerformer alloc] initWithSelector: aSelector
|
||
target: target
|
||
argument: argument
|
||
order: order
|
||
modes: modes];
|
||
/* Add new item to list - reverse ordering */
|
||
if (count == 0)
|
||
{
|
||
[_performers addObject:item];
|
||
}
|
||
else
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < count; i++)
|
||
{
|
||
if ([[_performers objectAtIndex:i] order] <= order)
|
||
{
|
||
[_performers insertObject:item atIndex:i];
|
||
break;
|
||
}
|
||
}
|
||
if (i == count)
|
||
{
|
||
[_performers addObject:item];
|
||
}
|
||
}
|
||
[item release];
|
||
}
|
||
|
||
- (void) removePort: (NSPort*)port
|
||
forMode: (NSString*)mode
|
||
{
|
||
return [self removeEvent:(void*)port type: ET_RPORT forMode:mode all:NO];
|
||
}
|
||
|
||
@end
|
||
|