Update SFML Time classes to C++17

Ensure Clock uses a monotonic clock

Statically assert against low precision clocks

Allow suspend-aware clock for Android

Add descriptive documentation to sf::Clock
This commit is contained in:
Pawel Paruzel 2021-12-06 15:49:05 +01:00 committed by Vittorio Romeo
parent e9e353a7b2
commit dfff93fe04
13 changed files with 146 additions and 461 deletions

View File

@ -105,6 +105,11 @@ endif()
# Android options # Android options
if(SFML_OS_ANDROID) if(SFML_OS_ANDROID)
sfml_set_option(SFML_ANDROID_USE_SUSPEND_AWARE_CLOCK FALSE BOOL "TRUE to use an sf::Clock implementation which takes system sleep time into account (keeps advancing during suspension), FALSE to default to another available monotonic clock")
if (SFML_ANDROID_USE_SUSPEND_AWARE_CLOCK)
add_definitions(-DSFML_ANDROID_USE_SUSPEND_AWARE_CLOCK)
endif()
# avoid missing libraries when building SFML for Android with NDK r19c and later # avoid missing libraries when building SFML for Android with NDK r19c and later
set(CMAKE_FIND_ROOT_PATH "${PROJECT_SOURCE_DIR};${CMAKE_FIND_ROOT_PATH}") set(CMAKE_FIND_ROOT_PATH "${PROJECT_SOURCE_DIR};${CMAKE_FIND_ROOT_PATH}")

View File

@ -30,10 +30,56 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/System/Export.hpp> #include <SFML/System/Export.hpp>
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <chrono>
#include <ratio>
#include <type_traits>
#ifdef SFML_SYSTEM_ANDROID
#include <SFML/System/SuspendAwareClock.hpp>
#endif
namespace sf namespace sf
{ {
namespace priv
{
////////////////////////////////////////////////////////////
/// \brief Chooses a monotonic clock of highest resolution
///
/// The high_resolution_clock is usually an alias for other
/// clocks: steady_clock or system_clock, whichever has a
/// higher precision.
///
/// sf::Clock, however, is aimed towards monotonic time
/// measurements and so system_clock could never be a choice
/// as its subject to discontinuous jumps in the system time
/// (e.g., if the system administrator manually changes
/// the clock), and by the incremental adjustments performed
/// by `adjtime` and Network Time Protocol. On the other
/// hand, monotonic clocks are unaffected by this behavior.
///
/// Note: Linux implementation of a monotonic clock that
/// takes sleep time into account is represented by
/// CLOCK_BOOTTIME. Android devices can define the macro:
/// SFML_ANDROID_USE_SUSPEND_AWARE_CLOCK to use a separate
/// implementation of that clock, instead.
///
/// For more information on Linux clocks visit:
/// https://linux.die.net/man/2/clock_gettime
///
////////////////////////////////////////////////////////////
#if defined(SFML_SYSTEM_ANDROID) && defined(SFML_ANDROID_USE_SUSPEND_AWARE_CLOCK)
using MostSuitableClock = SuspendAwareClock;
#else
using MostSuitableClock = std::conditional_t<
std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock,
std::chrono::steady_clock>;
#endif
} // namespace priv
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Utility class that measures the elapsed time /// \brief Utility class that measures the elapsed time
/// ///
@ -74,11 +120,28 @@ public:
Time restart(); Time restart();
private: private:
using ClockImpl = priv::MostSuitableClock;
static_assert(ClockImpl::is_steady,
"Provided implementation is not a monotonic clock");
static_assert(std::ratio_less_equal<ClockImpl::period, std::micro>::value,
"Clock resolution is too low. Expecting at least a microsecond precision");
////////////////////////////////////////////////////////////
/// \brief Convert clock duration to Time
///
/// This function acts as a utility for converting clock
/// duration type instance into sf::Time
///
/// \return Time instance
///
////////////////////////////////////////////////////////////
static Time durationToTime(ClockImpl::duration duration);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time m_startTime; //!< Time of last reset, in microseconds ClockImpl::time_point m_startTime; //!< Time of last reset
}; };
} // namespace sf } // namespace sf

View File

