diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index 4d553499..0072ed45 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -12,10 +12,12 @@ #include "looksblocks.h" #include "../engine/internal/randomgenerator.h" +#include "../engine/internal/clock.h" using namespace libscratchcpp; IRandomGenerator *LooksBlocks::rng = nullptr; +IClock *LooksBlocks::clock = nullptr; std::string LooksBlocks::name() const { @@ -25,6 +27,10 @@ std::string LooksBlocks::name() const void LooksBlocks::registerBlocks(IEngine *engine) { // Blocks + engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs); + engine->addCompileFunction(this, "looks_say", &compileSay); + engine->addCompileFunction(this, "looks_thinkforsecs", &compileThinkForSecs); + engine->addCompileFunction(this, "looks_think", &compileThink); engine->addCompileFunction(this, "looks_show", &compileShow); engine->addCompileFunction(this, "looks_hide", &compileHide); engine->addCompileFunction(this, "looks_changeeffectby", &compileChangeEffectBy); @@ -49,6 +55,8 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addMonitorNameFunction(this, "looks_size", &sizeMonitorName); // Inputs + engine->addInput(this, "MESSAGE", MESSAGE); + engine->addInput(this, "SECS", SECS); engine->addInput(this, "CHANGE", CHANGE); engine->addInput(this, "SIZE", SIZE); engine->addInput(this, "COSTUME", COSTUME); @@ -77,6 +85,34 @@ void LooksBlocks::registerBlocks(IEngine *engine) engine->addFieldValue(this, "backward", Backward); } +void LooksBlocks::compileSayForSecs(Compiler *compiler) +{ + compiler->addInput(MESSAGE); + compiler->addInput(SECS); + compiler->addFunctionCall(&startSayForSecs); + compiler->addFunctionCall(&sayForSecs); +} + +void LooksBlocks::compileSay(Compiler *compiler) +{ + compiler->addInput(MESSAGE); + compiler->addFunctionCall(&say); +} + +void LooksBlocks::compileThinkForSecs(Compiler *compiler) +{ + compiler->addInput(MESSAGE); + compiler->addInput(SECS); + compiler->addFunctionCall(&startThinkForSecs); + compiler->addFunctionCall(&thinkForSecs); +} + +void LooksBlocks::compileThink(Compiler *compiler) +{ + compiler->addInput(MESSAGE); + compiler->addFunctionCall(&think); +} + void LooksBlocks::compileShow(Compiler *compiler) { compiler->addFunctionCall(&show); @@ -515,6 +551,114 @@ const std::string &LooksBlocks::sizeMonitorName(Block *block) return name; } +void LooksBlocks::startWait(VirtualMachine *vm, double secs) +{ + if (!clock) + clock = Clock::instance().get(); + + auto currentTime = clock->currentSteadyTime(); + m_timeMap[vm] = { currentTime, secs * 1000 }; + vm->engine()->requestRedraw(); +} + +bool LooksBlocks::wait(VirtualMachine *vm) +{ + if (!clock) + clock = Clock::instance().get(); + + auto currentTime = clock->currentSteadyTime(); + assert(m_timeMap.count(vm) == 1); + + if (std::chrono::duration_cast(currentTime - m_timeMap[vm].first).count() >= m_timeMap[vm].second) { + m_timeMap.erase(vm); + vm->stop(true, true, false); + return true; + } else { + vm->stop(true, true, true); + return false; + } +} + +void LooksBlocks::showBubble(VirtualMachine *vm, Target::BubbleType type, const std::string &text) +{ + Target *target = vm->target(); + + if (target) { + target->setBubbleType(type); + target->setBubbleText(text); + m_waitingBubbles.erase(target); + } +} + +void LooksBlocks::hideBubble(Target *target) +{ + if (!target) + return; + + target->setBubbleText(""); + m_waitingBubbles.erase(target); +} + +unsigned int LooksBlocks::startSayForSecs(VirtualMachine *vm) +{ + Target *target = vm->target(); + + if (target) { + showBubble(vm, Target::BubbleType::Say, vm->getInput(0, 2)->toString()); + m_waitingBubbles[target] = vm; + startWait(vm, vm->getInput(1, 2)->toDouble()); + } + + return 2; +} + +unsigned int LooksBlocks::sayForSecs(VirtualMachine *vm) +{ + if (wait(vm)) { + Target *target = vm->target(); + + if (target) { + auto it = m_waitingBubbles.find(target); + + // Clear bubble if it hasn't been changed + if (it != m_waitingBubbles.cend() && it->second == vm) + hideBubble(vm->target()); + } + } + + return 0; +} + +unsigned int LooksBlocks::say(VirtualMachine *vm) +{ + showBubble(vm, Target::BubbleType::Say, vm->getInput(0, 1)->toString()); + return 1; +} + +unsigned int LooksBlocks::startThinkForSecs(VirtualMachine *vm) +{ + Target *target = vm->target(); + + if (target) { + showBubble(vm, Target::BubbleType::Think, vm->getInput(0, 2)->toString()); + m_waitingBubbles[target] = vm; + startWait(vm, vm->getInput(1, 2)->toDouble()); + } + + return 2; +} + +unsigned int LooksBlocks::thinkForSecs(VirtualMachine *vm) +{ + return sayForSecs(vm); // there isn't any difference +} + +unsigned int LooksBlocks::think(VirtualMachine *vm) +{ + showBubble(vm, Target::BubbleType::Think, vm->getInput(0, 1)->toString()); + return 1; +} + unsigned int LooksBlocks::show(VirtualMachine *vm) { Sprite *sprite = dynamic_cast(vm->target()); diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 585fc0ec..91a598b4 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -3,16 +3,19 @@ #pragma once #include +#include #include +#include +#include namespace libscratchcpp { -class Target; class Stage; class Value; class IGraphicsEffect; class IRandomGenerator; +class IClock; /*! \brief The LooksBlocks class contains the implementation of looks blocks. */ class LooksBlocks : public IBlockSection @@ -20,6 +23,8 @@ class LooksBlocks : public IBlockSection public: enum Inputs { + MESSAGE, + SECS, CHANGE, SIZE, COSTUME, @@ -57,6 +62,10 @@ class LooksBlocks : public IBlockSection void registerBlocks(IEngine *engine) override; + static void compileSayForSecs(Compiler *compiler); + static void compileSay(Compiler *compiler); + static void compileThinkForSecs(Compiler *compiler); + static void compileThink(Compiler *compiler); static void compileShow(Compiler *compiler); static void compileHide(Compiler *compiler); static void compileChangeEffectBy(Compiler *compiler); @@ -79,6 +88,19 @@ class LooksBlocks : public IBlockSection static const std::string &backdropNumberNameMonitorName(Block *block); static const std::string &sizeMonitorName(Block *block); + static void startWait(VirtualMachine *vm, double secs); + static bool wait(VirtualMachine *vm); + static void showBubble(VirtualMachine *vm, Target::BubbleType type, const std::string &text); + static void hideBubble(Target *target); + + static unsigned int startSayForSecs(VirtualMachine *vm); + static unsigned int sayForSecs(VirtualMachine *vm); + static unsigned int say(VirtualMachine *vm); + + static unsigned int startThinkForSecs(VirtualMachine *vm); + static unsigned int thinkForSecs(VirtualMachine *vm); + static unsigned int think(VirtualMachine *vm); + static unsigned int show(VirtualMachine *vm); static unsigned int hide(VirtualMachine *vm); @@ -141,6 +163,9 @@ class LooksBlocks : public IBlockSection static unsigned int backdropNumber(VirtualMachine *vm); static unsigned int backdropName(VirtualMachine *vm); + static inline std::unordered_map> m_timeMap; + static inline std::unordered_map m_waitingBubbles; + static inline std::vector m_customGraphicsEffects; static inline IGraphicsEffect *m_colorEffect = nullptr; static inline IGraphicsEffect *m_fisheyeEffect = nullptr; @@ -151,6 +176,7 @@ class LooksBlocks : public IBlockSection static inline IGraphicsEffect *m_ghostEffect = nullptr; static IRandomGenerator *rng; + static IClock *clock; }; } // namespace libscratchcpp diff --git a/test/blocks/looks_blocks_test.cpp b/test/blocks/looks_blocks_test.cpp index 9057051a..288c4781 100644 --- a/test/blocks/looks_blocks_test.cpp +++ b/test/blocks/looks_blocks_test.cpp @@ -9,12 +9,14 @@ #include #include #include +#include #include "../common.h" #include "blocks/looksblocks.h" #include "blocks/operatorblocks.h" #include "engine/internal/engine.h" #include "engine/internal/randomgenerator.h" +#include "engine/internal/clock.h" using namespace libscratchcpp; @@ -104,6 +106,10 @@ TEST_F(LooksBlocksTest, CategoryVisible) TEST_F(LooksBlocksTest, RegisterBlocks) { // Blocks + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_sayforsecs", &LooksBlocks::compileSayForSecs)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_say", &LooksBlocks::compileSay)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_thinkforsecs", &LooksBlocks::compileThinkForSecs)); + EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_think", &LooksBlocks::compileThink)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_show", &LooksBlocks::compileShow)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_hide", &LooksBlocks::compileHide)); EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "looks_changeeffectby", &LooksBlocks::compileChangeEffectBy)); @@ -128,6 +134,8 @@ TEST_F(LooksBlocksTest, RegisterBlocks) EXPECT_CALL(m_engineMock, addMonitorNameFunction(m_section.get(), "looks_size", &LooksBlocks::sizeMonitorName)); // Inputs + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "MESSAGE", LooksBlocks::MESSAGE)); + EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SECS", LooksBlocks::SECS)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "CHANGE", LooksBlocks::CHANGE)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SIZE", LooksBlocks::SIZE)); EXPECT_CALL(m_engineMock, addInput(m_section.get(), "COSTUME", LooksBlocks::COSTUME)); @@ -158,6 +166,418 @@ TEST_F(LooksBlocksTest, RegisterBlocks) m_section->registerBlocks(&m_engineMock); } +TEST_F(LooksBlocksTest, SayForSecs) +{ + Compiler compiler(&m_engineMock); + + // say "Hello!" for 3.5 seconds + auto block = std::make_shared("a", "looks_sayforsecs"); + addValueInput(block, "MESSAGE", LooksBlocks::MESSAGE, "Hello!"); + addValueInput(block, "SECS", LooksBlocks::SECS, 3.5); + + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::startSayForSecs)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::sayForSecs)).WillOnce(Return(1)); + + compiler.init(); + compiler.setBlock(block); + LooksBlocks::compileSayForSecs(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "Hello!", 3.5 })); +} + +TEST_F(LooksBlocksTest, SayForSecsImpl) +{ + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::startSayForSecs, &LooksBlocks::sayForSecs, &LooksBlocks::say }; + static Value constValues[] = { "test", 5.5, "hello" }; + + Target target; + target.setBubbleType(Target::BubbleType::Think); + VirtualMachine vm(&target, &m_engineMock, nullptr); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.setBytecode(bytecode1); + + ClockMock clock; + LooksBlocks::clock = &clock; + + std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(1000)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(6450)); + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1)); + target.setBubbleType(Target::BubbleType::Think); + target.setBubbleText("another"); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "another"); + + std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(6500)); + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_TRUE(target.bubbleText().empty()); + + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_TRUE(target.bubbleText().empty()); + + // Run the say block while waiting + VirtualMachine vm2(&target, &m_engineMock, nullptr); + vm2.setFunctions(functions); + vm2.setConstValues(constValues); + vm2.setBytecode(bytecode2); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + vm2.run(); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "hello"); + + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "hello"); + + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "hello"); + + // Run the say for secs block while waiting + vm2.reset(); + vm2.setBytecode(bytecode3); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm2.reset(); + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "hello"); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.reset(); + vm.run(); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + LooksBlocks::clock = Clock::instance().get(); +} + +TEST_F(LooksBlocksTest, Say) +{ + Compiler compiler(&m_engineMock); + + // say "Hello!" + auto block = std::make_shared("a", "looks_say"); + addValueInput(block, "MESSAGE", LooksBlocks::MESSAGE, "Hello!"); + + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::say)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + LooksBlocks::compileSay(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toString(), "Hello!"); +} + +TEST_F(LooksBlocksTest, SayImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::say }; + static Value constValues[] = { "test" }; + + Target target; + VirtualMachine vm(&target, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); + + target.setBubbleType(Target::BubbleType::Think); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "test"); +} + +TEST_F(LooksBlocksTest, ThinkForSecs) +{ + Compiler compiler(&m_engineMock); + + // think "Hmm..." for 3.5 seconds + auto block = std::make_shared("a", "looks_thinkforsecs"); + addValueInput(block, "MESSAGE", LooksBlocks::MESSAGE, "Hmm..."); + addValueInput(block, "SECS", LooksBlocks::SECS, 3.5); + + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::startThinkForSecs)).WillOnce(Return(0)); + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::thinkForSecs)).WillOnce(Return(1)); + + compiler.init(); + compiler.setBlock(block); + LooksBlocks::compileThinkForSecs(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues(), std::vector({ "Hmm...", 3.5 })); +} + +TEST_F(LooksBlocksTest, ThinkForSecsImpl) +{ + static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT }; + static unsigned int bytecode2[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 2, vm::OP_HALT }; + static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_CONST, 1, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::startThinkForSecs, &LooksBlocks::thinkForSecs, &LooksBlocks::think }; + static Value constValues[] = { "test", 5.5, "hello" }; + + Target target; + target.setBubbleType(Target::BubbleType::Say); + VirtualMachine vm(&target, &m_engineMock, nullptr); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.setBytecode(bytecode1); + + ClockMock clock; + LooksBlocks::clock = &clock; + + std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(1000)); + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(6450)); + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1)); + target.setBubbleType(Target::BubbleType::Say); + target.setBubbleText("another"); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_EQ(target.bubbleText(), "another"); + + std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(6500)); + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_TRUE(target.bubbleText().empty()); + + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Say); + ASSERT_TRUE(target.bubbleText().empty()); + + // Run the say block while waiting + VirtualMachine vm2(&target, &m_engineMock, nullptr); + vm2.setFunctions(functions); + vm2.setConstValues(constValues); + vm2.setBytecode(bytecode2); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + vm2.run(); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "hello"); + + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "hello"); + + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "hello"); + + // Run the say for secs block while waiting + vm2.reset(); + vm2.setBytecode(bytecode3); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm2.reset(); + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) != LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "hello"); + + EXPECT_CALL(clock, currentSteadyTime()).Times(2).WillRepeatedly(Return(startTime)); + EXPECT_CALL(m_engineMock, requestRedraw()); + vm.reset(); + vm.run(); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2)); + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) == LooksBlocks::m_timeMap.cend()); + ASSERT_FALSE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + vm2.run(); + + ASSERT_EQ(vm2.registerCount(), 0); + ASSERT_TRUE(LooksBlocks::m_timeMap.find(&vm2) == LooksBlocks::m_timeMap.cend()); + ASSERT_TRUE(vm2.atEnd()); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + LooksBlocks::clock = Clock::instance().get(); +} + +TEST_F(LooksBlocksTest, Think) +{ + Compiler compiler(&m_engineMock); + + // say "Hmm..." + auto block = std::make_shared("a", "looks_think"); + addValueInput(block, "MESSAGE", LooksBlocks::MESSAGE, "Hmm..."); + + EXPECT_CALL(m_engineMock, functionIndex(&LooksBlocks::think)).WillOnce(Return(0)); + + compiler.init(); + compiler.setBlock(block); + LooksBlocks::compileThink(&compiler); + compiler.end(); + + ASSERT_EQ(compiler.bytecode(), std::vector({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT })); + ASSERT_EQ(compiler.constValues().size(), 1); + ASSERT_EQ(compiler.constValues()[0].toString(), "Hmm..."); +} + +TEST_F(LooksBlocksTest, ThinkImpl) +{ + static unsigned int bytecode[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT }; + static BlockFunc functions[] = { &LooksBlocks::think }; + static Value constValues[] = { "test" }; + + Target target; + VirtualMachine vm(&target, nullptr, nullptr); + vm.setBytecode(bytecode); + vm.setFunctions(functions); + vm.setConstValues(constValues); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); + + target.setBubbleType(Target::BubbleType::Say); + vm.reset(); + vm.run(); + + ASSERT_EQ(vm.registerCount(), 0); + ASSERT_EQ(target.bubbleType(), Target::BubbleType::Think); + ASSERT_EQ(target.bubbleText(), "test"); +} + TEST_F(LooksBlocksTest, Show) { Compiler compiler(&m_engineMock);