Skip to content

Commit 0605d8e

Browse files
committed
Refactor the event loop
1 parent d151855 commit 0605d8e

File tree

9 files changed

+137
-114
lines changed

9 files changed

+137
-114
lines changed

include/scratchcpp/iengine.h

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
4242
*/
4343
virtual void compile() = 0;
4444

45-
/*!
46-
* Runs a single frame.\n
47-
* Use this if you want to use a custom event loop
48-
* in your project player.
49-
* \note Nothing will happen until start() is called.
50-
*/
51-
virtual void frame() = 0;
52-
5345
/*!
5446
* Calls all "when green flag clicked" blocks.
55-
* \note Nothing will happen until run() or frame() is called.
47+
* \note Nothing will happen until the event loop is started.
5648
*/
5749
virtual void start() = 0;
5850

@@ -85,13 +77,19 @@ class LIBSCRATCHCPP_EXPORT IEngine
8577
virtual void deinitClone(Sprite *clone) = 0;
8678

8779
/*!
88-
* Runs the event loop and calls "when green flag clicked" blocks.
80+
* Calls and runs "when green flag clicked" blocks.
8981
* \note This function returns when all scripts finish.\n
90-
* If you need to implement something advanced, such as a GUI with the
91-
* green flag button, use frame().
82+
* If you need an event loop that runs even after the project stops,
83+
* use runEventLoop().
9284
*/
9385
virtual void run() = 0;
9486

87+
/*!
88+
* Runs the event loop. Call start() (from another thread) to start the project.
89+
* \note This should be called from another thread in GUI project players to keep the UI responsive.
90+
*/
91+
virtual void runEventLoop() = 0;
92+
9593
/*! Returns true if the project is currently running. */
9694
virtual bool isRunning() const = 0;
9795

include/scratchcpp/project.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class LIBSCRATCHCPP_EXPORT Project
2525

2626
bool load();
2727

28-
void frame();
2928
void start();
3029
void run();
30+
void runEventLoop();
3131

3232
const std::string &fileName() const;
3333
void setFileName(const std::string &newFileName);

src/engine/internal/engine.cpp

Lines changed: 101 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -136,52 +136,6 @@ void Engine::compile()
136136
}
137137
}
138138

139-
void Engine::frame()
140-
{
141-
m_lockFrame = false;
142-
143-
for (int i = 0; i < m_runningScripts.size(); i++) {
144-
auto script = m_runningScripts[i];
145-
m_breakFrame = false;
146-
147-
do {
148-
script->run();
149-
if (script->atEnd() && m_running) {
150-
for (auto &[key, value] : m_runningBroadcastMap) {
151-
size_t index = 0;
152-
153-
for (const auto &pair : value) {
154-
if (pair.second == script.get()) {
155-
value.erase(value.begin() + index);
156-
break;
157-
}
158-
159-
index++;
160-
}
161-
}
162-
163-
if (std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), script.get()) == m_scriptsToRemove.end())
164-
m_scriptsToRemove.push_back(script.get());
165-
}
166-
} while (!script->atEnd() && !m_breakFrame);
167-
}
168-
169-
assert(m_running || m_scriptsToRemove.empty());
170-
171-
for (auto script : m_scriptsToRemove) {
172-
size_t index = -1;
173-
for (size_t i = 0; i < m_runningScripts.size(); i++) {
174-
if (m_runningScripts[i].get() == script) {
175-
index = i;
176-
break;
177-
}
178-
}
179-
assert(index != -1);
180-
m_runningScripts.erase(m_runningScripts.begin() + index);
181-
}
182-
m_scriptsToRemove.clear();
183-
}
184-
185139
void Engine::start()
186140
{
187141
// NOTE: Running scripts should be deleted, but this method will probably be removed anyway
@@ -221,7 +175,7 @@ void Engine::startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<T
221175

222176
if (topLevelBlock->next()) {
223177
auto script = m_scripts[topLevelBlock];
224-
m_runningScripts.push_back(script->start());
178+
addRunningScript(script->start());
225179
}
226180
}
227181

@@ -290,7 +244,7 @@ void Engine::broadcastByPtr(Broadcast *broadcast, VirtualMachine *sourceScript,
290244

291245
if (it == runningBroadcastScripts.end()) {
292246
auto vm = script->start(target);
293-
m_runningScripts.push_back(vm);
247+
addRunningScript(vm);
294248
m_runningBroadcastMap[broadcast].push_back({ sourceScript, vm.get() });
295249
}
296250
}
@@ -342,7 +296,7 @@ void Engine::initClone(Sprite *clone)
342296

343297
for (auto script : scripts) {
344298
auto vm = script->start(clone);
345-
m_runningScripts.push_back(vm);
299+
addRunningScript(vm);
346300
}
347301
}
348302

@@ -357,35 +311,110 @@ void Engine::deinitClone(Sprite *clone)
357311

