Oolite 1.91.0.7644-241112-7f5034b
Loading...
Searching...
No Matches
OOJSTimer.m
Go to the documentation of this file.
1/*
2
3OOJSTimer.m
4
5
6Oolite
7Copyright (C) 2004-2013 Giles C Williams and contributors
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22MA 02110-1301, USA.
23
24*/
25
26#import "OOJSTimer.h"
28#import "Universe.h"
29
30
31// Minimum allowable interval for repeating timers.
32#define kMinInterval 0.25
33
34
35static JSObject *sTimerPrototype;
36static JSClass sTimerClass;
37
38
39@interface OOJSTimer (Private)
40
41- (id) initWithDelay:(OOTimeAbsolute)delay
42 interval:(OOTimeDelta)interval
43 context:(JSContext *)context
44 function:(jsval)function
45 this:(JSObject *)jsThis;
46
47@end
48
49
50@implementation OOJSTimer
51
52- (id) initWithDelay:(OOTimeAbsolute)delay
53 interval:(OOTimeDelta)interval
54 context:(JSContext *)context
55 function:(jsval)function
56 this:(JSObject *)jsThis
57{
58 self = [super initWithNextTime:[UNIVERSE getTime] + delay interval:interval];
59 if (self != nil)
60 {
61 NSAssert(OOJSValueIsFunction(context, function), @"Attempt to init OOJSTimer with a function that isn't.");
62
63 _jsThis = jsThis;
64 OOJSAddGCObjectRoot(context, &_jsThis, "OOJSTimer this");
65
66 _function = function;
67 OOJSAddGCValueRoot(context, &_function, "OOJSTimer function");
68
69 _jsSelf = JS_NewObject(context, &sTimerClass, sTimerPrototype, NULL);
70 if (_jsSelf != NULL)
71 {
72 if (!JS_SetPrivate(context, _jsSelf, [self retain])) _jsSelf = NULL;
73 }
74 if (_jsSelf == NULL)
75 {
76 [self release];
77 return nil;
78 }
79
81
82 [[NSNotificationCenter defaultCenter] addObserver:self
83 selector:@selector(deleteJSPointers)
84 name:kOOJavaScriptEngineWillResetNotification
86 }
87
88 return self;
89}
90
91
92- (void) deleteJSPointers
93{
94 [self unscheduleTimer];
95
96 if (_jsThis != NULL)
97 {
98 _jsThis = NULL;
99 _function = JSVAL_VOID;
100
101 JSContext *context = OOJSAcquireContext();
102 JS_RemoveObjectRoot(context, &_jsThis);
103 JS_RemoveValueRoot(context, &_function);
104 OOJSRelinquishContext(context);
105
106 [[NSNotificationCenter defaultCenter] removeObserver:self
107 name:kOOJavaScriptEngineWillResetNotification
109 }
110}
111
112
113- (void) dealloc
114{
115 [_owningScript release];
116
117 [self deleteJSPointers];
118
119 [super dealloc];
120}
121
122
123- (NSString *) descriptionComponents
124{
125 NSString *funcName = nil;
126 JSContext *context = NULL;
127
128 if (JSVAL_IS_VOID(_function) || JSVAL_IS_NULL(_function))
129 {
130 return @"invalid";
131 }
132
133 context = OOJSAcquireContext();
134 funcName = OOStringFromJSString(context, JS_GetFunctionId(JS_ValueToFunction(context, _function)));
135 OOJSRelinquishContext(context);
136
137 if (funcName == nil)
138 {
139 funcName = @"anonymous";
140 }
141
142 return [NSString stringWithFormat:@"%@, function: %@", [super descriptionComponents], funcName];
143}
144
145
146- (NSString *) oo_jsClassName
147{
148 return @"Timer";
149}
150
151
152- (void) timerFired
153{
154 jsval rval = JSVAL_VOID;
155 NSString *description = nil;
156
158 JSContext *context = OOJSAcquireContext();
159
160 // stop and remove the timer if _jsThis (the first parameter in the constructor) dies.
161 id object = OOJSNativeObjectFromJSObject(context, _jsThis);
162 if (object != nil)
163 {
164 description = [object oo_jsDescription];
165 if (description == nil) description = [object description];
166 }
167
168 if (description == nil)
169 {
170 [self unscheduleTimer];
171 OOJSRelinquishContext(context);
172 return;
173 }
174
175 [OOJSScript pushScript:_owningScript];
176 [engine callJSFunction:_function
177 forObject:_jsThis
178 argc:0
179 argv:NULL
180 result:&rval];
181 [OOJSScript popScript:_owningScript];
182
183 OOJSRelinquishContext(context);
184}
185
186
187- (jsval) oo_jsValueInContext:(JSContext *)context
188{
189 return OBJECT_TO_JSVAL(_jsSelf);
190}
191
192@end
193
194
195static JSBool TimerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value);
196static JSBool TimerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value);
197static void TimerFinalize(JSContext *context, JSObject *this);
198static JSBool TimerConstruct(JSContext *context, uintN argc, jsval *vp);
199
200// Methods
201static JSBool TimerStart(JSContext *context, uintN argc, jsval *vp);
202static JSBool TimerStop(JSContext *context, uintN argc, jsval *vp);
203
204
205static JSClass sTimerClass =
206{
207 "Timer",
208 JSCLASS_HAS_PRIVATE,
209
210 JS_PropertyStub, // addProperty
211 JS_PropertyStub, // delProperty
212 TimerGetProperty, // getProperty
213 TimerSetProperty, // setProperty
214 JS_EnumerateStub, // enumerate
215 JS_ResolveStub, // resolve
216 JS_ConvertStub, // convert
217 TimerFinalize, // finalize
218 JSCLASS_NO_OPTIONAL_MEMBERS
219};
220
221
222enum
223{
224 // Property IDs
225 kTimer_nextTime, // next fire time, double, read/write
226 kTimer_interval, // interval, double, read/write
227 kTimer_isRunning // is scheduled, boolean, read-only
229
230
231static JSPropertySpec sTimerProperties[] =
232{
233 // JS name ID flags
235 { "isRunning", kTimer_isRunning, OOJS_PROP_READONLY_CB },
237 { 0 }
238};
239
240
241static JSFunctionSpec sTimerMethods[] =
242{
243 // JS name Function min args
244 { "toString", OOJSObjectWrapperToString, 0 },
245 { "start", TimerStart, 0 },
246 { "stop", TimerStop, 0 },
247 { 0 }
248};
249
250
252
253
254void InitOOJSTimer(JSContext *context, JSObject *global)
255{
256 sTimerPrototype = JS_InitClass(context, global, NULL, &sTimerClass, TimerConstruct, 0, sTimerProperties, sTimerMethods, NULL, NULL);
258}
259
260
261static JSBool TimerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value)
262{
263 if (!JSID_IS_INT(propID)) return YES;
264
265 OOJS_NATIVE_ENTER(context)
266
267 OOJSTimer *timer = nil;
268
269 if (EXPECT_NOT(!JSTimerGetTimer(context, this, &timer))) return NO;
270
271 switch (JSID_TO_INT(propID))
272 {
273 case kTimer_nextTime:
274 return JS_NewNumberValue(context, [timer nextTime], value);
275
276 case kTimer_interval:
277 return JS_NewNumberValue(context, [timer interval], value);
278
279 case kTimer_isRunning:
280 *value = OOJSValueFromBOOL([timer isScheduled]);
281 return YES;
282
283 default:
284 OOJSReportBadPropertySelector(context, this, propID, sTimerProperties);
285 return NO;
286 }
287
289}
290
291
292static JSBool TimerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value)
293{
294 if (!JSID_IS_INT(propID)) return YES;
295
296 OOJS_NATIVE_ENTER(context)
297
298 OOJSTimer *timer = nil;
299 double fValue;
300
301 if (EXPECT_NOT(!JSTimerGetTimer(context, this, &timer))) return NO;
302
303 switch (JSID_TO_INT(propID))
304 {
305 case kTimer_nextTime:
306 if (JS_ValueToNumber(context, *value, &fValue))
307 {
308 if (![timer setNextTime:fValue])
309 {
310 OOJSReportWarning(context, @"Ignoring attempt to change next fire time for running timer %@.", timer);
311 }
312 return YES;
313 }
314 break;
315
316 case kTimer_interval:
317 if (JS_ValueToNumber(context, *value, &fValue))
318 {
319 [timer setInterval:fValue];
320 return YES;
321 }
322 break;
323
324 default:
325 OOJSReportBadPropertySelector(context, this, propID, sTimerProperties);
326 return NO;
327 }
328
329 OOJSReportBadPropertyValue(context, this, propID, sTimerProperties, *value);
330 return NO;
331
333}
334
335
336static void TimerFinalize(JSContext *context, JSObject *this)
337{
339
340 // Can't use JSTimerGetTimer() here - potential chicken-and-egg problem manifesting as a crash.
341 OOJSTimer *timer = (OOJSTimer *)JS_GetPrivate(context, this);
342
343 if (timer != nil)
344 {
345 if ([timer isScheduled])
346 {
347 OOLogWARN(@"script.javaScript.unrootedTimer", @"Timer %@ is being garbage-collected while still running. You must keep a reference to all running timers, or they will stop unpredictably!", timer);
348 }
349 [timer release];
350 JS_SetPrivate(context, this, NULL);
351 }
352
354}
355
356
357// new Timer(this : Object, function : Function, delay : Number [, interval : Number]) : Timer
358static JSBool TimerConstruct(JSContext *context, uintN argc, jsval *vp)
359{
360 OOJS_NATIVE_ENTER(context)
361
362 jsval function = JSVAL_VOID;
363 double delay;
364 double interval = -1.0;
365 OOJSTimer *timer = nil;
366 JSObject *callbackThis = NULL;
367
368 if (EXPECT_NOT(!JS_IsConstructing(context, vp)))
369 {
370 OOJSReportError(context, @"Timer() cannot be called as a function, it must be used as a constructor (as in new Timer(...)).");
371 return NO;
372 }
373
374 if (argc < 3)
375 {
376 OOJSReportBadArguments(context, nil, @"Timer", argc, OOJS_ARGV, @"Invalid arguments in constructor", @"(object, function, number [, number])");
377 return NO;
378 }
379
380 if (!JSVAL_IS_NULL(OOJS_ARGV[0]) && !JSVAL_IS_VOID(OOJS_ARGV[0]))
381 {
382 if (!JS_ValueToObject(context, OOJS_ARGV[0], &callbackThis))
383 {
384 OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV, @"Invalid argument in constructor", @"object");
385 return NO;
386 }
387 }
388
389 function = OOJS_ARGV[1];
390 if (JS_ValueToFunction(context, function) == NULL)
391 {
392 OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV + 1, @"Invalid argument in constructor", @"function");
393 return NO;
394 }
395
396 if (!JS_ValueToNumber(context, OOJS_ARGV[2], &delay) || isnan(delay))
397 {
398 OOJSReportBadArguments(context, nil, @"Timer", 1, OOJS_ARGV + 2, @"Invalid argument in constructor", @"number");
399 return NO;
400 }
401
402 // Fourth argument is optional.
403 if (3 < argc && !JS_ValueToNumber(context, OOJS_ARGV[3], &interval)) interval = -1;
404
405 // Ensure interval is not too small.
406 if (0.0 < interval && interval < kMinInterval) interval = kMinInterval;
407
408 timer = [[OOJSTimer alloc] initWithDelay:delay
409 interval:interval
410 context:context
411 function:function
412 this:callbackThis];
413 if (EXPECT_NOT(!timer)) return NO;
414
415 if (delay >= 0) // Leave in stopped state if delay is negative
416 {
417 [timer scheduleTimer];
418 }
419 [timer autorelease];
420 OOJS_RETURN_OBJECT(timer);
421
423}
424
425
426// *** Methods ***
427
428// start() : Boolean
429static JSBool TimerStart(JSContext *context, uintN argc, jsval *vp)
430{
431 OOJS_NATIVE_ENTER(context)
432
433 OOJSTimer *thisTimer = nil;
434
435 if (EXPECT_NOT(!JSTimerGetTimer(context, OOJS_THIS, &thisTimer))) return NO;
436
437 OOJS_RETURN_BOOL([thisTimer scheduleTimer]);
438
440}
441
442
443// stop()
444static JSBool TimerStop(JSContext *context, uintN argc, jsval *vp)
445{
446 OOJS_NATIVE_ENTER(context)
447
448 OOJSTimer *thisTimer = nil;
449
450 if (EXPECT_NOT(!JSTimerGetTimer(context, OOJS_THIS, &thisTimer))) return NO;
451
452 [thisTimer unscheduleTimer];
454
456}
#define EXPECT_NOT(x)
#define OOJS_PROFILE_EXIT_VOID
#define OOJS_NATIVE_ENTER(cx)
#define OOJS_NATIVE_EXIT
#define OOJS_PROFILE_ENTER
static JSBool TimerSetProperty(JSContext *context, JSObject *this, jsid propID, JSBool strict, jsval *value)
Definition OOJSTimer.m:292
static JSObject * sTimerPrototype
Definition OOJSTimer.m:35
void InitOOJSTimer(JSContext *context, JSObject *global)
Definition OOJSTimer.m:254
static JSBool TimerGetProperty(JSContext *context, JSObject *this, jsid propID, jsval *value)
Definition OOJSTimer.m:261
static JSPropertySpec sTimerProperties[]
Definition OOJSTimer.m:231
static JSClass sTimerClass
Definition OOJSTimer.m:36
static JSBool TimerConstruct(JSContext *context, uintN argc, jsval *vp)
Definition OOJSTimer.m:358
static JSBool TimerStart(JSContext *context, uintN argc, jsval *vp)
Definition OOJSTimer.m:429
static void TimerFinalize(JSContext *context, JSObject *this)
Definition OOJSTimer.m:336
static JSFunctionSpec sTimerMethods[]
Definition OOJSTimer.m:241
@ kTimer_isRunning
Definition OOJSTimer.m:227
@ kTimer_nextTime
Definition OOJSTimer.m:225
@ kTimer_interval
Definition OOJSTimer.m:226
#define kMinInterval
Definition OOJSTimer.m:32
static JSBool TimerStop(JSContext *context, uintN argc, jsval *vp)
Definition OOJSTimer.m:444
void OOJSReportWarning(JSContext *context, NSString *format,...)
#define OOJS_THIS
JSBool OOJSObjectWrapperToString(JSContext *context, uintN argc, jsval *vp)
#define OOJS_PROP_READWRITE_CB
void OOJSRegisterObjectConverter(JSClass *theClass, OOJSClassConverterCallback converter)
id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *object)
#define DEFINE_JS_OBJECT_GETTER(NAME, JSCLASS, JSPROTO, OBJCCLASSNAME)
#define OOJS_RETURN_OBJECT(o)
void OOJSReportBadPropertySelector(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec)
#define OOJS_RETURN_BOOL(v)
OOINLINE BOOL OOJSValueIsFunction(JSContext *context, jsval value)
OOINLINE JSContext * OOJSAcquireContext(void)
void OOJSReportError(JSContext *context, NSString *format,...)
#define OOJS_ARGV
OOINLINE jsval OOJSValueFromBOOL(int b) INLINE_CONST_FUNC
#define OOJSAddGCObjectRoot(context, root, name)
OOINLINE void OOJSRelinquishContext(JSContext *context)
void OOJSReportBadPropertyValue(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec, jsval value)
id OOJSBasicPrivateObjectConverter(JSContext *context, JSObject *object)
void OOJSReportBadArguments(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, NSString *message, NSString *expectedArgsDescription)
#define OOJS_PROP_READONLY_CB
#define OOJSAddGCValueRoot(context, root, name)
#define OOJS_RETURN_VOID
NSString * OOStringFromJSString(JSContext *context, JSString *string)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
return nil
double OOTimeDelta
Definition OOTypes.h:224
double OOTimeAbsolute
Definition OOTypes.h:223
void pushScript:(OOJSScript *script)
Definition OOJSScript.m:537
OOJSScript * currentlyRunningScript()
Definition OOJSScript.m:339
void popScript:(OOJSScript *script)
Definition OOJSScript.m:550
BOOL callJSFunction:forObject:argc:argv:result:(jsval function,[forObject] JSObject *jsThis,[argc] uintN argc,[argv] jsval *argv,[result] jsval *outResult)
OOJavaScriptEngine * sharedEngine()
void setInterval:(OOTimeDelta interval)
id initWithNextTime:interval:(OOTimeAbsolute nextTime,[interval] OOTimeDelta interval)