Index: binaries/data/mods/_test.sim/simulation/components/test-serialize.js =================================================================== --- binaries/data/mods/_test.sim/simulation/components/test-serialize.js +++ binaries/data/mods/_test.sim/simulation/components/test-serialize.js @@ -7,7 +7,6 @@ }; TestScript1_values.prototype.GetX = function() { -// print(uneval(this)); return this.x; }; Index: binaries/data/mods/public/globalscripts/tests/test_vector.js =================================================================== --- /dev/null +++ binaries/data/mods/public/globalscripts/tests/test_vector.js @@ -0,0 +1,30 @@ +class TestClass +{ + constructor() + { + this.vec = new Vector2D(55, 1); + } +} + +function OtherClass() +{ + this.vec = new Vector2D(55, 1); +} + +function test() +{ + let test_val = new Vector2D(1, 2); + let rt = Engine.SerializationRoundTrip(test_val); + TS_ASSERT_EQUALS(test_val.constructor, rt.constructor); + TS_ASSERT_EQUALS(rt.add(test_val).x, 2); + + test_val = new OtherClass(); + rt = Engine.SerializationRoundTrip(test_val); + TS_ASSERT_EQUALS(test_val.constructor, rt.constructor); + + test_val = new TestClass(); + rt = Engine.SerializationRoundTrip(test_val); + TS_ASSERT_EQUALS(test_val.constructor, rt.constructor); +} + +test(); Index: binaries/data/mods/public/globalscripts/vector.js =================================================================== --- binaries/data/mods/public/globalscripts/vector.js +++ binaries/data/mods/public/globalscripts/vector.js @@ -18,6 +18,7 @@ return new Vector2D(this.x, this.y); }; + // Mutating 2D functions // // These functions modify the current object, Index: source/simulation2/components/tests/test_scripts.h =================================================================== --- source/simulation2/components/tests/test_scripts.h +++ source/simulation2/components/tests/test_scripts.h @@ -16,6 +16,8 @@ */ #include "simulation2/system/ComponentTest.h" +#include "simulation2/serialization/StdDeserializer.h" +#include "simulation2/serialization/StdSerializer.h" #include "ps/Filesystem.h" @@ -56,6 +58,21 @@ TS_ASSERT(componentManager->LoadScript(VfsPath(L"simulation/helpers") / pathname)); } + static JS::Value Script_SerializationRoundTrip(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue value) + { + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue val(cx); + val = value; + std::stringstream stream; + CStdSerializer serializer(*pCxPrivate->pScriptInterface, stream); + serializer.ScriptVal("", &val); + CStdDeserializer deserializer(*pCxPrivate->pScriptInterface, stream); + deserializer.ScriptVal("", &val); + return val; + } + void test_global_scripts() { if (!VfsDirectoryExists(L"globalscripts/tests/")) @@ -71,6 +88,9 @@ CSimContext context; CComponentManager componentManager(context, g_ScriptRuntime, true); ScriptTestSetup(componentManager.GetScriptInterface()); + + componentManager.GetScriptInterface().RegisterFunction ("SerializationRoundTrip"); + load_script(componentManager.GetScriptInterface(), path); } } @@ -96,6 +116,7 @@ componentManager.GetScriptInterface().RegisterFunction ("LoadComponentScript"); componentManager.GetScriptInterface().RegisterFunction ("LoadHelperScript"); + componentManager.GetScriptInterface().RegisterFunction ("SerializationRoundTrip"); componentManager.LoadComponentTypes(); Index: source/simulation2/serialization/BinarySerializer.cpp =================================================================== --- source/simulation2/serialization/BinarySerializer.cpp +++ source/simulation2/serialization/BinarySerializer.cpp @@ -155,48 +155,38 @@ { // Object class - check for user-defined prototype JS::RootedObject proto(cx); - JS_GetPrototype(cx, obj, &proto); - if (!proto) + if (!JS_GetPrototype(cx, obj, &proto)) throw PSERROR_Serialize_ScriptError("JS_GetPrototype failed"); - if (m_SerializablePrototypes->empty() || !IsSerializablePrototype(proto)) + bool treatAsStandardObject = true; + + CStrW protoName; + JS::RootedValue val(cx); + val = JS::ObjectOrNullValue(JS_GetConstructor(cx, proto)); + if (!m_ScriptInterface.GetProperty(val, "name", protoName)) + throw PSERROR_Serialize_ScriptError("GetProperty name failed."); + + if (protoName != L"Object") { - // Standard Object prototype + // Try serializing objects prototype name, so we can reconstruct them properly. + // Only do this for objects with a constructor that can be called without arguments. + JS::RootedObject global(cx, &m_ScriptInterface.GetGlobalObject().toObject()); + JS::RootedValue valueOfClass(cx); + if (!JS_GetProperty(cx, global, protoName.ToUTF8().c_str(), &valueOfClass)) + throw PSERROR_Serialize_ScriptError("JS_GetProperty failed."); + + u32 nbArgs; + m_ScriptInterface.GetProperty(val, "length", nbArgs); + if (nbArgs == 0) + treatAsStandardObject = false; + } + + if (treatAsStandardObject) 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 - bool hasCustomSerialize; - if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize)) - throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); - - if (hasCustomSerialize) - { - JS::RootedValue serialize(cx); - if (!JS_GetProperty(cx, obj, "Serialize", &serialize)) - throw PSERROR_Serialize_ScriptError("JS_GetProperty failed"); - - // If serialize is null, so don't serialize anything more - if (!serialize.isNull()) - { - JS::RootedValue data(cx); - if (!m_ScriptInterface.CallFunction(val, "Serialize", &data)) - throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed"); - HandleScriptVal(data); - } - break; - } + m_Serializer.String("proto name", protoName, 0, 256); } } else if (protokey == JSProto_Number) Index: source/simulation2/serialization/StdDeserializer.cpp =================================================================== --- source/simulation2/serialization/StdDeserializer.cpp +++ source/simulation2/serialization/StdDeserializer.cpp @@ -142,11 +142,7 @@ case SCRIPT_TYPE_OBJECT_PROTOTYPE: { JS::RootedObject obj(cx); - if (appendParent) - { - obj.set(appendParent); - } - else if (type == SCRIPT_TYPE_ARRAY) + if (type == SCRIPT_TYPE_ARRAY) { u32 length; NumberU32_Unbounded("array length", length); @@ -158,46 +154,32 @@ } else // SCRIPT_TYPE_OBJECT_PROTOTYPE { - std::wstring prototypeName; + CStrW prototypeName; String("proto name", prototypeName, 0, 256); - // Get constructor object - JS::RootedObject proto(cx); - GetSerializablePrototype(prototypeName, &proto); - if (!proto) - throw PSERROR_Deserialize_ScriptError("Failed to find serializable prototype for object"); + JS::RootedObject global(cx, &m_ScriptInterface.GetGlobalObject().toObject()); + JS::RootedValue valueOfClass(cx); + if (!JS_GetProperty(cx, global, prototypeName.ToUTF8().c_str(), &valueOfClass)) + throw PSERROR_Serialize_ScriptError("JS_GetProperty failed."); - obj.set(JS_NewObjectWithGivenProto(cx, nullptr, proto)); - if (!obj) - throw PSERROR_Deserialize_ScriptError("JS_NewObject failed"); - - // Does it have custom Deserialize function? - // if so, we let it handle the deserialized data, rather than adding properties directly - bool hasCustomDeserialize, hasCustomSerialize; - if (!JS_HasProperty(cx, obj, "Serialize", &hasCustomSerialize) || !JS_HasProperty(cx, obj, "Deserialize", &hasCustomDeserialize)) - throw PSERROR_Serialize_ScriptError("JS_HasProperty failed"); - - if (hasCustomDeserialize) + if (valueOfClass.isNullOrUndefined()) { - AddScriptBackref(obj); - - JS::RootedValue serialize(cx); - if (!JS_GetProperty(cx, obj, "Serialize", &serialize)) - throw PSERROR_Serialize_ScriptError("JS_GetProperty failed"); - bool hasNullSerialize = hasCustomSerialize && serialize.isNull(); - - // If Serialize is null, we'll still call Deserialize but with undefined argument - JS::RootedValue data(cx); - if (!hasNullSerialize) - ScriptVal("data", &data); - - JS::RootedValue objVal(cx, JS::ObjectValue(*obj)); - m_ScriptInterface.CallFunctionVoid(objVal, "Deserialize", data); - - return JS::ObjectValue(*obj); + JS::RootedValue glob(cx); + glob = JS::ObjectValue(m_ScriptInterface.GetGlobalObject().toObject()); + JS::RootedValue lexical_scopes(cx); + lexical_scopes = JS::ObjectValue(*JS_GlobalLexicalScope(m_ScriptInterface.GetGlobalObject().toObjectOrNull())); + m_ScriptInterface.GetProperty(lexical_scopes, prototypeName.ToUTF8().c_str(), &valueOfClass); } + + JS::RootedValue createdObj(cx); + if (!JS::Construct(cx, valueOfClass, JS::HandleValueArray::empty(), &createdObj)) + throw PSERROR_Deserialize_ScriptError("Deserializer failed to construct object"); + obj.set(createdObj.toObjectOrNull()); } + if (appendParent) + obj.set(appendParent); + if (!obj) throw PSERROR_Deserialize_ScriptError("Deserializer failed to create new object");