diff --git a/src/Core/Debug/OODebugMonitor.m b/src/Core/Debug/OODebugMonitor.m index 1a1faff0..101db1f2 100644 --- a/src/Core/Debug/OODebugMonitor.m +++ b/src/Core/Debug/OODebugMonitor.m @@ -201,9 +201,12 @@ static OODebugMonitor *sSingleton = nil; - (oneway void)performJSConsoleCommand:(in NSString *)command { + JSContext *context = OOJSAcquireContext(); + jsval commandVal = OOJSValueFromNativeObject(context, command); OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit); - [_script doEvent:OOJSID("consolePerformJSCommand") withArguments:[NSArray arrayWithObject:command]]; + [_script callMethod:OOJSID("consolePerformJSCommand") inContext:context withArguments:&commandVal count:1 result:NULL]; OOJSStopTimeLimiter(); + OOJSRelinquishContext(context); } diff --git a/src/Core/Entities/PlayerEntity.h b/src/Core/Entities/PlayerEntity.h index 38cb7cfd..a7891136 100644 --- a/src/Core/Entities/PlayerEntity.h +++ b/src/Core/Entities/PlayerEntity.h @@ -808,7 +808,7 @@ typedef enum // In general, script events should be sent through doScriptEvent:..., which // will forward to the world scripts. //- (void) doWorldScriptEvent:(OOJSPropID)message withArguments:(NSArray *)arguments timeLimit:(OOTimeDelta)limit; -- (BOOL) doWorldEventUntilMissionScreen:(NSString *)message; +- (BOOL) doWorldEventUntilMissionScreen:(OOJSPropID)message; - (void) doWorldScriptEvent:(OOJSPropID)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc timeLimit:(OOTimeDelta)limit; - (BOOL)showInfoFlag; diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m index b3bf26c8..4cd1740c 100644 --- a/src/Core/Entities/PlayerEntity.m +++ b/src/Core/Entities/PlayerEntity.m @@ -4623,7 +4623,7 @@ static bool minShieldLevelPercentageInitialised = false; [[OOJavaScriptEngine sharedEngine] garbageCollectionOpportunity]; // When a mission screen is started, any on-screen message is removed immediately. - [self doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; // also displays docking reports first. + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // also displays docking reports first. } @@ -4642,7 +4642,7 @@ static bool minShieldLevelPercentageInitialised = false; [self doMissionCallback]; } // notify older scripts, but do not trigger missionScreenOpportunity. - [self doWorldEventUntilMissionScreen:@"missionScreenEnded"]; + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; } if (station == [UNIVERSE station]) @@ -8071,47 +8071,6 @@ static NSString *last_outfitting_key=nil; } -#if 0 -- (void) doScriptEvent:(OOJSPropID)message withArguments:(NSArray *)arguments -{ - JSContext *context = OOJSAcquireContext(); - uintN i, argc; - jsval *argv = NULL; - - // Convert arguments to JS values and make them temporarily un-garbage-collectable. - argc = [arguments count]; - if (argc != 0) - { - argv = malloc(sizeof *argv * argc); - if (argv != NULL) - { - for (i = 0; i != argc; ++i) - { - argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context]; - OOJSAddGCValueRoot(context, &argv[i], "JSScript event parameter"); - } - } - else argc = 0; - } - - [super doScriptEvent:message inContext:context withArguments:argv count:argc]; - [self doWorldScriptEvent:message inContext:context withArguments:argv count:argc timeLimit:0.0]; - - // Re-garbage-collectibalize the arguments and free the array. - if (argv != NULL) - { - for (i = 0; i != argc; ++i) - { - JS_RemoveValueRoot(context, &argv[i]); - } - free(argv); - } - - OOJSRelinquishContext(context); -} -#endif - - - (void) doScriptEvent:(OOJSPropID)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc { [super doScriptEvent:message inContext:context withArguments:argv count:argc]; @@ -8119,11 +8078,10 @@ static NSString *last_outfitting_key=nil; } -- (BOOL) doWorldEventUntilMissionScreen:(NSString *)message +- (BOOL) doWorldEventUntilMissionScreen:(OOJSPropID)message { NSEnumerator *scriptEnum = [worldScripts objectEnumerator]; OOScript *theScript; - OOJSPropID messageID = OOJSPropIDFromString(message); // Check for the pressence of report messages first. if (gui_screen != GUI_SCREEN_MISSION && [dockingReport length] > 0 && [self isDocked] && ![dockedStation suppressArrivalReports]) @@ -8132,12 +8090,13 @@ static NSString *last_outfitting_key=nil; [[UNIVERSE message_gui] clear]; return YES; } - - // FIXME: does this work ok in all situations? Needs fixing if not. + + JSContext *context = OOJSAcquireContext(); while ((theScript = [scriptEnum nextObject]) && gui_screen != GUI_SCREEN_MISSION && [self isDocked]) { - [theScript doEvent:messageID withArguments:nil]; + [theScript callMethod:message inContext:context withArguments:NULL count:0 result:NULL]; } + OOJSRelinquishContext(context); if (gui_screen == GUI_SCREEN_MISSION) { @@ -8160,7 +8119,7 @@ static NSString *last_outfitting_key=nil; for (scriptEnum = [worldScripts objectEnumerator]; (theScript = [scriptEnum nextObject]); ) { OOJSStartTimeLimiterWithTimeLimit(limit); - [theScript doEvent:message inContext:context withArguments:argv count:argc]; + [theScript callMethod:message inContext:context withArguments:argv count:argc result:NULL]; OOJSStopTimeLimiter(); } } diff --git a/src/Core/Entities/PlayerEntityControls.m b/src/Core/Entities/PlayerEntityControls.m index 42289780..f071c5be 100644 --- a/src/Core/Entities/PlayerEntityControls.m +++ b/src/Core/Entities/PlayerEntityControls.m @@ -653,7 +653,7 @@ static NSTimeInterval time_last_frame; [self doMissionCallback]; } // notify older scripts, but do not trigger missionScreenOpportunity. - [self doWorldEventUntilMissionScreen:@"missionScreenEnded"]; + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; } } else if (!paused) @@ -924,7 +924,12 @@ static NSTimeInterval time_last_frame; { // primedEquipment == [eqScripts count] means we don't want to activate any equipment. if(primedEquipment < [eqScripts count]) - [(OOScript *)[[eqScripts oo_arrayAtIndex:primedEquipment] objectAtIndex:1] doEvent:OOJSID("activated") withArguments:nil]; + { + OOJSScript *eqScript = [[eqScripts oo_arrayAtIndex:primedEquipment] objectAtIndex:1]; + JSContext *context = OOJSAcquireContext(); + [eqScript callMethod:OOJSID("activated") inContext:context withArguments:NULL count:0 result:NULL]; + OOJSRelinquishContext(context); + } } activate_equipment_pressed = YES; } @@ -2022,7 +2027,7 @@ static NSTimeInterval time_last_frame; { [self setGuiToStatusScreen]; [self doScriptEvent:OOJSID("reportScreenEnded")]; // last report given. Screen is now free for missionscreens. - [self doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } else { @@ -3196,7 +3201,7 @@ static BOOL toggling_music; [[OOMusicController sharedController] stopThemeMusic]; [[UNIVERSE gameView] supressKeysUntilKeyUp]; // to prevent a missionscreen on the first page from reacting on this keypress. [self setGuiToStatusScreen]; - [self doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; // trigger missionScreenOpportunity immediately after (re)start + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after (re)start } if ([gameView isDown:gvArrowKeyLeft]) // '<--' { @@ -3276,7 +3281,7 @@ static BOOL toggling_music; if ([self status] != STATUS_DOCKED) // did we launch inside callback? / are we in flight? { - [self doWorldEventUntilMissionScreen:@"missionScreenEnded"]; // no opportunity events. + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]; // no opportunity events. } else { diff --git a/src/Core/Entities/PlayerEntityLegacyScriptEngine.m b/src/Core/Entities/PlayerEntityLegacyScriptEngine.m index 7e20f8d3..3bcc3418 100644 --- a/src/Core/Entities/PlayerEntityLegacyScriptEngine.m +++ b/src/Core/Entities/PlayerEntityLegacyScriptEngine.m @@ -2325,10 +2325,10 @@ static int scriptRandomSeed = -1; // ensure proper random function - (void) endMissionScreenAndNoteOpportunity { // Older scripts might intercept missionScreenEnded first, and call secondary mission screens. - if(![self doWorldEventUntilMissionScreen:@"missionScreenEnded"]) + if(![self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")]) { // if we're here, no mission screen is running. Opportunity! :) - [self doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; + [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } } diff --git a/src/Core/Entities/PlayerEntityLoadSave.m b/src/Core/Entities/PlayerEntityLoadSave.m index a9f4dd6c..bcb46fcf 100644 --- a/src/Core/Entities/PlayerEntityLoadSave.m +++ b/src/Core/Entities/PlayerEntityLoadSave.m @@ -530,7 +530,7 @@ static uint16_t PersonalityForCommanderDict(NSDictionary *dict); [UNIVERSE setGalaxy_seed: galaxy_seed andReinit:YES]; // set overridden planet names on long range map [[UNIVERSE gameView] supressKeysUntilKeyUp]; [self setGuiToStatusScreen]; - if (loadedOK) [self doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; // trigger missionScreenOpportunity immediately after loading + if (loadedOK) [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // trigger missionScreenOpportunity immediately after loading return loadedOK; } diff --git a/src/Core/Entities/ShipEntity.m b/src/Core/Entities/ShipEntity.m index cc2efc67..685f1bce 100644 --- a/src/Core/Entities/ShipEntity.m +++ b/src/Core/Entities/ShipEntity.m @@ -9137,10 +9137,10 @@ Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q { args[0] = INT_TO_JSVAL(i); OOJSStartTimeLimiter(); - OK = [script callMethodNamed:OOJSID("coordinatesForEscortPosition") - withArguments:args count:sizeof args / sizeof *args - inContext:context - gettingResult:&result]; + OK = [script callMethod:OOJSID("coordinatesForEscortPosition") + inContext:context + withArguments:args count:sizeof args / sizeof *args + result:&result]; OOJSStopTimeLimiter(); if (OK) OK = JSValueToVector(context, result, &_escortPositions[i]); @@ -9900,7 +9900,6 @@ static BOOL AuthorityPredicate(Entity *entity, void *parameter) // *** Script event dispatch. -// For ease of overriding, these all go through -doScriptEvent:inContext:withArguments:count:. - (void) doScriptEvent:(OOJSPropID)message { JSContext *context = OOJSAcquireContext(); @@ -9949,7 +9948,7 @@ static BOOL AuthorityPredicate(Entity *entity, void *parameter) for (i = 0; i != argc; ++i) { argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context]; - OOJSAddGCValueRoot(context, &argv[i], "JSScript event parameter"); + OOJSAddGCValueRoot(context, &argv[i], "event parameter"); } } else argc = 0; @@ -9973,7 +9972,8 @@ static BOOL AuthorityPredicate(Entity *entity, void *parameter) - (void) doScriptEvent:(OOJSPropID)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc { - [script doEvent:message inContext:context withArguments:argv count:argc]; + // This method is a bottleneck so that PlayerEntity can override at one point. + [script callMethod:message inContext:context withArguments:argv count:argc result:NULL]; } diff --git a/src/Core/OOCharacter.m b/src/Core/OOCharacter.m index 1707c676..ec885dbb 100644 --- a/src/Core/OOCharacter.m +++ b/src/Core/OOCharacter.m @@ -478,7 +478,9 @@ MA 02110-1301, USA. - (void) doScriptEvent:(OOJSPropID)message { - [_script doEvent:message withArguments:nil]; + JSContext *context = OOJSAcquireContext(); + [_script callMethod:message inContext:context withArguments:NULL count:0 result:NULL]; + OOJSRelinquishContext(context); } diff --git a/src/Core/Scripting/OOJSScript.h b/src/Core/Scripting/OOJSScript.h index a410cc6f..72e1cba5 100644 --- a/src/Core/Scripting/OOJSScript.h +++ b/src/Core/Scripting/OOJSScript.h @@ -55,13 +55,14 @@ MA 02110-1301, USA. + (void) pushScript:(OOJSScript *)script; + (void) popScript:(OOJSScript *)script; -/* Low-level interface to call a JavaScript method. +/* Call a method. Requires a request on context. + outResult may be NULL. */ -- (BOOL) callMethodNamed:(OOJSPropID)methodID - withArguments:(jsval *)argv count:(intN)argc - inContext:(JSContext *)context - gettingResult:(jsval *)outResult; +- (BOOL) callMethod:(OOJSPropID)methodID + inContext:(JSContext *)context + withArguments:(jsval *)argv count:(intN)argc + result:(jsval *)outResult; - (id) propertyWithID:(OOJSPropID)propID inContext:(JSContext *)context; // Set a property which can be modified or deleted by the script. @@ -78,9 +79,11 @@ MA 02110-1301, USA. @interface OOScript (JavaScriptEvents) -// These only do anything for JS scripts, but can be safely called on plist scripts too. -- (BOOL) doEvent:(OOJSPropID)eventID withArguments:(NSArray *)arguments; -- (BOOL) doEvent:(OOJSPropID)eventID inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc; +// For simplicity, calling methods on non-JS scripts works but does nothing. +- (BOOL) callMethod:(OOJSPropID)methodID + inContext:(JSContext *)context + withArguments:(jsval *)argv count:(intN)argc + result:(jsval *)outResult; @end diff --git a/src/Core/Scripting/OOJSScript.m b/src/Core/Scripting/OOJSScript.m index 359d5322..202272da 100644 --- a/src/Core/Scripting/OOJSScript.m +++ b/src/Core/Scripting/OOJSScript.m @@ -36,6 +36,7 @@ MA 02110-1301, USA. #import "Entity.h" #import "NSStringOOExtensions.h" #import "EntityOOJavaScriptExtensions.h" +#import "OOConstToJSString.h" #if OO_CACHE_JS_SCRIPTS #import @@ -94,7 +95,6 @@ static JSFunctionSpec sScriptMethods[] = @interface OOJSScript (OOPrivate) - (NSString *)scriptNameFromPath:(NSString *)path; -- (BOOL) doEvent:(OOJSPropID)eventID withMethod:(jsval)method andArguments:(jsval *)argv count:(uintN)argc inContext:(JSContext *)context; @end @@ -314,88 +314,33 @@ static JSFunctionSpec sScriptMethods[] = - (void)runWithTarget:(Entity *)target { - [self doEvent:OOJSID("tickle") withArguments:[NSArray arrayWithObject:[PLAYER status_string]]]; -} - - -- (BOOL) doEvent:(OOJSPropID)eventID withArguments:(NSArray *)arguments -{ - JSContext *context = OOJSAcquireContext(); - uintN i, argc; - jsval *argv = NULL; - jsval function; - JSObject *fakeRoot; - BOOL OK = YES; - - if (OOJSGetMethod(context, _jsSelf, eventID, &fakeRoot, &function) && !JSVAL_IS_VOID(function)) - { - // Convert arguments to JS values and make them temporarily un-garbage-collectable. - argc = [arguments count]; - if (argc != 0) - { - argv = malloc(sizeof *argv * argc); - if (argv != NULL) - { - for (i = 0; i != argc; ++i) - { - argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context]; - OOJSAddGCValueRoot(context, &argv[i], "JSScript event parameter"); - } - } - else argc = 0; - } - - OK = [self doEvent:eventID withMethod:function andArguments:argv count:argc inContext:context]; - - // Re-garbage-collectibalize the arguments and free the array. - if (argv != NULL) - { - for (i = 0; i != argc; ++i) - { - JS_RemoveValueRoot(context, &argv[i]); - } - free(argv); - } - } - + JSContext *context = OOJSAcquireContext(); + jsval arg = OOJSValueFromEntityStatus(context, [PLAYER status]); + [self callMethod:OOJSID("tickle") inContext:context withArguments:&arg count:1 result:NULL]; OOJSRelinquishContext(context); - - return OK; } -- (BOOL) doEvent:(OOJSPropID)eventID inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc +- (BOOL) callMethod:(OOJSPropID)methodID + inContext:(JSContext *)context + withArguments:(jsval *)argv count:(intN)argc + result:(jsval *)outResult { - NSParameterAssert(context != NULL && JS_IsInRequest(context)); + NSParameterAssert(name != NULL && (argv != NULL || argc == 0) && context != NULL && JS_IsInRequest(context)); - jsval function; - JSObject *fakeRoot; - BOOL OK = YES; + JSObject *root = NULL; + BOOL OK = NO; + jsval method; + jsval ignoredResult = JSVAL_VOID; - if (OOJSGetMethod(context, _jsSelf, eventID, &fakeRoot, &function) && !JSVAL_IS_VOID(function)) - { - OK = [self doEvent:eventID withMethod:function andArguments:argv count:argc inContext:context]; - } + if (outResult == NULL) outResult = &ignoredResult; + OOJSAddGCObjectRoot(context, &root, "OOJSScript method root"); - return OK; -} - - -- (BOOL) callMethodNamed:(OOJSPropID)methodID - withArguments:(jsval *)argv count:(intN)argc - inContext:(JSContext *)context - gettingResult:(jsval *)outResult -{ - NSParameterAssert(name != NULL && (argv != NULL || argc == 0) && context != NULL && JS_IsInRequest(context) && outResult != NULL); - - BOOL OK = NO; - JSObject *fakeRoot = NULL; - jsval method; - if (EXPECT(OOJSGetMethod(context, _jsSelf, methodID, &fakeRoot, &method) && !JSVAL_IS_VOID(method))) + if (EXPECT(OOJSGetMethod(context, _jsSelf, methodID, &root, &method) && !JSVAL_IS_VOID(method))) { #ifndef NDEBUG - OOLog(@"script.trace.javaScript.callback", @"Calling [%@].%@()", [self name], OOStringFromJSPropID(methodID)); - OOLogIndentIf(@"script.trace.javaScript.callback"); + OOLog(@"script.trace.javaScript", @"Calling [%@].%@()", [self name], OOStringFromJSPropID(methodID)); + OOLogIndentIf(@"script.trace.javaScript"); #endif // Push self on stack of running scripts. @@ -414,11 +359,17 @@ static JSFunctionSpec sScriptMethods[] = // Pop running scripts stack sRunningStack = stackElement.back; +#if !OO_NEW_JS + JS_ClearNewbornRoots(context); +#endif + #ifndef NDEBUG - OOLogOutdentIf(@"script.trace.javaScript.callback"); + OOLogOutdentIf(@"script.trace.javaScript"); #endif } + JS_RemoveObjectRoot(context, &root); + return OK; } @@ -574,64 +525,17 @@ static JSFunctionSpec sScriptMethods[] = return StrippedName([theName stringByAppendingString:@".anon-script"]); } - -- (BOOL) doEvent:(OOJSPropID)eventID withMethod:(jsval)method andArguments:(jsval *)argv count:(uintN)argc inContext:(JSContext *)context -{ - BOOL OK = YES; - jsval value = JSVAL_VOID; - -#ifndef NDEBUG - NSAssert1(OOJSValueIsFunction(context, method), @"Expected function, got %@.", OOStringFromJSValueEvenIfNull(context, method)); - OOLog(@"script.trace.javaScript.event", @"Calling [%@].%@()", [self name], OOStringFromJSPropID(eventID)); - OOLogIndentIf(@"script.trace.javaScript.event"); -#endif - - // Push self on stack of running scripts. - RunningStack stackElement = - { - .back = sRunningStack, - .current = self - }; - sRunningStack = &stackElement; - - // Call the method. - OOJSStartTimeLimiter(); - OK = JS_CallFunctionValue(context, _jsSelf, method, argc, argv, &value); - OOJSStopTimeLimiter(); - - // Pop running scripts stack. - sRunningStack = stackElement.back; - -#if !OO_NEW_JS - JS_ClearNewbornRoots(context); -#endif - -#ifndef NDEBUG - OOLogOutdentIf(@"script.trace.javaScript.event"); -#endif - - return OK; -} - @end @implementation OOScript (JavaScriptEvents) -- (BOOL) doEvent:(OOJSPropID)eventID withArguments:(NSArray *)arguments +- (BOOL) callMethod:(OOJSPropID)methodID + inContext:(JSContext *)context + withArguments:(jsval *)argv count:(intN)argc + result:(jsval *)outResult { - return YES; -} - - -- (BOOL) doEvent:(OOJSPropID)eventID inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc -{ - return YES; -} - -- (jsval)oo_jsValueInContext:(JSContext *)context -{ - return JSVAL_NULL; + return NO; } @end diff --git a/src/Core/Universe.m b/src/Core/Universe.m index 27f94d9e..0746dc4e 100644 --- a/src/Core/Universe.m +++ b/src/Core/Universe.m @@ -8452,7 +8452,7 @@ Entity *gOOJSPlayerIfStale = nil; if(!showDemo) { [player setGuiToStatusScreen]; - [player doWorldEventUntilMissionScreen:@"missionScreenOpportunity"]; + [player doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; } no_update = NO;