diff --git a/Source/Engine/Core/ISerializable.h b/Source/Engine/Core/ISerializable.h index c99051fc0..e706b4b57 100644 --- a/Source/Engine/Core/ISerializable.h +++ b/Source/Engine/Core/ISerializable.h @@ -29,6 +29,15 @@ public: /// typedef JsonWriter SerializeStream; + /// + /// Serialization callback context container. Used by OnSerializing, OnSerialized, OnDeserializing, OnDeserialized methods. + /// + struct FLAXENGINE_API CallbackContext + { + // The deserialization modifier object. + ISerializeModifier* Modifier = nullptr; + }; + public: /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 46f0ec245..4f732353e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1879,6 +1879,25 @@ namespace Flax.Build.Bindings return false; } + private static void GenerateCppAutoSerializationCallback(BuildData buildData, StringBuilder contents, ApiTypeInfo typeInfo, string callback, string context) + { + // Generate invoking custom serialization callbacks (pre/post serialization/deserialization) + if (typeInfo is ClassStructInfo classStructInfo) + { + foreach (var functionInfo in classStructInfo.Functions) + { + if (functionInfo.Name != callback) + continue; + if (functionInfo.Parameters.Count != 1 && functionInfo.Parameters[0].Type.Type != "CallbackContext") + { + Log.Warning(GetBuildErrorLocation(typeInfo, $"Invalid serialization callback parameters in function '{typeInfo.Name}::{functionInfo.Name}'")); + continue; + } + contents.AppendLine($" {typeInfo.NativeName}::{functionInfo.Name}({context});"); + } + } + } + private static void GenerateCppAutoSerialization(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ApiTypeInfo typeInfo, string typeNameNative) { var classInfo = typeInfo as ClassInfo; @@ -1892,9 +1911,14 @@ namespace Flax.Build.Bindings CppAutoSerializeProperties.Clear(); CppIncludeFiles.Add("Engine/Serialization/Serialization.h"); + // ISerializable::CallbackContext + var callbackContextSerialize = "{ nullptr }"; + var callbackContextDeserialize = "{ modifier }"; + contents.AppendLine(); contents.Append($"void {typeNameNative}::Serialize(SerializeStream& stream, const void* otherObj)").AppendLine(); contents.Append('{').AppendLine(); + GenerateCppAutoSerializationCallback(buildData, contents, typeInfo, "OnSerializing", callbackContextSerialize); if (baseType != null) contents.Append($" {baseType.FullNameNative}::Serialize(stream, otherObj);").AppendLine(); contents.Append($" SERIALIZE_GET_OTHER_OBJ({typeNameNative});").AppendLine(); @@ -1953,11 +1977,13 @@ namespace Flax.Build.Bindings } } + GenerateCppAutoSerializationCallback(buildData, contents, typeInfo, "OnSerialized", callbackContextSerialize); contents.Append('}').AppendLine(); contents.AppendLine(); contents.Append($"void {typeNameNative}::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)").AppendLine(); contents.Append('{').AppendLine(); + GenerateCppAutoSerializationCallback(buildData, contents, typeInfo, "OnDeserializing", callbackContextDeserialize); if (baseType != null) contents.Append($" {baseType.FullNameNative}::Deserialize(stream, modifier);").AppendLine(); @@ -1979,6 +2005,7 @@ namespace Flax.Build.Bindings contents.AppendLine(" }"); } + GenerateCppAutoSerializationCallback(buildData, contents, typeInfo, "OnDeserialized", callbackContextDeserialize); contents.Append('}').AppendLine(); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 14058aaf5..c22f28212 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -62,6 +62,15 @@ namespace Flax.Build.Bindings return $"{context.File.Name}({context.Tokenizer.CurrentLine}): {msg}"; } + private static string GetBuildErrorLocation(ApiTypeInfo typeInfo, string msg) + { + // Make it a link clickable in Visual Studio build output + var file = typeInfo.File; + if (file != null) + return $"{file.Name}(0): {msg}"; + return msg; + } + private static string[] ParseComment(ref ParsingContext context) { if (context.StringCache == null)