eventcore/source/eventcore/drivers/timer.d
Sönke Ludwig 60407b783b Fix re-enqueuing of periodic timers. Fixes #25.
Periodic timers were first re-enqueued and then removed, effectively causing them to stay at their old position, cutting off the tail of the timer queue.
2017-09-26 15:49:13 +02:00

195 lines
4.6 KiB
D

/**
Efficient generic management of large numbers of timers.
*/
module eventcore.drivers.timer;
import eventcore.driver;
import eventcore.internal.dlist;
final class LoopTimeoutTimerDriver : EventDriverTimers {
import std.experimental.allocator.building_blocks.free_list;
import std.experimental.allocator.building_blocks.region;
import std.experimental.allocator.mallocator;
import std.experimental.allocator : dispose, make;
import std.container.array;
import std.datetime : Clock;
import std.range : SortedRange, assumeSorted, take;
import core.time : hnsecs, Duration;
import core.memory : GC;
private {
static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator;
TimerSlot*[TimerID] m_timers;
StackDList!TimerSlot m_timerQueue;
TimerID m_lastTimerID;
TimerSlot*[] m_firedTimers;
}
static this()
{
ms_allocator.parent = Mallocator.instance;
}
package @property size_t pendingCount() const @safe nothrow { return m_timerQueue.length; }
final package Duration getNextTimeout(long stdtime)
@safe nothrow {
if (m_timerQueue.empty) return Duration.max;
return (m_timerQueue.front.timeout - stdtime).hnsecs;
}
final package bool process(long stdtime)
@trusted nothrow {
assert(m_firedTimers.length == 0);
if (m_timerQueue.empty) return false;
TimerSlot ts = void;
ts.timeout = stdtime+1;
foreach (tm; m_timerQueue[]) {
if (tm.timeout > stdtime) break;
if (tm.repeatDuration > 0) {
do tm.timeout += tm.repeatDuration;
while (tm.timeout <= stdtime);
} else tm.pending = false;
m_firedTimers ~= tm;
}
foreach (tm; m_firedTimers) {
m_timerQueue.remove(tm);
if (tm.repeatDuration > 0)
enqueueTimer(tm);
}
foreach (tm; m_firedTimers) {
auto cb = tm.callback;
tm.callback = null;
if (cb) cb(tm.id);
}
bool any_fired = m_firedTimers.length > 0;
m_firedTimers.length = 0;
m_firedTimers.assumeSafeAppend();
return any_fired;
}
final override TimerID create()
@trusted {
auto id = cast(TimerID)(++m_lastTimerID);
TimerSlot* tm;
try tm = ms_allocator.make!TimerSlot;
catch (Exception e) return TimerID.invalid;
GC.addRange(tm, TimerSlot.sizeof, typeid(TimerSlot));
assert(tm !is null);
tm.id = id;
tm.refCount = 1;
tm.timeout = long.max;
m_timers[id] = tm;
return id;
}
final override void set(TimerID timer, Duration timeout, Duration repeat)
@trusted {
scope (failure) assert(false);
auto tm = m_timers[timer];
if (tm.pending) stop(timer);
tm.timeout = Clock.currStdTime + timeout.total!"hnsecs";
tm.repeatDuration = repeat.total!"hnsecs";
tm.pending = true;
enqueueTimer(tm);
}
final override void stop(TimerID timer)
@trusted {
auto tm = m_timers[timer];
if (!tm.pending) return;
tm.pending = false;
m_timerQueue.remove(tm);
}
final override bool isPending(TimerID descriptor)
{
return m_timers[descriptor].pending;
}
final override bool isPeriodic(TimerID descriptor)
{
return m_timers[descriptor].repeatDuration > 0;
}
final override void wait(TimerID timer, TimerCallback callback)
{
assert(!m_timers[timer].callback, "Calling wait() no a timer that is already waiting.");
m_timers[timer].callback = callback;
}
final override void cancelWait(TimerID timer)
{
auto pt = m_timers[timer];
assert(pt.callback);
pt.callback = null;
}
final override void addRef(TimerID descriptor)
{
assert(descriptor != TimerID.init, "Invalid timer ID.");
assert(descriptor in m_timers, "Unknown timer ID.");
if (descriptor !in m_timers) return;
m_timers[descriptor].refCount++;
}
final override bool releaseRef(TimerID descriptor)
{
assert(descriptor != TimerID.init, "Invalid timer ID.");
assert(descriptor in m_timers, "Unknown timer ID.");
if (descriptor !in m_timers) return true;
auto tm = m_timers[descriptor];
if (!--tm.refCount) {
if (tm.pending) stop(tm.id);
m_timers.remove(descriptor);
() @trusted {
scope (failure) assert(false);
ms_allocator.dispose(tm);
GC.removeRange(tm);
} ();
return false;
}
return true;
}
final bool isUnique(TimerID descriptor)
const {
if (descriptor == TimerID.init) return false;
return m_timers[descriptor].refCount == 1;
}
private void enqueueTimer(TimerSlot* tm)
nothrow {
TimerSlot* ns;
foreach_reverse (t; m_timerQueue[])
if (t.timeout <= tm.timeout) {
ns = t;
break;
}
if (ns) m_timerQueue.insertAfter(tm, ns);
else m_timerQueue.insertFront(tm);
}
}
struct TimerSlot {
TimerSlot* prev, next;
TimerID id;
uint refCount;
bool pending;
long timeout; // stdtime
long repeatDuration;
TimerCallback callback; // TODO: use a list with small-value optimization
}