From 702a66b17c6e199654dbad857d7f0df667c85cdf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 29 Aug 2023 20:54:01 +0200 Subject: [PATCH 01/14] Add engine property to Target --- include/scratchcpp/target.h | 4 ++++ src/scratch/target.cpp | 12 ++++++++++++ src/scratch/target_p.h | 2 ++ test/scratch_classes/CMakeLists.txt | 2 ++ test/scratch_classes/target_test.cpp | 11 +++++++++++ 5 files changed, 31 insertions(+) diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h index 3a9b01a7..7262394b 100644 --- a/include/scratchcpp/target.h +++ b/include/scratchcpp/target.h @@ -10,6 +10,7 @@ namespace libscratchcpp { +class IEngine; class Variable; class List; class Block; @@ -67,6 +68,9 @@ class LIBSCRATCHCPP_EXPORT Target int volume() const; void setVolume(int newVolume); + IEngine *engine() const; + void setEngine(IEngine *engine); + private: spimpl::unique_impl_ptr impl; }; diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index e0413a1b..5555b403 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -289,3 +289,15 @@ void Target::setVolume(int newVolume) { impl->volume = newVolume; } + +/*! Returns the engine. */ +IEngine *Target::engine() const +{ + return impl->engine; +} + +/*! Sets the engine. */ +void Target::setEngine(IEngine *engine) +{ + impl->engine = engine; +} diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h index 1a990b23..4ea13e05 100644 --- a/src/scratch/target_p.h +++ b/src/scratch/target_p.h @@ -11,6 +11,7 @@ namespace libscratchcpp { +class IEngine; class Variable; class List; class Block; @@ -20,6 +21,7 @@ struct TargetPrivate TargetPrivate(); TargetPrivate(const TargetPrivate &) = delete; + IEngine *engine = nullptr; std::string name; std::vector> variables; std::vector> lists; diff --git a/test/scratch_classes/CMakeLists.txt b/test/scratch_classes/CMakeLists.txt index 787496d7..49bb075b 100644 --- a/test/scratch_classes/CMakeLists.txt +++ b/test/scratch_classes/CMakeLists.txt @@ -63,7 +63,9 @@ add_executable( target_link_libraries( target_test GTest::gtest_main + GTest::gmock_main scratchcpp + scratchcpp_mocks ) gtest_discover_tests(target_test) diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp index 4112f12d..ce623368 100644 --- a/test/scratch_classes/target_test.cpp +++ b/test/scratch_classes/target_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../common.h" @@ -194,3 +195,13 @@ TEST(TargetTest, Volume) target.setVolume(50); ASSERT_EQ(target.volume(), 50); } + +TEST(TargetTest, Engine) +{ + Target target; + ASSERT_EQ(target.engine(), nullptr); + + EngineMock engine; + target.setEngine(&engine); + ASSERT_EQ(target.engine(), &engine); +} From aab0942dcc705ed9cfa36c5dd32b17dee3905adc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:07:21 +0200 Subject: [PATCH 02/14] Make targets aware of engine --- src/engine/internal/engine.cpp | 3 ++- test/engine/engine_test.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 2eefcca0..4ed65248 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -461,8 +461,9 @@ void Engine::setTargets(const std::vector> &newTargets) { m_targets = newTargets; - // Set engine and target in all blocks + // Set engine in targets and engine and target in all blocks for (auto target : m_targets) { + target->setEngine(this); auto blocks = target->blocks(); for (auto block : blocks) { block->setEngine(this); diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index f9fed87b..2b4ee447 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -255,7 +255,11 @@ TEST(EngineTest, Targets) auto t1 = std::make_shared(); t1->setName("Sprite1"); auto t2 = std::make_shared(); + auto block1 = std::make_shared("", ""); + auto block2 = std::make_shared("", ""); t2->setName("Sprite2"); + t2->addBlock(block1); + t2->addBlock(block2); auto t3 = std::make_shared(); t3->setName("Stage"); engine.setTargets({ t1, t2, t3 }); @@ -271,4 +275,11 @@ TEST(EngineTest, Targets) ASSERT_EQ(engine.findTarget("Sprite1"), 0); ASSERT_EQ(engine.findTarget("Sprite2"), 1); ASSERT_EQ(engine.findTarget("Stage"), 2); + + ASSERT_EQ(t1->engine(), &engine); + ASSERT_EQ(t2->engine(), &engine); + ASSERT_EQ(t3->engine(), &engine); + + ASSERT_EQ(block1->engine(), &engine); + ASSERT_EQ(block2->engine(), &engine); } From f3e0aab4e55567ba384dd36dc2884b94472b6894 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:15:47 +0200 Subject: [PATCH 03/14] Add a way to use a custom target in Script::start() --- include/scratchcpp/script.h | 1 + src/engine/script.cpp | 8 +++++++- test/script/script_test.cpp | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h index 8090d603..0e01a80c 100644 --- a/include/scratchcpp/script.h +++ b/include/scratchcpp/script.h @@ -36,6 +36,7 @@ class LIBSCRATCHCPP_EXPORT Script void setLists(const std::vector &lists); std::shared_ptr start(); + std::shared_ptr start(Target *target); private: spimpl::unique_impl_ptr impl; diff --git a/src/engine/script.cpp b/src/engine/script.cpp index a6739fe9..729b363f 100644 --- a/src/engine/script.cpp +++ b/src/engine/script.cpp @@ -35,7 +35,13 @@ void Script::setBytecode(const std::vector &code) /*! Starts the script (creates a virtual machine). */ std::shared_ptr Script::start() { - auto vm = std::make_shared(impl->target, impl->engine, this); + return start(impl->target); +} + +/*! Starts the script (creates a virtual machine). */ +std::shared_ptr Script::start(Target *target) +{ + auto vm = std::make_shared(target, impl->engine, this); vm->setBytecode(impl->bytecode); vm->setProcedures(impl->procedures); vm->setFunctions(impl->functions); diff --git a/test/script/script_test.cpp b/test/script/script_test.cpp index 36ac6cc1..d0d9ad6a 100644 --- a/test/script/script_test.cpp +++ b/test/script/script_test.cpp @@ -81,4 +81,15 @@ TEST_F(ScriptTest, Start) ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString()); ASSERT_EQ(vm->variables()[0], variables[0]); ASSERT_EQ(vm->lists()[0], lists[0]); + + Target target; + vm = script3.start(&target); + ASSERT_TRUE(vm); + ASSERT_EQ(vm->target(), &target); + ASSERT_EQ(vm->bytecode()[0], bytecode[0]); + ASSERT_EQ(vm->procedures()[0], procedures[0]); + ASSERT_EQ(vm->functions()[0], functions[0]); + ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString()); + ASSERT_EQ(vm->variables()[0], variables[0]); + ASSERT_EQ(vm->lists()[0], lists[0]); } From 76ab1172fad20692a024e2392f14442dbf62663b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:42:19 +0200 Subject: [PATCH 04/14] Sprite: Add clone info methods --- include/scratchcpp/sprite.h | 6 ++++++ src/scratch/sprite.cpp | 27 +++++++++++++++++++++++++++ src/scratch/sprite_p.cpp | 13 +++++++++++++ src/scratch/sprite_p.h | 5 +++++ 4 files changed, 51 insertions(+) diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h index d9ede3d5..952040d2 100644 --- a/include/scratchcpp/sprite.h +++ b/include/scratchcpp/sprite.h @@ -22,9 +22,15 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target }; Sprite(); + ~Sprite(); void setInterface(ISpriteHandler *newInterface); + bool isClone() const; + + Sprite *cloneRoot() const; + Sprite *cloneParent() const; + bool visible() const; void setVisible(bool newVisible); diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp index 206db9c6..7ae1b2db 100644 --- a/src/scratch/sprite.cpp +++ b/src/scratch/sprite.cpp @@ -16,6 +16,15 @@ Sprite::Sprite() : { } +/*! Destroys the Sprite object. */ +Sprite::~Sprite() +{ + if (isClone()) { + assert(impl->cloneParent); + impl->cloneParent->impl->removeClone(this); + } +} + /*! Sets the sprite interface. */ void Sprite::setInterface(ISpriteHandler *newInterface) { @@ -24,6 +33,24 @@ void Sprite::setInterface(ISpriteHandler *newInterface) impl->iface->onSpriteChanged(this); } +/*! Returns true if this is a clone. */ +bool Sprite::isClone() const +{ + return (impl->cloneParent != nullptr); +} + +/*! Returns the sprite this clone was created from, or nullptr if this isn't a clone. */ +Sprite *Sprite::cloneRoot() const +{ + return impl->cloneRoot; +} + +/*! Returns the sprite or clone this clone was created from, or nullptr if this isn't a clone. */ +Sprite *Sprite::cloneParent() const +{ + return impl->cloneParent; +} + /*! Returns true if the sprite is visible. */ bool Sprite::visible() const { diff --git a/src/scratch/sprite_p.cpp b/src/scratch/sprite_p.cpp index 81333fbc..22267c42 100644 --- a/src/scratch/sprite_p.cpp +++ b/src/scratch/sprite_p.cpp @@ -10,6 +10,19 @@ SpritePrivate::SpritePrivate() { } +void SpritePrivate::removeClone(Sprite *clone) +{ + int index = 0; + for (const auto &child : childClones) { + if (child.get() == clone) { + childClones.erase(childClones.begin() + index); + return; + } + + index++; + } +} + void SpritePrivate::setCostumeData(const char *data) { if (iface) diff --git a/src/scratch/sprite_p.h b/src/scratch/sprite_p.h index 2656f228..84ca01ea 100644 --- a/src/scratch/sprite_p.h +++ b/src/scratch/sprite_p.h @@ -12,9 +12,14 @@ struct SpritePrivate SpritePrivate(); SpritePrivate(const SpritePrivate &) = delete; + void removeClone(Sprite *clone); + void setCostumeData(const char *data); ISpriteHandler *iface = nullptr; + Sprite *cloneRoot = nullptr; + Sprite *cloneParent = nullptr; + std::vector> childClones; bool visible = true; double x = 0; double y = 0; From af49e8be4f2ccb2697651cf78b4873b6b1caf1e5 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:45:31 +0200 Subject: [PATCH 05/14] Engine: Add clone API --- include/scratchcpp/iengine.h | 7 +++++ src/engine/internal/engine.cpp | 49 ++++++++++++++++++++++++++++++++++ src/engine/internal/engine.h | 4 +++ test/mocks/enginemock.h | 3 +++ 4 files changed, 63 insertions(+) diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index 0d9d8fca..94d6648a 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -15,6 +15,7 @@ class IBlockSection; class Broadcast; class Block; class Target; +class Sprite; class Script; /*! @@ -70,6 +71,9 @@ class LIBSCRATCHCPP_EXPORT IEngine */ virtual void stopTarget(Target *target, VirtualMachine *exceptScript) = 0; + /*! Calls the "when I start as a clone" blocks of the given sprite. */ + virtual void initClone(Sprite *clone) = 0; + /*! * Runs the event loop and calls "when green flag clicked" blocks. * \note This function returns when all scripts finish.\n @@ -157,6 +161,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Registers the broadcast script. */ virtual void addBroadcastScript(std::shared_ptr whenReceivedBlock, std::shared_ptr broadcast) = 0; + /* Registers the given "when I start as clone" script. */ + virtual void addCloneInitScript(std::shared_ptr hatBlock) = 0; + /*! Returns the list of targets. */ virtual const std::vector> &targets() const = 0; diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 4ed65248..6257c5a9 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -258,6 +259,37 @@ void Engine::stopTarget(Target *target, VirtualMachine *exceptScript) stopScript(script); } +void Engine::initClone(Sprite *clone) +{ + if (!clone) + return; + + Sprite *source = clone->cloneParent(); + Target *root = clone->cloneRoot(); + assert(source); + assert(root); + + if (!source || !root) + return; + + auto it = m_cloneInitScriptsMap.find(root); + + if (it != m_cloneInitScriptsMap.cend()) { + const auto &scripts = it->second; + +#ifndef NDEBUG + // Since we're initializing the clone, it shouldn't have any running scripts + for (const auto script : m_runningScripts) + assert(script->target() != clone); +#endif + + for (auto script : scripts) { + auto vm = script->start(clone); + m_runningScripts.push_back(vm); + } + } +} + void Engine::run() { auto frameDuration = std::chrono::milliseconds(33); @@ -442,6 +474,7 @@ void Engine::addBroadcastScript(std::shared_ptr whenReceivedBlock, std::s auto id = findBroadcast(broadcast->name()); if (m_broadcastMap.count(id) == 1) { std::vector