Skip to content

Instantly share code, notes, and snippets.

@ialex32x
Last active July 24, 2019 03:03
Show Gist options
  • Save ialex32x/1da03e0311dc3a73f891589820fd3b6c to your computer and use it in GitHub Desktop.
Save ialex32x/1da03e0311dc3a73f891589820fd3b6c to your computer and use it in GitHub Desktop.
simple timingwheel in csharp
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1
{
internal class TimeHandle
{
public uint id;
public Action<uint> action;
public int deadline;
public bool deleted;
public WheelSlot slot;
}
internal class WheelSlot
{
private List<TimeHandle> _timers = new List<TimeHandle>();
public void Add(TimeHandle timer)
{
timer.slot = this;
_timers.Add(timer);
}
public bool Remove(TimeHandle timer)
{
var size = _timers.Count;
for (var i = 0; i < size; ++i)
{
var it = _timers[i];
if (it == timer)
{
timer.slot = null;
_timers.RemoveAt(i);
return true;
}
}
return false;
}
public void Collect(List<TimeHandle> cache)
{
var size = _timers.Count;
for (var i = 0; i < size; ++i)
{
var it = _timers[i];
it.slot = null;
cache.Add(it);
}
_timers.Clear();
}
}
internal class Wheel
{
private int _depth;
private int _jiffies;
private int _interval;
private int _timerange;
private int _index;
private WheelSlot[] _slots;
public int range { get { return _timerange; } }
public Wheel(int depth, int jiffies, int interval, int slots)
{
_depth = depth;
_index = 0;
_jiffies = jiffies;
_interval = interval;
_timerange = _interval * slots;
_slots = new WheelSlot[slots];
for (var i = 0; i < slots; i++)
{
_slots[i] = new WheelSlot();
}
var united = (float)_timerange / 1000f;
var repr = string.Empty;
if (united < 60)
{
repr = united + "s";
}
else if (united < 60 * 60)
{
repr = (united / 60) + "m";
}
else if (united < 60 * 60 * 24)
{
repr = (united / (60 * 60)) + "h";
}
else
{
repr = (united / (60 * 60 * 24)) + "d";
}
Console.WriteLine($"[init] wheel#{_depth} scale: {_interval} range: {_timerange} ({repr})");
}
public int Add(int delay, TimeHandle timer)
{
var offset = (delay - (_interval - _jiffies)) / _interval;
var index = (_index + offset) % _slots.Length;
_slots[index].Add(timer);
return index;
}
public void Collect(List<TimeHandle> cache)
{
_slots[_index].Collect(cache);
}
public bool Tick()
{
++_index;
if (_index == _slots.Length)
{
_index = 0;
return true;
}
return false;
}
}
public class Timer
{
private Scheduler _scheduler;
private uint _id;
private bool _enabled;
private int _repeat;
private int _interval;
private Action<Timer> _fn;
public int repeat { get { return _repeat; } }
public Action<Timer> callback
{
get { return _fn; }
set { _fn = value; }
}
public Timer(Scheduler scheduler, int interval, Action<Timer> fn, int repeat)
{
_scheduler = scheduler;
_interval = interval;
_fn = fn;
_repeat = repeat;
_enabled = false;
}
private void OnTick(uint id)
{
_id = 0;
--_repeat;
_fn(this);
if (_repeat != 0 && _enabled)
{
_id = _scheduler.Add(_interval, OnTick);
}
}
public bool enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
_scheduler.Remove(_id);
_id = 0;
if (value)
{
_id = _scheduler.Add(_interval, OnTick);
}
}
}
}
}
public class Scheduler
{
private List<TimeHandle> _pool = new List<TimeHandle>();
private Dictionary<uint, TimeHandle> _timers = new Dictionary<uint, TimeHandle>();
private Wheel[] _wheels;
private int _timeslice;
private int _elapsed;
private int _jiffies;
private uint _idgen;
private List<TimeHandle> _tcache1 = new List<TimeHandle>();
private List<TimeHandle> _tcache2 = new List<TimeHandle>();
public Scheduler(int jiffies = 1, int slots = 200, int depth = 5)
{
_jiffies = jiffies;
_wheels = new Wheel[depth];
for (int i = 0; i < depth; i++)
{
int interval = 1;
for (var j = 0; j < i; j++)
{
interval *= slots;
}
_wheels[i] = new Wheel(i, jiffies, jiffies * interval, slots);
}
}
private void Rearrange(TimeHandle timer)
{
var delay = Math.Max(0, timer.deadline - _elapsed);
var wheelCount = _wheels.Length;
for (var i = 0; i < wheelCount; i++)
{
var wheel = _wheels[i];
if (delay < wheel.range)
{
wheel.Add(delay, timer);
return;
}
}
_wheels[wheelCount - 1].Add(delay, timer);
}
private TimeHandle GetTimeHandle(uint id, int delay, Action<uint> fn)
{
var available = _pool.Count;
TimeHandle timer;
if (available > 0)
{
timer = _pool[available - 1];
_pool.RemoveAt(available - 1);
}
else
{
timer = new TimeHandle();
}
timer.id = id;
timer.deadline = delay + _elapsed;
timer.action = fn;
timer.deleted = false;
timer.slot = null;
return timer;
}
public int now
{
get { return _elapsed; }
}
public Timer CreateTimer(int interval, Action<Timer> fn, int repeat)
{
var timer = new Timer(this, interval, fn, repeat);
timer.enabled = true;
return timer;
}
public uint Add(int delay, Action<uint> fn)
{
if (delay < 0)
{
delay = 0;
}
var id = ++_idgen;
var timer = GetTimeHandle(id, delay, fn);
_timers[id] = timer;
Rearrange(timer);
return id;
}
public void Remove(uint id)
{
if (id > 0)
{
TimeHandle timer;
if (_timers.TryGetValue(id, out timer))
{
_timers.Remove(id);
timer.deleted = true;
if (timer.slot != null)
{
timer.slot.Remove(timer);
timer.slot = null;
}
_pool.Add(timer);
}
}
}
public void Tick(int ms)
{
_elapsed += ms;
_timeslice += ms;
while (_timeslice >= _jiffies)
{
// console.log(`[schedule] @${this.elapsed}`);
_timeslice -= _jiffies;
var wheelIndex = 0;
// console.log(`[schedule.wheel#${wheelIndex}] slot ${this._wheels[wheelIndex].index} @${this.elapsed}`)
_wheels[wheelIndex].Collect(_tcache1);
if (_wheels[wheelIndex++].Tick())
{
while (wheelIndex < _wheels.Length)
{
// console.log(`[schedule.wheel#${wheelIndex}] slot ${this._wheels[wheelIndex].index} @${this.now}`)
_wheels[wheelIndex].Collect(_tcache2);
if (_tcache2 != null)
{
for (var i = 0; i < _tcache2.Count; ++i)
{
var timer = _tcache2[i];
Rearrange(timer);
}
}
_tcache2.Clear();
if (!_wheels[wheelIndex++].Tick())
{
break;
}
}
}
if (_tcache1 != null)
{
for (var i = 0; i < _tcache1.Count; ++i)
{
var timer = _tcache1[i];
var handler = timer.action;
Remove(timer.id);
// console.log(`[timer#${timer.id}] active`);
handler(timer.id);
}
}
_tcache1.Clear();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment