xlua - lua协程模拟Unity协程

目的:能在lua中像下面这样方便的使用协程
public class CoTest : MonoBehaviour
{
    void Start()
    {
        var enu = MyCo();
        StartCoroutine(enu);
    }

    IEnumerator MyCo()
    {
        Debug.Log($"frame 1: {Time.frameCount}");
        yield return null;
        Debug.Log($"frame 2: {Time.frameCount}");
        Debug.Log($"time 1: {Time.time}");
        yield return new WaitForSeconds(1.2f);
        Debug.Log($"time 2: {Time.time}");
    }
}

 

 
Assets/Lua/LuaCoMgr.lua.txt
local _Time = CS.UnityEngine.Time

local Mgr = {}
LuaCoMgr = Mgr

local Need_Wait_Flag = {}

local Inner = {}

local m_IdIcr = 0
local m_RunningCos = {}

function Mgr.Start(func, args)
    local co = coroutine.create(func)
    m_IdIcr = m_IdIcr + 1
    local coId = m_IdIcr

    local isSucc, yieldParam = coroutine.resume(co, args) --执行到第1个yield处暂停
    if not isSucc then
        if debug and debug.traceback then
            local strackTrace = debug.traceback(co)
            print("Mgr.Start: co first start err: "..strackTrace)
        else
            print("Mgr.Start: co first start err")
        end
        return
    end

    if "dead" ~= coroutine.status(co) then
        local coCtxt = {
            id = coId,
            co = co,
            lastResumeTime = _Time.time,
        }
        if args then
            coCtxt.desc = args.desc
        end
        coCtxt.IsWait = Inner.IsNeedWait(yieldParam)

        table.insert(m_RunningCos, coCtxt)
        return coId
    else
        --已执行结束
    end

    return 0
end

function Mgr.StopCoById(coId)
    for i, v in ipairs(m_RunningCos) do
        if v.id == coId then
            v.stopFlag = true
            return true
        end
    end
    return false
end

function Mgr.WaitForSeconds(sec)
    local endTime = _Time.time + sec
    local obj = {
        flag = Need_Wait_Flag,
        IsWait = function()
            local leftTime = endTime - _Time.time
            return (leftTime > 0)
        end,
    }
    coroutine.yield(obj) --在此处暂停, resume唤醒时将得到yield这边传的参数
end

function Mgr.WaitForFrames(frames)
    local endFrame = _Time.frameCount + frames
    local obj = {
        flag = Need_Wait_Flag,
        IsWait = function()
            local leftFrame = endFrame - _Time.frameCount
            return (leftFrame > 0)
        end,
    }
    coroutine.yield(obj) --在此处暂停, resume唤醒时将得到yield这边传的参数
end

function Inner.IsNeedWait(yieldParam)
    if nil == yieldParam or "table" ~= type(yieldParam) then
        return nil
    end
    if yieldParam.flag ~= Need_Wait_Flag then
        return nil
    end
    
    local isWait = yieldParam.IsWait
    if nil == isWait or "function" ~= type(isWait) then
        return nil
    end
    return isWait
end

function Inner.ResumeCo(i, coCtxt)
    local isSucc, yieldParam = coroutine.resume(coCtxt.co)
    if not isSucc then
        --协程执行失败?
        if debug and debug.traceback then
            local strackTrace = debug.traceback(coCtxt.co)
            print("co resume fail: id:"..coCtxt.id..", "..strackTrace)
        else
            print("co resume fail: id:"..coCtxt.id)
        end
        table.remove(m_RunningCos, i)
    else
        if "dead" == coroutine.status(coCtxt.co) then
            print("co finish: id:"..coCtxt.id)
            table.remove(m_RunningCos, i)
        else
            coCtxt.IsWait = Inner.IsNeedWait(yieldParam) --resume后可能还要wait
        end
    end
end

function Mgr.OnUpdate()
    local curTime = _Time.time
    
    for i=#m_RunningCos,1,-1 do
        local coCtxt = m_RunningCos[i]

        if coCtxt.stopFlag or "dead" == coroutine.status(coCtxt.co) then
            table.remove(m_RunningCos, i)
        else
            local isWait = coCtxt.IsWait
            if isWait and isWait() then
                --wait
                local deltaTime = curTime - coCtxt.lastResumeTime
                if deltaTime >= 30 then
                    print("!!!!!! co wait too long: id:"..coCtxt.coId..", time:"..deltaTime)
                end
            else
                coCtxt.lastResumeTime = curTime
                Inner.ResumeCo(i, coCtxt)
            end
        end
    end
end

 

lua测试代码: Assets/Lua/Test5.lua.txt

require("Lua.LuaCoMgr")
local _Time = CS.UnityEngine.Time;

LuaCoMgr.Start(function()
    print("frame 1:".._Time.frameCount)
    LuaCoMgr.WaitForFrames(1)
    print("frame 2:".._Time.frameCount)
    print("time 1:".._Time.time)
    LuaCoMgr.WaitForSeconds(1.2)
    print("time 2:".._Time.time)
end)

 

c#部分: Assets/Test5/Test5.cs

public class Test5 : MonoBehaviour
{
    private LuaEnv m_LuaEnv;

    private LuaTable m_LuaCoMgr;
    private LuaFunction m_LuaCoMgr_OnUpdate;

    void Start()
    {
        m_LuaEnv = new LuaEnv();
        m_LuaEnv.AddLoader((ref string filePath) =>
        {
            filePath = filePath.Replace('.', '/');
            filePath = $"Assets/{filePath}.lua.txt";
            
            Debug.Log($"filePath:{filePath}");
            var txtAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(filePath);
            return Encoding.UTF8.GetBytes(txtAsset.text);
        });

        m_LuaEnv.Global.Set("Test5MonoInst", this); //在lua中增加一个全局变量,可以在lua中用TestMonoInst来访问c#对象
        m_LuaEnv.DoString("require('Lua.Test5')");
        m_LuaCoMgr = m_LuaEnv.Global.Get<LuaTable>("LuaCoMgr");
        m_LuaCoMgr_OnUpdate = m_LuaCoMgr.Get<LuaFunction>("OnUpdate");
    }

    void Update()
    {
        m_LuaCoMgr_OnUpdate.Action(0);
    }

    void OnDestroy()
    {
        m_LuaCoMgr = null;
        m_LuaCoMgr_OnUpdate = null;
        if (null != m_LuaEnv)
        {
            m_LuaEnv.Dispose();
        }
    }
    
}

 


 
posted @ 2025-07-01 00:13  yanghui01  阅读(40)  评论(0)    收藏  举报