Skip to content

Commit 7a10ac0

Browse files
committed
Add edge-activated hat API to IEngine
1 parent d1a45d1 commit 7a10ac0

File tree

6 files changed

+72
-7
lines changed

6 files changed

+72
-7
lines changed

include/scratchcpp/iengine.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ class LIBSCRATCHCPP_EXPORT IEngine
223223
*/
224224
virtual void addCompileFunction(IBlockSection *section, const std::string &opcode, BlockComp f) = 0;
225225

226+
/*!
227+
* Call this from IBlockSection#registerBlocks() to add a hat block predicate compile function to a block section.
228+
* \note This only works with edge-activated hats.
229+
* \see <a href="blockSections.html">Block sections</a>
230+
*/
231+
virtual void addHatPredicateCompileFunction(IBlockSection *section, const std::string &opcode, HatPredicateCompileFunc f) = 0;
232+
226233
/*!
227234
* Call this from IBlockSection#registerBlocks() to add a monitor name function to a block section.
228235
* \see <a href="blockSections.html">Block sections</a>

src/engine/internal/blocksectioncontainer.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ void BlockSectionContainer::addCompileFunction(const std::string &opcode, BlockC
1111
m_compileFunctions[opcode] = f;
1212
}
1313

14+
void BlockSectionContainer::addHatPredicateCompileFunction(const std::string &opcode, HatPredicateCompileFunc f)
15+
{
16+
m_hatPredicateCompileFunctions[opcode] = f;
17+
}
18+
1419
void BlockSectionContainer::addMonitorNameFunction(const std::string &opcode, MonitorNameFunc f)
1520
{
1621
m_monitorNameFunctions[opcode] = f;
@@ -48,6 +53,13 @@ BlockComp BlockSectionContainer::resolveBlockCompileFunc(const std::string &opco
4853
return nullptr;
4954
}
5055

56+
HatPredicateCompileFunc BlockSectionContainer::resolveHatPredicateCompileFunc(const std::string &opcode) const
57+
{
58+
if (m_hatPredicateCompileFunctions.count(opcode) == 1)
59+
return m_hatPredicateCompileFunctions.at(opcode);
60+
return nullptr;
61+
}
62+
5163
MonitorNameFunc BlockSectionContainer::resolveMonitorNameFunc(const std::string &opcode) const
5264
{
5365
if (m_monitorNameFunctions.count(opcode) == 1)

src/engine/internal/blocksectioncontainer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class BlockSectionContainer
1616
BlockSectionContainer(const BlockSectionContainer &) = delete;
1717

1818
void addCompileFunction(const std::string &opcode, BlockComp f);
19+
void addHatPredicateCompileFunction(const std::string &opcode, HatPredicateCompileFunc f);
1920
void addMonitorNameFunction(const std::string &opcode, MonitorNameFunc f);
2021
void addMonitorChangeFunction(const std::string &opcode, MonitorChangeFunc f);
2122
void addHatBlock(const std::string &opcode);
@@ -24,6 +25,7 @@ class BlockSectionContainer
2425
void addFieldValue(const std::string &value, int id);
2526

2627
BlockComp resolveBlockCompileFunc(const std::string &opcode) const;
28+
HatPredicateCompileFunc resolveHatPredicateCompileFunc(const std::string &opcode) const;
2729
MonitorNameFunc resolveMonitorNameFunc(const std::string &opcode) const;
2830
MonitorChangeFunc resolveMonitorChangeFunc(const std::string &opcode) const;
2931
int resolveInput(const std::string &name) const;
@@ -32,6 +34,7 @@ class BlockSectionContainer
3234

3335
private:
3436
std::unordered_map<std::string, BlockComp> m_compileFunctions;
37+
std::unordered_map<std::string, HatPredicateCompileFunc> m_hatPredicateCompileFunctions;
3538
std::unordered_map<std::string, MonitorNameFunc> m_monitorNameFunctions;
3639
std::unordered_map<std::string, MonitorChangeFunc> m_monitorChangeFunctions;
3740
std::unordered_map<std::string, int> m_inputs;

src/engine/internal/engine.cpp

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const std::unordered_map<Engine::HatType, bool> Engine::m_hatRestartExistingThre
3838
{ HatType::CloneInit, false }, { HatType::KeyPressed, false }, { HatType::TargetClicked, true }
3939
};
4040

41+
const std::unordered_map<Engine::HatType, bool> Engine::m_hatEdgeActivated = {
42+
{ HatType::GreenFlag, false }, { HatType::BroadcastReceived, false }, { HatType::BackdropChanged, false },
43+
{ HatType::CloneInit, false }, { HatType::KeyPressed, false }, { HatType::TargetClicked, false }
44+
};
45+
4146
Engine::Engine() :
4247
m_defaultTimer(std::make_unique<Timer>()),
4348
m_timer(m_defaultTimer.get()),
@@ -79,6 +84,7 @@ void Engine::clear()
7984
m_whenKeyPressedHats.clear();
8085

8186
m_scriptHatFields.clear();
87+
m_edgeActivatedHatValues.clear();
8288

8389
m_running = false;
8490
}
@@ -93,8 +99,11 @@ void Engine::resolveIds()
9399
auto container = blockSectionContainer(block->opcode());
94100
block->setNext(getBlock(block->nextId()));
95101
block->setParent(getBlock(block->parentId()));
96-
if (container)
102+
103+
if (container) {
97104
block->setCompileFunction(container->resolveBlockCompileFunc(block->opcode()));
105+
block->setHatPredicateCompileFunction(container->resolveHatPredicateCompileFunc(block->opcode()));
106+
}
98107

99108
const auto &inputs = block->inputs();
100109
for (const auto &input : inputs) {
@@ -215,6 +224,8 @@ void Engine::compile()
215224
compiler.compile(block);
216225

217226
script->setBytecode(compiler.bytecode());
227+
script->setHatPredicateBytecode(compiler.hatPredicateBytecode());
228+
218229
if (block->opcode() == "procedures_definition") {
219230
auto b = block->inputAt(block->findInput("custom_block"))->valueBlock();
220231
procedureBytecodeMap[b->mutationPrototype()->procCode()] = script->bytecode();
@@ -295,6 +306,7 @@ void Engine::start()
295306

296307
m_eventLoopMutex.lock();
297308
m_timer->reset();
309+
m_edgeActivatedHatValues.clear();
298310
m_running = true;
299311

300312
// Start "when green flag clicked" scripts
@@ -432,6 +444,30 @@ void Engine::step()
432444
// Clean up threads that were told to stop during or since the last step
433445
m_threads.erase(std::remove_if(m_threads.begin(), m_threads.end(), [](std::shared_ptr<VirtualMachine> thread) { return thread->atEnd(); }), m_threads.end());
434446

447+
// Find all edge-activated hats, and add them to threads to be evaluated
448+
for (auto const &[hatType, edgeActivated] : m_hatEdgeActivated) {
449+
if (edgeActivated) {
450+
auto newThreads = startHats(hatType, {}, nullptr);
451+
452+
// Process edge-triggered hats (must happen here because of Scratch 2 compatibility)
453+
// Processing the hats means running their predicates (if they didn't change their return value from false to true, remove the threads)
454+
for (auto thread : newThreads) {
455+
bool oldValue = false;
456+
auto it = m_edgeActivatedHatValues.find(hatType);
457+
458+
if (it != m_edgeActivatedHatValues.cend())
459+
oldValue = it->second;
460+
461+
bool newValue = thread->script()->runHatPredicate();
462+
bool edgeWasActivated = !oldValue && newValue; // changed from false true
463+
m_edgeActivatedHatValues[hatType] = newValue;
464+
465+
if (!edgeWasActivated)
466+
stopThread(thread.get());
467+
}
468+
}
469+
}
470+
435471
m_redrawRequested = false;
436472

437473
// Step threads
@@ -856,6 +892,14 @@ void Engine::addCompileFunction(IBlockSection *section, const std::string &opcod
856892
container->addCompileFunction(opcode, f);
857893
}
858894

895+
void Engine::addHatPredicateCompileFunction(IBlockSection *section, const std::string &opcode, HatPredicateCompileFunc f)
896+
{
897+
auto container = blockSectionContainer(section);
898+
899+
if (container)
900+
container->addHatPredicateCompileFunction(opcode, f);
901+
}
902+
859903
void Engine::addMonitorNameFunction(IBlockSection *section, const std::string &opcode, MonitorNameFunc f)
860904
{
861905
auto container = blockSectionContainer(section);
@@ -1718,11 +1762,5 @@ Engine::startHats(HatType hatType, const std::unordered_map<HatField, std::varia
17181762
},
17191763
optTarget);
17201764

1721-
// Run edge-triggered hats (for compatibility with Scratch 2)
1722-
// TODO: Find out what "edge-triggered" hats are and execute them
1723-
// Uncommenting this would cause infinite recursion in some cases, so let's keep it commented
1724-
/*for (auto thread : newThreads)
1725-
thread->run();*/
1726-
17271765
return newThreads;
17281766
}

