Unity's coroutine and Lua's coroutine

This article shares Unity's coroutines and Lua's coroutines

Coroutine plays an important role in our game development.

The full name of coroutine is coroutine. Unlike threads, coroutines are executed attached to the main thread, which is equivalent to seizing a certain execution time slice on the main thread.

It can also be understood as opening up a separate call stack outside the call of the main thread, and can save internal information and share common information with other coroutines before the coroutine dies.

The essence of the coroutine is to run on the main thread, the code is still executed synchronously, but at some point it is suspended and then the execution of other codes of the main thread is resumed, and then when re-entering the coroutine, it can continue to execute from the point of departure. This point and recursion Somewhat similar.

The core concepts of the coroutine are suspend and resume . Suspend means that the execution stops after reaching a certain position, until it is resumed externally.

Only one coroutine can be running at the same time, other coroutines need to be suspended.

Coroutines have the following main uses:

  • Synchronous writing of asynchronous logic
  • Non-recursive writing of recursive code

There are some differences between Unity and Lua in the realization of the concept of coroutines, and I will briefly share them with you below.

Coroutines in Unity

Unity uses C# iterators and the yield keyword to implement coroutines.

The realization principle is as follows:

  • method returns an iterator ( IEnumerator )

  • Use the yield keyword to add an iterable object to the iterator each time ( null or inherit from the YieldInstruction class)

  • Yield break or end the coroutine after the method is executed

  • Start or close the coroutine through the methods StartCoroutine and StopCoroutine

  • The iterable objects here will be added to the life cycle of Update, FixedUpdate, etc.

void Start()
{
	var cor = StartCoroutine(Example());
	StopCoroutine(cor);
}

IEnumerator Example()
{
    print(Time.time);
    yield return new WaitForSeconds(5);
    print(Time.time);
}

Iterable objects used in conjunction with yield

When yield is called, some available iteration objects can be returned to perform predefined behaviors, and of course, custom objects can also be used.

In addition to returning null, other classes are inherited from the YieldInstruction class, and custom classes can also be inherited from CustomYieldInstruction to define their own coroutine logic. We will share these two classes separately in another article. This article is just a simple one. Introduction and comparison with Lua's coroutines.

yield return null

When returning null, Unity will resume the coroutine between Update and LateUpdate of the next frame.

The following code will print "Test" every frame:

IEnumerator Example()
{
	while(true)
	{
		Debug.Log("Test")
		yield return null;
	}
}

Of course, it is also possible to return a number, for example yield return 0; yield return 1, but regardless of returning null or a number, it is only delayed by one frame, that is, a "yield return null or number" call is delayed by one frame, and multiple calls are required if multiple frames need to be delayed.

yield return new WaitForEndOfFrame()

As the name implies, it is called at the end of this frame.

That is, after all cameras and GUI are rendered, but before the frame is displayed on the screen.

Sometimes this call is very convenient when it is necessary to save a screenshot of the content to be displayed in this frame.

yield return new WaitForFixedUpdate()

As the name suggests, on the next FixedUpdate call.

yield return new WaitForSeconds(time)

Starting from the end of the current frame, wait for a certain amount of time before continuing execution.

This time is affected by Time.timeScale , which may be inconsistent with the actual time.

Also note that this call starts from the end of the current frame, if the current frame executes the time process, then the actual waiting time will be inconsistent with the set time.

yield return new WaitForSecondsRealtime(time)

Same as WaitForSeconds, but using unscaled time ( Time.unscaledTime ), which can keep the time as accurate as possible.

We will share other classes inherited from CustomYieldInstruction in another article.


Coroutines in Lua

Now many large-scale projects will be developed using the combination of C# and Lua, and Lua also has the concept of coroutines, let's share it below.

The coroutine in Lua is handled by a special package (Package), namely "coroutine".

As follows:

local cor = coroutine.create(function()
    print("2222")
    local result = coroutine.yield()
    print(result)

    print("4444")
end)

