Skip to content

Commit 0744481

Browse files
committed
Implement looks_sayforsecs block
1 parent 78b1399 commit 0744481

File tree

3 files changed

+206
-3
lines changed

3 files changed

+206
-3
lines changed

src/blocks/looksblocks.cpp

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
// SPDX-License-Identifier: Apache-2.0
22

33
#include <scratchcpp/iengine.h>
4-
#include <scratchcpp/target.h>
5-
#include <scratchcpp/textbubble.h>
4+
#include <scratchcpp/compiler.h>
5+
#include <scratchcpp/compilerconstant.h>
6+
#include <scratchcpp/executioncontext.h>
7+
#include <scratchcpp/istacktimer.h>
8+
#include <scratchcpp/thread.h>
9+
#include <scratchcpp/sprite.h>
10+
#include <scratchcpp/stringptr.h>
11+
#include <scratchcpp/value.h>
12+
#include <utf8.h>
13+
614
#include "looksblocks.h"
715

816
using namespace libscratchcpp;
@@ -24,10 +32,24 @@ Rgb LooksBlocks::color() const
2432

2533
void LooksBlocks::registerBlocks(IEngine *engine)
2634
{
35+
engine->addCompileFunction(this, "looks_sayforsecs", &compileSayForSecs);
2736
}
2837

2938
void LooksBlocks::onInit(IEngine *engine)
3039
{
40+
engine->threadAboutToStop().connect([](Thread *thread) {
41+
/*
42+
* TODO: Scratch uses promises for text bubble timeout
43+
* which clear the text bubbles even after the thread
44+
* destroyed. Since we don't use promises, we just
45+
* clear the text bubble when the thread is destroyed.
46+
*/
47+
Target *target = thread->target();
48+
49+
if (target->bubble()->owner() == thread)
50+
target->bubble()->setText("");
51+
});
52+
3153
engine->stopped().connect([engine]() {
3254
const auto &targets = engine->targets();
3355

@@ -37,3 +59,63 @@ void LooksBlocks::onInit(IEngine *engine)
3759
}
3860
});
3961
}
62+
63+
void LooksBlocks::compileSayOrThinkForSecs(Compiler *compiler, const std::string function)
64+
{
65+
auto message = compiler->addInput("MESSAGE");
66+
auto duration = compiler->addInput("SECS");
67+
auto saveThread = compiler->addConstValue(true);
68+
compiler->addFunctionCallWithCtx(function, Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { message, saveThread });
69+
compiler->addFunctionCallWithCtx("looks_start_stack_timer", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration });
70+
compiler->createYield();
71+
72+
compiler->beginLoopCondition();
73+
auto elapsed = compiler->addFunctionCallWithCtx("looks_update_bubble", Compiler::StaticType::Bool);
74+
compiler->beginRepeatUntilLoop(elapsed);
75+
compiler->endLoop();
76+
}
77+
78+
CompilerValue *LooksBlocks::compileSayForSecs(Compiler *compiler)
79+
{
80+
compileSayOrThinkForSecs(compiler, "looks_say");
81+
return nullptr;
82+
}
83+
84+
extern "C" void looks_start_stack_timer(ExecutionContext *ctx, double duration)
85+
{
86+
ctx->stackTimer()->start(duration);
87+
}
88+
89+
void looks_show_bubble(Thread *thread, TextBubble::Type type, const StringPtr *message, bool saveThread)
90+
{
91+
Target *target = thread->target();
92+
// TODO: Use UTF-16 for text bubbles
93+
std::string u8message = utf8::utf16to8(std::u16string(message->data));
94+
target->bubble()->setType(type);
95+
96+
if (saveThread)
97+
target->bubble()->setText(u8message, thread);
98+
else
99+
target->bubble()->setText(u8message);
100+
}
101+
102+
extern "C" bool looks_update_bubble(ExecutionContext *ctx)
103+
{
104+
if (ctx->stackTimer()->elapsed()) {
105+
Thread *thread = ctx->thread();
106+
Target *target = thread->target();
107+
108+
// Clear bubble if it hasn't been changed
109+
if (target->bubble()->owner() == thread)
110+
target->bubble()->setText("");
111+
112+
return true;
113+
}
114+
115+
return false;
116+
}
117+
118+
extern "C" void looks_say(ExecutionContext *ctx, const StringPtr *message, bool saveThread)
119+
{
120+
looks_show_bubble(ctx->thread(), TextBubble::Type::Say, message, saveThread);
121+
}

src/blocks/looksblocks.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
#pragma once
44

55
#include <scratchcpp/iextension.h>
6+
#include <scratchcpp/textbubble.h>
7+
#include <unordered_map>
68

79
namespace libscratchcpp
810
{
911

12+
class Target;
13+
class Thread;
14+
1015
class LooksBlocks : public IExtension
1116
{
1217
public:
@@ -16,6 +21,10 @@ class LooksBlocks : public IExtension
1621

1722
void registerBlocks(IEngine *engine) override;
1823
void onInit(IEngine *engine) override;
24+
25+
private:
26+
static void compileSayOrThinkForSecs(Compiler *compiler, const std::string function);
27+
static CompilerValue *compileSayForSecs(Compiler *compiler);
1928
};
2029

2130
} // namespace libscratchcpp

test/blocks/looks_blocks_test.cpp

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
#include <scratchcpp/test/scriptbuilder.h>
33
#include <scratchcpp/sprite.h>
44
#include <scratchcpp/stage.h>
5-
#include <scratchcpp/textbubble.h>
5+
#include <scratchcpp/compiler.h>
6+
#include <scratchcpp/script.h>
7+
#include <scratchcpp/thread.h>
8+
#include <scratchcpp/executablecode.h>
9+
#include <scratchcpp/executioncontext.h>
610
#include <enginemock.h>
711
#include <graphicseffectmock.h>
12+
#include <stacktimermock.h>
813

