2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "WORunLoop.h"
26 #if !LIB_FOUNDATION_LIBRARY && !APPLE_Foundation_LIBRARY && !NeXT_Foundation_LIBRARY
28 #ifndef CREATE_AUTORELEASE_POOL
29 # define CREATE_AUTORELEASE_POOL(pool) \
30 id pool = [[NSAutoreleasePool alloc] init]
33 #include <sys/types.h>
34 #include <sys/errno.h>
37 #include <sys/time.h> /* for struct timeval */
42 #include <sys/select.h>
44 #include "WORunLoop.h"
45 #import <Foundation/Foundation.h>
47 #if NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY
48 # include <FoundationExt/objc-runtime.h>
50 # include <extensions/objc-runtime.h>
54 #warning breaks AppKit, should *extend* NSRunLoop on MacOSX
55 @implementation NSRunLoop(Override)
56 + (NSRunLoop *)currentRunLoop {
57 return [WORunLoop currentRunLoop];
64 NSPosixNoActivity = 0,
65 NSPosixReadableActivity = 1,
66 NSPosixWritableActivity = 2,
67 NSPosixExceptionalActivity = 4
68 } NSPosixFileActivities;
71 NSString* NSFileObjectBecameActiveNotificationName =
72 @"NSFileObjectBecameActiveNotificationName";
74 static char *activityDesc[8] = {
85 @interface WORunLoopFileObjectInfo : NSObject
88 NSPosixFileActivities watchedActivities;
92 - (id)initWithFileObject:(id)_fileObject
93 activities:(NSPosixFileActivities)_activities;
96 - (int)fileDescriptor;
97 - (NSPosixFileActivities)watchedActivities;
99 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
103 @implementation WORunLoopFileObjectInfo
105 - (id)initWithFileObject:(id)_fileObject
106 activities:(NSPosixFileActivities)_activities
108 self->fileObject = RETAIN(_fileObject);
109 self->watchedActivities = _activities;
110 self->canCheckAlive = [_fileObject respondsToSelector:@selector(isAlive)];
115 [self errorWithFormat:@"do not use init with WORunLoopFileObjectInfo .."];
122 RELEASE(self->fileObject); self->fileObject = nil;
126 - (BOOL)isEqual:(WORunLoopFileObjectInfo*)otherInfo
128 return [self->fileObject isEqual:otherInfo->fileObject];
132 return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
134 - (int)fileDescriptor
136 return [self->fileObject fileDescriptor];
139 - (NSPosixFileActivities)watchedActivities
141 return self->watchedActivities;
144 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
146 //NSLog(@"FileObject %@ was active ..", self->fileObject);
148 [[NSNotificationCenter defaultCenter]
149 postNotificationName:
150 NSFileObjectBecameActiveNotificationName
151 object:self->fileObject];
154 - (NSString *)description
156 return [NSString stringWithFormat:
157 @"<%@[0x%08X]: object=%@ actitivity=%s>",
158 NSStringFromClass([self class]), self,
160 activityDesc[self->watchedActivities]
166 @interface WORunLoopTimerInfo : NSObject
172 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
173 - (void)recomputeFireDate;
174 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject;
179 @implementation WORunLoopTimerInfo
181 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)aTimer
183 WORunLoopTimerInfo* info = [self new];
185 info->timer = RETAIN(aTimer);
186 info->fireDate = RETAIN([aTimer fireDate]);
187 return AUTORELEASE(info);
197 - (void)recomputeFireDate
199 if ([timer isValid]) {
200 id tmp = [timer fireDate];
201 ASSIGN(fireDate, tmp);
205 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject
207 return [fireDate compare:anObject->fireDate];
210 - (NSTimer*)timer { return timer; }
211 - (NSDate*)fireDate { return fireDate; }
215 @interface WORunLoopActionHolder : NSObject
222 + objectWithTarget:(id)target
223 argument:(id)argument
226 - (BOOL)isEqual:(id)anotherHolder;
230 @implementation WORunLoopActionHolder
232 + objectWithTarget:(id)_target
233 argument:(id)_argument
234 selector:(SEL)_action
237 WORunLoopActionHolder* holder = AUTORELEASE([self alloc]);
239 holder->target = RETAIN(_target);
240 holder->argument = RETAIN(_argument);
241 holder->action = _action;
242 holder->order = _order;
249 return [(NSObject*)target hash];
252 - (BOOL)isEqual:(WORunLoopActionHolder*)anotherHolder
254 return [target isEqual:anotherHolder->target]
255 && [argument isEqual:anotherHolder->argument]
256 && SEL_EQ(action, anotherHolder->action);
261 [target performSelector:action withObject:argument];
264 - (NSComparisonResult)compare:(WORunLoopActionHolder*)anotherHolder
266 return order - anotherHolder->order;
272 @interface WORunLoopInputManager : NSObject
274 NSMutableArray* fileObjects;
275 NSMutableArray* timers;
276 NSMutableArray* otherOperations;
279 - (void)addFileObject:(id)_fileObject
280 activities:(NSPosixFileActivities)_activities;
281 - (void)removeFileObject:(id)_fileObject;
283 - (void)addTimer:(NSTimer*)aTimer;
285 - (NSMutableArray*)fileObjects;
286 - (NSMutableArray*)timers;
288 - (void)addOperation:(WORunLoopActionHolder*)holder;
289 - (void)removeOperation:(WORunLoopActionHolder*)holder;
290 - (void)performAdditionalOperations;
294 @implementation WORunLoopInputManager
298 fileObjects = [NSMutableArray new];
299 timers = [NSMutableArray new];
300 otherOperations = [NSMutableArray new];
306 RELEASE(fileObjects);
308 RELEASE(otherOperations);
312 - (void)addFileObject:(id)_fileObject
313 activities:(NSPosixFileActivities)_activities
315 WORunLoopFileObjectInfo *info = nil;
316 //NSAssert(_activities, @"no activity to watch ?!");
317 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
318 initWithFileObject:_fileObject
319 activities:_activities];
320 [self->fileObjects addObject:info];
321 //NSLog(@"file objects now: %@", self->fileObjects);
322 RELEASE(info); info = nil;
324 - (void)removeFileObject:(id)_fileObject
326 WORunLoopFileObjectInfo *info = nil;
327 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
328 initWithFileObject:_fileObject
330 [self->fileObjects removeObject:info];
331 //NSLog(@"file objects now: %@", self->fileObjects);
332 RELEASE(info); info = nil;
335 - (void)addTimer:(NSTimer*)aTimer
337 [timers addObject:[WORunLoopTimerInfo infoWithTimer:aTimer]];
340 - (void)addOperation:(WORunLoopActionHolder*)holder
342 [otherOperations addObject:holder];
343 [otherOperations sortUsingSelector:@selector(compare:)];
346 - (void)removeOperation:(WORunLoopActionHolder*)holder
348 [otherOperations removeObject:holder];
351 - (void)performAdditionalOperations
353 [otherOperations makeObjectsPerformSelector:@selector(execute)];
356 - (NSMutableArray*)fileObjects { return fileObjects; }
357 - (NSMutableArray*)timers { return timers; }
359 @end /* WORunLoopInputManager */
361 @implementation WORunLoop
364 static WORunLoop *currentRunLoop = nil;
365 static BOOL taskIsMultithreaded = NO;
367 + (void)error:(id)_o {
372 + (NSRunLoop *)currentRunLoop
374 if (taskIsMultithreaded) {
375 NSLog(@"WORunLoop does not work multithreaded, exit ..");
380 currentRunLoop = [[self alloc] init];
381 return currentRunLoop;
386 self->inputsForMode = [[NSMutableDictionary allocWithZone:[self zone]] init];
387 self->mode = RETAIN(NSDefaultRunLoopMode);
392 RELEASE(self->inputsForMode);
397 - (NSString *)currentMode {
401 static inline WORunLoopInputManager*
402 _getInputManager(WORunLoop *self, NSString *_mode)
404 WORunLoopInputManager* inputManager;
406 inputManager = [self->inputsForMode objectForKey:_mode];
407 if (inputManager == nil) {
408 inputManager = [WORunLoopInputManager new];
409 [self->inputsForMode setObject:inputManager forKey:_mode];
410 RELEASE(inputManager);
415 static int compare_fire_dates(id timer1, id timer2, void* userData)
417 return [[timer1 fireDate] compare:[timer2 fireDate]];
420 - (NSDate*)limitDateForMode:(NSString*)aMode
422 NSString *format = @"%s: Caught exception %@ with reason %@ ";
423 NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
424 volatile int i, count;
425 NSMutableArray *copyOfTimers;
429 /* Remove invalid timers */
430 for(count = [timers count], i = count - 1; i >= 0; i--)
431 if(![[[timers objectAtIndex:i] timer] isValid]) {
432 [timers removeObjectAtIndex:i];
436 /* Currently only timers have limit dates associated with them */
440 copyOfTimers = [timers mutableCopy];
442 /* Sort the timers based on their fire date */
443 [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
445 /* Fire all the timers with their fire date expired */
446 for(i = 0; i < count; i++) {
447 WORunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
448 NSDate* fireDate = [timerInfo fireDate];
449 NSDate* currentDate = [NSDate date];
451 if([fireDate earlierDate:currentDate] == fireDate
452 || [fireDate isEqualToDate:currentDate]) {
453 NSTimer* timer = [timerInfo timer];
457 NSLog(format, __PRETTY_FUNCTION__,
458 [localException name], [localException reason]);
462 #warning no repeated timers !
469 RELEASE(copyOfTimers);
471 /* Recompute the fire dates for this cycle */
472 [timers makeObjectsPerformSelector:@selector(recomputeFireDate)];
474 /* Sort the timers based on their fire date */
475 [timers sortUsingFunction:compare_fire_dates context:NULL];
477 return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
480 - (void)addTimer:(NSTimer*)aTimer
481 forMode:(NSString*)aMode
483 [_getInputManager(self, aMode) addTimer:aTimer];
486 - (BOOL)runMode:(NSString*)aMode
487 beforeDate:(NSDate*)limitDate
489 id inputManager, fileObjects;
493 /* Retain the limitDate so it doesn't get released by limitDateForMode:
494 if it fires a timer that has as fireDate the limitDate.
495 (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
497 (void)RETAIN(limitDate);
499 inputManager = [inputsForMode objectForKey:aMode];
500 timers = [inputManager timers];
501 fileObjects = [inputManager fileObjects];
503 if (([timers count] != 0) || ([fileObjects count] != 0)) {
504 CREATE_AUTORELEASE_POOL(pool);
506 date = [self limitDateForMode:aMode];
507 date = date ? [date earlierDate:limitDate] : limitDate;
508 [self acceptInputForMode:aMode beforeDate:date];
518 /* Runs the loop until limitDate or until the earliest limit date for input
519 sources in the specified mode. */
520 - (void)acceptInputForMode:(NSString*)aMode
521 beforeDate:(NSDate*)limitDate
523 id inputManager, fileObjects;
524 struct timeval tp = { 0, 0 };
525 struct timeval* timeout = NULL;
526 NSTimeInterval delay = 0;
527 fd_set readSet, writeSet, exceptionsSet;
528 volatile int i, r, count;
532 if(limitDate == nil) // delay = 0
533 limitDate = [NSDate distantFuture];
535 delay = [limitDate timeIntervalSinceNow];
536 /* delay > 0 means a future date */
538 /* If limitDate is in the past return */
543 inputManager = [inputsForMode objectForKey:aMode];
544 fileObjects = [inputManager fileObjects];
546 /* Compute the timeout for select */
547 if([limitDate isEqual:[NSDate distantFuture]])
551 tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
559 FD_ZERO(&exceptionsSet);
562 count = [fileObjects count];
563 for (i = 0; i < count; i++) {
564 WORunLoopFileObjectInfo *info;
565 NSPosixFileActivities fileActivity;
568 info = [fileObjects objectAtIndex:i];
572 fileActivity = [info watchedActivities];
573 fd = [info fileDescriptor];
576 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
577 if (fd >= FD_SETSIZE) {
578 NSLog(@"%s: fd %i of %@ exceeds select size %i",
580 fd, info, FD_SETSIZE);
583 #endif /* !defined(__MINGW32__) */
585 //NSLog(@"registering activity %s for fd %i ..",
586 // activityDesc[fileActivity], fd);
588 if (fileActivity & NSPosixReadableActivity)
589 FD_SET(fd, &readSet);
590 if (fileActivity & NSPosixWritableActivity)
591 FD_SET(fd, &writeSet);
592 if (fileActivity & NSPosixExceptionalActivity)
593 FD_SET(fd, &exceptionsSet);
597 // ???: errno = 0; What is this good for ?
598 r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
600 if (errno == EINTR) {
601 /* Interrupt occured; break the loop to give a chance to
602 UnixSignalHandler to handle the signals. */
607 NSLog(@"%s: select() error: '%s'",
608 __PRETTY_FUNCTION__, strerror (errno));
617 NSString* format = @"%s: Caught exception %@ with reason %@ ";
619 *(&fileObjectsCopy) = nil;
622 // made copy, so that modifications in the delegate don't
624 fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
625 count = [fileObjectsCopy count];
627 for (i = 0; (i < count) && (r > 0); i++) {
628 WORunLoopFileObjectInfo *info;
629 NSPosixFileActivities activity = 0;
632 info = [fileObjectsCopy objectAtIndex:i];
633 fd = [info fileDescriptor];
636 //NSLog(@"checking activity for %i info %@ ..", fd, info);
638 if (FD_ISSET(fd, &readSet)) {
639 activity |= NSPosixReadableActivity;
642 if (FD_ISSET(fd, &writeSet)) {
643 activity |= NSPosixWritableActivity;
646 if (FD_ISSET(fd, &exceptionsSet)) {
647 activity |= NSPosixExceptionalActivity;
652 [info activity:activity onDescriptor:fd];
656 [self warnWithFormat:@"could not resolve all activities (%i) ..",
661 [self errorWithFormat:format, __PRETTY_FUNCTION__,
662 [localException name], [localException reason]];
666 RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
669 [inputManager performAdditionalOperations];
670 #if !NeXT_Foundation_LIBRARY
671 [NSNotificationQueue runLoopASAP];
675 - (void)runUntilDate:(NSDate*)limitDate
677 BOOL shouldContinue = YES;
680 limitDate = [NSDate distantFuture];
682 /* If limitDate is in the past return */
683 if([limitDate timeIntervalSinceNow] < 0)
687 while (shouldContinue) {
688 CREATE_AUTORELEASE_POOL(pool);
690 if ([limitDate laterDate:[NSDate date]] == limitDate) {
691 if([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
702 [self runUntilDate:[NSDate distantFuture]];
705 - (void)performSelector:(SEL)aSelector
707 argument:(id)anArgument
708 order:(unsigned)order
709 modes:(NSArray*)modes
711 id holder = [WORunLoopActionHolder objectWithTarget:target
715 int i, count = [modes count];
717 for (i = 0; i < count; i++)
718 [[inputsForMode objectForKey:[modes objectAtIndex:i]]
719 addOperation:holder];
722 - (void)cancelPerformSelector:(SEL)aSelector
724 argument:(id)anArgument
726 id holder = [WORunLoopActionHolder objectWithTarget:target
730 id enumerator = [inputsForMode keyEnumerator];
733 while ((aMode = [enumerator nextObject]))
734 [[inputsForMode objectForKey:aMode] removeOperation:holder];
737 /* Monitoring file objects */
739 - (void)addFileObject:(id)_fileObject
740 activities:(NSPosixFileActivities)_activities
741 forMode:(NSString *)_mode
743 [_getInputManager(self, _mode) addFileObject:_fileObject
744 activities:_activities];
747 - (void)removeFileObject:(id)_fileObject
748 forMode:(NSString *)_mode
750 [_getInputManager(self, _mode) removeFileObject:_fileObject];
755 #endif /* !LIB_FOUNDATION_LIBRARY */