@ -22,40 +22,59 @@
// //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#ifndef SFML_CLOCKIMPLWIN32_HPP
#define SFML_CLOCKIMPLWIN32_HPP #ifndef SFML_SUSPENDAWARECLOCK_HPP
#define SFML_SUSPENDAWARECLOCK_HPP
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Config.hpp> #include <SFML/System/Export.hpp>
#include <SFML/System/Time.hpp> #include <chrono>
#include <type_traits>
namespace sf namespace sf
{ {
namespace priv
{
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Windows implementation of sf::Clock /// \brief Android, chrono-compatible, suspend-aware clock
///
/// Linux steady clock is represented by CLOCK_MONOTONIC.
/// However, this implementation does not work properly for
/// long-running clocks that work in the background when the
/// system is suspended.
///
/// SuspendAwareClock uses CLOCK_BOOTTIME which is identical
/// to CLOCK_MONOTONIC, except that it also includes any time
/// that the system is suspended.
///
/// Note: In most cases, CLOCK_MONOTONIC is a better choice.
/// Make sure this implementation is required for your use case.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
class ClockImpl class SFML_SYSTEM_API SuspendAwareClock
{ {
public: public:
////////////////////////////////////////////////////////////
/// \brief Type traits and static members
///
/// These type traits and static members meet the requirements
/// of a Clock concept in the C++ Standard. More specifically,
/// TrivialClock requirements are met. Thus, naming convention
/// has been kept consistent to allow for extended use e.g.
/// https://en.cppreference.com/w/cpp/chrono/is_clock
///
////////////////////////////////////////////////////////////
using duration = std::chrono::nanoseconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<SuspendAwareClock, duration>;
//////////////////////////////////////////////////////////// static constexpr bool is_steady = true;
/// \brief Get the current time
/// static time_point now() noexcept;
/// \return Current time
///
////////////////////////////////////////////////////////////
static Time getCurrentTime();
}; };
} // namespace priv
} // namespace sf } // namespace sf
#endif // SFML_SUSPENDAWARECLOCK_HPP
#endif // SFML_CLOCKIMPLWIN32_HPP

View File

@ -22,31 +22,24 @@
// //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#ifndef SFML_SLEEPIMPLUNIX_HPP
#define SFML_SLEEPIMPLUNIX_HPP
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Config.hpp> #include <SFML/System/SuspendAwareClock.hpp>
#include <SFML/System/Time.hpp> #include <ctime>
namespace sf namespace sf
{ {
namespace priv
{
////////////////////////////////////////////////////////////
/// \brief Unix implementation of sf::Sleep
///
/// \param time Time to sleep
///
////////////////////////////////////////////////////////////
void sleepImpl(Time time);
} // namespace priv SuspendAwareClock::time_point SuspendAwareClock::now() noexcept
{
::timespec ts;
#ifdef CLOCK_BOOTTIME
clock_gettime(CLOCK_BOOTTIME, &ts);
#else
#error "CLOCK_BOOTTIME is essential for SuspendAwareClock to work"
#endif // CLOCK_BOOTTIME
return time_point(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec));
}
} // namespace sf } // namespace sf
#endif // SFML_SLEEPIMPLUNIX_HPP

View File

@ -29,38 +29,22 @@ set(SRC
${INCROOT}/FileInputStream.hpp ${INCROOT}/FileInputStream.hpp
${SRCROOT}/MemoryInputStream.cpp ${SRCROOT}/MemoryInputStream.cpp
${INCROOT}/MemoryInputStream.hpp ${INCROOT}/MemoryInputStream.hpp
${INCROOT}/SuspendAwareClock.hpp
) )
source_group("" FILES ${SRC}) source_group("" FILES ${SRC})
# add platform specific sources # add platform specific sources
if(SFML_OS_WINDOWS) if(SFML_OS_ANDROID)
set(PLATFORM_SRC set(PLATFORM_SRC ${PLATFORM_SRC}
${SRCROOT}/Win32/ClockImpl.cpp ${SRCROOT}/Android/Activity.hpp
${SRCROOT}/Win32/ClockImpl.hpp ${SRCROOT}/Android/Activity.cpp
${SRCROOT}/Win32/SleepImpl.cpp ${SRCROOT}/Android/NativeActivity.cpp
${SRCROOT}/Win32/SleepImpl.hpp ${SRCROOT}/Android/ResourceStream.cpp
${SRCROOT}/Android/ResourceStream.cpp
${SRCROOT}/Android/SuspendAwareClock.cpp
) )
source_group("windows" FILES ${PLATFORM_SRC})
else()
set(PLATFORM_SRC
${SRCROOT}/Unix/ClockImpl.cpp
${SRCROOT}/Unix/ClockImpl.hpp
${SRCROOT}/Unix/SleepImpl.cpp
${SRCROOT}/Unix/SleepImpl.hpp
)
if(SFML_OS_ANDROID)
set(PLATFORM_SRC ${PLATFORM_SRC}
${SRCROOT}/Android/Activity.hpp
${SRCROOT}/Android/Activity.cpp
${SRCROOT}/Android/NativeActivity.cpp
${SRCROOT}/Android/ResourceStream.cpp
${SRCROOT}/Android/ResourceStream.cpp
)
endif()
source_group("unix" FILES ${PLATFORM_SRC})
endif() endif()
source_group("unix" FILES ${PLATFORM_SRC})
# define the sfml-system target # define the sfml-system target
sfml_add_library(sfml-system sfml_add_library(sfml-system

View File

@ -27,18 +27,12 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/System/Clock.hpp> #include <SFML/System/Clock.hpp>
#if defined(SFML_SYSTEM_WINDOWS)
#include <SFML/System/Win32/ClockImpl.hpp>
#else
#include <SFML/System/Unix/ClockImpl.hpp>
#endif
namespace sf namespace sf
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Clock::Clock() : Clock::Clock() :
m_startTime(priv::ClockImpl::getCurrentTime()) m_startTime(ClockImpl::now())
{ {
} }
@ -46,18 +40,27 @@ m_startTime(priv::ClockImpl::getCurrentTime())
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time Clock::getElapsedTime() const Time Clock::getElapsedTime() const
{ {
return priv::ClockImpl::getCurrentTime() - m_startTime; return durationToTime(ClockImpl::now() - m_startTime);
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time Clock::restart() Time Clock::restart()
{ {
Time now = priv::ClockImpl::getCurrentTime(); const ClockImpl::time_point now = ClockImpl::now();
Time elapsed = now - m_startTime; Time elapsed = durationToTime(now - m_startTime);
m_startTime = now; m_startTime = now;
return elapsed; return elapsed;
} }
////////////////////////////////////////////////////////////
Time Clock::durationToTime(Clock::ClockImpl::duration duration)
{
using std::chrono::duration_cast;
using std::chrono::microseconds;
return sf::microseconds(duration_cast<microseconds>(duration).count());
}
} // namespace sf } // namespace sf

View File

@ -26,12 +26,8 @@
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/System/Sleep.hpp> #include <SFML/System/Sleep.hpp>
#include <chrono>
#if defined(SFML_SYSTEM_WINDOWS) #include <thread>
#include <SFML/System/Win32/SleepImpl.hpp>
#else
#include <SFML/System/Unix/SleepImpl.hpp>
#endif
namespace sf namespace sf
@ -39,8 +35,8 @@ namespace sf
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void sleep(Time duration) void sleep(Time duration)
{ {
if (duration >= Time::Zero) const auto time = std::chrono::duration<Int64, std::micro>(duration.asMicroseconds());
priv::sleepImpl(duration); std::this_thread::sleep_for(time);
} }
} // namespace sf } // namespace sf

View File

@ -1,64 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Unix/ClockImpl.hpp>
#if defined(SFML_SYSTEM_MACOS) || defined(SFML_SYSTEM_IOS)
#include <mach/mach_time.h>
#else
#include <ctime>
#endif
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
Time ClockImpl::getCurrentTime()
{
#if defined(SFML_SYSTEM_MACOS) || defined(SFML_SYSTEM_IOS)
// Mac OS X specific implementation (it doesn't support clock_gettime)
static mach_timebase_info_data_t frequency = {0, 0};
if (frequency.denom == 0)
mach_timebase_info(&frequency);
Uint64 nanoseconds = mach_absolute_time() * frequency.numer / frequency.denom;
return sf::microseconds(nanoseconds / 1000);
#else
// POSIX implementation
timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return sf::microseconds(time.tv_sec * 1000000 + time.tv_nsec / 1000);
#endif
}
} // namespace priv
} // namespace sf

