1
0
forked from 0ad/0ad

Extends binary serializer to support some standard JS classes: Number, String, Boolean. Fixes #406.

Extends binary serializer to support typed arrays.
Extends binary serializer to support custom JS prototype objects in AIs,
fixes #407.
Allows full serialization of AIs (not yet implemented). Refs #1089,
#1886
Increases binary serializer script backref arena from 8 MB to 16 MB,
refs #1842

This was SVN commit r13429.
This commit is contained in:
historic_bruno 2013-05-26 21:57:24 +00:00
parent e58fe92892
commit 88c4e5bdd0
7 changed files with 636 additions and 36 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -92,6 +92,8 @@ private:
m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CAIPlayer::PostCommand>("PostCommand");
m_ScriptInterface.RegisterFunction<void, std::wstring, std::vector<u32>, u32, u32, u32, CAIPlayer::DumpImage>("DumpImage");
m_ScriptInterface.RegisterFunction<void, std::wstring, CScriptVal, CAIPlayer::RegisterSerializablePrototype>("RegisterSerializablePrototype");
}
~CAIPlayer()
@ -167,6 +169,18 @@ private:
tex_free(&t);
}
static void RegisterSerializablePrototype(void* cbdata, std::wstring name, CScriptVal proto)
{
CAIPlayer* self = static_cast<CAIPlayer*> (cbdata);
// Add our player number to avoid name conflicts with other prototypes
// TODO: it would be better if serializable prototypes were stored in ScriptInterfaces
// and then each serializer would access those matching its own context, but that's
// not possible with AIs sharing data across contexts
std::wstringstream protoID;
protoID << self->m_Player << L"." << name.c_str();
self->m_Worker.RegisterSerializablePrototype(protoID.str(), proto);
}
bool LoadScripts(const std::wstring& moduleName)
{
// Ignore modules that are already loaded
@ -579,6 +593,8 @@ public:
else
{
CStdSerializer serializer(m_ScriptInterface, stream);
// TODO: see comment in Deserialize()
serializer.SetSerializablePrototypes(m_SerializablePrototypes);
SerializeState(serializer);
}
}
@ -614,11 +630,18 @@ public:
serializer.ScriptVal("command", val);
}
CScriptVal scriptData;
if (!m_ScriptInterface.CallFunction(m_Players[i]->m_Obj.get(), "Serialize", scriptData))
LOGERROR(L"AI script Serialize call failed");
serializer.ScriptVal("data", scriptData);
bool hasCustomSerialize = m_ScriptInterface.HasProperty(m_Players[i]->m_Obj.get(), "Serialize");
if (hasCustomSerialize)
{
CScriptVal scriptData;
if (!m_ScriptInterface.CallFunction(m_Players[i]->m_Obj.get(), "Serialize", scriptData))
LOGERROR(L"AI script Serialize call failed");
serializer.ScriptVal("data", scriptData);
}
else
{
serializer.ScriptVal("data", m_Players[i]->m_Obj.get());
}
}
}
@ -672,16 +695,31 @@ public:
deserializer.ScriptVal("command", val);
m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get()));
}
CScriptVal scriptData;
deserializer.ScriptVal("data", scriptData);
if (m_Players[i]->m_UseSharedComponent)
// TODO: this is yucky but necessary while the AIs are sharing data between contexts;
// ideally a new (de)serializer instance would be created for each player
// so they would have a single, consistent script context to use and serializable
// prototypes could be stored in their ScriptInterface
deserializer.SetSerializablePrototypes(m_DeserializablePrototypes);
bool hasCustomDeserialize = m_ScriptInterface.HasProperty(m_Players.back()->m_Obj.get(), "Deserialize");
if (hasCustomDeserialize)
{
if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData, m_SharedAIObj))
LOGERROR(L"AI script Deserialize call failed");
CScriptVal scriptData;
deserializer.ScriptVal("data", scriptData);
if (m_Players[i]->m_UseSharedComponent)
{
if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData, m_SharedAIObj))
LOGERROR(L"AI script Deserialize call failed");
}
else if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData))
{
LOGERROR(L"AI script deserialize() call failed");
}
}
else if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData))
else
{
LOGERROR(L"AI script deserialize() call failed");
deserializer.ScriptVal("data", m_Players.back()->m_Obj);
}
}
}
@ -691,6 +729,17 @@ public:
return m_Players.size();
}
void RegisterSerializablePrototype(std::wstring name, CScriptVal proto)
{
// Require unique prototype and name (for reverse lookup)
// TODO: this is yucky - see comment in Deserialize()
JSObject* obj = JSVAL_TO_OBJECT(proto.get());
std::pair<std::map<JSObject*, std::wstring>::iterator, bool> ret1 = m_SerializablePrototypes.insert(std::make_pair(obj, name));
std::pair<std::map<std::wstring, JSObject*>::iterator, bool> ret2 = m_DeserializablePrototypes.insert(std::make_pair(name, obj));
if (!ret1.second || !ret2.second)
LOGERROR(L"RegisterSerializablePrototype called with same prototype multiple times: p=%p n='%ls'", obj, name.c_str());
}
private:
CScriptValRooted LoadMetadata(const VfsPath& path)
{
@ -792,6 +841,9 @@ private:
CScriptValRooted m_TerritoryMapVal;
bool m_CommandsComputed;
std::map<JSObject*, std::wstring> m_SerializablePrototypes;
std::map<std::wstring, JSObject*> m_DeserializablePrototypes;
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,11 +25,39 @@
#include "ps/CLogger.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptExtraHeaders.h" // for JSDOUBLE_IS_INT32
#include "scriptinterface/ScriptExtraHeaders.h" // for JSDOUBLE_IS_INT32, typed arrays
static u8 GetArrayType(uint32 arrayType)
{
switch(arrayType)
{
case js::TypedArray::TYPE_INT8:
return SCRIPT_TYPED_ARRAY_INT8;
case js::TypedArray::TYPE_UINT8:
return SCRIPT_TYPED_ARRAY_UINT8;
case js::TypedArray::TYPE_INT16:
return SCRIPT_TYPED_ARRAY_INT16;
case js::TypedArray::TYPE_UINT16:
return SCRIPT_TYPED_ARRAY_UINT16;
case js::TypedArray::TYPE_INT32:
return SCRIPT_TYPED_ARRAY_INT32;
case js::TypedArray::TYPE_UINT32:
return SCRIPT_TYPED_ARRAY_UINT32;
case js::TypedArray::TYPE_FLOAT32:
return SCRIPT_TYPED_ARRAY_FLOAT32;
case js::TypedArray::TYPE_FLOAT64:
return SCRIPT_TYPED_ARRAY_FLOAT64;
case js::TypedArray::TYPE_UINT8_CLAMPED:
return SCRIPT_TYPED_ARRAY_UINT8_CLAMPED;
default:
LOGERROR(L"Cannot serialize unrecognized typed array view: %d", arrayType);
throw PSERROR_Serialize_InvalidScriptValue();
}
}
CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(ScriptInterface& scriptInterface, ISerializer& serializer) :
m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_Rooter(m_ScriptInterface),
m_ScriptBackrefsArena(8*MiB), m_ScriptBackrefs(backrefs_t::key_compare(), ScriptBackrefsAlloc(m_ScriptBackrefsArena)), m_ScriptBackrefsNext(1)
m_ScriptBackrefsArena(16*MiB), m_ScriptBackrefs(backrefs_t::key_compare(), ScriptBackrefsAlloc(m_ScriptBackrefsArena)), m_ScriptBackrefsNext(1)
{
}
@ -68,6 +96,7 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val)
break;
}
// Arrays are special cases of Object
if (JS_IsArrayObject(cx, obj))
{
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY);
@ -80,18 +109,130 @@ void CBinarySerializerScriptImpl::HandleScriptVal(jsval val)
throw PSERROR_Serialize_ScriptError("JS_GetArrayLength failed");
m_Serializer.NumberU32_Unbounded("array length", length);
}
else if (js_IsTypedArray(obj))
{
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_TYPED_ARRAY);
js::TypedArray* typedArray = js::TypedArray::fromJSObject(obj);
m_Serializer.NumberU8_Unbounded("array type", GetArrayType(typedArray->type));
m_Serializer.NumberU32_Unbounded("byte offset", typedArray->byteOffset);
m_Serializer.NumberU32_Unbounded("length", typedArray->length);
// Now handle its array buffer
// this may be a backref, since ArrayBuffers can be shared by multiple views
HandleScriptVal(OBJECT_TO_JSVAL(typedArray->bufferJS));
break;
}
else if (js_IsArrayBuffer(obj))
{
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY_BUFFER);
js::ArrayBuffer* arrayBuffer = js::ArrayBuffer::fromJSObject(obj);
#if BYTE_ORDER != LITTLE_ENDIAN
#error TODO: need to convert JS ArrayBuffer data to little-endian
#endif
u32 length = arrayBuffer->byteLength;
m_Serializer.NumberU32_Unbounded("buffer length", length);
m_Serializer.RawBytes("buffer data", (const u8*)arrayBuffer->data, length);
break;
}
else
{
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);
// Find type of object
JSClass* jsclass = JS_GET_CLASS(cx, obj);
if (!jsclass)
throw PSERROR_Serialize_ScriptError("JS_GET_CLASS failed");
JSProtoKey protokey = JSCLASS_CACHED_PROTO_KEY(jsclass);
// if (JS_GetClass(cx, obj))
// {
// LOGERROR("Cannot serialise JS objects of type 'object' with a class");
// throw PSERROR_Serialize_InvalidScriptValue();
// }
// TODO: ought to complain only about non-standard classes
// TODO: probably ought to do something cleverer for classes, prototypes, etc
// (See Trac #406, #407)
if (protokey == JSProto_Object)
{
// Object class - check for user-defined prototype
JSObject* proto = JS_GetPrototype(cx, obj);
if (!proto)
throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed");
if (m_SerializablePrototypes.empty() || !IsSerializablePrototype(proto))
{
// Standard Object prototype
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);
// TODO: maybe we should throw an error for unrecognized non-Object prototypes?
// (requires fixing AI serialization first and excluding component scripts)
}
else
{
// User-defined custom prototype
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_PROTOTYPE);
const std::wstring& prototypeName = GetPrototypeName(proto);
m_Serializer.String("proto name", prototypeName, 0, 256);
// Does it have custom Serialize function?
// if so, we serialize the data it returns, rather than the object's properties directly
JSBool hasCustomSerialize;
if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize))
throw PSERROR_Serialize_ScriptError("JS_HasProperty failed");
if (hasCustomSerialize)
{
jsval serialize;
if (!JS_LookupProperty(cx, obj, "Serialize", &serialize))
throw PSERROR_Serialize_ScriptError("JS_LookupProperty failed");
// If serialize is null, so don't serialize anything more
if (!JSVAL_IS_NULL(serialize))
{
CScriptValRooted data;
if (!m_ScriptInterface.CallFunction(val, "Serialize", data))
throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed");
HandleScriptVal(data.get());
}
break;
}
}
}
else if (protokey == JSProto_Number)
{
// Standard Number object
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER);
// Get primitive value
jsdouble d;
if (!JS_ValueToNumber(cx, val, &d))
throw PSERROR_Serialize_ScriptError("JS_ValueToNumber failed");
m_Serializer.NumberDouble_Unbounded("value", d);
break;
}
else if (protokey == JSProto_String)
{
// Standard String object
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_STRING);
// Get primitive value
JSString* str = JS_ValueToString(cx, val);
if (!str)
throw PSERROR_Serialize_ScriptError("JS_ValueToString failed");
ScriptString("value", str);
break;
}
else if (protokey == JSProto_Boolean)
{
// Standard Boolean object
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_BOOLEAN);
// Get primitive value
JSBool b;
if (!JS_ValueToBoolean(cx, val, &b))
throw PSERROR_Serialize_ScriptError("JS_ValueToBoolean failed");
m_Serializer.Bool("value", b == JS_TRUE);
break;
}
else
{
// Unrecognized class
LOGERROR(L"Cannot serialise JS objects with unrecognized class '%hs'", jsclass->name);
throw PSERROR_Serialize_InvalidScriptValue();
}
}
// Find all properties (ordered by insertion time)
@ -246,3 +387,20 @@ u32 CBinarySerializerScriptImpl::GetScriptBackrefTag(JSObject* obj)
// Return a non-tag number so callers know they need to serialize the object
return 0;
}
bool CBinarySerializerScriptImpl::IsSerializablePrototype(JSObject* prototype)
{
return m_SerializablePrototypes.find(prototype) != m_SerializablePrototypes.end();
}
std::wstring CBinarySerializerScriptImpl::GetPrototypeName(JSObject* prototype)
{
std::map<JSObject*, std::wstring>::iterator it = m_SerializablePrototypes.find(prototype);
ENSURE(it != m_SerializablePrototypes.end());
return it->second;
}
void CBinarySerializerScriptImpl::SetSerializablePrototypes(std::map<JSObject*, std::wstring>& prototypes)
{
m_SerializablePrototypes = prototypes;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -59,6 +59,7 @@ public:
void ScriptString(const char* name, JSString* string);
void HandleScriptVal(jsval val);
void SetSerializablePrototypes(std::map<JSObject*, std::wstring>& prototypes);
private:
ScriptInterface& m_ScriptInterface;
ISerializer& m_Serializer;
@ -73,6 +74,11 @@ private:
u32 GetScriptBackrefTag(JSObject* obj);
AutoGCRooter m_Rooter;
std::map<JSObject*, std::wstring> m_SerializablePrototypes;
bool IsSerializablePrototype(JSObject* prototype);
std::wstring GetPrototypeName(JSObject* prototype);
};
/**
@ -100,6 +106,11 @@ public:
{
}
virtual void SetSerializablePrototypes(std::map<JSObject*, std::wstring>& prototypes)
{
m_ScriptImpl->SetSerializablePrototypes(prototypes);
}
protected:
/*
The Put* implementations here are designed for subclasses

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,12 +23,32 @@ enum
SCRIPT_TYPE_VOID = 0,
SCRIPT_TYPE_NULL = 1,
SCRIPT_TYPE_ARRAY = 2,
SCRIPT_TYPE_OBJECT = 3,
SCRIPT_TYPE_OBJECT = 3, // standard Object prototype
SCRIPT_TYPE_STRING = 4,
SCRIPT_TYPE_INT = 5,
SCRIPT_TYPE_DOUBLE = 6,
SCRIPT_TYPE_BOOLEAN = 7,
SCRIPT_TYPE_BACKREF = 8
SCRIPT_TYPE_BACKREF = 8,
SCRIPT_TYPE_TYPED_ARRAY = 9, // ArrayBufferView subclasses - see below
SCRIPT_TYPE_ARRAY_BUFFER = 10, // ArrayBuffer containing actual typed array data (may be shared by multiple views)
SCRIPT_TYPE_OBJECT_PROTOTYPE = 11, // user-defined prototype
SCRIPT_TYPE_OBJECT_NUMBER = 12, // standard Number class
SCRIPT_TYPE_OBJECT_STRING = 13, // standard String class
SCRIPT_TYPE_OBJECT_BOOLEAN = 14 // standard Boolean class
};
// ArrayBufferView subclasses (to avoid relying directly on the JSAPI enums)
enum
{
SCRIPT_TYPED_ARRAY_INT8 = 0,
SCRIPT_TYPED_ARRAY_UINT8 = 1,
SCRIPT_TYPED_ARRAY_INT16 = 2,
SCRIPT_TYPED_ARRAY_UINT16 = 3,
SCRIPT_TYPED_ARRAY_INT32 = 4,
SCRIPT_TYPED_ARRAY_UINT32 = 5,
SCRIPT_TYPED_ARRAY_FLOAT32 = 6,
SCRIPT_TYPED_ARRAY_FLOAT64 = 7,
SCRIPT_TYPED_ARRAY_UINT8_CLAMPED = 8
};
#endif // INCLUDED_SERIALIZEDSCRIPTTYPES

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,11 +23,38 @@
#include "StdSerializer.h" // for DEBUG_SERIALIZER_ANNOTATE
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays
#include "js/jsapi.h"
#include "lib/byte_order.h"
static uint32 GetJSArrayType(u8 arrayType)
{
switch(arrayType)
{
case SCRIPT_TYPED_ARRAY_INT8:
return js::TypedArray::TYPE_INT8;
case SCRIPT_TYPED_ARRAY_UINT8:
return js::TypedArray::TYPE_UINT8;
case SCRIPT_TYPED_ARRAY_INT16:
return js::TypedArray::TYPE_INT16;
case SCRIPT_TYPED_ARRAY_UINT16:
return js::TypedArray::TYPE_UINT16;
case SCRIPT_TYPED_ARRAY_INT32:
return js::TypedArray::TYPE_INT32;
case SCRIPT_TYPED_ARRAY_UINT32:
return js::TypedArray::TYPE_UINT32;
case SCRIPT_TYPED_ARRAY_FLOAT32:
return js::TypedArray::TYPE_FLOAT32;
case SCRIPT_TYPED_ARRAY_FLOAT64:
return js::TypedArray::TYPE_FLOAT64;
case SCRIPT_TYPED_ARRAY_UINT8_CLAMPED:
return js::TypedArray::TYPE_UINT8_CLAMPED;
default:
throw PSERROR_Deserialize_ScriptError("Failed to deserialize unrecognized typed array view");
}
}
CStdDeserializer::CStdDeserializer(ScriptInterface& scriptInterface, std::istream& stream) :
m_ScriptInterface(scriptInterface), m_Stream(stream)
@ -97,6 +124,18 @@ JSObject* CStdDeserializer::GetScriptBackref(u32 tag)
return it->second;
}
u32 CStdDeserializer::ReserveScriptBackref()
{
AddScriptBackref(NULL);
return m_ScriptBackrefs.size();
}
void CStdDeserializer::SetReservedScriptBackref(u32 tag, JSObject* obj)
{
std::pair<std::map<u32, JSObject*>::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair(tag, obj));
ENSURE(!it.second);
}
void CStdDeserializer::FreeScriptBackrefs()
{
std::map<u32, JSObject*>::iterator it = m_ScriptBackrefs.begin();
@ -126,6 +165,7 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append
case SCRIPT_TYPE_ARRAY:
case SCRIPT_TYPE_OBJECT:
case SCRIPT_TYPE_OBJECT_PROTOTYPE:
{
JSObject* obj;
if (appendParent)
@ -138,13 +178,57 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append
NumberU32_Unbounded("array length", length);
obj = JS_NewArrayObject(cx, length, NULL);
}
else
else if (type == SCRIPT_TYPE_OBJECT)
{
obj = JS_NewObject(cx, NULL, NULL, NULL);
}
else // SCRIPT_TYPE_OBJECT_PROTOTYPE
{
std::wstring prototypeName;
String("proto name", prototypeName, 0, 256);
// Get constructor object
JSObject* proto = GetSerializablePrototype(prototypeName);
if (!proto)
throw PSERROR_Deserialize_ScriptError("Failed to find serializable prototype for object");
JSObject* parent = JS_GetParent(cx, proto);
if (!proto || !parent)
throw PSERROR_Deserialize_ScriptError();
obj = JS_NewObject(cx, NULL, proto, parent);
if (!obj)
throw PSERROR_Deserialize_ScriptError("JS_NewObject failed");
CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
// Does it have custom Deserialize function?
// if so, we let it handle the deserialized data, rather than adding properties directly
JSBool hasCustomDeserialize, hasCustomSerialize;
if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize) || !JS_HasProperty(cx, obj, "Deserialize", &hasCustomDeserialize))
throw PSERROR_Serialize_ScriptError("JS_HasProperty failed");
if (hasCustomDeserialize)
{
jsval serialize;
if (!JS_LookupProperty(cx, obj, "Serialize", &serialize))
throw PSERROR_Serialize_ScriptError("JS_LookupProperty failed");
bool hasNullSerialize = hasCustomSerialize && JSVAL_IS_NULL(serialize);
// If Serialize is null, we'll still call Deserialize but with undefined argument
CScriptValRooted data;
if (!hasNullSerialize)
ScriptVal("data", data);
m_ScriptInterface.CallFunctionVoid(OBJECT_TO_JSVAL(obj), "Deserialize", data);
AddScriptBackref(obj);
return OBJECT_TO_JSVAL(obj);
}
}
if (!obj)
throw PSERROR_Deserialize_ScriptError();
throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object");
CScriptValRooted objRoot(cx, OBJECT_TO_JSVAL(obj));
AddScriptBackref(obj);
@ -202,6 +286,115 @@ jsval CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JSObject* append
throw PSERROR_Deserialize_ScriptError("Invalid backref tag");
return OBJECT_TO_JSVAL(obj);
}
case SCRIPT_TYPE_OBJECT_NUMBER:
{
double value;
NumberDouble_Unbounded("value", value);
jsval val;
if (!JS_NewNumberValue(cx, value, &val))
throw PSERROR_Deserialize_ScriptError();
CScriptValRooted objRoot(cx, val);
JSObject* ctorobj;
if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Number, &ctorobj))
throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
JSObject* obj = JS_New(cx, ctorobj, 1, &val);
if (!obj)
throw PSERROR_Deserialize_ScriptError("JS_New failed");
AddScriptBackref(obj);
return OBJECT_TO_JSVAL(obj);
}
case SCRIPT_TYPE_OBJECT_STRING:
{
JSString* str;
ScriptString("value", str);
if (!str)
throw PSERROR_Deserialize_ScriptError();
jsval val = STRING_TO_JSVAL(str);
CScriptValRooted valRoot(cx, val);
JSObject* ctorobj;
if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_String, &ctorobj))
throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
JSObject* obj = JS_New(cx, ctorobj, 1, &val);
if (!obj)
throw PSERROR_Deserialize_ScriptError("JS_New failed");
AddScriptBackref(obj);
return OBJECT_TO_JSVAL(obj);
}
case SCRIPT_TYPE_OBJECT_BOOLEAN:
{
bool value;
Bool("value", value);
jsval val = BOOLEAN_TO_JSVAL(value ? JS_TRUE : JS_FALSE);
CScriptValRooted objRoot(cx, val);
JSObject* ctorobj;
if (!JS_GetClassObject(cx, JS_GetGlobalObject(cx), JSProto_Boolean, &ctorobj))
throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed");
JSObject* obj = JS_New(cx, ctorobj, 1, &val);
if (!obj)
throw PSERROR_Deserialize_ScriptError("JS_New failed");
AddScriptBackref(obj);
return OBJECT_TO_JSVAL(obj);
}
case SCRIPT_TYPE_TYPED_ARRAY:
{
u8 arrayType;
u32 byteOffset, length;
NumberU8_Unbounded("array type", arrayType);
NumberU32_Unbounded("byte offset", byteOffset);
NumberU32_Unbounded("length", length);
// To match the serializer order, we reserve the typed array's backref tag here
u32 arrayTag = ReserveScriptBackref();
// Get buffer object
jsval bufferVal = ReadScriptVal("buffer", NULL);
CScriptValRooted bufferValRoot(cx, bufferVal);
if (!JSVAL_IS_OBJECT(bufferVal))
throw PSERROR_Deserialize_ScriptError();
JSObject* bufferObj = JSVAL_TO_OBJECT(bufferVal);
if (!js_IsArrayBuffer(bufferObj))
throw PSERROR_Deserialize_ScriptError("js_IsArrayBuffer failed");
JSObject* arrayObj = js_CreateTypedArrayWithBuffer(cx, GetJSArrayType(arrayType), bufferObj, byteOffset, length);
if (!arrayObj)
throw PSERROR_Deserialize_ScriptError("js_CreateTypedArrayWithBuffer failed");
SetReservedScriptBackref(arrayTag, arrayObj);
return OBJECT_TO_JSVAL(arrayObj);
}
case SCRIPT_TYPE_ARRAY_BUFFER:
{
u32 length;
NumberU32_Unbounded("buffer length", length);
u8* bufferData = new u8[length];
RawBytes("buffer data", bufferData, length);
#if BYTE_ORDER != LITTLE_ENDIAN
#error TODO: need to convert JS ArrayBuffer data from little-endian
#endif
JSObject* bufferObj = js_CreateArrayBuffer(cx, length);
if (!bufferObj)
throw PSERROR_Deserialize_ScriptError("js_CreateArrayBuffer failed");
AddScriptBackref(bufferObj);
js::ArrayBuffer* buffer = js::ArrayBuffer::fromJSObject(bufferObj);
memcpy(buffer->data, bufferData, length);
delete[] bufferData;
return OBJECT_TO_JSVAL(bufferObj);
}
default:
throw PSERROR_Deserialize_OutOfBounds();
}
@ -252,3 +445,22 @@ void CStdDeserializer::ScriptObjectAppend(const char* name, jsval& obj)
ReadScriptVal(name, JSVAL_TO_OBJECT(obj));
}
void CStdDeserializer::SetSerializablePrototypes(std::map<std::wstring, JSObject*>& prototypes)
{
m_SerializablePrototypes = prototypes;
}
bool CStdDeserializer::IsSerializablePrototype(const std::wstring& name)
{
return m_SerializablePrototypes.find(name) != m_SerializablePrototypes.end();
}
JSObject* CStdDeserializer::GetSerializablePrototype(const std::wstring& name)
{
std::map<std::wstring, JSObject*>::iterator it = m_SerializablePrototypes.find(name);
if (it != m_SerializablePrototypes.end())
return it->second;
else
return NULL;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -39,6 +39,8 @@ public:
virtual std::istream& GetStream();
virtual void RequireBytesInStream(size_t numBytes);
virtual void SetSerializablePrototypes(std::map<std::wstring, JSObject*>& prototypes);
protected:
virtual void Get(const char* name, u8* data, size_t len);
@ -49,11 +51,18 @@ private:
virtual void AddScriptBackref(JSObject* obj);
virtual JSObject* GetScriptBackref(u32 tag);
virtual u32 ReserveScriptBackref();
virtual void SetReservedScriptBackref(u32 tag, JSObject* obj);
void FreeScriptBackrefs();
std::map<u32, JSObject*> m_ScriptBackrefs; // vector would be nice but maintaining JS roots would be harder
ScriptInterface& m_ScriptInterface;
std::istream& m_Stream;
std::map<std::wstring, JSObject*> m_SerializablePrototypes;
bool IsSerializablePrototype(const std::wstring& name);
JSObject* GetSerializablePrototype(const std::wstring& name);
};
#endif // INCLUDED_STDDESERIALIZER

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -384,12 +384,150 @@ public:
helper_script_roundtrip("unicode", "\"\\ud800\\uffff\"", "(new String(\"\\uD800\\uFFFF\"))");
}
void TODO_test_script_objects()
void test_script_objects()
{
helper_script_roundtrip("Number", "([1, new Number('2.0'), 3])", "([1, new Number(2), 3])");
helper_script_roundtrip("Number", "[1, new Number('2.0'), 3]", "[1, (new Number(2)), 3]");
helper_script_roundtrip("Number with props", "var n=new Number('2.0'); n.foo='bar'; n", "(new Number(2))");
helper_script_roundtrip("String", "['test1', new String('test2'), 'test3']", "[\"test1\", (new String(\"test2\")), \"test3\"]");
helper_script_roundtrip("String with props", "var s=new String('test'); s.foo='bar'; s", "(new String(\"test\"))");
helper_script_roundtrip("Boolean", "[new Boolean('true'), false]", "[(new Boolean(true)), false]");
helper_script_roundtrip("Boolean with props", "var b=new Boolean('true'); b.foo='bar'; b", "(new Boolean(true))");
}
void test_script_typed_arrays_simple()
{
helper_script_roundtrip("Int8Array",
"var arr=new Int8Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*32;"
"arr",
/* expected: */
"({0:32, 1:64, 2:96, 3:-128, 4:-96, 5:-64, 6:-32, 7:0})"
);
helper_script_roundtrip("Uint8Array",
"var arr=new Uint8Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*32;"
"arr",
/* expected: */
"({0:32, 1:64, 2:96, 3:128, 4:160, 5:192, 6:224, 7:0})"
);
helper_script_roundtrip("Int16Array",
"var arr=new Int16Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*8192;"
"arr",
/* expected: */
"({0:8192, 1:16384, 2:24576, 3:-32768, 4:-24576, 5:-16384, 6:-8192, 7:0})"
);
helper_script_roundtrip("Uint16Array",
"var arr=new Uint16Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*8192;"
"arr",
/* expected: */
"({0:8192, 1:16384, 2:24576, 3:32768, 4:40960, 5:49152, 6:57344, 7:0})"
);
helper_script_roundtrip("Int32Array",
"var arr=new Int32Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*536870912;"
"arr",
/* expected: */
"({0:536870912, 1:1073741824, 2:1610612736, 3:-2147483648, 4:-1610612736, 5:-1073741824, 6:-536870912, 7:0})"
);
helper_script_roundtrip("Uint32Array",
"var arr=new Uint32Array(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i+1)*536870912;"
"arr",
/* expected: */
"({0:536870912, 1:1073741824, 2:1610612736, 3:2147483648, 4:2684354560, 5:3221225472, 6:3758096384, 7:0})"
);
helper_script_roundtrip("Float32Array",
"var arr=new Float32Array(2);"
"arr[0]=3.4028234e38;"
"arr[1]=Infinity;"
"arr",
/* expected: */
"({0:3.4028234663852886e+38, 1:Infinity})"
);
helper_script_roundtrip("Float64Array",
"var arr=new Float64Array(2);"
"arr[0]=1.7976931348623157e308;"
"arr[1]=-Infinity;"
"arr",
/* expected: */
"({0:1.7976931348623157e+308, 1:-Infinity})"
);
helper_script_roundtrip("Uint8ClampedArray",
"var arr=new Uint8ClampedArray(8);"
"for(i=0; i<arr.length; ++i)"
" arr[i]=(i-2)*64;"
"arr",
/* expected: */
"({0:0, 1:0, 2:0, 3:64, 4:128, 5:192, 6:255, 7:255})"
);
}
void test_script_typed_arrays_complex()
{
helper_script_roundtrip("ArrayBuffer with Int16Array",
"var buf=new ArrayBuffer(16);"
"var int16=Int16Array(buf);"
"for(i=0; i<int16.length; ++i)"
" int16[i]=(i+1)*8192;"
"int16",
/* expected: */
"({0:8192, 1:16384, 2:24576, 3:-32768, 4:-24576, 5:-16384, 6:-8192, 7:0})"
);
helper_script_roundtrip("ArrayBuffer with Int16Array and Uint32Array",
"var buf = new ArrayBuffer(16);"
"var int16 = Int16Array(buf);"
"for(i=0; i < int16.length; ++i)"
" int16[i] = (i+1)*32768;"
"var uint32 = new Uint32Array(buf);"
"uint32[0] = 4294967295;"
"[int16, uint32]",
/* expected: */ "["
"{0:-1, 1:-1, 2:-32768, 3:0, 4:-32768, 5:0, 6:-32768, 7:0}, "
"{0:4294967295, 1:32768, 2:32768, 3:32768}"
"]"
);
helper_script_roundtrip("ArrayBuffer with complex structure",
"var buf=new ArrayBuffer(16);" // 16 bytes
"var chunk1=Int8Array(buf, 0, 4);" // 4 bytes
"var chunk2=Uint16Array(buf, 4, 2);" // 4 bytes
"var chunk3=Int32Array(buf, 8, 2);" // 8 bytes
"for(i=0; i<chunk1.length; ++i)"
" chunk1[i]=255;"
"for(i=0; i<chunk2.length; ++i)"
" chunk2[i]=65535;"
"for(i=0; i<chunk3.length; ++i)"
" chunk3[i]=4294967295;"
"var bytes = Uint8Array(buf);"
"({'struct':[chunk1, chunk2, chunk3], 'bytes':bytes})",
/* expected: */ "({"
"struct:[{0:-1, 1:-1, 2:-1, 3:-1}, {0:65535, 1:65535}, {0:-1, 1:-1}], "
"bytes:{0:255, 1:255, 2:255, 3:255, 4:255, 5:255, 6:255, 7:255, 8:255, 9:255, 10:255, 11:255, 12:255, 13:255, 14:255, 15:255}"
"})"
);
}
// TODO: prototype objects
void test_script_nonfinite()
{
helper_script_roundtrip("nonfinite", "[0, Infinity, -Infinity, NaN, -1/Infinity]", "[0, Infinity, -Infinity, NaN, -0]");