Skip to content

Commit b6ae58f

Browse files
committed
ScriptBuilder: Add support for building multiple scripts
1 parent 24b98b2 commit b6ae58f

File tree

5 files changed

+135
-36
lines changed

5 files changed

+135
-36
lines changed

include/scratchcpp/dev/test/scriptbuilder.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ class ScriptBuilderPrivate;
2525
class LIBSCRATCHCPP_EXPORT ScriptBuilder
2626
{
2727
public:
28-
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
28+
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock = true);
2929
ScriptBuilder(const ScriptBuilder &) = delete;
3030

3131
~ScriptBuilder();
3232

33+
void addBlock(std::shared_ptr<Block> block);
3334
void addBlock(const std::string &opcode);
3435
void captureBlockReturnValue();
3536

@@ -53,9 +54,15 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
5354

5455
List *capturedValues() const;
5556

57+
static void buildMultiple(const std::vector<ScriptBuilder *> &builders);
58+
5659
private:
57-
void addBlock(std::shared_ptr<Block> block);
60+
void addBlockToList(std::shared_ptr<Block> block);
5861
void build(std::shared_ptr<Target> target);
62+
std::string nextId();
63+
64+
static void addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks);
65+
static void addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target);
5966

6067
spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
6168
};

src/dev/test/scriptbuilder.cpp

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,53 @@
1616
using namespace libscratchcpp;
1717
using namespace libscratchcpp::test;
1818

19-
static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;
19+
// TODO: Add support for return values captures when building multiple scripts
20+
static std::unordered_map<const ScriptBuilder *, std::shared_ptr<List>> captureLists;
21+
static ScriptBuilder *currentScriptBuilder = nullptr;
2022

2123
/*! Constructs ScriptBuilder. */
22-
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
24+
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock) :
2325
impl(spimpl::make_unique_impl<ScriptBuilderPrivate>(engine, target))
2426
{
2527
// Create capture list
26-
if (captureLists.find(engine) != captureLists.cend()) {
27-
std::cerr << "error: only one ScriptBuilder can be created for each engine" << std::endl;
28-
return;
29-
}
30-
31-
captureLists[engine] = std::make_shared<List>("", "");
28+
captureLists[this] = std::make_shared<List>("", "");
3229

3330
// Add start hat block
34-
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "script_builder_init");
35-
engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
36-
compiler->engine()->addGreenFlagScript(compiler->block());
37-
return nullptr;
38-
});
39-
addBlock(block);
31+
if (createHatBlock) {
32+
auto block = std::make_shared<Block>(nextId(), "script_builder_init");
33+
engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
34+
compiler->engine()->addGreenFlagScript(compiler->block());
35+
return nullptr;
36+
});
37+
addBlockToList(block);
38+
}
4039

4140
// Add compile function for return value capture block
4241
engine->addCompileFunction(extension, "script_builder_capture", [](Compiler *compiler) -> CompilerValue * {
4342
CompilerValue *input = compiler->addInput("VALUE");
44-
compiler->createListAppend(captureLists[compiler->engine()].get(), input);
43+
compiler->createListAppend(captureLists[currentScriptBuilder].get(), input);
4544
return nullptr;
4645
});
4746
}
4847

4948
/*! Destroys ScriptBuilder. */
5049
ScriptBuilder::~ScriptBuilder()
5150
{
52-
captureLists.erase(impl->engine);
51+
captureLists.erase(this);
52+
}
53+
54+
/*! Adds the given block to the script. */
55+
void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
56+
{
57+
block->setId(nextId());
58+
impl->lastBlock = block;
59+
addBlockToList(block);
5360
}
5461

5562
/*! Adds a block with the given opcode to the script. */
5663
void ScriptBuilder::addBlock(const std::string &opcode)
5764
{
58-
impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
59-
addBlock(impl->lastBlock);
65+
addBlock(std::make_shared<Block>("", opcode));
6066
}
6167