View File

@ -1,61 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#ifndef SFML_CLOCKIMPLUNIX_HPP
#define SFML_CLOCKIMPLUNIX_HPP
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Config.hpp>
#include <SFML/System/Time.hpp>
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
/// \brief Unix implementation of sf::Clock
///
////////////////////////////////////////////////////////////
class ClockImpl
{
public:
////////////////////////////////////////////////////////////
/// \brief Get the current time
///
/// \return Current time
///
////////////////////////////////////////////////////////////
static Time getCurrentTime();
};
} // namespace priv
} // namespace sf
#endif // SFML_CLOCKIMPLUNIX_HPP

View File

@ -1,59 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Unix/SleepImpl.hpp>
#include <cerrno>
#include <ctime>
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
void sleepImpl(Time time)
{
Int64 usecs = time.asMicroseconds();
// Construct the time to wait
timespec ti;
ti.tv_nsec = static_cast<long>((usecs % 1000000) * 1000);
ti.tv_sec = static_cast<time_t>(usecs / 1000000);
// Wait...
// If nanosleep returns -1, we check errno. If it is EINTR
// nanosleep was interrupted and has set ti to the remaining
// duration. We continue sleeping until the complete duration
// has passed. We stop sleeping if it was due to an error.
while ((nanosleep(&ti, &ti) == -1) && (errno == EINTR))
{
}
}
} // namespace priv
} // namespace sf

