diff --git a/Source/Engine/Animations/Curve.cs b/Source/Engine/Animations/Curve.cs
index 7632653bc..4c47dbb6e 100644
--- a/Source/Engine/Animations/Curve.cs
+++ b/Source/Engine/Animations/Curve.cs
@@ -1,8 +1,10 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+using FlaxEngine.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace FlaxEngine
@@ -454,6 +456,40 @@ namespace FlaxEngine
time = end;
}
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The keyframes array.
+ /// The raw keyframes data.
+ protected static unsafe byte[] MarshalKeyframes(Keyframe[] keyframes)
+ {
+ if (keyframes == null || keyframes.Length == 0)
+ return null;
+ var keyframeSize = Unsafe.SizeOf();
+ var result = new byte[keyframes.Length * keyframeSize];
+ fixed (byte* resultPtr = result)
+ {
+ var keyframesHandle = ManagedHandle.Alloc(keyframes, GCHandleType.Pinned);
+ var keyframesPtr = Marshal.UnsafeAddrOfPinnedArrayElement(keyframes, 0);
+ Buffer.MemoryCopy((void*)keyframesPtr, resultPtr, (uint)result.Length, (uint)result.Length);
+ keyframesHandle.Free();
+ }
+ return result;
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The keyframes array.
+ protected static unsafe Keyframe[] MarshalKeyframes(byte[] raw) where Keyframe : struct
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ fixed (byte* rawPtr = raw)
+ return MemoryMarshal.Cast(new Span(rawPtr, raw.Length)).ToArray();
+ }
}
///
@@ -709,6 +745,30 @@ namespace FlaxEngine
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The curve to copy.
+ /// The raw keyframes data.
+ public static unsafe implicit operator byte[](LinearCurve curve)
+ {
+ if (curve == null)
+ return null;
+ return MarshalKeyframes(curve.Keyframes);
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The curve.
+ public static unsafe implicit operator LinearCurve(byte[] raw)
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ return new LinearCurve(MarshalKeyframes(raw));
+ }
}
///
@@ -1000,5 +1060,29 @@ namespace FlaxEngine
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The curve to copy.
+ /// The raw keyframes data.
+ public static unsafe implicit operator byte[](BezierCurve curve)
+ {
+ if (curve == null)
+ return null;
+ return MarshalKeyframes(curve.Keyframes);
+ }
+
+ ///
+ /// Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ ///
+ /// The raw keyframes data.
+ /// The curve.
+ public static unsafe implicit operator BezierCurve(byte[] raw)
+ {
+ if (raw == null || raw.Length == 0)
+ return null;
+ return new BezierCurve(MarshalKeyframes(raw));
+ }
}
}
diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h
index b259dae66..e3e0be33e 100644
--- a/Source/Engine/Animations/Curve.h
+++ b/Source/Engine/Animations/Curve.h
@@ -501,7 +501,7 @@ protected:
/// An animation spline represented by a set of keyframes, each representing an endpoint of a curve.
///
template>
-class Curve : public CurveBase
+API_CLASS(InBuild, Template, MarshalAs=Span) class Curve : public CurveBase
{
public:
typedef CurveBase Base;
@@ -763,28 +763,42 @@ public:
}
return true;
}
+
+ // Raw memory copy (used by scripting bindings - see MarshalAs tag).
+ Curve& operator=(const Span& raw)
+ {
+ ASSERT((raw.Length() % sizeof(KeyFrame)) == 0);
+ const int32 count = raw.Length() / sizeof(KeyFrame);
+ _keyframes.Resize(count, false);
+ Platform::MemoryCopy(_keyframes.Get(), raw.Get(), sizeof(KeyFrame) * count);
+ return *this;
+ }
+ operator Span()
+ {
+ return Span((const byte*)_keyframes.Get(), _keyframes.Count() * sizeof(KeyFrame));
+ }
};
///
/// An animation spline represented by a set of keyframes, each representing a value point.
///
template
-using StepCurve = Curve>;
+API_TYPEDEF() using StepCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
///
template
-using LinearCurve = Curve>;
+API_TYPEDEF() using LinearCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve.
///
template
-using HermiteCurve = Curve>;
+API_TYPEDEF() using HermiteCurve = Curve>;
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of Bezier curve.
///
template
-using BezierCurve = Curve>;
+API_TYPEDEF() using BezierCurve = Curve>;
diff --git a/Source/Engine/Level/Actors/Spline.cpp b/Source/Engine/Level/Actors/Spline.cpp
index 8dd79c8ae..bc46c3da1 100644
--- a/Source/Engine/Level/Actors/Spline.cpp
+++ b/Source/Engine/Level/Actors/Spline.cpp
@@ -478,9 +478,7 @@ void Spline::GetKeyframes(MArray* data)
void Spline::SetKeyframes(MArray* data)
{
- const int32 count = MCore::Array::GetLength(data);
- Curve.GetKeyframes().Resize(count, false);
- Platform::MemoryCopy(Curve.GetKeyframes().Get(), MCore::Array::GetAddress(data), sizeof(Keyframe) * count);
+ Curve = Span((const byte*)MCore::Array::GetAddress(data), MCore::Array::GetLength(data));
UpdateSpline();
}
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
index 551417ebf..5d03d0489 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs
@@ -20,12 +20,12 @@ namespace Flax.Build.Bindings
private static readonly Dictionary CSharpAdditionalCodeCache = new Dictionary();
#if USE_NETCORE
private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void");
- private static readonly List CSharpEventBindParams = new List() { new FunctionInfo.ParameterInfo() { Name = "bind", Type = new TypeInfo("bool") } };
+ private static readonly List CSharpEventBindParams = new List { new FunctionInfo.ParameterInfo { Name = "bind", Type = new TypeInfo("bool") } };
#endif
public static event Action GenerateCSharpTypeInternals;
- internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary()
+ internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary
{
// Language types
{ "bool", "bool" },
@@ -46,7 +46,7 @@ namespace Flax.Build.Bindings
{ "double", "double" },
};
- internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary()
+ internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary
{
// Engine types
{ "String", "string" },
@@ -632,11 +632,11 @@ namespace Flax.Build.Bindings
else if (returnValueType == "object[]")
returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
else if (functionInfo.ReturnType.Type == "Dictionary")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (returnValueType == "byte[]")
- returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
+ returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
else if (returnValueType == "bool[]")
{
// Boolean arrays does not support custom marshalling for some unknown reason
@@ -691,11 +691,15 @@ namespace Flax.Build.Bindings
parameterMarshalType += ", In"; // The usage of 'LibraryImportAttribute' does not follow recommendations. It is recommended to use explicit '[In]' and '[Out]' attributes on array parameters.
}
else if (parameterInfo.Type.Type == "Dictionary")
- parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (nativeType == "bool")
parameterMarshalType = "MarshalAs(UnmanagedType.U1)";
else if (nativeType == "char")
parameterMarshalType = "MarshalAs(UnmanagedType.I2)";
+ else if (nativeType.EndsWith("[]"))
+ {
+ parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>))";
+ }
if (!string.IsNullOrEmpty(parameterMarshalType))
contents.Append($"[{parameterMarshalType}] ");
@@ -730,7 +734,7 @@ namespace Flax.Build.Bindings
if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer")
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")";
else if (parameterInfo.Type.Type == "Dictionary")
- parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
+ parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
}
if (nativeType == "System.Type")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))";
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 138291177..a189c363a 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -980,6 +980,10 @@ namespace Flax.Build.Bindings
UseReferenceForResult = UsePassByReference(buildData, functionInfo.ReturnType, caller),
CustomParameters = new List(),
};
+ var returnType = functionInfo.ReturnType;
+ var returnApiType = FindApiTypeInfo(buildData, returnType, caller);
+ if (returnApiType != null && returnApiType.MarshalAs != null)
+ returnType = returnApiType.MarshalAs;
bool returnTypeIsContainer = false;
var returnValueConvert = GenerateCppWrapperNativeToManaged(buildData, functionInfo.ReturnType, caller, out var returnValueType, functionInfo);
@@ -999,7 +1003,7 @@ namespace Flax.Build.Bindings
});
}
#if USE_NETCORE
- else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BitArray" || functionInfo.ReturnType.Type == "BytesContainer")
+ else if (returnType.Type == "Array" || returnType.Type == "Span" || returnType.Type == "DataContainer" || returnType.Type == "BitArray" || returnType.Type == "BytesContainer")
{
returnTypeIsContainer = true;
functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo
@@ -1173,7 +1177,7 @@ namespace Flax.Build.Bindings
{
callBegin += "*__resultAsRef = ";
}
- else if (!functionInfo.ReturnType.IsVoid)
+ else if (!returnType.IsVoid)
{
if (useInlinedReturn)
callBegin += "return ";
@@ -1186,7 +1190,7 @@ namespace Flax.Build.Bindings
if (returnTypeIsContainer)
{
callReturnCount = indent;
- if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer")
+ if (returnType.Type == "Span" || returnType.Type == "BytesContainer")
callReturnCount += "*__returnCount = {0}.Length();";
else
callReturnCount += "*__returnCount = {0}.Count();";
@@ -1278,7 +1282,8 @@ namespace Flax.Build.Bindings
#if USE_NETCORE
if (!string.IsNullOrEmpty(callReturnCount))
{
- contents.Append(indent).Append("const auto& __callTemp = ").Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine();
+ var tempVar = returnTypeIsContainer && returnType != functionInfo.ReturnType ? $"{returnType} __callTemp = " : "const auto& __callTemp = ";
+ contents.Append(indent).Append(tempVar).Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine();
call = "__callTemp";
contents.Append(string.Format(callReturnCount, call));
contents.AppendLine();
@@ -1357,7 +1362,7 @@ namespace Flax.Build.Bindings
}
}
- if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !functionInfo.ReturnType.IsVoid)
+ if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !returnType.IsVoid)
{
contents.Append(indent).Append("return __result;").AppendLine();
}
@@ -1817,6 +1822,10 @@ namespace Flax.Build.Bindings
// Add includes to properly compile bindings (eg. SoftObjectReference)
GenerateCppAddFileReference(buildData, caller, typeInfo, apiTypeInfo);
+ // TODO: find a better way to reference other include files for types that have separate serialization header
+ if (typeInfo.Type.EndsWith("Curve") && typeInfo.GenericArgs != null)
+ CppIncludeFilesList.Add("Engine/Animations/CurveSerialization.h");
+
return false;
}
@@ -3126,13 +3135,12 @@ namespace Flax.Build.Bindings
// Includes
header.Clear();
CppReferencesFiles.Remove(null);
- CppIncludeFilesList.Clear();
foreach (var fileInfo in CppReferencesFiles)
CppIncludeFilesList.Add(fileInfo.Name);
CppIncludeFilesList.AddRange(CppIncludeFiles);
CppIncludeFilesList.Sort();
if (CppIncludeFilesList.Remove("Engine/Serialization/Serialization.h"))
- CppIncludeFilesList.Add("Engine/Serialization/Serialization.h");
+ CppIncludeFilesList.Add("Engine/Serialization/Serialization.h"); // Include serialization header as the last one to properly handle specialization of custom types serialization
foreach (var path in CppIncludeFilesList)
header.AppendFormat("#include \"{0}\"", path).AppendLine();
contents.Insert(headerPos, header.ToString());