Line data Source code
1 0 : /* 2 : 3 : OOScriptTimer.m 4 : 5 : 6 : Oolite 7 : Copyright (C) 2004-2013 Giles C Williams and contributors 8 : 9 : This program is free software; you can redistribute it and/or 10 : modify it under the terms of the GNU General Public License 11 : as published by the Free Software Foundation; either version 2 12 : of the License, or (at your option) any later version. 13 : 14 : This program is distributed in the hope that it will be useful, 15 : but WITHOUT ANY WARRANTY; without even the implied warranty of 16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 : GNU General Public License for more details. 18 : 19 : You should have received a copy of the GNU General Public License 20 : along with this program; if not, write to the Free Software 21 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 22 : MA 02110-1301, USA. 23 : 24 : */ 25 : 26 : #import "OOScriptTimer.h" 27 : #import "Universe.h" 28 : #import "OOLogging.h" 29 : #import "OOPriorityQueue.h" 30 : 31 : 32 0 : static OOPriorityQueue *sTimers; 33 : 34 : // During an update, new timers must be deferred to avoid an infinite loop. 35 0 : static BOOL sUpdating; 36 0 : static NSMutableArray *sDeferredTimers; 37 : 38 : 39 : @implementation OOScriptTimer 40 : 41 : - (id) initWithNextTime:(OOTimeAbsolute)nextTime 42 : interval:(OOTimeDelta)interval 43 : { 44 : OOTimeAbsolute now; 45 : 46 : if ((self = [super init])) 47 : { 48 : if (interval <= 0.0) interval = -1.0; 49 : 50 : now = [UNIVERSE getTime]; 51 : if (nextTime < 0.0) nextTime = now + interval; 52 : if (nextTime < now && interval < 0) 53 : { 54 : // Negative or old nextTime and negative interval = meaningless. 55 : [self release]; 56 : self = nil; 57 : } 58 : else 59 : { 60 : _nextTime = nextTime; 61 : _interval = interval; 62 : _hasBeenRun = NO; 63 : } 64 : } 65 : 66 : return self; 67 : } 68 : 69 : // Sets nextTime to current time + delay. 70 : - (id) initOneShotTimerWithDelay:(OOTimeDelta)delay 71 : { 72 : return [self initWithNextTime:[UNIVERSE getTime] + delay interval:-1.0]; 73 : } 74 : 75 : 76 0 : - (void) dealloc 77 : { 78 : if (_isScheduled) [self unscheduleTimer]; 79 : 80 : [super dealloc]; 81 : } 82 : 83 : 84 0 : - (NSString *) descriptionComponents 85 : { 86 : NSString *intervalDesc = nil; 87 : 88 : if (_interval <= 0.0) intervalDesc = @"one-shot"; 89 : else intervalDesc = [NSString stringWithFormat:@"interval: %g", _interval]; 90 : 91 : return [NSString stringWithFormat:@"nextTime: %g, %@, %srunning", _nextTime, intervalDesc, _isScheduled ? "" : "not "]; 92 : } 93 : 94 : 95 : - (OOTimeAbsolute)nextTime 96 : { 97 : return _nextTime; 98 : } 99 : 100 : 101 : - (BOOL)setNextTime:(OOTimeAbsolute)nextTime 102 : { 103 : if (_isScheduled) return NO; 104 : 105 : _nextTime = nextTime; 106 : return YES; 107 : } 108 : 109 : 110 : - (OOTimeDelta)interval 111 : { 112 : return _interval; 113 : } 114 : 115 : 116 : - (void)setInterval:(OOTimeDelta)interval 117 : { 118 : if (interval <= 0.0) interval = -1.0; 119 : _interval = interval; 120 : } 121 : 122 : 123 : - (void) timerFired 124 : { 125 : OOLogGenericSubclassResponsibility(); 126 : } 127 : 128 : 129 : - (BOOL) scheduleTimer 130 : { 131 : if (_isScheduled) return YES; 132 : if (![self isValidForScheduling]) return NO; 133 : 134 : if (EXPECT(!sUpdating)) 135 : { 136 : if (EXPECT_NOT(sTimers == nil)) sTimers = [[OOPriorityQueue alloc] initWithComparator:@selector(compareByNextFireTime:)]; 137 : [sTimers addObject:self]; 138 : } 139 : else 140 : { 141 : if (sDeferredTimers == nil) sDeferredTimers = [[NSMutableArray alloc] init]; 142 : [sDeferredTimers addObject:self]; 143 : } 144 : 145 : _isScheduled = YES; 146 : return YES; 147 : } 148 : 149 : 150 : - (void) unscheduleTimer 151 : { 152 : [sTimers removeExactObject:self]; 153 : _isScheduled = NO; 154 : _hasBeenRun = NO; 155 : } 156 : 157 : 158 : - (BOOL) isScheduled 159 : { 160 : return _isScheduled; 161 : } 162 : 163 : 164 : + (void) updateTimers 165 : { 166 : OOScriptTimer *timer = nil; 167 : OOTimeAbsolute now; 168 : 169 : sUpdating = YES; 170 : 171 : now = [UNIVERSE getTime]; 172 : for (;;) 173 : { 174 : timer = [sTimers peekAtNextObject]; 175 : if (timer == nil || now < [timer nextTime]) break; 176 : 177 : [sTimers removeNextObject]; 178 : 179 : // Must fire before rescheduling so that the timer callback can stop itself. -- Ahruman 2011-01-01 180 : [timer timerFired]; 181 : 182 : timer->_hasBeenRun = YES; 183 : 184 : if (timer->_isScheduled) 185 : { 186 : timer->_isScheduled = NO; 187 : [timer scheduleTimer]; 188 : } 189 : } 190 : 191 : if (sDeferredTimers != nil) 192 : { 193 : [sTimers addObjects:sDeferredTimers]; 194 : DESTROY(sDeferredTimers); 195 : } 196 : 197 : sUpdating = NO; 198 : } 199 : 200 : 201 : + (void) noteGameReset 202 : { 203 : NSArray *timers = nil; 204 : NSEnumerator *timerEnum = nil; 205 : OOScriptTimer *timer = nil; 206 : 207 : // Intermediate array is required so we don't get stuck in an endless loop over reinserted timers. Note that -sortedObjects also clears the queue! 208 : timers = [sTimers sortedObjects]; 209 : for (timerEnum = [timers objectEnumerator]; (timer = [timerEnum nextObject]); ) 210 : { 211 : timer->_isScheduled = NO; 212 : } 213 : } 214 : 215 : 216 : - (BOOL) isValidForScheduling 217 : { 218 : OOTimeAbsolute now; 219 : double scaled; 220 : 221 : now = [UNIVERSE getTime]; 222 : if (_nextTime <= now) 223 : { 224 : if (_interval <= 0.0 && _hasBeenRun) return NO; // One-shot timer which has expired 225 : 226 : // Move _nextTime to the closest future time that's a multiple of _interval 227 : scaled = (now - _nextTime) / _interval; 228 : scaled = ceil(scaled); 229 : _nextTime += scaled * _interval; 230 : if (_nextTime <= now && _hasBeenRun) 231 : { 232 : // Should only happen if _nextTime is exactly equal to now after previous stuff 233 : _nextTime += _interval; 234 : } 235 : } 236 : 237 : return YES; 238 : } 239 : 240 : - (NSComparisonResult) compareByNextFireTime:(OOScriptTimer *)other 241 : { 242 : OOTimeAbsolute otherTime = -INFINITY; 243 : 244 : @try 245 : { 246 : if (other != nil) otherTime = [other nextTime]; 247 : } 248 : @catch (NSException *exception) 249 : { 250 : OOLog(kOOLogException, @"\n\n***** Ignoring Timer Exception: %@ : %@ *****\n\n",[exception name], [exception reason]); 251 : } 252 : 253 : if (_nextTime < otherTime) return NSOrderedAscending; 254 : else if (_nextTime > otherTime) return NSOrderedDescending; 255 : else return NSOrderedSame; 256 : } 257 : 258 : @end