Skip to content

Commit b07ee25

Browse files
authored
Merge pull request #318 from scratchcpp/refactor_event_loop
Refactor the event loop and loop blocks behavior
2 parents 9ee5bde + 40900f4 commit b07ee25

35 files changed

+314
-414
lines changed

docs/Getting started.md

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,5 @@ int main(int argc, char **argv) {
2424
The \link libscratchcpp::Project::run() run() \endlink method runs an event loop which stops after all scripts finish.
2525
2626
For CLI project players, using \link libscratchcpp::Project::run() run() \endlink is enough. If you are developing
27-
a GUI project player and need to receive input events such as key presses, you'll need to implement your own event loop
28-
or use the one provided by the GUI framework.
29-
30-
## GUI example (Qt)
31-
Qt provides an event loop which can be easily used to run frames with a specific frame rate.
32-
33-
For example, a `Player` class which **inherits from QObject** can use a timer:
34-
```cpp
35-
#include <QObject>
36-
#include <scratchcpp/project.h>
37-
38-
class Player : public QObject {
39-
Q_OBJECT
40-
public:
41-
Player(QObject *parent = nullptr);
42-
Player(const Player &) = delete; // Project is not copyable
43-
44-
protected:
45-
void timerEvent(QTimerEvent *event) override;
46-
47-
private:
48-
libscratchcpp::Project m_proj;
49-
}
50-
51-
Player::Player(QObject *parent) {
52-
m_proj.setFileName("/path/to/project.sb3");
53-
m_proj.start();
54-
int fps = 30;
55-
startTimer(1000.0 / fps);
56-
}
57-
58-
void Player::timerEvent(QTimerEvent *event) {
59-
m_proj.frame();
60-
event->accept();
61-
}
62-
```
27+
a GUI project player and need to receive input events such as key presses, you'll need to use \link libscratchcpp::Project::runEventLoop() runEventLoop() \endlink
28+
and run it in another thread (to keep the UI responsive).

include/scratchcpp/compiler.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class LIBSCRATCHCPP_EXPORT Compiler
6262
void addProcedureArg(const std::string &procCode, const std::string &argName);
6363
void moveToSubstack(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2, SubstackType type);
6464
void moveToSubstack(std::shared_ptr<Block> substack, SubstackType type);
65-
void breakAtomicScript();
6665
void warp();
6766

6867
Input *input(int id) const;

include/scratchcpp/iengine.h

Lines changed: 12 additions & 27 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

@@ -159,23 +157,10 @@ class LIBSCRATCHCPP_EXPORT IEngine
159157
virtual bool broadcastByPtrRunning(Broadcast *broadcast, VirtualMachine *sourceScript) = 0;
160158

161159
/*!
162-
* Call this from a block implementation to force a "screen refresh".
160+
* Call this from a block implementation to force a redraw (screen refresh).
163161
* \note This has no effect in "run without screen refresh" custom blocks.
164162
*/
165-
virtual void breakFrame() = 0;
166-
167-
/*! Returns true if breakFrame() was called. */
168-
virtual bool breakingCurrentFrame() = 0;
169-
170-
/*!
171-
* Call this from a block implementation to skip a frame and run the next frame immediately.\n
172-
* The screen will be refreshed according to the frame rate.
173-
* \note This also works in "run without screen refresh" custom blocks.
174-
*/
175-
virtual void skipFrame() = 0;
176-
177-
/*! Call this from a block implementation to ignore calls to skipFrame() until the current frame ends. */
178-
virtual void lockFrame() = 0;
163+
virtual void requestRedraw() = 0;
179164

180165
/*! Returns the timer of the project. */
181166
virtual ITimer *timer() const = 0;

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);

