Lua定时器

2021-09-01 09:23发布

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中定时器的分享,大家可以一学习,共同进步哟。