Skip to content

Commit f24ce30

Browse files
authored
Merge pull request #297 from scratchcpp/clone_limit
Add clone limit
2 parents f5927e1 + f40cb9d commit f24ce30

File tree

9 files changed

+204
-49
lines changed

9 files changed

+204
-49
lines changed

include/scratchcpp/iengine.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
8181
/*! Calls the "when I start as a clone" blocks of the given sprite. */
8282
virtual void initClone(Sprite *clone) = 0;
8383

84+
/*! Automatically called from clones that are being deleted. */
85+
virtual void deinitClone(Sprite *clone) = 0;
86+
8487
/*!
8588
* Runs the event loop and calls "when green flag clicked" blocks.
8689
* \note This function returns when all scripts finish.\n
@@ -137,6 +140,12 @@ class LIBSCRATCHCPP_EXPORT IEngine
137140
/*! Sets the stage height. */
138141
virtual void setStageHeight(unsigned int height) = 0;
139142

143+
/*! Returns the maximum number of clones (or -1 if the limit is disabled). */
144+
virtual int cloneLimit() const = 0;
145+
146+
/*! Sets the maximum number of clones (use -1 or any negative number to disable the limit). */
147+
virtual void setCloneLimit(int limit) = 0;
148+
140149
/*! Returns true if there are any running script of the broadcast with the given index. */
141150
virtual bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) = 0;
142151

src/engine/internal/engine.cpp

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ Engine::Engine() :
3232
{
3333
}
3434

35+
Engine::~Engine()
36+
{
37+
m_clones.clear();
38+
}
39+
3540
void Engine::clear()
3641
{
3742
m_sections.clear();
3843
m_targets.clear();
3944
m_broadcasts.clear();
45+
m_clones.clear();
4046
}
4147

4248
// Resolves ID references and sets pointers of entities.
@@ -172,6 +178,11 @@ void Engine::frame()
172178

173179
void Engine::start()
174180
{
181+
if (m_running)
182+
finalize();
183+
184+
deleteClones();
185+
175186
m_timer->reset();
176187
m_running = true;
177188

@@ -184,9 +195,8 @@ void Engine::start()
184195

185196
void Engine::stop()
186197
{
187-
m_runningScripts.clear();
188-
m_scriptsToRemove.clear();
189-
m_running = false;
198+
finalize();
199+
deleteClones();
190200
}
191201

192202
void Engine::startScript(std::shared_ptr<Block> topLevelBlock, std::shared_ptr<Target> target)
@@ -301,7 +311,7 @@ void Engine::stopTarget(Target *target, VirtualMachine *exceptScript)
301311

302312
void Engine::initClone(Sprite *clone)
303313
{
304-
if (!clone)
314+
if (!clone || ((m_cloneLimit >= 0) && (m_clones.size() >= m_cloneLimit)))
305315
return;
306316

307317
Sprite *source = clone->cloneParent();
@@ -328,6 +338,14 @@ void Engine::initClone(Sprite *clone)
328338
m_runningScripts.push_back(vm);
329339
}
330340
}
341+
342+
assert(std::find(m_clones.begin(), m_clones.end(), clone) == m_clones.end());
343+
m_clones.push_back(clone);
344+
}
345+
346+
void Engine::deinitClone(Sprite *clone)
347+
{
348+
m_clones.erase(std::remove(m_clones.begin(), m_clones.end(), clone), m_clones.end());
331349
}
332350

333351
void Engine::run()
@@ -360,7 +378,7 @@ void Engine::run()
360378
lastFrameTime = currentTime;
361379
}
362380

363-
stop();
381+
finalize();
364382
}
365383

366384
bool Engine::isRunning() const
@@ -463,6 +481,16 @@ void Engine::setStageHeight(unsigned int height)
463481
m_stageHeight = height;
464482
}
465483

484+
int Engine::cloneLimit() const
485+
{
486+
return m_cloneLimit;
487+
}
488+
489+
void Engine::setCloneLimit(int limit)
490+
{
491+
m_cloneLimit = limit < 0 ? -1 : limit;
492+
}
493+
466494
bool Engine::broadcastRunning(unsigned int index, VirtualMachine *sourceScript)
467495
{
468496
if (index < 0 || index >= m_broadcasts.size())
@@ -879,6 +907,31 @@ BlockSectionContainer *Engine::blockSectionContainer(IBlockSection *section) con
879907
return nullptr;
880908
}
881909

