diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h index 9a604702..cc7db35a 100644 --- a/include/scratchcpp/compiler.h +++ b/include/scratchcpp/compiler.h @@ -49,7 +49,7 @@ class LIBSCRATCHCPP_EXPORT Compiler Target *target() const; std::shared_ptr block() const; - std::shared_ptr compile(std::shared_ptr startBlock); + std::shared_ptr compile(std::shared_ptr startBlock, bool isHatPredicate = false); void preoptimize(); CompilerValue *addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {}); diff --git a/include/scratchcpp/executablecode.h b/include/scratchcpp/executablecode.h index 094d0181..c55a4983 100644 --- a/include/scratchcpp/executablecode.h +++ b/include/scratchcpp/executablecode.h @@ -21,6 +21,9 @@ class LIBSCRATCHCPP_EXPORT ExecutableCode /*! Runs the script until it finishes or yields. */ virtual void run(ExecutionContext *context) = 0; + /*! Runs the hat predicate and returns its return value. */ + virtual bool runPredicate(ExecutionContext *context) = 0; + /*! Stops the code. isFinished() will return true. */ virtual void kill(ExecutionContext *context) = 0; diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h index 3032cd4c..13d3b825 100644 --- a/include/scratchcpp/script.h +++ b/include/scratchcpp/script.h @@ -31,6 +31,9 @@ class LIBSCRATCHCPP_EXPORT Script ExecutableCode *code() const; void setCode(std::shared_ptr code); + ExecutableCode *hatPredicateCode() const; + void setHatPredicateCode(std::shared_ptr code); + bool runHatPredicate(Target *target); std::shared_ptr start(); diff --git a/include/scratchcpp/thread.h b/include/scratchcpp/thread.h index bc0578d7..994de694 100644 --- a/include/scratchcpp/thread.h +++ b/include/scratchcpp/thread.h @@ -27,6 +27,7 @@ class LIBSCRATCHCPP_EXPORT Thread Script *script() const; void run(); + bool runPredicate(); void kill(); void reset(); diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index f9777dac..7179d59b 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,9 +11,13 @@ #include #include #include +#include +#include #include #include "eventblocks.h" +#include "audio/audioinput.h" +#include "audio/iaudioloudness.h" using namespace libscratchcpp; @@ -33,6 +38,7 @@ Rgb EventBlocks::color() const void EventBlocks::registerBlocks(IEngine *engine) { + // Blocks engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject); engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked); engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked); @@ -43,6 +49,10 @@ void EventBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "event_broadcast", &compileBroadcast); engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait); engine->addCompileFunction(this, "event_whenkeypressed", &compileWhenKeyPressed); + + // Hat predicates + engine->addHatPredicateCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObjectPredicate); + engine->addHatPredicateCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThanPredicate); } CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler) @@ -51,6 +61,21 @@ CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler) return nullptr; } +CompilerValue *EventBlocks::compileWhenTouchingObjectPredicate(Compiler *compiler) +{ + Input *input = compiler->input("TOUCHINGOBJECTMENU"); + CompilerValue *name = nullptr; + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + name = compiler->addConstValue(value); + } else + name = compiler->addInput(input); + + return compiler->addTargetFunctionCall("event_whentouchingobject_predicate", Compiler::StaticType::Bool, { Compiler::StaticType::String }, { name }); +} + CompilerValue *EventBlocks::compileWhenFlagClicked(Compiler *compiler) { compiler->engine()->addGreenFlagScript(compiler->block()); @@ -99,6 +124,27 @@ CompilerValue *EventBlocks::compileWhenGreaterThan(Compiler *compiler) return nullptr; } +CompilerValue *EventBlocks::compileWhenGreaterThanPredicate(Compiler *compiler) +{ + Field *field = compiler->field("WHENGREATERTHANMENU"); + std::string predicate; + + if (field) { + const std::string option = field->value().toString(); + + if (option == "LOUDNESS") + predicate = "event_whengreaterthan_loudness_predicate"; + else if (option == "TIMER") + predicate = "event_whengreaterthan_timer_predicate"; + else + return compiler->addConstValue(false); + } else + return compiler->addConstValue(false); + + CompilerValue *value = compiler->addInput("VALUE"); + return compiler->addFunctionCallWithCtx(predicate, Compiler::StaticType::Bool, { Compiler::StaticType::Number }, { value }); +} + CompilerValue *EventBlocks::compileBroadcast(Compiler *compiler) { auto input = compiler->addInput("BROADCAST_INPUT"); @@ -127,6 +173,43 @@ CompilerValue *EventBlocks::compileWhenKeyPressed(Compiler *compiler) return nullptr; } +extern "C" bool event_whentouchingobject_predicate(Target *target, const StringPtr *name) +{ + static const StringPtr MOUSE_STR = StringPtr("_mouse_"); + static const StringPtr EDGE_STR = StringPtr("_edge_"); + + IEngine *engine = target->engine(); + + if (string_compare_case_sensitive(name, &MOUSE_STR) == 0) + return target->touchingPoint(engine->mouseX(), engine->mouseY()); + else if (string_compare_case_sensitive(name, &EDGE_STR) == 0) + return target->touchingEdge(); + else { + // TODO: Use UTF-16 in engine + const std::string u8name = utf8::utf16to8(std::u16string(name->data)); + Target *anotherTarget = engine->targetAt(engine->findTarget(u8name)); + + if (anotherTarget && !anotherTarget->isStage()) + return target->touchingSprite(static_cast(anotherTarget)); + else + return false; + } +} + +extern "C" bool event_whengreaterthan_loudness_predicate(ExecutionContext *ctx, double value) +{ + if (!EventBlocks::audioInput) + EventBlocks::audioInput = AudioInput::instance().get(); + + auto audioLoudness = EventBlocks::audioInput->audioLoudness(); + return (audioLoudness->getLoudness() > value); +} + +extern "C" bool event_whengreaterthan_timer_predicate(ExecutionContext *ctx, double value) +{ + return ctx->engine()->timer()->value() > value; +} + extern "C" void event_broadcast(ExecutionContext *ctx, const StringPtr *name, bool wait) { Thread *thread = ctx->thread(); diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index 512bae0c..360eff7d 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -7,6 +7,8 @@ namespace libscratchcpp { +class IAudioInput; + class EventBlocks : public IExtension { public: @@ -16,14 +18,18 @@ class EventBlocks : public IExtension void registerBlocks(IEngine *engine) override; + static inline IAudioInput *audioInput = nullptr; + private: static CompilerValue *compileWhenTouchingObject(Compiler *compiler); + static CompilerValue *compileWhenTouchingObjectPredicate(Compiler *compiler); static CompilerValue *compileWhenFlagClicked(Compiler *compiler); static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler); static CompilerValue *compileWhenStageClicked(Compiler *compiler); static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler); static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler); static CompilerValue *compileWhenGreaterThan(Compiler *compiler); + static CompilerValue *compileWhenGreaterThanPredicate(Compiler *compiler); static CompilerValue *compileBroadcast(Compiler *compiler); static CompilerValue *compileBroadcastAndWait(Compiler *compiler); static CompilerValue *compileWhenKeyPressed(Compiler *compiler); diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp index dda27e2f..17ea93ce 100644 --- a/src/engine/compiler.cpp +++ b/src/engine/compiler.cpp @@ -44,7 +44,7 @@ std::shared_ptr Compiler::block() const } /*! Compiles the script starting with the given block. */ -std::shared_ptr Compiler::compile(std::shared_ptr startBlock) +std::shared_ptr Compiler::compile(std::shared_ptr startBlock, bool isHatPredicate) { BlockPrototype *procedurePrototype = nullptr; @@ -60,13 +60,32 @@ std::shared_ptr Compiler::compile(std::shared_ptr startBl } } - impl->builder = impl->builderFactory->create(impl->ctx, procedurePrototype); + impl->builder = impl->builderFactory->create(impl->ctx, procedurePrototype, isHatPredicate); impl->substackTree.clear(); impl->substackHit = false; impl->emptySubstack = false; impl->warp = false; impl->block = startBlock; + if (impl->block && isHatPredicate) { + auto f = impl->block->hatPredicateCompileFunction(); + + if (f) { + CompilerValue *ret = f(this); + assert(ret); + + if (!ret) + std::cout << "warning: '" << impl->block->opcode() << "' hat predicate compile function doesn't return a valid value" << std::endl; + } else { + std::cout << "warning: unsupported hat predicate: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + addConstValue(false); // return false if unsupported + } + + impl->block = nullptr; + return impl->builder->finalize(); + } + while (impl->block) { if (impl->block->compileFunction()) { assert(impl->customIfStatementCount == 0); diff --git a/src/engine/internal/codebuilderfactory.cpp b/src/engine/internal/codebuilderfactory.cpp index bb3cb1e9..42ea1122 100644 --- a/src/engine/internal/codebuilderfactory.cpp +++ b/src/engine/internal/codebuilderfactory.cpp @@ -13,10 +13,10 @@ std::shared_ptr CodeBuilderFactory::instance() return m_instance; } -std::shared_ptr CodeBuilderFactory::create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const +std::shared_ptr CodeBuilderFactory::create(CompilerContext *ctx, BlockPrototype *procedurePrototype, bool isPredicate) const { assert(dynamic_cast(ctx)); - return std::make_shared(static_cast(ctx), procedurePrototype); + return std::make_shared(static_cast(ctx), procedurePrototype, isPredicate); } std::shared_ptr CodeBuilderFactory::createCtx(IEngine *engine, Target *target) const diff --git a/src/engine/internal/codebuilderfactory.h b/src/engine/internal/codebuilderfactory.h index 942a9a1e..c0c29568 100644 --- a/src/engine/internal/codebuilderfactory.h +++ b/src/engine/internal/codebuilderfactory.h @@ -11,7 +11,7 @@ class CodeBuilderFactory : public ICodeBuilderFactory { public: static std::shared_ptr instance(); - std::shared_ptr create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const override; + std::shared_ptr create(CompilerContext *ctx, BlockPrototype *procedurePrototype, bool isPredicate) const override; std::shared_ptr createCtx(IEngine *engine, Target *target) const override; private: diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 24a1a36c..cf0e831e 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -274,6 +274,9 @@ void Engine::compile() auto script = std::make_shared