2 Copyright (C) 2000-2003 SKYRIX Software AG
4 This file is part of OGo
6 OGo 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 OGo 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 OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "WORunLoop.h"
27 #if !LIB_FOUNDATION_LIBRARY && !APPLE_Foundation_LIBRARY && !NeXT_Foundation_LIBRARY
29 #ifndef CREATE_AUTORELEASE_POOL
30 # define CREATE_AUTORELEASE_POOL(pool) \
31 id pool = [[NSAutoreleasePool alloc] init]
34 #include <sys/types.h>
35 #include <sys/errno.h>
38 #include <sys/time.h> /* for struct timeval */
43 #include <sys/select.h>
45 #include "WORunLoop.h"
46 #import <Foundation/Foundation.h>
48 #if NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY
49 # include <FoundationExt/objc-runtime.h>
51 # include <extensions/objc-runtime.h>
55 #warning breaks AppKit, should *extend* NSRunLoop on MacOSX
56 @implementation NSRunLoop(Override)
57 + (NSRunLoop *)currentRunLoop {
58 return [WORunLoop currentRunLoop];
65 NSPosixNoActivity = 0,
66 NSPosixReadableActivity = 1,
67 NSPosixWritableActivity = 2,
68 NSPosixExceptionalActivity = 4
69 } NSPosixFileActivities;
72 NSString* NSFileObjectBecameActiveNotificationName =
73 @"NSFileObjectBecameActiveNotificationName";
75 static char *activityDesc[8] = {
86 @interface WORunLoopFileObjectInfo : NSObject
89 NSPosixFileActivities watchedActivities;
93 - (id)initWithFileObject:(id)_fileObject
94 activities:(NSPosixFileActivities)_activities;
97 - (int)fileDescriptor;
98 - (NSPosixFileActivities)watchedActivities;
100 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
104 @implementation WORunLoopFileObjectInfo
106 - (id)initWithFileObject:(id)_fileObject
107 activities:(NSPosixFileActivities)_activities
109 self->fileObject = RETAIN(_fileObject);
110 self->watchedActivities = _activities;
111 self->canCheckAlive = [_fileObject respondsToSelector:@selector(isAlive)];
116 NSLog(@"ERROR: do not use init with WORunLoopFileObjectInfo ..");
123 RELEASE(self->fileObject); self->fileObject = nil;
127 - (BOOL)isEqual:(WORunLoopFileObjectInfo*)otherInfo
129 return [self->fileObject isEqual:otherInfo->fileObject];
133 return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
135 - (int)fileDescriptor
137 return [self->fileObject fileDescriptor];
140 - (NSPosixFileActivities)watchedActivities
142 return self->watchedActivities;
145 - (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
147 //NSLog(@"FileObject %@ was active ..", self->fileObject);
149 [[NSNotificationCenter defaultCenter]
150 postNotificationName:
151 NSFileObjectBecameActiveNotificationName
152 object:self->fileObject];
155 - (NSString *)description
157 return [NSString stringWithFormat:
158 @"<%@[0x%08X]: object=%@ actitivity=%s>",
159 NSStringFromClass([self class]), self,
161 activityDesc[self->watchedActivities]
167 @interface WORunLoopTimerInfo : NSObject
173 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
174 - (void)recomputeFireDate;
175 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject;
180 @implementation WORunLoopTimerInfo
182 + (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)aTimer
184 WORunLoopTimerInfo* info = [self new];
186 info->timer = RETAIN(aTimer);
187 info->fireDate = RETAIN([aTimer fireDate]);
188 return AUTORELEASE(info);
198 - (void)recomputeFireDate
200 if ([timer isValid]) {
201 id tmp = [timer fireDate];
202 ASSIGN(fireDate, tmp);
206 - (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject
208 return [fireDate compare:anObject->fireDate];
211 - (NSTimer*)timer { return timer; }
212 - (NSDate*)fireDate { return fireDate; }
216 @interface WORunLoopActionHolder : NSObject
223 + objectWithTarget:(id)target
224 argument:(id)argument
227 - (BOOL)isEqual:(id)anotherHolder;
231 @implementation WORunLoopActionHolder
233 + objectWithTarget:(id)_target
234 argument:(id)_argument
235 selector:(SEL)_action
238 WORunLoopActionHolder* holder = AUTORELEASE([self alloc]);
240 holder->target = RETAIN(_target);
241 holder->argument = RETAIN(_argument);
242 holder->action = _action;
243 holder->order = _order;
250 return [(NSObject*)target hash];
253 - (BOOL)isEqual:(WORunLoopActionHolder*)anotherHolder
255 return [target isEqual:anotherHolder->target]
256 && [argument isEqual:anotherHolder->argument]
257 && SEL_EQ(action, anotherHolder->action);
262 [target performSelector:action withObject:argument];
265 - (NSComparisonResult)compare:(WORunLoopActionHolder*)anotherHolder
267 return order - anotherHolder->order;
273 @interface WORunLoopInputManager : NSObject
275 NSMutableArray* fileObjects;
276 NSMutableArray* timers;
277 NSMutableArray* otherOperations;
280 - (void)addFileObject:(id)_fileObject
281 activities:(NSPosixFileActivities)_activities;
282 - (void)removeFileObject:(id)_fileObject;
284 - (void)addTimer:(NSTimer*)aTimer;
286 - (NSMutableArray*)fileObjects;
287 - (NSMutableArray*)timers;
289 - (void)addOperation:(WORunLoopActionHolder*)holder;
290 - (void)removeOperation:(WORunLoopActionHolder*)holder;
291 - (void)performAdditionalOperations;
295 @implementation WORunLoopInputManager
299 fileObjects = [NSMutableArray new];
300 timers = [NSMutableArray new];
301 otherOperations = [NSMutableArray new];
307 RELEASE(fileObjects);
309 RELEASE(otherOperations);
313 - (void)addFileObject:(id)_fileObject
314 activities:(NSPosixFileActivities)_activities
316 WORunLoopFileObjectInfo *info = nil;
317 //NSAssert(_activities, @"no activity to watch ?!");
318 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
319 initWithFileObject:_fileObject
320 activities:_activities];
321 [self->fileObjects addObject:info];
322 //NSLog(@"file objects now: %@", self->fileObjects);
323 RELEASE(info); info = nil;
325 - (void)removeFileObject:(id)_fileObject
327 WORunLoopFileObjectInfo *info = nil;
328 info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
329 initWithFileObject:_fileObject
331 [self->fileObjects removeObject:info];
332 //NSLog(@"file objects now: %@", self->fileObjects);
333 RELEASE(info); info = nil;
336 - (void)addTimer:(NSTimer*)aTimer
338 [timers addObject:[WORunLoopTimerInfo infoWithTimer:aTimer]];
341 - (void)addOperation:(WORunLoopActionHolder*)holder
343 [otherOperations addObject:holder];
344 [otherOperations sortUsingSelector:@selector(compare:)];
347 - (void)removeOperation:(WORunLoopActionHolder*)holder
349 [otherOperations removeObject:holder];
352 - (void)performAdditionalOperations
354 [otherOperations makeObjectsPerformSelector:@selector(execute)];
357 - (NSMutableArray*)fileObjects { return fileObjects; }
358 - (NSMutableArray*)timers { return timers; }
360 @end /* WORunLoopInputManager */
362 @implementation WORunLoop
365 static WORunLoop *currentRunLoop = nil;
366 static BOOL taskIsMultithreaded = NO;
368 + (void)error:(id)_o {
373 + (NSRunLoop *)currentRunLoop
375 if (taskIsMultithreaded) {
376 NSLog(@"WORunLoop does not work multithreaded, exit ..");
381 currentRunLoop = [[self alloc] init];
382 return currentRunLoop;
387 self->inputsForMode = [[NSMutableDictionary allocWithZone:[self zone]] init];
388 self->mode = RETAIN(NSDefaultRunLoopMode);
393 RELEASE(self->inputsForMode);
398 - (NSString *)currentMode {
402 static inline WORunLoopInputManager*
403 _getInputManager(WORunLoop *self, NSString *_mode)
405 WORunLoopInputManager* inputManager;
407 inputManager = [self->inputsForMode objectForKey:_mode];
408 if (inputManager == nil) {
409 inputManager = [WORunLoopInputManager new];
410 [self->inputsForMode setObject:inputManager forKey:_mode];
411 RELEASE(inputManager);
416 static int compare_fire_dates(id timer1, id timer2, void* userData)
418 return [[timer1 fireDate] compare:[timer2 fireDate]];
421 - (NSDate*)limitDateForMode:(NSString*)aMode
423 NSString *format = @"%s: Caught exception %@ with reason %@ ";
424 NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
425 volatile int i, count;
426 NSMutableArray *copyOfTimers;
430 /* Remove invalid timers */
431 for(count = [timers count], i = count - 1; i >= 0; i--)
432 if(![[[timers objectAtIndex:i] timer] isValid]) {
433 [timers removeObjectAtIndex:i];
437 /* Currently only timers have limit dates associated with them */
441 copyOfTimers = [timers mutableCopy];
443 /* Sort the timers based on their fire date */
444 [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
446 /* Fire all the timers with their fire date expired */
447 for(i = 0; i < count; i++) {
448 WORunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
449 NSDate* fireDate = [timerInfo fireDate];
450 NSDate* currentDate = [NSDate date];
452 if([fireDate earlierDate:currentDate] == fireDate
453 || [fireDate isEqualToDate:currentDate]) {
454 NSTimer* timer = [timerInfo timer];
458 NSLog(format, __PRETTY_FUNCTION__,
459 [localException name], [localException reason]);
463 #warning no repeated timers !
470 RELEASE(copyOfTimers);
472 /* Recompute the fire dates for this cycle */
473 [timers makeObjectsPerformSelector:@selector(recomputeFireDate)];
475 /* Sort the timers based on their fire date */
476 [timers sortUsingFunction:compare_fire_dates context:NULL];
478 return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
481 - (void)addTimer:(NSTimer*)aTimer
482 forMode:(NSString*)aMode
484 [_getInputManager(self, aMode) addTimer:aTimer];
487 - (BOOL)runMode:(NSString*)aMode
488 beforeDate:(NSDate*)limitDate
490 id inputManager, fileObjects;
494 /* Retain the limitDate so it doesn't get released by limitDateForMode:
495 if it fires a timer that has as fireDate the limitDate.
496 (bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
498 (void)RETAIN(limitDate);
500 inputManager = [inputsForMode objectForKey:aMode];
501 timers = [inputManager timers];
502 fileObjects = [inputManager fileObjects];
504 if (([timers count] != 0) || ([fileObjects count] != 0)) {
505 CREATE_AUTORELEASE_POOL(pool);
507 date = [self limitDateForMode:aMode];
508 date = date ? [date earlierDate:limitDate] : limitDate;
509 [self acceptInputForMode:aMode beforeDate:date];
519 /* Runs the loop until limitDate or until the earliest limit date for input
520 sources in the specified mode. */
521 - (void)acceptInputForMode:(NSString*)aMode
522 beforeDate:(NSDate*)limitDate
524 id inputManager, fileObjects;
525 struct timeval tp = { 0, 0 };
526 struct timeval* timeout = NULL;
527 NSTimeInterval delay = 0;
528 fd_set readSet, writeSet, exceptionsSet;
529 volatile int i, r, count;
533 if(limitDate == nil) // delay = 0
534 limitDate = [NSDate distantFuture];
536 delay = [limitDate timeIntervalSinceNow];
537 /* delay > 0 means a future date */
539 /* If limitDate is in the past return */
544 inputManager = [inputsForMode objectForKey:aMode];
545 fileObjects = [inputManager fileObjects];
547 /* Compute the timeout for select */
548 if([limitDate isEqual:[NSDate distantFuture]])
552 tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
560 FD_ZERO(&exceptionsSet);
563 count = [fileObjects count];
564 for (i = 0; i < count; i++) {
565 WORunLoopFileObjectInfo *info;
566 NSPosixFileActivities fileActivity;
569 info = [fileObjects objectAtIndex:i];
573 fileActivity = [info watchedActivities];
574 fd = [info fileDescriptor];
577 #if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
578 if (fd >= FD_SETSIZE) {
579 NSLog(@"%s: fd %i of %@ exceeds select size %i",
581 fd, info, FD_SETSIZE);
584 #endif /* !defined(__MINGW32__) */
586 //NSLog(@"registering activity %s for fd %i ..",
587 // activityDesc[fileActivity], fd);
589 if (fileActivity & NSPosixReadableActivity)
590 FD_SET(fd, &readSet);
591 if (fileActivity & NSPosixWritableActivity)
592 FD_SET(fd, &writeSet);
593 if (fileActivity & NSPosixExceptionalActivity)
594 FD_SET(fd, &exceptionsSet);
598 // ???: errno = 0; What is this good for ?
599 r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
601 if (errno == EINTR) {
602 /* Interrupt occured; break the loop to give a chance to
603 UnixSignalHandler to handle the signals. */
608 NSLog(@"%s: select() error: '%s'",
609 __PRETTY_FUNCTION__, strerror (errno));
618 NSString* format = @"%s: Caught exception %@ with reason %@ ";
620 *(&fileObjectsCopy) = nil;
623 // made copy, so that modifications in the delegate don't
625 fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
626 count = [fileObjectsCopy count];
628 for (i = 0; (i < count) && (r > 0); i++) {
629 WORunLoopFileObjectInfo *info;
630 NSPosixFileActivities activity = 0;
633 info = [fileObjectsCopy objectAtIndex:i];
634 fd = [info fileDescriptor];
637 //NSLog(@"checking activity for %i info %@ ..", fd, info);
639 if (FD_ISSET(fd, &readSet)) {
640 activity |= NSPosixReadableActivity;
643 if (FD_ISSET(fd, &writeSet)) {
644 activity |= NSPosixWritableActivity;
647 if (FD_ISSET(fd, &exceptionsSet)) {
648 activity |= NSPosixExceptionalActivity;
653 [info activity:activity onDescriptor:fd];
657 NSLog(@"WARNING: could not resolve all activities (%i) ..",
662 NSLog(format, __PRETTY_FUNCTION__,
663 [localException name], [localException reason]);
667 RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
670 [inputManager performAdditionalOperations];
671 #if !NeXT_Foundation_LIBRARY
672 [NSNotificationQueue runLoopASAP];
676 - (void)runUntilDate:(NSDate*)limitDate
678 BOOL shouldContinue = YES;
681 limitDate = [NSDate distantFuture];
683 /* If limitDate is in the past return */
684 if([limitDate timeIntervalSinceNow] < 0)
688 while (shouldContinue) {
689 CREATE_AUTORELEASE_POOL(pool);
691 if ([limitDate laterDate:[NSDate date]] == limitDate) {
692 if([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
703 [self runUntilDate:[NSDate distantFuture]];
706 - (void)performSelector:(SEL)aSelector
708 argument:(id)anArgument
709 order:(unsigned)order
710 modes:(NSArray*)modes
712 id holder = [WORunLoopActionHolder objectWithTarget:target
716 int i, count = [modes count];
718 for (i = 0; i < count; i++)
719 [[inputsForMode objectForKey:[modes objectAtIndex:i]]
720 addOperation:holder];
723 - (void)cancelPerformSelector:(SEL)aSelector
725 argument:(id)anArgument
727 id holder = [WORunLoopActionHolder objectWithTarget:target
731 id enumerator = [inputsForMode keyEnumerator];
734 while ((aMode = [enumerator nextObject]))
735 [[inputsForMode objectForKey:aMode] removeOperation:holder];
738 /* Monitoring file objects */
740 - (void)addFileObject:(id)_fileObject
741 activities:(NSPosixFileActivities)_activities
742 forMode:(NSString *)_mode
744 [_getInputManager(self, _mode) addFileObject:_fileObject
745 activities:_activities];
748 - (void)removeFileObject:(id)_fileObject
749 forMode:(NSString *)_mode
751 [_getInputManager(self, _mode) removeFileObject:_fileObject];
756 #endif /* !LIB_FOUNDATION_LIBRARY */