Index: source/simulation2/components/CCmpMotionBall.cpp =================================================================== --- source/simulation2/components/CCmpMotionBall.cpp +++ source/simulation2/components/CCmpMotionBall.cpp @@ -55,15 +55,20 @@ virtual void Serialize(ISerializer& serialize) { - serialize.NumberFloat_Unbounded("speed x", m_SpeedX); - serialize.NumberFloat_Unbounded("speed z", m_SpeedZ); + // TODO: this is FP-unsage + serialize.NumberFixed_Unbounded("speed x", fixed::FromFloat(m_SpeedX)); + serialize.NumberFixed_Unbounded("speed z", fixed::FromFloat(m_SpeedZ)); } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); - deserialize.NumberFloat_Unbounded("speed x", m_SpeedX); - deserialize.NumberFloat_Unbounded("speed z", m_SpeedZ); + fixed x; + fixed z; + deserialize.NumberFixed_Unbounded("speed x", x); + deserialize.NumberFixed_Unbounded("speed z", z); + m_SpeedX = x.ToFloat(); + m_SpeedZ = z.ToFloat(); } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) Index: source/simulation2/serialization/BinarySerializer.h =================================================================== --- source/simulation2/serialization/BinarySerializer.h +++ source/simulation2/serialization/BinarySerializer.h @@ -76,6 +76,20 @@ } }; +class CBinarySerializerScriptImpl; + +/** + * This is a just a trick to be able to use those functions in CBinarySerializerScriptImpl + * while those remain protected, to avoid accidental serialization of floating-point values. + */ +class IBinarySerializer : public ISerializer +{ + friend class CBinarySerializerScriptImpl; +protected: + virtual void PutNumber(const char* name, float value) = 0; + virtual void PutNumber(const char* name, double value) = 0; +}; + /** * PutScriptVal implementation details. * (Split out from the main class because it's too big to be inlined.) @@ -83,13 +97,13 @@ class CBinarySerializerScriptImpl { public: - CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, ISerializer& serializer); + CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, IBinarySerializer& serializer); void ScriptString(const char* name, JS::HandleString string); void HandleScriptVal(JS::HandleValue val); private: const ScriptInterface& m_ScriptInterface; - ISerializer& m_Serializer; + IBinarySerializer& m_Serializer; JS::PersistentRootedSymbol m_ScriptBackrefSymbol; i32 m_ScriptBackrefsNext; @@ -101,7 +115,7 @@ * (We use this templated approach to allow compiler inlining.) */ template -class CBinarySerializer : public ISerializer +class CBinarySerializer : public IBinarySerializer { NONCOPYABLE(CBinarySerializer); public: @@ -167,16 +181,20 @@ m_Impl.Put(name, (const u8*)&v, sizeof(int32_t)); } +protected: virtual void PutNumber(const char* name, float value) { + //ENSURE(!std::isnan(value)); m_Impl.Put(name, (const u8*)&value, sizeof(float)); } virtual void PutNumber(const char* name, double value) { + //ENSURE(!std::isnan(value)); m_Impl.Put(name, (const u8*)&value, sizeof(double)); } +public: virtual void PutNumber(const char* name, fixed value) { int32_t v = (i32)to_le32((u32)value.GetInternalValue()); Index: source/simulation2/serialization/BinarySerializer.cpp =================================================================== --- source/simulation2/serialization/BinarySerializer.cpp +++ source/simulation2/serialization/BinarySerializer.cpp @@ -54,7 +54,7 @@ } } -CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, ISerializer& serializer) : +CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(const ScriptInterface& scriptInterface, IBinarySerializer& serializer) : m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_ScriptBackrefsNext(0) { ScriptRequest rq(m_ScriptInterface); @@ -224,13 +224,20 @@ } else if (protokey == JSProto_Number) { - // Standard Number object - m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER); - // Get primitive value double d; if (!JS::ToNumber(rq.cx, val, &d)) throw PSERROR_Serialize_ScriptError("JS::ToNumber failed"); - m_Serializer.NumberDouble_Unbounded("value", d); + + if (std::isnan(d)) + { + m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NAN); + m_Serializer.Bool("object", true); + break; + } + // Standard Number object + m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT_NUMBER); + // Get primitive value + m_Serializer.PutNumber("value", d); break; } else if (protokey == JSProto_String) @@ -345,7 +352,12 @@ // to be represented as integers. A number like 33 could be stored as integer on the computer of one player // and as double on the other player's computer. That would cause out of sync errors in multiplayer games because // their binary representation and thus the hash would be different. - + if (val == JS::NaNValue()) + { + m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NAN); + m_Serializer.Bool("object", false); + break; + } double d; d = val.toNumber(); i32 integer; @@ -358,7 +370,7 @@ else { m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE); - m_Serializer.NumberDouble_Unbounded("value", d); + m_Serializer.PutNumber("value", d); } break; } Index: source/simulation2/serialization/ISerializer.h =================================================================== --- source/simulation2/serialization/ISerializer.h +++ source/simulation2/serialization/ISerializer.h @@ -119,6 +119,7 @@ */ class ISerializer { + friend class TestSerializer; public: virtual ~ISerializer(); @@ -178,6 +179,7 @@ PutNumber(name, value); } +protected: void NumberFloat_Unbounded(const char* name, float value) ///@copydoc NumberU8_Unbounded() { PutNumber(name, value); @@ -188,6 +190,7 @@ PutNumber(name, value); } +public: void NumberFixed_Unbounded(const char* name, fixed value) ///@copydoc NumberU8_Unbounded() { PutNumber(name, value); Index: source/simulation2/serialization/SerializedScriptTypes.h =================================================================== --- source/simulation2/serialization/SerializedScriptTypes.h +++ source/simulation2/serialization/SerializedScriptTypes.h @@ -36,7 +36,8 @@ SCRIPT_TYPE_OBJECT_STRING = 13, // standard String class SCRIPT_TYPE_OBJECT_BOOLEAN = 14, // standard Boolean class SCRIPT_TYPE_OBJECT_MAP = 15, // Map class - SCRIPT_TYPE_OBJECT_SET = 16 // Set class + SCRIPT_TYPE_OBJECT_SET = 16, // Set class + SCRIPT_TYPE_NAN = 17 }; // ArrayBufferView subclasses (to avoid relying directly on the JSAPI enums) Index: source/simulation2/serialization/StdDeserializer.cpp =================================================================== --- source/simulation2/serialization/StdDeserializer.cpp +++ source/simulation2/serialization/StdDeserializer.cpp @@ -231,6 +231,30 @@ AddScriptBackref(obj); return JS::ObjectValue(*obj); } + case SCRIPT_TYPE_NAN: + { + bool object; + Bool("object", object); + JS::RootedValue rval(rq.cx, JS::NaNValue()); + if (!object) + { + if (rval.isNull()) + throw PSERROR_Deserialize_ScriptError("JS_NewNumberValue failed"); + return rval; + } + else + { + JS::RootedObject ctorobj(rq.cx); + if (!JS_GetClassObject(rq.cx, JSProto_Number, &ctorobj)) + throw PSERROR_Deserialize_ScriptError("JS_GetClassObject failed"); + + JS::RootedObject obj(rq.cx, JS_New(rq.cx, ctorobj, JS::HandleValueArray(rval))); + if (!obj) + throw PSERROR_Deserialize_ScriptError("JS_New failed"); + AddScriptBackref(obj); + return JS::ObjectValue(*obj); + } + } case SCRIPT_TYPE_OBJECT_STRING: { JS::RootedString str(rq.cx); Index: source/simulation2/tests/test_Serializer.h =================================================================== --- source/simulation2/tests/test_Serializer.h +++ source/simulation2/tests/test_Serializer.h @@ -736,20 +736,26 @@ void test_script_numbers() { const char stream[] = "\x02" // SCRIPT_TYPE_ARRAY - "\x04\0\0\0" // num props - "\x04\0\0\0" // array length + "\x07\0\0\0" // num props + "\x07\0\0\0" // array length "\x01\x01\0\0\0" "0" // "0" "\x05" "\0\0\0\x80" // SCRIPT_TYPE_INT -2147483648 (JS_INT_MIN) "\x01\x01\0\0\0" "1" // "1" "\x06" "\0\0\x20\0\0\0\xE0\xC1" // SCRIPT_TYPE_DOUBLE -2147483649 (JS_INT_MIN-1) "\x01\x01\0\0\0" "2" // "2" - "\x05" "\xFF\xFF\xFF\x7F" // SCRIPT_TYPE_INT 2147483647 (JS_INT_MAX) + "\x11" "\00" // SCRIPT_TYPE_NAN (object==false) "\x01\x01\0\0\0" "3" // "3" + "\x11" "\01" // SCRIPT_TYPE_NAN (object==true) + "\x01\x01\0\0\0" "4" // "4" + "\x0C" "\0\0\0\0\0\0\x14\x40" // SCRIPT_TYPE_OBJECT_NUMBER 5 + "\x01\x01\0\0\0" "5" // "5" + "\x05" "\xFF\xFF\xFF\x7F" // SCRIPT_TYPE_INT 2147483647 (JS_INT_MAX) + "\x01\x01\0\0\0" "6" // "6" "\x06" "\0\0\0\0\0\0\xE0\x41" // SCRIPT_TYPE_DOUBLE 2147483648 (JS_INT_MAX+1) ; - helper_script_roundtrip("numbers", "[-2147483648, -2147483649, 2.147483647e+9, 2147483648]", - "[-2147483648, -2147483649, 2147483647, 2147483648]", sizeof(stream) - 1, stream); + helper_script_roundtrip("numbers", "[-2147483648, -2147483649, Number.NaN, new Number(Number.Nan), new Number(5), 2.147483647e+9, 2147483648]", + "[-2147483648, -2147483649, NaN, (new Number(NaN)), (new Number(5)), 2147483647, 2147483648]", sizeof(stream) - 1, stream); } void test_script_exceptions()