Skip to content

Commit 3cf001f

Browse files
authored
Merge pull request #148 from scratchcpp/refactor_broadcasts
Refactor broadcasts
2 parents e5500ee + 2665942 commit 3cf001f

File tree

7 files changed

+129
-25
lines changed

7 files changed

+129
-25
lines changed

include/scratchcpp/iengine.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class LIBSCRATCHCPP_EXPORT IEngine
5858
virtual void startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<Target> target) = 0;
5959

6060
/*! Starts the script of the broadcast with the given index. */
61-
virtual void broadcast(unsigned int index, VirtualMachine *sourceScript) = 0;
61+
virtual void broadcast(unsigned int index, VirtualMachine *sourceScript, bool wait = false) = 0;
6262

6363
/*! Stops the given script. */
6464
virtual void stopScript(VirtualMachine *vm) = 0;
@@ -79,7 +79,7 @@ class LIBSCRATCHCPP_EXPORT IEngine
7979
virtual void run() = 0;
8080

8181
/*! Returns true if there are any running script of the broadcast with the given index. */
82-
virtual bool broadcastRunning(unsigned int index) = 0;
82+
virtual bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) = 0;
8383

8484
/*!
8585
* Call this from a block implementation to force a "screen refresh".
@@ -90,6 +90,16 @@ class LIBSCRATCHCPP_EXPORT IEngine
9090
/*! Returns true if breakFrame() was called. */
9191
virtual bool breakingCurrentFrame() = 0;
9292

