Skip to content

Implement timer #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -46,6 +46,7 @@ target_sources(scratchcpp
include/scratchcpp/target.h
include/scratchcpp/stage.h
include/scratchcpp/sprite.h
include/scratchcpp/itimer.h
)

add_library(zip SHARED
Expand Down
4 changes: 4 additions & 0 deletions include/scratchcpp/iengine.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Sprite;
class Variable;
class List;
class Script;
class ITimer;

/*!
* \brief The IEngine interface provides an API for running Scratch projects.
Expand Down Expand Up @@ -106,6 +107,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
/*! Call this from a block implementation to ignore calls to skipFrame() until the current frame ends. */
virtual void lockFrame() = 0;

/*! Returns the timer of the project. */
virtual ITimer *timer() const = 0;

/*!
* Registers the given block section.
* \see <a href="blockSections.html">Block sections</a>
Expand Down
25 changes: 25 additions & 0 deletions include/scratchcpp/itimer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

namespace libscratchcpp
{

/*!
* \brief The ITimer interface represents a timer of a Scratch project.
*
* You can get a project timer using Engine#timer().
*/
class ITimer
{
public:
virtual ~ITimer() { }

/*! Returns the time since last reset in seconds. */
virtual double value() const = 0;

/*! Resets the timer. */
virtual void reset() = 0;
};

} // namespace libscratchcpp
5 changes: 5 additions & 0 deletions src/engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ target_sources(scratchcpp
script_p.h
internal/engine.cpp
internal/engine.h
internal/clock.cpp
internal/clock.h
internal/iclock.h
internal/timer.cpp
internal/timer.h
internal/blocksectioncontainer.cpp
internal/blocksectioncontainer.h
internal/global.h
Expand Down
21 changes: 21 additions & 0 deletions src/engine/internal/clock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0

#include "clock.h"

using namespace libscratchcpp;

std::shared_ptr<Clock> Clock::m_instance = std::make_shared<Clock>();

Clock::Clock()
{
}

std::shared_ptr<Clock> Clock::instance()
{
return m_instance;
}

std::chrono::steady_clock::time_point Clock::currentTime() const
{
return std::chrono::steady_clock::now();
}
25 changes: 25 additions & 0 deletions src/engine/internal/clock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <memory>
#include "iclock.h"

namespace libscratchcpp
{

class Clock : public IClock
{
public:
Clock();
Clock(const Clock &) = delete;

static std::shared_ptr<Clock> instance();

std::chrono::steady_clock::time_point currentTime() const override;

private:
static std::shared_ptr<Clock> m_instance;
};

} // namespace libscratchcpp
17 changes: 16 additions & 1 deletion src/engine/internal/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

#include "engine.h"
#include "blocksectioncontainer.h"
#include "timer.h"
#include "../../blocks/standardblocks.h"

using namespace libscratchcpp;

Engine::Engine()
Engine::Engine() :
m_defaultTimer(std::make_unique<Timer>()),
m_timer(m_defaultTimer.get())
{
srand(time(NULL));
}
Expand Down Expand Up @@ -165,6 +168,8 @@ void Engine::frame()

void Engine::start()
{
m_timer->reset();

for (auto target : m_targets) {
auto gfBlocks = target->greenFlagBlocks();
for (auto block : gfBlocks)
Expand Down Expand Up @@ -377,6 +382,16 @@ void Engine::lockFrame()
m_lockFrame = true;
}

ITimer *Engine::timer() const
{
return m_timer;
}

void Engine::setTimer(ITimer *timer)
{
m_timer = timer;
}

void Engine::registerSection(std::shared_ptr<IBlockSection> section)
{
if (section) {
Expand Down
7 changes: 7 additions & 0 deletions src/engine/internal/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <scratchcpp/iengine.h>
#include <scratchcpp/target.h>
#include <scratchcpp/itimer.h>
#include <unordered_map>
#include <memory>
#include <chrono>
Expand Down Expand Up @@ -43,6 +44,9 @@ class Engine : public IEngine
void skipFrame() override;
void lockFrame() override;

ITimer *timer() const override;
void setTimer(ITimer *timer);

void registerSection(std::shared_ptr<IBlockSection> section) override;
std::vector<std::shared_ptr<IBlockSection>> registeredSections() const;
unsigned int functionIndex(BlockFunc f) override;
Expand Down Expand Up @@ -101,6 +105,9 @@ class Engine : public IEngine
std::unordered_map<Variable *, Target *> m_variableOwners;
std::unordered_map<List *, Target *> m_listOwners;

std::unique_ptr<ITimer> m_defaultTimer;
ITimer *m_timer = nullptr;

bool m_breakFrame = false;
bool m_skipFrame = false;
bool m_lockFrame = false;
Expand Down
18 changes: 18 additions & 0 deletions src/engine/internal/iclock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <chrono>

namespace libscratchcpp
{

class IClock
{
public:
virtual ~IClock() { }

virtual std::chrono::steady_clock::time_point currentTime() const = 0;
};

} // namespace libscratchcpp
37 changes: 37 additions & 0 deletions src/engine/internal/timer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Apache-2.0

#include <cassert>

#include "timer.h"
#include "clock.h"

using namespace libscratchcpp;

Timer::Timer()
{
m_clock = Clock::instance().get();
resetTimer();
}

Timer::Timer(IClock *clock) :
m_clock(clock)
{
assert(clock);
resetTimer();
}

double Timer::value() const
{
return std::chrono::duration_cast<std::chrono::milliseconds>(m_clock->currentTime() - m_startTime).count() / 1000.0;
}

void Timer::reset()
{
resetTimer();
}

// Required to avoid calling the virtual method from the constructors
void Timer::resetTimer()
{
m_startTime = m_clock->currentTime();
}
30 changes: 30 additions & 0 deletions src/engine/internal/timer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <scratchcpp/itimer.h>
#include <chrono>

namespace libscratchcpp
{

class IClock;

class Timer : public ITimer
{
public:
Timer();
Timer(IClock *clock);
Timer(const Timer &) = delete;

double value() const override;
void reset() override;

private:
void resetTimer();

std::chrono::steady_clock::time_point m_startTime;
IClock *m_clock = nullptr;
};

} // namespace libscratchcpp
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ add_subdirectory(assets)
add_subdirectory(script)
add_subdirectory(extensions)
add_subdirectory(engine)
add_subdirectory(clock)
add_subdirectory(timer)
12 changes: 12 additions & 0 deletions test/clock/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_executable(
clock_test
clock_test.cpp
)

target_link_libraries(
clock_test
GTest::gtest_main
scratchcpp
)

gtest_discover_tests(clock_test)
20 changes: 20 additions & 0 deletions test/clock/clock_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <engine/internal/clock.h>
#include <cmath>

#include "../common.h"

using namespace libscratchcpp;

TEST(ClockTest, CurrentTime)
{
auto clock = Clock::instance();
ASSERT_TRUE(clock);

auto currentTime = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
double currentTimeMs = std::round(currentTime.time_since_epoch().count() / 10) * 10;

auto reportedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(clock->currentTime());
double reportedTimeMs = std::round(reportedTime.time_since_epoch().count() / 10) * 10;

ASSERT_EQ(reportedTimeMs, currentTimeMs);
}
2 changes: 2 additions & 0 deletions test/engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ add_executable(
target_link_libraries(
engine_test
GTest::gtest_main
GTest::gmock_main
scratchcpp
scratchcpp_mocks
)

gtest_discover_tests(engine_test)
21 changes: 21 additions & 0 deletions test/engine/engine_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <scratchcpp/project.h>
#include <scratchcpp/variable.h>
#include <scratchcpp/list.h>
#include <timermock.h>

#include "../common.h"
#include "testsection.h"
Expand Down Expand Up @@ -42,6 +43,26 @@ TEST(EngineTest, BreakFrame)
ASSERT_TRUE(engine.breakingCurrentFrame());
}

TEST(EngineTest, Timer)
{
Engine engine;
ASSERT_TRUE(engine.timer());
engine.timer()->reset(); // shouldn't crash

TimerMock timer;
engine.setTimer(&timer);
ASSERT_EQ(engine.timer(), &timer);

EXPECT_CALL(timer, reset()).Times(1);
engine.start();

EXPECT_CALL(timer, reset()).Times(0);
engine.stop();

EXPECT_CALL(timer, reset()).Times(1);
engine.run();
}

TEST(EngineTest, Sections)
{
Engine engine;
Expand Down
12 changes: 12 additions & 0 deletions test/mocks/clockmock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <engine/internal/iclock.h>
#include <gmock/gmock.h>

using namespace libscratchcpp;

class ClockMock : public IClock
{
public:
MOCK_METHOD(std::chrono::steady_clock::time_point, currentTime, (), (const override));
};
2 changes: 2 additions & 0 deletions test/mocks/enginemock.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class EngineMock : public IEngine
MOCK_METHOD(void, skipFrame, (), (override));
MOCK_METHOD(void, lockFrame, (), (override));

MOCK_METHOD(ITimer *, timer, (), (const, override));

MOCK_METHOD(void, registerSection, (std::shared_ptr<IBlockSection>), (override));
MOCK_METHOD(unsigned int, functionIndex, (BlockFunc), (override));

Expand Down
14 changes: 14 additions & 0 deletions test/mocks/timermock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <scratchcpp/itimer.h>
#include <gmock/gmock.h>

using namespace libscratchcpp;

class TimerMock : public ITimer
{
public:
MOCK_METHOD(double, value, (), (const, override));

MOCK_METHOD(void, reset, (), (override));
};
Loading