Last active
July 24, 2019 03:03
-
-
Save ialex32x/1da03e0311dc3a73f891589820fd3b6c to your computer and use it in GitHub Desktop.
simple timingwheel in csharp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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