Replace float/UInt64 durations by a more precise Time class (#388)

Improve Clock class with atomic RestartIfOver method and allows to choose required precision
This commit is contained in:
Jérôme Leclercq
2022-12-29 21:31:46 +01:00
committed by GitHub
parent 1de5f65536
commit dd421a6385
84 changed files with 1278 additions and 663 deletions

View File

@@ -9,40 +9,46 @@
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Core/Config.hpp>
#include <Nazara/Core/Time.hpp>
#include <optional>
namespace Nz
{
class NAZARA_CORE_API Clock
template<bool HighPrecision>
class Clock
{
public:
Clock(UInt64 startingValue = 0, bool paused = false);
Clock(Time startingValue = Time::Zero(), bool paused = false);
Clock(const Clock& clock) = default;
Clock(Clock&& clock) = default;
Clock(Clock&& clock) noexcept = default;
~Clock() = default;
float GetSeconds() const;
UInt64 GetMicroseconds() const;
UInt64 GetMilliseconds() const;
Time GetElapsedTime() const;
bool IsPaused() const;
void Pause();
UInt64 Restart(UInt64 startingValue = 0, bool paused = false);
Time Restart(Time startingPoint = Time::Zero(), bool paused = false);
std::optional<Time> RestartIfOver(Time time);
void Unpause();
Clock& operator=(const Clock& clock) = default;
Clock& operator=(Clock&& clock) = default;
Clock& operator=(Clock&& clock) noexcept = default;
static Time Now();
private:
UInt64 m_elapsedTime;
UInt64 m_refTime;
Time m_elapsedTime;
Time m_refTime;
bool m_paused;
};
using ClockFunction = UInt64 (*)();
extern NAZARA_CORE_API ClockFunction GetElapsedMicroseconds;
extern NAZARA_CORE_API ClockFunction GetElapsedMilliseconds;
using HighPrecisionClock = Clock<true>;
using MillisecondClock = Clock<false>;
}
#include <Nazara/Core/Clock.inl>
#endif // NAZARA_CORE_CLOCK_HPP

View File

@@ -0,0 +1,151 @@
// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Core module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Core/Clock.hpp>
#include <Nazara/Core/Debug.hpp>
namespace Nz
{
/*!
* \ingroup core
* \class Nz::Clock
* \brief Utility class that measure the elapsed time
*/
/*!
* \brief Constructs a Clock object
*
* \param startingValue The starting time value, in microseconds
* \param paused The clock pause state
*/
template<bool HighPrecision>
Clock<HighPrecision>::Clock(Time startingValue, bool paused) :
m_elapsedTime(startingValue),
m_refTime(Now()),
m_paused(paused)
{
}
/*!
* Returns the elapsed time
* \return Duration elapsed
*/
template<bool HighPrecision>
Time Clock<HighPrecision>::GetElapsedTime() const
{
Time elapsedNanoseconds = m_elapsedTime;
if (!m_paused)
elapsedNanoseconds += Now() - m_refTime;
return elapsedNanoseconds;
}
/*!
* Returns the current pause state of the clock
* \return Boolean indicating if the clock is currently paused
*
* \see Pause, Unpause
*/
template<bool HighPrecision>
bool Clock<HighPrecision>::IsPaused() const
{
return m_paused;
}
/*!
* \brief Pause the clock
*
* Pauses the clock, making the time retrieving functions to always return the value at the time the clock was paused
* This has no effect if the clock is already paused
*
* \see IsPaused, Unpause
*/
template<bool HighPrecision>
void Clock<HighPrecision>::Pause()
{
if (!m_paused)
{
m_elapsedTime += Now() - m_refTime;
m_paused = true;
}
}
/*!
* \brief Restart the clock
* \return Time elapsed since creation or last restart call
*
* Restarts the clock, putting its time counter back to the starting value
* It also compute the elapsed microseconds since the last Restart() call without any time loss (a problem that the combination of GetElapsedTime and Restart have).
*/
template<bool HighPrecision>
Time Clock<HighPrecision>::Restart(Time startingValue, bool paused)
{
Time now = Now();
Time elapsedTime = m_elapsedTime;
if (!m_paused)
elapsedTime += now - m_refTime;
m_elapsedTime = startingValue;
m_refTime = now;
m_paused = paused;
return elapsedTime;
}
/*!
* \brief Restart the clock if more than time elapsed
* \return If more than time elapsed since creation or last restart call
*
* Restarts the clock, putting its time counter back to zero
* This function allows to check the elapsed time of a clock and restart it if over some value in a single call, preventing some loss between GetElapsedTime and Restart
*/
template<bool HighPrecision>
std::optional<Time> Clock<HighPrecision>::RestartIfOver(Time time)
{
Time now = Now();
Time elapsedTime = m_elapsedTime;
if (!m_paused)
elapsedTime += now - m_refTime;
if (elapsedTime < time)
return std::nullopt;
m_elapsedTime = Time::Zero();
m_refTime = now;
return elapsedTime;
}
/*!
* \brief Unpause the clock
*
* Unpauses the clock, making the clock continue to measure the time
* This has no effect if the clock is already unpaused
*
* \see IsPaused, Unpause
*/
template<bool HighPrecision>
void Clock<HighPrecision>::Unpause()
{
if (m_paused)
{
m_refTime = Now();
m_paused = false;
}
}
template<bool HighPrecision>
Time Clock<HighPrecision>::Now()
{
if constexpr (HighPrecision)
return GetElapsedNanoseconds();
else
return GetElapsedMilliseconds();
}
}
#include <Nazara/Core/DebugOff.hpp>

View File

@@ -8,6 +8,7 @@
#define NAZARA_CORE_COMPONENTS_LIFETIMECOMPONENT_HPP
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Core/Time.hpp>
#include <Nazara/Utility/Config.hpp>
namespace Nz
@@ -15,14 +16,14 @@ namespace Nz
class LifetimeComponent
{
public:
inline LifetimeComponent(float lifetime);
inline LifetimeComponent(Time lifetime);
LifetimeComponent(const LifetimeComponent&) = default;
LifetimeComponent(LifetimeComponent&&) = default;
~LifetimeComponent() = default;
inline void DecreaseLifetime(float elapsedTime);
inline void DecreaseLifetime(Time elapsedTime);
inline float GetRemainingLifeTime() const;
inline Time GetRemainingLifeTime() const;
inline bool IsAlive() const;
@@ -30,7 +31,7 @@ namespace Nz
LifetimeComponent& operator=(LifetimeComponent&&) = default;
private:
float m_remainingLifetime;
Time m_remainingLifetime;
};
}

