From 22d547e87b81e60d586f6832554c1d7981eda1c7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 29 Jul 2021 22:56:11 +0200 Subject: [PATCH] Refactor Variant type ManagedObject serialization to be usable for Visual Scripting --- Source/Editor/Utilities/VariantUtils.cs | 43 +++++++++++-- Source/Engine/Content/Assets/VisualScript.cpp | 21 +++++-- Source/Engine/Content/Assets/VisualScript.h | 2 +- Source/Engine/Core/Types/Variant.cpp | 40 +++++++++++- Source/Engine/Core/Types/Variant.h | 3 + Source/Engine/Scripting/ManagedCLR/MUtils.cpp | 6 +- .../Engine/Scripting/ManagedSerialization.cpp | 10 +-- .../Engine/Scripting/ManagedSerialization.h | 2 +- Source/Engine/Serialization/JsonSerializer.cs | 11 +++- Source/Engine/Serialization/Serialization.cpp | 37 ++++++++++- Source/Engine/Serialization/Stream.cpp | 62 ++++++++++++++++++- 11 files changed, 209 insertions(+), 28 deletions(-) diff --git a/Source/Editor/Utilities/VariantUtils.cs b/Source/Editor/Utilities/VariantUtils.cs index d09228c37..c5b355bcb 100644 --- a/Source/Editor/Utilities/VariantUtils.cs +++ b/Source/Editor/Utilities/VariantUtils.cs @@ -359,6 +359,7 @@ namespace FlaxEditor.Utilities Type type = null; var variantType = (VariantType)stream.ReadByte(); var typeNameLength = stream.ReadInt32(); + string typeName = string.Empty; if (typeNameLength == int.MaxValue) { typeNameLength = stream.ReadInt32(); @@ -368,7 +369,7 @@ namespace FlaxEditor.Utilities var c = stream.ReadByte(); data[i] = (byte)(c ^ 77); } - var typeName = System.Text.Encoding.ASCII.GetString(data); + typeName = System.Text.Encoding.ASCII.GetString(data); type = TypeUtils.GetManagedType(typeName); } else if (typeNameLength > 0) @@ -380,13 +381,12 @@ namespace FlaxEditor.Utilities var c = stream.ReadUInt16(); data[i] = (char)(c ^ 77); } - var typeName = new string(data); + typeName = new string(data); type = TypeUtils.GetManagedType(typeName); } switch (variantType) { case VariantType.Null: - case VariantType.ManagedObject: case VariantType.Void: return null; case VariantType.Bool: return stream.ReadByte() != 0; case VariantType.Int16: return stream.ReadInt16(); @@ -475,9 +475,26 @@ namespace FlaxEditor.Utilities var c = stream.ReadByte(); data[i] = (byte)(c ^ -14); } - var typeName = System.Text.Encoding.ASCII.GetString(data); + typeName = System.Text.Encoding.ASCII.GetString(data); return TypeUtils.GetType(typeName); } + case VariantType.ManagedObject: + { + if (type == null) + throw new Exception("Missing type of the Variant typename " + typeName); + var format = stream.ReadByte(); + if (format == 0) + { + // No Data + } + else if (format == 1) + { + // Json + var json = stream.ReadStrAnsi(-71); + return FlaxEngine.Json.JsonSerializer.Deserialize(json, type); + } + return null; + } default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType}." + (type != null ? $" Type: {type.FullName}" : string.Empty)); } } @@ -515,6 +532,7 @@ namespace FlaxEditor.Utilities break; case VariantType.Enum: case VariantType.Structure: + case VariantType.ManagedObject: stream.Write(int.MaxValue); stream.WriteStrAnsi(type.FullName, 77); break; @@ -645,6 +663,14 @@ namespace FlaxEditor.Utilities else if (value is ScriptType) stream.WriteStrAnsi(((ScriptType)value).TypeName, -14); break; + case VariantType.ManagedObject: + { + stream.Write((byte)1); + var json = FlaxEngine.Json.JsonSerializer.Serialize(value, type); + stream.WriteStrAnsi(json, -71); + break; + } + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} for type {type.FullName}."); } } @@ -700,7 +726,6 @@ namespace FlaxEditor.Utilities switch (variantType) { case VariantType.Null: - case VariantType.ManagedObject: case VariantType.Void: stream.WriteStartObject(); stream.WriteEndObject(); @@ -1089,7 +1114,13 @@ namespace FlaxEditor.Utilities else if (value is ScriptType) stream.WriteValue(((ScriptType)value).TypeName); break; - default: throw new NotImplementedException($"TODO: serialize {variantType} to Json"); + case VariantType.ManagedObject: + { + var json = FlaxEngine.Json.JsonSerializer.Serialize(value, type); + stream.WriteRaw(json); + break; + } + default: throw new ArgumentOutOfRangeException($"Unknown Variant Type {variantType} for type {type.FullName}."); } // ReSharper restore PossibleNullReferenceException diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 85f4c3c4a..366117707 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -289,7 +289,7 @@ void VisualScriptExecutor::ProcessGroupPacking(Box* box, Node* node, Value& valu if (mclass) { // Fallback to C#-only types - auto instance = mono_gchandle_get_target(structureValue.AsUint); + auto instance = (MonoObject*)structureValue.AsUint; CHECK(instance); if (structureValue.Type.Type != VariantType::ManagedObject || mono_object_get_class(instance) != mclass->GetNative()) { @@ -1768,7 +1768,18 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri auto& instanceParams = visualScript->_instances[object->GetID()].Params; instanceParams.Resize(visualScript->Graph.Parameters.Count()); for (int32 i = 0; i < instanceParams.Count(); i++) - instanceParams[i] = visualScript->Graph.Parameters[i].Value; + { + auto& param = instanceParams[i]; + param = visualScript->Graph.Parameters[i].Value; + if (param.Type.Type == VariantType::ManagedObject) + { + // Special case for C# object property in Visual Script so duplicate the object instead of cloning the reference to it + MemoryWriteStream writeStream; + writeStream.WriteVariant(param); + MemoryReadStream readStream(writeStream.GetHandle(), writeStream.GetPosition()); + readStream.ReadVariant(¶m); + } + } return object; } @@ -2135,9 +2146,9 @@ VisualScript::Instance* VisualScript::GetScriptInstance(ScriptingObject* instanc return instance ? _instances.TryGet(instance->GetID()) : nullptr; } -Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const +const Variant& VisualScript::GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const { - CHECK_RETURN(instance, Variant()); + CHECK_RETURN(instance, Variant::Null); for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++) { if (Graph.Parameters[paramIndex].Name == name) @@ -2150,7 +2161,7 @@ Variant VisualScript::GetScriptInstanceParameterValue(const StringView& name, Sc } } LOG(Warning, "Failed to get {0} parameter '{1}'", ToString(), name); - return Variant(); + return Variant::Null; } void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value) const diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 27ee21b18..e903a0b6a 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -210,7 +210,7 @@ public: /// The parameter name. /// The object instance. /// The property value. - API_FUNCTION() Variant GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const; + API_FUNCTION() const Variant& GetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance) const; /// /// Sets the value of the Visual Script parameter of the given instance. diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 730454d4b..1f45dc06f 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -57,6 +57,20 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName) } } +VariantType::VariantType(Types type, _MonoClass* klass) +{ + Type = type; + TypeName = nullptr; + if (klass) + { + MString typeName; + MUtils::GetClassFullname(klass, typeName); + int32 length = typeName.Length() + 1; + TypeName = static_cast(Allocator::Allocate(length)); + Platform::MemoryCopy(TypeName, typeName.Get(), length); + } +} + VariantType::VariantType(const VariantType& other) { Type = other.Type; @@ -501,7 +515,7 @@ Variant::Variant(Asset* v) } Variant::Variant(_MonoObject* v) - : Type(VariantType::ManagedObject) + : Type(VariantType::ManagedObject, v ? mono_object_get_class(v) : nullptr) { AsUint = v ? mono_gchandle_new(v, true) : 0; } @@ -999,6 +1013,7 @@ bool Variant::operator==(const Variant& other) const } return true; case VariantType::ManagedObject: + // TODO: invoke C# Equality logic? return AsUint == other.AsUint || mono_gchandle_get_target(AsUint) == mono_gchandle_get_target(other.AsUint); case VariantType::Typename: if (AsBlob.Data == nullptr && other.AsBlob.Data == nullptr) @@ -1443,7 +1458,7 @@ Variant::operator void*() const case VariantType::Blob: return AsBlob.Data; case VariantType::ManagedObject: - return mono_gchandle_get_target(AsUint); + return AsUint ? mono_gchandle_get_target(AsUint) : nullptr; default: return nullptr; } @@ -1484,6 +1499,11 @@ Variant::operator ScriptingObject*() const } } +Variant::operator _MonoObject*() const +{ + return AsUint ? mono_gchandle_get_target(AsUint) : nullptr; +} + Variant::operator Asset*() const { switch (Type.Type) @@ -2220,6 +2240,22 @@ void Variant::SetObject(ScriptingObject* object) object->Deleted.Bind(this); } +void Variant::SetManagedObject(_MonoObject* object) +{ + if (object) + { + if (Type.Type != VariantType::ManagedObject) + SetType(VariantType(VariantType::ManagedObject, mono_object_get_class(object))); + AsUint = mono_gchandle_new(object, true); + } + else + { + if (Type.Type != VariantType::ManagedObject || Type.TypeName) + SetType(VariantType(VariantType::ManagedObject)); + AsUint = 0; + } +} + void Variant::SetAsset(Asset* asset) { if (Type.Type != VariantType::Asset) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 07425ed20..0c8f06f15 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -92,6 +92,7 @@ public: explicit VariantType(Types type, const StringView& typeName); explicit VariantType(Types type, const StringAnsiView& typeName); + explicit VariantType(Types type, struct _MonoClass* klass); VariantType(const VariantType& other); VariantType(VariantType&& other) noexcept; @@ -272,6 +273,7 @@ public: explicit operator StringView() const; explicit operator StringAnsiView() const; explicit operator ScriptingObject*() const; + explicit operator struct _MonoObject*() const; explicit operator Asset*() const; explicit operator Vector2() const; explicit operator Vector3() const; @@ -309,6 +311,7 @@ public: void SetBlob(int32 length); void SetBlob(const void* data, int32 length); void SetObject(ScriptingObject* object); + void SetManagedObject(struct _MonoObject* object); void SetAsset(Asset* asset); String ToString() const; diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index a43220f08..80360a056 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -459,7 +459,7 @@ MonoObject* MUtils::BoxVariant(const Variant& value) return nullptr; } case VariantType::ManagedObject: - return mono_gchandle_get_target(value.AsUint); + return (MonoObject*)value; case VariantType::Typename: { const auto klass = Scripting::FindClassNative((StringAnsiView)value); @@ -666,7 +666,7 @@ MonoClass* MUtils::GetClass(const Variant& value) case VariantType::Enum: return Scripting::FindClassNative(StringAnsiView(value.Type.TypeName)); case VariantType::ManagedObject: - return GetClass(mono_gchandle_get_target(value.AsUint)); + return GetClass((MonoObject*)value); default: ; } return nullptr; @@ -755,7 +755,7 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa { if (value.Type.Type != VariantType::Enum) { - value.SetType(VariantType(VariantType::Enum, GetClassFullname(klass))); + value.SetType(VariantType(VariantType::Enum, klass)); value.AsUint64 = 0; } return &value.AsUint64; diff --git a/Source/Engine/Scripting/ManagedSerialization.cpp b/Source/Engine/Scripting/ManagedSerialization.cpp index d435fd0f2..70658222b 100644 --- a/Source/Engine/Scripting/ManagedSerialization.cpp +++ b/Source/Engine/Scripting/ManagedSerialization.cpp @@ -86,6 +86,8 @@ void ManagedSerialization::SerializeDiff(ISerializable::SerializeStream& stream, void ManagedSerialization::Deserialize(ISerializable::DeserializeStream& stream, MonoObject* object) { + if (!object) + return; ASSERT(stream.IsObject()); // Get serialized data @@ -93,15 +95,15 @@ void ManagedSerialization::Deserialize(ISerializable::DeserializeStream& stream, rapidjson_flax::Writer writer(buffer); stream.Accept(writer); - Deserialize(buffer, object); + Deserialize(StringAnsiView(buffer.GetString(), (int32)buffer.GetSize()), object); } -void ManagedSerialization::Deserialize(const rapidjson_flax::StringBuffer& data, MonoObject* object) +void ManagedSerialization::Deserialize(const StringAnsiView& data, MonoObject* object) { if (!object) return; - const char* str = data.GetString(); - const int32 len = (int32)data.GetSize(); + const char* str = data.Get(); + const int32 len = data.Length(); // Skip case {} to improve performance if (StringUtils::Compare(str, "{}") == 0) diff --git a/Source/Engine/Scripting/ManagedSerialization.h b/Source/Engine/Scripting/ManagedSerialization.h index 8f6ff035e..dd73361f6 100644 --- a/Source/Engine/Scripting/ManagedSerialization.h +++ b/Source/Engine/Scripting/ManagedSerialization.h @@ -39,5 +39,5 @@ public: /// /// The input data. /// The object to deserialize. - static void Deserialize(const rapidjson_flax::StringBuffer& data, MonoObject* object); + static void Deserialize(const StringAnsiView& data, MonoObject* object); }; diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index fe2d6b903..24c6abb6d 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -279,7 +279,16 @@ namespace FlaxEngine.Json cache.MemoryStream.Initialize(jsonBuffer, jsonLength); cache.Reader.DiscardBufferedData(); var jsonReader = new JsonTextReader(cache.Reader); - cache.JsonSerializer.Populate(jsonReader, input); + if (*jsonBuffer != (byte)'{' && input is LocalizedString asLocalizedString) + { + // Hack for objects that are serialized into sth different thant "{..}" (eg. LocalizedString can be saved as plain string if not using localization) + asLocalizedString.Id = null; + asLocalizedString.Value = jsonReader.ReadAsString(); + } + else + { + cache.JsonSerializer.Populate(jsonReader, input); + } if (!cache.JsonSerializer.CheckAdditionalContent) return; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 1b0e0b45c..adda45652 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Serialization.h" +#include "Engine/Core/Log.h" #include "Engine/Core/Types/Version.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Core/Types/DateTime.h" @@ -20,7 +21,12 @@ #include "Engine/Core/Math/Color.h" #include "Engine/Core/Math/Color32.h" #include "Engine/Core/Math/Matrix.h" +#include "Engine/Scripting/ManagedSerialization.h" +#include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Utilities/Encryption.h" +#if USE_MONO +#include +#endif void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* memberName, ISerializeModifier* modifier) { @@ -99,7 +105,6 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari { case VariantType::Null: case VariantType::Void: - case VariantType::ManagedObject: stream.StartObject(); stream.EndObject(); break; @@ -201,6 +206,9 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari else stream.String("", 0); break; + case VariantType::ManagedObject: + ManagedSerialization::Serialize(stream, (MonoObject*)v); + break; default: Platform::CheckFailed("", __FILE__, __LINE__); stream.StartObject(); @@ -217,7 +225,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian return; VariantType type; Deserialize(mType->value, type, modifier); - v.SetType(type); + v.SetType(MoveTemp(type)); const auto mValue = SERIALIZE_FIND_MEMBER(stream, "Value"); if (mValue == stream.MemberEnd()) @@ -228,7 +236,6 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian { case VariantType::Null: case VariantType::Void: - case VariantType::ManagedObject: break; case VariantType::Bool: v.AsBool = value.GetBool(); @@ -330,6 +337,30 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian CHECK(value.IsString()); v.SetTypename(value.GetStringAnsiView()); break; + case VariantType::ManagedObject: + { + auto obj = (MonoObject*)v; + if (!obj && v.Type.TypeName) + { + MonoClass* klass = MUtils::GetClass(v.Type); + if (!klass) + { + LOG(Error, "Invalid variant type {0}", v.Type); + return; + } + obj = mono_object_new(mono_domain_get(), klass); + if (!obj) + { + LOG(Error, "Failed to managed instance of the variant type {0}", v.Type); + return; + } + if (!mono_class_is_valuetype(klass)) + mono_runtime_object_init(obj); + v.SetManagedObject(obj); + } + ManagedSerialization::Deserialize(value, obj); + break; + } default: Platform::CheckFailed("", __FILE__, __LINE__); } diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index ba6281cf3..0f8e75346 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -2,13 +2,18 @@ #include "ReadStream.h" #include "WriteStream.h" +#include "JsonWriters.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" #include "Engine/Debug/DebugLog.h" +#include "Engine/Scripting/ManagedSerialization.h" +#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObjectReference.h" +#include "Engine/Scripting/ManagedCLR/MCore.h" +#include "Engine/Scripting/ManagedCLR/MUtils.h" void ReadStream::ReadStringAnsi(StringAnsi* data) { @@ -269,7 +274,6 @@ void ReadStream::ReadVariant(Variant* data) { case VariantType::Null: case VariantType::Void: - case VariantType::ManagedObject: break; case VariantType::Bool: data->AsBool = ReadBool(); @@ -335,6 +339,42 @@ void ReadStream::ReadVariant(Variant* data) data->SetObject(FindObject(id, ScriptingObject::GetStaticClass())); break; } + case VariantType::ManagedObject: + { + const byte format = ReadByte(); + if (format == 0) + { + // No data + } + else if (format == 1) + { + // Json + StringAnsi json; + ReadStringAnsi(&json, -71); + MCore::AttachThread(); + MonoClass* klass = MUtils::GetClass(data->Type); + if (!klass) + { + LOG(Error, "Invalid variant type {0}", data->Type); + return; + } + MonoObject* obj = mono_object_new(mono_domain_get(), klass); + if (!obj) + { + LOG(Error, "Failed to managed instance of the variant type {0}", data->Type); + return; + } + if (!mono_class_is_valuetype(klass)) + mono_runtime_object_init(obj); + ManagedSerialization::Deserialize(json, obj); + data->SetManagedObject(obj); + } + else + { + LOG(Error, "Invalid Variant {0) format {1}", data->Type.ToString(), format); + } + break; + } case VariantType::Structure: { int32 length; @@ -572,7 +612,6 @@ void WriteStream::WriteVariant(const Variant& data) { case VariantType::Null: case VariantType::Void: - case VariantType::ManagedObject: break; case VariantType::Bool: WriteBool(data.AsBool); @@ -674,6 +713,25 @@ void WriteStream::WriteVariant(const Variant& data) case VariantType::Typename: WriteStringAnsi((StringAnsiView)data, -14); break; + case VariantType::ManagedObject: + { + MonoObject* obj = (MonoObject*)data; + if (obj) + { + WriteByte(1); + rapidjson_flax::StringBuffer json; + CompactJsonWriter writerObj(json); + MCore::AttachThread(); + ManagedSerialization::Serialize(writerObj, obj); + WriteStringAnsi(StringAnsiView(json.GetString(), (int32)json.GetSize()), -71); + } + else + { + WriteByte(0); + } + break; + } + break; default: CRASH; }