Skip to content

Commit

Permalink
Introduce system/phase timers
Browse files Browse the repository at this point in the history
Close: #181
  • Loading branch information
AndreasLrx committed Nov 22, 2024
1 parent e1fcd84 commit 4f4cb2a
Show file tree
Hide file tree
Showing 9 changed files with 567 additions and 8 deletions.
65 changes: 65 additions & 0 deletions doc/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,71 @@ I strongly recommend you to use enum types, or const values/macros.
registry.runSystemsPhase<Pipeline::PredefinedPhases::OnUpdate>();
```

### Timers

By default, phases and systems are runned at each frame (ie each `registry.run()` call).
However you may want to limit them to run every two frames, or every 5 seconds.
This is possible through the @ref ecstasy::Timer "Timer" class.

Every @ref ecstasy::ISystem "ISystem" and @ref ecstasy::Pipeline::Phase "Phase" have a timer instance accessible through `getTimer()` getter.

#### Interval

Want to limit your system to run at a specific time ? Use @ref ecstasy::Timer::setInterval "setInterval()" function:

@warning
Setting an interval means it will always wait **at least** the required interval between two systems calls but it can be longer.

```cpp
ecstasy::Registry registry;

registry.addSystem<MySystem>().getTimer().setInterval(std::chrono::milliseconds(500));
// Thanks to std::chrono, you can easily use other time units
registry.addSystem<MySystem>().getTimer().setInterval(std::chrono::seconds(5));

// Set render to every 16ms -> ~60Hz
registry.getPipeline().getPhase(Pipeline::PredefinedPhases::OnStore).getTimer().setInterval(std::chrono::milliseconds(16));
```

#### Rate

Want to limit your system to run at a frame frequency instead ? Use @ref ecstasy::Timer::setRate "setRate()" function:

```cpp
ecstasy::Registry registry;

// Will run every two frames (ie run one, skip one)
registry.addSystem<MySystem>().getTimer().setRate(2);