914
#include "../common.h"
1015
#include "blocks/looksblocks.h"
1116

1217
using namespace libscratchcpp;
18+
using namespace libscratchcpp::test;
1319

1420
using ::testing::Return;
1521

@@ -50,3 +56,109 @@ TEST_F(LooksBlocksTest, StopProject)
5056
ASSERT_EQ(stage->graphicsEffectValue(&effect), 0);
5157
ASSERT_EQ(sprite->graphicsEffectValue(&effect), 0);
5258
}
59+
60+
TEST_F(LooksBlocksTest, SayForSecs)
61+
{
62+
auto sprite = std::make_shared<Sprite>();
63+
sprite->setEngine(&m_engineMock);
64+
ScriptBuilder builder(m_extension.get(), m_engine, sprite);
65+
66+
builder.addBlock("looks_sayforsecs");
67+
builder.addValueInput("MESSAGE", "Hello world");
68+
builder.addValueInput("SECS", 2.5);
69+
auto block = builder.currentBlock();
70+
71+
Compiler compiler(&m_engineMock, sprite.get());
72+
auto code = compiler.compile(block);
73+
Script script(sprite.get(), block, &m_engineMock);
74+
script.setCode(code);
75+
Thread thread(sprite.get(), &m_engineMock, &script);
76+
auto ctx = code->createExecutionContext(&thread);
77+
StackTimerMock timer;
78+
ctx->setStackTimer(&timer);
79+
80+
EXPECT_CALL(timer, start(2.5));
81+
EXPECT_CALL(m_engineMock, requestRedraw());
82+
code->run(ctx.get());
83+
ASSERT_FALSE(code->isFinished(ctx.get()));
84+
ASSERT_EQ(sprite->bubble()->text(), "Hello world");
85+
ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say);
86+
87+
EXPECT_CALL(timer, elapsed()).WillOnce(Return(false));
88+
EXPECT_CALL(m_engineMock, requestRedraw).Times(0);
89+
code->run(ctx.get());
90+
ASSERT_FALSE(code->isFinished(ctx.get()));
91+
ASSERT_EQ(sprite->bubble()->text(), "Hello world");
92+
ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say);
93+
94+
EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
95+
EXPECT_CALL(m_engineMock, requestRedraw).Times(0);
96+
code->run(ctx.get());
97+
ASSERT_TRUE(code->isFinished(ctx.get()));
98+
ASSERT_TRUE(sprite->bubble()->text().empty());
99+
100+
// Change text while waiting
101+
code->reset(ctx.get());
102+
103+
EXPECT_CALL(timer, start(2.5));
104+
EXPECT_CALL(m_engineMock, requestRedraw());
105+
code->run(ctx.get());
106+
ASSERT_FALSE(code->isFinished(ctx.get()));
107+
108+
EXPECT_CALL(m_engineMock, requestRedraw());
109+
sprite->bubble()->setText("test");
110+
111+
EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
112+
EXPECT_CALL(m_engineMock, requestRedraw).Times(0);
113+
code->run(ctx.get());
114+
ASSERT_TRUE(code->isFinished(ctx.get()));
115+
ASSERT_EQ(sprite->bubble()->text(), "test");
116+
ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say);
117+
118+
code->reset(ctx.get());
119+
120+
EXPECT_CALL(timer, start(2.5));
121+
EXPECT_CALL(m_engineMock, requestRedraw());
122+
code->run(ctx.get());
123+
ASSERT_FALSE(code->isFinished(ctx.get()));
124+
125+
EXPECT_CALL(m_engineMock, requestRedraw());
126+
sprite->bubble()->setText("Hello world");
127+
128+
EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
129+
EXPECT_CALL(m_engineMock, requestRedraw).Times(0);
130+
code->run(ctx.get());
131+
ASSERT_TRUE(code->isFinished(ctx.get()));
132+
ASSERT_EQ(sprite->bubble()->text(), "Hello world");
133+
ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say);
134+
135+
// Kill waiting thread
136+
code->reset(ctx.get());
137+
138+
EXPECT_CALL(timer, start(2.5));
139+
EXPECT_CALL(m_engineMock, requestRedraw());
140+
code->run(ctx.get());
141+
ASSERT_FALSE(code->isFinished(ctx.get()));
142+
143+
m_engine->threadAboutToStop()(&thread);
144+
code->kill(ctx.get());
145+
ASSERT_EQ(sprite->bubble()->owner(), nullptr);
146+
ASSERT_TRUE(sprite->bubble()->text().empty());
147+
148+
// Kill waiting thread after changing text
149+
code->reset(ctx.get());
150+
151+
EXPECT_CALL(timer, start(2.5));
152+
EXPECT_CALL(m_engineMock, requestRedraw());
153+
code->run(ctx.get());
154+
ASSERT_FALSE(code->isFinished(ctx.get()));
155+
156+
EXPECT_CALL(m_engineMock, requestRedraw());
157+
sprite->bubble()->setText("test");
158+
159+
m_engine->threadAboutToStop()(&thread);
160+
code->kill(ctx.get());
161+
ASSERT_EQ(sprite->bubble()->owner(), nullptr);
162+
ASSERT_EQ(sprite->bubble()->text(), "test");
163+
ASSERT_EQ(sprite->bubble()->type(), TextBubble::Type::Say);
164+
}

0 commit comments

Comments
 (0)