From f4a352ec8de8d792b74859d60b929fbcd37f31a9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 8 Mar 2024 13:10:36 +0100 Subject: [PATCH] Add support for animating `LocalizedString` value in Scene Animation --- .../Timeline/Tracks/KeyframesPropertyTrack.cs | 88 ++++++++---- .../Editor/GUI/Timeline/Tracks/ObjectTrack.cs | 1 + .../SceneAnimations/SceneAnimation.cpp | 20 ++- .../SceneAnimations/SceneAnimation.h | 1 + .../SceneAnimations/SceneAnimationPlayer.cpp | 126 +++++++++++++----- 5 files changed, 175 insertions(+), 61 deletions(-) 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) {