From 961b9580720dcb4565144553a08df4668f9347f6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:20:22 +0200 Subject: [PATCH 01/71] Set version to 0.10.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d9a8456..66a900c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.9.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.10.0 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) From cd85b4430cf5038e691be6962872d55a319e451e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 10 Apr 2024 07:56:47 +0200 Subject: [PATCH 02/71] fix #535: Do not delete audio decoder in cloned sounds --- src/audio/internal/audioplayer.cpp | 9 +++++++-- src/audio/internal/audioplayer.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/audio/internal/audioplayer.cpp b/src/audio/internal/audioplayer.cpp index 9cab04ba..e7cc03a5 100644 --- a/src/audio/internal/audioplayer.cpp +++ b/src/audio/internal/audioplayer.cpp @@ -18,11 +18,15 @@ AudioPlayer::~AudioPlayer() { if (m_loaded) { ma_sound_uninit(m_sound); - ma_decoder_uninit(m_decoder); + + if (!m_copy) + ma_decoder_uninit(m_decoder); } - delete m_decoder; delete m_sound; + + if (!m_copy) + delete m_decoder; } bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sampleRate) @@ -74,6 +78,7 @@ bool AudioPlayer::loadCopy(IAudioPlayer *player) } m_loaded = true; + m_copy = true; ma_sound_set_volume(m_sound, m_volume); return true; } diff --git a/src/audio/internal/audioplayer.h b/src/audio/internal/audioplayer.h index 854691ae..8dd77b54 100644 --- a/src/audio/internal/audioplayer.h +++ b/src/audio/internal/audioplayer.h @@ -33,6 +33,7 @@ class AudioPlayer : public IAudioPlayer ma_decoder *m_decoder = nullptr; ma_sound *m_sound; bool m_loaded = false; + bool m_copy = false; bool m_started = false; float m_volume = 1; }; From cede35175efef171c42d3e8666129b9ff1fe39e9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:36:48 +0200 Subject: [PATCH 03/71] fix #524: Convert strings to lowercase in string contains Closes #524 --- src/engine/virtualmachine_p.cpp | 10 +++++-- test/virtual_machine/virtual_machine_test.cpp | 29 ++++++++++--------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp index b9a99521..50397788 100644 --- a/src/engine/virtualmachine_p.cpp +++ b/src/engine/virtualmachine_p.cpp @@ -764,8 +764,14 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset) DISPATCH(); OP(STR_CONTAINS) : - REPLACE_RET_VALUE(READ_REG(0, 2)->toUtf16().find(READ_REG(1, 2)->toUtf16()) != std::u16string::npos, 2); - FREE_REGS(1); + { + std::u16string string1 = READ_REG(0, 2)->toUtf16(); + std::u16string string2 = READ_REG(1, 2)->toUtf16(); + std::transform(string1.begin(), string1.end(), string1.begin(), ::tolower); + std::transform(string2.begin(), string2.end(), string2.begin(), ::tolower); + REPLACE_RET_VALUE(string1.find(string2) != std::u16string::npos, 2); + FREE_REGS(1); + } DISPATCH(); OP(EXEC) : diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp index 30ea6dbb..3a91c12c 100644 --- a/test/virtual_machine/virtual_machine_test.cpp +++ b/test/virtual_machine/virtual_machine_test.cpp @@ -1359,25 +1359,28 @@ TEST(VirtualMachineTest, OP_STR_LENGTH) TEST(VirtualMachineTest, OP_STR_CONTAINS) { static unsigned int bytecode[] = { - OP_START, OP_CONST, 0, OP_CONST, 1, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 2, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 3, - OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 4, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 5, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 6, - OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 7, OP_STR_CONTAINS, OP_CONST, 4, OP_CONST, 4, OP_STR_CONTAINS, OP_HALT + OP_START, OP_CONST, 0, OP_CONST, 1, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 2, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 3, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 4, + OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 5, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 6, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 7, OP_STR_CONTAINS, OP_CONST, 4, OP_CONST, 4, + OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 8, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 9, OP_STR_CONTAINS, OP_CONST, 0, OP_CONST, 10, OP_STR_CONTAINS, OP_HALT }; - static Value constValues[] = { "abcd efg hijý abcĎĐ", "ĎĐ", "a", "test", "", " ", " ", "k" }; + static Value constValues[] = { "abcd efg hijý abcĎĐ", "ĎĐ", "a", "test", "", " ", " ", "k", "ab", "aB", "AB" }; VirtualMachine vm; vm.setBytecode(bytecode); vm.setConstValues(constValues); vm.run(); - ASSERT_EQ(vm.registerCount(), 8); - ASSERT_EQ(vm.getInput(0, 8)->toBool(), true); - ASSERT_EQ(vm.getInput(1, 8)->toBool(), true); - ASSERT_EQ(vm.getInput(2, 8)->toBool(), false); - ASSERT_EQ(vm.getInput(3, 8)->toBool(), true); - ASSERT_EQ(vm.getInput(4, 8)->toBool(), true); - ASSERT_EQ(vm.getInput(5, 8)->toBool(), true); - ASSERT_EQ(vm.getInput(6, 8)->toBool(), false); - ASSERT_EQ(vm.getInput(7, 8)->toBool(), true); + ASSERT_EQ(vm.registerCount(), 11); + ASSERT_EQ(vm.getInput(0, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(1, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(2, 11)->toBool(), false); + ASSERT_EQ(vm.getInput(3, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(4, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(5, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(6, 11)->toBool(), false); + ASSERT_EQ(vm.getInput(7, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(8, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(9, 11)->toBool(), true); + ASSERT_EQ(vm.getInput(10, 11)->toBool(), true); } unsigned int testFunction1(VirtualMachine *vm) From d41aad0491abb569c1e6fe8c859885b666bc6486 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:10:55 +0200 Subject: [PATCH 04/71] fix #434: Round mouse pointer position Closes #434 --- src/engine/internal/engine.cpp | 4 ++-- test/engine/engine_test.cpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index d2923948..220d5e22 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -778,7 +778,7 @@ double Engine::mouseX() const void Engine::setMouseX(double x) { - m_mouseX = x; + m_mouseX = std::round(x); } double Engine::mouseY() const @@ -788,7 +788,7 @@ double Engine::mouseY() const void Engine::setMouseY(double y) { - m_mouseY = y; + m_mouseY = std::round(y); } bool Engine::mousePressed() const diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 5b8b6f96..198678bc 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -827,7 +827,10 @@ TEST(EngineTest, MouseX) ASSERT_EQ(engine.mouseX(), 0); engine.setMouseX(-128.038); - ASSERT_EQ(engine.mouseX(), -128.038); + ASSERT_EQ(engine.mouseX(), -128); + + engine.setMouseX(35.7); + ASSERT_EQ(engine.mouseX(), 36); } TEST(EngineTest, MouseY) @@ -835,8 +838,11 @@ TEST(EngineTest, MouseY) Engine engine; ASSERT_EQ(engine.mouseY(), 0); - engine.setMouseY(179.9258); - ASSERT_EQ(engine.mouseY(), 179.9258); + engine.setMouseY(179.1258); + ASSERT_EQ(engine.mouseY(), 179); + + engine.setMouseY(-12.98); + ASSERT_EQ(engine.mouseY(), -13); } TEST(EngineTest, MousePressed) From 8836ab117d08181145d3f1387fd50dbf65c411aa Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 13 Apr 2024 16:17:36 +0200 Subject: [PATCH 05/71] Make sounds aware of target --- include/scratchcpp/sound.h | 4 ++++ src/scratch/sound.cpp | 12 ++++++++++++ src/scratch/sound_p.h | 3 +++ src/scratch/target.cpp | 4 +++- test/assets/sound_test.cpp | 11 +++++++++++ test/scratch_classes/target_test.cpp | 4 ++++ 6 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index 4185ade0..c07ee6a9 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -9,6 +9,7 @@ namespace libscratchcpp { +class Target; class SoundPrivate; /*! \brief The Sound class represents a Scratch sound. */ @@ -32,6 +33,9 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset virtual bool isPlaying(); + Target *target() const; + void setTarget(Target *target); + std::shared_ptr clone() const; protected: diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index 295626b2..f3a2bfa2 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -62,6 +62,18 @@ bool Sound::isPlaying() return impl->player->isPlaying(); } +/*! Returns the sprite or stage this variable belongs to. */ +Target *Sound::target() const +{ + return impl->target; +} + +/*! Sets the sprite or stage this variable belongs to. */ +void Sound::setTarget(Target *target) +{ + impl->target = target; +} + /*! Returns an independent copy of the sound which is valid for as long as the original sound exists. */ std::shared_ptr Sound::clone() const { diff --git a/src/scratch/sound_p.h b/src/scratch/sound_p.h index 082b05fd..68cbe980 100644 --- a/src/scratch/sound_p.h +++ b/src/scratch/sound_p.h @@ -10,6 +10,8 @@ namespace libscratchcpp { +class Target; + struct SoundPrivate { SoundPrivate(); @@ -19,6 +21,7 @@ struct SoundPrivate int sampleCount = 0; static IAudioOutput *audioOutput; std::shared_ptr player = nullptr; + Target *target = nullptr; }; } // namespace libscratchcpp diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index 14a6f337..651d373b 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -344,8 +344,10 @@ int Target::addSound(std::shared_ptr sound) assert(sound); - if (sound) + if (sound) { + sound->setTarget(this); sound->setVolume(impl->volume); + } impl->sounds.push_back(sound); return impl->sounds.size() - 1; diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index 3cd80665..e197e930 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -106,6 +107,16 @@ TEST_F(SoundTest, IsPlaying) SoundPrivate::audioOutput = nullptr; } +TEST_F(SoundTest, Target) +{ + Sound sound("sound1", "a", "wav"); + ASSERT_EQ(sound.target(), nullptr); + + Target target; + sound.setTarget(&target); + ASSERT_EQ(sound.target(), &target); +} + TEST_F(SoundTest, Clone) { Sound sound("sound1", "a", "wav"); diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp index 7c521da5..ca9dea89 100644 --- a/test/scratch_classes/target_test.cpp +++ b/test/scratch_classes/target_test.cpp @@ -442,6 +442,10 @@ TEST(TargetTest, Sounds) ASSERT_EQ(target.findSound("sound2"), 1); ASSERT_EQ(target.findSound("sound3"), 2); + ASSERT_EQ(s1->target(), &target); + ASSERT_EQ(s2->target(), &target); + ASSERT_EQ(s3->target(), &target); + SoundPrivate::audioOutput = nullptr; } From 4ec3f1faae24e5f6a3f21129c979be4869ee0125 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 13 Apr 2024 23:24:03 +0200 Subject: [PATCH 06/71] fix #538: Fix sound clones not stopping --- include/scratchcpp/sound.h | 2 + src/engine/internal/engine.cpp | 7 +--- src/scratch/sound.cpp | 32 ++++++++++++++++ test/assets/sound_test.cpp | 61 +++++++++++++++++++++++++------ test/blocks/sound_blocks_test.cpp | 8 ++-- test/engine/engine_test.cpp | 4 +- 6 files changed, 92 insertions(+), 22 deletions(-) diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index c07ee6a9..382ea007 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -42,6 +42,8 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset void processData(unsigned int size, void *data) override; private: + void stopCloneSounds(); + spimpl::unique_impl_ptr impl; }; diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 220d5e22..e8e92db4 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -459,14 +459,11 @@ void Engine::deinitClone(std::shared_ptr clone) void Engine::stopSounds() { - // TODO: Loop through clones for (auto target : m_targets) { const auto &sounds = target->sounds(); - for (auto sound : sounds) { - if (sound->isPlaying()) - sound->stop(); - } + for (auto sound : sounds) + sound->stop(); } } diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index f3a2bfa2..53772e90 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include "sound_p.h" @@ -47,12 +48,16 @@ void Sound::setVolume(double volume) /*! Starts the playback of the sound. */ void Sound::start() { + // Stop sounds in clones (#538) + stopCloneSounds(); impl->player->start(); } /*! Stops the playback of the sound. */ void Sound::stop() { + // Stop sounds in clones (#538) + stopCloneSounds(); impl->player->stop(); } @@ -95,3 +100,30 @@ void Sound::processData(unsigned int size, void *data) if (!impl->player->load(size, data, impl->rate)) std::cerr << "Failed to load sound " << name() << std::endl; } + +void Sound::stopCloneSounds() +{ + if (impl->target && !impl->target->isStage()) { + Sprite *sprite = static_cast(impl->target); + + if (sprite->isClone()) + sprite = sprite->cloneSprite(); + + // Stop the sound in the sprite (clone root) + auto sound = sprite->soundAt(sprite->findSound(name())); + + if (sound && sound.get() != this) + sound->impl->player->stop(); + + // Stop the sound in clones + const auto &clones = sprite->clones(); + + for (auto clone : clones) { + auto sound = clone->soundAt(clone->findSound(name())); + assert(sound); + + if (sound && sound.get() != this) + sound->impl->player->stop(); + } + } +} diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index e197e930..7f7e9d44 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -1,8 +1,9 @@ #include -#include +#include #include #include #include +#include #include "../common.h" @@ -119,16 +120,16 @@ TEST_F(SoundTest, Target) TEST_F(SoundTest, Clone) { - Sound sound("sound1", "a", "wav"); - sound.setRate(44100); - sound.setSampleCount(10000); + auto sound = std::make_shared("sound1", "a", "wav"); + sound->setRate(44100); + sound->setSampleCount(10000); const char *data = "abc"; void *dataPtr = const_cast(static_cast(data)); EXPECT_CALL(*m_player, isLoaded()).WillOnce(Return(false)); EXPECT_CALL(*m_player, load(3, dataPtr, 44100)).WillOnce(Return(true)); - sound.setData(3, dataPtr); + sound->setData(3, dataPtr); auto clonePlayer = std::make_shared(); EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(clonePlayer)); @@ -136,11 +137,49 @@ TEST_F(SoundTest, Clone) EXPECT_CALL(*m_player, volume()).WillOnce(Return(0.45)); EXPECT_CALL(*clonePlayer, setVolume(0.45)); EXPECT_CALL(*clonePlayer, isLoaded()).WillOnce(Return(true)); - auto clone = sound.clone(); + auto clone = sound->clone(); ASSERT_TRUE(clone); - ASSERT_EQ(clone->name(), sound.name()); - ASSERT_EQ(clone->id(), sound.id()); - ASSERT_EQ(clone->dataFormat(), sound.dataFormat()); - ASSERT_EQ(clone->rate(), sound.rate()); - ASSERT_EQ(clone->sampleCount(), sound.sampleCount()); + ASSERT_EQ(clone->name(), sound->name()); + ASSERT_EQ(clone->id(), sound->id()); + ASSERT_EQ(clone->dataFormat(), sound->dataFormat()); + ASSERT_EQ(clone->rate(), sound->rate()); + ASSERT_EQ(clone->sampleCount(), sound->sampleCount()); + + // Stopping/starting the sound should stop its clones + auto anotherPlayer = std::make_shared(); + EXPECT_CALL(m_playerFactory, createAudioPlayer()).WillOnce(Return(anotherPlayer)); + auto another = std::make_shared("another", "c", "mp3"); + Sprite sprite; + EngineMock engine; + sprite.setEngine(&engine); + + EXPECT_CALL(engine, cloneLimit()).WillOnce(Return(-1)); + EXPECT_CALL(engine, initClone); + EXPECT_CALL(engine, requestRedraw); + EXPECT_CALL(engine, moveSpriteBehindOther); + auto spriteClone = sprite.clone(); + + EXPECT_CALL(*anotherPlayer, setVolume).Times(2); + EXPECT_CALL(*m_player, setVolume); + EXPECT_CALL(*clonePlayer, setVolume); + sprite.addSound(another); + sprite.addSound(sound); + spriteClone->addSound(another); + spriteClone->addSound(clone); + + EXPECT_CALL(*m_player, stop()); + EXPECT_CALL(*clonePlayer, stop()); + sound->stop(); + + EXPECT_CALL(*m_player, stop()); + EXPECT_CALL(*clonePlayer, stop()); + clone->stop(); + + EXPECT_CALL(*m_player, start()); + EXPECT_CALL(*clonePlayer, stop()); + sound->start(); + + EXPECT_CALL(*m_player, stop()); + EXPECT_CALL(*clonePlayer, start()); + clone->start(); } diff --git a/test/blocks/sound_blocks_test.cpp b/test/blocks/sound_blocks_test.cpp index c5b2b366..77fef23b 100644 --- a/test/blocks/sound_blocks_test.cpp +++ b/test/blocks/sound_blocks_test.cpp @@ -2,12 +2,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include "../common.h" #include "blocks/soundblocks.h" @@ -212,7 +212,8 @@ TEST_F(SoundBlocksTest, PlayImpl) EXPECT_CALL(*player1, setVolume); EXPECT_CALL(*player2, setVolume); EXPECT_CALL(*player3, setVolume); - Target target; + TargetMock target; + EXPECT_CALL(target, isStage()).WillRepeatedly(Return(true)); target.addSound(std::make_shared("some sound", "", "")); target.addSound(std::make_shared("test", "", "")); target.addSound(std::make_shared("another sound", "", "")); @@ -412,7 +413,8 @@ TEST_F(SoundBlocksTest, PlayUntilDoneImpl) EXPECT_CALL(*player1, setVolume); EXPECT_CALL(*player2, setVolume); EXPECT_CALL(*player3, setVolume); - Target target; + TargetMock target; + EXPECT_CALL(target, isStage()).WillRepeatedly(Return(true)); target.addSound(std::make_shared("some sound", "", "")); target.addSound(std::make_shared("test", "", "")); target.addSound(std::make_shared("another sound", "", "")); diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 198678bc..0481f8cd 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -275,9 +275,7 @@ TEST(EngineTest, StopSounds) auto engine = p.engine(); - EXPECT_CALL(*player1, isPlaying()).WillOnce(Return(false)); - EXPECT_CALL(*player2, isPlaying()).WillOnce(Return(true)); - EXPECT_CALL(*player3, isPlaying()).WillOnce(Return(true)); + EXPECT_CALL(*player1, stop()); EXPECT_CALL(*player2, stop()); EXPECT_CALL(*player3, stop()); engine->stopSounds(); From 0ee4164676119f3c42c145c2c71249b731815225 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 14 Apr 2024 14:52:24 +0200 Subject: [PATCH 07/71] Add minimal build test workflow --- .github/workflows/utests-minimal.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/utests-minimal.yml diff --git a/.github/workflows/utests-minimal.yml b/.github/workflows/utests-minimal.yml new file mode 100644 index 00000000..00e02639 --- /dev/null +++ b/.github/workflows/utests-minimal.yml @@ -0,0 +1,28 @@ +name: Unit tests (minimal build) + +on: + push: + branches: '*' + pull_request: + branches: [ "master" ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBSCRATCHCPP_BUILD_UNIT_TESTS=ON -DLIBSCRATCHCPP_AUDIO_SUPPORT=OFF -DLIBSCRATCHCPP_NETWORK_SUPPORT=OFF -DLIBSCRATCHCPP_COMPUTED_GOTO=OFF + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all) + + - name: Run unit tests + run: ctest --test-dir build -V From 3200f4bb09b20912314510db35534e0b1f3f34af Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 14 Apr 2024 16:40:45 +0200 Subject: [PATCH 08/71] Link compiler test against nlohmann_json --- test/compiler/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/compiler/CMakeLists.txt b/test/compiler/CMakeLists.txt index 867f0265..9dd42696 100644 --- a/test/compiler/CMakeLists.txt +++ b/test/compiler/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries( compiler_test GTest::gtest_main scratchcpp + nlohmann_json::nlohmann_json ) gtest_discover_tests(compiler_test) From 809951690905be46cd7e8b5ce48762cf7ba9a863 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:16:46 +0200 Subject: [PATCH 09/71] Add project stopped signal --- include/scratchcpp/iengine.h | 3 ++ src/engine/internal/engine.cpp | 7 ++++ src/engine/internal/engine.h | 2 + test/engine/engine_test.cpp | 67 ++++++++++++++++++++++++++-------- test/mocks/enginemock.h | 1 + 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index 88e95521..105b9f27 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -116,6 +116,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Emits when a script is about to stop. */ virtual sigslot::signal &threadAboutToStop() = 0; + /*! Emits when the project is stopped by calling stop(). */ + virtual sigslot::signal<> &stopped() = 0; + /*! Returns true if the project is currently running. */ virtual bool isRunning() const = 0; diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index e8e92db4..a370a68e 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -383,6 +383,8 @@ void Engine::stop() m_threads.clear(); m_running = false; } + + m_stopped(); } VirtualMachine *Engine::startScript(std::shared_ptr topLevelBlock, Target *target) @@ -565,6 +567,11 @@ sigslot::signal &Engine::threadAboutToStop() return m_threadAboutToStop; } +sigslot::signal<> &Engine::stopped() +{ + return m_stopped; +} + std::vector> Engine::stepThreads() { // https://github.com/scratchfoundation/scratch-vm/blob/develop/src/engine/sequencer.js#L70-L173 diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 69b05dd6..76319ac0 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -51,6 +51,7 @@ class Engine : public IEngine sigslot::signal<> &aboutToRender() override; sigslot::signal &threadAboutToStop() override; + sigslot::signal<> &stopped() override; bool isRunning() const override; @@ -268,6 +269,7 @@ class Engine : public IEngine bool m_redrawRequested = false; sigslot::signal<> m_aboutToRedraw; sigslot::signal m_threadAboutToStop; + sigslot::signal<> m_stopped; bool m_stopEventLoop = false; std::mutex m_stopEventLoopMutex; diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp index 0481f8cd..0454da9d 100644 --- a/test/engine/engine_test.cpp +++ b/test/engine/engine_test.cpp @@ -47,10 +47,11 @@ class RedrawMock MOCK_METHOD(void, redraw, ()); }; -class ThreadAboutToStopMock +class StopMock { public: MOCK_METHOD(void, threadRemoved, (VirtualMachine *)); + MOCK_METHOD(void, stopped, ()); }; class AddRemoveMonitorMock @@ -124,13 +125,13 @@ TEST(EngineTest, ClearThreadAboutToStopSignal) engine->start(); engine->step(); - ThreadAboutToStopMock threadRemovedMock; - EXPECT_CALL(threadRemovedMock, threadRemoved(_)).Times(3).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { + StopMock stopMock; + EXPECT_CALL(stopMock, threadRemoved(_)).Times(3).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { ASSERT_TRUE(vm); ASSERT_FALSE(vm->atEnd()); }))); - engine->threadAboutToStop().connect(&ThreadAboutToStopMock::threadRemoved, &threadRemovedMock); + engine->threadAboutToStop().connect(&StopMock::threadRemoved, &stopMock); engine->clear(); } @@ -143,16 +144,50 @@ TEST(EngineTest, StopThreadAboutToStopSignal) engine->start(); engine->step(); - ThreadAboutToStopMock threadRemovedMock; - EXPECT_CALL(threadRemovedMock, threadRemoved(_)).Times(3).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { + StopMock stopMock; + EXPECT_CALL(stopMock, threadRemoved(_)).Times(3).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { ASSERT_TRUE(vm); ASSERT_FALSE(vm->atEnd()); }))); - engine->threadAboutToStop().connect(&ThreadAboutToStopMock::threadRemoved, &threadRemovedMock); + engine->threadAboutToStop().connect(&StopMock::threadRemoved, &stopMock); engine->stop(); } +TEST(EngineTest, StopSignal) +{ + StopMock stopMock; + + { + Project p("3_threads.sb3"); + ASSERT_TRUE(p.load()); + auto engine = p.engine(); + + engine->stopped().connect(&StopMock::stopped, &stopMock); + EXPECT_CALL(stopMock, stopped()); + engine->start(); + engine->step(); + + EXPECT_CALL(stopMock, stopped()); + engine->stop(); + } + + { + Project p("stop_all_bypass.sb3"); + ASSERT_TRUE(p.load()); + auto engine = p.engine(); + + engine->stopped().connect(&StopMock::stopped, &stopMock); + EXPECT_CALL(stopMock, stopped()); + engine->start(); + EXPECT_CALL(stopMock, stopped()); + engine->step(); + + EXPECT_CALL(stopMock, stopped()); + engine->stop(); + } +} + TEST(EngineTest, CompileAndExecuteMonitors) { Engine engine; @@ -1894,13 +1929,13 @@ TEST(EngineTest, BroadcastsProject) auto engine = p.engine(); - ThreadAboutToStopMock threadRemovedMock; - EXPECT_CALL(threadRemovedMock, threadRemoved(_)).Times(21).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { + StopMock stopMock; + EXPECT_CALL(stopMock, threadRemoved(_)).Times(21).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { ASSERT_TRUE(vm); ASSERT_FALSE(vm->atEnd()); }))); - engine->threadAboutToStop().connect(&ThreadAboutToStopMock::threadRemoved, &threadRemovedMock); + engine->threadAboutToStop().connect(&StopMock::threadRemoved, &stopMock); engine->setFps(1000); p.run(); @@ -1942,13 +1977,13 @@ TEST(EngineTest, StopAllBypass) auto engine = p.engine(); - ThreadAboutToStopMock threadRemovedMock; - EXPECT_CALL(threadRemovedMock, threadRemoved(_)).Times(2).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { + StopMock stopMock; + EXPECT_CALL(stopMock, threadRemoved(_)).Times(2).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { ASSERT_TRUE(vm); ASSERT_FALSE(vm->atEnd()); }))); - engine->threadAboutToStop().connect(&ThreadAboutToStopMock::threadRemoved, &threadRemovedMock); + engine->threadAboutToStop().connect(&StopMock::threadRemoved, &stopMock); p.run(); Stage *stage = engine->stage(); @@ -1970,13 +2005,13 @@ TEST(EngineTest, StopOtherScriptsInSprite) auto engine = p.engine(); - ThreadAboutToStopMock threadRemovedMock; - EXPECT_CALL(threadRemovedMock, threadRemoved(_)).Times(4).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { + StopMock stopMock; + EXPECT_CALL(stopMock, threadRemoved(_)).Times(4).WillRepeatedly(WithArgs<0>(Invoke([](VirtualMachine *vm) { ASSERT_TRUE(vm); ASSERT_FALSE(vm->atEnd()); }))); - engine->threadAboutToStop().connect(&ThreadAboutToStopMock::threadRemoved, &threadRemovedMock); + engine->threadAboutToStop().connect(&StopMock::threadRemoved, &stopMock); p.run(); Stage *stage = engine->stage(); diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h index 1a3d67ee..7992ef08 100644 --- a/test/mocks/enginemock.h +++ b/test/mocks/enginemock.h @@ -34,6 +34,7 @@ class EngineMock : public IEngine MOCK_METHOD(sigslot::signal<> &, aboutToRender, (), (override)); MOCK_METHOD(sigslot::signal &, threadAboutToStop, (), (override)); + MOCK_METHOD(sigslot::signal<> &, stopped, (), (override)); MOCK_METHOD(bool, isRunning, (), (const, override)); From bba47fcd52bc343ee8fa502d4b6625b95a470c80 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:29:34 +0200 Subject: [PATCH 10/71] Add missing clocale include to Value --- include/scratchcpp/value.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index fa53716a..ebde5d1c 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "global.h" From 9f8f75c7431a3856e55121e9eb691a32ea4ead84 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:36:08 +0200 Subject: [PATCH 11/71] Add IAudioEngine interface with volume methods --- src/audio/CMakeLists.txt | 3 +++ src/audio/iaudioengine.h | 21 +++++++++++++++++ src/audio/internal/audioengine.cpp | 30 +++++++++++++++++++++---- src/audio/internal/audioengine.h | 16 ++++++++++--- src/audio/internal/audioenginestub.cpp | 26 +++++++++++++++++++++ src/audio/internal/audioenginestub.h | 24 ++++++++++++++++++++ src/audio/internal/audioplayer.cpp | 1 + test/audio/CMakeLists.txt | 18 +++++++++++++++ test/audio/audioengine_test.cpp | 31 ++++++++++++++++++++++++++ test/mocks/audioenginemock.h | 13 +++++++++++ 10 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 src/audio/iaudioengine.h create mode 100644 src/audio/internal/audioenginestub.cpp create mode 100644 src/audio/internal/audioenginestub.h create mode 100644 test/audio/audioengine_test.cpp create mode 100644 test/mocks/audioenginemock.h diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 5bf4e0b3..536c266b 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -15,6 +15,7 @@ endif() target_sources(scratchcpp-audio PUBLIC + iaudioengine.h iaudioplayer.h iaudiooutput.h audiooutput.cpp @@ -38,6 +39,8 @@ if (LIBSCRATCHCPP_AUDIO_SUPPORT) else() target_sources(scratchcpp-audio PUBLIC + internal/audioenginestub.cpp + internal/audioenginestub.h internal/audioplayerstub.cpp internal/audioplayerstub.h internal/audioloudnessstub.cpp diff --git a/src/audio/iaudioengine.h b/src/audio/iaudioengine.h new file mode 100644 index 00000000..8100334b --- /dev/null +++ b/src/audio/iaudioengine.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class IAudioEngine +{ + public: + virtual ~IAudioEngine() { } + + static IAudioEngine *instance(); + + virtual float volume() const = 0; + virtual void setVolume(float volume) = 0; +}; + +} // namespace libscratchcpp diff --git a/src/audio/internal/audioengine.cpp b/src/audio/internal/audioengine.cpp index 710a394e..1af3874d 100644 --- a/src/audio/internal/audioengine.cpp +++ b/src/audio/internal/audioengine.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include "audioengine.h" @@ -8,12 +9,17 @@ using namespace libscratchcpp; AudioEngine AudioEngine::instance; +IAudioEngine *IAudioEngine::instance() +{ + return &AudioEngine::instance; +} + ma_engine *AudioEngine::engine() { if (!instance.m_initialized) instance.init(); - return instance.m_initialized ? &instance.m_engine : nullptr; + return instance.m_initialized ? instance.m_engine : nullptr; } bool AudioEngine::initialized() @@ -24,20 +30,36 @@ bool AudioEngine::initialized() return instance.m_initialized; } +float AudioEngine::volume() const +{ + return m_volume; +} + +void AudioEngine::setVolume(float volume) +{ + m_volume = volume; + + if (m_initialized) + ma_engine_set_volume(m_engine, volume); +} + AudioEngine::AudioEngine() { } AudioEngine::~AudioEngine() { - if (m_initialized) - ma_engine_uninit(&m_engine); + if (m_initialized && m_engine) { + ma_engine_uninit(m_engine); + delete m_engine; + } } void AudioEngine::init() { ma_result result; - result = ma_engine_init(NULL, &m_engine); + m_engine = new ma_engine; + result = ma_engine_init(NULL, m_engine); if (result != MA_SUCCESS) { std::cerr << "Failed to initialize audio engine." << std::endl; diff --git a/src/audio/internal/audioengine.h b/src/audio/internal/audioengine.h index 49097871..02672ff3 100644 --- a/src/audio/internal/audioengine.h +++ b/src/audio/internal/audioengine.h @@ -3,18 +3,26 @@ #pragma once #include -#include + +#include "../iaudioengine.h" + +struct ma_engine; namespace libscratchcpp { // This is a singleton which initializes and uninitializes the miniaudio engine -class AudioEngine +class AudioEngine : public IAudioEngine { public: + friend class IAudioEngine; + static ma_engine *engine(); static bool initialized(); + float volume() const override; + void setVolume(float volume) override; + private: AudioEngine(); ~AudioEngine(); @@ -22,8 +30,10 @@ class AudioEngine void init(); static AudioEngine instance; - ma_engine m_engine; + + ma_engine *m_engine = nullptr; bool m_initialized = false; + float m_volume = 1.0f; }; } // namespace libscratchcpp diff --git a/src/audio/internal/audioenginestub.cpp b/src/audio/internal/audioenginestub.cpp new file mode 100644 index 00000000..83b8bd45 --- /dev/null +++ b/src/audio/internal/audioenginestub.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "audioenginestub.h" + +using namespace libscratchcpp; + +AudioEngineStub AudioEngineStub::instance; + +IAudioEngine *IAudioEngine::instance() +{ + return &AudioEngineStub::instance; +} + +AudioEngineStub::AudioEngineStub() +{ +} + +float AudioEngineStub::volume() const +{ + return m_volume; +} + +void AudioEngineStub::setVolume(float volume) +{ + m_volume = volume; +} diff --git a/src/audio/internal/audioenginestub.h b/src/audio/internal/audioenginestub.h new file mode 100644 index 00000000..e49e076c --- /dev/null +++ b/src/audio/internal/audioenginestub.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../iaudioengine.h" + +namespace libscratchcpp +{ + +class AudioEngineStub : public IAudioEngine +{ + public: + friend class IAudioEngine; + AudioEngineStub(); + + float volume() const override; + void setVolume(float volume) override; + + private: + static AudioEngineStub instance; + float m_volume = 1.0f; +}; + +} // namespace libscratchcpp diff --git a/src/audio/internal/audioplayer.cpp b/src/audio/internal/audioplayer.cpp index e7cc03a5..4bb4922d 100644 --- a/src/audio/internal/audioplayer.cpp +++ b/src/audio/internal/audioplayer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "audioplayer.h" #include "audioengine.h" diff --git a/test/audio/CMakeLists.txt b/test/audio/CMakeLists.txt index c4aba7b4..40897a6e 100644 --- a/test/audio/CMakeLists.txt +++ b/test/audio/CMakeLists.txt @@ -1,3 +1,21 @@ +# audioengine_test +add_executable( + audioengine_test + audioengine_test.cpp +) + +target_link_libraries( + audioengine_test + GTest::gtest_main + scratchcpp +) + +gtest_discover_tests(audioengine_test) + +if (LIBSCRATCHCPP_AUDIO_SUPPORT) + target_compile_definitions(audioengine_test PRIVATE LIBSCRATCHCPP_AUDIO_SUPPORT) +endif() + # audiooutput_test add_executable( audiooutput_test diff --git a/test/audio/audioengine_test.cpp b/test/audio/audioengine_test.cpp new file mode 100644 index 00000000..e0476858 --- /dev/null +++ b/test/audio/audioengine_test.cpp @@ -0,0 +1,31 @@ +#ifdef LIBSCRATCHCPP_AUDIO_SUPPORT +#include