6268
/*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
@@ -101,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
101107
block->setParent(impl->lastBlock);
102108

103109
while (block) {
104-
block->setId(std::to_string(impl->blockId++));
110+
block->setId(nextId());
105111
impl->inputBlocks.push_back(block);
106112

107113
auto parent = block->parent();
@@ -128,7 +134,7 @@ void ScriptBuilder::addNullObscuredInput(const std::string &name)
128134
return;
129135

130136
auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
131-
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
137+
auto block = std::make_shared<Block>(nextId(), "");
132138
block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
133139
input->setValueBlock(block);
134140
impl->inputBlocks.push_back(block);
@@ -144,7 +150,7 @@ void ScriptBuilder::addDropdownInput(const std::string &name, const std::string
144150
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
145151
impl->lastBlock->addInput(input);
146152

147-
auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), impl->lastBlock->opcode() + "_menu");
153+
auto menu = std::make_shared<Block>(nextId(), impl->lastBlock->opcode() + "_menu");
148154
menu->setShadow(true);
149155
impl->inputBlocks.push_back(menu);
150156
input->setValueBlock(menu);
@@ -170,7 +176,7 @@ void ScriptBuilder::addEntityInput(const std::string &name, const std::string &e
170176
return;
171177

172178
if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
173-
entity->setId(std::to_string(impl->blockId++));
179+
entity->setId(nextId());
174180
impl->entities.push_back(entity);
175181
}
176182

@@ -188,7 +194,7 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Enti
188194
return;
189195

190196
if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
191-
entity->setId(std::to_string(impl->blockId++));
197+
entity->setId(nextId());
192198
impl->entities.push_back(entity);
193199
}
194200

@@ -218,6 +224,10 @@ std::shared_ptr<Block> ScriptBuilder::currentBlock()
218224
target->addList(list);
219225

220226
build(target);
227+
228+
std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
229+
targets.erase(std::remove(targets.begin(), targets.end(), target), targets.end());
230+
impl->engine->setTargets(targets);
221231
}
222232

223233
return impl->lastBlock;
@@ -256,10 +266,31 @@ void ScriptBuilder::run()
256266
/*! Returns the list of captured block return values. */
257267
List *ScriptBuilder::capturedValues() const
258268
{
259-
return captureLists[impl->engine].get();
269+
return captureLists[this].get();
260270
}
261271

262-
void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
272+
/*!
273+
* Builds multiple scripts using the given script builders.
274+
* \note Using run() on any of the script builders will result in all scripts without a custom hat block being called. Use this only with a single when flag clicked block.
275+
* \note Return value capturing is not supported when building multiple scripts.
276+
*/
277+
void ScriptBuilder::buildMultiple(const std::vector<ScriptBuilder *> &builders)
278+
{
279+
std::unordered_set<IEngine *> engines;
280+
281+
for (ScriptBuilder *builder : builders) {
282+
auto target = builder->impl->target;
283+
addBlocksToTarget(target.get(), builder->impl->blocks);
284+
addBlocksToTarget(target.get(), builder->impl->inputBlocks);
285+
addTargetToEngine(builder->impl->engine, target);
286+
engines.insert(builder->impl->engine);
287+
}
288+
289+
for (IEngine *engine : engines)
290+
engine->compile();
291+
}
292+
293+
void ScriptBuilder::addBlockToList(std::shared_ptr<Block> block)
263294
{
264295
if (!impl->blocks.empty()) {
265296
auto lastBlock = impl->blocks.back();
@@ -272,20 +303,37 @@ void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
272303

273304
void ScriptBuilder::build(std::shared_ptr<Target> target)
274305
{
275-
if (target->blocks().empty()) {
276-
for (auto block : impl->blocks)
277-
target->addBlock(block);
306+
currentScriptBuilder = this;
307+
308+
addBlocksToTarget(target.get(), impl->blocks);
309+
addBlocksToTarget(target.get(), impl->inputBlocks);
310+
addTargetToEngine(impl->engine, target);
311+
312+
impl->engine->compile();
313+
currentScriptBuilder = nullptr;
314+
}
315+
316+
std::string ScriptBuilder::nextId()
317+
{
318+
return std::to_string((uintptr_t)this) + '.' + std::to_string(impl->blockId++);
319+
}
320+
321+
void ScriptBuilder::addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks)
322+
{
323+
auto targetBlocks = target->blocks();
278324

279-
for (auto block : impl->inputBlocks)
325+
for (auto block : blocks) {
326+
if (std::find(targetBlocks.begin(), targetBlocks.end(), block) == targetBlocks.end())
280327
target->addBlock(block);
281328
}
329+
}
282330

283-
std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
331+
void ScriptBuilder::addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target)
332+
{
333+
std::vector<std::shared_ptr<Target>> targets = engine->targets();
284334

285335
if (std::find(targets.begin(), targets.end(), target) == targets.end()) {
286336
targets.push_back(target);
287-
impl->engine->setTargets({ target });
337+
engine->setTargets(targets);
288338
}
289-
290-
impl->engine->compile();
291339
}

test/dev/test_api/scriptbuilder_test.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <scratchcpp/dev/test/scriptbuilder.h>
22
#include <scratchcpp/project.h>
33
#include <scratchcpp/sprite.h>
4+
#include <scratchcpp/stage.h>
45
#include <scratchcpp/iengine.h>
56
#include <scratchcpp/value.h>
67
#include <scratchcpp/block.h>
@@ -43,11 +44,18 @@ TEST_F(ScriptBuilderTest, AddBlock)
4344
ASSERT_EQ(block->opcode(), "test_simple");
4445
ASSERT_TRUE(block->compileFunction());
4546

47+
block = std::make_shared<Block>("", "test_simple");
48+
m_builder->addBlock(block);
49+
block = m_builder->currentBlock();
50+
ASSERT_TRUE(block);
51+
ASSERT_EQ(block->opcode(), "test_simple");
52+
ASSERT_TRUE(block->compileFunction());
53+
4654
m_builder->build();
4755

4856
testing::internal::CaptureStdout();
4957
m_builder->run();
50-
ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
58+
ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\n");
5159
}
5260

