blob: 99fb7411948c38fcc2fcbef602c4238385de69f7 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <stdint.h>
#include <limits>
#include <ostream>
#include <type_traits>
#include "callback.h"
#include "optional.h"
namespace overnet {
// Number of microseconds per millisecond.
static const uint64_t kUsPerMs = 1000;
static const uint64_t kUsPerSec = 1000000;
class TimeDelta {
public:
static constexpr TimeDelta PositiveInf() {
return TimeDelta(std::numeric_limits<int64_t>::max());
}
static constexpr TimeDelta NegativeInf() {
return TimeDelta(std::numeric_limits<int64_t>::min());
}
static constexpr TimeDelta Zero() { return TimeDelta(0); }
static constexpr TimeDelta FromMicroseconds(int64_t delta) {
return TimeDelta(delta);
}
static constexpr TimeDelta FromMilliseconds(int64_t delta) {
return TimeDelta(delta * kUsPerMs);
}
static constexpr TimeDelta FromSeconds(int64_t delta) {
return TimeDelta(delta * kUsPerSec);
}
static constexpr TimeDelta FromMinutes(int64_t delta) {
return FromSeconds(delta * 60);
}
static constexpr TimeDelta FromHours(int64_t delta) {
return FromMinutes(delta * 60);
}
constexpr int64_t as_us() const { return delta_; }
private:
explicit constexpr TimeDelta(int64_t delta) : delta_(delta) {}
int64_t delta_;
};
class TimeStamp {
public:
static constexpr TimeStamp Epoch() { return TimeStamp(TimeDelta::Zero()); }
static constexpr TimeStamp AfterEpoch(TimeDelta td) { return TimeStamp(td); }
constexpr TimeDelta after_epoch() const { return td_; }
private:
explicit constexpr TimeStamp(TimeDelta td) : td_(td) {}
TimeDelta td_;
};
inline constexpr bool operator==(TimeDelta a, TimeDelta b) {
return a.as_us() == b.as_us();
}
inline constexpr bool operator!=(TimeDelta a, TimeDelta b) {
return a.as_us() != b.as_us();
}
inline std::ostream& operator<<(std::ostream& out, TimeDelta a) {
if (a == TimeDelta::PositiveInf()) return out << "+inf";
if (a == TimeDelta::NegativeInf()) return out << "-inf";
auto us = a.as_us();
if (us > -1000 && us < 1000) return out << us << "us";
if (us > -1000000 && us < 1000000) return out << (us / 1000.0) << "ms";
return out << (us / 1000000.0) << "s";
}
inline std::ostream& operator<<(std::ostream& out, TimeStamp ts) {
return out << '@' << ts.after_epoch();
}
inline constexpr TimeDelta operator+(TimeDelta a, TimeDelta b) {
if (a == TimeDelta::PositiveInf() || a == TimeDelta::NegativeInf() ||
b == TimeDelta::Zero()) {
return a;
}
if (b == TimeDelta::PositiveInf()) return b;
if (b == TimeDelta::NegativeInf()) return b;
if (b.as_us() > 0) {
if (a.as_us() > 0 &&
b.as_us() > TimeDelta::PositiveInf().as_us() - a.as_us()) {
return TimeDelta::PositiveInf();
}
} else {
if (a.as_us() < 0 &&
b.as_us() < TimeDelta::NegativeInf().as_us() - a.as_us()) {
return TimeDelta::NegativeInf();
}
}
return TimeDelta::FromMicroseconds(a.as_us() + b.as_us());
}
inline constexpr TimeDelta operator-(TimeDelta x) {
if (x == TimeDelta::NegativeInf()) return TimeDelta::PositiveInf();
if (x == TimeDelta::PositiveInf()) return TimeDelta::NegativeInf();
return TimeDelta::FromMicroseconds(-x.as_us());
}
inline constexpr TimeDelta operator-(TimeDelta a, TimeDelta b) {
return a + (-b);
}
inline constexpr bool operator>(TimeDelta a, TimeDelta b) {
return a.as_us() > b.as_us();
}
inline constexpr bool operator<(TimeDelta a, TimeDelta b) {
return a.as_us() < b.as_us();
}
inline constexpr bool operator>=(TimeDelta a, TimeDelta b) {
return a.as_us() >= b.as_us();
}
inline constexpr bool operator<=(TimeDelta a, TimeDelta b) {
return a.as_us() <= b.as_us();
}
inline constexpr TimeDelta operator-(TimeStamp a, TimeStamp b) {
return a.after_epoch() - b.after_epoch();
}
inline constexpr TimeStamp operator+(TimeStamp a, TimeDelta b) {
return TimeStamp::AfterEpoch(a.after_epoch() + b);
}
inline constexpr bool operator>(TimeStamp a, TimeStamp b) {
return a.after_epoch() > b.after_epoch();
}
inline constexpr bool operator<(TimeStamp a, TimeStamp b) {
return a.after_epoch() < b.after_epoch();
}
inline constexpr bool operator>=(TimeStamp a, TimeStamp b) {
return a.after_epoch() >= b.after_epoch();
}
inline constexpr bool operator<=(TimeStamp a, TimeStamp b) {
return a.after_epoch() <= b.after_epoch();
}
inline constexpr bool operator==(TimeStamp a, TimeStamp b) {
return a.after_epoch() == b.after_epoch();
}
inline constexpr bool operator!=(TimeStamp a, TimeStamp b) {
return a.after_epoch() != b.after_epoch();
}
class Timeout;
class Timer {
friend class Timeout;
public:
virtual TimeStamp Now() = 0;
template <class F>
void At(TimeStamp t, F f);
protected:
template <class T>
T* TimeoutStorage(Timeout* timeout);
static void FireTimeout(Timeout* timeout, Status status);
private:
virtual void InitTimeout(Timeout* timeout, TimeStamp when) = 0;
virtual void CancelTimeout(Timeout* timeout, Status status) = 0;
};
class Timeout {
friend class Timer;
public:
static constexpr uint64_t kMaxTimerStorage = 5 * sizeof(void*);
Timeout(const Timeout&) = delete;
Timeout& operator=(const Timeout&) = delete;
// Initialize a timeout for timestamp when. cb will be called when the timeout
// expires (with status OK) or when the timeout is cancelled (with non-OK
// status).
Timeout(Timer* timer, TimeStamp when, StatusCallback cb)
: timer_(timer), cb_(std::move(cb)) {
assert(!cb_.empty());
timer_->InitTimeout(this, when);
}
~Timeout() {
if (!cb_.empty()) Cancel();
assert(cb_.empty());
}
void Cancel(Status status = Status::Cancelled()) {
timer_->CancelTimeout(this, std::move(status));
}
private:
Timer* const timer_;
StatusCallback cb_;
typename std::aligned_storage<kMaxTimerStorage>::type storage_;
};
template <class T>
T* Timer::TimeoutStorage(Timeout* timeout) {
static_assert(Timeout::kMaxTimerStorage >= sizeof(T),
"Must have enough storage in Timeout for Timer data");
return reinterpret_cast<T*>(&timeout->storage_);
}
inline void Timer::FireTimeout(Timeout* timeout, Status status) {
if (timeout->cb_.empty()) return;
timeout->cb_(status);
}
template <class F>
void Timer::At(TimeStamp t, F f) {
struct CB {
CB(F f) : fn(std::move(f)) {}
Optional<Timeout> timeout;
F fn;
bool done = false;
bool initialized = false;
};
auto* cb = new CB(std::move(f));
cb->timeout.Reset(this, t, [cb](const Status& status) {
if (status.is_ok()) {
cb->fn();
}
cb->done = true;
if (cb->done && cb->initialized) {
delete cb;
}
});
cb->initialized = true;
if (cb->done && cb->initialized) {
delete cb;
}
}
} // namespace overnet