1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "ecs_animation.h"
16 
17 #include <algorithm>
18 #include <PropertyTools/property_api_impl.inl>
19 #include <PropertyTools/property_data.h>
20 
21 #include <3d/ecs/systems/intf_animation_system.h>
22 #include <3d/ecs/systems/intf_node_system.h>
23 #include <core/intf_engine.h>
24 
25 #include <meta/api/make_callback.h>
26 
27 #include "ecs_util.h"
28 
29 using namespace BASE_NS;
30 using namespace CORE_NS;
31 using namespace CORE3D_NS;
32 using namespace META_NS;
33 
34 SCENE_BEGIN_NAMESPACE()
35 
36 namespace {
37 
EndsWith(string_view str,string_view postfix)38 bool EndsWith(string_view str, string_view postfix)
39 {
40     return str.size() >= postfix.size() && 0 == str.compare(str.size() - postfix.size(), postfix.size(), postfix);
41 }
42 
43 template<typename type>
GetArrayTypeUid()44 const BASE_NS::Uid GetArrayTypeUid()
45 {
46     return UidFromType<type[]>();
47 }
48 
RLock(IPropertyHandle & targetHandle,string_view property)49 PropertyData::PropertyOffset RLock(IPropertyHandle& targetHandle, string_view property)
50 {
51     PropertyData pData;
52 
53     string path, name;
54     auto containerHandle = ResolveContainerProperty(targetHandle, string(property), path, name);
55     if (containerHandle) {
56         if (auto po = pData.RLock(*containerHandle, name); po) {
57             return po;
58         }
59     }
60 
61     if (auto po = pData.RLock(targetHandle, property); po) {
62         return po;
63     }
64 
65     return PropertyData::PropertyOffset();
66 }
67 
ResolvePathToAnimationRoot(IEcs & ecs,Entity root,Entity target)68 BASE_NS::string ResolvePathToAnimationRoot(IEcs& ecs, Entity root, Entity target)
69 {
70     BASE_NS::string result;
71 
72     CORE3D_NS::INodeSystem* nodeSystem = GetSystem<INodeSystem>(ecs);
73     if (nodeSystem) {
74         auto rootNode = nodeSystem->GetNode(root);
75         auto node = nodeSystem->GetNode(target);
76         if (rootNode && node) {
77             if (rootNode->IsAncestorOf(*node)) {
78                 auto current = node;
79                 while (current) {
80                     // Found root node, break.
81                     if (current == rootNode) {
82                         break;
83                     }
84 
85                     // Add this to path.
86                     if (result.empty()) {
87                         result = current->GetName();
88                     } else {
89                         result = current->GetName() + "/" + result;
90                     }
91 
92                     // Process parent next.
93                     current = current->GetParent();
94                 }
95             }
96         }
97     }
98 
99     return result;
100 }
101 
UpdateTrackTargets(IEcs & ecs,Entity animationEntity,Entity rootNode)102 void UpdateTrackTargets(IEcs& ecs, Entity animationEntity, Entity rootNode)
103 {
104     auto& nameManager_ = *GetManager<INameComponentManager>(ecs);
105     auto animationManager = GetManager<IAnimationComponentManager>(ecs);
106     auto animationTrackManager = GetManager<IAnimationTrackComponentManager>(ecs);
107     auto& entityManager = ecs.GetEntityManager();
108 
109     auto* nodeSystem = GetSystem<INodeSystem>(ecs);
110     CORE_ASSERT(nodeSystem);
111     if (!nodeSystem) {
112         return;
113     }
114     auto* node = nodeSystem->GetNode(rootNode);
115     CORE_ASSERT(node);
116     if (!node) {
117         return;
118     }
119 
120     if (const ScopedHandle<const AnimationComponent> animationData = animationManager->Read(animationEntity);
121         animationData) {
122         vector<Entity> targetEntities;
123         targetEntities.reserve(animationData->tracks.size());
124         std::transform(animationData->tracks.begin(), animationData->tracks.end(), std::back_inserter(targetEntities),
125             [&nameManager = nameManager_, &node](const Entity& trackEntity) {
126                 if (auto nameHandle = nameManager.Read(trackEntity); nameHandle) {
127                     if (nameHandle->name.empty()) {
128                         return node->GetEntity();
129                     } else {
130                         if (auto targetNode = node->LookupNodeByPath(nameHandle->name); targetNode) {
131                             return targetNode->GetEntity();
132                         }
133                     }
134                 }
135                 return Entity {};
136             });
137         if (animationData->tracks.size() == targetEntities.size()) {
138             auto targetIt = targetEntities.begin();
139             for (const auto& trackEntity : animationData->tracks) {
140                 if (auto track = animationTrackManager->Write(trackEntity); track) {
141                     if (track->target) {
142                         CORE_LOG_D("AnimationTrack %s already targetted",
143                             to_hex(static_cast<const Entity&>(track->target).id).data());
144                     }
145                     track->target = entityManager.GetReferenceCounted(*targetIt);
146                 }
147                 ++targetIt;
148             }
149         }
150     }
151 }
152 } // namespace
153 
Keyframes() const154 IProperty::Ptr EcsTrackAnimation::Keyframes() const
155 {
156     return keyframes_;
157 }
158 
AddKeyframe(float timestamp,const META_NS::IAny::ConstPtr & from)159 size_t EcsTrackAnimation::AddKeyframe(float timestamp, const META_NS::IAny::ConstPtr& from)
160 {
161     // TODO: Implement.
162     return {};
163 }
164 
RemoveKeyframe(size_t index)165 bool EcsTrackAnimation::RemoveKeyframe(size_t index)
166 {
167     // TODO: Implement.
168     return false;
169 }
RemoveAllKeyframes()170 void EcsTrackAnimation::RemoveAllKeyframes()
171 {
172     // TODO: Implement.
173 }
174 
Build(const META_NS::IMetadata::Ptr &)175 bool EcsTrackAnimation::Build(const META_NS::IMetadata::Ptr&)
176 {
177     auto& registry = META_NS::GetObjectRegistry();
178 
179     auto arr = ConstructArrayProperty<float>("Keyframes", {}, META_NS::ObjectFlagBits::NONE);
180     keyframes_ = arr.GetProperty();
181     if (!keyframes_) {
182         CORE_LOG_E("Invalid property type for EcsTrackAnimation: <float>");
183         return false;
184     }
185     return true;
186 }
187 
Step(const META_NS::IClock::ConstPtr & clock)188 void EcsTrackAnimation::Step(const META_NS::IClock::ConstPtr& clock)
189 {
190     // TODO: Implement.
191 }
192 
Start()193 void EcsTrackAnimation::Start()
194 {
195     // TODO: Implement.
196     META_NS::Invoke<META_NS::IOnChanged>(OnStarted());
197 }
198 
Stop()199 void EcsTrackAnimation::Stop()
200 {
201 }
202 
Pause()203 void EcsTrackAnimation::Pause()
204 {
205 }
206 
Restart()207 void EcsTrackAnimation::Restart()
208 {
209     Stop();
210     Start();
211 }
212 
Finish()213 void EcsTrackAnimation::Finish()
214 {
215     // TODO: Implement.
216 }
217 
Seek(float position)218 void EcsTrackAnimation::Seek(float position)
219 {
220     // TODO: Implement.
221 }
222 
GetName() const223 BASE_NS::string EcsAnimation::GetName() const
224 {
225     return Name()->GetValue();
226 }
227 
Build(const META_NS::IMetadata::Ptr &)228 bool EcsAnimation::Build(const META_NS::IMetadata::Ptr& /*data*/)
229 {
230     GetSelf<META_NS::IRequiredInterfaces>()->SetRequiredInterfaces({ IEcsTrackAnimation::UID });
231 
232     // TODO: Loop count.
233     // TODO: Running.
234     Name()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnNamePropertyChanged(); }));
235     Duration()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnDurationPropertyChanged(); }));
236     Progress()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnProgressPropertyChanged(); }));
237 
238     return true;
239 }
240 
SetRootEntity(CORE_NS::Entity entity)241 bool EcsAnimation::SetRootEntity(CORE_NS::Entity entity)
242 {
243     if (EntityUtil::IsValid(root_)) {
244         // Cannot change root entity, once set.
245         return entity == root_;
246     }
247 
248     root_ = entity;
249     return true;
250 }
251 
GetRootEntity() const252 CORE_NS::Entity EcsAnimation::GetRootEntity() const
253 {
254     return root_;
255 }
256 
Retarget(CORE_NS::Entity entity)257 bool EcsAnimation::Retarget(CORE_NS::Entity entity)
258 {
259     if (ecs_) {
260         UpdateTrackTargets(*ecs_, GetEntity(), entity);
261         root_ = entity;
262 
263         return true;
264     }
265     return false;
266 }
267 
SetEntity(CORE_NS::IEcs & ecs,CORE_NS::Entity entity)268 void EcsAnimation::SetEntity(CORE_NS::IEcs& ecs, CORE_NS::Entity entity)
269 {
270     ecs_ = &ecs;
271     entity_ = ecs_->GetEntityManager().GetReferenceCounted(entity);
272     animationStateManager_ = nullptr;
273 
274     for (auto manager : ecs.GetComponentManagers()) {
275         if (manager->GetName() == "AnimationStateComponent") {
276             animationStateManager_ = manager;
277             break;
278         }
279     }
280 
281     animationManager_ = GetManager<IAnimationComponentManager>(*ecs_);
282     animationTrackManager_ = GetManager<IAnimationTrackComponentManager>(*ecs_);
283     animationInputManager_ = GetManager<IAnimationInputComponentManager>(*ecs_);
284     animationOutputManager_ = GetManager<IAnimationOutputComponentManager>(*ecs_);
285     nameManager_ = GetManager<INameComponentManager>(*ecs_);
286 
287     OnAnimationNameChanged(IEcs::ComponentListener::EventType::MODIFIED);
288     OnAnimationChanged(IEcs::ComponentListener::EventType::MODIFIED);
289 
290     if (auto ecsListener = ecsListener_.lock()) {
291         auto po = GetSelf<SCENE_NS::IEcsProxyObject>();
292         ecsListener->AddEntity(entity_, po, *nameManager_);
293         ecsListener->AddEntity(entity_, po, *animationStateManager_);
294         ecsListener->AddEntity(entity_, po, *animationManager_);
295         ecsListener->AddEntity(entity_, po, *animationTrackManager_);
296         ecsListener->AddEntity(entity_, po, *animationInputManager_);
297     }
298 
299     // If the animation root is not set, then try to resolve it.
300     if (!EntityUtil::IsValid(root_)) {
301         root_ = TryResolveAnimationRoot();
302     }
303 
304     META_ACCESS_PROPERTY(Valid)->SetValue(true);
305 }
306 
TryResolveAnimationRoot()307 CORE_NS::Entity EcsAnimation::TryResolveAnimationRoot()
308 {
309     if (!ecs_) {
310         return CORE_NS::Entity {};
311     }
312 
313     CORE3D_NS::INodeSystem* nodeSystem = GetSystem<INodeSystem>(*ecs_);
314 
315     // We will try to resolve the animation root from the first animation track.
316     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
317     if (tracks.empty()) {
318         // No way to resolve, no tracks.
319         return {};
320     }
321 
322     IEcsTrackAnimation::Ptr track = tracks.at(0);
323 
324     BASE_NS::string path;
325     if (auto nameHandle = nameManager_->Read(track->GetEntity()); nameHandle) {
326         // The name of the track actually represents the path to the animated entity.
327         path = nameHandle->name;
328     }
329 
330     ISceneNode* node = nullptr;
331     if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
332         // This is the node that is being animated by the path.
333         node = nodeSystem->GetNode(trackHandle->target);
334         if (!node) {
335             return {};
336         }
337 
338         // Resolve the path to parent.
339         while (!path.empty()) {
340             BASE_NS::string suffix = node->GetName();
341             if (EndsWith(path, suffix)) {
342                 // Remove name of this node from the path.
343                 path = string(path.substr(0, path.length() - suffix.length()));
344                 if (EndsWith(path, "/")) {
345                     path = string(path.substr(0, path.length() - 1));
346                 }
347                 // Go up the tree.
348                 node = node->GetParent();
349             } else {
350                 // Error.
351                 node = nullptr;
352                 break;
353             }
354         }
355     }
356 
357     if (node) {
358         SetRootEntity(node->GetEntity());
359     }
360 
361     return node ? node->GetEntity() : Entity {};
362 }
363 
AddAnimation(const IAnimation::Ptr & animation)364 void EcsAnimation::AddAnimation(const IAnimation::Ptr& animation)
365 {
366     GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(animation));
367     // ToDo: Can we rely that someone has added an listener for tracks
368 }
369 
RemoveAnimation(const IAnimation::Ptr & animation)370 void EcsAnimation::RemoveAnimation(const IAnimation::Ptr& animation)
371 {
372     GetSelf<META_NS::IContainer>()->Remove(interface_pointer_cast<IObject>(animation));
373     // ToDo: In principle, the tracks should deal common listener and removing parent, might be worth checking though
374 }
375 
GetAnimations() const376 vector<IAnimation::Ptr> EcsAnimation::GetAnimations() const
377 {
378     return META_NS::GetAll<IAnimation>(GetSelf<META_NS::IContainer>());
379 }
380 
DoComponentEvent(IEcs::ComponentListener::EventType type,const IComponentManager & componentManager,const CORE_NS::Entity & entity)381 void EcsAnimation::DoComponentEvent(
382     IEcs::ComponentListener::EventType type, const IComponentManager& componentManager, const CORE_NS::Entity& entity)
383 {
384     bool isAnimationNameChange = componentManager.GetUid() == INameComponentManager::UID;
385     bool isAnimationChange = componentManager.GetUid() == IAnimationComponentManager::UID;
386     bool isAnimationStateChange = componentManager.GetUid() == animationStateManager_->GetUid();
387     bool isAnimationInputChange = componentManager.GetUid() == IAnimationInputComponentManager::UID;
388     bool isAnimationTrackChange = componentManager.GetUid() == IAnimationTrackComponentManager::UID;
389 
390     if (isAnimationChange || isAnimationStateChange) {
391         // For animation and animation state, we are interested about changes concerning entity_.
392 
393         if (isAnimationChange) {
394             // Animation has changed for this entity.
395             OnAnimationChanged(type);
396         } else if (isAnimationStateChange) {
397             // Animation state has changed for this entity.
398             OnAnimationStateChanged(type);
399         }
400     } else if (isAnimationTrackChange) {
401         OnAnimationTracksChanged(type, entity);
402     } else if (isAnimationInputChange) {
403         OnAnimationInputsChanged(type, entity);
404     } else if (isAnimationNameChange) {
405         OnAnimationNameChanged(type);
406     }
407 }
408 
OnAnimationStateChanged(IEcs::ComponentListener::EventType event)409 void EcsAnimation::OnAnimationStateChanged(IEcs::ComponentListener::EventType event)
410 {
411     // This function is triggered when ECS dispatch changes at the end of the frame.
412     if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) {
413         return;
414     }
415 
416     // Propagate changes back to object's properties.
417     auto stateHandle = animationStateManager_->GetData(entity_);
418     const auto metaData = stateHandle->Owner()->MetaData();
419     for (const auto& data : metaData) {
420         if (data.name == "time") {
421             auto* time = static_cast<const float*>(
422                 static_cast<const void*>(static_cast<const uint8_t*>(stateHandle->RLock()) + data.offset));
423 
424             updateGuard_ = true;
425             auto duration = GetValue(TotalDuration()).ToSecondsFloat();
426             if (duration > 0) {
427                 SetProgress(*time / duration);
428             } else {
429                 SetProgress(0);
430             }
431             updateGuard_ = false;
432 
433             stateHandle->RUnlock();
434             break;
435         }
436     }
437 }
438 
OnAnimationNameChanged(IEcs::ComponentListener::EventType event)439 void EcsAnimation::OnAnimationNameChanged(IEcs::ComponentListener::EventType event)
440 {
441     // This function is triggered when ECS dispatch changes at the end of the frame.
442     if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) {
443         return;
444     }
445 
446     if (nameManager_->HasComponent(GetEntity())) {
447         updateGuard_ = true;
448         SetValue(Name(), nameManager_->Get(GetEntity()).name);
449         updateGuard_ = false;
450     }
451 }
452 
OnAnimationChanged(IEcs::ComponentListener::EventType event)453 void EcsAnimation::OnAnimationChanged(IEcs::ComponentListener::EventType event)
454 {
455     // This function is triggered when ECS dispatch changes at the end of the frame.
456     if (!ecs_ || event == IEcs::ComponentListener::EventType::DESTROYED) {
457         return;
458     }
459 
460     // Propagate changes back to object's properties.
461     auto handle = animationManager_->Read(entity_);
462     if (handle) {
463         updateGuard_ = true;
464         repeatCount_ = static_cast<int32_t>(handle->repeatCount);
465         // Update animation duration.
466         SetValue(META_ACCESS_PROPERTY(Duration), TimeSpan::Seconds(handle->duration));
467         updateGuard_ = false;
468 
469         // Update animation tracks, if needed.
470         if (IsAnimationTrackArrayModified()) {
471             GatherAnimationTracks();
472         }
473     }
474 }
475 
OnAnimationTracksChanged(IEcs::ComponentListener::EventType event,CORE_NS::Entity entity)476 void EcsAnimation::OnAnimationTracksChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity)
477 {
478     bool animationHasNewOrRemovedTracks = false;
479 
480     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
481 
482     // Go through all created / modified / destroyed track entities.
483     auto iterator = std::find_if(
484         tracks.begin(), tracks.end(), [entity](const auto& track) { return track->GetEntity() == entity; });
485     if (iterator != tracks.end()) {
486         // Animation track was reported changed.
487         if (event == IEcs::ComponentListener::EventType::MODIFIED) {
488             // An animation track has been modified, so update it.
489             auto index = std::distance(tracks.begin(), iterator);
490 
491             auto track = tracks.at(index);
492             OnAnimationTrackChanged(*track, entity);
493 
494         } else {
495             // If we have new or removed tracks, we will simply update all.
496             animationHasNewOrRemovedTracks = true;
497         }
498     }
499 
500     // If new or removed tracks, refresh all.
501     if (animationHasNewOrRemovedTracks) {
502         GatherAnimationTracks();
503     }
504 }
505 
OnAnimationInputsChanged(IEcs::ComponentListener::EventType event,CORE_NS::Entity entity)506 void EcsAnimation::OnAnimationInputsChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity)
507 {
508     if (!ecs_) {
509         return;
510     }
511 
512     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
513     // Go through all created / modified / destroyed track entities.
514     for (size_t i = 0; i < tracks.size(); ++i) {
515         auto track = tracks.at(i);
516         if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
517             if (trackHandle->timestamps == entity) {
518                 // Timestamps for this track have changed.
519                 OnAnimationTimestampsChanged(*track, trackHandle->timestamps);
520                 break;
521             }
522         }
523     }
524 }
525 
IsAnimationTrackArrayModified()526 bool EcsAnimation::IsAnimationTrackArrayModified()
527 {
528     if (!ecs_) {
529         return false;
530     }
531 
532     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
533     auto handle = animationManager_->Read(entity_);
534     if (handle) {
535         if (handle->tracks.size() != tracks.size()) {
536             // The amount of tracks is different.
537             return true;
538         }
539 
540         for (size_t i = 0; i < handle->tracks.size(); ++i) {
541             if (tracks.at(i)->GetEntity() != handle->tracks[i]) {
542                 // Track entity id has changed.
543                 return true;
544             }
545         }
546     }
547 
548     return false;
549 }
550 
GatherAnimationTracks()551 void EcsAnimation::GatherAnimationTracks()
552 {
553     if (!ecs_) {
554         return;
555     }
556 
557     // Take copy of the animation tracks.
558     auto oldTracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
559 
560     // Clear tracks.
561     GetSelf<META_NS::IContainer>()->RemoveAll();
562 
563     // Update animation tracks.
564     auto handle = animationManager_->Read(entity_);
565     if (handle) {
566         for (const auto& track : handle->tracks) {
567             // See if we have this track stored.
568             auto it = std::find_if(oldTracks.begin(), oldTracks.end(), [track](const auto& t) {
569                 if (t->GetEntity() == track) {
570                     return true;
571                 }
572                 return false;
573             });
574 
575             IEcsTrackAnimation::Ptr animationTrack;
576             if (it != oldTracks.end()) {
577                 animationTrack = *it;
578             } else {
579                 animationTrack =
580                     META_NS::GetObjectRegistry().Create<IEcsTrackAnimation>(SCENE_NS::ClassId::EcsTrackAnimation);
581                 interface_pointer_cast<SCENE_NS::IEcsProxyObject>(animationTrack)
582                     ->SetCommonListener(ecsListener_.lock());
583             }
584 
585             OnAnimationTrackChanged(*animationTrack, track);
586             GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(animationTrack));
587         }
588     }
589 }
590 
OnAnimationTrackChanged(IEcsTrackAnimation & track,Entity trackEntity)591 void EcsAnimation::OnAnimationTrackChanged(IEcsTrackAnimation& track, Entity trackEntity)
592 {
593     if (!ecs_ || updateGuard_) {
594         return;
595     }
596 
597     if (auto trackHandle = animationTrackManager_->Read(trackEntity); trackHandle) {
598         auto nameComponent = nameManager_->Get(trackHandle->target.operator Entity());
599 
600         track.SetEntity(trackEntity);
601 
602         auto named = interface_cast<INamed>(&track);
603         if (named) {
604             SetValue(named->Name(), nameComponent.name + '.' + trackHandle->property.data());
605         }
606         OnAnimationTimestampsChanged(track, trackHandle->timestamps);
607     }
608 }
609 
UpdateTimestamps(IEcsTrackAnimation & track,Entity timestampEntity)610 void EcsAnimation::UpdateTimestamps(IEcsTrackAnimation& track, Entity timestampEntity)
611 {
612     if (!ecs_) {
613         return;
614     }
615 
616     if (animationInputManager_->HasComponent(timestampEntity)) {
617         const auto timestamps = animationInputManager_->Get(timestampEntity);
618         const auto trackAnimation = interface_cast<ITrackAnimation>(&track);
619         const auto timedAnimation = interface_cast<ITimedAnimation>(&track);
620 
621         vector<float> times;
622         times.reserve(timestamps.timestamps.size());
623 
624         const auto duration = META_NS::GetValue(timedAnimation->Duration()).ToSecondsFloat();
625 
626         for (const auto timestamp : timestamps.timestamps) {
627             const auto offset = (duration > 0) ? (timestamp / duration) : 0.0f;
628             times.push_back(offset);
629         }
630 
631         trackAnimation->Timestamps()->SetValue(times);
632     }
633 }
OnAnimationTimestampsChanged(IEcsTrackAnimation & track,Entity timestampEntity)634 void EcsAnimation::OnAnimationTimestampsChanged(IEcsTrackAnimation& track, Entity timestampEntity)
635 {
636     if (!ecs_ || updateGuard_) {
637         return;
638     }
639 
640     UpdateTimestamps(track, timestampEntity);
641 
642     // If any of the tracks in this animation is sharing the same timestamps, then make all tracks read-only.
643     bool hasSharedTimestamps = false;
644 
645     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
646     for (const auto& other : tracks) {
647         if (other->GetEntity() != track.GetEntity()) {
648             if (auto trackHandle = animationTrackManager_->Read(other->GetEntity()); trackHandle) {
649                 if (trackHandle->timestamps == timestampEntity) {
650                     hasSharedTimestamps = true;
651                     break;
652                 }
653             }
654         }
655     }
656 
657     if (GetValue(ReadOnly()) != hasSharedTimestamps) {
658         META_ACCESS_PROPERTY(ReadOnly)->SetValue(hasSharedTimestamps);
659     }
660 }
661 
SetDuration(uint32_t ms)662 void EcsAnimation::SetDuration(uint32_t ms)
663 {
664     if (!ecs_) {
665         return;
666     }
667 
668     const auto newDuration = TimeSpan::Milliseconds(ms);
669     const auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
670     for (const auto& track : tracks) {
671         if (const auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
672             interface_cast<META_NS::ITimedAnimation>(track)->Duration()->SetValue(newDuration);
673             UpdateTimestamps(*track, trackHandle->timestamps);
674         }
675     }
676 
677     META_ACCESS_PROPERTY(Duration)->SetValue(newDuration);
678 }
679 
AddKey(IEcsTrackAnimation::Ptr track,float time)680 void EcsAnimation::AddKey(IEcsTrackAnimation::Ptr track, float time)
681 {
682     if (!ecs_) {
683         return;
684     }
685 
686     updateGuard_ = true;
687 
688     auto handle = animationTrackManager_->Read(track->GetEntity());
689     if (handle) {
690         auto propertyData = GetProperty(handle->component, handle->target, handle->property);
691         if (propertyData) {
692             SetKeyFrameData(track->GetEntity(), time, propertyData.data);
693         }
694     }
695 
696     UpdateAnimationTrackDuration(track->GetEntity());
697 
698     updateGuard_ = false;
699 
700     uint32_t timeMs = static_cast<uint32_t>(time * 1000.0f);
701     if (GetValue(Duration()).ToMilliseconds() < timeMs) {
702         META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs));
703     }
704 
705     OnAnimationTrackChanged(*track, track->GetEntity());
706 }
707 
RemoveKey(IEcsTrackAnimation::Ptr track,uint32_t index)708 void EcsAnimation::RemoveKey(IEcsTrackAnimation::Ptr track, uint32_t index)
709 {
710     if (!ecs_) {
711         return;
712     }
713 
714     updateGuard_ = true;
715 
716     // remove timestamp at pos
717     if (auto animationTrack = animationTrackManager_->Read(track->GetEntity()); animationTrack) {
718         auto stampsEntity = animationTrack->timestamps.operator Entity();
719         auto inputData = animationInputManager_->Write(stampsEntity);
720         vector<float>::iterator iit = inputData->timestamps.begin();
721         inputData->timestamps.erase(iit + index);
722         // remove data at pos
723         auto dataEntity = animationTrack->data.operator Entity();
724         if (auto outputData = animationOutputManager_->Write(dataEntity); outputData) {
725             auto targetEntity = animationTrack->target.operator Entity();
726 
727             auto manager = ecs_->GetComponentManager(animationTrack->component);
728 
729             auto target = manager->GetData(targetEntity);
730             auto poffset = RLock(*target, animationTrack->property);
731             vector<uint8_t>::iterator oit = outputData->data.begin();
732             outputData->data.erase(oit + (index * poffset.property->size),
733                 oit + (index * poffset.property->size) + poffset.property->size);
734         }
735     }
736 
737     updateGuard_ = false;
738 
739     OnAnimationTrackChanged(*track, track->GetEntity());
740 }
741 
UpdateKey(IEcsTrackAnimation::Ptr track,uint32_t oldKeyIndex,uint32_t newKeyIndex,float time)742 void EcsAnimation::UpdateKey(IEcsTrackAnimation::Ptr track, uint32_t oldKeyIndex, uint32_t newKeyIndex, float time)
743 {
744     if (!ecs_) {
745         return;
746     }
747 
748     updateGuard_ = true;
749 
750     {
751         auto animationTrack = animationTrackManager_->Read(track->GetEntity());
752         auto stampsEntity = animationTrack->timestamps.operator Entity();
753         auto inputData = animationInputManager_->Write(stampsEntity);
754 
755         auto dataEntity = animationTrack->data.operator Entity();
756         auto outputData = animationOutputManager_->Write(dataEntity);
757         auto targetEntity = animationTrack->target.operator Entity();
758 
759         vector<float>::iterator iit = inputData->timestamps.begin();
760         inputData->timestamps.erase(iit + oldKeyIndex);
761 
762         auto manager = ecs_->GetComponentManager(animationTrack->component);
763 
764         auto target = manager->GetData(targetEntity);
765         auto poffset = RLock(*target, animationTrack->property);
766         if (poffset) {
767             vector<uint8_t>::iterator oit = outputData->data.begin();
768 
769             vector<uint8_t> moveData(oit + (oldKeyIndex * poffset.property->size),
770                 oit + (oldKeyIndex * poffset.property->size) + poffset.property->size);
771 
772             outputData->data.erase(oit + (oldKeyIndex * poffset.property->size),
773                 oit + (oldKeyIndex * poffset.property->size) + poffset.property->size);
774 
775             inputData->timestamps.insert(iit + newKeyIndex, time);
776 
777             vector<uint8_t>::iterator noit = outputData->data.begin();
778             outputData->data.insert(
779                 noit + (newKeyIndex * poffset.property->size), std::begin(moveData), std::end(moveData));
780         }
781     }
782 
783     updateGuard_ = false;
784 
785     uint32_t timeMs = static_cast<uint32_t>(time * 1000.0f);
786     if (GetValue(Duration()).ToMilliseconds() < timeMs) {
787         META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs));
788     }
789 
790     OnAnimationTrackChanged(*track, track->GetEntity());
791 }
792 
OnDestroyAnimationTrack(IEcsTrackAnimation::Ptr track)793 void EcsAnimation::OnDestroyAnimationTrack(IEcsTrackAnimation::Ptr track)
794 {
795     if (ecs_) {
796         if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
797             // Destroy timestamps and keys.
798             ecs_->GetEntityManager().Destroy(trackHandle->timestamps);
799             ecs_->GetEntityManager().Destroy(trackHandle->data);
800         }
801 
802         {
803             // Remove track from animation.
804             const auto animationHandle = animationManager_->Write(GetEntity());
805             auto it = std::find(animationHandle->tracks.begin(), animationHandle->tracks.end(), track->GetEntity());
806             if (it != animationHandle->tracks.end()) {
807                 animationHandle->tracks.erase(it);
808             }
809         }
810 
811         // Destroy track.
812         ecs_->GetEntityManager().Destroy(track->GetEntity());
813     }
814 
815     GetSelf<META_NS::IContainer>()->Remove(interface_pointer_cast<IObject>(track));
816 }
817 
GetAllRelatedEntities() const818 BASE_NS::vector<CORE_NS::EntityReference> EcsAnimation::GetAllRelatedEntities() const
819 {
820     BASE_NS::vector<CORE_NS::EntityReference> result;
821 
822     if (animationManager_) {
823         auto animationHandle = animationManager_->Read(GetEntity());
824         if (animationHandle) {
825             for (auto& track : animationHandle->tracks) {
826                 const auto trackHandle = animationTrackManager_->Read(track);
827                 if (trackHandle) {
828                     if (EntityUtil::IsValid(trackHandle->timestamps)) {
829                         result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->timestamps));
830                     }
831 
832                     if (EntityUtil::IsValid(trackHandle->data)) {
833                         result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->data));
834                     }
835 
836                     result.push_back(ecs_->GetEntityManager().GetReferenceCounted(track));
837                 }
838             }
839         }
840 
841         // Add self.
842         result.push_back(ecs_->GetEntityManager().GetReferenceCounted(GetEntity()));
843     }
844 
845     return result;
846 }
847 
CreateAnimationTrack(CORE_NS::Entity rootEntity,CORE_NS::Entity targetEntity,BASE_NS::string_view fullPropertyPath)848 IEcsTrackAnimation::Ptr EcsAnimation::CreateAnimationTrack(
849     CORE_NS::Entity rootEntity, CORE_NS::Entity targetEntity, BASE_NS::string_view fullPropertyPath)
850 {
851     // Extract property path.
852     auto separatorPosition = fullPropertyPath.find_first_of('.');
853     if (!ecs_ || separatorPosition == BASE_NS::string::npos) {
854         return {};
855     }
856 
857     IComponentManager* componentManager { nullptr };
858     auto componentManagerName = fullPropertyPath.substr(0, separatorPosition);
859     auto propertyPath = fullPropertyPath.substr(separatorPosition + 1);
860 
861     for (const auto& manager : ecs_->GetComponentManagers()) {
862         if (manager->GetName() == componentManagerName) {
863             componentManager = manager;
864             break;
865         }
866     }
867 
868     if (!componentManager) {
869         return {};
870     }
871 
872     updateGuard_ = true;
873 
874     EntityReference animationTrack;
875 
876     if (IPropertyHandle* targetHandle = componentManager->GetData(targetEntity); targetHandle) {
877         if (auto po = RLock(*targetHandle, propertyPath); po) {
878             BASE_NS::string targetName = "Unnamed";
879             if (nameManager_->HasComponent(targetEntity)) {
880                 targetName = nameManager_->Get(targetEntity).name;
881             }
882 
883             const auto timeStamps = ecs_->GetEntityManager().CreateReferenceCounted();
884             {
885                 NameComponent nameComponent;
886                 nameComponent.name = "TimeStamps - " + targetName + ":" + propertyPath;
887                 nameManager_->Set(timeStamps, nameComponent);
888 
889                 animationInputManager_->Create(timeStamps);
890             }
891 
892             const auto keys = ecs_->GetEntityManager().CreateReferenceCounted();
893             {
894                 NameComponent nameComponent;
895                 nameComponent.name = "Keys - " + targetName + ":" + propertyPath;
896                 nameManager_->Set(keys, nameComponent);
897 
898                 animationOutputManager_->Create(keys);
899                 auto keysHandle = animationOutputManager_->Write(keys);
900                 keysHandle->type = po.property->type;
901             }
902 
903             const auto targetRef = ecs_->GetEntityManager().GetReferenceCounted(targetEntity);
904             animationTrack = ecs_->GetEntityManager().CreateReferenceCounted();
905             {
906                 NameComponent nameComponent;
907                 nameComponent.name = ResolvePathToAnimationRoot(*ecs_, rootEntity, targetEntity);
908                 nameManager_->Set(animationTrack, nameComponent);
909 
910                 animationTrackManager_->Create(animationTrack);
911                 auto trackHandle = animationTrackManager_->Write(animationTrack);
912                 trackHandle->component = componentManager->GetUid();
913                 trackHandle->property = propertyPath;
914                 trackHandle->interpolationMode = AnimationTrackComponent::Interpolation::LINEAR;
915                 trackHandle->timestamps = timeStamps;
916                 trackHandle->data = keys;
917                 trackHandle->target = targetRef;
918             }
919 
920             const auto animationHandle = animationManager_->Write(GetEntity());
921             animationHandle->tracks.emplace_back(animationTrack);
922         }
923     }
924 
925     updateGuard_ = false;
926 
927     IEcsTrackAnimation::Ptr track =
928         META_NS::GetObjectRegistry().Create<IEcsTrackAnimation>(SCENE_NS::ClassId::EcsTrackAnimation);
929     OnAnimationTrackChanged(*track, animationTrack);
930 
931     GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(track));
932 
933     return track;
934 }
935 
GetAnimationTrack(CORE_NS::Entity target,BASE_NS::string_view fullPropertyPath)936 IEcsTrackAnimation::Ptr EcsAnimation::GetAnimationTrack(CORE_NS::Entity target, BASE_NS::string_view fullPropertyPath)
937 {
938     // Extract property path.
939     auto separatorPosition = fullPropertyPath.find_first_of('.');
940     if (!ecs_ || separatorPosition == BASE_NS::string::npos) {
941         return {};
942     }
943 
944     auto propertyPath = fullPropertyPath.substr(separatorPosition + 1);
945 
946     auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
947     for (auto& track : tracks) {
948         const auto trackHandle = animationTrackManager_->Read(track->GetEntity());
949         if (trackHandle) {
950             if (trackHandle->target == target && trackHandle->property == propertyPath) {
951                 return track;
952             }
953         }
954     }
955 
956     return {};
957 }
958 
DestroyAnimationTrack(IEcsTrackAnimation::Ptr track)959 void EcsAnimation::DestroyAnimationTrack(IEcsTrackAnimation::Ptr track)
960 {
961     updateGuard_ = true;
962     OnDestroyAnimationTrack(track);
963     updateGuard_ = false;
964 }
965 
DestroyAllAnimationTracks()966 void EcsAnimation::DestroyAllAnimationTracks()
967 {
968     updateGuard_ = true;
969     auto container = GetSelf<META_NS::IContainer>();
970     while (container->GetSize() > 0) {
971         auto topmost = container->GetAt(0);
972         if (auto track = interface_pointer_cast<IEcsTrackAnimation>(topmost)) {
973             OnDestroyAnimationTrack(track);
974         } else {
975             container->Remove(topmost);
976         }
977     }
978     updateGuard_ = false;
979 }
980 
Destroy()981 void EcsAnimation::Destroy()
982 {
983     SCENE_PLUGIN_VERBOSE_LOG("Tearing down: %s", META_NS::GetValue(Name()).c_str());
984 
985     if (ecs_ && entity_) {
986         if (auto ecsListener = ecsListener_.lock()) {
987             auto po = GetSelf<SCENE_NS::IEcsProxyObject>();
988             ecsListener->RemoveEntity(entity_, po, *nameManager_);
989             ecsListener->RemoveEntity(entity_, po, *animationStateManager_);
990             ecsListener->RemoveEntity(entity_, po, *animationManager_);
991             ecsListener->RemoveEntity(entity_, po, *animationTrackManager_);
992             ecsListener->RemoveEntity(entity_, po, *animationInputManager_);
993         }
994     }
995 
996     root_ = {};
997     entity_ = {};
998 }
999 
OnDurationPropertyChanged()1000 void EcsAnimation::OnDurationPropertyChanged()
1001 {
1002     SetValue(META_ACCESS_PROPERTY(TotalDuration), GetValue(Duration()) * repeatCount_);
1003     if (!ecs_ || updateGuard_) {
1004         return;
1005     }
1006 
1007     auto handle = animationManager_->Write(entity_);
1008     if (handle) {
1009         handle->duration = GetValue(Duration()).ToSecondsFloat();
1010     }
1011 }
1012 
OnNamePropertyChanged()1013 void EcsAnimation::OnNamePropertyChanged()
1014 {
1015     if (!ecs_ || updateGuard_) {
1016         return;
1017     }
1018 
1019     auto handle = nameManager_->Write(entity_);
1020     if (handle) {
1021         handle->name = GetValue(Name());
1022     }
1023 }
1024 
OnProgressPropertyChanged()1025 void EcsAnimation::OnProgressPropertyChanged()
1026 {
1027     if (updateGuard_) {
1028         return;
1029     }
1030 
1031     auto progress = GetValue(Progress());
1032     auto duration = GetValue(TotalDuration()).ToMilliseconds();
1033     SetTime(uint32_t(progress * duration));
1034 }
1035 
GetEntity() const1036 CORE_NS::Entity EcsAnimation::GetEntity() const
1037 {
1038     return entity_;
1039 }
1040 
SetProgress(float progress)1041 void EcsAnimation::SetProgress(float progress)
1042 {
1043     META_ACCESS_PROPERTY(Progress)->SetValue(Base::Math::clamp(progress, 0.0f, 1.0f));
1044 }
1045 
SetTime(uint32_t value)1046 void EcsAnimation::SetTime(uint32_t value)
1047 {
1048     if (ecs_) {
1049         auto stateHandle = animationStateManager_->GetData(entity_);
1050         const auto metaData = stateHandle->Owner()->MetaData();
1051         for (const auto& data : metaData) {
1052             if (data.name == "time") {
1053                 auto* time =
1054                     static_cast<float*>(static_cast<void*>(static_cast<uint8_t*>(stateHandle->WLock()) + data.offset));
1055                 *time = value / 1000.0f;
1056                 stateHandle->WUnlock();
1057                 break;
1058             }
1059         }
1060     }
1061 }
1062 
Step(const IClock::ConstPtr & clock)1063 void EcsAnimation::Step(const IClock::ConstPtr& clock)
1064 {
1065     auto duration = GetValue(TotalDuration()).ToSecondsFloat();
1066     if (duration <= 0.0f) {
1067         return;
1068     }
1069 
1070     if (!lastFrameTime_) {
1071         lastFrameTime_ = clock->GetTime();
1072     }
1073 
1074     auto step = (clock->GetTime() - *lastFrameTime_).ToSecondsFloat();
1075     SetProgress(GetValue(Progress()) + (step / duration));
1076 
1077     lastFrameTime_ = clock->GetTime();
1078 }
1079 
Start()1080 void EcsAnimation::Start()
1081 {
1082     auto loopAnimation_ = true;
1083 
1084     if (animationManager_) {
1085         if (auto animation = animationManager_->Write(entity_); animation) {
1086             animation->state = AnimationComponent::PlaybackState::PAUSE;
1087             if (loopAnimation_) {
1088                 animation->repeatCount = AnimationComponent::REPEAT_COUNT_INFINITE;
1089             } else {
1090                 animation->repeatCount = 0;
1091             }
1092         }
1093     }
1094 
1095     lastFrameTime_.reset();
1096     META_ACCESS_PROPERTY(Running)->SetValue(true);
1097     META_NS::Invoke<META_NS::IOnChanged>(OnStarted());
1098 }
1099 
Stop()1100 void EcsAnimation::Stop()
1101 {
1102     if (animationManager_) {
1103         if (auto animation = animationManager_->Write(entity_); animation) {
1104             animation->state = AnimationComponent::PlaybackState::PAUSE;
1105         }
1106     }
1107     lastFrameTime_.reset();
1108     META_ACCESS_PROPERTY(Running)->SetValue(false);
1109 }
1110 
Pause()1111 void EcsAnimation::Pause()
1112 {
1113     if (animationManager_) {
1114         if (auto animation = animationManager_->Write(entity_); animation) {
1115             animation->state = AnimationComponent::PlaybackState::PAUSE;
1116         }
1117     }
1118     META_ACCESS_PROPERTY(Running)->SetValue(false);
1119 }
1120 
Restart()1121 void EcsAnimation::Restart()
1122 {
1123     Stop();
1124     Start();
1125 }
1126 
Finish()1127 void EcsAnimation::Finish()
1128 {
1129     Seek(1.0f);
1130 }
1131 
Seek(float position)1132 void EcsAnimation::Seek(float position)
1133 {
1134     // TODO: Implement.
1135     SetProgress(position);
1136 }
1137 
GetProperty(Uid componentUid,Entity entity,string property) const1138 EcsAnimation::Data EcsAnimation::GetProperty(Uid componentUid, Entity entity, string property) const
1139 {
1140     Data data;
1141     if (ecs_) {
1142         auto cm = ecs_->GetComponentManager(componentUid);
1143         if (IPropertyHandle* targetHandle = cm->GetData(entity); targetHandle) {
1144             if (auto po = RLock(*targetHandle, property); po) {
1145                 data.property = &(po.property->type);
1146                 const uint8_t* src = reinterpret_cast<uint8_t*>(po.offset);
1147                 data.data.resize(po.property->size);
1148                 CloneData(data.data.data(), data.data.size(), src, po.property->size);
1149             }
1150         }
1151     }
1152     return data;
1153 }
1154 
SetKeyFrameData(Entity animationTrack,float timeStamp,vector<uint8_t> valueData)1155 void EcsAnimation::SetKeyFrameData(Entity animationTrack, float timeStamp, vector<uint8_t> valueData)
1156 {
1157     if (!ecs_) {
1158         return;
1159     }
1160 
1161     BASE_ASSERT(EntityUtil::IsValid(animationTrack));
1162     auto trackHandle = animationTrackManager_->Read(animationTrack);
1163     if (trackHandle) {
1164         const auto timeStamps = trackHandle->timestamps;
1165         const auto keys = trackHandle->data;
1166         BASE_ASSERT(EntityUtil::IsValid(timeStamps));
1167         BASE_ASSERT(EntityUtil::IsValid(keys));
1168 
1169         size_t index = 0;
1170         bool replace = false;
1171         // insert/replace the timestamp
1172         // find out the position where keyframe belongs based on timestamp
1173         auto timeLineHandle = animationInputManager_->Write(timeStamps);
1174         if (timeLineHandle) {
1175             auto& data = timeLineHandle->timestamps;
1176             const size_t oldCount = data.size();
1177             const size_t newCount = oldCount + 1;
1178 
1179             for (vector<float>::iterator it = data.begin(); it < data.end(); it++) {
1180                 if (*it == timeStamp) {
1181                     replace = true;
1182                     break;
1183                 }
1184                 if (*it > timeStamp) {
1185                     break;
1186                 }
1187                 index++;
1188             }
1189 
1190             if (!replace) {
1191                 // reserve space for one more
1192                 data.reserve(newCount);
1193                 vector<float>::iterator insertIterator = data.begin() + index;
1194                 data.insert(insertIterator, timeStamp);
1195             }
1196         }
1197 
1198         auto keysHandle = animationOutputManager_->Write(keys);
1199         if (keysHandle) {
1200             auto& data = keysHandle->data;
1201             const size_t oldSize = data.size();
1202             const size_t oldCount = oldSize / valueData.size();
1203 
1204             if (!replace) {
1205                 const size_t newCount = oldCount + 1;
1206                 const size_t newSize = newCount * valueData.size();
1207                 // reserve space for one more
1208                 data.reserve(newSize);
1209                 vector<unsigned char>::iterator insertIterator = data.begin() + (index * valueData.size());
1210                 data.insert(insertIterator, valueData.begin(), valueData.end());
1211             } else {
1212                 auto dst = data.data() + index * valueData.size();
1213                 CloneData(dst, valueData.size(), valueData.data(), valueData.size());
1214             }
1215         }
1216     }
1217 }
1218 
UpdateAnimationTrackDuration(Entity animationTrack)1219 void EcsAnimation::UpdateAnimationTrackDuration(Entity animationTrack)
1220 {
1221     if (ecs_) {
1222         auto duration = GetTrackDuration(animationTrack);
1223         auto animationTrackHandle = animationTrackManager_->Read(animationTrack);
1224         if (animationTrackHandle) {
1225             auto target = animationTrackHandle->target;
1226             if (EntityUtil::IsValid(target)) {
1227                 if (IPropertyHandle* targetHandle = animationManager_->GetData(target); targetHandle) {
1228                     PropertyData pData;
1229                     if (auto po = pData.WLock(*targetHandle, "duration"); po) {
1230                         float* dst = reinterpret_cast<float*>(po.offset);
1231                         *dst = duration;
1232                     }
1233                 }
1234             }
1235         }
1236     }
1237 }
1238 
GetTrackDuration(Entity animationTrack)1239 float EcsAnimation::GetTrackDuration(Entity animationTrack)
1240 {
1241     BASE_ASSERT(EntityUtil::IsValid(animationTrack));
1242 
1243     float duration = 0.0f;
1244 
1245     if (ecs_) {
1246         auto trackHandle = animationTrackManager_->Read(animationTrack);
1247         if (trackHandle) {
1248             const auto timeStamps = trackHandle->timestamps;
1249             BASE_ASSERT(EntityUtil::IsValid(timeStamps));
1250 
1251             auto timeLineHandle = animationInputManager_->Read(timeStamps);
1252             if (timeLineHandle) {
1253                 auto& stamps = timeLineHandle->timestamps;
1254                 for (auto& t : stamps) {
1255                     if (t > duration) {
1256                         duration = t;
1257                     }
1258                 }
1259             }
1260         }
1261     }
1262 
1263     return duration;
1264 }
1265 /*
1266 bool EcsAnimation::Export(Serialization::IExportContext& context, Serialization::ClassPrimitive& value) const
1267 {
1268     return ObjectContainerFwd::Export(context, value);
1269 }
1270 
1271 bool EcsAnimation::Import(Serialization::IImportContext& context, const Serialization::ClassPrimitive& value)
1272 {
1273     return ObjectContainerFwd::Import(context, value);
1274 }
1275 */
RegisterEcsAnimationObjectType()1276 void RegisterEcsAnimationObjectType()
1277 {
1278     META_NS::GetObjectRegistry().RegisterObjectType<EcsTrackAnimation>();
1279     META_NS::GetObjectRegistry().RegisterObjectType<EcsAnimation>();
1280 }
1281 
UnregisterEcsAnimationObjectType()1282 void UnregisterEcsAnimationObjectType()
1283 {
1284     META_NS::GetObjectRegistry().UnregisterObjectType<EcsTrackAnimation>();
1285     META_NS::GetObjectRegistry().UnregisterObjectType<EcsAnimation>();
1286 }
1287 
1288 SCENE_END_NAMESPACE()
1289