print("1111")
coroutine.resume(cor)

print("33333")
coroutine.resume(cor, "#####")

print("55555")
coroutine.resume(cor)

-- 会打印:
-- 1111
-- 2222
-- 3333
-- #####
-- 4444
-- 55555

Use coroutine.createto create a coroutine, and use coroutine.resumeto start, until coroutine.yield()the coroutine is encountered, it will suspend the coroutine, waiting for external activation.

You can coroutine.resume(cor, "xxxx")pass variables to the coroutine, so you can do some selection logic inside the coroutine, such as completely stop the coroutine running: return.

other interface

Other interfaces, such as coroutine.wrapthe ability to start the coroutine immediately when it is created, coroutine.statusto view the status of the coroutine, etc., interested students can learn by themselves.

Similarities and differences with Unity coroutines

Same point

  • Because they are all coroutines, the core concept is that there are suspend and resume operations
  • The pending operations are the same, and they all use the concept of "yield" (that is, the meaning of giving up execution permissions)
  • Both can pass data between the main thread and the coroutine

difference

  • The main difference is that the recovery operation of Unity's coroutine is controlled by Unity, which only provides you with some preset behaviors and interfaces that provide custom behaviors
  • Since Lua has no main loop, the recovery of the coroutine needs to be manually recovered in the main thread
  • Lua does not provide some preset behaviors. It is more flexible to use, but lacks convenience. Some common behaviors, such as delay, need to be implemented with interfaces such as Unity's Update

Example sharing

Some usage examples of coroutines are shared below.

1. Synchronous writing of asynchronous code

In game development, asynchronous code appears frequently, such as requesting server messages, asynchronously loading resources, etc.

When requesting server messages, it is often necessary to call messages sequentially, but because sending messages is an asynchronous process, the request for the next message can only be processed in the callback of the previous message.

This kind of code can be written approximately synchronously using coroutines.

It looks like this (Lua):

NetMgr.SendMsg("msg1", function() 
	NetMgr.SendMsg("msg2", function() 
        NetMgr.SendMsg("msg3", function() 
            NetMgr.SendMsg("msg4", function() 
    			do xxxxxxxxxxxxxxxxxxxx return end
            end)
		end)
    end)
end)
-------------------------------------------------------------------
coroutine.wrap(function() 
	NetMgr.SendMsg("msg1", function() 
		coroutine.resume()
	end)
	
	coroutine.yield()
    NetMgr.SendMsg("msg2", function() 
		coroutine.resume()
	end)
	
    coroutine.yield()
    NetMgr.SendMsg("msg3", function() 
		coroutine.resume()
	end)
	
    coroutine.yield()
    NetMgr.SendMsg("msg4", function() 
		coroutine.resume()
	end)
	
    coroutine.yield()
    do xxxxxxxxxxxxxxxxxxxx return end
end)

To switch scenes asynchronously, the code is as follows (C#):

private void OnClick()
{
	StarCoroutine(LoadScene(sceneName));
}

private IEnumerator LoadScene(sceneName)
{
	var result = SceneManagement.SceneManager.LoadSceneAsync(sceneName);
	while(not result.isDone)
	{
		yield return null;
	}
	
	Debug.Log("创建切换成功!");
}

2. Non-recursive writing of recursive code

Some logic needs to use recursion, such as the most common Fibonacci sequence.

It looks like this (C#):

public IEnumerable Fib(int total)
{
	int pre = 0, cur = 1;
	for (int count = 0; count < total; ++count)
	{
		yield return pre;
		
		int newCur = pre + cur;
		pre = cur;
		cur = newCur;
	}
}

void Main()
{
	foreach(var itor in Fib(10))
	{
		Console.WriteLine(itor.ToString());
	}
}

Guess you like

Origin blog.csdn.net/woodengm/article/details/119255321#comments_27849981