src/engine/internal/engine.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class Engine : public IEngine
105105
unsigned int functionIndex(BlockFunc f) override;
106106

107107
void addCompileFunction(IBlockSection *section, const std::string &opcode, BlockComp f) override;
108+
void addHatPredicateCompileFunction(IBlockSection *section, const std::string &opcode, HatPredicateCompileFunc f) override;
108109
void addMonitorNameFunction(IBlockSection *section, const std::string &opcode, MonitorNameFunc f) override;
109110
void addMonitorChangeFunction(IBlockSection *section, const std::string &opcode, MonitorChangeFunc f) override;
110111
void addHatBlock(IBlockSection *section, const std::string &opcode) override;
@@ -216,6 +217,7 @@ class Engine : public IEngine
216217
startHats(HatType hatType, const std::unordered_map<HatField, std::variant<std::string, const char *, Entity *>> &optMatchFields, Target *optTarget);
217218

218219
static const std::unordered_map<HatType, bool> m_hatRestartExistingThreads; // used to check whether a hat should restart existing threads
220+
static const std::unordered_map<HatType, bool> m_hatEdgeActivated; // used to check whether a hat is edge-activated (runs when a predicate becomes true)
219221

220222
std::unordered_map<std::shared_ptr<IBlockSection>, std::unique_ptr<BlockSectionContainer>> m_sections;
221223
std::vector<std::shared_ptr<Target>> m_targets;
@@ -241,6 +243,8 @@ class Engine : public IEngine
241243

