Browse Source

Math: introduce Nanoseconds and Seconds types.

Like the Deg / Rad classes, these are for strongly-typed representation
of time. Because the current way, either with untyped and imprecise
Float, or the insanely-hard-to-use and bloated std::chrono::nanoseconds,
was just too crappy.

This is just the types alone, corresponding typedefs in the root
namespace, and conversion from std::chrono. Using these in the Animation
library, in Timeline, in DebugTools::FrameProfiler, GL::TimeQuery etc.,
will eventually and gradually follow.
pull/638/head
Vladimír Vondruš 2 years ago
parent
commit
489c7128fd
  1. 3
      doc/changelog.dox
  2. 16
      doc/namespaces.dox
  3. 67
      doc/snippets/MagnumMath-stl.cpp
  4. 75
      doc/snippets/MagnumMath.cpp
  5. 32
      doc/types.dox
  6. 1
      src/Magnum/CMakeLists.txt
  7. 12
      src/Magnum/Magnum.h
  8. 2
      src/Magnum/Math/CMakeLists.txt
  9. 2
      src/Magnum/Math/Math.h
  10. 2
      src/Magnum/Math/Test/CMakeLists.txt
  11. 246
      src/Magnum/Math/Test/TimeStlTest.cpp
  12. 442
      src/Magnum/Math/Test/TimeTest.cpp
  13. 44
      src/Magnum/Math/Time.cpp
  14. 398
      src/Magnum/Math/Time.h
  15. 87
      src/Magnum/Math/TimeStl.h

3
doc/changelog.dox