93+
/*!
94+
* Call this from a block implementation to skip a frame and run the next frame immediately.\n
95+
* The screen will be refreshed according to the frame rate.
96+
* \note This also works in "run without screen refresh" custom blocks.
97+
*/
98+
virtual void skipFrame() = 0;
99+
100+
/*! Call this from a block implementation to ignore calls to skipFrame() until the current frame ends. */
101+
virtual void lockFrame() = 0;
102+
93103
/*!
94104
* Registers the given block section.
95105
* \see <a href="blockSections.html">Block sections</a>

src/blocks/controlblocks.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ unsigned int ControlBlocks::stopOtherScriptsInSprite(VirtualMachine *vm)
171171
unsigned int ControlBlocks::startWait(VirtualMachine *vm)
172172
{
173173
auto currentTime = std::chrono::steady_clock::now();
174-
assert(m_timeMap.count(vm) == 0);
175174
m_timeMap[vm] = { currentTime, vm->getInput(0, 1)->toDouble() * 1000 };
176175
return 1;
177176
}
@@ -185,6 +184,8 @@ unsigned int ControlBlocks::wait(VirtualMachine *vm)
185184
vm->stop(true, true, false);
186185
} else
187186
vm->stop(true, true, true);
187+
188+
vm->engine()->lockFrame();
188189
return 0;
189190
}
190191

src/blocks/eventblocks.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ void EventBlocks::compileBroadcastAndWait(Compiler *compiler)
4848
compiler->addInput(input);
4949
if (input->type() != Input::Type::ObscuredShadow) {
5050
input->primaryValue()->setValue(compiler->engine()->findBroadcast(input->primaryValue()->value().toString()));
51-
compiler->addFunctionCall(&broadcastByIndex);
51+
compiler->addFunctionCall(&broadcastByIndexAndWait);
5252
compiler->addInput(input);
5353
compiler->addFunctionCall(&checkBroadcastByIndex);
5454
} else {
55-
compiler->addFunctionCall(&broadcast);
55+
compiler->addFunctionCall(&broadcastAndWait);
5656
compiler->addInput(input);
5757
compiler->addFunctionCall(&checkBroadcast);
5858
}
@@ -77,16 +77,28 @@ unsigned int EventBlocks::broadcastByIndex(VirtualMachine *vm)
7777
return 1;
7878
}
7979

80+
unsigned int EventBlocks::broadcastAndWait(VirtualMachine *vm)
81+
{
82+
vm->engine()->broadcast(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString()), vm, true);
83+
return 1;
84+
}
85+
86+
unsigned int EventBlocks::broadcastByIndexAndWait(VirtualMachine *vm)
87+
{
88+
vm->engine()->broadcast(vm->getInput(0, 1)->toLong(), vm, true);
89+
return 1;
90+
}
91+
8092
unsigned int EventBlocks::checkBroadcast(VirtualMachine *vm)
8193
{
82-
if (vm->engine()->broadcastRunning(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString())))
94+
if (vm->engine()->broadcastRunning(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString()), vm))
8395
vm->stop(true, true, true);
8496
return 1;
8597
}
8698

8799
unsigned int EventBlocks::checkBroadcastByIndex(VirtualMachine *vm)
88100
{
89-
if (vm->engine()->broadcastRunning(vm->getInput(0, 1)->toLong()))
101+
if (vm->engine()->broadcastRunning(vm->getInput(0, 1)->toLong(), vm))
90102
vm->stop(true, true, true);
91103
return 1;
92104
}

src/blocks/eventblocks.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class EventBlocks : public IBlockSection
3535
private:
3636
static unsigned int broadcast(VirtualMachine *vm);
3737
static unsigned int broadcastByIndex(VirtualMachine *vm);
38+
static unsigned int broadcastAndWait(VirtualMachine *vm);
39+
static unsigned int broadcastByIndexAndWait(VirtualMachine *vm);
3840
static unsigned int checkBroadcast(VirtualMachine *vm);
3941
static unsigned int checkBroadcastByIndex(VirtualMachine *vm);
4042
};

src/engine/engine.cpp

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,28 @@ void Engine::compile()
119119

120120
void Engine::frame()
121121
{
122+
m_lockFrame = false;
123+
122124
for (int i = 0; i < m_runningScripts.size(); i++) {
123125
auto script = m_runningScripts[i];
124126
m_breakFrame = false;
125127

126128
do {
127129
script->run();
128130
if (script->atEnd()) {
129-
for (auto &[key, value] : m_broadcastMap)
130-
value.erase(std::remove(value.begin(), value.end(), script->script()), value.end());
131+
for (auto &[key, value] : m_runningBroadcastMap) {
132+
size_t index = 0;
133+
134+
for (const auto &pair : value) {
135+
if (pair.second == script.get()) {
136+
value.erase(value.begin() + index);
137+
break;
138+
}
139+
140+
index++;
141+
}
142+
}
143+
131144
m_scriptsToRemove.push_back(script.get());
132145
}
133146
} while (!script->atEnd() && !m_breakFrame);
@@ -180,24 +193,48 @@ void Engine::startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<T
180193
}
181194
}
182195

183-
void libscratchcpp::Engine::broadcast(unsigned int index, VirtualMachine *sourceScript)
196+
void libscratchcpp::Engine::broadcast(unsigned int index, VirtualMachine *sourceScript, bool wait)
184197
{
198+
bool previousSkipFrame = m_skipFrame;
199+
skipFrame();
185200
const std::vector<Script *> &scripts = m_broadcastMap[index];
201+
186202
for (auto script : scripts) {
187-
size_t index = -1;
188-
for (size_t i = 0; i < m_runningScripts.size(); i++) {
203+
long scriptIndex = -1;
204+
for (long i = 0; i < m_runningScripts.size(); i++) {
189205
if (m_runningScripts[i]->script() == script) {
190-
index = i;
206+
scriptIndex = i;
191207
break;
192208
}
193209
}
194-
if (index != -1) {
210+
211+
if (scriptIndex != -1) {
195212
// Reset the script if it's already running
196-
m_runningScripts[index]->reset();
197-
if (script == sourceScript->script())
213+
auto vm = m_runningScripts[scriptIndex];
214+
vm->reset();
215+
216+
// Remove the script from scripts to remove because it's going to run again
217+
m_scriptsToRemove.erase(std::remove(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), vm.get()), m_scriptsToRemove.end());
218+
assert(std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), m_runningScripts[scriptIndex].get()) == m_scriptsToRemove.end());
219+
220+
auto &scripts = m_runningBroadcastMap[index];
221+
222+
for (auto &pair : scripts) {
223+
if (pair.second->script() == script)
224+
pair.first = sourceScript;
225+
}
226+
227+
if (script == sourceScript->script()) {
198228
sourceScript->stop(false, true);
229+
230+
if (!previousSkipFrame && !wait)
231+
m_skipFrame = false;
232+
} else
233+
sourceScript->stop(true, true);
199234
} else {
200-
m_runningScripts.push_back(script->start());
235+
auto vm = script->start();
236+
m_runningScripts.push_back(vm);
237+
m_runningBroadcastMap[index].push_back({ sourceScript, vm.get() });
201238
}
202239
}
203240
}
@@ -227,6 +264,8 @@ void Engine::run()
227264

228265
while (true) {
229266
auto lastFrameTime = std::chrono::steady_clock::now();
267+
m_skipFrame = false;
268+
230269
// Execute the frame
231270
frame();
232271
if (m_runningScripts.size() <= 0)
@@ -236,17 +275,29 @@ void Engine::run()
236275
auto currentTime = std::chrono::steady_clock::now();
237276
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - lastFrameTime);
238277
auto sleepTime = frameDuration - elapsedTime;
278+
bool timeOut = sleepTime <= std::chrono::milliseconds::zero();
239279

240-
if (sleepTime > std::chrono::milliseconds::zero())
280+
if (!timeOut && !m_skipFrame)
241281
std::this_thread::sleep_for(sleepTime);
242282

283+
if ((m_skipFrame && timeOut) || !m_skipFrame) {
284+
// TODO: Repaint here
285+
}
286+
243287
lastFrameTime = currentTime;
244288
}
245289
}
246290

247-
bool Engine::broadcastRunning(unsigned int index)
291+
bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
248292
{
249-
return !m_broadcastMap[index].empty();
293+
const auto &scripts = m_runningBroadcastMap[index];
294+
295+
for (const auto &pair : scripts) {
296+
if (pair.first == sourceScript)
297+
return true;
298+
}
299+
300+
return false;
250301
}
251302

252303
void Engine::breakFrame()
@@ -259,6 +310,20 @@ bool libscratchcpp::Engine::breakingCurrentFrame()
259310
return m_breakFrame;
260311
}
261312

313+
void Engine::skipFrame()
314+
{
315+
if (!m_lockFrame) {
316+
breakFrame();
317+
m_skipFrame = true;
318+
}
319+
}
320+
321+
void Engine::lockFrame()
322+
{
323+
m_skipFrame = false;
324+
m_lockFrame = true;
325+
}
326+
262327
void Engine::registerSection(std::shared_ptr<IBlockSection> section)
263328
{
264329
if (section) {
@@ -364,8 +429,13 @@ void libscratchcpp::Engine::addBroadcastScript(std::shared_ptr<Block> whenReceiv
364429
if (m_broadcastMap.count(id) == 1) {
365430
std::vector<Script *> &scripts = m_broadcastMap[id];
366431
scripts.push_back(m_scripts[whenReceivedBlock].get());
367-
} else
432+
} else {
368433
m_broadcastMap[id] = { m_scripts[whenReceivedBlock].get() };
434+
435+
// Create a vector of running scripts for this broadcast
436+
// so we don't need to check if it's there
437+
m_runningBroadcastMap[id] = {};
438+
}
369439
}
370440

371441
const std::vector<std::shared_ptr<Target>> &Engine::targets() const

src/engine/engine.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@ class Engine : public IEngine
3030
void start() override;
3131
void stop() override;
3232
void startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<Target> target) override;
33-
void broadcast(unsigned int index, VirtualMachine *sourceScript) override;
33+
void broadcast(unsigned int index, VirtualMachine *sourceScript, bool wait = false) override;
3434
void stopScript(VirtualMachine *vm) override;
3535
void stopTarget(Target *target, VirtualMachine *exceptScript) override;
3636
void run() override;
3737

38-
bool broadcastRunning(unsigned int index) override;
38+
bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
3939

4040
void breakFrame() override;
4141
bool breakingCurrentFrame() override;
4242

43+
void skipFrame() override;
44+
void lockFrame() override;
45+
4346
void registerSection(std::shared_ptr<IBlockSection> section) override;
4447
unsigned int functionIndex(BlockFunc f) override;
4548

@@ -81,13 +84,16 @@ class Engine : public IEngine
8184
std::vector<std::shared_ptr<Target>> m_targets;
8285
std::vector<std::shared_ptr<Broadcast>> m_broadcasts;
8386
std::unordered_map<unsigned int, std::vector<Script *>> m_broadcastMap;
87+
std::unordered_map<unsigned int, std::vector<std::pair<VirtualMachine *, VirtualMachine *>>> m_runningBroadcastMap; // source script, "when received" script
8488
std::vector<std::string> m_extensions;
8589
std::vector<std::shared_ptr<VirtualMachine>> m_runningScripts;
8690
std::vector<VirtualMachine *> m_scriptsToRemove;
8791
std::unordered_map<std::shared_ptr<Block>, std::shared_ptr<Script>> m_scripts;
8892
std::vector<BlockFunc> m_functions;
8993

9094
bool m_breakFrame = false;
95+
bool m_skipFrame = false;
96+
bool m_lockFrame = false;
9197
};
9298

9399
} // namespace libscratchcpp

test/mocks/enginemock.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ class EngineMock : public IEngine
1818
MOCK_METHOD(void, start, (), (override));
1919
MOCK_METHOD(void, stop, (), (override));
2020
MOCK_METHOD(void, startScript, (std::shared_ptr<Block>, std::shared_ptr<Target>), (override));
21-
MOCK_METHOD(void, broadcast, (unsigned int, VirtualMachine *), (override));
21+
MOCK_METHOD(void, broadcast, (unsigned int, VirtualMachine *, bool), (override));
2222
MOCK_METHOD(void, stopScript, (VirtualMachine *), (override));
2323
MOCK_METHOD(void, stopTarget, (Target *, VirtualMachine *), (override));
2424
MOCK_METHOD(void, run, (), (override));
2525

26-
MOCK_METHOD(bool, broadcastRunning, (unsigned int), (override));
26+
MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
2727

2828
MOCK_METHOD(void, breakFrame, (), (override));
2929
MOCK_METHOD(bool, breakingCurrentFrame, (), (override));
3030

31+
MOCK_METHOD(void, skipFrame, (), (override));
32+
MOCK_METHOD(void, lockFrame, (), (override));
33+
3134
MOCK_METHOD(void, registerSection, (std::shared_ptr<IBlockSection>), (override));
3235
MOCK_METHOD(unsigned int, functionIndex, (BlockFunc), (override));
3336

0 commit comments

Comments
 (0)