Merge pull request #18 from vibe-d/timer_opt
Use a linked list for the timer queue.
This commit is contained in:
commit
8bf3773f5a
|
@ -4,6 +4,7 @@
|
|||
module eventcore.drivers.timer;
|
||||
|
||||
import eventcore.driver;
|
||||
import eventcore.internal.dlist;
|
||||
|
||||
|
||||
final class LoopTimeoutTimerDriver : EventDriverTimers {
|
||||
|
@ -20,7 +21,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
private {
|
||||
static FreeList!(Mallocator, TimerSlot.sizeof) ms_allocator;
|
||||
TimerSlot*[TimerID] m_timers;
|
||||
Array!(TimerSlot*) m_timerQueue;
|
||||
StackDList!TimerSlot m_timerQueue;
|
||||
TimerID m_lastTimerID;
|
||||
TimerSlot*[] m_firedTimers;
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
|
||||
final package Duration getNextTimeout(long stdtime)
|
||||
@safe nothrow {
|
||||
if (m_timerQueue.length == 0) return Duration.max;
|
||||
if (m_timerQueue.empty) return Duration.max;
|
||||
return (m_timerQueue.front.timeout - stdtime).hnsecs;
|
||||
}
|
||||
|
||||
|
@ -45,30 +46,23 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
|
||||
TimerSlot ts = void;
|
||||
ts.timeout = stdtime+1;
|
||||
auto fired = m_timerQueue[].assumeSorted!((a, b) => a.timeout < b.timeout).lowerBound(&ts);
|
||||
foreach (tm; fired) {
|
||||
foreach (tm; m_timerQueue[]) {
|
||||
if (tm.timeout > stdtime) break;
|
||||
if (tm.repeatDuration > 0) {
|
||||
do tm.timeout += tm.repeatDuration;
|
||||
while (tm.timeout <= stdtime);
|
||||
auto tail = m_timerQueue[fired.length .. $].assumeSorted!((a, b) => a.timeout < b.timeout).upperBound(tm);
|
||||
try m_timerQueue.insertBefore(tail.release, tm);
|
||||
catch (Exception e) { assert(false, e.msg); }
|
||||
enqueueTimer(tm);
|
||||
} else tm.pending = false;
|
||||
m_firedTimers ~= tm;
|
||||
}
|
||||
|
||||
// NOTE: this isn't yet verified to work under all circumstances
|
||||
auto elems = m_timerQueue[0 .. fired.length];
|
||||
|
||||
{
|
||||
scope (failure) assert(false);
|
||||
m_timerQueue.linearRemove(elems);
|
||||
}
|
||||
foreach (tm; m_firedTimers)
|
||||
m_timerQueue.remove(tm);
|
||||
|
||||
foreach (tm; m_firedTimers) {
|
||||
auto cb = tm.callback;
|
||||
tm.callback = null;
|
||||
|
||||
if (cb) cb(tm.id);
|
||||
}
|
||||
|
||||
|
@ -103,10 +97,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
tm.timeout = Clock.currStdTime + timeout.total!"hnsecs";
|
||||
tm.repeatDuration = repeat.total!"hnsecs";
|
||||
tm.pending = true;
|
||||
|
||||
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); }
|
||||
enqueueTimer(tm);
|
||||
}
|
||||
|
||||
final override void stop(TimerID timer)
|
||||
|
@ -114,20 +105,7 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
auto tm = m_timers[timer];
|
||||
if (!tm.pending) return;
|
||||
tm.pending = false;
|
||||
|
||||
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();
|
||||
}
|
||||
m_timerQueue.remove(tm);
|
||||
}
|
||||
|
||||
final override bool isPending(TimerID descriptor)
|
||||
|
@ -189,9 +167,23 @@ final class LoopTimeoutTimerDriver : EventDriverTimers {
|
|||
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;
|
||||
|
|
155
source/eventcore/internal/dlist.d
Normal file
155
source/eventcore/internal/dlist.d
Normal file
|
@ -0,0 +1,155 @@
|
|||
module eventcore.internal.dlist;
|
||||
|
||||
struct StackDList(T) {
|
||||
@safe nothrow @nogc:
|
||||
|
||||
private {
|
||||
T* m_first, m_last;
|
||||
size_t m_length;
|
||||
}
|
||||
|
||||
@disable this(this);
|
||||
|
||||
void clear()
|
||||
{
|
||||
T* itm = m_first;
|
||||
while (itm) {
|
||||
auto next = itm.next;
|
||||
itm.prev = null;
|
||||
itm.next = null;
|
||||
itm = next;
|
||||
}
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
@property bool empty() const { return m_first is null; }
|
||||
|
||||
@property size_t length() const { return m_length; }
|
||||
|
||||
@property T* front() { return m_first; }
|
||||
@property T* back() { return m_last; }
|
||||
|
||||
void insertFront(T* item)
|
||||
{
|
||||
assert(!item.prev && !item.next);
|
||||
item.next = m_first;
|
||||
if (m_first) {
|
||||
m_first.prev = item;
|
||||
m_first = item;
|
||||
} else {
|
||||
m_last = item;
|
||||
m_first = item;
|
||||
}
|
||||
m_length++;
|
||||
}
|
||||
|
||||
void insertBack(T* item)
|
||||
{
|
||||
assert(!item.prev && !item.next);
|
||||
item.prev = m_last;
|
||||
if (m_last) {
|
||||
m_last.next = item;
|
||||
m_last = item;
|
||||
} else {
|
||||
m_first = item;
|
||||
m_last = item;
|
||||
}
|
||||
m_length++;
|
||||
}
|
||||
|
||||
void insertAfter(T* item, T* after)
|
||||
{
|
||||
if (!after) insertBack(item);
|
||||
else {
|
||||
item.prev = after;
|
||||
item.next = after.next;
|
||||
after.next = item;
|
||||
if (item.next) item.next.prev = item;
|
||||
else m_last = item;
|
||||
}
|
||||
m_length++;
|
||||
}
|
||||
|
||||
void remove(T* item)
|
||||
{
|
||||
if (item.prev) item.prev.next = item.next;
|
||||
else m_first = item.next;
|
||||
if (item.next) item.next.prev = item.prev;
|
||||
else m_last = item.prev;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
m_length--;
|
||||
}
|
||||
|
||||
void removeFront()
|
||||
{
|
||||
if (m_first) remove(m_first);
|
||||
m_length--;
|
||||
}
|
||||
|
||||
void removeBack()
|
||||
{
|
||||
if (m_last) remove(m_last);
|
||||
m_length--;
|
||||
}
|
||||
|
||||
static struct Range {
|
||||
private T* m_first, m_last;
|
||||
|
||||
this(T* first, T* last)
|
||||
{
|
||||
m_first = first;
|
||||
m_last = last;
|
||||
}
|
||||
|
||||
@property bool empty() const { return m_first is null; }
|
||||
@property T* front() { return m_first; }
|
||||
@property T* back() { return m_last; }
|
||||
|
||||
void popFront() {
|
||||
assert(m_first !is null);
|
||||
m_first = m_first.next;
|
||||
if (!m_first) m_last = null;
|
||||
}
|
||||
|
||||
void popBack() {
|
||||
assert(m_last !is null);
|
||||
m_last = m_last.prev;
|
||||
if (!m_last) m_first = null;
|
||||
}
|
||||
}
|
||||
|
||||
Range opSlice() { return Range(m_first, m_last); }
|
||||
}
|
||||
|
||||
unittest {
|
||||
import std.algorithm.comparison : equal;
|
||||
import std.algorithm.iteration : map;
|
||||
|
||||
struct S { size_t i; S* prev, next; }
|
||||
|
||||
S[10] s;
|
||||
foreach (i, ref rs; s) rs.i = i;
|
||||
|
||||
StackDList!S list;
|
||||
|
||||
assert(list.empty);
|
||||
assert(list[].empty);
|
||||
list.insertBack(&s[0]);
|
||||
assert(list[].map!(s => s.i).equal([0]));
|
||||
list.insertBack(&s[1]);
|
||||
assert(list[].map!(s => s.i).equal([0, 1]));
|
||||
list.insertAfter(&s[2], &s[0]);
|
||||
assert(list[].map!(s => s.i).equal([0, 2, 1]));
|
||||
list.insertFront(&s[3]);
|
||||
assert(list[].map!(s => s.i).equal([3, 0, 2, 1]));
|
||||
list.removeBack();
|
||||
assert(list[].map!(s => s.i).equal([3, 0, 2]));
|
||||
list.remove(&s[0]);
|
||||
assert(list[].map!(s => s.i).equal([3, 2]));
|
||||
list.remove(&s[3]);
|
||||
assert(list[].map!(s => s.i).equal([2]));
|
||||
list.remove(&s[2]);
|
||||
assert(list.empty);
|
||||
assert(list[].empty);
|
||||
}
|
Loading…
Reference in a new issue