358312
void Engine::run()
359313
{
360-
updateFrameDuration();
361314
start();
315+
eventLoop(true);
316+
finalize();
317+
}
318+
319+
void Engine::runEventLoop()
320+
{
321+
eventLoop();
322+
}
323+
324+
void Engine::eventLoop(bool untilProjectStops)
325+
{
326+
updateFrameDuration();
327+
m_newScripts.clear();
362328

363329
while (true) {
364-
auto lastFrameTime = m_clock->currentSteadyTime();
365-
m_skipFrame = false;
330+
auto frameStart = m_clock->currentSteadyTime();
331+
std::chrono::steady_clock::time_point currentTime;
332+
std::chrono::milliseconds elapsedTime, sleepTime;
333+
m_lockFrame = false;
334+
m_redrawRequested = false;
335+
bool timeout = false;
336+
bool stop = false;
337+
std::vector<std::shared_ptr<VirtualMachine>> scripts = m_runningScripts; // this must be copied
338+
339+
do {
340+
m_scriptsToRemove.clear();
341+
342+
// Execute new scripts from last frame
343+
runScripts(m_newScripts, scripts);
344+
345+
// Execute all running scripts
346+
m_newScripts.clear();
347+
runScripts(scripts, scripts);
348+
349+
// Stop the event loop if the project has finished running (and untilProjectStops is set to true)
350+
if (untilProjectStops && m_runningScripts.empty()) {
351+
stop = true;
352+
break;
353+
}
354+
355+
currentTime = m_clock->currentSteadyTime();
356+
elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - frameStart);
357+
sleepTime = m_frameDuration - elapsedTime;
358+
timeout = sleepTime <= std::chrono::milliseconds::zero();
359+
} while (!m_redrawRequested && !timeout && !stop);
366360

367-
// Execute the frame
368-
frame();
369-
if (m_runningScripts.size() <= 0)
361+
if (stop)
370362
break;
371363

372-
// Sleep until the time for the next frame
373-
auto currentTime = m_clock->currentSteadyTime();
374-
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - lastFrameTime);
375-
auto sleepTime = m_frameDuration - elapsedTime;
376-
bool timeOut = sleepTime <= std::chrono::milliseconds::zero();
364+
// Redraw
365+
// TODO: Redraw here
377366

378-
if (!timeOut && !m_skipFrame)
367+
// If the timeout hasn't been reached yet (redraw was requested), sleep
368+
if (!timeout)
379369
m_clock->sleep(sleepTime);
370+
}
380371

381-
if ((m_skipFrame && timeOut) || !m_skipFrame) {
382-
// TODO: Repaint here
383-
}
372+
finalize();
373+
}
374+
375+
void Engine::runScripts(const std::vector<std::shared_ptr<VirtualMachine>> &scripts, std::vector<std::shared_ptr<VirtualMachine>> &globalScripts)
376+
{
377+
// globalScripts is used to remove "scripts to remove" from it so that they're removed from the correct list
378+
for (int i = 0; i < scripts.size(); i++) {
379+
auto script = scripts[i];
380+
assert(script);
381+
m_breakFrame = false;
384382

385-
lastFrameTime = currentTime;
383+
do {
384+
script->run();
385+
if (script->atEnd() && m_running) {
386+
for (auto &[key, value] : m_runningBroadcastMap) {
387+
size_t index = 0;
388+
389+
for (const auto &pair : value) {
390+
if (pair.second == script.get()) {
391+
value.erase(value.begin() + index);
392+
break;
393+
}
394+
395+
index++;
396+
}
397+
}
398+
399+
if (std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), script.get()) == m_scriptsToRemove.end())
400+
m_scriptsToRemove.push_back(script.get());
401+
}
402+
} while (!script->atEnd() && !m_breakFrame);
386403
}
387404

388-
finalize();
405+
assert(m_running || m_scriptsToRemove.empty());
406+
407+
for (auto script : m_scriptsToRemove) {
408+
auto pred = [script](std::shared_ptr<VirtualMachine> vm) { return vm.get() == script; };
409+
auto it1 = std::find_if(m_runningScripts.begin(), m_runningScripts.end(), pred);
410+
auto it2 = std::find_if(globalScripts.begin(), globalScripts.end(), pred);
411+
assert(it1 != m_runningScripts.end());
412+
m_runningScripts.erase(it1);
413+
414+
if (it2 != globalScripts.end())
415+
globalScripts.erase(it2);
416+
}
417+
m_scriptsToRemove.clear();
389418
}
390419

391420
bool Engine::isRunning() const
@@ -984,6 +1013,12 @@ void Engine::updateFrameDuration()
9841013
m_frameDuration = std::chrono::milliseconds(static_cast<long>(1000 / m_fps));
9851014
}
9861015