View File

@@ -7,24 +7,24 @@
namespace Nz
{
inline LifetimeComponent::LifetimeComponent(float lifetime) :
inline LifetimeComponent::LifetimeComponent(Time lifetime) :
m_remainingLifetime(lifetime)
{
}
inline void LifetimeComponent::DecreaseLifetime(float elapsedTime)
inline void LifetimeComponent::DecreaseLifetime(Time elapsedTime)
{
m_remainingLifetime -= elapsedTime;
}
inline float LifetimeComponent::GetRemainingLifeTime() const
inline Time LifetimeComponent::GetRemainingLifeTime() const
{
return m_remainingLifetime;
}
inline bool LifetimeComponent::IsAlive() const
{
return m_remainingLifetime >= 0.f;
return m_remainingLifetime >= Time::Zero();
}
}

View File

@@ -9,6 +9,7 @@
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Core/Config.hpp>
#include <Nazara/Core/Time.hpp>
#include <Nazara/Utils/TypeList.hpp>
#include <entt/entt.hpp>
@@ -26,7 +27,7 @@ namespace Nz
LifetimeSystem(LifetimeSystem&&) = delete;
~LifetimeSystem() = default;
void Update(float elapsedTime);
void Update(Time elapsedTime);
LifetimeSystem& operator=(const LifetimeSystem&) = delete;
LifetimeSystem& operator=(LifetimeSystem&&) = delete;

