TPPreciseTimer.m 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. //
  2. // TPPreciseTimer.m
  3. // Loopy
  4. //
  5. // Created by Michael Tyson on 06/09/2011.
  6. // Copyright 2011 A Tasty Pixel. All rights reserved.
  7. //
  8. #import "TPPreciseTimer.h"
  9. #import <mach/mach_time.h>
  10. #import <pthread.h>
  11. #define kSpinLockTime 0.01
  12. //#define kAnalyzeTiming // Uncomment to display timing discrepancy reports
  13. static TPPreciseTimer *__sharedInstance = nil;
  14. static NSString *kTimeKey = @"time";
  15. static NSString *kTargetKey = @"target";
  16. static NSString *kSelectorKey = @"selector";
  17. static NSString *kArgumentKey = @"argument";
  18. #if NS_BLOCKS_AVAILABLE
  19. static NSString *kBlockKey = @"block";
  20. #endif
  21. @interface TPPreciseTimer ()
  22. - (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval;
  23. - (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval;
  24. - (void)cancelAction:(SEL)action target:(id)target;
  25. - (void)cancelAction:(SEL)action target:(id)target context:(id)context;
  26. #if NS_BLOCKS_AVAILABLE
  27. - (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval;
  28. #endif
  29. - (void)addSchedule:(NSDictionary*)schedule;
  30. void thread_signal(int signal);
  31. void *thread_entry(void* argument);
  32. - (void)thread;
  33. @end
  34. @implementation TPPreciseTimer
  35. + (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval {
  36. if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
  37. [__sharedInstance scheduleAction:action target:target inTimeInterval:timeInterval];
  38. }
  39. + (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval {
  40. if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
  41. [__sharedInstance scheduleAction:action target:target context:context inTimeInterval:timeInterval];
  42. }
  43. + (void)cancelAction:(SEL)action target:(id)target {
  44. if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
  45. [__sharedInstance cancelAction:action target:target];
  46. }
  47. + (void)cancelAction:(SEL)action target:(id)target context:(id)context {
  48. if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
  49. [__sharedInstance cancelAction:action target:target context:context];
  50. }
  51. #if NS_BLOCKS_AVAILABLE
  52. + (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval {
  53. if ( !__sharedInstance ) __sharedInstance = [[TPPreciseTimer alloc] init];
  54. [__sharedInstance scheduleBlock:block inTimeInterval:timeInterval];
  55. }
  56. #endif
  57. - (id)init {
  58. if ( !(self = [super init]) ) return nil;
  59. struct mach_timebase_info timebase;
  60. mach_timebase_info(&timebase);
  61. timebase_ratio = ((double)timebase.numer / (double)timebase.denom) * 1.0e-9;
  62. events = [[NSMutableArray alloc] init];
  63. condition = [[NSCondition alloc] init];
  64. pthread_attr_t attr;
  65. pthread_attr_init(&attr);
  66. struct sched_param param;
  67. param.sched_priority = sched_get_priority_max(SCHED_FIFO);
  68. pthread_attr_setschedparam(&attr, &param);
  69. pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
  70. pthread_create(&thread, &attr, thread_entry, (void*)CFBridgingRetain(self));
  71. return self;
  72. }
  73. - (void)scheduleAction:(SEL)action target:(id)target inTimeInterval:(NSTimeInterval)timeInterval {
  74. [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
  75. NSStringFromSelector(action), kSelectorKey,
  76. target, kTargetKey,
  77. [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
  78. nil]];
  79. }
  80. - (void)scheduleAction:(SEL)action target:(id)target context:(id)context inTimeInterval:(NSTimeInterval)timeInterval {
  81. [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
  82. NSStringFromSelector(action), kSelectorKey,
  83. target, kTargetKey,
  84. [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
  85. context, kArgumentKey,
  86. nil]];
  87. }
  88. - (void)cancelAction:(SEL)action target:(id)target {
  89. [condition lock];
  90. NSDictionary *originalNextEvent = [events count] > 0 ? [events objectAtIndex:0] : nil;
  91. [events filterUsingPredicate:[NSPredicate predicateWithFormat:@"%K != %@ AND %K != %@", kTargetKey, target, kSelectorKey, NSStringFromSelector(action)]];
  92. BOOL mustSignal = originalNextEvent != ([events count] > 0 ? [events objectAtIndex:0] : nil);
  93. [condition signal];
  94. [condition unlock];
  95. if ( mustSignal ) {
  96. pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until
  97. }
  98. }
  99. - (void)cancelAction:(SEL)action target:(id)target context:(id)context {
  100. [condition lock];
  101. NSDictionary *originalNextEvent = [events count] > 0 ? [events objectAtIndex:0] : nil;
  102. [events filterUsingPredicate:[NSPredicate predicateWithFormat:@"%K != %@ AND %K != %@ AND %K != %@", kTargetKey, target, kSelectorKey, NSStringFromSelector(action), kArgumentKey, context]];
  103. BOOL mustSignal = originalNextEvent != ([events count] > 0 ? [events objectAtIndex:0] : nil);
  104. [condition signal];
  105. [condition unlock];
  106. if ( mustSignal ) {
  107. pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until
  108. }
  109. }
  110. #if NS_BLOCKS_AVAILABLE
  111. - (void)scheduleBlock:(void (^)(void))block inTimeInterval:(NSTimeInterval)timeInterval {
  112. [self addSchedule:[NSDictionary dictionaryWithObjectsAndKeys:
  113. [block copy], kBlockKey,
  114. [NSNumber numberWithUnsignedLongLong:mach_absolute_time() + (timeInterval / timebase_ratio)], kTimeKey,
  115. nil]];
  116. }
  117. #endif
  118. - (void)addSchedule:(NSDictionary*)schedule {
  119. [condition lock];
  120. [events addObject:schedule];
  121. [events sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:kTimeKey ascending:YES]]];
  122. BOOL mustSignal = [events count] > 1 && [events objectAtIndex:0] == schedule;
  123. [condition signal];
  124. [condition unlock];
  125. if ( mustSignal ) {
  126. pthread_kill(thread, SIGALRM); // Interrupt thread if it's performing a mach_wait_until and new schedule is earlier
  127. }
  128. }
  129. void *thread_entry(void* argument) {
  130. [(TPPreciseTimer*)CFBridgingRelease(argument) thread];
  131. return NULL;
  132. }
  133. void thread_signal(int signal) {
  134. // Ignore
  135. }
  136. - (void)thread {
  137. signal(SIGALRM, thread_signal);
  138. [condition lock];
  139. while ( 1 ) {
  140. while ( [events count] == 0 ) {
  141. [condition wait];
  142. }
  143. @autoreleasepool {
  144. NSDictionary *nextEvent = [events objectAtIndex:0];
  145. NSTimeInterval time = [[nextEvent objectForKey:kTimeKey] unsignedLongLongValue] * timebase_ratio;
  146. [condition unlock];
  147. mach_wait_until((uint64_t)((time - kSpinLockTime) / timebase_ratio));
  148. if ( (double)(mach_absolute_time() * timebase_ratio) >= time-kSpinLockTime ) {
  149. // Spin lock until it's time
  150. uint64_t end = time / timebase_ratio;
  151. while ( mach_absolute_time() < end );
  152. #ifdef kAnalyzeTiming
  153. double discrepancy = (double)(mach_absolute_time()*timebase_ratio) - time;
  154. printf("TPPreciseTimer fired: %lfs time discrepancy\n", discrepancy);
  155. #endif
  156. // Perform action
  157. #if NS_BLOCKS_AVAILABLE
  158. void (^block)(void) = [nextEvent objectForKey:kBlockKey];
  159. if ( block ) {
  160. block();
  161. } else {
  162. #endif
  163. id target = [nextEvent objectForKey:kTargetKey];
  164. SEL selector = NSSelectorFromString([nextEvent objectForKey:kSelectorKey]);
  165. if ( [nextEvent objectForKey:kArgumentKey] ) {
  166. [target performSelector:selector withObject:[nextEvent objectForKey:kArgumentKey]];
  167. } else {
  168. [target performSelector:selector];
  169. }
  170. #if NS_BLOCKS_AVAILABLE
  171. }
  172. #endif
  173. [condition lock];
  174. [events removeObject:nextEvent];
  175. } else {
  176. [condition lock];
  177. }
  178. }
  179. }
  180. }
  181. @end