1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickspriteengine_p.h"
41#include "qquicksprite_p.h"
42#include <qqmlinfo.h>
43#include <qqml.h>
44#include <QDebug>
45#include <QPainter>
46#include <QRandomGenerator>
47#include <QSet>
48#include <QtGui/qopengl.h>
49#include <QOpenGLFunctions>
50
51QT_BEGIN_NAMESPACE
52
53/*
54 \internal Stochastic/Sprite engine implementation docs
55
56 Nomenclature: 'thing' refers to an instance of a running sprite or state. It could be renamed.
57 States and Transitions are referred to in the state machine sense here, NOT in the QML sense.
58
59 The Stochastic State engine takes states with stochastic state transitions defined and transitions them.
60 When a state is started, it's added to a list of pending updates sorted by their time they want to update.
61 An external driver calls the update function with an elapsed time, which becomes the new time offset.
62 The pending update stack is popped until all entries are past the current time, which simulates all intervening time.
63
64 The Sprite Engine subclass has two major differences. Firstly all states are sprites (and there's a new vector with them
65 cast to sprite). Secondly, it chops up images and states to fit a texture friendly format.
66 Before the Sprite Engine starts running, its user requests a texture assembled from all the sprite images. This
67 texture is made by pasting the sprites into one image, with one sprite animation per row (in the future it is planned to have
68 arbitrary X/Y start ends, but they will still be assembled and recorded here and still have to be contiguous lines).
69 This cut-up allows the users to calcuate frame positions with a texture percentage width and elapsed time.
70 It also means that large sprites cover multiple lines to fit inside the texture memory limit (which is a square).
71
72 Large sprites covering multiple lines breaks this simple interface for the users, so each line is treated as a pseudostate
73 and it's mostly hidden from the spriteengine users (except that they'll get advanced signals where the state is the same
74 but the visual parameters changed). These are not real states because that would get very complex with bindings. Instead,
75 when sprite attributes are requested from a sprite that has multiple pseudostates, it returns the values for the psuedostate
76 it is in. State advancement is intercepted and hollow for pseudostates, except the last one. The last one transitions as the
77 state normally does.
78*/
79
80static const int NINF = -1000000;//magic number for random start time - should be more negative than a single realistic animation duration
81//#define SPRITE_IMAGE_DEBUG
82#ifdef SPRITE_IMAGE_DEBUG
83#include <QFile>
84#include <QDir>
85#endif
86/* TODO:
87 make sharable?
88 solve the state data initialization/transfer issue so as to not need to make friends
89*/
90
91QQuickStochasticEngine::QQuickStochasticEngine(QObject *parent) :
92 QObject(parent), m_timeOffset(0), m_addAdvance(false)
93{
94 //Default size 1
95 setCount(1);
96}
97
98QQuickStochasticEngine::QQuickStochasticEngine(const QList<QQuickStochasticState *> &states, QObject *parent) :
99 QObject(parent), m_states(states), m_timeOffset(0), m_addAdvance(false)
100{
101 //Default size 1
102 setCount(1);
103}
104
105QQuickStochasticEngine::~QQuickStochasticEngine()
106{
107}
108
109QQuickSpriteEngine::QQuickSpriteEngine(QObject *parent)
110 : QQuickStochasticEngine(parent), m_startedImageAssembly(false), m_loaded(false)
111{
112}
113
114QQuickSpriteEngine::QQuickSpriteEngine(const QList<QQuickSprite *> &sprites, QObject *parent)
115 : QQuickSpriteEngine(parent)
116{
117 for (QQuickSprite* sprite : sprites)
118 m_states << (QQuickStochasticState*)sprite;
119}
120
121QQuickSpriteEngine::~QQuickSpriteEngine()
122{
123}
124
125
126int QQuickSpriteEngine::maxFrames() const
127{
128 return m_maxFrames;
129}
130
131/* States too large to fit in one row are split into multiple rows
132 This is more efficient for the implementation, but should remain an implementation detail (invisible from QML)
133 Therefore the below functions abstract sprite from the viewpoint of classes that pass the details onto shaders
134 But States maintain their listed index for internal structures
135TODO: All these calculations should be pre-calculated and cached during initialization for a significant performance boost
136TODO: Above idea needs to have the varying duration offset added to it
137*/
138//TODO: Should these be adding advanceTime as well? But only if advanceTime was added to your startTime...
139/*
140 To get these working with duration=-1, m_startTimes will be messed with should duration=-1
141 m_startTimes will be set in advance/restart to 0->(m_framesPerRow-1) and can be used directly as extra.
142 This makes it 'frame' instead, but is more memory efficient than two arrays and less hideous than a vector of unions.
143*/
144int QQuickSpriteEngine::pseudospriteProgress(int sprite, int state, int* rowDuration) const
145{
146 int myRowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow / m_sprites[state]->m_frames;
147 if (rowDuration)
148 *rowDuration = myRowDuration;
149
150 if (m_sprites[state]->reverse()) //shift start-time back by the amount of time the first frame is smaller than rowDuration
151 return (m_timeOffset - (m_startTimes[sprite] - (myRowDuration - (m_duration[sprite] % myRowDuration))) )
152 / myRowDuration;
153 else
154 return (m_timeOffset - m_startTimes[sprite]) / myRowDuration;
155}
156
157int QQuickSpriteEngine::spriteState(int sprite) const
158{
159 if (!m_loaded)
160 return 0;
161 int state = m_things[sprite];
162 if (!m_sprites[state]->m_generatedCount)
163 return state;
164
165 int extra;
166 if (m_sprites[state]->frameSync())
167 extra = m_startTimes[sprite];
168 else if (!m_duration[sprite])
169 return state;
170 else
171 extra = pseudospriteProgress(sprite, state);
172 if (m_sprites[state]->reverse())
173 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
174
175 return state + extra;
176}
177
178int QQuickSpriteEngine::spriteStart(int sprite) const
179{
180 if (!m_duration[sprite] || !m_loaded)
181 return m_timeOffset;
182 int state = m_things[sprite];
183 if (!m_sprites[state]->m_generatedCount)
184 return m_startTimes[sprite];
185 int rowDuration;
186 int extra = pseudospriteProgress(sprite, state, rowDuration: &rowDuration);
187 if (m_sprites[state]->reverse())
188 return m_startTimes[sprite] + (extra ? (extra - 1)*rowDuration + (m_duration[sprite] % rowDuration) : 0);
189 return m_startTimes[sprite] + extra*rowDuration;
190}
191
192int QQuickSpriteEngine::spriteFrames(int sprite) const
193{
194 if (!m_loaded)
195 return 1;
196 int state = m_things[sprite];
197 if (!m_sprites[state]->m_generatedCount)
198 return m_sprites[state]->frames();
199
200 int extra;
201 if (m_sprites[state]->frameSync())
202 extra = m_startTimes[sprite];
203 else if (!m_duration[sprite])
204 return m_sprites[state]->frames();
205 else
206 extra = pseudospriteProgress(sprite, state);
207 if (m_sprites[state]->reverse())
208 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
209
210
211 if (extra == m_sprites[state]->m_generatedCount - 1) {//last state
212 const int framesRemaining = m_sprites[state]->frames() % m_sprites[state]->m_framesPerRow;
213 if (framesRemaining > 0)
214 return framesRemaining;
215 }
216 return m_sprites[state]->m_framesPerRow;
217}
218
219int QQuickSpriteEngine::spriteDuration(int sprite) const //Full duration, not per frame
220{
221 if (!m_duration[sprite] || !m_loaded)
222 return m_duration[sprite];
223 int state = m_things[sprite];
224 if (!m_sprites[state]->m_generatedCount)
225 return m_duration[sprite];
226 int rowDuration;
227 int extra = pseudospriteProgress(sprite, state, rowDuration: &rowDuration);
228 if (m_sprites[state]->reverse())
229 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
230
231 if (extra == m_sprites[state]->m_generatedCount - 1) {//last state
232 const int durationRemaining = m_duration[sprite] % rowDuration;
233 if (durationRemaining > 0)
234 return durationRemaining;
235 }
236 return rowDuration;
237}
238
239int QQuickSpriteEngine::spriteY(int sprite) const
240{
241 if (!m_loaded)
242 return 0;
243 int state = m_things[sprite];
244 if (!m_sprites[state]->m_generatedCount)
245 return m_sprites[state]->m_rowY;
246
247 int extra;
248 if (m_sprites[state]->frameSync())
249 extra = m_startTimes[sprite];
250 else if (!m_duration[sprite])
251 return m_sprites[state]->m_rowY;
252 else
253 extra = pseudospriteProgress(sprite, state);
254 if (m_sprites[state]->reverse())
255 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
256
257
258 return m_sprites[state]->m_rowY + m_sprites[state]->m_frameHeight * extra;
259}
260
261int QQuickSpriteEngine::spriteX(int sprite) const
262{
263 if (!m_loaded)
264 return 0;
265 int state = m_things[sprite];
266 if (!m_sprites[state]->m_generatedCount)
267 return m_sprites[state]->m_rowStartX;
268
269 int extra;
270 if (m_sprites[state]->frameSync())
271 extra = m_startTimes[sprite];
272 else if (!m_duration[sprite])
273 return m_sprites[state]->m_rowStartX;
274 else
275 extra = pseudospriteProgress(sprite, state);
276 if (m_sprites[state]->reverse())
277 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
278
279 if (extra)
280 return 0;
281 return m_sprites[state]->m_rowStartX;
282}
283
284QQuickSprite* QQuickSpriteEngine::sprite(int sprite) const
285{
286 return m_sprites[m_things[sprite]];
287}
288
289int QQuickSpriteEngine::spriteWidth(int sprite) const
290{
291 int state = m_things[sprite];
292 return m_sprites[state]->m_frameWidth;
293}
294
295int QQuickSpriteEngine::spriteHeight(int sprite) const
296{
297 int state = m_things[sprite];
298 return m_sprites[state]->m_frameHeight;
299}
300
301int QQuickSpriteEngine::spriteCount() const //TODO: Actually image state count, need to rename these things to make sense together
302{
303 return m_imageStateCount;
304}
305
306void QQuickStochasticEngine::setGoal(int state, int sprite, bool jump)
307{
308 if (sprite >= m_things.count() || state >= m_states.count()
309 || sprite < 0 || state < 0)
310 return;
311 if (!jump){
312 m_goals[sprite] = state;
313 return;
314 }
315
316 if (m_things.at(i: sprite) == state)
317 return;//Already there
318 m_things[sprite] = state;
319 m_duration[sprite] = m_states.at(i: state)->variedDuration();
320 m_goals[sprite] = -1;
321 restart(index: sprite);
322 emit stateChanged(idx: sprite);
323 emit m_states.at(i: state)->entered();
324 return;
325}
326
327QQuickPixmap::Status QQuickSpriteEngine::status() const //Composed status of all Sprites
328{
329 if (!m_startedImageAssembly)
330 return QQuickPixmap::Null;
331 int null, loading, ready;
332 null = loading = ready = 0;
333 for (QQuickSprite* s : m_sprites) {
334 switch (s->m_pix.status()) {
335 // ### Maybe add an error message here, because this null shouldn't be reached but when it does, the image fails without an error message.
336 case QQuickPixmap::Null : null++; break;
337 case QQuickPixmap::Loading : loading++; break;
338 case QQuickPixmap::Error : return QQuickPixmap::Error;
339 case QQuickPixmap::Ready : ready++; break;
340 }
341 }
342 if (null)
343 return QQuickPixmap::Null;
344 if (loading)
345 return QQuickPixmap::Loading;
346 if (ready)
347 return QQuickPixmap::Ready;
348 return QQuickPixmap::Null;
349}
350
351void QQuickSpriteEngine::startAssemblingImage()
352{
353 if (m_startedImageAssembly)
354 return;
355 m_loaded = false;
356 m_errorsPrinted = false;
357
358 //This could also trigger the start of the image loading in Sprites, however that currently happens in Sprite::setSource
359
360 QList<QQuickStochasticState*> removals;
361
362 for (QQuickStochasticState* s : qAsConst(t&: m_states)) {
363 QQuickSprite* sprite = qobject_cast<QQuickSprite*>(object: s);
364 if (sprite) {
365 m_sprites << sprite;
366 } else {
367 removals << s;
368 qDebug() << "Error: Non-sprite in QQuickSpriteEngine";
369 }
370 }
371 for (QQuickStochasticState* s : qAsConst(t&: removals))
372 m_states.removeAll(t: s);
373 m_startedImageAssembly = true;
374}
375
376QImage QQuickSpriteEngine::assembledImage(int maxSize)
377{
378 QQuickPixmap::Status stat = status();
379 if (!m_errorsPrinted && stat == QQuickPixmap::Error) {
380 for (QQuickSprite* s : qAsConst(t&: m_sprites))
381 if (s->m_pix.isError())
382 qmlWarning(me: s) << s->m_pix.error();
383 m_errorsPrinted = true;
384 }
385
386 if (stat != QQuickPixmap::Ready)
387 return QImage();
388
389 int h = 0;
390 int w = 0;
391 m_maxFrames = 0;
392 m_imageStateCount = 0;
393 qreal pixelRatio = 1.0;
394
395 for (QQuickSprite* state : qAsConst(t&: m_sprites)) {
396 if (state->frames() > m_maxFrames)
397 m_maxFrames = state->frames();
398
399 QImage img = state->m_pix.image();
400
401 {
402 const QSize frameSize(state->m_frameWidth, state->m_frameHeight);
403 if (!(img.size() - frameSize).isValid()) {
404 qmlWarning(me: state).nospace() << "SpriteEngine: Invalid frame size " << frameSize << "."
405 " It's bigger than image size " << img.size() << ".";
406 return QImage();
407 }
408 }
409
410 //Check that the frame sizes are the same within one sprite
411 if (!state->m_frameWidth)
412 state->m_frameWidth = img.width() / state->frames();
413
414 if (!state->m_frameHeight)
415 state->m_frameHeight = img.height();
416
417 pixelRatio = qMax(a: pixelRatio, b: state->devicePixelRatio());
418
419 if (state->frames() * state->frameWidth() > maxSize){
420 struct helper{
421 static int divRoundUp(int a, int b){return (a+b-1)/b;}
422 };
423 int rowsNeeded = helper::divRoundUp(a: state->frames(), b: (maxSize / state->frameWidth()));
424 if (h + rowsNeeded * state->frameHeight() > maxSize){
425 if (rowsNeeded * state->frameHeight() > maxSize)
426 qmlWarning(me: state) << "SpriteEngine: Animation too large to fit in one texture:" << state->source().toLocalFile();
427 else
428 qmlWarning(me: state) << "SpriteEngine: Animations too large to fit in one texture, pushed over the edge by:" << state->source().toLocalFile();
429 qmlWarning(me: state) << "SpriteEngine: Your texture max size today is " << maxSize;
430 return QImage();
431 }
432 state->m_generatedCount = rowsNeeded;
433 h += state->frameHeight() * rowsNeeded;
434 w = qMax(a: w, b: ((int)(maxSize / state->frameWidth())) * state->frameWidth());
435 m_imageStateCount += rowsNeeded;
436 }else{
437 h += state->frameHeight();
438 w = qMax(a: w, b: state->frameWidth() * state->frames());
439 m_imageStateCount++;
440 }
441 }
442
443 if (h > maxSize){
444 qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
445 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
446 return QImage();
447 }
448
449 //maxFrames is max number in a line of the texture
450 QImage image(w * pixelRatio, h * pixelRatio, QImage::Format_ARGB32_Premultiplied);
451 image.setDevicePixelRatio(pixelRatio);
452 image.fill(pixel: 0);
453 QPainter p(&image);
454 int y = 0;
455 for (QQuickSprite* state : qAsConst(t&: m_sprites)) {
456 QImage img(state->m_pix.image());
457 const int frameWidth = state->m_frameWidth;
458 const int frameHeight = state->m_frameHeight;
459 const int imgHeight = img.height() / img.devicePixelRatioF();
460 const int imgWidth = img.width() / img.devicePixelRatioF();
461 if (imgHeight == frameHeight && imgWidth < maxSize){ //Simple case
462 p.drawImage(targetRect: QRect(0, y, state->m_frames * frameWidth, frameHeight),
463 image: img,
464 sourceRect: QRect(state->m_frameX * img.devicePixelRatioF(), 0, state->m_frames * frameWidth * img.devicePixelRatioF(), frameHeight * img.devicePixelRatioF()));
465 state->m_rowStartX = 0;
466 state->m_rowY = y;
467 y += frameHeight;
468 } else { //Chopping up image case
469 state->m_framesPerRow = w/frameWidth;
470 state->m_rowY = y;
471 int x = 0;
472 int curX = state->m_frameX;
473 int curY = state->m_frameY;
474 int framesLeft = state->frames();
475 while (framesLeft > 0){
476 if (w - x + curX <= imgWidth){//finish a row in image (dest)
477 int copied = w - x;
478 framesLeft -= copied/frameWidth;
479 p.drawImage(targetRect: QRect(x, y, copied, frameHeight),
480 image: img,
481 sourceRect: QRect(curX * img.devicePixelRatioF(), curY * img.devicePixelRatioF(), copied * img.devicePixelRatioF(), frameHeight * img.devicePixelRatioF()));
482 y += frameHeight;
483 curX += copied;
484 x = 0;
485 if (curX == imgWidth){
486 curX = 0;
487 curY += frameHeight;
488 }
489 }else{//finish a row in img (src)
490 int copied = imgWidth - curX;
491 framesLeft -= copied/frameWidth;
492 p.drawImage(targetRect: QRect(x, y, copied, frameHeight),
493 image: img,
494 sourceRect: QRect(curX * img.devicePixelRatioF(), curY * img.devicePixelRatioF(), copied * img.devicePixelRatioF(), frameHeight * img.devicePixelRatioF()));
495 curY += frameHeight;
496 x += copied;
497 curX = 0;
498 }
499 }
500 if (x)
501 y += frameHeight;
502 }
503 }
504
505#ifdef SPRITE_IMAGE_DEBUG
506 QString fPath = QDir::tempPath() + "/SpriteImage.%1.png";
507 int acc = 0;
508 while (QFile::exists(fPath.arg(acc))) acc++;
509 image.save(fPath.arg(acc), "PNG");
510 qDebug() << "Assembled image output to: " << fPath.arg(acc);
511#endif
512
513 m_loaded = true;
514 m_startedImageAssembly = false;
515 return image;
516}
517
518//TODO: Add a reset() function, for completeness in the case of a SpriteEngine being restarted from 0
519void QQuickStochasticEngine::setCount(int c)
520{
521 m_things.resize(asize: c);
522 m_goals.resize(asize: c);
523 m_duration.resize(asize: c);
524 m_startTimes.resize(asize: c);
525}
526
527void QQuickStochasticEngine::start(int index, int state)
528{
529 if (index >= m_things.count())
530 return;
531 m_things[index] = state;
532 m_duration[index] = m_states.at(i: state)->variedDuration();
533 if (m_states.at(i: state)->randomStart())
534 m_startTimes[index] = NINF;
535 else
536 m_startTimes[index] = 0;
537 m_goals[index] = -1;
538 m_addAdvance = false;
539 restart(index);
540 m_addAdvance = true;
541}
542
543void QQuickStochasticEngine::stop(int index)
544{
545 if (index >= m_things.count())
546 return;
547 //Will never change until start is called again with a new state (or manually advanced) - this is not a 'pause'
548 for (int i=0; i<m_stateUpdates.count(); i++)
549 m_stateUpdates[i].second.removeAll(t: index);
550}
551
552void QQuickStochasticEngine::restart(int index)
553{
554 bool randomStart = (m_startTimes.at(i: index) == NINF);
555 m_startTimes[index] = m_timeOffset;
556 if (m_addAdvance)
557 m_startTimes[index] += m_advanceTimer.elapsed();
558 if (randomStart)
559 m_startTimes[index] -= QRandomGenerator::global()->bounded(highest: m_duration.at(i: index));
560 int time = m_duration.at(i: index) + m_startTimes.at(i: index);
561 for (int i=0; i<m_stateUpdates.count(); i++)
562 m_stateUpdates[i].second.removeAll(t: index);
563 if (m_duration.at(i: index) >= 0)
564 addToUpdateList(t: time, idx: index);
565}
566
567void QQuickSpriteEngine::restart(int index) //Reimplemented to recognize and handle pseudostates
568{
569 bool randomStart = (m_startTimes.at(i: index) == NINF);
570 if (m_loaded && m_sprites.at(i: m_things.at(i: index))->frameSync()) {//Manually advanced
571 m_startTimes[index] = 0;
572 if (randomStart && m_sprites.at(i: m_things.at(i: index))->m_generatedCount)
573 m_startTimes[index] += QRandomGenerator::global()->bounded(highest: m_sprites.at(i: m_things.at(i: index))->m_generatedCount);
574 } else {
575 m_startTimes[index] = m_timeOffset;
576 if (m_addAdvance)
577 m_startTimes[index] += m_advanceTimer.elapsed();
578 if (randomStart)
579 m_startTimes[index] -= QRandomGenerator::global()->bounded(highest: m_duration.at(i: index));
580 int time = spriteDuration(sprite: index) + m_startTimes.at(i: index);
581 if (randomStart) {
582 int curTime = m_timeOffset + (m_addAdvance ? m_advanceTimer.elapsed() : 0);
583 while (time < curTime) //Fast forward through psuedostates as needed
584 time += spriteDuration(sprite: index);
585 }
586
587 for (int i=0; i<m_stateUpdates.count(); i++)
588 m_stateUpdates[i].second.removeAll(t: index);
589 addToUpdateList(t: time, idx: index);
590 }
591}
592
593void QQuickStochasticEngine::advance(int idx)
594{
595 if (idx >= m_things.count())
596 return;//TODO: Proper fix(because this has happened and I just ignored it)
597 int nextIdx = nextState(curState: m_things.at(i: idx), idx);
598 m_things[idx] = nextIdx;
599 m_duration[idx] = m_states.at(i: nextIdx)->variedDuration();
600 restart(index: idx);
601 emit m_states.at(i: nextIdx)->entered();
602 emit stateChanged(idx);
603}
604
605void QQuickSpriteEngine::advance(int idx) //Reimplemented to recognize and handle pseudostates
606{
607 if (!m_loaded) {
608 qWarning() << QLatin1String("QQuickSpriteEngine: Trying to advance sprites before sprites finish loading. Ignoring directive");
609 return;
610 }
611
612 if (idx >= m_things.count())
613 return;//TODO: Proper fix(because this has happened and I just ignored it)
614 if (m_duration.at(i: idx) == 0) {
615 if (m_sprites.at(i: m_things.at(i: idx))->frameSync()) {
616 //Manually called, advance inner substate count
617 m_startTimes[idx]++;
618 if (m_startTimes.at(i: idx) < m_sprites.at(i: m_things.at(i: idx))->m_generatedCount) {
619 //only a pseudostate ended
620 emit stateChanged(idx);
621 return;
622 }
623 }
624 //just go past the pseudostate logic
625 } else if (m_startTimes.at(i: idx) + m_duration.at(i: idx)
626 > int(m_timeOffset + (m_addAdvance ? m_advanceTimer.elapsed() : 0))) {
627 //only a pseduostate ended
628 emit stateChanged(idx);
629 addToUpdateList(t: spriteStart(sprite: idx) + spriteDuration(sprite: idx) + (m_addAdvance ? m_advanceTimer.elapsed() : 0), idx);
630 return;
631 }
632 int nextIdx = nextState(curState: m_things.at(i: idx), idx);
633 m_things[idx] = nextIdx;
634 m_duration[idx] = m_states.at(i: nextIdx)->variedDuration();
635 restart(index: idx);
636 emit m_states.at(i: nextIdx)->entered();
637 emit stateChanged(idx);
638}
639
640int QQuickStochasticEngine::nextState(int curState, int curThing)
641{
642 int nextIdx = -1;
643 int goalPath = goalSeek(curState, idx: curThing);
644 if (goalPath == -1){//Random
645 qreal r = QRandomGenerator::global()->generateDouble();
646 qreal total = 0.0;
647 for (QVariantMap::const_iterator iter=m_states.at(i: curState)->m_to.constBegin();
648 iter!=m_states.at(i: curState)->m_to.constEnd(); ++iter)
649 total += (*iter).toReal();
650 r*=total;
651 for (QVariantMap::const_iterator iter= m_states.at(i: curState)->m_to.constBegin();
652 iter!=m_states.at(i: curState)->m_to.constEnd(); ++iter){
653 if (r < (*iter).toReal()){
654 bool superBreak = false;
655 for (int i=0; i<m_states.count(); i++){
656 if (m_states.at(i)->name() == iter.key()){
657 nextIdx = i;
658 superBreak = true;
659 break;
660 }
661 }
662 if (superBreak)
663 break;
664 }
665 r -= (*iter).toReal();
666 }
667 }else{//Random out of shortest paths to goal
668 nextIdx = goalPath;
669 }
670 if (nextIdx == -1)//No 'to' states means stay here
671 nextIdx = curState;
672 return nextIdx;
673}
674
675uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
676{
677 //Sprite State Update;
678 m_timeOffset = time;
679 m_addAdvance = false;
680 int i = 0;
681 for (; i < m_stateUpdates.count() && time >= m_stateUpdates.at(i).first; ++i) {
682 const auto copy = m_stateUpdates.at(i).second;
683 for (int idx : copy)
684 advance(idx);
685 }
686
687 m_stateUpdates.remove(i: 0, n: i);
688 m_advanceTimer.start();
689 m_addAdvance = true;
690 if (m_stateUpdates.isEmpty())
691 return uint(-1);
692 return m_stateUpdates.constFirst().first;
693}
694
695int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
696{
697 QString goalName;
698 if (m_goals.at(i: spriteIdx) != -1)
699 goalName = m_states.at(i: m_goals.at(i: spriteIdx))->name();
700 else
701 goalName = m_globalGoal;
702 if (goalName.isEmpty())
703 return -1;
704 //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitrarily anyways)
705 // Paraphrased - implement in an *efficient* manner
706 for (int i=0; i<m_states.count(); i++)
707 if (m_states.at(i: curIdx)->name() == goalName)
708 return curIdx;
709 if (dist < 0)
710 dist = m_states.count();
711 QQuickStochasticState* curState = m_states.at(i: curIdx);
712 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
713 iter!=curState->m_to.constEnd(); ++iter){
714 if (iter.key() == goalName)
715 for (int i=0; i<m_states.count(); i++)
716 if (m_states.at(i)->name() == goalName)
717 return i;
718 }
719 QSet<int> options;
720 for (int i=1; i<dist; i++){
721 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
722 iter!=curState->m_to.constEnd(); ++iter){
723 int option = -1;
724 for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
725 if (m_states.at(i: j)->name() == iter.key())
726 if (goalSeek(curIdx: j, spriteIdx, dist: i) != -1)
727 option = j;
728 if (option != -1)
729 options << option;
730 }
731 if (!options.isEmpty()){
732 if (options.count()==1)
733 return *(options.begin());
734 int option = -1;
735 qreal r = QRandomGenerator::global()->generateDouble();
736 qreal total = 0;
737 for (QSet<int>::const_iterator iter=options.constBegin();
738 iter!=options.constEnd(); ++iter)
739 total += curState->m_to.value(akey: m_states.at(i: (*iter))->name()).toReal();
740 r *= total;
741 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
742 iter!=curState->m_to.constEnd(); ++iter){
743 bool superContinue = true;
744 for (int j=0; j<m_states.count(); j++)
745 if (m_states.at(i: j)->name() == iter.key())
746 if (options.contains(value: j))
747 superContinue = false;
748 if (superContinue)
749 continue;
750 if (r < (*iter).toReal()){
751 bool superBreak = false;
752 for (int j=0; j<m_states.count(); j++){
753 if (m_states.at(i: j)->name() == iter.key()){
754 option = j;
755 superBreak = true;
756 break;
757 }
758 }
759 if (superBreak)
760 break;
761 }
762 r-=(*iter).toReal();
763 }
764 return option;
765 }
766 }
767 return -1;
768}
769
770void QQuickStochasticEngine::addToUpdateList(uint t, int idx)
771{
772 for (int i=0; i<m_stateUpdates.count(); i++){
773 if (m_stateUpdates.at(i).first == t){
774 m_stateUpdates[i].second << idx;
775 return;
776 } else if (m_stateUpdates.at(i).first > t) {
777 QVector<int> tmpList;
778 tmpList << idx;
779 m_stateUpdates.insert(i, t: qMakePair(x: t, y: tmpList));
780 return;
781 }
782 }
783 QVector<int> tmpList;
784 tmpList << idx;
785 m_stateUpdates << qMakePair(x: t, y: tmpList);
786}
787
788QT_END_NAMESPACE
789
790#include "moc_qquickspriteengine_p.cpp"
791

source code of qtdeclarative/src/quick/items/qquickspriteengine.cpp