5361
TEST_F(ScriptBuilderTest, AddValueInput)
@@ -271,3 +279,31 @@ TEST_F(ScriptBuilderTest, CaptureBlockReturnValue)
271279
ASSERT_EQ(str, "test");
272280
ASSERT_EQ(value_toDouble(&values->operator[](1)), -93.4);
273281
}
282+
283+
TEST_F(ScriptBuilderTest, MultipleScripts)
284+
{
285+
ScriptBuilder builder1(&m_extension, m_engine, m_target, false);
286+
builder1.addBlock("test_click_hat");
287+
builder1.addBlock("test_simple");
288+
289+
ScriptBuilder builder2(&m_extension, m_engine, m_target);
290+
builder2.addBlock("test_print");
291+
builder2.addValueInput("STRING", "Hello world");
292+
293+
Project project;
294+
IEngine *engine = project.engine().get();
295+
m_extension.registerBlocks(engine);
296+
auto target = std::make_shared<Stage>();
297+
ScriptBuilder builder3(&m_extension, engine, target);
298+
builder3.addBlock("test_simple");
299+
300+
ScriptBuilder::buildMultiple({ &builder1, &builder2, &builder3 });
301+
302+
testing::internal::CaptureStdout();
303+
builder2.run();
304+
ASSERT_EQ(testing::internal::GetCapturedStdout(), "Hello world\n");
305+
306+
testing::internal::CaptureStdout();
307+
builder3.run();
308+
ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
309+
}

test/dev/test_api/testextension.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ void TestExtension::registerBlocks(IEngine *engine)
3030
engine->addCompileFunction(this, "test_teststr", &compileTestStr);
3131
engine->addCompileFunction(this, "test_input", &compileInput);
3232
engine->addCompileFunction(this, "test_substack", &compileSubstack);
33+
engine->addCompileFunction(this, "test_click_hat", &compileClickHat);
3334
}
3435

3536
CompilerValue *TestExtension::compileSimple(Compiler *compiler)
@@ -77,6 +78,12 @@ CompilerValue *TestExtension::compileSubstack(Compiler *compiler)
7778
return nullptr;
7879
}
7980

81+
CompilerValue *TestExtension::compileClickHat(Compiler *compiler)
82+
{
83+
compiler->engine()->addTargetClickScript(compiler->block());
84+
return nullptr;
85+
}
86+
8087
extern "C" void test_simple()
8188
{
8289
std::cout << "test" << std::endl;

test/dev/test_api/testextension.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class TestExtension : public IExtension
2121
static CompilerValue *compileTestStr(Compiler *compiler);
2222
static CompilerValue *compileInput(Compiler *compiler);
2323
static CompilerValue *compileSubstack(Compiler *compiler);
24+
static CompilerValue *compileClickHat(Compiler *compiler);
2425
};
2526

2627
} // namespace libscratchcpp

0 commit comments

Comments
 (0)