ftp.nice.ch/Attic/openStep/implementation/gnustep/sources/gstep-base-0.2.7.tgz#/gstep-base-0.2.7/src/RunLoop.m

This is RunLoop.m in view mode; [Download] [Up]

/* 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; // xxx #define this constant
	    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)
    {
      NSFreeMapTable (fd_2_object);
      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

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.