242244
std::unordered_map<Script *, std::unordered_map<HatField, int>> m_scriptHatFields; // HatField, field ID from the block implementation
243245

246+
std::unordered_map<HatType, bool> m_edgeActivatedHatValues; // edge-activated hats only run after the value changes from false to true
247+
244248
std::unique_ptr<ITimer> m_defaultTimer;
245249
ITimer *m_timer = nullptr;
246250
double m_fps = 30; // default FPS

test/mocks/enginemock.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class EngineMock : public IEngine
8686
MOCK_METHOD(unsigned int, functionIndex, (BlockFunc), (override));
8787

8888
MOCK_METHOD(void, addCompileFunction, (IBlockSection *, const std::string &, BlockComp), (override));
89+
MOCK_METHOD(void, addHatPredicateCompileFunction, (IBlockSection *, const std::string &, HatPredicateCompileFunc), (override));
8990
MOCK_METHOD(void, addMonitorNameFunction, (IBlockSection *, const std::string &, MonitorNameFunc), (override));
9091
MOCK_METHOD(void, addMonitorChangeFunction, (IBlockSection *, const std::string &, MonitorChangeFunc), (override));
9192
MOCK_METHOD(void, addHatBlock, (IBlockSection *, const std::string &), (override));

0 commit comments

Comments
 (0)