Files
EVEngine/test/time_contract.cpp

179 lines
6.7 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <limits>
#include "common/Time.h"
namespace {
class ManualTimeSource final : public eve::ITimeSource {
public:
eve::MonotonicTimestamp monotonicNow() const override { return monotonic_; }
eve::WallClockTimestamp wallClockNow() const override { return wallClock_; }
void advance(std::int64_t nanoseconds) {
monotonic_ = eve::MonotonicTimestamp(monotonic_.nanoseconds() + nanoseconds);
}
void setMonotonic(std::int64_t nanoseconds) { monotonic_ = eve::MonotonicTimestamp(nanoseconds); }
void setWallClock(std::int64_t nanoseconds) { wallClock_ = eve::WallClockTimestamp(nanoseconds); }
private:
eve::MonotonicTimestamp monotonic_;
eve::WallClockTimestamp wallClock_;
};
} // namespace
TEST_CASE("time.durationAndTimestampUseStrongFixedUnits") {
auto duration = eve::Duration::fromSeconds(1.25);
REQUIRE(duration.ok());
CHECK_EQ(duration.value().nanoseconds(), std::int64_t(1250000000));
CHECK_EQ(duration.value().seconds(), 1.25);
eve::MonotonicTimestamp later(150);
auto elapsed = later.since(eve::MonotonicTimestamp(50));
REQUIRE(elapsed.ok());
CHECK_EQ(elapsed.value().nanoseconds(), std::int64_t(100));
auto backwards = eve::MonotonicTimestamp(10).since(eve::MonotonicTimestamp(11));
CHECK(!backwards.ok());
auto maximum =
eve::MonotonicTimestamp(std::numeric_limits<std::int64_t>::max()).since(eve::MonotonicTimestamp::zero());
REQUIRE(maximum.ok());
CHECK_EQ(maximum.value().nanoseconds(), std::numeric_limits<std::int64_t>::max());
auto tooWide = eve::MonotonicTimestamp(std::numeric_limits<std::int64_t>::max())
.since(eve::MonotonicTimestamp(std::numeric_limits<std::int64_t>::min()));
CHECK(!tooWide.ok());
CHECK_EQ(tooWide.code(), eve::StatusCode::Failed);
REQUIRE(tooWide.error() != nullptr);
CHECK_EQ(tooWide.error()->code(), eve::DiagnosticCode::InvariantViolation);
}
TEST_CASE("time.simulationClock.fixedStepPauseAndSlowMotion") {
ManualTimeSource source;
eve::SimulationClock clock(source, eve::Duration::fromNanoseconds(100));
auto first = clock.sample();
REQUIRE(first.ok());
CHECK(first.value().empty());
CHECK_EQ(clock.currentTick().value(), std::uint64_t(0));
CHECK_EQ(clock.frameIndex().value(), std::uint64_t(1));
source.advance(350);
auto steps = clock.sample();
REQUIRE(steps.ok());
CHECK_EQ(steps.value().size(), std::size_t(3));
CHECK_EQ(steps.value()[0].tick.value(), std::uint64_t(1));
CHECK_EQ(steps.value()[2].tick.value(), std::uint64_t(3));
CHECK_EQ(steps.value()[0].delta.nanoseconds(), std::int64_t(100));
clock.setPaused(true);
source.advance(1000);
auto paused = clock.sample();
REQUIRE(paused.ok());
CHECK(paused.value().empty());
CHECK_EQ(clock.currentTick().value(), std::uint64_t(3));
clock.setPaused(false);
source.advance(100);
auto resumed = clock.sample();
REQUIRE(resumed.ok());
CHECK_EQ(resumed.value().size(), std::size_t(1));
CHECK_EQ(resumed.value()[0].tick.value(), std::uint64_t(4));
clock.reset();
auto rate = clock.setRate(0.5);
REQUIRE(rate.ok());
auto rateStart = clock.sample();
REQUIRE(rateStart.ok());
source.advance(250);
auto slow = clock.sample();
REQUIRE(slow.ok());
CHECK_EQ(slow.value().size(), std::size_t(1));
CHECK_EQ(clock.currentTick().value(), std::uint64_t(1));
auto badRate = clock.setRate(-1.0);
CHECK(!badRate.ok());
}
TEST_CASE("time.clockUsesMonotonicOnlyAndWallClockIsMetadata") {
ManualTimeSource firstSource;
ManualTimeSource secondSource;
firstSource.setWallClock(1000);
secondSource.setWallClock(9000000);
eve::SimulationClock first(firstSource, eve::Duration::fromNanoseconds(100));
eve::SimulationClock second(secondSource, eve::Duration::fromNanoseconds(100));
REQUIRE(first.sample().ok());
REQUIRE(second.sample().ok());
firstSource.advance(250);
secondSource.advance(250);
auto firstSteps = first.sample();
auto secondSteps = second.sample();
REQUIRE(firstSteps.ok());
REQUIRE(secondSteps.ok());
CHECK_EQ(firstSteps.value().size(), secondSteps.value().size());
CHECK_EQ(first.currentTick(), second.currentTick());
CHECK_EQ(firstSource.wallClockNow().unixNanoseconds(), std::int64_t(1000));
CHECK_EQ(secondSource.wallClockNow().unixNanoseconds(), std::int64_t(9000000));
}
TEST_CASE("time.clockRejectsBackwardSourceWithoutStateMutation") {
ManualTimeSource source;
eve::SimulationClock clock(source, eve::Duration::fromNanoseconds(100));
REQUIRE(clock.sample().ok());
source.advance(100);
auto accepted = clock.sample();
REQUIRE(accepted.ok());
const auto tick = clock.currentTick();
const auto frame = clock.frameIndex();
source.advance(-200);
auto rejected = clock.sample();
CHECK(!rejected.ok());
CHECK_EQ(clock.currentTick(), tick);
CHECK_EQ(clock.frameIndex(), frame);
}
TEST_CASE("time.clockRejectsTimestampDistanceOutsideDurationWithoutStateMutation") {
ManualTimeSource source;
source.setMonotonic(std::numeric_limits<std::int64_t>::min());
eve::SimulationClock clock(source, eve::Duration::fromNanoseconds(100));
REQUIRE(clock.sample().ok());
const auto tick = clock.currentTick();
const auto frame = clock.frameIndex();
source.setMonotonic(std::numeric_limits<std::int64_t>::max());
auto rejected = clock.sample();
CHECK(!rejected.ok());
REQUIRE(rejected.error() != nullptr);
CHECK_EQ(rejected.error()->code(), eve::DiagnosticCode::InvariantViolation);
CHECK_EQ(clock.currentTick(), tick);
CHECK_EQ(clock.frameIndex(), frame);
}
TEST_CASE("time.clockRejectsExtremeCatchUpWithoutMultiplicationOverflow") {
ManualTimeSource source;
eve::SimulationClock clock(source, eve::Duration::fromNanoseconds(1));
const auto beforeTick = clock.currentTick();
const auto beforeFrame = clock.frameIndex();
auto extreme = clock.advance(eve::Duration::fromNanoseconds(std::numeric_limits<std::int64_t>::max()));
CHECK(!extreme.ok());
CHECK_EQ(extreme.code(), eve::StatusCode::Failed);
REQUIRE(extreme.error() != nullptr);
CHECK_EQ(extreme.error()->code(), eve::DiagnosticCode::InvariantViolation);
CHECK_EQ(clock.currentTick(), beforeTick);
CHECK_EQ(clock.frameIndex(), beforeFrame);
eve::SimulationClock boundary(source, eve::Duration::fromNanoseconds(std::numeric_limits<std::int64_t>::max()));
auto oneStep = boundary.advance(eve::Duration::fromNanoseconds(std::numeric_limits<std::int64_t>::max()));
REQUIRE(oneStep.ok());
CHECK_EQ(oneStep.value().size(), std::size_t(1));
CHECK_EQ(boundary.currentTick().value(), std::uint64_t(1));
}