diff --git a/Source/RunLoop.m b/Source/RunLoop.m index e0956fc86..65c9436c6 100644 --- a/Source/RunLoop.m +++ b/Source/RunLoop.m @@ -21,6 +21,37 @@ 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. + + Distinguishing between different MODES is not currently implemented. + 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 #include #include @@ -37,9 +68,6 @@ Just define it to use memset(). */ #define bzero(PTR, LEN) memset (PTR, 0, LEN) -/* Alternate names: InputDemuxer, InputListener - Alternate names for Notification classes: Dispatcher, EventDistributor, */ - static int debug_run_loop = 1; @implementation RunLoop @@ -55,99 +83,180 @@ static RunLoop *current_run_loop; /* This is the designated initializer. */ - init { - FD_ZERO (&_fds); - _fd_2_object = NSCreateMapTable (NSIntMapKeyCallBacks, - NSObjectMapValueCallBacks, 0); - _fd_objects = [Bag new]; - _timers = [Heap new]; - _dispatcher = [NotificationDispatcher defaultInstance]; + [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; } -- notificationDispatcher +- (id ) currentMode { - return _dispatcher; -} - -- (void) setNotificationDispatcher: d -{ - _dispatcher = d; + return _current_mode; } -/* xxx This is similar to [NotificationDispatcher addObserver...] - Perhaps NotificationDispatcher and InputDemuxer will be merged. */ +/* Adding and removing file descriptors. */ - (void) addFileDescriptor: (int)fd - invocation: invocation + object: listener forMode: (id )mode { - /* xxx But actually we should let it be added multiple times, - and keep count. (?) */ - if (debug_run_loop) - printf ("\tRunLoop adding fd %d\n", fd); - assert (!NSMapGet (_fd_2_object, (void*)fd)); - NSMapInsert (_fd_2_object, (void*)fd, invocation); - FD_SET (fd, &_fds); + /* 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 )mode { - if (debug_run_loop) - printf ("\tRunLoop removing fd %d\n", fd); - assert (NSMapGet (_fd_2_object, (void*)fd)); - NSMapRemove (_fd_2_object, (void*)fd); - FD_CLR (fd, &_fds); +#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 )mode +{ + /* xxx Perhaps this should be a Bag instead. */ + 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 )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 )mode { - assert (_timers); - [_timers addObjectIfAbsent: timer]; + 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]; } -/* Running the loop. */ +/* Fire appropriate timers. */ - limitDateForMode: (id )mode { + /* Linux doesn't always return double from methods, even though + I'm using -lieee. */ #if 1 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]) + while ((min_timer = [timers minObject]) && ([[min_timer fireDate] timeIntervalSinceNow] > 0)) { - [_timers removeFirstObject]; + [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]; + [timers addObject: min_timer]; } } if (debug_run_loop) printf ("\tlimit date %f\n", [[min_timer fireDate] timeIntervalSinceReferenceDate]); + + _current_mode = saved_mode; return [min_timer fireDate]; #endif } + +/* Listen to input sources */ + - (void) acceptInputForMode: (id )mode beforeDate: limit_date { NSTimeInterval ti; struct timeval timeout; void *select_timeout; - fd_set fds_copy; + 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; + 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) @@ -180,37 +289,93 @@ static RunLoop *current_run_loop; select_timeout = NULL; } - fds_copy = _fds; + /* 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); - /* Wait for incoming data, listening to the file descriptors in FDS. */ - select_return = select (FD_SETSIZE, &fds_copy, NULL, NULL, select_timeout); + + /* 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]; + 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 ("\tselect 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; - invoke the corresponding invocation for each of the ready ones. */ + 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, &fds_copy)) + if (FD_ISSET (fd_index, &read_fds)) { - id fd_invocation = (id) NSMapGet (_fd_2_object, (void*)fd_index); - assert (fd_invocation); - [fd_invocation - invokeWithObject: [NSNumber numberWithInt: fd_index]]; - /* xxx We can get rid of this NSNumber autorelease later. */ + 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; } -- (BOOL) runMode: (id )mode - beforeDate: date + +/* Running the run loop once through for timers and input listening. */ + +- (BOOL) runOnceBeforeDate: date forMode: (id )mode { id d; @@ -223,23 +388,26 @@ static RunLoop *current_run_loop; } /* Find out how long we can wait; and fire timers that are ready. */ - d = [self limitDateForMode: nil]; + d = [self limitDateForMode: mode]; if (!d) d = date; /* Wait, listening to our input sources. */ [self acceptInputForMode: mode beforeDate: d]; + return YES; } - (BOOL) runOnceBeforeDate: date - forMode: (id )mode { - return [self runMode: mode beforeDate: date]; + return [self runOnceBeforeDate: date forMode: _current_mode]; } -- (void) runUntilDate: date + +/* Running the run loop multiple times through. */ + +- (void) runUntilDate: date forMode: (id )mode { volatile double ti; @@ -250,17 +418,23 @@ static RunLoop *current_run_loop; id arp = [NSAutoreleasePool new]; if (debug_run_loop) printf ("\trun until date %f seconds from now\n", ti); - [self runMode: nil beforeDate: date]; + [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 @@ -275,21 +449,48 @@ static RunLoop *current_run_loop; [current_run_loop runUntilDate: date]; } ++ (void) runUntilDate: date forMode: (id )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 )mode +{ + return [current_run_loop runOnceBeforeDate: date forMode: mode]; +} + + currentInstance { assert (current_run_loop); return current_run_loop; } ++ (id ) 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