1016+
void Engine::addRunningScript(std::shared_ptr<VirtualMachine> vm)
1017+
{
1018+
m_runningScripts.push_back(vm);
1019+
m_newScripts.push_back(vm);
1020+
}
1021+
9871022
void Engine::startWhenKeyPressedScripts(const std::vector<Script *> &scripts)
9881023
{
9891024
for (auto script : scripts) {

src/engine/internal/engine.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class Engine : public IEngine
2828
void resolveIds();
2929
void compile() override;
3030

31-
void frame() override;
3231
void start() override;
3332
void stop() override;
3433
void startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<Target> target) override;
@@ -39,6 +38,7 @@ class Engine : public IEngine
3938
void initClone(libscratchcpp::Sprite *clone) override;
4039
void deinitClone(libscratchcpp::Sprite *clone) override;
4140
void run() override;
41+
void runEventLoop() override;
4242

4343
bool isRunning() const override;
4444

@@ -122,6 +122,8 @@ class Engine : public IEngine
122122
IClock *m_clock = nullptr;
123123

124124
private:
125+
void eventLoop(bool untilProjectStops = false);
126+
void runScripts(const std::vector<std::shared_ptr<VirtualMachine>> &scripts, std::vector<std::shared_ptr<VirtualMachine>> &globalScripts);
125127
void finalize();
126128
void deleteClones();
127129
std::shared_ptr<Block> getBlock(const std::string &id);
@@ -132,6 +134,7 @@ class Engine : public IEngine
132134
std::shared_ptr<IBlockSection> blockSection(const std::string &opcode) const;
133135

134136
void updateFrameDuration();
137+
void addRunningScript(std::shared_ptr<VirtualMachine> vm);
135138
void startWhenKeyPressedScripts(const std::vector<Script *> &scripts);
136139

137140
std::unordered_map<std::shared_ptr<IBlockSection>, std::unique_ptr<BlockSectionContainer>> m_sections;
@@ -143,14 +146,15 @@ class Engine : public IEngine
143146
std::unordered_map<std::string, std::vector<Script *>> m_whenKeyPressedScripts; // key name, "when key pressed" scripts
144147
std::vector<std::string> m_extensions;
145148
std::vector<std::shared_ptr<VirtualMachine>> m_runningScripts;
149+
std::vector<std::shared_ptr<VirtualMachine>> m_newScripts;
146150
std::vector<VirtualMachine *> m_scriptsToRemove;
147151
std::unordered_map<std::shared_ptr<Block>, std::shared_ptr<Script>> m_scripts;
148152
std::vector<BlockFunc> m_functions;
149153

150154
std::unique_ptr<ITimer> m_defaultTimer;
151155
ITimer *m_timer = nullptr;
152156
double m_fps = 30; // default FPS
153-
std::chrono::milliseconds m_frameDuration; // will be computed in run()
157+
std::chrono::milliseconds m_frameDuration; // will be computed in eventLoop()
154158
std::unordered_map<std::string, bool> m_keyMap; // holds key states
155159
bool m_anyKeyPressed = false;
156160
double m_mouseX = 0;

src/project.cpp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,6 @@ bool Project::load()
3636
return impl->load();
3737
}
3838

39-
/*!
40-
* Runs a single frame.\n
41-
* Use this if you want to use a custom event loop
42-
* in your project player.
43-
* \note Nothing will happen until start() is called.
44-
*/
45-
void Project::frame()
46-
{
47-
impl->frame();
48-
}
49-
5039
/*!
5140
* Calls all "when green flag clicked" blocks.
5241
* \note Nothing will happen until run() or frame() is called.
@@ -57,16 +46,25 @@ void Project::start()
5746
}
5847

5948
/*!
60-
* Runs the event loop and calls "when green flag clicked" blocks.
49+
* Calls and runs "when green flag clicked" blocks.
6150
* \note This function returns when all scripts finish.\n
62-
* If you need to implement something advanced, such as a GUI with the
63-
* green flag button, use frame().
51+
* If you need an event loop that runs even after the project stops,
52+
* use runEventLoop().
6453
*/
6554
void Project::run()
6655
{
6756
impl->run();
6857
}
6958

59+
/*!
60+
* Runs the event loop. Call start() (from another thread) to start the project.
61+
* \note This should be called from another thread in GUI project players to keep the UI responsive.
62+
*/
63+
void Project::runEventLoop()
64+
{
65+
impl->runEventLoop();
66+
}
67+
7068
/*! Returns the project file name. */
7169
const std::string &Project::fileName() const
7270
{

src/project_p.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,6 @@ bool ProjectPrivate::load()
6666
return true;
6767
}
6868

69-
void ProjectPrivate::frame()
70-
{
71-
engine->frame();
72-
}
73-
7469
void ProjectPrivate::start()
7570
{
7671
engine->start();
@@ -81,6 +76,11 @@ void ProjectPrivate::run()
8176
engine->run();
8277
}
8378

79+
void ProjectPrivate::runEventLoop()
80+
{
81+
engine->runEventLoop();
82+
}
83+
8484
void ProjectPrivate::setScratchVersion(ScratchVersion version)
8585
{
8686
// TODO: Use this when more versions become supported

0 commit comments

Comments
 (0)