// Will render every 5 frames
registry.getPipeline().getPhase(Pipeline::PredefinedPhases::OnStore).getTimer().setRate(5);
```

#### Tips when using timers on Systems and Phases

The one sentence to remember about combining system and phases timers is this one:
**The system timer is only evaluated if the phase timer succeed.**

Below are some resulting behaviors.

1. System and Phase rates

Combined rates are multiplied. Inverting the values will have the same behaviors.
Ex: Rate limit of 5 on the system and 2 on the phase will result in a system rate of 10 frames.

2. Phase interval and system rate

The final interval is the phase interval multiplied by the system rate.
Ex: Interval of 5s with rate of 3 will result in a system interval of 15s.

3. Phase rate (R) and system interval (I)

The system will be called at frames when the frame id is a multiple of the `R` **and** at least `I` seconds elapsed since last system call.

4. Phase and system intervals

The longest interval need to be satisfied. They are not added.

## Using resources

Creating a resource is even simpler than creating a system: you only have to inherit @ref ecstasy::IResource "IResource".
Expand Down
5 changes: 4 additions & 1 deletion src/ecstasy/registry/Registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ namespace ecstasy

void Registry::runSystem(const std::type_index &systemId)
{
_systems.get(systemId).run(*this);
ISystem &system = _systems.get(systemId);

if (system.getTimer().trigger())
system.run(*this);
}

void Registry::runSystems()
Expand Down
2 changes: 2 additions & 0 deletions src/ecstasy/system/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ set(SRC
${INCROOT}/Pipeline.hpp
${SRCROOT}/Pipeline.cpp
${INCROOT}/ISystem.hpp
${INCROOT}/Timer.hpp
${SRCROOT}/Timer.cpp
PARENT_SCOPE
)
38 changes: 38 additions & 0 deletions src/ecstasy/system/ISystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#ifndef ECSTASY_SYSTEM_ISYSTEM_HPP_
#define ECSTASY_SYSTEM_ISYSTEM_HPP_

#include "ecstasy/system/Timer.hpp"

namespace ecstasy
{
/// @brief Forward declaration of Registry class.
Expand All @@ -37,6 +39,42 @@ namespace ecstasy
/// @since 1.0.0 (2022-10-17)
///
virtual void run(Registry &registry) = 0;

///
/// @brief Get the system timer.
///
/// @note The timer is used to control the execution of the system. You can also use
/// @ref ecstasy::Pipeline::Phase "Phase" scale timers.
///
/// @return Timer& Reference to the system timer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-22)
///
[[nodiscard]] constexpr Timer &getTimer() noexcept
{
return _timer;
}

///
/// @brief Get the system timer.
///
/// @note The timer is used to control the execution of the system. You can also use
/// @ref ecstasy::Pipeline::Phase "Phase" scale timers.
///
/// @return const Timer& Reference to the system timer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-22)
///
[[nodiscard]] constexpr const Timer &getTimer() const noexcept
{
return _timer;
}

private:
/// @brief Timer to control the execution of the system.
Timer _timer;
};
} // namespace ecstasy

Expand Down
8 changes: 5 additions & 3 deletions src/ecstasy/system/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ namespace ecstasy
return _pipeline._systemsIds.begin() + static_cast<long>(end_idx());
}

void Pipeline::Phase::run() const
void Pipeline::Phase::run()
{
if (!_timer.trigger())
return;
for (auto it = begin(); it < end(); ++it) {
_pipeline._registry.runSystem(*it);
}
Expand Down Expand Up @@ -54,14 +56,14 @@ namespace ecstasy
}
}

void Pipeline::run() const
void Pipeline::run()
{
for (auto &phase : _phases) {
phase.second.run();
}
}

void Pipeline::run(Pipeline::PhaseId phase) const
void Pipeline::run(Pipeline::PhaseId phase)
{
auto phaseIt = _phases.find(phase);

Expand Down
49 changes: 45 additions & 4 deletions src/ecstasy/system/Pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <typeindex>
#include <vector>
#include "ecstasy/system/ISystem.hpp"
#include "ecstasy/system/Timer.hpp"

namespace ecstasy
{
Expand Down Expand Up @@ -72,7 +73,7 @@ namespace ecstasy
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
constexpr Phase(Pipeline &pipeline, PhaseId id) : _pipeline(pipeline), _begin(), _size(0), _id(id)
Phase(Pipeline &pipeline, PhaseId id) : _pipeline(pipeline), _begin(), _size(0), _id(id)
{
}

Expand All @@ -83,7 +84,7 @@ namespace ecstasy
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
void run() const;
void run();

///
/// @brief Get the number of systems in this phase.
Expand Down Expand Up @@ -132,6 +133,44 @@ namespace ecstasy
///
[[nodiscard]] SystemIterator end() const noexcept;

///
/// @brief Get the phase timer.
///
/// @note The timer is used to control the execution of the phase. You can also use
/// @ref ecstasy::ISystem "ISystem" scale timers.
///
/// @warning Rate limiting both the phase and the systems will result in multiplied timers (not added).
/// However interval timers will be cumulative.
///
/// @return Timer& Reference to the system timer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-22)
///
[[nodiscard]] constexpr Timer &getTimer() noexcept
{
return _timer;
}

///
/// @brief Get the phase timer.
///
/// @note The timer is used to control the execution of the phase. You can also use
/// @ref ecstasy::ISystem "ISystem" scale timers.
///
/// @warning Rate limiting both the phase and the systems will result in multiplied timers (not added).
/// However interval timers will be cumulative.
///
/// @return const Timer& Reference to the system timer.
///
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-22)
///
[[nodiscard]] constexpr const Timer &getTimer() const noexcept
{
return _timer;
}

private:
/// @brief Owning pipeline.
Pipeline &_pipeline;
Expand All @@ -141,6 +180,8 @@ namespace ecstasy
std::size_t _size;
/// @brief Identifier of the phase.
PhaseId _id;
/// @brief Timer to control the execution of the phase.
Timer _timer;

///
/// @brief Get the index of the first system in this phase.
Expand Down Expand Up @@ -266,7 +307,7 @@ namespace ecstasy
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
void run() const;
void run();

///
/// @brief Run a specific phase of the pipeline.
Expand All @@ -276,7 +317,7 @@ namespace ecstasy
/// @author Andréas Leroux ([email protected])
/// @since 1.0.0 (2024-11-21)
///
void run(PhaseId phase) const;
void run(PhaseId phase);

private:
/// @brief Ordered list of systems.
Expand Down
88 changes: 88 additions & 0 deletions src/ecstasy/system/Timer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
///
/// @file Timer.cpp
/// @author Andréas Leroux ([email protected])
/// @brief
/// @version 1.0.0
/// @date 2024-11-21
///
/// @copyright Copyright (c) ECSTASY 2022 - 2024
///
///

#include "Timer.hpp"
#include <stdexcept>

namespace ecstasy
{
Timer::Timer()
{
_lastTrigger = TimePoint::min();
setRate(0);
}

Timer::Timer(Timer::Interval interval) : Timer()
{
setInterval(interval);
}

Timer::Timer(std::uint32_t rate) : Timer()
{
setRate(rate);
}

void Timer::setRate(std::uint32_t rate) noexcept
{
_type = Type::Rate;
if (rate == 0) {
rate = 1;
}
_rate.rate = rate;
// Reset the countdown to trigger to run the first time
_rate.triggerCountdown = 0;
}

std::uint32_t Timer::getRate() const
{
if (_type != Type::Rate)
throw std::runtime_error("Timer is not of type Rate");
return _rate.rate;
}

void Timer::setInterval(Timer::Interval interval) noexcept
{
_type = Type::TimeInterval;
_timer.interval = interval;
}

Timer::Interval Timer::getInterval() const
{
if (_type != Type::TimeInterval)
throw std::runtime_error("Timer is not of type Rate");
return _timer.interval;
}

bool Timer::trigger() noexcept
{
switch (_type) {
case Type::TimeInterval: {
auto tp = std::chrono::system_clock::now();

if (tp - _timer.interval >= _lastTrigger) {
_lastTrigger = tp;
return true;
}
return false;
}
case Type::Rate: {
if (_rate.triggerCountdown == 0) {
_rate.triggerCountdown = _rate.rate - 1;
_lastTrigger = std::chrono::system_clock::now();
return true;
}
--_rate.triggerCountdown;
return false;
}
}
return false;
}
} // namespace ecstasy
Loading

0 comments on commit 4f4cb2a

Please sign in to comment.