@ -76,6 +76,7 @@ See also:
typedefs for half-float angles and ranges
- New @ref Range1Dui, @ref Range2Dui and @ref Range3Dui typedefs for unsigned
integer ranges
- New @ref Nanoseconds and @ref Seconds typedefs for time values
- Added MSVC Natvis files and pretty-printers for GDB. See @ref debuggers,
[mosra/magnum#589](https://github.com/mosra/magnum/pull/589),
[mosra/magnum#595](https://github.com/mosra/magnum/pull/595),
@ -248,6 +249,8 @@ See also:
mostly useless in practice
- New @ref Magnum/Math/ColorBatch.h header with utilities for performing Y
flip of various block-compressed formats
- New @ref Math::Nanoseconds and @ref Math::Seconds classes for strongly
typed representation of time values
@subsubsection changelog-latest-new-materialtools MaterialTools library

16
doc/namespaces.dox

@ -124,10 +124,11 @@ more information.
/** @namespace Magnum::Math::Literals
@brief Math literals
Literals for easy construction of angle, color and other values. The namespace
is further split to prevent potential ambiguity and conflicts with literals
defined by other code, but the second namespace level is @cpp inline @ce so to
get for example the color literals you can do either of these two:
Literals for easy construction of angle, color, time and other values. The
namespace is further split to prevent potential ambiguity and conflicts with
literals defined by other code, but the second namespace level is
@cpp inline @ce so to get for example the color literals you can do either of
these two:
@snippet MagnumMath.cpp Literals-using
@ -171,6 +172,13 @@ more information.
See the @ref Literals namespace and the @ref Half class for more information.
*/
/** @namespace Magnum::Math::Literals::TimeLiterals
@brief Math time literals
@m_since_latest
See the @ref Literals namespace and the @ref Nanoseconds and @ref Seconds
classes for more information.
*/
/** @dir Magnum/Math/Algorithms
* @brief Namespace @ref Magnum::Math::Algorithms

67
doc/snippets/MagnumMath-stl.cpp

@ -23,10 +23,13 @@
DEALINGS IN THE SOFTWARE.
*/
#include <ctime>
#include <chrono>
#include <map>
#include <set>
#include "Magnum/Math/StrictWeakOrdering.h"
#include "Magnum/Math/TimeStl.h"
#include "Magnum/Math/Vector4.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
@ -36,6 +39,25 @@ using namespace Magnum;
using namespace Magnum::Math::Literals;
int main() {
{
Nanoseconds previousFrameTime;
/* The (void) is to avoid -Wvexing-parse */
void stillCanDoSomething(void);
/* The include is already above, so doing it again here should be harmless */
/* [types-time] */
#include <Magnum/Math/TimeStl.h>
DOXYGEN_ELLIPSIS()
using namespace Math::Literals;
Nanoseconds currentFrameTime{std::chrono::steady_clock::now()};
if(currentFrameTime - previousFrameTime < 16.667_msec)
stillCanDoSomething();
/* [types-time] */
}
{
/* [StrictWeakOrdering] */
std::set<Vector2, Math::StrictWeakOrdering> mySet;
@ -44,4 +66,49 @@ std::map<Vector4, Int, Math::StrictWeakOrdering> myMap;
static_cast<void>(myMap);
static_cast<void>(mySet);
}
{
void usleep(Long);
/* The DOXYGEN_IGNORE() is to avoid -Wvexing-parse */
/* [Nanoseconds-usage-convert] */
Float fileCopyDuration(DOXYGEN_IGNORE(void));
/* Assuming std::time_t is seconds */
Nanoseconds a{std::time(nullptr)*1000000000};
Seconds b{fileCopyDuration()};
/* And usleep() takes microseconds */
usleep(Long(2.0_sec)/1000);
/* [Nanoseconds-usage-convert] */
}
{
/* The include is already above, so doing it again here should be harmless */
/* [Nanoseconds-usage] */
#include <Magnum/Math/TimeStl.h>
Nanoseconds a{std::chrono::high_resolution_clock::now()};
std::chrono::nanoseconds b(16.67_msec);
/* [Nanoseconds-usage] */
static_cast<void>(b);
}
/* std::time_t is long long or convertible/aliased to long long only on 64-bit
Linux, apparently. Not on 32-bit, not on Emscripten, not on macOS, not on
Windows. */
#if defined(__linux__) && !defined(CORRADE_TARGET_32BIT)
{
/* [Nanoseconds-usage-time] */
Nanoseconds a1{std::time(nullptr)}; // wrong, the input is seconds
Nanoseconds a2{std::time(nullptr)*1000000000ll}; // correct
std::time_t b1(35.0_sec); // wrong, the input is nanoseconds
std::time_t b2(35.0_sec/1000000000ll); // correct
/* [Nanoseconds-usage-time] */
static_cast<void>(a1);
static_cast<void>(a2);
static_cast<void>(b1);
static_cast<void>(b2);
}
#endif
}

75
doc/snippets/MagnumMath.cpp

@ -35,6 +35,7 @@
#include "Magnum/Math/Half.h"
#include "Magnum/Math/Range.h"
#include "Magnum/Math/Swizzle.h"
#include "Magnum/Math/Time.h"
#include "Magnum/Math/Algorithms/GramSchmidt.h"
#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__
@ -735,6 +736,80 @@ static_cast<void>(tan1);
static_cast<void>(tan2);
}
{
/* [Nanoseconds-usage] */
using namespace Math::Literals;
Nanoseconds fiveSeconds = 5.0_sec; // 5000000000
Seconds frameTime = 16.667_msec; // 0.016667
/* [Nanoseconds-usage] */
static_cast<void>(fiveSeconds);
static_cast<void>(frameTime);
}
{
/* [Nanoseconds-usage-operations] */
Seconds a = 0.15_sec + 16.67_msec; // 0.16667
Nanoseconds b = 1000.0_usec*1.25; // 1250000
//auto c = 10.0_msec*10.0_sec; // error, undefined resulting unit
/* [Nanoseconds-usage-operations] */
static_cast<void>(a);
static_cast<void>(b);
}
{
/* The DOXYGEN_IGNORE() is to avoid -Wvexing-parse */
void stillCanDoSomething(void);
/* [Nanoseconds-usage-comparison] */
Nanoseconds frameTime(DOXYGEN_IGNORE(void));
if(frameTime() < 15.0_msec)
stillCanDoSomething();
/* [Nanoseconds-usage-comparison] */
}
{
/* [_nsec] */
using namespace Math::Literals;
Nanoseconds twoSeconds = 2000000000_nsec;
/* [_nsec] */
static_cast<void>(twoSeconds);
}
{
/* [_usec] */
using namespace Math::Literals;
Nanoseconds a = 2000000.0_usec;
Seconds b = 2000000.0_usec;
/* [_usec] */
static_cast<void>(a);
static_cast<void>(b);
}
{
/* [_msec] */
using namespace Math::Literals;
Nanoseconds a = 16.67_msec;
Seconds b = 16.67_msec;
/* [_msec] */
static_cast<void>(a);
static_cast<void>(b);
}
{
/* [_sec] */
using namespace Math::Literals;
Nanoseconds a = 45.0_sec;
Seconds b = 45.0_sec;
/* [_sec] */
static_cast<void>(a);
static_cast<void>(b);
}
{
Vector3 epsilon;
/* [BitVector-boolean] */

32
doc/types.dox

@ -155,7 +155,7 @@ Half-precision vector and matrix types such as @ref Vector3h or @ref Matrix3x3h
work similarly --- you can construct them and convert them from/to other types,
but can't perform any arithmetic.
@section types-special Special types
@section types-angle Angle types
Magnum has a special type for strongly-typed representation of angles, namely
the @ref Deg and @ref Rad classes (or @ref Degd / @ref Degh and @ref Radd /
@ -190,6 +190,27 @@ any need to care about what input the function expects:
@snippet MagnumMath.cpp types-literals-usage
@section types-time Time types
Similarly to @ref Deg and @ref Rad, there's @ref Nanoseconds and @ref Seconds
for strongly-typed representation of time values. The @ref Nanoseconds is a
64-bit integer type, giving the best possible precision over a range of ±292
years, while @ref Seconds is a 32-bit floating-point type that should be
sufficient for most practical uses where neither large precision nor a large
range is needed, such as animation keyframe timing. As with the angle types,
they're *not* implicitly constructible from their underlying representation,
instead you can construct them explicitly or use the
@link Math::Literals::TimeLiterals::operator""_sec() _sec @endlink,
@link Math::Literals::TimeLiterals::operator""_msec() _msec @endlink,
@link Math::Literals::TimeLiterals::operator""_usec() _usec @endlink and
@link Math::Literals::TimeLiterals::operator""_nsec() _nsec @endlink
convenience literals that are provided in the @ref Math::Literals namespace.
The time types are similar in spirit to @ref std::chrono type definitions, but
without a dependency on STL. An opt-in conversion is available if you include
@link Magnum/Math/TimeStl.h @endlink.
@snippet MagnumMath-stl.cpp types-time
@section types-other Other types
Other types, which don't have their GLSL equivalent, are:
@ -234,13 +255,14 @@ Example:
@snippet MagnumMath.cpp types-literals-init
@section types-thirdparty-integration Integration with types from 3rd party APIs
@section types-thirdparty-integration Integration with types from the STL and 3rd party APIs
To simplify the workflow when interacting with 3rd party APIs, all Magnum math
types can be made explicitly convertible to and from types coming from external
libraries. Currently, various Magnum libraries provide these conversion, see
documentation of each `Integration.h` header for details:
types can be made explicitly convertible to and from types coming from the STL
or external libraries. Currently, various Magnum libraries provide these
conversions, see documentation of each header for details:
- @ref std::chrono types --- @ref Magnum/Math/TimeStl.h
- Math-related Vulkan structures --- @ref Magnum/Vk/Integration.h, part of
the @ref Vk library
- All Eigen types --- @ref Magnum/EigenIntegration/Integration.h and

1
src/Magnum/CMakeLists.txt

@ -150,6 +150,7 @@ set(MagnumMath_SRCS
Math/Color.cpp
Math/Half.cpp
Math/Packing.cpp
Math/Time.cpp
Math/instantiation.cpp)
set(MagnumMath_GracefulAssert_SRCS

12
src/Magnum/Magnum.h

@ -922,6 +922,18 @@ typedef Math::Range3D<Int> Range3Di;
/** @brief Float frustum */
typedef Math::Frustum<Float> Frustum;
/**
@brief 64-bit signed integer nanoseconds
@m_since_latest
*/
typedef Math::Nanoseconds<Long> Nanoseconds;
/**
@brief 32-bit float seconds
@m_since_latest
*/
typedef Math::Seconds<Float> Seconds;
/* Since 1.8.17, the original short-hand group closing doesn't work anymore.
FFS. */
/**

2
src/Magnum/Math/CMakeLists.txt

@ -59,6 +59,8 @@ set(MagnumMath_HEADERS
StrictWeakOrdering.h
Swizzle.h
Tags.h
Time.h
TimeStl.h
Unit.h
Vector.h
Vector2.h

2
src/Magnum/Math/Math.h

@ -77,6 +77,8 @@ template<class T> using Matrix4x3 = RectangularMatrix<4, 3, T>;
template<template<class> class, class> class Unit;
template<class> class Deg;
template<class> class Rad;
template<class> class Nanoseconds;
template<class> class Seconds;
class Half;

2
src/Magnum/Math/Test/CMakeLists.txt

@ -66,6 +66,8 @@ corrade_add_test(MathMatrix4Test Matrix4Test.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathSwizzleTest SwizzleTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathUnitTest UnitTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathAngleTest AngleTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathTimeTest TimeTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathTimeStlTest TimeStlTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathRangeTest RangeTest.cpp LIBRARIES MagnumMathTestLib)
corrade_add_test(MathDualTest DualTest.cpp LIBRARIES MagnumMathTestLib)

246
src/Magnum/Math/Test/TimeStlTest.cpp

@ -0,0 +1,246 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/Math/TimeStl.h"
namespace Magnum { namespace Math { namespace Test { namespace {
struct TimeStlTest: TestSuite::Tester {
explicit TimeStlTest();
void chronoDurationTypedefs();
void chronoDurationFloatingPoint();
void chronoTimePoint();
};
TimeStlTest::TimeStlTest() {
addTests({&TimeStlTest::chronoDurationTypedefs,
&TimeStlTest::chronoDurationFloatingPoint,
&TimeStlTest::chronoTimePoint});
}
using Magnum::Nanoseconds;
using namespace Math::Literals;
void TimeStlTest::chronoDurationTypedefs() {
/* Negative values should work as well */
std::chrono::nanoseconds a1{1234567891234567890ll};
std::chrono::nanoseconds a2{-1234567891234567890ll};
/* The rest is implemented in a generic way, so no need to test both
variants of every */
std::chrono::microseconds b1{4567891234567890ll};
std::chrono::milliseconds c1{-7891234567890ll};
std::chrono::seconds d1{1234567890ll};
std::chrono::minutes e1{-34567890ll};
std::chrono::hours f1{567890ll};
Nanoseconds a3{a1};
Nanoseconds a4{a2};
CORRADE_COMPARE(a3, 1234567891234567890_nsec);
CORRADE_COMPARE(a4, -1234567891234567890_nsec);
/* Using the _nsec literal to circumvent potential rounding errors when
using the _usec etc literals on platforms without an 80-bit long
double */
CORRADE_COMPARE(Nanoseconds{b1}, 4567891234567890000_nsec);
CORRADE_COMPARE(Nanoseconds{c1}, -7891234567890000000_nsec);
CORRADE_COMPARE(Nanoseconds{d1}, 1234567890000000000_nsec);
CORRADE_COMPARE(Nanoseconds{e1}, 60*-34567890000000000_nsec);
CORRADE_COMPARE(Nanoseconds{f1}, 60*60*567890000000000_nsec);
/* Only nanoseconds can be converted back */
std::chrono::nanoseconds a5(a3);
std::chrono::nanoseconds a6(a4);
CORRADE_COMPARE(a5.count(), 1234567891234567890ll);
CORRADE_COMPARE(a6.count(), -1234567891234567890ll);
constexpr std::chrono::nanoseconds ca1{1234567891234567890ll};
constexpr std::chrono::nanoseconds ca2{-1234567891234567890ll};
constexpr std::chrono::microseconds cb1{4567891234567890ll};
constexpr std::chrono::milliseconds cc1{-7891234567890ll};
constexpr std::chrono::seconds cd1{1234567890ll};
constexpr std::chrono::minutes ce1{-34567890ll};
constexpr std::chrono::hours cf1{567890ll};
constexpr Nanoseconds ca3{ca1};
constexpr Nanoseconds ca4{ca2};
constexpr Nanoseconds cb2{cb1};
constexpr Nanoseconds cc2{cc1};
constexpr Nanoseconds cd2{cd1};
constexpr Nanoseconds ce2{ce1};
constexpr Nanoseconds cf2{cf1};
CORRADE_COMPARE(ca3, 1234567891234567890_nsec);
CORRADE_COMPARE(ca4, -1234567891234567890_nsec);
CORRADE_COMPARE(cb2, 4567891234567890000_nsec);
CORRADE_COMPARE(cc2, -7891234567890000000_nsec);
CORRADE_COMPARE(cd2, 1234567890000000000_nsec);
CORRADE_COMPARE(ce2, 60*-34567890000000000_nsec);
CORRADE_COMPARE(cf2, 60*60*567890000000000_nsec);
constexpr std::chrono::nanoseconds ca5(ca3);
constexpr std::chrono::nanoseconds ca6(ca4);
CORRADE_COMPARE(ca5.count(), 1234567891234567890ll);
CORRADE_COMPARE(ca6.count(), -1234567891234567890ll);
}
void TimeStlTest::chronoDurationFloatingPoint() {
/* Same as chronoDurationTypedefs(), except that this is using a
floating-point type, for which the precision shouldn't be lost for the
fractional part. In C++14 with std::chrono_literals this would be
9087654321987654321.0ns, -9087654321987.654321ms etc. */
std::chrono::duration<long double, std::nano> a1{9087654321987654321.0l};
std::chrono::duration<long double, std::nano> a2{-9087654321987654321.0l};
/* Again, everything except nanoseconds is implemented in a generic way, so
no need to test both variants of every */
std::chrono::duration<long double, std::micro> b1{9087654321987654.321l};
std::chrono::duration<long double, std::milli> c1{-9087654321987.654321l};
std::chrono::duration<long double> d1{9087654321.987654321l};
Nanoseconds a3{a1};
Nanoseconds a4{a2};
/* Not sure what Emscripten does here, but it behaves as if long double was
actually the full precision. Similar to the logic in
TimeTest::literals(). */
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(a3, 9087654321987654321_nsec);
CORRADE_COMPARE(a4, -9087654321987654321_nsec);
CORRADE_COMPARE(Nanoseconds{b1}, 9087654321987654321_nsec);
/* The conversion suffers from the same minor precision loss as with
the floating-point Nanosecond literals themselves. Again see
TimeTest::literals() for details. */
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Nanoseconds{c1}, -9087654321987654321_nsec);
CORRADE_COMPARE(Nanoseconds{d1}, 9087654321987654320_nsec);
#else
CORRADE_COMPARE(Nanoseconds{c1}, -9087654321987654320_nsec);
CORRADE_COMPARE(Nanoseconds{d1}, 9087654321987654321_nsec);
#endif
#else
CORRADE_COMPARE(a3, 9087654321987654656_nsec);
CORRADE_COMPARE(a4, -9087654321987654656_nsec);
CORRADE_COMPARE(Nanoseconds{b1}, 9087654321987653632_nsec);
CORRADE_COMPARE(Nanoseconds{c1}, -9087654321987654656_nsec);
CORRADE_COMPARE(Nanoseconds{d1}, 9087654321987653632_nsec);
#endif
/* Only nanoseconds can be converted back */
/** @todo enable conversion in the other direction for all ratios if using
a floating-point representation */
std::chrono::duration<long double, std::nano> a5(a3);
std::chrono::duration<long double, std::nano> a6(a4);
CORRADE_COMPARE(a5.count(), 9087654321987654321.0l);
CORRADE_COMPARE(a6.count(), -9087654321987654321.0l);
constexpr std::chrono::duration<long double, std::nano> ca1{9087654321987654321.0l};
constexpr std::chrono::duration<long double, std::nano> ca2{-9087654321987654321.0l};
constexpr std::chrono::duration<long double, std::micro> cb1{9087654321987654.321l};
constexpr std::chrono::duration<long double, std::milli> cc1{-9087654321987.654321l};
constexpr std::chrono::duration<long double> cd1{9087654321.987654321l};
constexpr Nanoseconds ca3{ca1};
constexpr Nanoseconds ca4{ca2};
constexpr Nanoseconds cb2{cb1};
constexpr Nanoseconds cc2{cc1};
constexpr Nanoseconds cd2{cd1};
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(ca3, 9087654321987654321_nsec);
CORRADE_COMPARE(ca4, -9087654321987654321_nsec);
CORRADE_COMPARE(cb2, 9087654321987654321_nsec);
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(cc2, -9087654321987654321_nsec);
CORRADE_COMPARE(cd2, 9087654321987654320_nsec);
#else
CORRADE_COMPARE(cc2, -9087654321987654320_nsec);
CORRADE_COMPARE(cd2, 9087654321987654321_nsec);
#endif
#else
CORRADE_COMPARE(ca3, 9087654321987654656_nsec);
CORRADE_COMPARE(ca4, -9087654321987654656_nsec);
CORRADE_COMPARE(cb2, 9087654321987653632_nsec);
CORRADE_COMPARE(cc2, -9087654321987654656_nsec);
CORRADE_COMPARE(cd2, 9087654321987653632_nsec);
#endif
/* Only nanoseconds can be converted back */
/** @todo enable conversion in the other direction for all ratios if using
a floating-point representation */
constexpr std::chrono::duration<long double, std::nano> ca5(ca3);
constexpr std::chrono::duration<long double, std::nano> ca6(ca4);
CORRADE_COMPARE(ca5.count(), 9087654321987654321.0l);
CORRADE_COMPARE(ca6.count(), -9087654321987654321.0l);
}
void TimeStlTest::chronoTimePoint() {
std::chrono::system_clock::time_point systemNow = std::chrono::system_clock::now();
std::chrono::steady_clock::time_point steadyNow = std::chrono::steady_clock::now();
std::chrono::high_resolution_clock::time_point highNow = std::chrono::high_resolution_clock::now();
Nanoseconds system{systemNow};
Nanoseconds steady{steadyNow};
Nanoseconds high{highNow};
CORRADE_COMPARE(Long(system), std::chrono::duration_cast<std::chrono::nanoseconds>(systemNow.time_since_epoch()).count());
CORRADE_COMPARE(Long(steady), std::chrono::duration_cast<std::chrono::nanoseconds>(steadyNow.time_since_epoch()).count());
CORRADE_COMPARE(Long(high), std::chrono::duration_cast<std::chrono::nanoseconds>(highNow.time_since_epoch()).count());
/* Conversion back is possible only if the STL clock is in nanoseconds.
That's the case for all three in libstdc++. In libc++ the system_clock
has only a microsecond precision:
https://github.com/llvm/llvm-project/blob/44d85c5b15bbf6226f442126735b764d81cbf6e3/libcxx/include/__chrono/system_clock.h#L28
In MSVC STL system_clock has a 100-nanosecond resolution:
https://github.com/microsoft/STL/blob/192a84008a59ac4d2e55681e1ffac73535788674/stl/inc/__msvc_chrono.hpp#L636.
std::ostream operators for std::chrono::duration are only available
since C++20 so the comparison is this insane. */
#if !defined(CORRADE_TARGET_LIBCXX) && !defined(CORRADE_TARGET_DINKUMWARE)
std::chrono::system_clock::time_point systemAgain(system);
#endif
std::chrono::steady_clock::time_point steadyAgain(steady);
std::chrono::high_resolution_clock::time_point highAgain(high);
#if !defined(CORRADE_TARGET_LIBCXX) && !defined(CORRADE_TARGET_DINKUMWARE)
CORRADE_COMPARE(systemAgain.time_since_epoch().count(), systemNow.time_since_epoch().count());
#endif
CORRADE_COMPARE(steadyAgain.time_since_epoch().count(), steadyNow.time_since_epoch().count());
CORRADE_COMPARE(highAgain.time_since_epoch().count(), highNow.time_since_epoch().count());
/* Constexpr variants with a custom value. Spec says the time_point
constructor is constexpr since C++14. Libstdc++ and MSVC STL has them
since C++11 already, libc++ explicitly only since C++14. */
#ifndef CORRADE_TARGET_LIBCXX
constexpr
#endif
std::chrono::high_resolution_clock::time_point ca{std::chrono::nanoseconds{1234567891234567890ll}};
#ifndef CORRADE_TARGET_LIBCXX
constexpr
#endif
Nanoseconds cb{ca};
#ifndef CORRADE_TARGET_LIBCXX
constexpr
#endif
std::chrono::high_resolution_clock::time_point cc{cb};
CORRADE_COMPARE(Long(cb),std::chrono::duration_cast<std::chrono::nanoseconds>(ca.time_since_epoch()).count());
CORRADE_COMPARE(cc.time_since_epoch().count(), ca.time_since_epoch().count());
}
}}}}
CORRADE_TEST_MAIN(Magnum::Math::Test::TimeStlTest)

