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"
25 // TODO: not used anymore?
28 #if !LIB_FOUNDATION_LIBRARY && !APPLE_Foundation_LIBRARY && !NeXT_Foundation_LIBRARY
30 #ifndef CREATE_AUTORELEASE_POOL
31 # define CREATE_AUTORELEASE_POOL(pool) \
32 id pool = [[NSAutoreleasePool alloc] init]
35 #include <sys/types.h>
36 #include <sys/errno.h>
39 #include <sys/time.h> /* for struct timeval */
44 #include <sys/select.h>
46 #include "WORunLoop.h"
47 #import <Foundation/Foundation.h>
49 #if NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY
50 # include <FoundationExt/objc-runtime.h>
52 # include <extensions/objc-runtime.h>
56 #warning breaks AppKit, should *extend* NSRunLoop on MacOSX
57 @implementation NSRunLoop(Override)
58 + (NSRunLoop *)currentRunLoop {
59 return [WORunLoop currentRunLoop];
66 NSPosixNoActivity = 0,
67 NSPosixReadableActivity = 1,
68 NSPosixWritableActivity = 2,
69 NSPosixExceptionalActivity = 4
70 } NSPosixFileActivities;
73 NSString* NSFileObjectBecameActiveNotificationName =
74 @"NSFileObjectBecameActiveNotificationName";
76 static char *activityDesc[8] = {
87 @interface WORunLoopFileObjectInfo : NSObject
90 NSPosixFileActivities watchedActivities;
94 - (id)initWithFileObject:(id)_fileObject
95 activities:(NSPosixFileActivities)_activities;
98 - (int)fileDescriptor;
99 - (NSPosixFileActivities)watchedActivities;
101 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
105 @implementation WORunLoopFileObjectInfo
107 - (id)initWithFileObject:(id)_fileObject
108 activities:(NSPosixFileActivities)_activities
110 self->fileObject = RETAIN(_fileObject);
111 self->watchedActivities = _activities;
112 self->canCheckAlive = [_fileObject respondsToSelector:@selector(isAlive)];
117 [self errorWithFormat:@"do not use init with WORunLoopFileObjectInfo .."];
124 RELEASE(self->fileObject); self->fileObject = nil;
128 - (BOOL)isEqual:(WORunLoopFileObjectInfo*)otherInfo
130 return [self->fileObject isEqual:otherInfo->fileObject];
134 return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
136 - (int)fileDescriptor
138 return [self->fileObject fileDescriptor];
141 - (NSPosixFileActivities)watchedActivities
143 return self->watchedActivities;
146 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
148 //NSLog(@"FileObject %@ was active ..", self->fileObject);
150 [[NSNotificationCenter defaultCenter]
151 postNotificationName:
152 NSFileObjectBecameActiveNotificationName
153 object:self->fileObject];
156 - (NSString *)description
158 return [NSString stringWithFormat:
159 @"<%@[0x%p]: object=%@ actitivity=%s>",
160 NSStringFromClass([self class]), self,
162 activityDesc[self->watchedActivities]
168 @interface WORunLoopTimerInfo : NSObject
174 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
175 - (void)recomputeFireDate;
176 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject;
181 @implementation WORunLoopTimerInfo
183 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)aTimer
185 WORunLoopTimerInfo* info = [self new];
187 info->timer = RETAIN(aTimer);
188 info->fireDate = RETAIN([aTimer fireDate]);
189 return AUTORELEASE(info);
199 - (void)recomputeFireDate
201 if ([timer isValid]) {
202 id tmp = [timer fireDate];
203 ASSIGN(fireDate, tmp);
207 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject
209 return [fireDate compare:anObject->fireDate];
212 - (NSTimer*)timer { return timer; }
213 - (NSDate*)fireDate { return fireDate; }
217 @interface WORunLoopActionHolder : NSObject
224 + objectWithTarget:(id)target
225 argument:(id)argument
228 - (BOOL)isEqual:(id)anotherHolder;
232 @implementation WORunLoopActionHolder
234 + objectWithTarget:(id)_target
235 argument:(id)_argument
236 selector:(SEL)_action
239 WORunLoopActionHolder* holder = AUTORELEASE([self alloc]);
241 holder->target = RETAIN(_target);
242 holder->argument = RETAIN(_argument);
243 holder->action = _action;
244 holder->order = _order;
251 return [(NSObject*)target hash];
254 - (BOOL)isEqual:(WORunLoopActionHolder*)anotherHolder
256 return [target isEqual:anotherHolder->target]
257 && [argument isEqual:anotherHolder->argument]
258 && SEL_EQ(action, anotherHolder->action);
263 [target performSelector:action withObject:argument];
266 - (NSComparisonResult)compare:(WORunLoopActionHolder*)anotherHolder
268 return order - anotherHolder->order;
274 @interface WORunLoopInputManager : NSObject
276 NSMutableArray* fileObjects;
277 NSMutableArray* timers;
278 NSMutableArray* otherOperations;
281 - (void)addFileObject:(id)_fileObject
282 activities:(NSPosixFileActivities)_activities;
283 - (void)removeFileObject:(id)_fileObject;
285 - (void)addTimer:(NSTimer*)aTimer;
287 - (NSMutableArray*)fileObjects;
288 - (NSMutableArray*)timers;
290 - (void)addOperation:(WORunLoopActionHolder*)holder;
291 - (void)removeOperation:(WORunLoopActionHolder*)holder;
292 - (void)performAdditionalOperations;
296 @implementation WORunLoopInputManager
300 fileObjects = [NSMutableArray new];
301 timers = [NSMutableArray new];
302 otherOperations = [NSMutableArray new];
308 RELEASE(fileObjects);
310 RELEASE(otherOperations);
314 - (void)addFileObject:(id)_fileObject
315 activities:(NSPosixFileActivities)_activities
317 WORunLoopFileObjectInfo *info = nil;
318 //NSAssert(_activities, @"no activity to watch ?!");
319 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
320 initWithFileObject:_fileObject
321 activities:_activities];
322 [self->fileObjects addObject:info];
323 //NSLog(@"file objects now: %@", self->fileObjects);
324 RELEASE(info); info = nil;
326 - (void)removeFileObject:(id)_fileObject
328 WORunLoopFileObjectInfo *info = nil;
329 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
330 initWithFileObject:_fileObject
332 [self->fileObjects removeObject:info];
333 //NSLog(@"file objects now: %@", self->fileObjects);
334 RELEASE(info); info = nil;
337 - (void)addTimer:(NSTimer*)aTimer
339 [timers addObject:[WORunLoopTimerInfo infoWithTimer:aTimer]];
342 - (void)addOperation:(WORunLoopActionHolder*)holder
344 [otherOperations addObject:holder];
345 [otherOperations sortUsingSelector:@selector(compare:)];
348 - (void)removeOperation:(WORunLoopActionHolder*)holder
350 [otherOperations removeObject:holder];
353 - (void)performAdditionalOperations
355 [otherOperations makeObjectsPerformSelector:@selector(execute)];
358 - (NSMutableArray*)fileObjects { return fileObjects; }
359 - (NSMutableArray*)timers { return timers; }
361 @end /* WORunLoopInputManager */
363 @implementation WORunLoop
366 static WORunLoop *currentRunLoop = nil;
367 static BOOL taskIsMultithreaded = NO;
369 + (void)error:(id)_o {
374 + (NSRunLoop *)currentRunLoop
376 if (taskIsMultithreaded) {
377 NSLog(@"WORunLoop does not work multithreaded, exit ..");
382 currentRunLoop = [[self alloc] init];
383 return currentRunLoop;
388 self->inputsForMode = [[NSMutableDictionary allocWithZone:[self zone]] init];
389 self->mode = RETAIN(NSDefaultRunLoopMode);
394 RELEASE(self->inputsForMode);
399 - (NSString *)currentMode {
403 static inline WORunLoopInputManager*
404 _getInputManager(WORunLoop *self, NSString *_mode)
406 WORunLoopInputManager* inputManager;
408 inputManager = [self->inputsForMode objectForKey:_mode];
409 if (inputManager == nil) {
410 inputManager = [WORunLoopInputManager new];
411 [self->inputsForMode setObject:inputManager forKey:_mode];
412 RELEASE(inputManager);
417 static int compare_fire_dates(id timer1, id timer2, void* userData)
419 return [[timer1 fireDate] compare:[timer2 fireDate]];
422 - (NSDate*)limitDateForMode:(NSString*)aMode
424 NSString *format = @"%s: Caught exception %@ with reason %@ ";
425 NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
426 volatile int i, count;
427 NSMutableArray *copyOfTimers;
431 /* Remove invalid timers */
432 for(count = [timers count], i = count - 1; i >= 0; i--)
433 if(![[[timers objectAtIndex:i] timer] isValid]) {
434 [timers removeObjectAtIndex:i];
438 /* Currently only timers have limit dates associated with them */
442 copyOfTimers = [timers mutableCopy];
444 /* Sort the timers based on their fire date */
445 [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
447 /* Fire all the timers with their fire date expired */
448 for(i = 0; i < count; i++) {
449 WORunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
450 NSDate* fireDate = [timerInfo fireDate];
451 NSDate* currentDate = [NSDate date];
453 if([fireDate earlierDate:currentDate] == fireDate
454 || [fireDate isEqualToDate:currentDate]) {
455 NSTimer* timer = [timerInfo timer];
459 NSLog(format, __PRETTY_FUNCTION__,
460 [localException name], [localException reason]);
464 #warning no repeated timers !
471 RELEASE(copyOfTimers);
473 /* Recompute the fire dates for this cycle */
474 [timers makeObjectsPerformSelector:@selector(recomputeFireDate)];
476 /* Sort the timers based on their fire date */
477 [timers sortUsingFunction:compare_fire_dates context:NULL];
479 return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
482 - (void)addTimer:(NSTimer*)aTimer
483 forMode:(NSString*)aMode
485 [_getInputManager(self, aMode) addTimer:aTimer];
488 - (BOOL)runMode:(NSString*)aMode
489 beforeDate:(NSDate*)limitDate
491 id inputManager, fileObjects;
495 /* Retain the limitDate so it doesn't get released by limitDateForMode:
496 if it fires a timer that has as fireDate the limitDate.
497 (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
499 (void)RETAIN(limitDate);
501 inputManager = [inputsForMode objectForKey:aMode];
502 timers = [inputManager timers];
503 fileObjects = [inputManager fileObjects];
505 if (([timers count] != 0) || ([fileObjects count] != 0)) {
506 CREATE_AUTORELEASE_POOL(pool);
508 date = [self limitDateForMode:aMode];
509 date = date ? [date earlierDate:limitDate] : limitDate;
510 [self acceptInputForMode:aMode beforeDate:date];
520 /* Runs the loop until limitDate or until the earliest limit date for input
521 sources in the specified mode. */
522 - (void)acceptInputForMode:(NSString*)aMode
523 beforeDate:(NSDate*)limitDate
525 id inputManager, fileObjects;
526 struct timeval tp = { 0, 0 };
527 struct timeval* timeout = NULL;
528 NSTimeInterval delay = 0;
529 fd_set readSet, writeSet, exceptionsSet;
530 volatile int i, r, count;
534 if(limitDate == nil) // delay = 0
535 limitDate = [NSDate distantFuture];
537 delay = [limitDate timeIntervalSinceNow];
538 /* delay > 0 means a future date */
540 /* If limitDate is in the past return */
545 inputManager = [inputsForMode objectForKey:aMode];
546 fileObjects = [inputManager fileObjects];
548 /* Compute the timeout for select */
549 if([limitDate isEqual:[NSDate distantFuture]])
553 tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
561 FD_ZERO(&exceptionsSet);
564 count = [fileObjects count];
565 for (i = 0; i < count; i++) {
566 WORunLoopFileObjectInfo *info;
567 NSPosixFileActivities fileActivity;
570 info = [fileObjects objectAtIndex:i];
574 fileActivity = [info watchedActivities];
575 fd = [info fileDescriptor];
578 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
579 if (fd >= FD_SETSIZE) {
580 NSLog(@"%s: fd %i of %@ exceeds select size %i",
582 fd, info, FD_SETSIZE);
585 #endif /* !defined(__MINGW32__) */
587 //NSLog(@"registering activity %s for fd %i ..",
588 // activityDesc[fileActivity], fd);
590 if (fileActivity & NSPosixReadableActivity)
591 FD_SET(fd, &readSet);
592 if (fileActivity & NSPosixWritableActivity)
593 FD_SET(fd, &writeSet);
594 if (fileActivity & NSPosixExceptionalActivity)
595 FD_SET(fd, &exceptionsSet);
599 // ???: errno = 0; What is this good for ?
600 r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
602 if (errno == EINTR) {
603 /* Interrupt occured; break the loop to give a chance to
604 UnixSignalHandler to handle the signals. */
609 NSLog(@"%s: select() error: '%s'",
610 __PRETTY_FUNCTION__, strerror (errno));
619 NSString* format = @"%s: Caught exception %@ with reason %@ ";
621 *(&fileObjectsCopy) = nil;
624 // made copy, so that modifications in the delegate don't
626 fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
627 count = [fileObjectsCopy count];
629 for (i = 0; (i < count) && (r > 0); i++) {
630 WORunLoopFileObjectInfo *info;
631 NSPosixFileActivities activity = 0;
634 info = [fileObjectsCopy objectAtIndex:i];
635 fd = [info fileDescriptor];
638 //NSLog(@"checking activity for %i info %@ ..", fd, info);
640 if (FD_ISSET(fd, &readSet)) {
641 activity |= NSPosixReadableActivity;
644 if (FD_ISSET(fd, &writeSet)) {
645 activity |= NSPosixWritableActivity;
648 if (FD_ISSET(fd, &exceptionsSet)) {
649 activity |= NSPosixExceptionalActivity;
654 [info activity:activity onDescriptor:fd];
658 [self warnWithFormat:@"could not resolve all activities (%i) ..",
663 [self errorWithFormat:format, __PRETTY_FUNCTION__,
664 [localException name], [localException reason]];
668 RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
671 [inputManager performAdditionalOperations];
672 #if !NeXT_Foundation_LIBRARY
673 [NSNotificationQueue runLoopASAP];
677 - (void)runUntilDate:(NSDate*)limitDate
679 BOOL shouldContinue = YES;
682 limitDate = [NSDate distantFuture];
684 /* If limitDate is in the past return */
685 if([limitDate timeIntervalSinceNow] < 0)
689 while (shouldContinue) {
690 CREATE_AUTORELEASE_POOL(pool);
692 if ([limitDate laterDate:[NSDate date]] == limitDate) {
693 if([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
704 [self runUntilDate:[NSDate distantFuture]];
707 - (void)performSelector:(SEL)aSelector
709 argument:(id)anArgument
710 order:(unsigned)order
711 modes:(NSArray*)modes
713 id holder = [WORunLoopActionHolder objectWithTarget:target
717 int i, count = [modes count];
719 for (i = 0; i < count; i++)
720 [[inputsForMode objectForKey:[modes objectAtIndex:i]]
721 addOperation:holder];
724 - (void)cancelPerformSelector:(SEL)aSelector
726 argument:(id)anArgument
728 id holder = [WORunLoopActionHolder objectWithTarget:target
732 id enumerator = [inputsForMode keyEnumerator];
735 while ((aMode = [enumerator nextObject]))
736 [[inputsForMode objectForKey:aMode] removeOperation:holder];
739 /* Monitoring file objects */
741 - (void)addFileObject:(id)_fileObject
742 activities:(NSPosixFileActivities)_activities
743 forMode:(NSString *)_mode
745 [_getInputManager(self, _mode) addFileObject:_fileObject
746 activities:_activities];
749 - (void)removeFileObject:(id)_fileObject
750 forMode:(NSString *)_mode
752 [_getInputManager(self, _mode) removeFileObject:_fileObject];
757 #endif /* !LIB_FOUNDATION_LIBRARY */