Skip to content

Commit 8439b3a

Browse files
committed
Implement sensing_askandwait and sensing_answer blocks
1 parent 086ca12 commit 8439b3a

File tree

3 files changed

+589
-0
lines changed

3 files changed

+589
-0
lines changed

src/blocks/sensingblocks.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/compiler.h>
55
#include <scratchcpp/compilerconstant.h>
6+
#include <scratchcpp/thread.h>
7+
#include <scratchcpp/promise.h>
8+
#include <scratchcpp/executioncontext.h>
69
#include <scratchcpp/value.h>
710
#include <scratchcpp/input.h>
811
#include <scratchcpp/sprite.h>
12+
#include <scratchcpp/textbubble.h>
913
#include <scratchcpp/stringptr.h>
1014
#include <scratchcpp/string_functions.h>
15+
#include <scratchcpp/string_pool.h>
1116
#include <utf8.h>
1217

1318
#include "sensingblocks.h"
@@ -35,6 +40,41 @@ void SensingBlocks::registerBlocks(IEngine *engine)
3540
engine->addCompileFunction(this, "sensing_touchingcolor", &compileTouchingColor);
3641
engine->addCompileFunction(this, "sensing_coloristouchingcolor", &compileColorIsTouchingColor);
3742
engine->addCompileFunction(this, "sensing_distanceto", &compileDistanceTo);
43+
engine->addCompileFunction(this, "sensing_askandwait", &compileAskAndWait);
44+
engine->addCompileFunction(this, "sensing_answer", &compileAnswer);
45+
}
46+
47+
void SensingBlocks::onInit(IEngine *engine)
48+
{
49+
engine->questionAnswered().connect(&onAnswer);
50+
51+
engine->threadAboutToStop().connect([engine](Thread *thread) {
52+
if (!m_questions.empty()) {
53+
// Abort the question of this thread if it's currently being displayed
54+
if (m_questions.front()->thread == thread) {
55+
thread->target()->bubble()->setText("");
56+
engine->questionAborted()();
57+
}
58+
59+
m_questions.erase(std::remove_if(m_questions.begin(), m_questions.end(), [thread](const std::unique_ptr<Question> &question) { return question->thread == thread; }), m_questions.end());
60+
}
61+
});
62+
}
63+
64+
void SensingBlocks::clearQuestions()
65+
{
66+
m_questions.clear();
67+
}
68+
69+
void SensingBlocks::askQuestion(ExecutionContext *ctx, const StringPtr *question)
70+
{
71+
const bool isQuestionAsked = !m_questions.empty();
72+
// TODO: Use UTF-16 in engine (and TextBubble?)
73+
std::string u8str = utf8::utf16to8(std::u16string(question->data));
74+
enqueueAsk(u8str, ctx->thread());
75+
76+
if (!isQuestionAsked)
77+
askNextQuestion();
3878
}
3979

4080
CompilerValue *SensingBlocks::compileTouchingObject(Compiler *compiler)
@@ -107,6 +147,76 @@ CompilerValue *SensingBlocks::compileDistanceTo(Compiler *compiler)
107147
return compiler->addConstValue(10000.0);
108148
}
109149