View File

@@ -31,7 +31,7 @@ namespace Nz
template<typename T> T& GetSystem() const;
void Update();
void Update(float elapsedTime);
void Update(Time elapsedTime);
SystemGraph& operator=(const SystemGraph&) = delete;
SystemGraph& operator=(SystemGraph&&) = delete;
@@ -41,7 +41,7 @@ namespace Nz
{
virtual ~NodeBase();
virtual void Update(float elapsedTime) = 0;
virtual void Update(Time elapsedTime) = 0;
Int64 executionOrder;
};
@@ -51,7 +51,7 @@ namespace Nz
{
template<typename... Args> Node(Args&&... args);
void Update(float elapsedTime) override;
void Update(Time elapsedTime) override;
T system;
};
@@ -60,7 +60,7 @@ namespace Nz
std::vector<NodeBase*> m_orderedNodes;
std::vector<std::unique_ptr<NodeBase>> m_nodes;
entt::registry& m_registry;
Nz::Clock m_clock;
Nz::HighPrecisionClock m_clock;
bool m_systemOrderUpdated;
};
}

View File

@@ -32,7 +32,7 @@ namespace Nz
}
template<typename T>
void SystemGraph::Node<T>::Update(float elapsedTime)
void SystemGraph::Node<T>::Update(Time elapsedTime)
{
system.Update(elapsedTime);
}

View File

@@ -0,0 +1,104 @@
// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Core module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#pragma once
#ifndef NAZARA_CORE_TIME_HPP
#define NAZARA_CORE_TIME_HPP
#include <Nazara/Prerequisites.hpp>
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/Config.hpp>
#include <chrono>
#include <ostream>
#include <type_traits>
namespace Nz
{
struct SerializationContext;
class Time
{
public:
Time() = default;
Time(const Time&) = default;
Time(Time&&) = default;
~Time() = default;
template<typename T> constexpr T AsDuration() const;
template<typename T = float> constexpr T AsSeconds() const;
constexpr Int64 AsMicroseconds() const;
constexpr Int64 AsMilliseconds() const;
constexpr Int64 AsNanoseconds() const;
Time& operator=(const Time&) = default;
Time& operator=(Time&&) = default;
constexpr Time& operator+=(Time time);
constexpr Time& operator-=(Time time);
constexpr Time& operator*=(Time time);
constexpr Time& operator/=(Time time);
constexpr Time& operator%=(Time time);
constexpr explicit operator Int64() const;
template<class Rep, class Period> static constexpr Time FromDuration(const std::chrono::duration<Rep, Period>& d);
static constexpr Time Microsecond();
static constexpr Time Microseconds(Int64 microseconds);
static constexpr Time Millisecond();
static constexpr Time Milliseconds(Int64 milliseconds);
static constexpr Time Nanosecond();
static constexpr Time Nanoseconds(Int64 nanoseconds);
static constexpr Time Second();
template<typename T> static constexpr Time Seconds(T seconds);
static constexpr Time TickDuration(Int64 tickRate);
static constexpr Time Zero();
// External part
friend constexpr Time operator+(Time time);
friend constexpr Time operator-(Time time);
friend constexpr Time operator+(Time lhs, Time rhs);
friend constexpr Time operator-(Time lhs, Time rhs);
friend constexpr Time operator*(Time lhs, Time rhs);
friend constexpr Time operator/(Time lhs, Time rhs);
friend constexpr Time operator%(Time lhs, Time rhs);
friend constexpr bool operator==(Time lhs, Time rhs);
friend constexpr bool operator!=(Time lhs, Time rhs);
friend constexpr bool operator<(Time lhs, Time rhs);
friend constexpr bool operator<=(Time lhs, Time rhs);
friend constexpr bool operator>(Time lhs, Time rhs);
friend constexpr bool operator>=(Time lhs, Time rhs);
friend inline std::ostream& operator<<(std::ostream& out, Time time);
friend inline bool Serialize(SerializationContext& context, Time time, TypeTag<Time>);
friend inline bool Unserialize(SerializationContext& context, Time* time, TypeTag<Time>);
private:
constexpr explicit Time(Int64 nanoseconds);
Int64 m_nanoseconds;
};
namespace Literals
{
constexpr Time operator ""_ms(unsigned long long milliseconds);
constexpr Time operator ""_ns(unsigned long long nanoseconds);
constexpr Time operator ""_us(unsigned long long microseconds);
constexpr Time operator ""_s(long double seconds);
constexpr Time operator ""_s(unsigned long long seconds);
}
using GetElapsedTimeFunction = Time(*)();
extern NAZARA_CORE_API GetElapsedTimeFunction GetElapsedMilliseconds;
extern NAZARA_CORE_API GetElapsedTimeFunction GetElapsedNanoseconds;
}
#include <Nazara/Core/Time.inl>
#endif // NAZARA_CORE_TIME_HPP

