|
@@ -1,212 +0,0 @@
|
|
|
-//
|
|
|
-// 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
|