442
src/Magnum/Math/Test/TimeTest.cpp

@ -0,0 +1,442 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <new>
#include <sstream> /** @todo remove once Debug is STL-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is STL-free */
#include "Magnum/Math/Time.h"
struct Time {
unsigned secondsSinceEpoch;
};
struct Keyframe {
float duration;
};
namespace Magnum { namespace Math {
namespace Implementation {
template<> struct NanosecondsConverter<Long, Time> {
constexpr static Nanoseconds<Long> from(Time other) {
return Nanoseconds<Long>{Long(other.secondsSinceEpoch)*1000000000ll};
}
constexpr static Time to(Nanoseconds<Long> other) {
return {unsigned(Long(other)/1000000000ll)};
}
};
template<> struct SecondsConverter<Float, Keyframe> {
constexpr static Seconds<Float> from(Keyframe other) {
return Seconds<Float>{other.duration};
}
constexpr static Keyframe to(Seconds<Float> other) {
return {Float(other)};
}
};
}
namespace Test { namespace {
struct TimeTest: TestSuite::Tester {
explicit TimeTest();
void limits();
void construct();
void constructDefault();
void constructNoInit();
void constructCopy();
void constructFromBase();
void convert();
void literals();
void conversion();
void nanosecondFloatScaling();
void debugNanoseconds();
void debugNanosecondsPacked();
void debugSeconds();
void debugSecondsPacked();
};
using Magnum::Nanoseconds;
using Magnum::Seconds;
using namespace Math::Literals;
TimeTest::TimeTest() {
addTests({&TimeTest::limits,
&TimeTest::construct,
&TimeTest::constructDefault,
&TimeTest::constructNoInit,
&TimeTest::constructCopy,
&TimeTest::constructFromBase,
&TimeTest::convert,
&TimeTest::literals,
&TimeTest::conversion,
&TimeTest::nanosecondFloatScaling,
&TimeTest::debugNanoseconds,
&TimeTest::debugNanosecondsPacked,
&TimeTest::debugSeconds,
&TimeTest::debugSecondsPacked});
}
void TimeTest::limits() {
/* There's apparently no way to say -0x8000000000000000ll, so there's also
no non-error-prone way to verify the values are correct. */
/* It should be all 64 bits (so 16 nibbles) being set */
CORRADE_COMPARE(UnsignedLong(Long(Nanoseconds::min()))|
UnsignedLong(Long(Nanoseconds::max())),
/* 0123456789abcdef */
0xffffffffffffffffull);
/* Assuming signed integer overflow is defined sanely, which it should */
CORRADE_COMPARE(Nanoseconds::min() - 1_nsec, Nanoseconds::max());
CORRADE_COMPARE(Nanoseconds::max() + 1_nsec, Nanoseconds::min());
/* This should also hold */
CORRADE_COMPARE(Nanoseconds::min() + Nanoseconds::max(), -1_nsec);
CORRADE_COMPARE(Nanoseconds::max() - Nanoseconds::min(), -1_nsec);
}
void TimeTest::construct() {
Nanoseconds a{-123456789123456789ll};
Seconds b{123.45f};
CORRADE_COMPARE(Long(a), -123456789123456789ll);
CORRADE_COMPARE(Float(b), 123.45f);
constexpr Nanoseconds ca{-123456789123456789ll};
constexpr Seconds cb{123.45f};
CORRADE_COMPARE(Long(ca), -123456789123456789ll);
CORRADE_COMPARE(Float(cb), 123.45f);
/* Implicit conversion is not allowed */
CORRADE_VERIFY(!std::is_convertible<Long, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Float, Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Long>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Float>::value);
}
void TimeTest::constructDefault() {
Nanoseconds a1;
Nanoseconds a2{ZeroInit};
Seconds b1;
Seconds b2{ZeroInit};
CORRADE_COMPARE(Long(a1), 0ll);
CORRADE_COMPARE(Long(a2), 0ll);
CORRADE_COMPARE(Float(b1), 0.0f);
CORRADE_COMPARE(Float(b2), 0.0f);
constexpr Nanoseconds ca1;
constexpr Nanoseconds ca2{ZeroInit};
constexpr Seconds cb1;
constexpr Seconds cb2{ZeroInit};
CORRADE_COMPARE(Long(ca1), 0ll);
CORRADE_COMPARE(Long(ca2), 0ll);
CORRADE_COMPARE(Float(cb1), 0.0f);
CORRADE_COMPARE(Float(cb2), 0.0f);
CORRADE_VERIFY(std::is_nothrow_default_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_default_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, ZeroInitT>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, ZeroInitT>::value);
/* Implicit construction is not allowed */
CORRADE_VERIFY(!std::is_convertible<ZeroInitT, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<ZeroInitT, Seconds>::value);
}
void TimeTest::constructNoInit() {
Nanoseconds a{123456789123456789ll};
Seconds b{123.45f};
new(&a) Nanoseconds{Magnum::NoInit};
new(&b) Seconds{Magnum::NoInit};
{
/* Explicitly check we're not on Clang because certain Clang-based IDEs
inherit __GNUC__ if GCC is used instead of leaving it at 4 like
Clang itself does */
#if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601
/* The warning is reported for both debug and release build */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
/* On GCC 13 it's -Wuninitialized now, the compiler is now EVEN MORE
DEFINITELY RIGHT that I'm doing something wrong */
#if __GNUC__ >= 13
#pragma GCC diagnostic ignored "-Wuninitialized"
#endif
#ifdef __OPTIMIZE__
CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value.");
#endif
#endif
CORRADE_COMPARE(Long(a), 123456789123456789ll);
CORRADE_COMPARE(Float(b), 123.45f);
#if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601
#pragma GCC diagnostic pop
#endif
}
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Magnum::NoInitT>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Magnum::NoInitT>::value);
/* Implicit construction is not allowed */
CORRADE_VERIFY(!std::is_convertible<Magnum::NoInitT, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Magnum::NoInitT, Seconds>::value);
}
void TimeTest::constructCopy() {
Nanoseconds a{-987654321987654321ll};
Nanoseconds b{a};
Seconds c{-543.21f};
Seconds d{c};
CORRADE_COMPARE(Long(b), -987654321987654321ll);
CORRADE_COMPARE(Float(d), -543.21f);
constexpr Nanoseconds ca{-987654321987654321ll};
constexpr Nanoseconds cb{ca};
constexpr Seconds cc{-543.21f};
constexpr Seconds cd{cc};
CORRADE_COMPARE(Long(cb), -987654321987654321ll);
CORRADE_COMPARE(Float(cd), -543.21f);
#ifndef CORRADE_NO_STD_IS_TRIVIALLY_TRAITS
CORRADE_VERIFY(std::is_trivially_copy_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_assignable<Nanoseconds>::value);
CORRADE_VERIFY(std::is_trivially_copy_assignable<Seconds>::value);
#endif
CORRADE_VERIFY(std::is_nothrow_copy_constructible<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_constructible<Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<Nanoseconds>::value);
CORRADE_VERIFY(std::is_nothrow_copy_assignable<Seconds>::value);
}
void TimeTest::constructFromBase() {
/* The operation returns Unit instead of the leaf type, so this can work
only if the base class has a "copy constructor" from the base type */
Nanoseconds a = 15.0_usec + 3.5_msec;
Seconds b = Seconds{15.0_msec} + Seconds{3.5_sec};
CORRADE_COMPARE(a, 3.515_msec);
/* Comparing as seconds because precision loss involved */
CORRADE_COMPARE_AS(b, 3.515_sec, Seconds);
}
void TimeTest::convert() {
/* From external type */
Time a0{1707678819};
Keyframe b0{56.72f};
Nanoseconds a1{a0};
Seconds b1{b0};
CORRADE_COMPARE(a1, 1707678819.0_sec);
CORRADE_COMPARE_AS(b1, 56.72_sec, Seconds);
constexpr Time ca0{1707678819};
constexpr Keyframe cb0{56.72f};
constexpr Nanoseconds ca1{ca0};
constexpr Seconds cb1{cb0};
CORRADE_COMPARE(ca1, 1707678819.0_sec);
CORRADE_COMPARE_AS(cb1, 56.72_sec, Seconds);
/* To external type */
Nanoseconds c0 = 1707678819.0_sec;
Seconds d0 = 56.72_sec;
Time c1(c0);
Keyframe d1(d0);
CORRADE_COMPARE(c1.secondsSinceEpoch, 1707678819);
CORRADE_COMPARE(d1.duration, 56.72f);
constexpr Nanoseconds cc0 = 1707678819.0_sec;
constexpr Seconds cd0 = 56.72_sec;
constexpr Time cc1(cc0);
constexpr Keyframe cd1(cd0);
CORRADE_COMPARE(cc1.secondsSinceEpoch, 1707678819);
CORRADE_COMPARE(cd1.duration, 56.72f);
/* It should not be possible to convert in a direction that may result in a
precision loss, i.e. Seconds with a NanosecondsConverter */
CORRADE_VERIFY(std::is_constructible<Seconds, Keyframe>::value);
CORRADE_VERIFY(!std::is_constructible<Seconds, Time>::value);
CORRADE_VERIFY(std::is_constructible<Nanoseconds, Time>::value);
CORRADE_VERIFY(!std::is_constructible<Nanoseconds, Keyframe>::value);
CORRADE_VERIFY(std::is_constructible<Time, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_constructible<Time, Seconds>::value);
CORRADE_VERIFY(std::is_constructible<Keyframe, Seconds>::value);
CORRADE_VERIFY(!std::is_constructible<Keyframe, Nanoseconds>::value);
/* Implicit conversion is not allowed */
CORRADE_VERIFY(!std::is_convertible<Keyframe, Seconds>::value);
CORRADE_VERIFY(!std::is_convertible<Time, Nanoseconds>::value);
CORRADE_VERIFY(!std::is_convertible<Nanoseconds, Time>::value);
CORRADE_VERIFY(!std::is_convertible<Seconds, Keyframe>::value);
}
void TimeTest::literals() {
/* Testing the full precision, 19 digits. Max representable 63-bit value is
9223372036854775807. */
auto a = 9087654321987654321_nsec;
auto b = 9087654321987654.321_usec;
auto c = 9087654321987.654321_msec;
auto d = 9087654321.987654321_sec;
CORRADE_VERIFY(std::is_same<decltype(a), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(b), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(c), Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(d), Nanoseconds>::value);
CORRADE_COMPARE(Long(a), 9087654321987654321ll);
/* Not sure what Emscripten does here, but it behaves as if long double was
actually the full precision */
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
/* 80-bit long double has a 63-bit mantissa, which means this is converted
without any precision loss. Otherwise the precision is just 52 bits. */
CORRADE_COMPARE(Long(b), 9087654321987654321ll);
/* Well, almost. On x86 this conversion has a slight imprecision in the
lowest bit for the _ms variant, on ARM64 and Emscripten for the _s
variant. */
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(c), 9087654321987654321ll);
CORRADE_COMPARE(Long(d), 9087654321987654320ll);
#else
CORRADE_COMPARE(Long(c), 9087654321987654320ll);
CORRADE_COMPARE(Long(d), 9087654321987654321ll);
#endif
#else
CORRADE_COMPARE(Long(b), 9087654321987653632ll);
CORRADE_COMPARE(Long(c), 9087654321987654656ll);
CORRADE_COMPARE(Long(d), 9087654321987653632ll);
#endif
constexpr auto ca = 9087654321987654321_nsec;
constexpr auto cb = 9087654321987654.321_usec;
constexpr auto cc = 9087654321987.654321_msec;
constexpr auto cd = 9087654321.987654321_sec;
CORRADE_VERIFY(std::is_same<decltype(ca), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cb), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cc), const Nanoseconds>::value);
CORRADE_VERIFY(std::is_same<decltype(cd), const Nanoseconds>::value);
CORRADE_COMPARE(Long(ca), 9087654321987654321ll);
#if !defined(CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(cb), 9087654321987654321ll);
#if (defined(CORRADE_TARGET_ARM) && !defined(CORRADE_TARGET_32BIT)) || defined(CORRADE_TARGET_EMSCRIPTEN)
CORRADE_COMPARE(Long(cc), 9087654321987654321ll);
CORRADE_COMPARE(Long(cd), 9087654321987654320ll);
#else
CORRADE_COMPARE(Long(cc), 9087654321987654320ll);
CORRADE_COMPARE(Long(cd), 9087654321987654321ll);
#endif
#else
CORRADE_COMPARE(Long(cb), 9087654321987653632ll);
CORRADE_COMPARE(Long(cc), 9087654321987654656ll);
CORRADE_COMPARE(Long(cd), 9087654321987653632ll);
#endif
}
void TimeTest::conversion() {
/* Implicit conversion should be allowed. Again testing (almost) the full
nanosecond precision, although not much of it is left when converting
to a 32-bit float. */
Nanoseconds a = Seconds{-987654321.987654321f};
Seconds b = 987654321987654321_nsec;
CORRADE_COMPARE(Long(a), -987654336000000000ll);
CORRADE_COMPARE(Float(b), 987654336.0f);
constexpr Nanoseconds ca = Seconds{987654321.987654321f};
constexpr Seconds cb = -987654321987654321_nsec;
CORRADE_COMPARE(Long(ca), 987654336000000000ll);
CORRADE_COMPARE(Float(cb), -987654336.0f);
CORRADE_VERIFY(std::is_nothrow_constructible<Nanoseconds, Seconds>::value);
CORRADE_VERIFY(std::is_nothrow_constructible<Seconds, Nanoseconds>::value);
}
void TimeTest::nanosecondFloatScaling() {
/* Nanoseconds is an integer type, but multiplying it with float should
give a reasonable output. The actual logic is in the Unit class, just
verify that it works from the high level perspective here. */
CORRADE_COMPARE(1000000000_nsec*1.25, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec*1.25f, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec/0.8, 1250000000_nsec);
CORRADE_COMPARE(1000000000_nsec/0.8f, 1250000000_nsec);
/* Compared to above this looks like it should "obviously work", although
internally both cases are the same, operating on Nanoseconds */
CORRADE_COMPARE(1.0_sec*1.25, 1.25_sec);
CORRADE_COMPARE(1.0_sec*1.25f, 1.25_sec);
CORRADE_COMPARE(1.0_sec/0.8, 1.25_sec);
CORRADE_COMPARE(1.0_sec/0.8f, 1.25_sec);
/* This would be nice if it worked, but so far it doesn't, as it's
calculated as an integer value */
CORRADE_COMPARE(1.0f/0.018f, 55.5556f);
{
CORRADE_EXPECT_FAIL("This doesn't work correctly.");
CORRADE_COMPARE(1.0_sec/18.0_msec, 55.5556f);
}
}
void TimeTest::debugNanoseconds() {
std::ostringstream out;
/* Also verify that the second expression compiles (it's the Unit type,
not Nanoseconds) */
Debug{&out} << 987654321987654321_nsec << 15.0_sec - 7.5_sec;
CORRADE_COMPARE(out.str(), "Nanoseconds(987654321987654321) Nanoseconds(7500000000)\n");
}
void TimeTest::debugNanosecondsPacked() {
std::ostringstream out;
/* Second is not packed, the first should not make any flags persistent */
Debug{&out} << Debug::packed << 15.0_sec << 45.0_sec;
CORRADE_COMPARE(out.str(), "15000000000 Nanoseconds(45000000000)\n");
}
void TimeTest::debugSeconds() {
std::ostringstream out;
/* Also verify that the second expression compiles (it's the Unit type,
not Nanoseconds) */
Debug{&out} << Seconds{123.45_sec} << Seconds{15.0_sec} - Seconds{7.5_sec};
CORRADE_COMPARE(out.str(), "Seconds(123.45) Seconds(7.5)\n");
}
void TimeTest::debugSecondsPacked() {
std::ostringstream out;
/* Second is not packed, the first should not make any flags persistent */
Debug{&out} << Debug::packed << Seconds{123.45_sec} << Seconds{45.0_sec};
CORRADE_COMPARE(out.str(), "123.45 Seconds(45)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Math::Test::TimeTest)

44
src/Magnum/Math/Time.cpp

@ -0,0 +1,44 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Time.h"
#include <Corrade/Utility/Debug.h>
namespace Magnum { namespace Math {
Utility::Debug& operator<<(Utility::Debug& debug, const Unit<Nanoseconds, Long>& value) {
if(debug.immediateFlags() >= Utility::Debug::Flag::Packed)
return debug << Long(value);
return debug << "Nanoseconds(" << Utility::Debug::nospace << Long(value) << Utility::Debug::nospace << ")";
}
Utility::Debug& operator<<(Utility::Debug& debug, const Unit<Seconds, Float>& value) {
if(debug.immediateFlags() >= Utility::Debug::Flag::Packed)
return debug << Float(value);
return debug << "Seconds(" << Utility::Debug::nospace << Float(value) << Utility::Debug::nospace << ")";
}
}}

398
src/Magnum/Math/Time.h

@ -0,0 +1,398 @@
#ifndef Magnum_Math_Time_h
#define Magnum_Math_Time_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Class @ref Magnum::Math::Nanoseconds, @ref Magnum::Math::Seconds, literal @link Magnum::Math::Literals::TimeLiterals::operator""_nsec() @endlink, @link Magnum::Math::Literals::TimeLiterals::operator""_usec() @endlink, @link Magnum::Math::Literals::TimeLiterals::operator""_msec() @endlink, @link Magnum::Math::Literals::TimeLiterals::operator""_sec() @endlink
* @m_since_latest
*/
#include <Corrade/Utility/Utility.h>
#include "Magnum/Magnum.h"
#include "Magnum/visibility.h"
#include "Magnum/Math/Unit.h"
namespace Magnum { namespace Math {
namespace Implementation {
template<class, class> struct NanosecondsConverter;
template<class, class> struct SecondsConverter;
}
/**
@brief Nanoseconds
@m_since_latest
Along with @ref Seconds provides convenience classes to make time specification
and conversion less error-prone. As there's little need to represent fractions
of nanoseconds, the @ref Magnum::Nanoseconds typedef uses a 64-bit signed
integer, which covers a span of ±292 years. In scenarios where nanosecond
precision or a large range isn't needed, the @ref Magnum::Seconds typedef,
which is a 32-bit floating-point type, may be sufficient.
@section Math-Nanoseconds-usage Usage
You can create the value by using one of the time literals. For all of them the
result type is @ref Nanoseconds for preserving maximum precision, but you can
directly convert the literal value @link Seconds @endlink:
@snippet MagnumMath.cpp Nanoseconds-usage
Or by explicitly converting a unitless value (such as an output from some
function) to either nanoseconds or seconds. And same can be done in the other direction:
@snippet MagnumMath-stl.cpp Nanoseconds-usage-convert
The classes support all arithmetic operations, such as addition, subtraction
or multiplication/division by a unitless number:
@snippet MagnumMath.cpp Nanoseconds-usage-operations
It is also possible to compare time values with all comparison operators. As
the literals are all producing @ref Nanoseconds, it's most convenient to
compare to nanosecond values. Comparison of @ref Nanoseconds and @ref Seconds
is not possible without conversion to a common type first.
@snippet MagnumMath.cpp Nanoseconds-usage-comparison
@section Math-Nanoseconds-stl STL compatibility
Instances of @ref Nanoseconds are explicitly convertible from and to
@ref std::chrono::duration and @ref std::chrono::time_point types if you
include @ref Magnum/Math/TimeStl.h. The conversion is provided in a separate
header to avoid unconditional @cpp #include <chrono> @ce, which can
significantly affect compile times. The following table lists allowed
conversions, conversions in certain directions aren't allowed as they cause a
precision loss:
Magnum type | | STL type
-----------------| - | ---------------------
@ref Nanoseconds | | @ref std::chrono::nanoseconds
@ref Nanoseconds | | @ref std::chrono::microseconds
@ref Nanoseconds | | @ref std::chrono::milliseconds
@ref Nanoseconds | | @ref std::chrono::seconds
@ref Nanoseconds | | @ref std::chrono::minutes
@ref Nanoseconds | | @ref std::chrono::hours
@ref Nanoseconds | | @ref std::chrono::duration
@ref Nanoseconds | | @ref std::chrono::duration "std::chrono::duration<Rep, std::nano>"
@ref Nanoseconds | | @ref std::chrono::time_point
@ref Nanoseconds | | @ref std::chrono::time_point "std::chrono::time_point<Clock, std::chrono::duration<Rep, std::nano>>"
Example:
@snippet MagnumMath-stl.cpp Nanoseconds-usage
<b></b>
@m_class{m-block m-warning}
@par Conversion from and to std::time_t
Even though @ref std::time_t may look like an implementation-defined strong
type, it's actually just an alias to an integer type, which in turn means
it's not possible to provide safe conversion for it. Thus a simple
conversion, while it may compile, won't do the right thing:
@par
@snippet MagnumMath-stl.cpp Nanoseconds-usage-time
@see @link Literals::TimeLiterals::operator""_nsec() @endlink,
@link Literals::TimeLiterals::operator""_usec() @endlink,
@link Literals::TimeLiterals::operator""_msec() @endlink,
@link Literals::TimeLiterals::operator""_sec() @endlink
*/
template<class T> class Nanoseconds: public Unit<Nanoseconds, T> {
public:
/**
* @brief Minimal representable value
*
* Returns @cpp -0x8000000000000000_nsec @ce.
*/
constexpr static Nanoseconds<T> min();
/**
* @brief Maximal representable value
*
* Returns @cpp 0x7fffffffffffffff_nsec @ce.
*/
constexpr static Nanoseconds<T> max();
/**
* @brief Default constructor
*
* Equivalent to @ref Nanoseconds(ZeroInitT).
*/
/* Needs to be Math::Nanoseconds here and in all other places because
older Clang and both MSVC 2015 and 2017 treat it as a template
instance Nanoseconds<T> instead of a Nanoseconds template */
constexpr /*implicit*/ Nanoseconds() noexcept: Unit<Math::Nanoseconds, T>{ZeroInit} {}
/** @brief Construct a zero time */
constexpr explicit Nanoseconds(ZeroInitT) noexcept: Unit<Math::Nanoseconds, T>{ZeroInit} {}
/** @brief Construct without initializing the contents */
explicit Nanoseconds(Magnum::NoInitT) noexcept: Unit<Math::Nanoseconds, T>{Magnum::NoInit} {}
/** @brief Explicit constructor from a unitless type */
constexpr explicit Nanoseconds(T value) noexcept: Unit<Math::Nanoseconds, T>{value} {}
/** @brief Copy constructor */
/* Needed in order to make arithmetic operations (which have a Unit
return type) convertible to Nanoseconds */
constexpr /*implicit*/ Nanoseconds(Unit<Math::Nanoseconds, T> other) noexcept: Unit<Math::Nanoseconds, T>(other) {}
/**
* @brief Construct nanoseconds from seconds
*
* The floating-point value is multiplied by a billion and rounded.
*/
template<class U> constexpr /*implicit*/ Nanoseconds(Unit<Seconds, U> value) noexcept;
/** @brief Construct nanoseconds from external representation */
template<class U, class V = decltype(Implementation::NanosecondsConverter<T, U>::from(std::declval<U>()))> constexpr explicit Nanoseconds(const U& other) noexcept: Nanoseconds{Implementation::NanosecondsConverter<T, U>::from(other)} {}
/** @brief Convert nanoseconds to external representation */
template<class U, class V = decltype(Implementation::NanosecondsConverter<T, U>::to(std::declval<Nanoseconds<T>>()))> constexpr explicit operator U() const {
return Implementation::NanosecondsConverter<T, U>::to(*this);
}
};
/* Doxygen can't match these to the class, meh */
#ifndef DOXYGEN_GENERATING_OUTPUT
template<> constexpr Nanoseconds<Long> Nanoseconds<Long>::min() {
/* There's apparently no way to say -0x8000000000000000ll. C++, LOL. */
return Nanoseconds<Long>{Long(0x8000000000000000ull)};
}
template<> constexpr Nanoseconds<Long> Nanoseconds<Long>::max() {
return Nanoseconds<Long>{0x7fffffffffffffffll};
}
#endif
/**
@brief Seconds
@m_since_latest
Represents a floating-point second value. Compared to @ref Nanoseconds, the
@ref Magnum::Seconds typedef uses a 32-bit float which offers a
microsecond-level precision and a reasonable range for scenarios where storing
a full 64-bit nanosecond value isn't needed. See @ref Nanoseconds for more
information and usage examples.
*/
template<class T> class Seconds: public Unit<Seconds, T> {
public:
/**
* @brief Default constructor
*
* Equivalent to @ref Seconds(ZeroInitT).
*/
/* Needs to be Math::Seconds here and in all other places because
older Clang and both MSVC 2015 and 2017 treat it as a template
instance Seconds<T> instead of a Seconds template */
constexpr /*implicit*/ Seconds() noexcept: Unit<Math::Seconds, T>{ZeroInit} {}
/** @brief Construct a zero time */
constexpr explicit Seconds(ZeroInitT) noexcept: Unit<Math::Seconds, T>{ZeroInit} {}
/** @brief Construct without initializing the contents */
explicit Seconds(Magnum::NoInitT) noexcept: Unit<Math::Seconds, T>{Magnum::NoInit} {}
/** @brief Explicit constructor from a unitless type */
constexpr explicit Seconds(T value) noexcept: Unit<Math::Seconds, T>{value} {}
/** @brief Copy constructor */
/* Needed in order to make arithmetic operations (which have a Unit
return type) convertible to Seconds */
constexpr /*implicit*/ Seconds(Unit<Math::Seconds, T> other) noexcept: Unit<Math::Seconds, T>(other) {}
/**
* @brief Construct seconds from nanoseconds
*
* A floating-point value can accurately only represent microseconds
* and only in a limited range, so the conversion may result in some
* precision loss.
*/
template<class U> constexpr /*implicit*/ Seconds(Unit<Nanoseconds, U> value) noexcept;
/** @brief Construct seconds from external representation */
template<class U, class V = decltype(Implementation::SecondsConverter<T, U>::from(std::declval<U>()))> constexpr explicit Seconds(const U& other) noexcept: Seconds{Implementation::SecondsConverter<T, U>::from(other)} {}
/** @brief Convert seconds to external representation */
template<class U, class V = decltype(Implementation::SecondsConverter<T, U>::to(std::declval<Seconds<T>>()))> constexpr explicit operator U() const {
return Implementation::SecondsConverter<T, U>::to(*this);
}
};
/* Unlike STL, where there's e.g. std::literals::string_literals with both
being inline, here's just the second inline because making both would cause
the literals to be implicitly available to all code in Math. Which isn't
great if there are eventually going to be conflicts. In case of STL the
expected use case was that literals are available to anybody who does
`using namespace std;`, that doesn't apply here as most APIs are in
subnamespaces that *should not* be pulled in via `using` as a whole. */
namespace Literals {
/** @todoc The inline causes "error: non-const getClassDef() called on
aliased member. Please report as a bug." on Doxygen 1.8.18, plus the
fork I have doesn't even mark them as inline in the XML output yet. And
it also duplicates the literal reference to parent namespace, adding
extra noise. Revisit once upgrading to a newer version. */
#ifndef DOXYGEN_GENERATING_OUTPUT
inline
#endif
namespace TimeLiterals {
/* Note on literal naming: while the STL in C++14 uses `ns`, `us`, `s` and
https://en.cppreference.com/w/cpp/chrono/operator%22%22s claims that having
`s` for both a string literal, taking const char*, and a second literal,
taking long double, "just works", at least on GCC and Clang it's only true
if both are defined next to each other IN THE SAME NAMESPACE. Which
completely breaks any library composability or encapsulation, as it means I
can not have a StringView _s literal defined in Corrade::Containers and a
Nanosecond _s literal defined in Magnum::Math. The only workaround I found
was to do something along the lines of the following, which was NOT NICE at
all (and no, `using namespace Containers::Literals` wasn't enough).
namespace Magnum { namespace Math { namespace Literals {
using Containers::Literals::operator""_s;
}}}
Thus I'm choosing a different name to prevent the conflict from even
happening. On the other hand, that's probably a better solution even without
the above design issue in the C++ spec, because it prevents potential
conflicts with _s / _us being eventually used for Short and UnsignedShort
literals, or _h conflicting between half-float and hour literals (now it'd
be _hr). Even C++14 picked `min` for minutes instead of `m` because it
apparently seemed to become problematic once/if distance literals for meters
and such get introduced. So why not go with the more clear name for
everything already. Seeing 15.0_sec in unfamiliar code doesn't feel
ambiguous, seeing 127_s or 0.5_h definitely does. */
/** @relatesalso Magnum::Math::Nanoseconds
@brief Nanosecond value literal
@m_since_latest
Compared to the microsecond, millisecond and second literals, this literal is
an integer value and not a floating-point, as it's not possible to represent
fractions of nanoseconds. Usage example:
@snippet MagnumMath.cpp _nsec
@see @link operator""_usec() @endlink, @link operator""_msec() @endlink,
@link operator""_sec() @endlink
@m_keywords{_nsec nsec}
*/
constexpr Nanoseconds<Long> operator "" _nsec(unsigned long long value) {
return Nanoseconds<Long>{Long(value)};
}
/** @relatesalso Magnum::Math::Nanoseconds
@brief Microsecond value literal
@m_since_latest
As the value is converted to whole nanoseconds, everything after thousandths is
truncated. Additionally, up to thousandths the conversion is without precision
loss only on systems with a 80-bit @cpp long double @ce (which has a 63-bit
mantissa). If you need to ensure nanosecond-level precision on systems that
have a 64-bit @cpp long double @ce, use @link operator""_nsec() @endlink
instead. On the other hand, if nanosecond-level precision isn't needed, it's
possible to convert directly to @ref Seconds that offer a microsecond-level
precision on a range of roughly ±8 seconds. For example:
@snippet MagnumMath.cpp _usec
@see @link operator""_msec() @endlink, @link operator""_sec() @endlink,
@ref CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE
@m_keywords{_usec usec}
*/
constexpr Nanoseconds<Long> operator "" _usec(long double value) {
return Nanoseconds<Long>{Long(value*1000.0l)};
}
/** @relatesalso Magnum::Math::Nanoseconds
@brief Millisecond value literal
@m_since_latest
As the value is converted to whole nanoseconds, everything after millionths is
truncated. Additionally, up to millionths the conversion is without precision
loss only on systems with a 80-bit @cpp long double @ce (which has a 63-bit
mantissa). If you need to ensure nanosecond-level precision on systems that
have a 64-bit @cpp long double @ce, use @link operator""_nsec() @endlink
instead. On the other hand, if nanosecond-level precision isn't needed, it's
possible to convert directly to @ref Seconds that offer a millisecond-level
precision on a range of roughly ±2 hours. For example:
@snippet MagnumMath.cpp _msec
@see @link operator""_usec() @endlink, @link operator""_sec() @endlink,
@ref CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE
@m_keywords{_msec msec}
*/
constexpr Nanoseconds<Long> operator "" _msec(long double value) {
return Nanoseconds<Long>{Long(value*1000000.0l)};
}
/** @relatesalso Magnum::Math::Nanoseconds
@brief Second value literal
@m_since_latest
As the value is converted to whole nanoseconds, everything after billionths is
truncated. Additionally, up to billionths the conversion is without precision
loss only on systems with a 80-bit @cpp long double @ce (which has a 63-bit
mantissa). If you need to ensure nanosecond-level precision on systems that
have a 64-bit @cpp long double @ce, use @link operator""_nsec() @endlink
instead. On the other hand, if nanosecond-level precision isn't needed, it's
possible to convert directly to @ref Seconds that offer a millisecond-level
precision on a range of roughly ±2 hours. For example:
@snippet MagnumMath.cpp _sec
@see @link operator""_usec() @endlink, @link operator""_msec() @endlink,
@ref CORRADE_LONG_DOUBLE_SAME_AS_DOUBLE
@m_keywords{_sec sec}
*/
constexpr Nanoseconds<Long> operator "" _sec(long double value) {
return Nanoseconds<Long>{Long(value*1000000000.0l)};
}
}}
template<class T> template<class U> constexpr Nanoseconds<T>::Nanoseconds(Unit<Seconds, U> value) noexcept: Unit<Math::Nanoseconds, T>{T(static_cast<long double>(U(value))*1000000000.0l)} {}
template<class T> template<class U> constexpr Seconds<T>::Seconds(Unit<Nanoseconds, U> value) noexcept: Unit<Math::Seconds, T>{T(static_cast<long double>(U(value))/1000000000.0l)} {}
/**
* @debugoperator{Nanoseconds}
* @m_since_latest
*/
MAGNUM_EXPORT Utility::Debug& operator<<(Utility::Debug& debug, const Unit<Nanoseconds, Long>& value);
/**
* @debugoperator{Seconds}
* @m_since_latest
*/
MAGNUM_EXPORT Utility::Debug& operator<<(Utility::Debug& debug, const Unit<Seconds, Float>& value);
}}
#endif

87
src/Magnum/Math/TimeStl.h

@ -0,0 +1,87 @@
#ifndef Magnum_Math_TimeStl_h
#define Magnum_Math_TimeStl_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
@brief STL @ref std::chrono compatibility for @ref Magnum::Math::Nanoseconds
@m_since_latest
Including this header allows you to convert a
@ref Magnum::Math::Nanoseconds from and to @ref std::chrono::duration and
@ref std::chrono::time_point. See @ref Math-Nanoseconds-stl for more
information.
*/
#include <chrono>
#include "Magnum/Math/Time.h"
namespace Magnum { namespace Math { namespace Implementation {
/* There's no NanosecondsConverter<Long, std::time_t> because this is a typedef
to some integral type, which when simply picks the Nanoseconds(Long)
constructor and not a NanosecondsConverter. C types yay. */
template<class Rep, std::intmax_t num, std::intmax_t denom> struct NanosecondsConverter<Long, std::chrono::duration<Rep, std::ratio<num, denom>>> {
constexpr static Nanoseconds<Long> from(std::chrono::duration<Rep, std::ratio<num, denom>> other) {
/* The Rep can be floating-point, truncate just the integral part but
only after converting to nanoseconds */
return Nanoseconds<Long>{Long(other.count()*num*(1000000000ll/denom))};
}
/* No to() because it can be an integer type, losing precision */
/** @todo add a floating-point-only to() once desirable */
};
template<class Rep> struct NanosecondsConverter<Long, std::chrono::duration<Rep, std::nano>> {
constexpr static Nanoseconds<Long> from(std::chrono::duration<Rep, std::nano> other) {
/* The Rep can be floating-point, truncate just the integral part --
we don't have anything for sub-nanosecond precision */
return Nanoseconds<Long>{Long(other.count())};
}
constexpr static std::chrono::duration<Rep, std::nano> to(Nanoseconds<Long> other) {
return std::chrono::duration<Rep, std::nano>{Long(other)};
}
};
template<class Clock, class Rep, std::intmax_t num, std::intmax_t denom> struct NanosecondsConverter<Long, std::chrono::time_point<Clock, std::chrono::duration<Rep, std::ratio<num, denom>>>> {
constexpr static Nanoseconds<Long> from(std::chrono::time_point<Clock, std::chrono::duration<Rep, std::ratio<num, denom>>> other) {
return Nanoseconds<Long>{other.time_since_epoch().count()*num*(1000000000ll/denom)};
}
/* No to() because it's an integer type, losing precision */
/** @todo is there even any std::chrono::time_point that would use a FP
duration type? */
};
template<class Clock, class Rep> struct NanosecondsConverter<Long, std::chrono::time_point<Clock, std::chrono::duration<Rep, std::nano>>> {
constexpr static Nanoseconds<Long> from(std::chrono::time_point<Clock, std::chrono::duration<Rep, std::nano>> other) {
return Nanoseconds<Long>{other.time_since_epoch().count()};
}
constexpr static std::chrono::time_point<Clock, std::chrono::duration<Rep, std::nano>> to(Nanoseconds<Long> other) {
return std::chrono::time_point<Clock, std::chrono::duration<Rep, std::nano>>{std::chrono::duration<Rep, std::nano>{Long(other)}};
}
};
}}}
#endif
Loading…
Cancel
Save