diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index cb0fc86375..86cb5f0d2a 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -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("PostCommand"); m_ScriptInterface.RegisterFunction, u32, u32, u32, CAIPlayer::DumpImage>("DumpImage"); + + m_ScriptInterface.RegisterFunction("RegisterSerializablePrototype"); } ~CAIPlayer() @@ -167,6 +169,18 @@ private: tex_free(&t); } + static void RegisterSerializablePrototype(void* cbdata, std::wstring name, CScriptVal proto) + { + CAIPlayer* self = static_cast (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::iterator, bool> ret1 = m_SerializablePrototypes.insert(std::make_pair(obj, name)); + std::pair::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 m_SerializablePrototypes; + std::map m_DeserializablePrototypes; }; diff --git a/source/simulation2/serialization/BinarySerializer.cpp b/source/simulation2/serialization/BinarySerializer.cpp index b67ef57638..17f51da134 100644 --- a/source/simulation2/serialization/BinarySerializer.cpp +++ b/source/simulation2/serialization/BinarySerializer.cpp @@ -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::iterator it = m_SerializablePrototypes.find(prototype); + ENSURE(it != m_SerializablePrototypes.end()); + return it->second; +} + +void CBinarySerializerScriptImpl::SetSerializablePrototypes(std::map& prototypes) +{ + m_SerializablePrototypes = prototypes; +} diff --git a/source/simulation2/serialization/BinarySerializer.h b/source/simulation2/serialization/BinarySerializer.h index 6685c878db..d79d44b6a1 100644 --- a/source/simulation2/serialization/BinarySerializer.h +++ b/source/simulation2/serialization/BinarySerializer.h @@ -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& prototypes); private: ScriptInterface& m_ScriptInterface; ISerializer& m_Serializer; @@ -73,6 +74,11 @@ private: u32 GetScriptBackrefTag(JSObject* obj); AutoGCRooter m_Rooter; + + std::map m_SerializablePrototypes; + + bool IsSerializablePrototype(JSObject* prototype); + std::wstring GetPrototypeName(JSObject* prototype); }; /** @@ -100,6 +106,11 @@ public: { } + virtual void SetSerializablePrototypes(std::map& prototypes) + { + m_ScriptImpl->SetSerializablePrototypes(prototypes); + } + protected: /* The Put* implementations here are designed for subclasses diff --git a/source/simulation2/serialization/SerializedScriptTypes.h b/source/simulation2/serialization/SerializedScriptTypes.h index b5f5cfe197..3ffad6c513 100644 --- a/source/simulation2/serialization/SerializedScriptTypes.h +++ b/source/simulation2/serialization/SerializedScriptTypes.h @@ -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 diff --git a/source/simulation2/serialization/StdDeserializer.cpp b/source/simulation2/serialization/StdDeserializer.cpp index 85b9c6a021..93dabac5a0 100644 --- a/source/simulation2/serialization/StdDeserializer.cpp +++ b/source/simulation2/serialization/StdDeserializer.cpp @@ -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::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair(tag, obj)); + ENSURE(!it.second); +} + void CStdDeserializer::FreeScriptBackrefs() { std::map::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& 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::iterator it = m_SerializablePrototypes.find(name); + if (it != m_SerializablePrototypes.end()) + return it->second; + else + return NULL; +} diff --git a/source/simulation2/serialization/StdDeserializer.h b/source/simulation2/serialization/StdDeserializer.h index 9f88d02867..681e8bd061 100644 --- a/source/simulation2/serialization/StdDeserializer.h +++ b/source/simulation2/serialization/StdDeserializer.h @@ -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& 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 m_ScriptBackrefs; // vector would be nice but maintaining JS roots would be harder ScriptInterface& m_ScriptInterface; std::istream& m_Stream; + + std::map m_SerializablePrototypes; + + bool IsSerializablePrototype(const std::wstring& name); + JSObject* GetSerializablePrototype(const std::wstring& name); }; #endif // INCLUDED_STDDESERIALIZER diff --git a/source/simulation2/tests/test_Serializer.h b/source/simulation2/tests/test_Serializer.h index c6ebb1cc17..431392e7e1 100644 --- a/source/simulation2/tests/test_Serializer.h +++ b/source/simulation2/tests/test_Serializer.h @@ -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