Experimental support for caching of compiled JavaScript (can be disabled with -DOO_CACHE_JS_SCRIPTS=0). Implemented JS Vector.rotateBy(quaternionExpression).

git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@1181 127b21dd-08f5-0310-b4b7-95ae10353056
This commit is contained in:
Jens Ayton 2007-09-22 15:16:40 +00:00
parent a677bc3744
commit 7d99e403a5
9 changed files with 219 additions and 83 deletions

View File

@ -97,6 +97,9 @@ NSString *QuaternionDescription(Quaternion quaternion); // @"(w + xi + yj + zk)"
#endif
Vector quaternion_rotate_vector(Quaternion q, Vector vector) CONST_FUNC;
/*** Only inline definitions beyond this point ***/

View File

@ -375,3 +375,12 @@ NSString *QuaternionDescription(Quaternion quaternion)
return [NSString stringWithFormat:@"(%g %c %gi %c %gj %c %gk)", quaternion.w, xs, x, ys, y, zs, z];
}
Vector quaternion_rotate_vector(Quaternion q, Vector vector)
{
Vector u, v, w;
basis_vectors_from_quaternion(q, &u, &v, &w);
return make_vector(dot_product(u, vector), dot_product(v, vector), dot_product(w, vector));
}

View File

@ -41,7 +41,6 @@ static JSBool EntityGetProperty(JSContext *context, JSObject *this, jsval name,
static JSBool EntitySetProperty(JSContext *context, JSObject *this, jsval name, jsval *value);
// Methods
static JSBool EntityToString(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool EntitySetPosition(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool EntitySetOrientation(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool EntityValid(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
@ -126,7 +125,7 @@ static JSPropertySpec sEntityProperties[] =
static JSFunctionSpec sEntityMethods[] =
{
// JS name Function min args
{ "toString", EntityToString, 0 },
{ "toString", JSObjectWrapperToString, 0 },
{ "setPosition", EntitySetPosition, 1 },
{ "setOrientation", EntitySetOrientation, 1 },
{ "valid", EntityValid, 0 },
@ -374,22 +373,6 @@ static JSBool EntitySetProperty(JSContext *context, JSObject *this, jsval name,
// *** Methods ***
static JSBool EntityToString(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
Entity *thisEnt = nil;
if (JSEntityGetEntity(context, this, &thisEnt))
{
*outResult = [[thisEnt description] javaScriptValueInContext:context];
}
else
{
*outResult = STRING_TO_JSVAL(JS_InternString(context, "[stale Entity]"));
}
return YES;
}
static JSBool EntitySetPosition(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
Entity *thisEnt;

View File

@ -32,6 +32,7 @@ MA 02110-1301, USA.
{
JSContext *context;
JSObject *object;
JSObject *scriptObject;
NSString *name;
NSString *description;

View File

@ -22,6 +22,11 @@ MA 02110-1301, USA.
*/
#ifndef OO_CACHE_JS_SCRIPTS
#define OO_CACHE_JS_SCRIPTS 1
#endif
#import "OOJSScript.h"
#import "OOLogging.h"
#import "OOConstToString.h"
@ -30,6 +35,11 @@ MA 02110-1301, USA.
#import "NSStringOOExtensions.h"
#import "EntityOOJavaScriptExtensions.h"
#if OO_CACHE_JS_SCRIPTS
#import <jsxdrapi.h>
#import "OOCacheManager.h"
#endif
typedef struct RunningStack RunningStack;
struct RunningStack
@ -47,6 +57,13 @@ static void JSScriptFinalize(JSContext *context, JSObject *this);
static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack);
static JSScript *LoadScriptWithName(JSContext *context, NSString *name, JSObject *object, NSString **outErrorMessage);
#if OO_CACHE_JS_SCRIPTS
static NSData *CompiledScriptData(JSContext *context, JSScript *script);
static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data);
#endif
static JSClass sScriptClass =
{
@ -96,8 +113,6 @@ static JSFunctionSpec sScriptMethods[] =
- (id)initWithPath:(NSString *)path properties:(NSDictionary *)properties context:(JSContext *)inContext
{
NSString *problem = nil; // Acts as error flag.
NSString *fileContents = nil;
NSData *data = nil;
JSScript *script = NULL;
jsval returnValue;
NSEnumerator *keyEnum = nil;
@ -112,7 +127,7 @@ static JSFunctionSpec sScriptMethods[] =
{
context = inContext;
// Do we actually want parent to be the global object here?
object = JS_NewObject(context, &sScriptClass, sScriptPrototype, JS_GetGlobalObject(context));
object = JS_NewObject(context, &sScriptClass, sScriptPrototype, NULL /*JS_GetGlobalObject(context)*/);
if (object == NULL) problem = @"allocation failure";
}
@ -131,16 +146,15 @@ static JSFunctionSpec sScriptMethods[] =
if (!problem)
{
fileContents = [NSString stringWithContentsOfUnicodeFile:path];
if (fileContents != nil) data = [fileContents utf16DataWithBOM:NO];
if (data == nil) problem = @"could not load file";
}
// Compile
if (!problem)
{
script = JS_CompileUCScript(context, object, [data bytes], [data length] / sizeof(unichar), [path UTF8String], 1);
if (script == NULL) problem = @"compilation failed";
script = LoadScriptWithName(context, path, object, &problem);
if (JS_GetScriptObject(script) == NULL)
{
/*scriptObject = JS_NewScriptObject(context, script);
if (!JS_AddNamedRoot(context, &scriptObject, "Cached script reference object"))
{
problem = @"could not add JavaScript root object";
}*/
}
}
// Set properties.
@ -202,6 +216,7 @@ static JSFunctionSpec sScriptMethods[] =
[version release];
[weakSelf weakRefDrop];
JS_RemoveRoot(context, &object);
// if (scriptObject != NULL) JS_RemoveRoot(context, &scriptObject);
[super dealloc];
}
@ -377,54 +392,6 @@ static JSFunctionSpec sScriptMethods[] =
}
/* Generate default name for script which doesn't set its name property when
first run.
The generated name is <name>.anon-script, where <name> is selected as
follows:
* If path is nil (futureproofing), use the address of the script object.
* If the file's name is something other than script.*, use the file name.
* If the containing directory is something other than Config, use the
containing directory's name.
* Otherwise, use the containing directory's parent (which will generally
be an OXP root directory).
* If either of the two previous steps results in an empty string, fall
back on the full path.
*/
- (NSString *)scriptNameFromPath:(NSString *)path
{
NSString *lastComponent = nil;
NSString *truncatedPath = nil;
NSString *theName = nil;
if (path == nil) theName = [NSString stringWithFormat:@"%p", self];
else
{
lastComponent = [path lastPathComponent];
if (![lastComponent hasPrefix:@"script."]) theName = lastComponent;
else
{
truncatedPath = [path stringByDeletingLastPathComponent];
if (NSOrderedSame == [[truncatedPath lastPathComponent] caseInsensitiveCompare:@"Config"])
{
truncatedPath = [truncatedPath stringByDeletingLastPathComponent];
}
if (NSOrderedSame == [[truncatedPath pathExtension] caseInsensitiveCompare:@"oxp"])
{
truncatedPath = [truncatedPath stringByDeletingPathExtension];
}
lastComponent = [truncatedPath lastPathComponent];
theName = lastComponent;
}
}
if (0 == [theName length]) theName = path;
return [theName stringByAppendingString:@".anon-script"];
}
- (jsval)javaScriptValueInContext:(JSContext *)context
{
return OBJECT_TO_JSVAL(object);
@ -461,7 +428,61 @@ static JSFunctionSpec sScriptMethods[] =
@end
@implementation OOScript(OOJavaScriptConversion)
@implementation OOJSScript (OOPrivate)
/* Generate default name for script which doesn't set its name property when
first run.
The generated name is <name>.anon-script, where <name> is selected as
follows:
* If path is nil (futureproofing), use the address of the script object.
* If the file's name is something other than script.*, use the file name.
* If the containing directory is something other than Config, use the
containing directory's name.
* Otherwise, use the containing directory's parent (which will generally
be an OXP root directory).
* If either of the two previous steps results in an empty string, fall
back on the full path.
*/
- (NSString *)scriptNameFromPath:(NSString *)path
{
NSString *lastComponent = nil;
NSString *truncatedPath = nil;
NSString *theName = nil;
if (path == nil) theName = [NSString stringWithFormat:@"%p", self];
else
{
lastComponent = [path lastPathComponent];
if (![lastComponent hasPrefix:@"script."]) theName = lastComponent;
else
{
truncatedPath = [path stringByDeletingLastPathComponent];
if (NSOrderedSame == [[truncatedPath lastPathComponent] caseInsensitiveCompare:@"Config"])
{
truncatedPath = [truncatedPath stringByDeletingLastPathComponent];
}
if (NSOrderedSame == [[truncatedPath pathExtension] caseInsensitiveCompare:@"oxp"])
{
truncatedPath = [truncatedPath stringByDeletingPathExtension];
}
lastComponent = [truncatedPath lastPathComponent];
theName = lastComponent;
}
}
if (0 == [theName length]) theName = path;
return [theName stringByAppendingString:@".anon-script"];
}
@end
@implementation OOScript (OOJavaScriptConversion)
- (jsval)javaScriptValueInContext:(JSContext *)context
{
@ -493,3 +514,98 @@ static void AddStackToArrayReversed(NSMutableArray *array, RunningStack *stack)
[array addObject:stack->current];
}
}
static JSScript *LoadScriptWithName(JSContext *context, NSString *path, JSObject *object, NSString **outErrorMessage)
{
#if OO_CACHE_JS_SCRIPTS
OOCacheManager *cache = nil;
#endif
NSString *fileContents = nil;
NSData *data = nil;
JSScript *script = NULL;
assert(outErrorMessage != NULL);
*outErrorMessage = nil;
#if OO_CACHE_JS_SCRIPTS
// Look for cached compiled script
cache = [OOCacheManager sharedCache];
data = [cache objectForKey:path inCache:@"compiled JavaScript scripts"];
if (data != nil)
{
script = ScriptWithCompiledData(context, data);
}
#endif
if (script == NULL)
{
fileContents = [NSString stringWithContentsOfUnicodeFile:path];
if (fileContents != nil) data = [fileContents utf16DataWithBOM:NO];
if (data == nil) *outErrorMessage = @"could not load file";
else
{
script = JS_CompileUCScript(context, object, [data bytes], [data length] / sizeof(unichar), [path UTF8String], 1);
if (script == NULL) *outErrorMessage = @"compilation failed";
}
#if OO_CACHE_JS_SCRIPTS
if (script != NULL)
{
// Write compiled script to cache
data = CompiledScriptData(context, script);
[cache setObject:data forKey:path inCache:@"compiled JavaScript scripts"];
}
#endif
}
return script;
}
#if OO_CACHE_JS_SCRIPTS
static NSData *CompiledScriptData(JSContext *context, JSScript *script)
{
JSXDRState *xdr = NULL;
NSData *result = nil;
uint32 length;
void *bytes = NULL;
xdr = JS_XDRNewMem(context, JSXDR_ENCODE);
if (xdr != NULL)
{
if (JS_XDRScript(xdr, &script))
{
bytes = JS_XDRMemGetData(xdr, &length);
if (bytes != NULL)
{
result = [NSData dataWithBytes:bytes length:length];
}
}
JS_XDRDestroy(xdr);
}
return result;
}
static JSScript *ScriptWithCompiledData(JSContext *context, NSData *data)
{
JSXDRState *xdr = NULL;
JSScript *result = NULL;
if (data == nil) return NULL;
xdr = JS_XDRNewMem(context, JSXDR_DECODE);
if (xdr != NULL)
{
JS_XDRMemSetData(xdr, (void *)[data bytes], [data length]);
if (!JS_XDRScript(xdr, &result)) result = NULL;
JS_XDRMemSetData(xdr, NULL, 0); // Don't let it be freed by XDRDestroy
JS_XDRDestroy(xdr);
}
return result;
}
#endif

View File

@ -105,6 +105,7 @@ enum
kShip_maxSpeed, // maximum flight speed, double, read-only
kShip_script, // script, Script, read-only
kShip_isPirate, // is pirate, boolean, read-only
kShip_isPlayer, // is player, boolean, read-only
kShip_isPolice, // is police, boolean, read-only
kShip_isThargoid, // is thargoid, boolean, read-only
kShip_isTrader, // is trader, boolean, read-only
@ -147,6 +148,7 @@ static JSPropertySpec sShipProperties[] =
{ "maxSpeed", kShip_maxSpeed, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "script", kShip_script, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "isPirate", kShip_isPirate, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "isPlayer", kShip_isPlayer, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "isPolice", kShip_isPolice, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "isThargoid", kShip_isThargoid, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
{ "isTrader", kShip_isTrader, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
@ -343,12 +345,16 @@ static JSBool ShipGetProperty(JSContext *context, JSObject *this, jsval name, js
case kShip_script:
result = [entity shipScript];
if (result == nil) result = [NSNull null];
break;
break;
case kShip_isPirate:
*outValue = BOOLToJSVal([entity isPirate]);
break;
case kShip_isPlayer:
*outValue = BOOLToJSVal([entity isPlayer]);
break;
case kShip_isPolice:
*outValue = BOOLToJSVal([entity isPolice]);
break;

View File

@ -351,7 +351,7 @@ static JSBool SystemToString(JSContext *context, JSObject *this, uintN argc, jsv
PlayerEntity *player = nil;
player = [PlayerEntity sharedPlayer];
systemDesc = [NSString stringWithFormat:@"<System %u:%u \"%@\">", [player currentGalaxyID], [player currentSystemID], [[UNIVERSE currentSystemData] objectForKey:KEY_NAME]];
systemDesc = [NSString stringWithFormat:@"[System %u:%u \"%@\"]", [player currentGalaxyID], [player currentSystemID], [[UNIVERSE currentSystemData] objectForKey:KEY_NAME]];
*outResult = [systemDesc javaScriptValueInContext:context];
return YES;
}

View File

@ -60,6 +60,7 @@ static JSBool VectorDirection(JSContext *context, JSObject *this, uintN argc, js
static JSBool VectorMagnitude(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool VectorSquaredMagnitude(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool VectorRotationTo(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool VectorRotateBy(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool VectorStaticInterpolate(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
@ -121,7 +122,7 @@ static JSFunctionSpec sVectorMethods[] =
{ "direction", VectorDirection, 0, },
{ "magnitude", VectorMagnitude, 0, },
{ "squaredMagnitude", VectorSquaredMagnitude, 0, },
// { "rotateBy", VectorRotateBy, 1, }, // TODO: Vector rotateBy(quaternionExpression)
{ "rotateBy", VectorRotateBy, 1, },
{ "rotationTo", VectorRotationTo, 1, },
{ 0 }
};
@ -614,6 +615,21 @@ static JSBool VectorRotationTo(JSContext *context, JSObject *this, uintN argc, j
}
// rotateBy(q : quaternionExpression) : Vector
static JSBool VectorRotateBy(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
Vector thisv, result;
Quaternion q;
if (!JSVectorGetVector(context, this, &thisv)) return NO;
if (!QuaternionFromArgumentList(context, @"Vector", @"rotateBy", argc, argv, &q, NULL)) return YES;
result = quaternion_rotate_vector(q, thisv);
return VectorToJSValue(context, result, outResult);
}
static JSBool VectorStaticInterpolate(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
Vector av, bv;

View File

@ -660,6 +660,8 @@ static BOOL JSNewNSDictionaryValue(JSContext *context, NSDictionary *dictionary,
JSString *string = NULL;
length = [self length];
if (length == 0) return JS_GetEmptyStringValue(context);
buffer = malloc(length * sizeof *buffer);
if (buffer == NULL) return JSVAL_VOID;