Skip to content

Commit 68f5c3e

Browse files
authored
Merge pull request #39 from scratchcpp/nonblocking_loading
Load projects in another thread
2 parents a087c68 + d3f4e7b commit 68f5c3e

File tree

4 files changed

+140
-58
lines changed

4 files changed

+140
-58
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Window {
102102
ProjectPlayer {
103103
id: player
104104
fileName: "/path/to/project.sb3"
105-
Component.onCompleted: start()
105+
onLoaded: start()
106106
}
107107
}
108108
```
@@ -123,7 +123,7 @@ Button {
123123

124124
- [x] JPEG and PNG rendering
125125
- [x] Everything related to displaying sprites (position, rotation, size, etc.)
126-
- [ ] Loading projects in another thread
126+
- [x] Loading projects in another thread
127127
- [x] API for engine properties (FPS, turbo mode, etc.)
128128
- [ ] Loading projects from URL
129129
- [ ] SVG rendering

ScratchCPPGui/ProjectPlayer.qml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ ProjectScene {
99
property alias turboMode: loader.turboMode
1010
property alias cloneLimit: loader.cloneLimit
1111
property alias spriteFencing: loader.spriteFencing
12+
signal loaded()
13+
signal failedToLoad()
1214

1315
id: root
1416
clip: true
@@ -18,6 +20,12 @@ ProjectScene {
1820
fileName: root.fileName
1921
stageWidth: parent.width
2022
stageHeight: parent.height
23+
onLoadingFinished: {
24+
if(loadStatus)
25+
loaded();
26+
else
27+
failedToLoad();
28+
}
2129
}
2230

2331
RenderedTarget {

ScratchCPPGui/projectloader.cpp

Lines changed: 121 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,16 @@ ProjectLoader::ProjectLoader(QObject *parent) :
3131

3232
ProjectLoader::~ProjectLoader()
3333
{
34+
if (m_loadThread.isRunning())
35+
m_loadThread.waitForFinished();
36+
3437
if (m_engine) {
3538
m_engine->stopEventLoop();
3639
m_eventLoop.waitForFinished();
3740
}
41+
42+
for (SpriteModel *sprite : m_sprites)
43+
sprite->deleteLater();
3844
}
3945

4046
const QString &ProjectLoader::fileName() const
@@ -44,6 +50,9 @@ const QString &ProjectLoader::fileName() const
4450

4551
void ProjectLoader::setFileName(const QString &newFileName)
4652
{
53+
if (m_loadThread.isRunning())
54+
m_loadThread.waitForFinished();
55+
4756
if (m_fileName == newFileName)
4857
return;
4958

@@ -56,89 +65,72 @@ void ProjectLoader::setFileName(const QString &newFileName)
5665

5766
m_project.setScratchVersion(ScratchVersion::Scratch3);
5867
m_project.setFileName(m_fileName.toStdString());
59-
m_loaded = m_project.load();
60-
m_engine = m_project.engine().get();
61-
62-
// Delete old sprites
63-
for (SpriteModel *sprite : m_sprites)
64-
sprite->deleteLater();
65-
66-
m_sprites.clear();
67-
68-
if (!m_engine) {
69-
emit fileNameChanged();
70-
emit loadedChanged();
71-
emit engineChanged();
72-
emit spritesChanged();
73-
return;
74-
}
75-
76-
m_engine->setFps(m_fps);
77-
m_engine->setTurboModeEnabled(m_turboMode);
78-
m_engine->setStageWidth(m_stageWidth);
79-
m_engine->setStageHeight(m_stageHeight);
80-
m_engine->setCloneLimit(m_cloneLimit);
81-
m_engine->setSpriteFencingEnabled(m_spriteFencing);
82-
83-
auto handler = std::bind(&ProjectLoader::emitTick, this);
84-
m_engine->setRedrawHandler(std::function<void()>(handler));
85-
86-
// Load targets
87-
const auto &targets = m_engine->targets();
88-
89-
for (auto target : targets) {
90-
if (target->isStage())
91-
dynamic_cast<Stage *>(target.get())->setInterface(&m_stage);
92-
else {
93-
SpriteModel *sprite = new SpriteModel(this);
94-
dynamic_cast<Sprite *>(target.get())->setInterface(sprite);
95-
m_sprites.push_back(sprite);
96-
}
97-
}
98-
99-
// Run event loop
100-
m_engine->setSpriteFencingEnabled(false);
101-
m_eventLoop = QtConcurrent::run(&runEventLoop, m_engine);
102-
68+
m_loadStatus = false;
69+
emit loadStatusChanged();
10370
emit fileNameChanged();
104-
emit loadedChanged();
105-
emit engineChanged();
106-
emit stageChanged();
107-
emit spritesChanged();
71+
72+
m_loadThread = QtConcurrent::run(&callLoad, this);
10873
}
10974

110-
bool ProjectLoader::loaded() const
75+
bool ProjectLoader::loadStatus() const
11176
{
112-
return m_loaded;
77+
if (m_loadThread.isRunning())
78+
return false;
79+
80+
return m_loadStatus;
11381
}
11482

11583
IEngine *ProjectLoader::engine() const
11684
{
85+
if (m_loadThread.isRunning())
86+
return nullptr;
87+
11788
return m_engine;
11889
}
11990

12091
StageModel *ProjectLoader::stage()
12192
{
93+
if (m_loadThread.isRunning())
94+
m_loadThread.waitForFinished();
95+
12296
return &m_stage;
12397
}
12498

12599
QQmlListProperty<SpriteModel> ProjectLoader::sprites()
126100
{
101+
if (m_loadThread.isRunning())
102+
m_loadThread.waitForFinished();
103+
127104
return QQmlListProperty<SpriteModel>(this, &m_sprites);
128105
}
129106

130107
void ProjectLoader::start()
131108
{
132-
m_engine->start();
109+
if (m_loadThread.isRunning())
110+
m_loadThread.waitForFinished();
111+
112+
if (m_loadStatus) {
113+
Q_ASSERT(m_engine);
114+
m_engine->start();
115+
}
133116
}
134117

135118
void ProjectLoader::stop()
136119
{
137-
m_engine->stop();
120+
if (m_loadThread.isRunning())
121+
m_loadThread.waitForFinished();
122+
123+
if (m_loadStatus) {
124+
Q_ASSERT(m_engine);
125+
m_engine->stop();
126+
}
138127
}
139128

140129
void ProjectLoader::timerEvent(QTimerEvent *event)
141130
{
131+
if (m_loadThread.isRunning())
132+
return;
133+
142134
auto stageRenderedTarget = m_stage.renderedTarget();
143135

144136
if (stageRenderedTarget)
@@ -154,6 +146,68 @@ void ProjectLoader::timerEvent(QTimerEvent *event)
154146
event->accept();
155147
}
156148

149+
void ProjectLoader::callLoad(ProjectLoader *loader)
150+
{
151+
loader->load();
152+
}
153+
154+
void ProjectLoader::load()
155+
{
156+
m_loadStatus = m_project.load();
157+
m_engineMutex.lock();
158+
m_engine = m_project.engine().get();
159+
160+
// Delete old sprites
161+
for (SpriteModel *sprite : m_sprites)
162+
sprite->deleteLater();
163+
164+
m_sprites.clear();
165+
166+
if (!m_engine) {
167+
emit fileNameChanged();
168+
emit loadStatusChanged();
169+
emit loadingFinished();
170+
emit engineChanged();
171+
emit spritesChanged();
172+
return;
173+
}
174+
175+
m_engine->setFps(m_fps);
176+
m_engine->setTurboModeEnabled(m_turboMode);
177+
m_engine->setStageWidth(m_stageWidth);
178+
m_engine->setStageHeight(m_stageHeight);
179+
m_engine->setCloneLimit(m_cloneLimit);
180+
m_engine->setSpriteFencingEnabled(m_spriteFencing);
181+
182+
auto handler = std::bind(&ProjectLoader::emitTick, this);
183+
m_engine->setRedrawHandler(std::function<void()>(handler));
184+
185+
// Load targets
186+
const auto &targets = m_engine->targets();
187+
188+
for (auto target : targets) {
189+
if (target->isStage())
190+
dynamic_cast<Stage *>(target.get())->setInterface(&m_stage);
191+
else {
192+
SpriteModel *sprite = new SpriteModel;
193+
sprite->moveToThread(qApp->thread());
194+
dynamic_cast<Sprite *>(target.get())->setInterface(sprite);
195+
m_sprites.push_back(sprite);
196+
}
197+
}
198+
199+
// Run event loop
200+
m_engine->setSpriteFencingEnabled(false);
201+
m_eventLoop = QtConcurrent::run(&runEventLoop, m_engine);
202+
m_engineMutex.unlock();
203+
204+
emit loadStatusChanged();
205+
emit loadingFinished();
206+
emit engineChanged();
207+
emit stageChanged();
208+
emit spritesChanged();
209+
}
210+
157211
void ProjectLoader::initTimer()
158212
{
159213
QScreen *screen = qApp->primaryScreen();
@@ -164,6 +218,9 @@ void ProjectLoader::initTimer()
164218

165219
void ProjectLoader::emitTick()
166220
{
221+
if (m_loadThread.isRunning())
222+
m_loadThread.waitForFinished();
223+
167224
auto stageRenderedTarget = m_stage.renderedTarget();
168225

169226
if (stageRenderedTarget)
@@ -188,10 +245,12 @@ void ProjectLoader::setFps(double newFps)
188245
return;
189246

190247
m_fps = newFps;
248+
m_engineMutex.lock();
191249

192250
if (m_engine)
193251
m_engine->setFps(m_fps);
194252

253+
m_engineMutex.unlock();
195254
emit fpsChanged();
196255
}
197256

@@ -206,10 +265,12 @@ void ProjectLoader::setTurboMode(bool newTurboMode)
206265
return;
207266

208267
m_turboMode = newTurboMode;
268+
m_engineMutex.lock();
209269

210270
if (m_engine)
211271
m_engine->setTurboModeEnabled(m_turboMode);
212272

273+
m_engineMutex.unlock();
213274
emit turboModeChanged();
214275
}
215276

@@ -224,10 +285,12 @@ void ProjectLoader::setStageWidth(unsigned int newStageWidth)
224285
return;
225286

226287
m_stageWidth = newStageWidth;
288+
m_engineMutex.lock();
227289

228290
if (m_engine)
229291
m_engine->setStageWidth(m_stageWidth);
230292

293+
m_engineMutex.unlock();
231294
emit stageWidthChanged();
232295
}
233296

@@ -242,10 +305,12 @@ void ProjectLoader::setStageHeight(unsigned int newStageHeight)
242305
return;
243306

244307
m_stageHeight = newStageHeight;
308+
m_engineMutex.lock();
245309

246310
if (m_engine)
247311
m_engine->setStageHeight(m_stageHeight);
248312

313+
m_engineMutex.unlock();
249314
emit stageHeightChanged();
250315
}
251316

@@ -260,10 +325,12 @@ void ProjectLoader::setCloneLimit(int newCloneLimit)
260325
return;
261326

262327
m_cloneLimit = newCloneLimit;
328+
m_engineMutex.lock();
263329

264330
if (m_engine)
265331
m_engine->setCloneLimit(m_cloneLimit);
266332

333+
m_engineMutex.unlock();
267334
emit cloneLimitChanged();
268335
}
269336

@@ -278,9 +345,11 @@ void ProjectLoader::setSpriteFencing(bool newSpriteFencing)
278345
return;
279346

280347
m_spriteFencing = newSpriteFencing;
348+
m_engineMutex.lock();
281349

282350
if (m_engine)
283351
m_engine->setSpriteFencingEnabled(m_spriteFencing);
284352

353+
m_engineMutex.unlock();
285354
emit spriteFencingChanged();
286355
}

ScratchCPPGui/projectloader.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ProjectLoader : public QObject
1919
Q_OBJECT
2020
QML_ELEMENT
2121
Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged)
22-
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
22+
Q_PROPERTY(bool loadStatus READ loadStatus NOTIFY loadStatusChanged)
2323
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine NOTIFY engineChanged)
2424
Q_PROPERTY(StageModel *stage READ stage NOTIFY stageChanged)
2525
Q_PROPERTY(QQmlListProperty<SpriteModel> sprites READ sprites NOTIFY spritesChanged)
@@ -37,7 +37,7 @@ class ProjectLoader : public QObject
3737
const QString &fileName() const;
3838
void setFileName(const QString &newFileName);
3939

40-
bool loaded() const;
40+
bool loadStatus() const;
4141

4242
libscratchcpp::IEngine *engine() const;
4343

@@ -71,7 +71,8 @@ class ProjectLoader : public QObject
7171

7272
signals:
7373
void fileNameChanged();
74-
void loadedChanged();
74+
void loadStatusChanged();
75+
void loadingFinished();
7576
void engineChanged();
7677
void stageChanged();
7778
void spritesChanged();
@@ -86,14 +87,18 @@ class ProjectLoader : public QObject
8687
void timerEvent(QTimerEvent *event) override;
8788

8889
private:
90+
static void callLoad(ProjectLoader *loader);
91+
void load();
8992
void initTimer();
9093
void emitTick();
9194

9295
int m_timerId = -1;
9396
QString m_fileName;
97+
QFuture<void> m_loadThread;
9498
libscratchcpp::Project m_project;
9599
libscratchcpp::IEngine *m_engine = nullptr;
96-
bool m_loaded = false;
100+
QMutex m_engineMutex;
101+
bool m_loadStatus = false;
97102
StageModel m_stage;
98103
QList<SpriteModel *> m_sprites;
99104
QFuture<void> m_eventLoop;

0 commit comments

Comments
 (0)