|
@@ -0,0 +1,212 @@
|
|
|
+//
|
|
|
+// TPPreciseTimer.m
|
|
|
+// Loopy
|
|
|
+//
|
|
|
+// Created by Michael Tyson on 06/09/2011.
|
|
|
+// Copyright 2011 A Tasty Pixel. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "TPPreciseTimer.h"
|
|
|
+#import <mach/mach_time.h>
|
|
|
+#import <pthread.h>
|
|
|
+
|
|
|
+#define kSpinLockTime 0.01
|
|
|
+//#define kAnalyzeTiming // Uncomment to display timing discrepancy reports
|
|
|
+
|
|
|
+static TPPreciseTimer *__sharedInstance = nil;
|
|
|
+
|
|
|
+static NSString *kTimeKey = @"time";
|
|
|
+static NSString *kTargetKey = @"target";
|
|
|
+static NSString *kSelectorKey = @"selector";
|
|
|
+static NSString *kArgumentKey = @"argument";
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
+static NSString *kBlockKey = @"block";
|
|
|
+#endif
|
|
|
+
|
|
|
+@interface TPPreciseTimer ()
|
|
|
+- (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval;
|
|
|
+- (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval;
|
|
|
+- (void)cancelAction:(SEL)action target:(id)target;
|
|
|
+- (void)cancelAction:(SEL)action target:(id)target context:(id)context;
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
+- (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval;
|
|
|
+#endif
|
|
|
+- (void)addSchedule:(NSDictionary*)schedule;
|
|
|
+void thread_signal(int signal);
|
|
|
+void *thread_entry(void* argument);
|
|
|
+- (void)thread;
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation TPPreciseTimer
|
|
|
+
|
|
|
++ (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
|
|
|
+ [__sharedInstance scheduleAction:action target:target inTimeInterval:timeInterval];
|
|
|
+}
|
|
|
++ (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
|
|
|
+ [__sharedInstance scheduleAction:action target:target context:context inTimeInterval:timeInterval];
|
|
|
+}
|
|
|
++ (void)cancelAction:(SEL)action target:(id)target {
|
|
|
+ if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
|
|
|
+ [__sharedInstance cancelAction:action target:target];
|
|
|
+}
|
|
|
++ (void)cancelAction:(SEL)action target:(id)target context:(id)context {
|
|
|
+ if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
|
|
|
+ [__sharedInstance cancelAction:action target:target context:context];
|
|
|
+}
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
++ (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
|
|
|
+ [__sharedInstance scheduleBlock:block inTimeInterval:timeInterval];
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+- (id)init {
|
|
|
+ if ( !(self = [super init]) ) return nil;
|
|
|
+
|
|
|
+ struct mach_timebase_info timebase;
|
|
|
+ mach_timebase_info(&timebase);
|
|
|
+ timebase_ratio = ((double)timebase.numer / (double)timebase.denom) * 1.0e-9;
|
|
|
+
|
|
|
+ events = [[NSMutableArray alloc] init];
|
|
|
+ condition = [[NSCondition alloc] init];
|
|
|
+
|
|
|
+ pthread_attr_t attr;
|
|
|
+ pthread_attr_init(&attr);
|
|
|
+ struct sched_param param;
|
|
|
+ param.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
|
|
+ pthread_attr_setschedparam(&attr, ¶m);
|
|
|
+ pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
|
|
|
+ pthread_create(&thread, &attr, thread_entry, (void*)self);
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
+ NSStringFromSelector(action), kSelectorKey,
|
|
|
+ target, kTargetKey,
|
|
|
+ [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
|
|
|
+ nil]];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
+ NSStringFromSelector(action), kSelectorKey,
|
|
|
+ target, kTargetKey,
|
|
|
+ [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
|
|
|
+ context, kArgumentKey,
|
|
|
+ nil]];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)cancelAction:(SEL)action target:(id)target {
|
|
|
+ [condition lock];
|
|
|
+ NSDictionary *originalNextEvent = [events count] > 0 ? [events objectAtIndex:0] : nil;
|
|
|
+ [events filterUsingPredicate:[NSPredicate predicateWithFormat:@"%K != %@ AND %K != %@", kTargetKey, target, kSelectorKey, NSStringFromSelector(action)]];
|
|
|
+ BOOL mustSignal = originalNextEvent != ([events count] > 0 ? [events objectAtIndex:0] : nil);
|
|
|
+ [condition signal];
|
|
|
+ [condition unlock];
|
|
|
+ if ( mustSignal ) {
|
|
|
+ pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)cancelAction:(SEL)action target:(id)target context:(id)context {
|
|
|
+ [condition lock];
|
|
|
+ NSDictionary *originalNextEvent = [events count] > 0 ? [events objectAtIndex:0] : nil;
|
|
|
+ [events filterUsingPredicate:[NSPredicate predicateWithFormat:@"%K != %@ AND %K != %@ AND %K != %@", kTargetKey, target, kSelectorKey, NSStringFromSelector(action), kArgumentKey, context]];
|
|
|
+ BOOL mustSignal = originalNextEvent != ([events count] > 0 ? [events objectAtIndex:0] : nil);
|
|
|
+ [condition signal];
|
|
|
+ [condition unlock];
|
|
|
+ if ( mustSignal ) {
|
|
|
+ pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
+- (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval {
|
|
|
+ [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
+ [[block copy] autorelease], kBlockKey,
|
|
|
+ [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
|
|
|
+ nil]];
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+- (void)addSchedule:(NSDictionary*)schedule {
|
|
|
+ [condition lock];
|
|
|
+ [events addObject:schedule];
|
|
|
+ [events sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:kTimeKey ascending:YES]]];
|
|
|
+ BOOL mustSignal = [events count] > 1 && [events objectAtIndex:0] == schedule;
|
|
|
+ [condition signal];
|
|
|
+ [condition unlock];
|
|
|
+ if ( mustSignal ) {
|
|
|
+ pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until and new schedule is earlier
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void *thread_entry(void* argument) {
|
|
|
+ [(TPPreciseTimer*)argument thread];
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void thread_signal(int signal) {
|
|
|
+ // Ignore
|
|
|
+}
|
|
|
+
|
|
|
+- (void)thread {
|
|
|
+ signal(SIGALRM, thread_signal);
|
|
|
+ [condition lock];
|
|
|
+
|
|
|
+ while ( 1 ) {
|
|
|
+ while ( [events count] == 0 ) {
|
|
|
+ [condition wait];
|
|
|
+ }
|
|
|
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
+ NSDictionary *nextEvent = [[[events objectAtIndex:0] retain] autorelease];
|
|
|
+ NSTimeInterval time = [[nextEvent objectForKey:kTimeKey] unsignedLongLongValue] * timebase_ratio;
|
|
|
+
|
|
|
+ [condition unlock];
|
|
|
+
|
|
|
+ mach_wait_until((uint64_t)((time - kSpinLockTime) / timebase_ratio));
|
|
|
+
|
|
|
+ if ( (double)(mach_absolute_time() * timebase_ratio) >= time-kSpinLockTime ) {
|
|
|
+
|
|
|
+ // Spin lock until it's time
|
|
|
+ uint64_t end = time / timebase_ratio;
|
|
|
+ while ( mach_absolute_time() < end );
|
|
|
+
|
|
|
+#ifdef kAnalyzeTiming
|
|
|
+ double discrepancy = (double)(mach_absolute_time()*timebase_ratio) - time;
|
|
|
+ printf("TPPreciseTimer fired: %lfs time discrepancy\n", discrepancy);
|
|
|
+#endif
|
|
|
+
|
|
|
+ // Perform action
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
+ void (^block)(void) = [nextEvent objectForKey:kBlockKey];
|
|
|
+ if ( block ) {
|
|
|
+ block();
|
|
|
+ } else {
|
|
|
+#endif
|
|
|
+ id target = [nextEvent objectForKey:kTargetKey];
|
|
|
+ SEL selector = NSSelectorFromString([nextEvent objectForKey:kSelectorKey]);
|
|
|
+ if ( [nextEvent objectForKey:kArgumentKey] ) {
|
|
|
+ [target performSelector:selector withObject:[nextEvent objectForKey:kArgumentKey]];
|
|
|
+ } else {
|
|
|
+ [target performSelector:selector];
|
|
|
+ }
|
|
|
+#if NS_BLOCKS_AVAILABLE
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ [condition lock];
|
|
|
+ [events removeObject:nextEvent];
|
|
|
+ } else {
|
|
|
+ [condition lock];
|
|
|
+ }
|
|
|
+
|
|
|
+ [pool release];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@end
|