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

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