Interaction principle of C# synergy and Lua synergy
In Unity game development, C# language + lua language is a common development mode. This paper mainly studies the basic principle of the interaction between C# coprocess and lua coprocess.
C #'s collaborative process
- C # coprocessor is essentially an iterator. (may be added later)
- yield return is a syntax sugar provided by C# that allows you to define iterators without explicitly.
- C #'s coroutine is non preemptive. It needs to actively call the MoveNext method of the iterator. In Unity, the engine is responsible for calling it.
- In Unity, the invocation of the coroutine is managed by the engine. When a coroutine is started, the corresponding iterator is added to the queue. At each frame, check whether the Current variable of the iterator meets the conditions (such as timing end and file download completion). If so, call MoveNext method and the coroutine will be executed downward.
lua's co process
- The covariance of lua is essentially a function.
- lua's cooperation process is also non preemptive. Call coroutine Resume() resume coroutine Yield() suspends the orchestration.
- lua's collaboration process management needs to be handled by itself. Generally, it is a main collaboration process + n sub collaboration processes. It is executed on the main collaboration process most of the time.
In lua, the timing function is realized in combination with C# co process
Upper code
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)) -- Timing 1 s print(i) end end) co() -- Start collaboration end function Test2() local co = coroutine.create(function() print('coroutine start!') local s = os.time() yield_return(CS.UnityEngine.WaitForSeconds(3)) -- Timing 3 s print('wait interval:', os.time() - s) local www = CS.UnityEngine.WWW('http://www.u3d8.com') yield_return(www) -- Wait for the file download to complete 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 It's asynchronous cs_coroutine_runner:YieldAndCallback(to_yield, cb) end return { -- yield_return Is synchronized (looks synchronized) yield_return = util.async_to_sync(async_yield_return) }
luautil.lua
--Pass in a async method local function async_to_sync(async_func, callback_pos) --Return a sync method return function(...) local _co = coroutine.running() or error ('this function must be run in coroutine') local rets local waiting = false --Define callbacks on completion local function cb_func(...) if waiting then --Callback after waiting --Recover the collaboration from the suspended position and pass the results assert(coroutine.resume(_co, ...)) else --If you call back directly without waiting, set the result directly rets = {...} end end --Parameter list add "callback" to the parameter list (add last by default) local params = {...} table.insert(params, callback_pos or (#params + 1), cb_func) --Call the asynchronous method and pass all the "parameters passed in through the synchronous method" and "callback" to the asynchronous method. async_func(unpack(params)) --If it does not return immediately, it is marked as waiting and suspended if rets == nil then waiting = true --Hang! "coroutine.yield()" The return value of is coroutine.resume The second parameter passed in rets = {coroutine.yield()} end --Sync results back 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)); //Start C# collaboration } private IEnumerator CoBody(object to_yield, Action callback) { //to_yield is the yield in lua_ Return function parameters //In the Test1() example, it is CS UnityEngine. WaitForSeconds(1) if (to_yield is IEnumerator) yield return StartCoroutine((IEnumerator)to_yield); else yield return to_yield; callback(); //The callback after the end of the association is the cb_ in the lua above. Func, restore lua synergy } }
In general, the basic principle is:
After creating a lua coroutine, suspend the lua coroutine after the asynchronous operation starts, and resume the lua coroutine after the asynchronous operation completes and calls the callback method. Asynchronous operation is in C# cooperation. The mutation step is called to wait for synchronization.
The benefit is that the code looks synchronized. Of course, the disadvantages are also obvious. Every time yield is called_ The return function will create a C# coroutine; Creating a collaboration (whether lua or C#) will cause gc; Moreover, the created C# collaboration cannot be interrupted actively.
lua coprocess replaces C# coprocess
Calling code
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)) -- Timing 3 s print('wait interval:', os.time() - s) local www = CS.UnityEngine.WWW('http://www.u3d8.com') coroutine.yield(www) -- Wait for the file download to complete 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() needs to pass in an ienumeror parameter
public interface IEnumerator { object Current {get;} bool MoveNext(); void Reset(); }
Therefore, we should construct an IEnumerator class on the lua side
luautil.lua
local move_end = {} local generator_mt = { __index = { MoveNext = function(self) -- every time C#When calling MoveNext on the side, call coroutine on the lua side Resume() resume the process, -- adopt coroutine.yield perhaps return return Current self.Current = self.co() if self.Current == move_end then -- lua The collaboration process has ended self.Current = nil return false else return true end end; Reset = function(self) self.co = coroutine.wrap(self.w_func) -- establish lua Synergetic process end } } local function cs_generator(func, ...) local params = {...} local generator = setmetatable({ w_func = function() func(unpack(params)) return move_end -- lua Return at the end of the collaboration move_end end }, generator_mt) generator:Reset() return generator end return { cs_generator = cs_generator }
Compared with the above method, the C# cooperation process is constructed directly on the lua side. No matter how complex the cooperation process is (the number of coroutine.yield), there is only one lua cooperation process and one C# cooperation process, and the C# cooperation process can be interrupted actively.
summary
Unity's C# coroutine provides a way to handle asynchronous operations, which can make the code look more coherent and easy to understand. The same effect can be achieved on the Lua side through certain interaction means and in combination with the collaborative process of lua.