Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -33,10 +33,11 @@ OFNumber.m \ OFObject.m \ OFObject+Serialization.m \ ${OFPLUGIN_M} \ OFProcess.m \ + OFRunLoop.m \ OFSeekableStream.m \ OFSet.m \ OFSHA1Hash.m \ OFSortedList.m \ OFStream.m \ @@ -50,10 +51,11 @@ OFString+XMLEscaping.m \ OFString+XMLUnescaping.m \ OFTCPSocket.m \ ${OFTHREAD_M} \ OFThreadPool.m \ + OFTimer.m \ OFURL.m \ OFXMLAttribute.m \ OFXMLCDATA.m \ OFXMLCharacters.m \ OFXMLComment.m \ Index: src/OFApplication.m ================================================================== --- src/OFApplication.m +++ src/OFApplication.m @@ -27,10 +27,11 @@ #import "OFApplication.h" #import "OFString.h" #import "OFArray.h" #import "OFDictionary.h" #import "OFThread.h" +#import "OFRunLoop.h" #import "OFNotImplementedException.h" #import "autorelease.h" @@ -293,16 +294,19 @@ #undef REGISTER_SIGNAL } - (void)run { + OFRunLoop *runLoop; + [OFThread _createMainThread]; + runLoop = [[[OFRunLoop alloc] init] autorelease]; + [OFRunLoop _setMainRunLoop: runLoop]; [delegate applicationDidFinishLaunching]; - for (;;) - [OFThread sleepForTimeInterval: 86400]; + [runLoop run]; } - (void)terminate { exit(0); ADDED src/OFRunLoop.h Index: src/OFRunLoop.h ================================================================== --- src/OFRunLoop.h +++ src/OFRunLoop.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" + +@class OFSortedList; +@class OFTimer; + +/** + * \brief A class providing a run loop for the application and its processes. + */ +@interface OFRunLoop: OFObject +{ + OFSortedList *timersQueue; +} + +/** + * \brief Returns the main run loop. + * + * \return The main run loop + */ ++ (OFRunLoop*)mainRunLoop; + +/** + * \brief Returns the run loop for the current thread. + * + * \return The run loop for the current thread + */ ++ (OFRunLoop*)currentRunLoop; + ++ (void)_setMainRunLoop: (OFRunLoop*)mainRunLoop; + +/** + * \brief Adds an OFTimer to the run loop. + * + * \param timer The timer to add + */ +- (void)addTimer: (OFTimer*)timer; + +/** + * \brief Starts the run loop. + */ +- (void)run; +@end ADDED src/OFRunLoop.m Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFRunLoop.h" +#import "OFThread.h" +#import "OFSortedList.h" +#import "OFTimer.h" +#import "OFDate.h" + +static OFTLSKey *currentRunLoopKey; +static OFRunLoop *mainRunLoop; + +@implementation OFRunLoop ++ (void)initialize +{ + if (self == [OFRunLoop class]) + currentRunLoopKey = [[OFTLSKey alloc] init]; +} + ++ (OFRunLoop*)mainRunLoop +{ + return [[mainRunLoop retain] autorelease]; +} + ++ (OFRunLoop*)currentRunLoop +{ + return [[[OFThread objectForTLSKey: currentRunLoopKey] + retain] autorelease]; +} + ++ (void)_setMainRunLoop: (OFRunLoop*)mainRunLoop_ +{ + mainRunLoop = [mainRunLoop_ retain]; +} + +- init +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + + timersQueue = [[[OFThread currentThread] _timersQueue] retain]; + [OFThread setObject: self + forTLSKey: currentRunLoopKey]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [timersQueue release]; + + [super dealloc]; +} + +- (void)addTimer: (OFTimer*)timer +{ + @synchronized (timersQueue) { + [timersQueue addObject: timer]; + } +} + +- (void)run +{ + for (;;) { + void *pool = objc_autoreleasePoolPush(); + OFDate *now = [OFDate date]; + + @synchronized (timersQueue) { + of_list_object_t *iter, *next; + + for (iter = [timersQueue firstListObject]; + iter != NULL; iter = next) { + /* + * If a timer is in the future, we can + * stop now as it is sorted. + */ + if ([[iter->object fireDate] compare: now] == + OF_ORDERED_DESCENDING) + break; + + [iter->object fire]; + + next = iter->next; + [timersQueue removeListObject: iter]; + } + + /* Sleep until we reach the next timer */ + if (iter != NULL) + [OFThread sleepUntilDate: + [iter->object fireDate]]; + else { + /* + * FIXME: + * Theoretically, another thread could add + * something to the run loop. This means we + * would need a way to block the thread until + * another event is added. The most easy way to + * achieve that would be if a run loop also + * handles streams: An OFStreamObserver could + * be called instead of sleepUntilDate above + * and get a timeout. If no timers are left, + * it could be called without a timeout and + * would automatically stop blocking once a new + * stream is added. This could also be used to + * stop blocking once a new timer is added. + */ + [OFThread sleepForTimeInterval: 24 * 3600]; + } + } + + objc_autoreleasePoolPop(pool); + } +} +@end Index: src/OFThread.h ================================================================== --- src/OFThread.h +++ src/OFThread.h @@ -18,10 +18,11 @@ #import "OFList.h" #import "threading.h" @class OFDate; +@class OFSortedList; #ifdef OF_HAVE_BLOCKS typedef id (^of_thread_block_t)(id object); #endif @@ -92,10 +93,16 @@ } running; #ifdef OF_HAVE_BLOCKS of_thread_block_t block; #endif id returnValue; +#ifdef OF_THREAD_M +@protected +#else +@private +#endif + OFSortedList *timersQueue; } #if defined(OF_HAVE_PROPERTIES) && defined(OF_HAVE_BLOCKS) @property (copy) of_thread_block_t block; #endif @@ -247,10 +254,12 @@ - (id)main; /** * \brief This routine is exectued when the thread's main method has finished * executing or terminate has been called. + * + * \note Be sure to call [super handleTermination]! */ - (void)handleTermination; /** * \brief Starts the thread. @@ -261,10 +270,12 @@ * \brief Joins a thread. * * \return The object returned by the main method of the thread. */ - (id)join; + +- (OFSortedList*)_timersQueue; @end /** * \brief A class for creating mutual exclusions. */ Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -28,10 +28,11 @@ #endif #import "OFThread.h" #import "OFList.h" #import "OFDate.h" +#import "OFSortedList.h" #import "OFAutoreleasePool.h" #ifdef _WIN32 # include #endif @@ -257,12 +258,17 @@ - initWithBlock: (of_thread_block_t)block_ object: (id)object_ { self = [super init]; - block = [block_ copy]; - object = [object_ retain]; + @try { + block = [block_ copy]; + object = [object_ retain]; + } @catch (id e) { + [self release]; + @throw e; + } return self; } #endif @@ -274,10 +280,17 @@ return nil; } - (void)handleTermination { + @synchronized (timersQueue) { + /* + * Make sure we don't run old timers when the thread is + * restarted. + */ + [timersQueue removeAllObjects]; + } } - (void)start { if (running == OF_THREAD_RUNNING) @@ -311,10 +324,22 @@ running = OF_THREAD_NOT_RUNNING; return returnValue; } + +- (OFSortedList*)_timersQueue +{ + if (timersQueue == nil) { + OFSortedList *tmp = [[OFSortedList alloc] init]; + + if (!of_atomic_cmpswap_ptr((void**)&timersQueue, nil, tmp)) + [tmp release]; + } + + return [[timersQueue retain] autorelease]; +} - (void)dealloc { if (running == OF_THREAD_RUNNING) @throw [OFThreadStillRunningException @@ -328,10 +353,11 @@ if (running == OF_THREAD_WAITING_FOR_JOIN) of_thread_detach(thread); [object release]; [returnValue release]; + [timersQueue release]; [super dealloc]; } @end ADDED src/OFTimer.h Index: src/OFTimer.h ================================================================== --- src/OFTimer.h +++ src/OFTimer.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" + +@class OFDate; + +/** + * \brief A class for creating and firing timers. + */ +@interface OFTimer: OFObject +{ + OFDate *fireDate; + double interval; + id target, object1, object2; + SEL selector; + uint8_t arguments; + BOOL repeats; +} + +/** + * \brief Creates and schedules a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + repeats: (BOOL)repeats; + +/** + * \brief Creates and schedules a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param object An object to pass when calling the selector on the target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object + repeats: (BOOL)repeats; + +/** + * \brief Creates and schedules a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param object1 The first object to pass when calling the selector on the + * target + * \param object2 The second object to pass when calling the selector on the + * target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + object: (id)object2 + repeats: (BOOL)repeats; + +/** + * \brief Creates a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + repeats: (BOOL)repeats; + +/** + * \brief Creates a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param object An object to pass when calling the selector on the target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object + repeats: (BOOL)repeats; + +/** + * \brief Creates a new timer with the specified time interval. + * + * \param interval The time interval after which the timer should be executed + * when fired + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param object1 The first object to pass when calling the selector on the + * target + * \param object2 The second object to pass when calling the selector on the + * target + * \param repeats Whether the timer repeats after it has been executed + * \return A new, autoreleased timer + */ ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + object: (id)object2 + repeats: (BOOL)repeats; + +/** + * \brief Initializes an already allocated timer with the specified time + * interval. + * + * \param fireDate The date at which the timer should fire + * \param interval The time interval after which to repeat the timer, if it is + * a repeating timer + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param repeats Whether the timer repeats after it has been executed + * \return An initialized timer + */ +- initWithFireDate: (OFDate*)fireDate + interval: (double)interval + target: (id)target + selector: (SEL)selector + repeats: (BOOL)repeats; + +/** + * \brief Initializes an already allocated timer with the specified time + * interval. + * + * \param fireDate The date at which the timer should fire + * \param interval The time interval after which to repeat the timer, if it is + * a repeating timer + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param object An object to pass when calling the selector on the target + * \param repeats Whether the timer repeats after it has been executed + * \return An initialized timer + */ +- initWithFireDate: (OFDate*)fireDate + interval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + repeats: (BOOL)repeats; + +/** + * \brief Initializes an already allocated timer with the specified time + * interval. + * + * \param fireDate The date at which the timer should fire + * \param interval The time interval after which to repeat the timer, if it is + * a repeating timer + * \param target The target on which to call the selector + * \param selector The selector to call on the target + * \param repeats Whether the timer repeats after it has been executed + * \return An initialized timer + */ +- initWithFireDate: (OFDate*)fireDate + interval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + object: (id)object2 + repeats: (BOOL)repeats; + +/** + * \brief Fires the timer, meaning it will execute the specified selector on the + * target. + */ +- (void)fire; + +/** + * \brief Returns the next date at which the timer will fire. + * + * \return The next date at which the timer will fire + */ +- (OFDate*)fireDate; +@end ADDED src/OFTimer.m Index: src/OFTimer.m ================================================================== --- src/OFTimer.m +++ src/OFTimer.m @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFTimer.h" +#import "OFDate.h" +#import "OFRunLoop.h" + +#import "OFInvalidArgumentException.h" + +#import "autorelease.h" +#import "macros.h" + +@implementation OFTimer ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + repeats: repeats] autorelease]; + + [[OFRunLoop currentRunLoop] addTimer: timer]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + object: object + repeats: repeats] autorelease]; + + [[OFRunLoop currentRunLoop] addTimer: timer]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + ++ scheduledTimerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + object: (id)object2 + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + object: object1 + object: object2 + repeats: repeats] autorelease]; + + [[OFRunLoop currentRunLoop] addTimer: timer]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + repeats: repeats] autorelease]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + object: object + repeats: repeats] autorelease]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + ++ timerWithTimeInterval: (double)interval + target: (id)target + selector: (SEL)selector + object: (id)object1 + object: (id)object2 + repeats: (BOOL)repeats +{ + void *pool = objc_autoreleasePoolPush(); + OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval]; + id timer = [[[self alloc] initWithFireDate: fireDate + interval: interval + target: target + selector: selector + object: object1 + object: object2 + repeats: repeats] autorelease]; + + [timer retain]; + objc_autoreleasePoolPop(pool); + + return [timer autorelease]; +} + +- _initWithFireDate: (OFDate*)fireDate_ + interval: (double)interval_ + target: (id)target_ + selector: (SEL)selector_ + object: (id)object1_ + object: (id)object2_ + arguments: (uint8_t)arguments_ + repeats: (BOOL)repeats_ +{ + self = [super init]; + + @try { + fireDate = [fireDate_ retain]; + interval = interval_; + target = [target_ retain]; + selector = selector_; + object1 = [object1_ retain]; + object2 = [object2_ retain]; + arguments = arguments_; + repeats = repeats_; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- initWithFireDate: (OFDate*)fireDate_ + interval: (double)interval_ + target: (id)target_ + selector: (SEL)selector_ + repeats: (BOOL)repeats_ +{ + return [self _initWithFireDate: fireDate_ + interval: interval_ + target: target_ + selector: selector_ + object: nil + object: nil + arguments: 0 + repeats: repeats_]; +} + +- initWithFireDate: (OFDate*)fireDate_ + interval: (double)interval_ + target: (id)target_ + selector: (SEL)selector_ + object: (id)object + repeats: (BOOL)repeats_ +{ + return [self _initWithFireDate: fireDate_ + interval: interval_ + target: target_ + selector: selector_ + object: object + object: nil + arguments: 1 + repeats: repeats_]; +} + +- initWithFireDate: (OFDate*)fireDate_ + interval: (double)interval_ + target: (id)target_ + selector: (SEL)selector_ + object: (id)object1_ + object: (id)object2_ + repeats: (BOOL)repeats_ +{ + return [self _initWithFireDate: fireDate_ + interval: interval_ + target: target_ + selector: selector_ + object: object1_ + object: object2_ + arguments: 2 + repeats: repeats_]; +} + +- (void)dealloc +{ + [fireDate release]; + [target release]; + [object1 release]; + [object2 release]; + + [super dealloc]; +} + +- (of_comparison_result_t)compare: (id )object_ +{ + OFTimer *otherTimer; + + if (![object_ isKindOfClass: [OFTimer class]]) + @throw[OFInvalidArgumentException + exceptionWithClass: [self class] + selector: _cmd]; + + otherTimer = (OFTimer*)object_; + + return [fireDate compare: otherTimer->fireDate]; +} + +- (void)fire +{ + OF_ENSURE(arguments >= 0 && arguments <= 2); + + switch (arguments) { + case 0: + [target performSelector: selector]; + break; + case 1: + [target performSelector: selector + withObject: object1]; + break; + case 2: + [target performSelector: selector + withObject: object1 + withObject: object2]; + break; + } + + if (repeats) { + OFDate *old = fireDate; + fireDate = [[OFDate alloc] + initWithTimeIntervalSinceNow: interval]; + [old release]; + + [[OFRunLoop currentRunLoop] addTimer: self]; + } +} + +- (OFDate*)fireDate +{ + return [[fireDate retain] autorelease]; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -68,10 +68,12 @@ #import "OFXMLElementBuilder.h" #import "OFSerialization.h" #import "OFApplication.h" +#import "OFTimer.h" +#import "OFRunLoop.h" #import "OFAllocFailedException.h" #import "OFException.h" #import "OFAcceptFailedException.h" #import "OFAddressTranslationFailedException.h"