View File

@@ -0,0 +1,270 @@
// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Core module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Core/Time.hpp>
#include <Nazara/Core/Debug.hpp>
namespace Nz
{
constexpr Time::Time(Int64 microseconds) :
m_nanoseconds(microseconds)
{
}
template<typename T>
constexpr T Time::AsDuration() const
{
if constexpr (std::is_same_v<T, std::chrono::nanoseconds>)
return std::chrono::nanoseconds(m_nanoseconds); //< make sure it's a no-op
else
return std::chrono::duration_cast<T>(std::chrono::nanoseconds(m_nanoseconds));
}
template<typename T>
constexpr T Time::AsSeconds() const
{
static_assert(std::is_floating_point_v<T>);
// TODO: Improve precision
return AsMicroseconds() / T(1'000'000.0) + (m_nanoseconds % 1000) / T(1'000'000'000);
}
constexpr Int64 Time::AsMicroseconds() const
{
return m_nanoseconds / 1'000;
}
constexpr Int64 Time::AsMilliseconds() const
{
return m_nanoseconds / 1'000'000;
}
constexpr Int64 Time::AsNanoseconds() const
{
return m_nanoseconds;
}
constexpr Time& Time::operator+=(Time time)
{
m_nanoseconds += time.m_nanoseconds;
return *this;
}
constexpr Time& Time::operator-=(Time time)
{
m_nanoseconds -= time.m_nanoseconds;
return *this;
}
constexpr Time& Time::operator*=(Time time)
{
m_nanoseconds *= time.m_nanoseconds;
return *this;
}
constexpr Time& Time::operator/=(Time time)
{
m_nanoseconds /= time.m_nanoseconds;
return *this;
}
constexpr Time& Time::operator%=(Time time)
{
m_nanoseconds %= time.m_nanoseconds;
return *this;
}
constexpr Time::operator Int64() const
{
return m_nanoseconds;
}
template<class Rep, class Period>
constexpr Time Time::FromDuration(const std::chrono::duration<Rep, Period>& d)
{
return Nanoseconds(std::chrono::duration_cast<std::chrono::nanoseconds>(d).count());
}
constexpr Time Time::Microsecond()
{
return Time(1'000);
}
constexpr Time Time::Microseconds(Int64 microseconds)
{
return Time(microseconds * 1'000);
}
constexpr Time Time::Millisecond()
{
return Time(1'000'000);
}
constexpr Time Time::Milliseconds(Int64 milliseconds)
{
return Time(milliseconds * 1'000'000);
}
constexpr Time Time::Nanosecond()
{
return Time(1);
}
constexpr Time Time::Nanoseconds(Int64 nanoseconds)
{
return Time(nanoseconds);
}
constexpr Time Time::Second()
{
return Time(1'000'000'000ull);
}
template<typename T>
constexpr Time Time::Seconds(T seconds)
{
if constexpr (std::is_floating_point_v<T>)
return Nanoseconds(static_cast<UInt64>(seconds * T(1'000'000'000.0)));
else if constexpr (std::is_integral_v<T>)
return Nanoseconds(seconds * 1'000'000'000LL);
else
static_assert(AlwaysFalse<T>(), "not an arithmetic type");
}
constexpr Time Time::TickDuration(Int64 tickRate)
{
return Second() / Nanoseconds(tickRate);
}
constexpr Time Time::Zero()
{
return Time(0);
}
constexpr Time operator+(Time time)
{
return time;
}
constexpr Time operator-(Time time)
{
return Time(-time.m_nanoseconds);
}
constexpr Time operator+(Time lhs, Time rhs)
{
return Time(lhs.m_nanoseconds + rhs.m_nanoseconds);
}
constexpr Time operator-(Time lhs, Time rhs)
{
return Time(lhs.m_nanoseconds - rhs.m_nanoseconds);
}
constexpr Time operator*(Time lhs, Time rhs)
{
return Time(lhs.m_nanoseconds * rhs.m_nanoseconds);
}
constexpr Time operator/(Time lhs, Time rhs)
{
return Time(lhs.m_nanoseconds / rhs.m_nanoseconds);
}
constexpr Time operator%(Time lhs, Time rhs)
{
return Time(lhs.m_nanoseconds % rhs.m_nanoseconds);
}
constexpr bool operator==(Time lhs, Time rhs)
{
return lhs.m_nanoseconds == rhs.m_nanoseconds;
}
constexpr bool operator!=(Time lhs, Time rhs)
{
return lhs.m_nanoseconds != rhs.m_nanoseconds;
}
constexpr bool operator<(Time lhs, Time rhs)
{
return lhs.m_nanoseconds < rhs.m_nanoseconds;
}
constexpr bool operator<=(Time lhs, Time rhs)
{
return lhs.m_nanoseconds <= rhs.m_nanoseconds;
}
constexpr bool operator>(Time lhs, Time rhs)
{
return lhs.m_nanoseconds > rhs.m_nanoseconds;
}
constexpr bool operator>=(Time lhs, Time rhs)
{
return lhs.m_nanoseconds >= rhs.m_nanoseconds;
}
inline std::ostream& operator<<(std::ostream& out, Time time)
{
if (time > Time::Second())
return out << time.AsSeconds<double>() << "s";
else
{
Int64 ns = time.AsNanoseconds();
if (time > Time::Millisecond())
return out << ns / 1'000'000.0 << "ms";
else if (time > Time::Microsecond())
return out << ns / 1'000.0 << "us";
else
return out << ns << "ns";
}
}
inline bool Serialize(SerializationContext& context, Time time, TypeTag<Time>)
{
if (!Serialize(context, time.m_nanoseconds))
return false;
return true;
}
inline bool Unserialize(SerializationContext& context, Time* time, TypeTag<Time>)
{
if (!Unserialize(context, &time->m_nanoseconds))
return false;
return true;
}
namespace Literals
{
constexpr Time operator ""_ms(unsigned long long milliseconds)
{
return Time::Milliseconds(static_cast<Int64>(milliseconds));
}
constexpr Time operator ""_ns(unsigned long long nanoseconds)
{
return Time::Nanoseconds(static_cast<Int64>(nanoseconds));
}
constexpr Time operator ""_us(unsigned long long microseconds)
{
return Time::Microseconds(static_cast<Int64>(microseconds));
}
constexpr Time operator ""_s(long double milliseconds)
{
return Time::Seconds(milliseconds);
}
constexpr Time operator ""_s(unsigned long long milliseconds)
{
return Time::Seconds(milliseconds);
}
}
}
#include <Nazara/Core/DebugOff.hpp>