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 
51 QT_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 
80 static 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 
QQuickStochasticEngine(QObject * parent)91 QQuickStochasticEngine::QQuickStochasticEngine(QObject *parent) :
92     QObject(parent), m_timeOffset(0), m_addAdvance(false)
93 {
94     //Default size 1
95     setCount(1);
96 }
97 
QQuickStochasticEngine(const QList<QQuickStochasticState * > & states,QObject * parent)98 QQuickStochasticEngine::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 
~QQuickStochasticEngine()105 QQuickStochasticEngine::~QQuickStochasticEngine()
106 {
107 }
108 
QQuickSpriteEngine(QObject * parent)109 QQuickSpriteEngine::QQuickSpriteEngine(QObject *parent)
110     : QQuickStochasticEngine(parent), m_startedImageAssembly(false), m_loaded(false)
111 {
112 }
113 
QQuickSpriteEngine(const QList<QQuickSprite * > & sprites,QObject * parent)114 QQuickSpriteEngine::QQuickSpriteEngine(const QList<QQuickSprite *> &sprites, QObject *parent)
115     : QQuickSpriteEngine(parent)
116 {
117     for (QQuickSprite* sprite : sprites)
118         m_states << (QQuickStochasticState*)sprite;
119 }
120 
~QQuickSpriteEngine()121 QQuickSpriteEngine::~QQuickSpriteEngine()
122 {
123 }
124 
125 
maxFrames() const126 int 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
135 TODO: All these calculations should be pre-calculated and cached during initialization for a significant performance boost
136 TODO: 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 */
pseudospriteProgress(int sprite,int state,int * rowDuration) const144 int 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 
spriteState(int sprite) const157 int 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 
spriteStart(int sprite) const178 int 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);
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 
spriteFrames(int sprite) const192 int 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 
spriteDuration(int sprite) const219 int 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);
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 
spriteY(int sprite) const239 int 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 
spriteX(int sprite) const261 int 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 
sprite(int sprite) const284 QQuickSprite* QQuickSpriteEngine::sprite(int sprite) const
285 {
286     return m_sprites[m_things[sprite]];
287 }
288 
spriteWidth(int sprite) const289 int QQuickSpriteEngine::spriteWidth(int sprite) const
290 {
291     int state = m_things[sprite];
292     return m_sprites[state]->m_frameWidth;
293 }
294 
spriteHeight(int sprite) const295 int QQuickSpriteEngine::spriteHeight(int sprite) const
296 {
297     int state = m_things[sprite];
298     return m_sprites[state]->m_frameHeight;
299 }
300 
spriteCount() const301 int QQuickSpriteEngine::spriteCount() const //TODO: Actually image state count, need to rename these things to make sense together
302 {
303     return m_imageStateCount;
304 }
305 
setGoal(int state,int sprite,bool jump)306 void 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(sprite) == state)
317         return;//Already there
318     m_things[sprite] = state;
319     m_duration[sprite] = m_states.at(state)->variedDuration();
320     m_goals[sprite] = -1;
321     restart(sprite);
322     emit stateChanged(sprite);
323     emit m_states.at(state)->entered();
324     return;
325 }
326 
status() const327 QQuickPixmap::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 
startAssemblingImage()351 void 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(m_states)) {
363         QQuickSprite* sprite = qobject_cast<QQuickSprite*>(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(removals))
372         m_states.removeAll(s);
373     m_startedImageAssembly = true;
374 }
375 
assembledImage(int maxSize)376 QImage QQuickSpriteEngine::assembledImage(int maxSize)
377 {
378     QQuickPixmap::Status stat = status();
379     if (!m_errorsPrinted && stat == QQuickPixmap::Error) {
380         for (QQuickSprite* s : qAsConst(m_sprites))
381             if (s->m_pix.isError())
382                 qmlWarning(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(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(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(pixelRatio, 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(state->frames(), (maxSize / state->frameWidth()));
424             if (h + rowsNeeded * state->frameHeight() > maxSize){
425                 if (rowsNeeded * state->frameHeight() > maxSize)
426                     qmlWarning(state) << "SpriteEngine: Animation too large to fit in one texture:" << state->source().toLocalFile();
427                 else
428                     qmlWarning(state) << "SpriteEngine: Animations too large to fit in one texture, pushed over the edge by:" << state->source().toLocalFile();
429                 qmlWarning(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(w, ((int)(maxSize / state->frameWidth())) * state->frameWidth());
435             m_imageStateCount += rowsNeeded;
436         }else{
437             h += state->frameHeight();
438             w = qMax(w, 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(0);
453     QPainter p(&image);
454     int y = 0;
455     for (QQuickSprite* state : qAsConst(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(QRect(0, y, state->m_frames * frameWidth, frameHeight),
463                         img,
464                         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(QRect(x, y, copied, frameHeight),
480                                 img,
481                                 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(QRect(x, y, copied, frameHeight),
493                                 img,
494                                 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
setCount(int c)519 void QQuickStochasticEngine::setCount(int c)
520 {
521     m_things.resize(c);
522     m_goals.resize(c);
523     m_duration.resize(c);
524     m_startTimes.resize(c);
525 }
526 
start(int index,int state)527 void 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(state)->variedDuration();
533     if (m_states.at(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 
stop(int index)543 void 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(index);
550 }
551 
restart(int index)552 void QQuickStochasticEngine::restart(int index)
553 {
554     bool randomStart = (m_startTimes.at(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(m_duration.at(index));
560     int time = m_duration.at(index) + m_startTimes.at(index);
561     for (int i=0; i<m_stateUpdates.count(); i++)
562         m_stateUpdates[i].second.removeAll(index);
563     if (m_duration.at(index) >= 0)
564         addToUpdateList(time, index);
565 }
566 
restart(int index)567 void QQuickSpriteEngine::restart(int index) //Reimplemented to recognize and handle pseudostates
568 {
569     bool randomStart = (m_startTimes.at(index) == NINF);
570     if (m_loaded && m_sprites.at(m_things.at(index))->frameSync()) {//Manually advanced
571         m_startTimes[index] = 0;
572         if (randomStart && m_sprites.at(m_things.at(index))->m_generatedCount)
573             m_startTimes[index] += QRandomGenerator::global()->bounded(m_sprites.at(m_things.at(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(m_duration.at(index));
580         int time = spriteDuration(index) + m_startTimes.at(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(index);
585         }
586 
587         for (int i=0; i<m_stateUpdates.count(); i++)
588             m_stateUpdates[i].second.removeAll(index);
589         addToUpdateList(time, index);
590     }
591 }
592 
advance(int idx)593 void 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(m_things.at(idx), idx);
598     m_things[idx] = nextIdx;
599     m_duration[idx] = m_states.at(nextIdx)->variedDuration();
600     restart(idx);
601     emit m_states.at(nextIdx)->entered();
602     emit stateChanged(idx);
603 }
604 
advance(int idx)605 void 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(idx) == 0) {
615         if (m_sprites.at(m_things.at(idx))->frameSync()) {
616             //Manually called, advance inner substate count
617             m_startTimes[idx]++;
618             if (m_startTimes.at(idx) < m_sprites.at(m_things.at(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(idx) + m_duration.at(idx)
626             > int(m_timeOffset + (m_addAdvance ? m_advanceTimer.elapsed() : 0))) {
627         //only a pseduostate ended
628         emit stateChanged(idx);
629         addToUpdateList(spriteStart(idx) + spriteDuration(idx) + (m_addAdvance ? m_advanceTimer.elapsed() : 0), idx);
630         return;
631     }
632     int nextIdx = nextState(m_things.at(idx), idx);
633     m_things[idx] = nextIdx;
634     m_duration[idx] = m_states.at(nextIdx)->variedDuration();
635     restart(idx);
636     emit m_states.at(nextIdx)->entered();
637     emit stateChanged(idx);
638 }
639 
nextState(int curState,int curThing)640 int QQuickStochasticEngine::nextState(int curState, int curThing)
641 {
642     int nextIdx = -1;
643     int goalPath = goalSeek(curState, 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(curState)->m_to.constBegin();
648             iter!=m_states.at(curState)->m_to.constEnd(); ++iter)
649             total += (*iter).toReal();
650         r*=total;
651         for (QVariantMap::const_iterator iter= m_states.at(curState)->m_to.constBegin();
652                 iter!=m_states.at(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 
updateSprites(uint time)675 uint 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(0, 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 
goalSeek(int curIdx,int spriteIdx,int dist)695 int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
696 {
697     QString goalName;
698     if (m_goals.at(spriteIdx) != -1)
699         goalName = m_states.at(m_goals.at(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(curIdx)->name() == goalName)
708             return curIdx;
709     if (dist < 0)
710         dist = m_states.count();
711     QQuickStochasticState* curState = m_states.at(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(j)->name() == iter.key())
726                     if (goalSeek(j, spriteIdx, 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(m_states.at((*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(j)->name() == iter.key())
746                         if (options.contains(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(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 
addToUpdateList(uint t,int idx)770 void 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, qMakePair(t, tmpList));
780             return;
781         }
782     }
783     QVector<int> tmpList;
784     tmpList << idx;
785     m_stateUpdates << qMakePair(t, tmpList);
786 }
787 
788 QT_END_NAMESPACE
789 
790 #include "moc_qquickspriteengine_p.cpp"
791