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