150+
CompilerValue *SensingBlocks::compileAskAndWait(Compiler *compiler)
151+
{
152+
CompilerValue *question = compiler->addInput("QUESTION");
153+
compiler->addFunctionCallWithCtx("sensing_askandwait", Compiler::StaticType::Void, { Compiler::StaticType::String }, { question });
154+
compiler->createYield();
155+
return nullptr;
156+
}
157+
158+
CompilerValue *SensingBlocks::compileAnswer(Compiler *compiler)
159+
{
160+
return compiler->addFunctionCallWithCtx("sensing_answer", Compiler::StaticType::String);
161+
}
162+
163+
void SensingBlocks::onAnswer(const std::string &answer)
164+
{
165+
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L99-L115
166+
if (!m_questions.empty()) {
167+
Question *question = m_questions.front().get();
168+
Thread *thread = question->thread;
169+
assert(thread);
170+
assert(thread->target());
171+
172+
// If the target was visible when asked, hide the say bubble unless the target was the stage
173+
if (question->wasVisible && !question->wasStage)
174+
thread->target()->bubble()->setText("");
175+
176+
m_questions.erase(m_questions.begin());
177+
thread->promise()->resolve();
178+
askNextQuestion();
179+
}
180+
}
181+
182+
void SensingBlocks::enqueueAsk(const std::string &question, Thread *thread)
183+
{
184+
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L117-L119
185+
assert(thread);
186+
Target *target = thread->target();
187+
assert(target);
188+
bool visible = true;
189+
bool isStage = target->isStage();
190+
191+
if (!isStage) {
192+
Sprite *sprite = static_cast<Sprite *>(target);
193+
visible = sprite->visible();
194+
}
195+
196+
m_questions.push_back(std::make_unique<Question>(question, thread, visible, isStage));
197+
}
198+
199+
void SensingBlocks::askNextQuestion()
200+
{
201+
// https://github.com/scratchfoundation/scratch-vm/blob/6055823f203a696165084b873e661713806583ec/src/blocks/scratch3_sensing.js#L121-L133
202+
if (m_questions.empty())
203+
return;
204+
205+
Question *question = m_questions.front().get();
206+
Target *target = question->thread->target();
207+
IEngine *engine = question->thread->engine();
208+
209+
// If the target is visible, emit a blank question and show
210+
// a bubble unless the target was the stage
211+
if (question->wasVisible && !question->wasStage) {
212+
target->bubble()->setType(TextBubble::Type::Say);
213+
target->bubble()->setText(question->question);
214+
215+
engine->questionAsked()("");
216+
} else
217+
engine->questionAsked()(question->question);
218+
}
219+
110220
extern "C" bool sensing_touching_mouse(Target *target)
111221
{
112222
IEngine *engine = target->engine();
@@ -191,3 +301,16 @@ extern "C" double sensing_distanceto(Sprite *sprite, const StringPtr *object)
191301

192302
return 10000.0;
193303
}
304+
305+
extern "C" void sensing_askandwait(ExecutionContext *ctx, const StringPtr *question)
306+
{
307+
SensingBlocks::askQuestion(ctx, question);
308+
ctx->thread()->setPromise(std::make_shared<Promise>());
309+
}
310+
311+
extern "C" StringPtr *sensing_answer(ExecutionContext *ctx)
312+
{
313+
StringPtr *ret = string_pool_new();
314+
string_assign(ret, ctx->engine()->answer());
315+
return ret;
316+
}

src/blocks/sensingblocks.h

Lines changed: 33 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 <vector>
7+
#include <memory>
68

79
namespace libscratchcpp
810
{
911

12+
class ExecutionContext;
13+
class Thread;
14+
1015
class SensingBlocks : public IExtension
1116
{
1217
public:
@@ -15,12 +20,40 @@ class SensingBlocks : public IExtension
1520
Rgb color() const override;
1621

1722
void registerBlocks(IEngine *engine) override;
23+
void onInit(IEngine *engine) override;
24+
25+
static void clearQuestions();
26+
static void askQuestion(ExecutionContext *ctx, const StringPtr *question);
1827

1928
private:
29+
struct Question
30+
{
31+
Question(const std::string &question, Thread *thread, bool wasVisible, bool wasStage) :
32+
question(question),
33+
thread(thread),
34+
wasVisible(wasVisible),
35+
wasStage(wasStage)
36+
{
37+
}
38+
39+
std::string question;
40+
Thread *thread = nullptr;
41+
bool wasVisible = false;
42+
bool wasStage = false;
43+
};
44+
2045
static CompilerValue *compileTouchingObject(Compiler *compiler);
2146
static CompilerValue *compileTouchingColor(Compiler *compiler);
2247
static CompilerValue *compileColorIsTouchingColor(Compiler *compiler);
2348
static CompilerValue *compileDistanceTo(Compiler *compiler);
49+
static CompilerValue *compileAskAndWait(Compiler *compiler);
50+
static CompilerValue *compileAnswer(Compiler *compiler);
51+
52+
static void onAnswer(const std::string &answer);
53+
static void enqueueAsk(const std::string &question, Thread *thread);
54+
static void askNextQuestion();
55+
56+
static inline std::vector<std::unique_ptr<Question>> m_questions;
2457
};
2558

2659
} // namespace libscratchcpp

0 commit comments

Comments
 (0)