Use a linked list for the timer queue.
Improves insertion and deletion times for many pending timers considerably.
This commit is contained in:
parent
beef9c2567
commit
09a67149bc
|
@ -4,6 +4,7 @@
|
||||||
module eventcore.drivers.timer;
|
module eventcore.drivers.timer;
|
||||||
|
|
||||||
import eventcore.driver;
|
import eventcore.driver;
|
||||||
|
import eventcore.internal.dlist;
|
||||||
|
|
||||||
|
|
||||||
final class LoopTimeoutTimerDriver : EventDriverTimers {
|
final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
|
@ -20,7 +21,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
private {
|
private {
|
||||||
static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator;
|
static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator;
|
||||||
TimerSlot*[TimerID] m_timers;
|
TimerSlot*[TimerID] m_timers;
|
||||||
Array!(TimerSlot*) m_timerQueue;
|
StackDList!TimerSlot m_timerQueue;
|
||||||
TimerID m_lastTimerID;
|
TimerID m_lastTimerID;
|
||||||
TimerSlot*[] m_firedTimers;
|
TimerSlot*[] m_firedTimers;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
|
|
||||||
final package Duration getNextTimeout(long stdtime)
|
final package Duration getNextTimeout(long stdtime)
|
||||||
@safe nothrow {
|
@safe nothrow {
|
||||||
if (m_timerQueue.length == 0) return Duration.max;
|
if (m_timerQueue.empty) return Duration.max;
|
||||||
return (m_timerQueue.front.timeout - stdtime).hnsecs;
|
return (m_timerQueue.front.timeout - stdtime).hnsecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,30 +46,23 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
|
|
||||||
TimerSlot ts = void;
|
TimerSlot ts = void;
|
||||||
ts.timeout = stdtime+1;
|
ts.timeout = stdtime+1;
|
||||||
auto fired = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).lowerBound(&ts);
|
foreach (tm; m_timerQueue[]) {
|
||||||
foreach (tm; fired) {
|
if (tm.timeout > stdtime) break;
|
||||||
if (tm.repeatDuration > 0) {
|
if (tm.repeatDuration > 0) {
|
||||||
do tm.timeout += tm.repeatDuration;
|
do tm.timeout += tm.repeatDuration;
|
||||||
while (tm.timeout <= stdtime);
|
while (tm.timeout <= stdtime);
|
||||||
auto tail = m_timerQueue[fired.length .. $].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm);
|
enqueueTimer(tm);
|
||||||
try m_timerQueue.insertBefore(tail.release, tm);
|
|
||||||
catch (Exception e) { assert(false, e.msg); }
|
|
||||||
} else tm.pending = false;
|
} else tm.pending = false;
|
||||||
m_firedTimers ~= tm;
|
m_firedTimers ~= tm;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this isn't yet verified to work under all circumstances
|
// NOTE: this isn't yet verified to work under all circumstances
|
||||||
auto elems = m_timerQueue[0 .. fired.length];
|
foreach (tm; m_firedTimers)
|
||||||
|
m_timerQueue.remove(tm);
|
||||||
{
|
|
||||||
scope (failure) assert(false);
|
|
||||||
m_timerQueue.linearRemove(elems);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (tm; m_firedTimers) {
|
foreach (tm; m_firedTimers) {
|
||||||
auto cb = tm.callback;
|
auto cb = tm.callback;
|
||||||
tm.callback = null;
|
tm.callback = null;
|
||||||
|
|
||||||
if (cb) cb(tm.id);
|
if (cb) cb(tm.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,10 +97,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
tm.timeout = Clock.currStdTime + timeout.total!"hnsecs";
|
tm.timeout = Clock.currStdTime + timeout.total!"hnsecs";
|
||||||
tm.repeatDuration = repeat.total!"hnsecs";
|
tm.repeatDuration = repeat.total!"hnsecs";
|
||||||
tm.pending = true;
|
tm.pending = true;
|
||||||
|
enqueueTimer(tm);
|
||||||
auto largerRange = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm);
|
|
||||||
try m_timerQueue.insertBefore(largerRange.release, tm);
|
|
||||||
catch (Exception e) { assert(false, e.msg); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final override void stop(TimerID timer)
|
final override void stop(TimerID timer)
|
||||||
|
@ -114,20 +105,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
auto tm = m_timers[timer];
|
auto tm = m_timers[timer];
|
||||||
if (!tm.pending) return;
|
if (!tm.pending) return;
|
||||||
tm.pending = false;
|
tm.pending = false;
|
||||||
|
m_timerQueue.remove(tm);
|
||||||
TimerSlot cmp = void;
|
|
||||||
cmp.timeout = tm.timeout-1;
|
|
||||||
auto upper = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(&cmp);
|
|
||||||
assert(!upper.empty);
|
|
||||||
while (!upper.empty) {
|
|
||||||
assert(upper.front.timeout == tm.timeout);
|
|
||||||
if (upper.front is tm) {
|
|
||||||
scope (failure) assert(false);
|
|
||||||
m_timerQueue.linearRemove(upper.release.take(1));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
upper.popFront();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final override bool isPending(TimerID descriptor)
|
final override bool isPending(TimerID descriptor)
|
||||||
|
@ -189,9 +167,23 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||||
if (descriptor == TimerID.init) return false;
|
if (descriptor == TimerID.init) return false;
|
||||||
return m_timers[descriptor].refCount == 1;
|
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 {
|
struct TimerSlot {
|
||||||
|
TimerSlot* prev, next;
|
||||||
TimerID id;
|
TimerID id;
|
||||||
uint refCount;
|
uint refCount;
|
||||||
bool pending;
|
bool pending;
|
||||||
|
|
Loading…
Reference in a new issue