mirror of https://github.com/mosra/magnum.git
Browse Source
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
15 changed files with 1420 additions and 9 deletions
@ -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) |
||||
@ -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) |
||||
@ -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 << ")"; |
||||
} |
||||
|
||||
}} |
||||
@ -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 |
||||
@ -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…
Reference in new issue