910+
void Engine::finalize()
911+
{
912+
m_runningScripts.clear();
913+
m_scriptsToRemove.clear();
914+
m_running = false;
915+
}
916+
917+
void Engine::deleteClones()
918+
{
919+
m_clones.clear();
920+
921+
for (auto target : m_targets) {
922+
Sprite *sprite = dynamic_cast<Sprite *>(target.get());
923+
924+
if (sprite) {
925+
std::vector<std::shared_ptr<Sprite>> clones = sprite->children();
926+
927+
for (auto clone : clones) {
928+
assert(clone);
929+
clone->~Sprite();
930+
}
931+
}
932+
}
933+
}
934+
882935
void Engine::updateFrameDuration()
883936
{
884937
m_frameDuration = std::chrono::milliseconds(static_cast<long>(1000 / m_fps));

src/engine/internal/engine.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Engine : public IEngine
2121
public:
2222
Engine();
2323
Engine(const Engine &) = delete;
24+
~Engine();
2425

2526
void clear() override;
2627
void resolveIds();
@@ -35,6 +36,7 @@ class Engine : public IEngine
3536
void stopScript(VirtualMachine *vm) override;
3637
void stopTarget(Target *target, VirtualMachine *exceptScript) override;
3738
void initClone(libscratchcpp::Sprite *clone) override;
39+
void deinitClone(libscratchcpp::Sprite *clone) override;
3840
void run() override;
3941

4042
bool isRunning() const override;
@@ -61,6 +63,9 @@ class Engine : public IEngine
6163
unsigned int stageHeight() const override;
6264
void setStageHeight(unsigned int height) override;
6365

66+
int cloneLimit() const override;
67+
void setCloneLimit(int limit) override;
68+
6469
bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
6570
bool broadcastByPtrRunning(Broadcast *broadcast, VirtualMachine *sourceScript) override;
6671

@@ -109,6 +114,8 @@ class Engine : public IEngine
109114
BlockSectionContainer *blockSectionContainer(IBlockSection *section) const;
110115

111116
private:
117+
void finalize();
118+
void deleteClones();
112119
std::shared_ptr<Block> getBlock(const std::string &id);
113120
std::shared_ptr<Variable> getVariable(const std::string &id);
114121
std::shared_ptr<List> getList(const std::string &id);
@@ -142,6 +149,8 @@ class Engine : public IEngine
142149
bool m_mousePressed = false;
143150
unsigned int m_stageWidth = 480;
144151
unsigned int m_stageHeight = 360;
152+
int m_cloneLimit = 300;
153+
std::vector<Sprite *> m_clones;
145154

146155
bool m_running = false;
147156
bool m_breakFrame = false;

src/scratch/sprite.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ Sprite::Sprite() :
2323
Sprite::~Sprite()
2424
{
2525
if (isClone()) {
26+
IEngine *eng = engine();
27+
28+
if (eng) {
29+
eng->deinitClone(this);
30+
31+
auto children = allChildren();
32+
for (auto child : children)
33+
eng->deinitClone(child.get());
34+
}
35+
2636
assert(impl->cloneParent);
2737
impl->cloneParent->impl->removeClone(this);
2838
}

test/clone_limit.sb3

2.31 KB
Binary file not shown.

test/engine/engine_test.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,58 @@ TEST(EngineTest, Clones)
547547
}
548548
}
549549

550+
TEST(EngineTest, CloneLimit)
551+
{
552+
Project p("clone_limit.sb3");
553+
ASSERT_TRUE(p.load());
554+
auto engine = p.engine();
555+
ASSERT_EQ(engine->cloneLimit(), 300);
556+
557+
// TODO: Set "infinite" FPS and remove this (#254)
558+
engine->setFps(100000);
559+
560+
Stage *stage = engine->stage();
561+
ASSERT_TRUE(stage);
562+
563+
p.run();
564+
ASSERT_VAR(stage, "count");
565+
ASSERT_EQ(GET_VAR(stage, "count")->value().toInt(), 300);
566+
ASSERT_VAR(stage, "delete_passed");
567+
ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
568+
569+
engine->setCloneLimit(475);
570+
ASSERT_EQ(engine->cloneLimit(), 475);
571+
p.run();
572+
ASSERT_VAR(stage, "count");
573+
ASSERT_EQ(GET_VAR(stage, "count")->value().toInt(), 475);
574+
ASSERT_VAR(stage, "delete_passed");
575+
ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
576+
577+
engine->setCloneLimit(0);
578+
ASSERT_EQ(engine->cloneLimit(), 0);
579+
p.run();
580+
ASSERT_VAR(stage, "count");
581+
ASSERT_EQ(GET_VAR(stage, "count")->value().toInt(), 0);
582+
ASSERT_VAR(stage, "delete_passed");
583+
ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
584+
585+
engine->setCloneLimit(-1);
586+
ASSERT_EQ(engine->cloneLimit(), -1);
587+
p.run();
588+
ASSERT_VAR(stage, "count");
589+
ASSERT_GT(GET_VAR(stage, "count")->value().toInt(), 500);
590+
ASSERT_VAR(stage, "delete_passed");
591+
ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
592+
593+
engine->setCloneLimit(-5);
594+
ASSERT_EQ(engine->cloneLimit(), -1);
595+
p.run();
596+
ASSERT_VAR(stage, "count");
597+
ASSERT_GT(GET_VAR(stage, "count")->value().toInt(), 500);
598+
ASSERT_VAR(stage, "delete_passed");
599+
ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
600+
}
601+
550602
// TODO: Uncomment this after fixing #256 and #257
551603
/*TEST(EngineTest, BackdropBroadcasts)
552604
{

test/mocks/enginemock.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class EngineMock : public IEngine
2323
MOCK_METHOD(void, stopScript, (VirtualMachine *), (override));
2424
MOCK_METHOD(void, stopTarget, (Target *, VirtualMachine *), (override));
2525
MOCK_METHOD(void, initClone, (Sprite *), (override));
26+
MOCK_METHOD(void, deinitClone, (Sprite *), (override));
2627
MOCK_METHOD(void, run, (), (override));
2728

2829
MOCK_METHOD(bool, isRunning, (), (const, override));
@@ -49,6 +50,9 @@ class EngineMock : public IEngine
4950
MOCK_METHOD(unsigned int, stageHeight, (), (const, override));
5051
MOCK_METHOD(void, setStageHeight, (unsigned int), (override));
5152

53+
MOCK_METHOD(int, cloneLimit, (), (const, override));
54+
MOCK_METHOD(void, setCloneLimit, (int), (override));
55+
5256
MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
5357
MOCK_METHOD(bool, broadcastByPtrRunning, (Broadcast *, VirtualMachine *), (override));
5458

0 commit comments

Comments
 (0)