1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "handler_p.h"
5#include <Qt3DAnimation/private/managers_p.h>
6#include <Qt3DAnimation/private/loadanimationclipjob_p.h>
7#include <Qt3DAnimation/private/findrunningclipanimatorsjob_p.h>
8#include <Qt3DAnimation/private/evaluateclipanimatorjob_p.h>
9#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
10#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
11#include <Qt3DAnimation/private/animationlogging_p.h>
12#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
13#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
14#include <Qt3DCore/private/qaspectjob_p.h>
15#include <Qt3DCore/private/vector_helper_p.h>
16
17QT_BEGIN_NAMESPACE
18
19namespace Qt3DAnimation {
20namespace Animation {
21
22Handler::Handler()
23 : m_animationClipLoaderManager(new AnimationClipLoaderManager)
24 , m_clockManager(new ClockManager)
25 , m_clipAnimatorManager(new ClipAnimatorManager)
26 , m_blendedClipAnimatorManager(new BlendedClipAnimatorManager)
27 , m_channelMappingManager(new ChannelMappingManager)
28 , m_channelMapperManager(new ChannelMapperManager)
29 , m_clipBlendNodeManager(new ClipBlendNodeManager)
30 , m_skeletonManager(new SkeletonManager)
31 , m_loadAnimationClipJob(new LoadAnimationClipJob)
32 , m_findRunningClipAnimatorsJob(new FindRunningClipAnimatorsJob)
33 , m_buildBlendTreesJob(new BuildBlendTreesJob)
34 , m_simulationTime(0)
35{
36 m_loadAnimationClipJob->setHandler(this);
37 m_findRunningClipAnimatorsJob->setHandler(this);
38 m_buildBlendTreesJob->setHandler(this);
39}
40
41Handler::~Handler()
42{
43}
44
45void Handler::setDirty(DirtyFlag flag, Qt3DCore::QNodeId nodeId)
46{
47 switch (flag) {
48 case AnimationClipDirty: {
49 QMutexLocker lock(&m_mutex);
50 const auto handle = m_animationClipLoaderManager->lookupHandle(id: nodeId);
51 if (!m_dirtyAnimationClips.contains(t: handle))
52 m_dirtyAnimationClips.push_back(t: handle);
53 break;
54 }
55
56 case ChannelMappingsDirty: {
57 break;
58 }
59
60 case ClipAnimatorDirty: {
61 QMutexLocker lock(&m_mutex);
62 const auto handle = m_clipAnimatorManager->lookupHandle(id: nodeId);
63 if (!m_dirtyClipAnimators.contains(t: handle))
64 m_dirtyClipAnimators.push_back(t: handle);
65 break;
66 }
67
68 case BlendedClipAnimatorDirty: {
69 QMutexLocker lock(&m_mutex);
70 const HBlendedClipAnimator handle = m_blendedClipAnimatorManager->lookupHandle(id: nodeId);
71 if (!m_dirtyBlendedAnimators.contains(t: handle))
72 m_dirtyBlendedAnimators.push_back(t: handle);
73 break;
74 }
75 }
76}
77
78// Called by the Jobs
79void Handler::setClipAnimatorRunning(const HClipAnimator &handle, bool running)
80{
81 QMutexLocker lock(&m_mutex);
82
83 // Add clip to running set if not already present
84 if (running && !m_runningClipAnimators.contains(t: handle)) {
85 m_runningClipAnimators.push_back(t: handle);
86 ClipAnimator *clipAnimator = m_clipAnimatorManager->data(handle);
87 if (clipAnimator)
88 clipAnimator->setStartTime(m_simulationTime);
89 }
90
91 // If being marked as not running, remove from set of running clips
92 if (!running) {
93 m_runningClipAnimators.removeAll(t: handle);
94 }
95}
96
97// Called by the Jobs
98void Handler::setBlendedClipAnimatorRunning(const HBlendedClipAnimator &handle, bool running)
99{
100 QMutexLocker lock(&m_mutex);
101
102 // Add clip to running set if not already present
103 if (running && !m_runningBlendedClipAnimators.contains(t: handle)) {
104 m_runningBlendedClipAnimators.push_back(t: handle);
105 BlendedClipAnimator *blendedClipAnimator = m_blendedClipAnimatorManager->data(handle);
106 if (blendedClipAnimator)
107 blendedClipAnimator->setStartTime(m_simulationTime);
108 }
109
110 // If being marked as not running, remove from set of running clips
111 if (!running) {
112 const auto it = std::find_if(first: m_runningBlendedClipAnimators.begin(),
113 last: m_runningBlendedClipAnimators.end(),
114 pred: [handle](const HBlendedClipAnimator &h) { return h == handle; });
115 if (it != m_runningBlendedClipAnimators.end())
116 m_runningBlendedClipAnimators.erase(pos: it);
117 }
118}
119
120// The vectors may get outdated when the application removes/deletes an
121// animator component in the meantime. Recognize this. This should be
122// relatively infrequent so in most cases the vectors will not change at all.
123void Handler::cleanupHandleList(QVector<HAnimationClip> *clips)
124{
125 for (auto it = clips->begin(); it != clips->end(); ) {
126 if (!m_animationClipLoaderManager->data(handle: *it))
127 it = clips->erase(pos: it);
128 else
129 ++it;
130 }
131}
132
133void Handler::cleanupHandleList(QVector<HClipAnimator> *animators)
134{
135 for (auto it = animators->begin(); it != animators->end(); ) {
136 if (!m_clipAnimatorManager->data(handle: *it))
137 it = animators->erase(pos: it);
138 else
139 ++it;
140 }
141}
142
143void Handler::cleanupHandleList(QVector<HBlendedClipAnimator> *animators)
144{
145 for (auto it = animators->begin(); it != animators->end(); ) {
146 if (!m_blendedClipAnimatorManager->data(handle: *it))
147 it = animators->erase(pos: it);
148 else
149 ++it;
150 }
151}
152
153std::vector<Qt3DCore::QAspectJobPtr> Handler::jobsToExecute(qint64 time)
154{
155 // Store the simulation time so we can mark the start time of
156 // animators which will allow us to calculate the local time of
157 // animation clips.
158 m_simulationTime = time;
159
160 std::vector<Qt3DCore::QAspectJobPtr> jobs;
161
162 QMutexLocker lock(&m_mutex);
163
164 // If there are any dirty animation clips that need loading,
165 // queue up a job for them
166 const bool hasLoadAnimationClipJob = !m_dirtyAnimationClips.isEmpty();
167 if (hasLoadAnimationClipJob) {
168 qCDebug(HandlerLogic) << "Added LoadAnimationClipJob";
169 cleanupHandleList(clips: &m_dirtyAnimationClips);
170 m_loadAnimationClipJob->addDirtyAnimationClips(animationClipHandles: m_dirtyAnimationClips);
171 jobs.push_back(x: m_loadAnimationClipJob);
172 m_dirtyAnimationClips.clear();
173 }
174
175 // If there are dirty clip animators, find the set that are able to
176 // run. I.e. are marked as running and have animation clips and
177 // channel mappings
178
179 const bool hasFindRunningClipAnimatorsJob = !m_dirtyClipAnimators.isEmpty();
180 if (hasFindRunningClipAnimatorsJob) {
181 qCDebug(HandlerLogic) << "Added FindRunningClipAnimatorsJob";
182 cleanupHandleList(animators: &m_dirtyClipAnimators);
183 m_findRunningClipAnimatorsJob->setDirtyClipAnimators(m_dirtyClipAnimators);
184 // Only set the dependency once
185 if (Q_UNLIKELY(m_findRunningClipAnimatorsJob->dependencies().empty()))
186 m_findRunningClipAnimatorsJob->addDependency(dependency: m_loadAnimationClipJob);
187 jobs.push_back(x: m_findRunningClipAnimatorsJob);
188 if (hasLoadAnimationClipJob)
189 m_dirtyClipAnimators.clear();
190 }
191
192 // Rebuild blending trees if a blend tree is dirty
193 const bool hasBuildBlendTreesJob = !m_dirtyBlendedAnimators.isEmpty();
194 if (hasBuildBlendTreesJob) {
195 const QVector<HBlendedClipAnimator> dirtyBlendedAnimators = Qt3DCore::moveAndClear(data&: m_dirtyBlendedAnimators);
196 m_buildBlendTreesJob->setBlendedClipAnimators(dirtyBlendedAnimators);
197 jobs.push_back(x: m_buildBlendTreesJob);
198 }
199
200 // TODO: Parallelise the animator evaluation and property building at a finer level
201
202 // If there are any running ClipAnimators, evaluate them for the current
203 // time and send property changes
204 cleanupHandleList(animators: &m_runningClipAnimators);
205 if (!m_runningClipAnimators.isEmpty()) {
206 qCDebug(HandlerLogic) << "Added EvaluateClipAnimatorJobs";
207
208 // Ensure we have a job per clip animator
209 const qsizetype oldSize = m_evaluateClipAnimatorJobs.size();
210 const qsizetype newSize = m_runningClipAnimators.size();
211 if (oldSize < newSize) {
212 m_evaluateClipAnimatorJobs.resize(size: newSize);
213 for (qsizetype i = oldSize; i < newSize; ++i) {
214 m_evaluateClipAnimatorJobs[i] = QSharedPointer<EvaluateClipAnimatorJob>::create();
215 m_evaluateClipAnimatorJobs[i]->setHandler(this);
216 }
217 }
218
219 // Set each job up with an animator to process and set dependencies
220 for (qsizetype i = 0; i < newSize; ++i) {
221 m_evaluateClipAnimatorJobs[i]->setClipAnimator(m_runningClipAnimators[i]);
222 Qt3DCore::QAspectJobPrivate::get(job: m_evaluateClipAnimatorJobs[i].data())->clearDependencies();
223 if (hasLoadAnimationClipJob)
224 m_evaluateClipAnimatorJobs[i]->addDependency(dependency: m_loadAnimationClipJob);
225 if (hasFindRunningClipAnimatorsJob)
226 m_evaluateClipAnimatorJobs[i]->addDependency(dependency: m_findRunningClipAnimatorsJob);
227 jobs.push_back(x: m_evaluateClipAnimatorJobs[i]);
228 }
229 }
230
231 // BlendClipAnimator execution
232 cleanupHandleList(animators: &m_runningBlendedClipAnimators);
233 if (!m_runningBlendedClipAnimators.isEmpty()) {
234 // Ensure we have a job per clip animator
235 const qsizetype oldSize = m_evaluateBlendClipAnimatorJobs.size();
236 const qsizetype newSize = m_runningBlendedClipAnimators.size();
237 if (oldSize < newSize) {
238 m_evaluateBlendClipAnimatorJobs.resize(size: newSize);
239 for (qsizetype i = oldSize; i < newSize; ++i) {
240 m_evaluateBlendClipAnimatorJobs[i] = QSharedPointer<EvaluateBlendClipAnimatorJob>::create();
241 m_evaluateBlendClipAnimatorJobs[i]->setHandler(this);
242 }
243 }
244
245 // Set each job up with an animator to process and set dependencies
246 for (qsizetype i = 0; i < newSize; ++i) {
247 m_evaluateBlendClipAnimatorJobs[i]->setBlendClipAnimator(m_runningBlendedClipAnimators[i]);
248 Qt3DCore::QAspectJobPrivate::get(job: m_evaluateBlendClipAnimatorJobs[i].data())->clearDependencies();
249 if (hasLoadAnimationClipJob)
250 m_evaluateBlendClipAnimatorJobs[i]->addDependency(dependency: m_loadAnimationClipJob);
251 if (hasBuildBlendTreesJob)
252 m_evaluateBlendClipAnimatorJobs[i]->addDependency(dependency: m_buildBlendTreesJob);
253 jobs.push_back(x: m_evaluateBlendClipAnimatorJobs[i]);
254 }
255 }
256
257 return jobs;
258}
259
260} // namespace Animation
261} // namespace Qt3DAnimation
262
263QT_END_NAMESPACE
264

source code of qt3d/src/animation/backend/handler.cpp