diff --git a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
index c18c64ac5..f7aba94b3 100644
--- a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
@@ -10,6 +10,7 @@ using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Json;
using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
@@ -54,7 +55,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var paramTypeName = LoadName(stream);
e.EventParamsTypes[i] = TypeUtils.GetManagedType(paramTypeName);
if (e.EventParamsTypes[i] == null)
+ {
+ Editor.LogError($"Unknown type {paramTypeName}.");
isInvalid = true;
+ }
}
if (isInvalid)
@@ -82,7 +86,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
for (int j = 0; j < paramsCount; j++)
{
stream.Read(dataBuffer, 0, e.EventParamsSizes[j]);
- key.Parameters[j] = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j]);
+ key.Parameters[j] = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), e.EventParamsTypes[j], e.EventParamsSizes[j]);
}
events[i] = new KeyframesEditor.Keyframe
@@ -125,8 +129,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
for (int j = 0; j < paramsCount; j++)
{
- Marshal.StructureToPtr(key.Parameters[j], ptr, true);
- Marshal.Copy(ptr, dataBuffer, 0, e.EventParamsSizes[j]);
+ Utilities.Utils.StructureToByteArray(key.Parameters[j], e.EventParamsSizes[j], ptr, dataBuffer);
stream.Write(dataBuffer, 0, e.EventParamsSizes[j]);
}
}
@@ -153,7 +156,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
///
/// The event key data.
///
- public struct EventKey
+ public struct EventKey : ICloneable
{
///
/// The parameters values.
@@ -178,6 +181,26 @@ namespace FlaxEditor.GUI.Timeline.Tracks
sb.Append(')');
return sb.ToString();
}
+
+ ///
+ public object Clone()
+ {
+ if (Parameters == null)
+ return new EventKey();
+
+ // Deep clone parameter values (especially boxed value types need to be duplicated to avoid referencing the same ones)
+ var parameters = new object[Parameters.Length];
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ var p = Parameters[i];
+ if (p == null || p is FlaxEngine.Object)
+ parameters[i] = Parameters[i];
+ else
+ parameters[i] = JsonSerializer.Deserialize(JsonSerializer.Serialize(p), p.GetType());
+ }
+
+ return new EventKey { Parameters = parameters };
+ }
}
///
@@ -234,6 +257,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var time = Timeline.CurrentTime;
if (!TryGetValue(out var value))
value = Events.Evaluate(time);
+ value = ((ICloneable)value).Clone();
// Find event at the current location
for (int i = Events.Keyframes.Count - 1; i >= 0; i--)
diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
index 389041381..32f1575ec 100644
--- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
@@ -77,7 +77,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
var time = stream.ReadSingle();
stream.Read(dataBuffer, 0, e.ValueSize);
- var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
+ var value = Utilities.Utils.ByteArrayToStructure(handle.AddrOfPinnedObject(), propertyType, e.ValueSize);
keyframes[i] = new KeyframesEditor.Keyframe
{
@@ -142,8 +142,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
- Marshal.StructureToPtr(keyframe.Value, ptr, true);
- Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
+ Utilities.Utils.StructureToByteArray(keyframe.Value, e.ValueSize, ptr, dataBuffer);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
}
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 949479783..688cec470 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -212,6 +212,10 @@ namespace FlaxEditor.Utilities
if (value is FlaxEngine.Object)
return value;
+ // For custom types use interface
+ if (value is ICloneable clonable)
+ return clonable.Clone();
+
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass))
{
@@ -548,6 +552,26 @@ namespace FlaxEditor.Utilities
return arr;
}
+ internal static void StructureToByteArray(object value, int valueSize, IntPtr tempBuffer, byte[] dataBuffer)
+ {
+ var valueType = value.GetType();
+ if (valueType.IsEnum)
+ {
+ var ptr = FlaxEngine.Interop.NativeInterop.ValueTypeUnboxer.GetPointer(value, valueType);
+ FlaxEngine.Utils.MemoryCopy(tempBuffer, ptr, (ulong)valueSize);
+ }
+ else
+ Marshal.StructureToPtr(value, tempBuffer, true);
+ Marshal.Copy(tempBuffer, dataBuffer, 0, valueSize);
+ }
+
+ internal static object ByteArrayToStructure(IntPtr valuePtr, Type valueType, int valueSize)
+ {
+ if (valueType.IsEnum)
+ return FlaxEngine.Interop.NativeInterop.MarshalToManaged(valuePtr, valueType);
+ return Marshal.PtrToStructure(valuePtr, valueType);
+ }
+
internal static unsafe string ReadStr(this BinaryReader stream, int check)
{
int length = stream.ReadInt32();