mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-25 01:31:08 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@1551 72102866-910b-0410-8b05-ffd578937521
573 lines
15 KiB
Objective-C
573 lines
15 KiB
Objective-C
/* Implementation of object for waiting on several input sources
|
||
Copyright (C) 1996 Free Software Foundation, Inc.
|
||
|
||
Written by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
|
||
Created: March 1996
|
||
|
||
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.
|
||
*/
|
||
|
||
/* This is the beginning of a RunLoop implementation.
|
||
It is still in the early stages of development, and will most likely
|
||
evolve quite a bit more before the interface settles.
|
||
|
||
Handling NSTimers is implemented, but currently disabled.
|
||
|
||
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?
|
||
|
||
*/
|
||
|
||
/* Alternate names: InputDemuxer, InputListener, EventListener.
|
||
Alternate names for Notification classes: Dispatcher, EventDistributor, */
|
||
|
||
#include <gnustep/base/preface.h>
|
||
#include <gnustep/base/RunLoop.h>
|
||
#include <gnustep/base/Heap.h>
|
||
#include <Foundation/NSMapTable.h>
|
||
#include <Foundation/NSDate.h>
|
||
#include <Foundation/NSValue.h>
|
||
#include <Foundation/NSAutoreleasePool.h>
|
||
#include <Foundation/NSTimer.h>
|
||
#ifndef WIN32
|
||
#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;
|
||
|
||
@implementation RunLoop
|
||
|
||
static RunLoop *current_run_loop;
|
||
|
||
+ (void) initialize
|
||
{
|
||
if (self == [RunLoop class])
|
||
current_run_loop = [self new];
|
||
}
|
||
|
||
/* This is the designated initializer. */
|
||
- init
|
||
{
|
||
[super init];
|
||
_current_mode = RunLoopDefaultMode;
|
||
_mode_2_timers = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
_mode_2_in_ports = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
_mode_2_fd_listeners = NSCreateMapTable (NSNonRetainedObjectMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
return self;
|
||
}
|
||
|
||
- (id <String>) currentMode
|
||
{
|
||
return _current_mode;
|
||
}
|
||
|
||
|
||
/* Adding and removing file descriptors. */
|
||
|
||
- (void) addFileDescriptor: (int)fd
|
||
object: listener
|
||
forMode: (id <String>)mode
|
||
{
|
||
/* xxx Perhaps this should be a Bag instead. */
|
||
Array *fd_listeners;
|
||
|
||
/* xxx We need to keep track of the FD too!
|
||
Perhaps I'll make a FileDescriptor class to hold all this;
|
||
it will be more analogous the Port object. */
|
||
[self notImplemented: _cmd];
|
||
|
||
fd_listeners = NSMapGet (_mode_2_fd_listeners, mode);
|
||
if (!fd_listeners)
|
||
{
|
||
fd_listeners = [Array new];
|
||
NSMapInsert (_mode_2_fd_listeners, mode, fd_listeners);
|
||
[fd_listeners release];
|
||
}
|
||
[fd_listeners addObject: listener];
|
||
}
|
||
|
||
- (void) removeFileDescriptor: (int)fd
|
||
forMode: (id <String>)mode
|
||
{
|
||
#if 1
|
||
[self notImplemented: _cmd];
|
||
#else
|
||
Array *fd_listeners;
|
||
|
||
fd_listeners = NSMapGet (_mode_2_fd_listeners, mode);
|
||
if (!fd_listeners)
|
||
/* xxx Careful, this is only suppose to "undo" one -addPort:.
|
||
If we change the -removeObject implementation later to remove
|
||
all instances of port, we'll have to change this code here. */
|
||
[fd_listeners removeObject: port];
|
||
#endif
|
||
}
|
||
|
||
|
||
/* Adding and removing port objects. */
|
||
|
||
- (void) addPort: port
|
||
forMode: (id <String>)mode
|
||
{
|
||
/* xxx Perhaps this should be a Bag instead; I think this currently works
|
||
when a port is added more than once, but it doesn't work prettily. */
|
||
Array *in_ports;
|
||
|
||
in_ports = NSMapGet (_mode_2_in_ports, mode);
|
||
if (!in_ports)
|
||
{
|
||
in_ports = [Array new];
|
||
NSMapInsert (_mode_2_in_ports, mode, in_ports);
|
||
[in_ports release];
|
||
}
|
||
[in_ports addObject: port];
|
||
}
|
||
|
||
- (void) removePort: port
|
||
forMode: (id <String>)mode
|
||
{
|
||
/* xxx Perhaps this should be a Bag instead. */
|
||
Array *in_ports;
|
||
|
||
in_ports = NSMapGet (_mode_2_in_ports, mode);
|
||
if (in_ports)
|
||
/* xxx Careful, this is only suppose to "undo" one -addPort:.
|
||
If we change the -removeObject implementation later to remove
|
||
all instances of port, we'll have to change this code here. */
|
||
[in_ports removeObject: port];
|
||
}
|
||
|
||
|
||
/* Adding timers. They are removed when they are invalid. */
|
||
|
||
- (void) addTimer: timer
|
||
forMode: (id <String>)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. */
|
||
|
||
- limitDateForMode: (id <String>)mode
|
||
{
|
||
/* Linux doesn't always return double from methods, even though
|
||
I'm using -lieee. */
|
||
#if 1
|
||
assert (mode);
|
||
return nil;
|
||
#else
|
||
Heap *timers;
|
||
NSTimer *min_timer = nil;
|
||
id saved_mode;
|
||
|
||
saved_mode = _current_mode;
|
||
_current_mode = mode;
|
||
|
||
timers = NSMapGet (_mode_2_timers, mode);
|
||
if (!timers)
|
||
return nil;
|
||
|
||
/* Does this properly handle timers that have been sent -invalidate? */
|
||
while ((min_timer = [timers minObject])
|
||
&& ([[min_timer fireDate] timeIntervalSinceNow] > 0))
|
||
{
|
||
[timers removeFirstObject];
|
||
/* Firing will also increment its fireDate, if it is repeating. */
|
||
if ([min_timer isValid])
|
||
{
|
||
[min_timer fire];
|
||
if ([[min_timer fireDate] timeIntervalSinceNow] < 0)
|
||
[timers addObject: min_timer];
|
||
}
|
||
}
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop limit date %f\n",
|
||
[[min_timer fireDate] timeIntervalSinceReferenceDate]);
|
||
|
||
_current_mode = saved_mode;
|
||
return [min_timer fireDate];
|
||
#endif
|
||
}
|
||
|
||
|
||
/* Listen to input sources.
|
||
If LIMIT_DATE is nil, then don't wait; i.e. call select() with 0 timeout */
|
||
|
||
- (void) acceptInputForMode: (id <String>)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. */
|
||
int select_return;
|
||
int fd_index;
|
||
NSMapTable *fd_2_object;
|
||
id saved_mode;
|
||
|
||
assert (mode);
|
||
saved_mode = _current_mode;
|
||
_current_mode = mode;
|
||
|
||
/* xxx No, perhaps this isn't the right thing to do. */
|
||
#if 0
|
||
/* If there are no input sources to listen to, just return. */
|
||
if (NSCountMapTable (_fd_2_object) == 0)
|
||
return;
|
||
#endif
|
||
|
||
/* 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 ("\tRunLoop accept input before %f (seconds from now %f)\n",
|
||
[limit_date timeIntervalSinceReferenceDate], ti);
|
||
/* If LIMIT_DATE has already past, return immediately. */
|
||
if (ti < 0)
|
||
{
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop limit date past, returning\n");
|
||
return;
|
||
}
|
||
timeout.tv_sec = ti;
|
||
timeout.tv_usec = ti * 1000000.0;
|
||
select_timeout = &timeout;
|
||
}
|
||
else if (ti <= 0.0)
|
||
{
|
||
/* The LIMIT_DATE has already past; return immediately without
|
||
polling any inputs. */
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
/* Wait forever. */
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop 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_2_object = NSCreateMapTable (NSIntMapKeyCallBacks,
|
||
NSObjectMapValueCallBacks, 0);
|
||
|
||
|
||
/* Do the pre-listening set-up for the file descriptors of this mode. */
|
||
{
|
||
}
|
||
|
||
/* Do the pre-listening set-up for the ports of this mode. */
|
||
{
|
||
id ports = NSMapGet (_mode_2_in_ports, mode);
|
||
if (ports)
|
||
{
|
||
id port;
|
||
int i;
|
||
|
||
/* If a port is invalid, remove it from this mode. */
|
||
for (i = [ports count]-1; i >= 0; i--)
|
||
{
|
||
port = [ports objectAtIndex: i];
|
||
if (![port isValid])
|
||
[ports removeObjectAtIndex: i];
|
||
}
|
||
|
||
/* Ask our ports for the list of file descriptors they
|
||
want us to listen to; add these to FD_LISTEN_SET. */
|
||
for (i = [ports count]-1; i >= 0; i--)
|
||
{
|
||
int port_fd_count = 128;
|
||
int port_fd_array[port_fd_count];
|
||
port = [ports objectAtIndex: i];
|
||
if ([port respondsTo: @selector(getFds:count:)])
|
||
[port getFds: port_fd_array count: &port_fd_count];
|
||
if (debug_run_loop)
|
||
printf("\tRunLoop listening to %d sockets\n", port_fd_count);
|
||
while (port_fd_count--)
|
||
{
|
||
FD_SET (port_fd_array[port_fd_count], &fds);
|
||
NSMapInsert (fd_2_object,
|
||
(void*)port_fd_array[port_fd_count], port);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Wait for incoming data, listening to the file descriptors in _FDS. */
|
||
read_fds = fds;
|
||
exception_fds = fds;
|
||
select_return = select (FD_SETSIZE, &read_fds, NULL, &exception_fds,
|
||
select_timeout);
|
||
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop select returned %d\n", select_return);
|
||
|
||
if (select_return < 0)
|
||
{
|
||
/* Some exceptional condition happened. */
|
||
/* xxx We can do something with exception_fds, instead of
|
||
aborting here. */
|
||
perror ("[TcpInPort receivePacketWithTimeout:] select()");
|
||
abort ();
|
||
}
|
||
else if (select_return == 0)
|
||
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, &read_fds))
|
||
{
|
||
id fd_object = (id) NSMapGet (fd_2_object, (void*)fd_index);
|
||
assert (fd_object);
|
||
[fd_object readyForReadingOnFileDescriptor: fd_index];
|
||
}
|
||
|
||
/* Clean up before returning. */
|
||
NSFreeMapTable (fd_2_object);
|
||
_current_mode = saved_mode;
|
||
}
|
||
|
||
|
||
/* Running the run loop once through for timers and input listening. */
|
||
|
||
- (BOOL) runOnceBeforeDate: date forMode: (id <String>)mode
|
||
{
|
||
id d;
|
||
|
||
/* If DATE is already later than now, just return. */
|
||
if ([date timeIntervalSinceNow] < 0)
|
||
{
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop run mode before date already past\n");
|
||
return NO;
|
||
}
|
||
|
||
/* Find out how long we can wait; and fire timers that are ready. */
|
||
d = [self limitDateForMode: mode];
|
||
if (!d)
|
||
d = date;
|
||
|
||
/* Wait, listening to our input sources. */
|
||
[self acceptInputForMode: mode
|
||
beforeDate: d];
|
||
|
||
/* xxx Is this where we detect if the RunLoop is idle, and whether
|
||
we should dispatch the notifications from NotificationQueue's
|
||
idle queue? */
|
||
|
||
return YES;
|
||
}
|
||
|
||
- (BOOL) runOnceBeforeDate: date
|
||
{
|
||
return [self runOnceBeforeDate: date forMode: _current_mode];
|
||
}
|
||
|
||
|
||
/* Running the run loop multiple times through. */
|
||
|
||
- (void) runUntilDate: date forMode: (id <String>)mode
|
||
{
|
||
volatile double ti;
|
||
|
||
ti = [date timeIntervalSinceNow];
|
||
/* Positive values are in the future. */
|
||
while (ti > 0)
|
||
{
|
||
id arp = [NSAutoreleasePool new];
|
||
if (debug_run_loop)
|
||
printf ("\tRunLoop run until date %f seconds from now\n", ti);
|
||
[self runOnceBeforeDate: date forMode: mode];
|
||
[arp release];
|
||
ti = [date timeIntervalSinceNow];
|
||
}
|
||
}
|
||
|
||
- (void) runUntilDate: date
|
||
{
|
||
[self runUntilDate: date forMode: _current_mode];
|
||
}
|
||
|
||
- (void) run
|
||
{
|
||
[self runUntilDate: [NSDate distantFuture]];
|
||
}
|
||
|
||
|
||
/* Class methods that send messages to the current instance. */
|
||
|
||
+ (void) run
|
||
{
|
||
assert (current_run_loop);
|
||
[current_run_loop run];
|
||
}
|
||
|
||
+ (void) runUntilDate: date
|
||
{
|
||
assert (current_run_loop);
|
||
[current_run_loop runUntilDate: date];
|
||
}
|
||
|
||
+ (void) runUntilDate: date forMode: (id <String>)mode
|
||
{
|
||
assert (current_run_loop);
|
||
[current_run_loop runUntilDate: date forMode: mode];
|
||
}
|
||
|
||
+ (BOOL) runOnceBeforeDate: date
|
||
{
|
||
return [current_run_loop runOnceBeforeDate: date];
|
||
}
|
||
|
||
+ (BOOL) runOnceBeforeDate: date forMode: (id <String>)mode
|
||
{
|
||
return [current_run_loop runOnceBeforeDate: date forMode: mode];
|
||
}
|
||
|
||
+ currentInstance
|
||
{
|
||
assert (current_run_loop);
|
||
return current_run_loop;
|
||
}
|
||
|
||
+ (id <String>) currentMode
|
||
{
|
||
return [current_run_loop currentMode];
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
/* RunLoop mode strings. */
|
||
|
||
id RunLoopDefaultMode = @"RunLoopDefaultMode";
|
||
|
||
|
||
/* NSObject method additions. */
|
||
|
||
@implementation NSObject (PerformingAfterDelay)
|
||
|
||
- (void) performSelector: (SEL)sel afterDelay: (NSTimeInterval)delay
|
||
{
|
||
[self notImplemented: _cmd];
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
#if 0
|
||
- getNotificationWithName: (id <String>)name
|
||
object: object
|
||
inMode: (id <String>)mode
|
||
beforeDate: date
|
||
{
|
||
/* See if any timers should fire, and fire them. */
|
||
|
||
/* Figure out how long we can listen to file descriptors before, either,
|
||
we need to fire another timer, or we need to return before DATE. */
|
||
|
||
/* Wait, listening to the file descriptors. */
|
||
|
||
/* Process active file descriptors. */
|
||
|
||
/* Is it time to return? If not, go back and check timers,
|
||
otherwise return. */
|
||
}
|
||
|
||
/* Some alternate names */
|
||
- waitForNotificationWithName: (id <String>)name
|
||
object: object
|
||
inMode: (id <String>)mode
|
||
untilDate: date
|
||
{
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
/* The old alternate names */
|
||
- (void) makeNotificationsForFileDescriptor: (int)fd
|
||
forMode: (id <String>)mode
|
||
name: (id <String>)name
|
||
object: object
|
||
postingTo: (id <NotificationPosting>)poster
|
||
postingStyle: style
|
||
- (void) addFileDescriptor: (int)fd
|
||
forMode: (id <String>)mode
|
||
postingWithName: (id <String>)name
|
||
object: object;
|
||
- (void) addFileDescriptor: (int)fd
|
||
withAttender: (id <FileDescriptorAttending>)object
|
||
- (void) addObserver: observer
|
||
selector: (SEL)
|
||
ofName: fileDescriptorString
|
||
withAttender: (id <FileDescriptorAttending>)object
|
||
|
||
#endif
|