View File

@ -1,87 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Win32/ClockImpl.hpp>
#include <mutex>
#include <windows.h>
namespace
{
LARGE_INTEGER getFrequency()
{
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
return frequency;
}
bool isWindowsXpOrOlder()
{
// Windows XP was the last 5.x version of Windows
return static_cast<DWORD>(LOBYTE(LOWORD(GetVersion()))) < 6;
}
}
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
Time ClockImpl::getCurrentTime()
{
// Calculate inverse of frequency multiplied by 1000000 to prevent overflow in final calculation
// Frequency is constant across the program lifetime
static double inverse = 1000000.0 / static_cast<double>(getFrequency().QuadPart);
// Detect if we are on Windows XP or older
static bool oldWindows = isWindowsXpOrOlder();
LARGE_INTEGER time;
if (oldWindows)
{
static std::recursive_mutex oldWindowsMutex;
// Acquire a lock (CRITICAL_SECTION) to prevent travelling back in time
std::scoped_lock lock(oldWindowsMutex);
// Get the current time
QueryPerformanceCounter(&time);
}
else
{
// Get the current time
QueryPerformanceCounter(&time);
}
// Return the current time as microseconds
return sf::microseconds(static_cast<sf::Int64>(static_cast<double>(time.QuadPart) * inverse));
}
} // namespace priv
} // namespace sf

View File

@ -1,55 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/System/Win32/SleepImpl.hpp>
#include <windows.h>
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
void sleepImpl(Time time)
{
// Get the supported timer resolutions on this system
TIMECAPS tc;
timeGetDevCaps(&tc, sizeof(TIMECAPS));
// Set the timer resolution to the minimum for the Sleep call
timeBeginPeriod(tc.wPeriodMin);
// Wait...
::Sleep(static_cast<DWORD>(time.asMilliseconds()));
// Reset the timer resolution back to the system default
timeEndPeriod(tc.wPeriodMin);
}
} // namespace priv
} // namespace sf

View File

@ -1,52 +0,0 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2021 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#ifndef SFML_SLEEPIMPLWIN32_HPP
#define SFML_SLEEPIMPLWIN32_HPP
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Config.hpp>
#include <SFML/System/Time.hpp>
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
/// \brief Windows implementation of sf::Sleep
///
/// \param time Time to sleep
///
////////////////////////////////////////////////////////////
void sleepImpl(Time time);
} // namespace priv
} // namespace sf
#endif // SFML_SLEEPIMPLWIN32_HPP