Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d932257
Implement control_forever
adazem009 Dec 11, 2024
cf1730c
LLVMCodeBuilder: Fix infinite count in repeat loop
adazem009 Dec 11, 2024
30ae487
Implement control_repeat
adazem009 Dec 11, 2024
94de97e
Implement control_if
adazem009 Dec 11, 2024
6784aaf
Add obscured input tests to control blocks test
adazem009 Dec 11, 2024
d2200b9
Implement control_if_else
adazem009 Dec 11, 2024
59daa77
LLVMCodeBuilder: Add createStop() method
adazem009 Dec 11, 2024
5b37719
Compiler: Add createStop() method
adazem009 Dec 11, 2024
77f687e
Implement control_stop
adazem009 Dec 11, 2024
21df2fe
Add StackTimer
adazem009 Dec 12, 2024
5744b3d
ExecutionContext: Add stack timer
adazem009 Dec 12, 2024
f6ee558
Compiler: Add createYield() method
adazem009 Dec 12, 2024
5a67268
Implement control_wait
adazem009 Dec 12, 2024
880e30e
Request redraw in wait block
adazem009 Dec 14, 2024
90b566c
Implement control_wait_until
adazem009 Dec 14, 2024
834b91c
Implement control_repeat_until
adazem009 Dec 14, 2024
f62c2e0
Implement control_while
adazem009 Dec 14, 2024
b389f56
LLVMCodeBuilder: Use unsigned integer in repeat loop
adazem009 Dec 14, 2024
f274cf6
LLVMCodeBuilder: Add addLoopIndex() method
adazem009 Dec 14, 2024
cc2aca9
Compiler: Add addLoopIndex() method
adazem009 Dec 14, 2024
53e30e2
Compiler: Allow custom code in empty substacks
adazem009 Dec 14, 2024
9e8440d
ScriptBuilder: Add support for substack sequences
adazem009 Dec 14, 2024
f98e1d1
Implement control_foreach
adazem009 Dec 14, 2024
6482b86
Implement control_start_as_clone
adazem009 Dec 16, 2024
1cd4016
Implement control_create_clone_of
adazem009 Dec 16, 2024
2388d9f
Implement control_delete_this_clone
adazem009 Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ target_sources(scratchcpp
include/scratchcpp/sprite.h
include/scratchcpp/textbubble.h
include/scratchcpp/itimer.h
include/scratchcpp/istacktimer.h
include/scratchcpp/keyevent.h
include/scratchcpp/rect.h
include/scratchcpp/igraphicseffect.h
Expand Down
4 changes: 4 additions & 0 deletions include/scratchcpp/dev/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
CompilerValue *addTargetFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
CompilerValue *addFunctionCallWithCtx(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
CompilerConstant *addConstValue(const Value &value);
CompilerValue *addLoopIndex();
CompilerValue *addVariableValue(Variable *variable);
CompilerValue *addListContents(List *list);
CompilerValue *addListItem(List *list, CompilerValue *index);
Expand Down Expand Up @@ -115,6 +116,9 @@ class LIBSCRATCHCPP_EXPORT Compiler
void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
void warp();

void createYield();
void createStop();

Input *input(const std::string &name) const;
Field *field(const std::string &name) const;

Expand Down
4 changes: 4 additions & 0 deletions include/scratchcpp/dev/executioncontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace libscratchcpp
class Thread;
class IEngine;
class Promise;
class IStackTimer;
class ExecutionContextPrivate;

/*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */
Expand All @@ -27,6 +28,9 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext
std::shared_ptr<Promise> promise() const;
void setPromise(std::shared_ptr<Promise> promise);

IStackTimer *stackTimer() const;
void setStackTimer(IStackTimer *newStackTimer);

private:
spimpl::unique_impl_ptr<ExecutionContextPrivate> impl;
};
Expand Down
33 changes: 33 additions & 0 deletions include/scratchcpp/istacktimer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "global.h"

namespace libscratchcpp
{

/*!
* \brief The IStackTimer interface represents a timer that can be used by blocks.
*
* You can get a stack timer using ExecutionContext#stackTimer().
*/
class LIBSCRATCHCPP_EXPORT IStackTimer
{
public:
virtual ~IStackTimer() { }

/*! Starts the timer. */
virtual void start(double seconds) = 0;

/*! Stops the timer. */
virtual void stop() = 0;

/*! Returns true if the timer has been stopped using stop() or wasn't used at all. */
virtual bool stopped() const = 0;

/*! Returns true if the timer has elapsed. */
virtual bool elapsed() const = 0;
};

} // namespace libscratchcpp
214 changes: 214 additions & 0 deletions src/dev/blocks/controlblocks.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// SPDX-License-Identifier: Apache-2.0

#include <scratchcpp/iengine.h>
#include <scratchcpp/dev/compiler.h>
#include <scratchcpp/dev/compilerconstant.h>
#include <scratchcpp/value.h>
#include <scratchcpp/input.h>
#include <scratchcpp/field.h>
#include <scratchcpp/dev/executioncontext.h>
#include <scratchcpp/thread.h>
#include <scratchcpp/istacktimer.h>
#include <scratchcpp/variable.h>
#include <scratchcpp/sprite.h>

#include "controlblocks.h"

using namespace libscratchcpp;
Expand All @@ -16,4 +28,206 @@ std::string ControlBlocks::description() const

void ControlBlocks::registerBlocks(IEngine *engine)
{
engine->addCompileFunction(this, "control_forever", &compileForever);
engine->addCompileFunction(this, "control_repeat", &compileRepeat);
engine->addCompileFunction(this, "control_if", &compileIf);
engine->addCompileFunction(this, "control_if_else", &compileIfElse);
engine->addCompileFunction(this, "control_stop", &compileStop);
engine->addCompileFunction(this, "control_wait", &compileWait);
engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil);
engine->addCompileFunction(this, "control_while", &compileWhile);
engine->addCompileFunction(this, "control_for_each", &compileForEach);
engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
engine->addCompileFunction(this, "control_create_clone_of", &compileCreateCloneOf);
engine->addCompileFunction(this, "control_delete_this_clone", &compileDeleteThisClone);
}

CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
compiler->beginLoopCondition();
compiler->moveToWhileLoop(compiler->addConstValue(true), substack ? substack->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileRepeat(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
compiler->moveToRepeatLoop(compiler->addInput("TIMES"), substack ? substack->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileIf(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
compiler->moveToIf(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileIfElse(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
auto substack2 = compiler->input("SUBSTACK2");
compiler->moveToIfElse(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr, substack2 ? substack2->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileStop(Compiler *compiler)
{
Field *option = compiler->field("STOP_OPTION");

if (option) {
std::string str = option->value().toString();

if (str == "all")
compiler->addFunctionCallWithCtx("control_stop_all", Compiler::StaticType::Void);
else if (str == "this script")
compiler->createStop();
else if (str == "other scripts in sprite" || str == "other scripts in stage")
compiler->addFunctionCallWithCtx("control_stop_other_scripts_in_target", Compiler::StaticType::Void);
}

return nullptr;
}

CompilerValue *ControlBlocks::compileWait(Compiler *compiler)
{
auto duration = compiler->addInput("DURATION");
compiler->addFunctionCallWithCtx("control_start_wait", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration });
compiler->createYield();

compiler->beginLoopCondition();
auto elapsed = compiler->addFunctionCallWithCtx("control_stack_timer_elapsed", Compiler::StaticType::Bool);
compiler->beginRepeatUntilLoop(elapsed);
compiler->endLoop();

return nullptr;
}

CompilerValue *ControlBlocks::compileWaitUntil(Compiler *compiler)
{
compiler->beginLoopCondition();
compiler->beginRepeatUntilLoop(compiler->addInput("CONDITION"));
compiler->endLoop();
return nullptr;
}

CompilerValue *ControlBlocks::compileRepeatUntil(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
compiler->beginLoopCondition();
compiler->moveToRepeatUntilLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileWhile(Compiler *compiler)
{
auto substack = compiler->input("SUBSTACK");
compiler->beginLoopCondition();
compiler->moveToWhileLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
return nullptr;
}

CompilerValue *ControlBlocks::compileForEach(Compiler *compiler)
{
Variable *var = static_cast<Variable *>(compiler->field("VARIABLE")->valuePtr().get());
assert(var);
auto substack = compiler->input("SUBSTACK");
compiler->moveToRepeatLoop(compiler->addInput("VALUE"), substack ? substack->valueBlock() : nullptr);
auto index = compiler->createAdd(compiler->addLoopIndex(), compiler->addConstValue(1));
compiler->createVariableWrite(var, index);
return nullptr;
}

CompilerValue *ControlBlocks::compileStartAsClone(Compiler *compiler)
{
compiler->engine()->addCloneInitScript(compiler->block());
return nullptr;
}

CompilerValue *ControlBlocks::compileCreateCloneOf(Compiler *compiler)
{
Input *input = compiler->input("CLONE_OPTION");

if (input->pointsToDropdownMenu()) {
std::string spriteName = input->selectedMenuItem();

if (spriteName == "_myself_")
compiler->addTargetFunctionCall("control_create_clone_of_myself");
else {
auto index = compiler->engine()->findTarget(spriteName);
CompilerValue *arg = compiler->addConstValue(index);
compiler->addFunctionCallWithCtx("control_create_clone_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { arg });
}
} else {
CompilerValue *arg = compiler->addInput("CLONE_OPTION");
compiler->addFunctionCallWithCtx("control_create_clone", Compiler::StaticType::Void, { Compiler::StaticType::String }, { arg });
}

return nullptr;
}

CompilerValue *ControlBlocks::compileDeleteThisClone(Compiler *compiler)
{
compiler->addTargetFunctionCall("control_delete_this_clone");
return nullptr;
}

extern "C" void control_stop_all(ExecutionContext *ctx)
{
ctx->engine()->stop();
}

extern "C" void control_stop_other_scripts_in_target(ExecutionContext *ctx)
{
Thread *thread = ctx->thread();
ctx->engine()->stopTarget(thread->target(), thread);
}

extern "C" void control_start_wait(ExecutionContext *ctx, double seconds)
{
ctx->stackTimer()->start(seconds);
ctx->engine()->requestRedraw();
}

extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx)
{
return ctx->stackTimer()->elapsed();
}

extern "C" void control_create_clone_of_myself(Target *target)
{
if (!target->isStage())
static_cast<Sprite *>(target)->clone();
}

extern "C" void control_create_clone_by_index(ExecutionContext *ctx, double index)
{
Target *target = ctx->engine()->targetAt(index);

if (!target->isStage())
static_cast<Sprite *>(target)->clone();
}

extern "C" void control_create_clone(ExecutionContext *ctx, const char *spriteName)
{
if (strcmp(spriteName, "_myself_") == 0)
control_create_clone_of_myself(ctx->thread()->target());
else {
IEngine *engine = ctx->engine();
auto index = engine->findTarget(spriteName);
Target *target = engine->targetAt(index);

if (!target->isStage())
static_cast<Sprite *>(target)->clone();
}
}

extern "C" void control_delete_this_clone(Target *target)
{
if (!target->isStage()) {
target->engine()->stopTarget(target, nullptr);
static_cast<Sprite *>(target)->deleteClone();
}
}
15 changes: 15 additions & 0 deletions src/dev/blocks/controlblocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ class ControlBlocks : public IExtension
std::string description() const override;

void registerBlocks(IEngine *engine) override;

private:
static CompilerValue *compileForever(Compiler *compiler);
static CompilerValue *compileRepeat(Compiler *compiler);
static CompilerValue *compileIf(Compiler *compiler);
static CompilerValue *compileIfElse(Compiler *compiler);
static CompilerValue *compileStop(Compiler *compiler);
static CompilerValue *compileWait(Compiler *compiler);
static CompilerValue *compileWaitUntil(Compiler *compiler);
static CompilerValue *compileRepeatUntil(Compiler *compiler);
static CompilerValue *compileWhile(Compiler *compiler);
static CompilerValue *compileForEach(Compiler *compiler);
static CompilerValue *compileStartAsClone(Compiler *compiler);
static CompilerValue *compileCreateCloneOf(Compiler *compiler);
static CompilerValue *compileDeleteThisClone(Compiler *compiler);
};

} // namespace libscratchcpp
Loading
Loading