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:
parent
e58fe92892
commit
88c4e5bdd0
@ -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);
|
||||
}
|
||||
|
||||
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,6 +695,16 @@ public:
|
||||
deserializer.ScriptVal("command", val);
|
||||
m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get()));
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
CScriptVal scriptData;
|
||||
deserializer.ScriptVal("data", scriptData);
|
||||
if (m_Players[i]->m_UseSharedComponent)
|
||||
@ -684,6 +717,11 @@ public:
|
||||
LOGERROR(L"AI script deserialize() call failed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deserializer.ScriptVal("data", m_Players.back()->m_Obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getPlayerSize()
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
// 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 (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);
|
||||
|
||||
// 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)
|
||||
// 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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
@ -40,6 +40,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
|
||||
|
@ -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]");
|
||||
|
Loading…
Reference in New Issue
Block a user