lua是实现热更新的主流技术,在lua中往往需要实现很多和时间相关的逻辑。比如延时多长时间调用,循环调用几次等功能。今天就和大家分享一下如何在lua中实现定时器的功能。
首先创建LuaTimer.cs定时器类,然后创建内部类Timer(存储定时器任务列表)和Wheel(管理Timer任务),如下:
class Timer { internal int sn; internal int cycle; internal int deadline; internal Func<int, bool> handler; internal bool delete; internal LinkedList<Timer> container; } class Wheel { internal static int dial_scale = 256; internal int head; internal LinkedList<Timer>[] vecDial; internal int dialSize; internal int timeRange; internal Wheel nextWheel; internal Wheel(int dialSize) { this.dialSize = dialSize; this.timeRange = dialSize * dial_scale; this.head = 0; this.vecDial = new LinkedList<Timer>[dial_scale]; for (int i = 0; i < dial_scale; ++i) { this.vecDial[i] = new LinkedList<Timer>(); } } internal LinkedList<Timer> nextDial() { return vecDial[head++]; } internal void add(int delay, Timer tm) { var container = vecDial[(head + (delay - (dialSize - jiffies_msec)) / dialSize) % dial_scale]; container.AddLast(tm); tm.container = container; } }
接下来在LuaTimer类中添加静态初始化方法init(),用来初始化Wheel类和列表,如下:
public static void init() { wheels = new Wheel[4]; for (int i = 0; i < 4; ++i) { wheels[i] = new Wheel(jiffies_msec * intpow(Wheel.dial_scale, i)); if (i > 0) { wheels[i - 1].nextWheel = wheels[i]; } } mapSnTimer = new Dictionary<int, Timer>(); executeTimers = new LinkedList<Timer>(); }
继续添加Tick方法,用来定时检查是否有计时任务,如下:
public static void Tick(float deltaTime) { nowTime += deltaTime; pileSecs += deltaTime; int cycle = 0; while (pileSecs >= jiffies_sec) { pileSecs -= jiffies_sec; cycle++; } for (int i = 0; i < cycle; ++i) { var timers = wheels[0].nextDial(); LinkedListNode<Timer> node = timers.First; for (int j = 0; j < timers.Count; ++j) { var tm = node.Value; executeTimers.AddLast(tm); node = node.Next; } timers.Clear(); for (int j = 0; j < wheels.Length; ++j) { var wheel = wheels[j]; if (wheel.head == Wheel.dial_scale) { wheel.head = 0; if (wheel.nextWheel != null) { var tms = wheel.nextWheel.nextDial(); LinkedListNode<Timer> tmsNode = tms.First; for (int k = 0; k < tms.Count; ++k) { var tm = tmsNode.Value; if (tm.delete) { mapSnTimer.Remove(tm.sn); } else { innerAdd(tm.deadline, tm); } tmsNode = tmsNode.Next; } tms.Clear(); } } else { break; } } } while (executeTimers.Count > 0) { var tm = executeTimers.First.Value; executeTimers.Remove(tm); if (!tm.delete && tm.handler(tm.sn) && tm.cycle > 0) { innerAdd(now() + tm.cycle, tm); } else { mapSnTimer.Remove(tm.sn); } } }
接下来添加可供lua调用的延时方法AddDelay,该方法有两个参数,第一个参数delay为延时多长时间调用第二个参数指定的方法。同时该方法返回一个int类型的id,该id方便删除指定的Timer任务。代码如下:
static int nextSn = 0; static int jiffies_msec = 20; static float jiffies_sec = jiffies_msec * .001f; static Wheel[] wheels; static float pileSecs; static float nowTime; static Dictionary<int, Timer> mapSnTimer; static LinkedList<Timer> executeTimers; public static int AddDelay(int delay, Action<int> handler) { return Add(delay, 0, (int sn) => { handler(sn); return false; }); } public static int Add(int delay, int cycle, Func<int, bool> handler) { Timer tm = new Timer(); tm.sn = fetchSn(); tm.cycle = cycle; tm.handler = handler; mapSnTimer[tm.sn] = tm; innerAdd(now() + delay, tm); return tm.sn; } static int intpow(int n, int m) { int ret = 1; for (int i = 0; i < m; ++i) ret *= n; return ret; } static void innerAdd(int deadline, Timer tm) { tm.deadline = deadline; int delay = Math.Max(0, deadline - now()); Wheel suitableWheel = wheels[wheels.Length - 1]; for (int i = 0; i < wheels.Length; ++i) { var wheel = wheels[i]; if (delay < wheel.timeRange) { suitableWheel = wheel; break; } } suitableWheel.add(delay, tm); } static void innerDel(Timer tm) { tm.delete = true; if (tm.container != null) { tm.container.Remove(tm); tm.container = null; } mapSnTimer.Remove(tm.sn); } static int now() { return (int)(nowTime * 1000); } static int fetchSn() { return ++nextSn; }
如果想要删除Timer任务,可以添加Delete方法,该方法需要一个参数为上文提到的Timer的id,代码如下:
internal static void del(int sn) { Timer tm; if (mapSnTimer.TryGetValue(sn, out tm)) { innerDel(tm); } } public static int Delete(int sn) { del(sn); return sn; }
这样定时器就完成了,一般lua热更我们都会有一个LuaManager管理类,在该的Awake方法中可以调用LuaTimer.init();进行定时器的初始化,在Update中调用LuaTimer.tick(Time.unscaledDeltaTime);进行实时校验是否有需要启动的任务。
接下来看下在lua脚本中如何添加一个定时器任务吧,导出wrap文件之后就可以调用到LuaTimer中的方法了,以下是一个案例,该案例等待三秒钟调用匿名函数中的业务代码,如下:
local timerTaskId = LuaTimer.AddDelay( 3000, function(args) //等待3秒要执行的代码 end )
以上就是关于lua中定时器的分享,大家可以一学习,共同进步哟。