twoapple-reboot/src/timer.d

353 lines
8.9 KiB
D

/+
+ timer.d
+
+ Copyright: 2012 Ed McCardell, 2007 Gerald Stocker
+
+ This file is part of twoapple-reboot.
+
+ twoapple-reboot is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ twoapple-reboot is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with twoapple-reboot; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+/
module timer;
final class Timer
{
private:
static struct Counter
{
uint start, curr;
bool active;
size_t next = -1, nextFree;
bool delegate() expiry;
ulong creationTick;
this(uint length, bool delegate() expiry, ulong creationTick)
{
start = curr = length;
this.expiry = expiry;
this.creationTick = creationTick;
active = true;
}
}
ulong _totalTicks;
uint start = uint.max, curr = uint.max, minCurr = uint.max;
uint balance;
size_t head, tail, nextFree;
Counter[] counters;
uint _hertz;
final void setNextFree()
{
for (size_t i = nextFree; i < counters.length; i++)
counters[i].nextFree = i + 1;
}
final void deleteCounter(size_t idx, size_t prev)
{
auto tmp = nextFree;
nextFree = idx;
counters[idx].nextFree = tmp;
auto next = counters[idx].next;
if (idx == head)
{
head = next;
}
else
{
counters[prev].next = next;
}
if (idx == tail)
{
tail = prev;
counters[tail].next = -1;
}
counters[idx].active = false;
}
public:
this(uint primaryLength, uint hertz, bool delegate() primaryStop)
{
counters = new Counter[50];
setNextFree();
_hertz = hertz;
addCounter(primaryLength, primaryStop);
}
final @property uint primaryRemaining()
{
return counters[0].curr;
}
final @property uint primaryLength()
{
return counters[0].start;
}
final @property uint hertz()
{
return _hertz;
}
final @property ulong totalTicks()
{
return _totalTicks;
}
final void tick()
{
_totalTicks++;
curr--;
if (!curr)
{
minCurr = uint.max;
size_t idx = head;
size_t prev = -1;
while (idx != -1)
{
if (counters[idx].active)
{
counters[idx].curr -= start;
if (counters[idx].curr) counters[idx].curr -= balance;
if (!counters[idx].curr)
{
if (counters[idx].expiry())
counters[idx].curr = counters[idx].start;
else
deleteCounter(idx, prev);
}
if (counters[idx].active && counters[idx].curr < minCurr)
minCurr = counters[idx].curr;
}
else
{
deleteCounter(idx, prev);
}
if (counters[idx].active) prev = idx;
idx = counters[idx].next;
}
start = curr = minCurr;
balance = 0;
}
}
final size_t addCounter(uint length, bool delegate() expiry)
{
if (nextFree == counters.length)
{
size_t idx = head;
size_t prev = -1;
while (idx != -1)
{
if (!counters[idx].active)
deleteCounter(idx, prev);
else
prev = idx;
idx = counters[idx].next;
}
}
if (nextFree == counters.length)
{
counters.length += 20;
setNextFree();
}
auto idx = nextFree;
nextFree = counters[nextFree].nextFree;
counters[idx] = Counter(length, expiry, _totalTicks);
counters[tail].next = idx;
tail = idx;
counters[tail].next = -1;
if (curr == 0)
{
counters[idx].curr += start;
}
else
{
if (counters[idx].curr < curr)
{
balance = start - curr;
start = curr = counters[idx].curr;
}
else
{
counters[idx].curr += (start - curr);
}
}
return idx;
}
final void removeCounter(ulong creationTick, size_t idx)
{
assert(counters[idx].creationTick == creationTick);
counters[idx].active = false;
}
final class Cycle
{
ulong startTick;
uint rollOver;
this(uint rollOver)
{
this.rollOver = rollOver;
restart();
}
final void restart()
{
startTick = _totalTicks;
}
final uint val()
{
return (_totalTicks - startTick) % rollOver;
}
}
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
int c1 = 0, c2 = 0;
t.addCounter(10, (){assert(t._totalTicks == 10); c1++; return true;});
foreach (i; 0..9) t.tick();
t.addCounter(5, (){assert(t._totalTicks == 14); c2++; return true;});
foreach (i; 0..5) t.tick();
assert (c1 == 1 && c2 == 1);
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
int c1 = 0, c2 = 0;
struct Dummy
{
bool exp1()
{
auto ticks = t._totalTicks;
assert((c1 == 0 && ticks == 10) || (c1 == 1 && ticks == 30));
c1++;
t.addCounter(10, &exp2);
return false;
}
bool exp2()
{
auto ticks = t._totalTicks;
assert((c2 == 0 && ticks == 20) || (c2 == 1 && ticks == 40));
c2++;
t.addCounter(10, &exp1);
return false;
}
}
Dummy d;
t.addCounter(10, &d.exp1);
foreach (i; 0..40) t.tick();
assert(c1 == 2 && c2 == 2);
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
int c1 = 0, c2 = 0, c3 = 0;
void addExtra()
{
t.addCounter(4, (){auto ticks = t._totalTicks;
assert((c1 == 0 && ticks == 14) ||
(c1 == 1 && ticks == 24));
c1++;
return false;});
t.addCounter(5, (){auto ticks = t._totalTicks;
assert((c2 == 0 && ticks == 15) ||
(c2 == 1 && ticks == 25));
c2++;
return false;});
t.addCounter(6, (){auto ticks = t._totalTicks;
assert((c3 == 0 && ticks == 16) ||
(c3 == 1 && ticks == 26));
c3++;
return false;});
}
t.addCounter(10, (){addExtra(); return true;});
foreach (i; 0..30) t.tick();
assert (c1 == 2 && c2 == 2 && c3 == 2);
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
auto c1 = t.addCounter(10, (){assert(false); return false;});
auto c1_tick = t._totalTicks;
foreach (i; 0..5) t.tick();
t.removeCounter(c1_tick, c1);
foreach (i; 0..5) t.tick();
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
int c1 = 0, c2 = 0;
t.addCounter(10, (){c1++; return true;});
auto idx = t.addCounter(15, (){c2++; return true;});
auto tick = t._totalTicks;
foreach (i; 0..20) t.tick();
assert(c1 == 2 && c2 == 1);
t.removeCounter(tick, idx);
foreach (i; 0..20) t.tick();
assert(c1 == 4 && c2 == 1);
}
unittest
{
bool primary() { return true; }
auto t = new Timer(10205, 1020484, &primary);
bool t1() { assert(!(t.totalTicks % 5000)); return true; }
bool t2() { assert(!(t.totalTicks % 4500)); return true; }
bool t3() { assert(!(t.totalTicks % 6500)); return true; }
bool junk() { assert(!((t.totalTicks - 7000) % 1500)); return true; }
t.addCounter(5000, &t1);
t.addCounter(4500, &t2);
t.addCounter(6500, &t3);
foreach (i; 0..7000) t.tick();
t.addCounter(1500, &junk);
t.addCounter(1500, &junk);
t.addCounter(1500, &junk);
t.addCounter(1500, &junk);
foreach (i; 0..7000) t.tick();
}