include/scratchcpp/virtualmachine.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ enum Opcode
7979
OP_CALL_PROCEDURE, /*! Calls the procedure (custom block) with the index in the argument. */
8080
OP_ADD_ARG, /*!< Adds a procedure (custom block) argument with the value from the last register. */
8181
OP_READ_ARG, /*!< Reads the procedure (custom block) argument with the index in the argument and stores the value in the last register. */
82-
OP_BREAK_ATOMIC, /*!< Breaks current frame at the end of the loop. */
82+
OP_BREAK_FRAME, /*!< Breaks current frame at the end of the loop. */
8383
OP_WARP /*! Runs the script without screen refresh. */
8484
};
8585

@@ -131,7 +131,7 @@ class LIBSCRATCHCPP_EXPORT VirtualMachine
131131
void reset();
132132
void moveToLastCheckpoint();
133133

134-
void stop(bool savePos = true, bool breakAtomic = false, bool goBack = false);
134+
void stop(bool savePos = true, bool breakFrame = false, bool goBack = false);
135135

136136
bool atEnd() const;
137137

src/blocks/controlblocks.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ void ControlBlocks::compileStop(Compiler *compiler)
139139
switch (option) {
140140
case StopAll:
141141
compiler->addFunctionCall(&stopAll);
142+
compiler->addInstruction(vm::OP_HALT);
142143
break;
143144

144145
case StopThisScript:
@@ -215,6 +216,8 @@ unsigned int ControlBlocks::startWait(VirtualMachine *vm)
215216

216217
auto currentTime = clock->currentSteadyTime();
217218
m_timeMap[vm] = { currentTime, vm->getInput(0, 1)->toDouble() * 1000 };
219+
vm->engine()->requestRedraw();
220+
218221
return 1;
219222
}
220223

@@ -230,8 +233,6 @@ unsigned int ControlBlocks::wait(VirtualMachine *vm)
230233
vm->stop(true, true, false);
231234
} else
232235
vm->stop(true, true, true);
233-
234-
vm->engine()->lockFrame();
235236
return 0;
236237
}
237238

src/blocks/motionblocks.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,6 @@ void MotionBlocks::continueGliding(VirtualMachine *vm)
500500
}
501501

502502
vm->stop(true, true, true);
503-
vm->engine()->lockFrame();
504503
}
505504
}
506505

src/engine/compiler.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ void Compiler::init()
2727

2828
impl->bytecode.clear();
2929
impl->procedurePrototype = nullptr;
30-
impl->atomic = true;
3130
impl->warp = false;
3231

3332
// Add start instruction
@@ -235,16 +234,6 @@ void Compiler::moveToSubstack(std::shared_ptr<Block> substack, SubstackType type
235234
moveToSubstack(substack, nullptr, type);
236235
}
237236

238-
/*!
239-
* Adds the vm::OP_BREAK_ATOMIC instruction at the end of the current loop.
240-
* This can be used for example in motion blocks.
241-
* \note Nothing will happen if the script is set to run without screen refresh.
242-
*/
243-
void Compiler::breakAtomicScript()
244-
{
245-
impl->atomic = false;
246-
}
247-
248237
/*! Makes current script run without screen refresh. */
249238
void Compiler::warp()
250239
{

src/engine/compiler_p.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ void CompilerPrivate::substackEnd()
3636
auto parent = substackTree.back();
3737
switch (parent.second) {
3838
case Compiler::SubstackType::Loop:
39-
if (!atomic) {
40-
if (!warp)
41-
addInstruction(OP_BREAK_ATOMIC);
42-
atomic = true;
43-
}
39+
// Break the frame at the end of the loop so that other scripts can run within the frame
40+
// This won't happen in "warp" scripts
41+
if (!warp)
42+
addInstruction(OP_BREAK_FRAME);
4443
addInstruction(OP_LOOP_END);
4544
break;
4645
case Compiler::SubstackType::IfStatement:

src/engine/compiler_p.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ struct CompilerPrivate
3636
std::vector<std::string> procedures;
3737
std::unordered_map<std::string, std::vector<std::string>> procedureArgs;
3838
BlockPrototype *procedurePrototype = nullptr;
39-
bool atomic = true;
4039
bool warp = false;
4140
};
4241

0 commit comments

Comments
 (0)