59 static_assert (Capacity > 0,
"capacity must be greater than zero");
60 static_assert (TickNs > 0,
"tick duration must be greater than zero");
72 template <
typename... Args>
74 : _commands (_queueSize)
75 , _generationsMem (Capacity * sizeof (
std::atomic_uint32_t))
76 , _generations (static_cast<
std::atomic_uint32_t*> (_generationsMem.get ()))
77 , _runner (
std::forward<Args> (args)...)
79 _allocator.reserveAll ();
81 for (uint32_t index = 0; index < Capacity; ++index)
83 new (&_generations[index]) std::atomic_uint32_t (0);
84 new (_allocator.getPtr (index)) Node ();
87 _allocator.releaseAll ();
89 _tick = toTick (ClockPolicy::now ());
127 for (uint32_t index = 0; index < Capacity; ++index)
129 static_cast<Node*
> (_allocator.getPtr (index))->~Node ();
130 _generations[index].~atomic ();
141 template <
class Rep,
class Period,
typename Func,
typename... Args>
142 ssize_t
setOneShot (std::chrono::duration<Rep, Period> duration, Func&& callback, Args&&... args)
noexcept
144 return createTimer (ticksFor (duration), 0, std::forward<Func> (callback), std::forward<Args> (args)...);
154 template <
class Rep,
class Period,
typename Func,
typename... Args>
155 ssize_t
setInterval (std::chrono::duration<Rep, Period> duration, Func&& callback, Args&&... args)
noexcept
157 const uint64_t ticks = ticksFor (duration);
158 return createTimer (ticks, ticks, std::forward<Func> (callback), std::forward<Args> (args)...);
167 int cancel (ssize_t
id,
bool sync =
false) noexcept
169 Node* node = resolve (
id);
183 if (
JOIN_UNLIKELY (writeCommand ({CommandType::Disarm, placeOf (
id), occupantOf (
id)}) == -1))
188 _runner.flush (*
this);
193 while (resolve (
id) !=
nullptr)
215 return resolve (
id) !=
nullptr;
223 std::chrono::nanoseconds
remaining (ssize_t
id)
const noexcept
225 const Node* node = resolve (
id);
229 return std::chrono::nanoseconds::zero ();
232 const uint64_t tick = toTick (ClockPolicy::now ());
233 const uint64_t deadline = node->deadline.load (std::memory_order_acquire);
237 return std::chrono::nanoseconds::zero ();
240 return std::chrono::nanoseconds ((deadline > tick + 1) ? ((deadline - tick - 1) * TickNs) : 0);
248 std::chrono::nanoseconds
interval (ssize_t
id)
const noexcept
250 const Node* node = resolve (
id);
254 return std::chrono::nanoseconds::zero ();
257 const uint64_t ticks = node->interval.load (std::memory_order_acquire);
261 return std::chrono::nanoseconds::zero ();
264 return std::chrono::nanoseconds (ticks * TickNs);
274 const Node* node = resolve (
id);
281 const uint64_t ticks = node->interval.load (std::memory_order_acquire);
283 return (resolve (
id) !=
nullptr) && (ticks == 0);
292 int mbind (
int numa)
const noexcept
294 if ((_allocator.mbind (numa) == -1) || (_commands.mbind (numa) == -1))
299 return _generationsMem.mbind (numa);
309 if ((_allocator.mlock () == -1) || (_commands.mlock () == -1))
314 return _generationsMem.
mlock ();
321 const RunPolicy&
runner () const noexcept
330 static constexpr std::chrono::nanoseconds
resolution () noexcept
332 return std::chrono::nanoseconds (TickNs);
348 enum class CommandType : uint8_t
357 enum class State : uint8_t
371 Node* prev =
nullptr;
374 Node* next =
nullptr;
377 std::atomic_uint64_t deadline{0};
380 std::atomic_uint64_t interval{0};
389 State state = State::Idle;
392 Function<void (), 48> callback;
395 static_assert (
sizeof (Node) == 128,
"node must fill exactly two cache lines");
411 uint64_t advance () noexcept
420 const uint64_t target = toTick (ClockPolicy::now ());
421 const uint64_t start = _tick;
430 uint64_t tick = start;
433 while ((next = nextTick (tick)) <= target)
443 fire (tick & _slotMask);
451 return target - start;
458 std::chrono::nanoseconds next () const noexcept
462 return std::chrono::nanoseconds::max ();
465 const uint64_t now =
static_cast<uint64_t
> (ClockPolicy::now ().time_since_epoch ().count ());
466 const uint64_t tick = now / TickNs;
467 const uint64_t ticks = (_due > tick) ? (_due - tick) : 0;
468 const uint64_t limit =
static_cast<uint64_t
> (std::numeric_limits<int64_t>::max () / 2) / TickNs;
469 const uint64_t elapsed = (ticks != 0) ? (now % TickNs) : 0;
471 return std::chrono::nanoseconds (((ticks < limit) ? ticks : limit) * TickNs - elapsed);
482 template <
typename Func,
typename... Args>
483 ssize_t createTimer (uint64_t ticks, uint64_t
interval, Func&& callback, Args&&... args)
noexcept
485 void* ptr = _allocator.tryAllocate (
sizeof (Node));
493 Node* node =
static_cast<Node*
> (ptr);
494 node->callback = std::bind (std::forward<Func> (callback), std::forward<Args> (args)...);
495 node->interval.store (
interval, std::memory_order_release);
496 node->state = State::Idle;
497 const uint64_t now = toTick (ClockPolicy::now ());
498 node->deadline.store (now + ticks + 1, std::memory_order_release);
500 const uint32_t place = _allocator.getIndex (node);
501 const uint32_t occupant = _generations[place].load (std::memory_order_relaxed) & _occupantMask;
508 _due = std::numeric_limits<uint64_t>::max ();
512 return makeId (occupant, place);
515 if (
JOIN_UNLIKELY (writeCommand ({CommandType::Arm, place, occupant}) == -1))
523 _runner.flush (*
this);
525 return makeId (occupant, place);
533 void armTimer (Node* node,
bool sameTick =
false) noexcept
535 const uint64_t tick = _tick;
536 const uint64_t deadline = node->deadline.load (std::memory_order_relaxed);
537 const uint64_t horizon = tick + ((uint64_t (1) << (_levels * _slotBits)) - 1);
538 const uint64_t due = (deadline > tick) ? deadline : (sameTick ? tick : (tick + 1));
539 const uint64_t expires = (due < horizon) ? due : horizon;
540 const uint64_t delta = (expires > tick) ? (expires - tick) : 1;
542 size_t level =
static_cast<size_t> (63 - __builtin_clzll (delta)) / _slotBits;
543 level = (level < _levels) ? level : (_levels - 1);
545 const size_t slot = (expires >> (level * _slotBits)) & _slotMask;
547 node->level =
static_cast<uint8_t
> (level);
548 node->slot =
static_cast<uint16_t
> (slot);
549 node->state = State::Linked;
550 node->prev =
nullptr;
551 node->next = _wheel[level][slot];
553 if (node->next !=
nullptr)
555 node->next->prev = node;
558 _wheel[level][slot] = node;
559 _occupied[level][slot >> 6] |= uint64_t (1) << (slot & 63);
561 const uint64_t
event = (expires >> (level * _slotBits)) << (level * _slotBits);
573 void disarmTimer (Node* node)
noexcept
577 node->state = State::Cancelled;
599 void releaseTimer (Node* node)
noexcept
601 const uint32_t place = _allocator.getIndex (node);
603 node->callback.reset ();
604 _generations[place].fetch_add (1, std::memory_order_release);
605 _allocator.deallocate (node);
613 uint64_t nextTick (uint64_t tick)
const noexcept
615 uint64_t next = std::numeric_limits<uint64_t>::max ();
617 for (
size_t level = 0; level < _levels; ++level)
619 const size_t shift = level * _slotBits;
620 const uint64_t
base = (tick >> shift) + 1;
621 const size_t start =
base & _slotMask;
622 const size_t word = start >> 6;
623 uint64_t bits = _occupied[level][word] & (~uint64_t (0) << (start & 63));
625 for (
size_t i = 0; i <= _slots / 64; ++i)
629 const size_t slot = ((word + i) % (_slots / 64)) * 64 + __builtin_ctzll (bits);
630 const uint64_t candidate = (
base + ((slot - start) & _slotMask)) << shift;
631 next = (candidate < next) ? candidate : next;
635 bits = _occupied[level][(word + i + 1) % (_slots / 64)];
638 if (next < (((tick >> (shift + _slotBits)) + 1) << (shift + _slotBits)))
651 void cascade (uint64_t tick)
noexcept
653 for (
size_t level = 1; level < _levels; ++level)
655 const size_t slot = (tick >> (level * _slotBits)) & _slotMask;
658 while ((node = _wheel[level][slot]) !=
nullptr)
661 armTimer (node,
true);
675 void fire (
size_t slot)
noexcept
677 Node* node = _wheel[0][slot];
679 while (node !=
nullptr)
683 node->state = State::Firing;
686 const bool cancelled = (node->state == State::Cancelled);
687 node->state = State::Idle;
689 if (
JOIN_LIKELY (!cancelled && (node->interval.load (std::memory_order_relaxed) != 0)))
691 node->deadline.fetch_add (node->interval.load (std::memory_order_relaxed),
692 std::memory_order_release);
701 node = _wheel[0][slot];
709 void unlink (Node* node)
noexcept
711 if (node->prev !=
nullptr)
713 node->prev->next = node->next;
717 _wheel[node->level][node->slot] = node->next;
719 if (node->next ==
nullptr)
721 _occupied[node->level][node->slot >> 6] &= ~(uint64_t (1) << (node->slot & 63));
725 if (node->next !=
nullptr)
727 node->next->prev = node->prev;
730 node->prev =
nullptr;
731 node->next =
nullptr;
732 node->state = State::Idle;
740 int writeCommand (
const Command& cmd)
noexcept
756 void readCommands () noexcept
760 while (_commands.tryPop (cmd) == 0)
762 processCommand (cmd);
770 void processCommand (
const Command& cmd)
noexcept
772 if (
JOIN_LIKELY ((_generations[cmd.place].load (std::memory_order_acquire) & _occupantMask) ==
775 Node* node =
static_cast<Node*
> (_allocator.getPtr (cmd.place));
777 if (cmd.type == CommandType::Arm)
781 _tick = toTick (ClockPolicy::now ());
782 _due = std::numeric_limits<uint64_t>::max ();
799 Node* resolve (ssize_t
id)
const noexcept
806 const uint32_t place = placeOf (
id);
813 if (
JOIN_UNLIKELY ((_generations[place].load (std::memory_order_acquire) & _occupantMask) !=
819 return static_cast<Node*
> (_allocator.getPtr (place));
828 static constexpr ssize_t makeId (uint32_t occupant, uint32_t place)
noexcept
830 return (
static_cast<ssize_t
> (occupant & _occupantMask) << 32) | (
static_cast<ssize_t
> (place) + 1);
838 static constexpr uint32_t placeOf (ssize_t
id)
noexcept
840 return static_cast<uint32_t
> ((
id & 0xFFFFFFFF) - 1);
848 static constexpr uint32_t occupantOf (ssize_t
id)
noexcept
850 return static_cast<uint32_t
> (
id >> 32) & _occupantMask;
858 static uint64_t toTick (
typename ClockPolicy::TimePoint timePoint)
noexcept
860 return static_cast<uint64_t
> (timePoint.time_since_epoch ().count ()) / TickNs;
868 template <
class Rep,
class Period>
869 static uint64_t ticksFor (std::chrono::duration<Rep, Period> duration)
noexcept
871 auto ns = std::chrono::duration_cast<std::chrono::nanoseconds> (duration);
878 return (
static_cast<uint64_t
> (ns.count ()) + TickNs - 1) / TickNs;
882 static constexpr size_t _slots = 256;
885 static constexpr size_t _slotBits = 8;
888 static constexpr size_t _slotMask = _slots - 1;
891 static constexpr size_t _levels = 6;
894 static constexpr uint32_t _occupantMask = 0x7FFFFFFF;
897 static constexpr size_t _queueSize = 2 * Capacity;
906 LocalMem _generationsMem;
909 std::atomic_uint32_t*
const _generations;
915 uint64_t _occupied[_levels][_slots / 64] = {};
918 uint64_t _due = std::numeric_limits<uint64_t>::max ();
921 alignas (64) Node* _wheel[_levels][_slots] = {};
924 alignas (64) uint64_t _tick = 0;