C#协程和Lua协程的交互原理

C#协程和Lua协程的交互原理

在Unity游戏开发中,C#语言+lua语言是一种常见的开发模式。本文主要研究下C#协程和lua协程的交互的基本原理。

C#的协程

  1. C#协程本质上是一个迭代器(后面可能再补充)
  2. yield return是C#提供的语法糖,利用这个语法糖可以不显式地定义迭代器。
  3. C#的协程是非抢占式的,需要主动调用迭代器的MoveNext方法,在Unity中则是引擎负责调用。
  4. 在Unity中,协程的调用由引擎管理。开启一个协程时将对应迭代器加入队列中,每一帧时检查迭代器的Current变量是否满足条件(比如定时结束、文件下载完成),如果满足则调用MoveNext方法,协程即往下执行。

lua的协程

  1. lua的协程本质上是一个函数
  2. lua的协程也是非抢占式的。调用coroutine.resume()恢复协程,coroutine.yield()挂起协程。
  3. lua的协程管理需要自己处理,一般是一个主协程+n个次协程,大多数时间在主协程上执行。

在lua中,结合C#协程实现定时功能

上代码
main.lua

local yield_return = (require "util.cs_coroutine").yield_return

function Test1()
	local co = coroutine.wrap(function()
		for i = 1, 10 do
			yield_return(CS.UnityEngine.WaitForSeconds(1))  -- 定时1s
			print(i)
		end
	end)
	co()  -- 开启协程
end

function Test2()
    local co = coroutine.create(function()
        print('coroutine start!')
        local s = os.time()
        yield_return(CS.UnityEngine.WaitForSeconds(3))  -- 定时3s
        print('wait interval:', os.time() - s)
 
        local www = CS.UnityEngine.WWW('http://www.u3d8.com')
        yield_return(www)  -- 等待文件下载完成
        if not www.error then
            print(www.bytes)
        else
            print('error:', www.error)
        end
    end)
 
    coroutine.resume(co)
end

Test1()
Test2()

cs_coroutine.lua

local util = require 'util.luautil'
 
local gameobject = CS.UnityEngine.GameObject('Coroutine_Runner')
CS.UnityEngine.Object.DontDestroyOnLoad(gameobject)
local cs_coroutine_runner = gameobject:AddComponent(typeof(CS.Coroutine_Runner))
 
local function async_yield_return(to_yield, cb)  -- async_yield_return是异步的
    cs_coroutine_runner:YieldAndCallback(to_yield, cb)
end
 
return {
	-- yield_return是同步的(看上去是同步的)
	yield_return = util.async_to_sync(async_yield_return)
}

luautil.lua

--传入一个async方法
local function async_to_sync(async_func, callback_pos)
    --返回一个sync方法
    return function(...)
        local _co = coroutine.running() or error ('this function must be run in coroutine')
        local rets
        local waiting = false
        --定义 完成时的回调
        local function cb_func(...)
            if waiting then
                --等待后回调的情况
                --从挂起的位置恢复协程,并传递结果
                assert(coroutine.resume(_co, ...))
            else
                --未等待就直接回调的情况,直接设置结果
                rets = {...}
            end
        end
         
        --参数列表 把 “回调” 加入参数列表(默认加入最后)
        local params = {...}
        table.insert(params, callback_pos or (#params + 1), cb_func)
 
        --调用异步方法,把 “通过同步方法传入的参数” 和 “回调” 全部传给异步方法。
        async_func(unpack(params))
 
        --如果没有立即返回,则标记为等待并挂起
        if rets == nil then
            waiting = true
            --挂起! "coroutine.yield()" 的返回值为 coroutine.resume 传入的第二个参数
            rets = {coroutine.yield()}
        end
        
        --将结果同步返回
        return unpack(rets)
    end
end

Coroutine_Runner.cs

using UnityEngine;
using System.Collections;
using System;
 
public class Coroutine_Runner : MonoBehaviour
{
    public void YieldAndCallback(object to_yield, Action callback)
    {
        StartCoroutine(CoBody(to_yield, callback));  //开启C#协程
    }
 
    private IEnumerator CoBody(object to_yield, Action callback)
    {
    	//to_yield就是lua中yield_return函数的参数
    	//在Test1()例子中就是CS.UnityEngine.WaitForSeconds(1)
        if (to_yield is IEnumerator)
            yield return StartCoroutine((IEnumerator)to_yield);
        else
            yield return to_yield;
        callback();  //协程结束后调用回调,就是上面lua中的cb_func,恢复lua协程
    }
}

总的来说,基本原理就是:
在创建一个lua协程,异步操作开始后将lua协程挂起,等异步操作完成调用回调方法后恢复lua协程。异步操作在C#协程中。变异步调用同步等待

带来的好处就是代码看起来同步了。当然缺点也是显而易见的,每一次调用yield_return函数都会创建一个C#协程;创建协程(无论lua还是C#)都会造成gc;而且创建的C#协程还不能主动中断。

lua协程替换C#协程

上调用代码
main.lua

function Test()
    local start = (require "util.cs_coroutine").start
    start(function()
		print('coroutine start!')
        local s = os.time()
        coroutine.yield(CS.UnityEngine.WaitForSeconds(3))  -- 定时3s
        print('wait interval:', os.time() - s)
 
        local www = CS.UnityEngine.WWW('http://www.u3d8.com')
        coroutine.yield(www)  -- 等待文件下载完成
        if not www.error then
            print(www.bytes)
        else
            print('error:', www.error)
        end
	end)
end

Test()

cs_couroutine.lua

local util = require 'util.luautil'

local gameobject = CS.UnityEngine.GameObject('Coroutine_Runner')
CS.UnityEngine.Object.DontDestroyOnLoad(gameobject)
local cs_coroutine_runner = gameobject:AddComponent(typeof(CS.Coroutine_Runner))

return {
    start = function(...)
        return cs_coroutine_runner:StartCoroutine(util.cs_generator(...))
    end;

    stop = function(coroutine)
        cs_coroutine_runner:StopCoroutine(coroutine)
    end
}

cs_coroutine_runner:StartCoroutine()需要传入一个IEnumerator参数

public interface IEnumerator
{
	object Current {get;}
	bool MoveNext();
	void Reset();
}

所以要在lua侧构造一个类IEnumerator
luautil.lua

local move_end = {}

local generator_mt = {
    __index = {
        MoveNext = function(self)
	        -- 每次C#侧调用MoveNext时,调用lua侧的coroutine.resume()恢复协程,
	        -- 通过coroutine.yield或者return返回Current
            self.Current = self.co()  
            if self.Current == move_end then  -- lua协程已结束
                self.Current = nil
                return false
            else
                return true
            end
        end;
        Reset = function(self)
            self.co = coroutine.wrap(self.w_func)  -- 创建lua协程
        end
    }
}

local function cs_generator(func, ...)
    local params = {...}
    local generator = setmetatable({
        w_func = function()
            func(unpack(params))
            return move_end  -- lua协程结束时返回move_end
        end
    }, generator_mt)
    generator:Reset()
    return generator
end

return {
	cs_generator = cs_generator
}

与上面的方法相比,在lua侧直接构造C#协程,无论协程多复杂(coroutine.yield的次数),都只有一个lua协程和一个C#协程,而且C#协程可以主动中断。

总结

Unity的C#协程提供了处理异步操作的一种方法,可以使代码看起来更连贯,易于理解。通过一定的交互手段,结合lua的协程,可以在lua侧实现相同的效果。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值