diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
index dbff7267a..ed1a53981 100644
--- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
@@ -7,6 +7,8 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
+using FlaxEditor.Scripting;
+using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
- var dataBuffer = new byte[e.ValueSize];
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null)
{
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return;
}
- GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
- for (int i = 0; i < keyframesCount; i++)
+ if (e.ValueSize != 0)
{
- var time = stream.ReadSingle();
- stream.Read(dataBuffer, 0, e.ValueSize);
- var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
-
- keyframes[i] = new KeyframesEditor.Keyframe
+ // POD value type - use raw memory
+ var dataBuffer = new byte[e.ValueSize];
+ GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
+ for (int i = 0; i < keyframesCount; i++)
{
- Time = time,
- Value = value,
- };
+ var time = stream.ReadSingle();
+ stream.Read(dataBuffer, 0, e.ValueSize);
+ var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
+
+ keyframes[i] = new KeyframesEditor.Keyframe
+ {
+ Time = time,
+ Value = value,
+ };
+ }
+ handle.Free();
+ }
+ else
+ {
+ // Generic value - use Json storage (as UTF-8)
+ for (int i = 0; i < keyframesCount; i++)
+ {
+ var time = stream.ReadSingle();
+ var len = stream.ReadInt32();
+ var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
+ keyframes[i] = new KeyframesEditor.Keyframe
+ {
+ Time = time,
+ Value = value,
+ };
+ }
}
- handle.Free();
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
e.Keyframes.SetKeyframes(keyframes);
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
stream.Write(propertyTypeNameData);
stream.Write('\0');
- var dataBuffer = new byte[e.ValueSize];
- IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
- for (int i = 0; i < keyframes.Count; i++)
+ if (e.ValueSize != 0)
{
- var keyframe = keyframes[i];
- Marshal.StructureToPtr(keyframe.Value, ptr, true);
- Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
- stream.Write(keyframe.Time);
- stream.Write(dataBuffer);
+ // POD value type - use raw memory
+ var dataBuffer = new byte[e.ValueSize];
+ IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
+ 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);
+ stream.Write(keyframe.Time);
+ stream.Write(dataBuffer);
+ }
+ Marshal.FreeHGlobal(ptr);
+ }
+ else
+ {
+ // Generic value - use Json storage (as UTF-8)
+ for (int i = 0; i < keyframes.Count; i++)
+ {
+ var keyframe = keyframes[i];
+ stream.Write(keyframe.Time);
+ var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
+ var len = json?.Length ?? 0;
+ stream.Write(len);
+ if (len > 0)
+ stream.Write(Encoding.UTF8.GetBytes(json));
+ }
}
- Marshal.FreeHGlobal(ptr);
}
private byte[] _keyframesEditingStartData;
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnKeyframesEditingEnd()
{
var after = EditTrackAction.CaptureData(this);
- if (!Utils.ArraysEqual(_keyframesEditingStartData, after))
+ if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
_keyframesEditingStartData = null;
}
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The default value.
protected virtual object GetDefaultValue(Type propertyType)
{
- return Activator.CreateInstance(propertyType);
+ var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
+ if (value == null)
+ value = Activator.CreateInstance(propertyType);
+ return value;
}
///
diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs
index eccd9ab06..f6fb4b4b2 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs
@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
+ { typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
{ typeof(string), StringPropertyTrack.GetArchetype() },
};
}
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
index 20924b5cc..5d75b676c 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp
@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
track.TrackStateIndex = TrackStatesCount++;
trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1);
- const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
+ int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
+ if (trackData->ValueSize == 0)
+ {
+ // When using json data (from non-POD types) read the sum of all keyframes data
+ const int32 keyframesDataStart = stream.GetPosition();
+ for (int32 j = 0; j < trackData->KeyframesCount; j++)
+ {
+ stream.Move(); // Time
+ int32 jsonLen;
+ stream.ReadInt32(&jsonLen);
+ stream.Move(jsonLen);
+ }
+ const int32 keyframesDataEnd = stream.GetPosition();
+ stream.SetPosition(keyframesDataStart);
+ keyframesDataSize = keyframesDataEnd - keyframesDataStart;
+ }
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
+ trackRuntime->KeyframesSize = keyframesDataSize;
needsParent = true;
break;
}
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
+ ASSERT(trackData->ValueSize > 0);
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1);
trackRuntime->ValueSize = trackData->ValueSize;
+ ASSERT(trackData->ValueSize > 0);
trackRuntime->KeyframesCount = trackData->KeyframesCount;
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount);
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
index bd1091214..f6beeb416 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h
@@ -289,6 +289,7 @@ public:
/// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize).
///
void* Keyframes;
+ int32 KeyframesSize;
};
};
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
index 982428057..ce23b92a4 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
@@ -7,6 +7,7 @@
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Level/Actors/Camera.h"
#include "Engine/Serialization/Serialization.h"
+#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h"
@@ -19,6 +20,7 @@
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
+#include "Engine/Scripting/Internal/ManagedSerialization.h"
// This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
case SceneAnimation::Track::Types::KeyframesProperty:
case SceneAnimation::Track::Types::ObjectReferenceProperty:
{
- const auto trackDataKeyframes = track.GetRuntimeData();
- const int32 count = trackDataKeyframes->KeyframesCount;
+ const auto trackRuntime = track.GetRuntimeData();
+ const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
- // Find the keyframe at time
- int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize;
-#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx))
- const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
- int32 start = 0;
- int32 searchLength = count;
- while (searchLength > 0)
+ // If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access
+ if (trackRuntime->ValueSize != 0)
{
- const int32 half = searchLength >> 1;
- int32 mid = start + half;
- if (keyTime < GET_KEY_TIME(mid))
+ // Find the keyframe at time (binary search)
+ int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
+#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx))
+ const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
+ int32 start = 0;
+ int32 searchLength = count;
+ while (searchLength > 0)
{
- searchLength = half;
+ const int32 half = searchLength >> 1;
+ int32 mid = start + half;
+ if (keyTime < GET_KEY_TIME(mid))
+ {
+ searchLength = half;
+ }
+ else
+ {
+ start = mid + 1;
+ searchLength -= half + 1;
+ }
+ }
+ int32 leftKey = Math::Max(0, start - 1);
+#undef GET_KEY_TIME
+
+ // Return the value
+ void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float));
+ if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
+ {
+ // Object ref track uses Guid for object Id storage
+ Guid id = *(Guid*)value;
+ _objectsMapping.TryGet(id, id);
+ auto obj = Scripting::FindObject(id);
+ value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
+ *(void**)target = value;
}
else
{
- start = mid + 1;
- searchLength -= half + 1;
+ // POD memory
+ Platform::MemoryCopy(target, value, trackRuntime->ValueSize);
}
}
- int32 leftKey = Math::Max(0, start - 1);
-#undef GET_KEY_TIME
-
- // Return the value
- void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
- if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
- {
- Guid id = *(Guid*)value;
- _objectsMapping.TryGet(id, id);
- auto obj = Scripting::FindObject(id);
- value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
- *(void**)target = value;
- }
else
{
- Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize);
+ // Clear pointer
+ *(void**)target = nullptr;
+
+ // Find the keyframe at time (linear search)
+ MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize);
+ int32 prevKeyPos = sizeof(float);
+ int32 jsonLen;
+ for (int32 key = 0; key < count; key++)
+ {
+ float keyTime;
+ stream.ReadFloat(&keyTime);
+ if (keyTime > time)
+ break;
+ prevKeyPos = stream.GetPosition();
+ stream.ReadInt32(&jsonLen);
+ stream.Move(jsonLen);
+ }
+
+ // Read json text
+ stream.SetPosition(prevKeyPos);
+ stream.ReadInt32(&jsonLen);
+ const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen);
+
+ // Create empty value of the keyframe type
+ const auto trackData = track.GetData();
+ const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength);
+ MClass* klass = Scripting::FindClass(propertyTypeName);
+ if (!klass)
+ return false;
+ MObject* obj = MCore::Object::New(klass);
+ if (!obj)
+ return false;
+ if (!klass->IsValueType())
+ MCore::Object::Init(obj);
+
+ // Deserialize value from json
+ ManagedSerialization::Deserialize(json, obj);
+
+ // Set value
+ *(void**)target = obj;
}
break;
}
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
}
case SceneAnimation::Track::Types::StringProperty:
{
- const auto trackDataKeyframes = track.GetRuntimeData();
- const int32 count = trackDataKeyframes->KeyframesCount;
+ const auto trackRuntime = track.GetRuntimeData();
+ const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
- const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
- const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount);
- const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount);
+ const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
+ const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
+ const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
// Find the keyframe at time
#define GET_KEY_TIME(idx) keyframesTimes[idx]
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
auto& childTrack = anim->Tracks[childTrackIndex];
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
continue;
- const auto childTrackRuntimeData = childTrack.GetRuntimeData();
+ const auto childTrackRuntime = childTrack.GetRuntimeData();
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
// Cache field
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
if (!type)
continue;
MClass* mclass = MCore::Type::GetClass(type);
- childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
+ childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
if (!childTrackState.Field)
continue;
}
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{
// Set the value
- if (MCore::Type::IsPointer(valueType))
+ auto valueTypes = MCore::Type::GetType(valueType);
+ if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType))
value = (void*)*(intptr*)value;
if (state.Property)
{