|
3 | 3 | #include <scratchcpp/iengine.h> |
4 | 4 | #include <scratchcpp/compiler.h> |
5 | 5 | #include <scratchcpp/compilerconstant.h> |
| 6 | +#include <scratchcpp/thread.h> |
| 7 | +#include <scratchcpp/promise.h> |
| 8 | +#include <scratchcpp/executioncontext.h> |
6 | 9 | #include <scratchcpp/value.h> |
7 | 10 | #include <scratchcpp/input.h> |
8 | 11 | #include <scratchcpp/sprite.h> |
| 12 | +#include <scratchcpp/textbubble.h> |
9 | 13 | #include <scratchcpp/stringptr.h> |
10 | 14 | #include <scratchcpp/string_functions.h> |
| 15 | +#include <scratchcpp/string_pool.h> |
11 | 16 | #include <utf8.h> |
12 | 17 |
|
13 | 18 | #include "sensingblocks.h" |
@@ -35,6 +40,41 @@ void SensingBlocks::registerBlocks(IEngine *engine) |
35 | 40 | engine->addCompileFunction(this, "sensing_touchingcolor", &compileTouchingColor); |
36 | 41 | engine->addCompileFunction(this, "sensing_coloristouchingcolor", &compileColorIsTouchingColor); |
37 | 42 | 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(); |
38 | 78 | } |
39 | 79 |
|
40 | 80 | CompilerValue *SensingBlocks::compileTouchingObject(Compiler *compiler) |
@@ -107,6 +147,76 @@ CompilerValue *SensingBlocks::compileDistanceTo(Compiler *compiler) |
107 | 147 | return compiler->addConstValue(10000.0); |
108 | 148 | } |
109 | 149 |
|
| 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 | + |
110 | 220 | extern "C" bool sensing_touching_mouse(Target *target) |
111 | 221 | { |
112 | 222 | IEngine *engine = target->engine(); |
@@ -191,3 +301,16 @@ extern "C" double sensing_distanceto(Sprite *sprite, const StringPtr *object) |
191 | 301 |
|
192 | 302 | return 10000.0; |
193 | 303 | } |
| 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 | +} |
0 commit comments