Unity3D游戏引擎“收益回报www”的基本机制


14

在Unity3D游戏引擎中,用于获取远程数据的常见代码序列如下:

WWW www = new WWW("http://remote.com/data/location/with/texture.png");
yield return www;

这里的潜在机制是什么?

我知道我们使用yield机制以便在下载完成时允许处理下一帧。但是,当我们这样做时,幕后到底是怎么回事yield return www

正在调用什么方法(如果有,在WWW类上)?Unity是否使用线程?是“上部” Unity层掌握了www实例并做了什么?

编辑:

  • 这个问题专门关于Unity3D内部。我对解释yield语句在C#中的工作方式不感兴趣。相反,我正在寻找有关Unity如何处理这些构造的内部视图,例如,允许WWW以分布式方式跨多个框架下载一条数据。

1
请注意,yield return用于异步操作是一种技巧。在“真实的” C#程序中,您将Task为此使用。Unity可能不使用它们,因为它是在Task引入.Net 4.0之前创建的。
BlueRaja-Danny Pflughoeft 2014年

Answers:


8

这是实际使用的C#yield关键字 -它对www对象没有做任何特殊的事情,而是对其包含的方法有特殊的意义。特别地,此关键字只能在返回IEnumerable(或IEnumerator)的方法中使用,并使用指示在调用MoveNext时枚举器将“返回”哪个对象。

之所以起作用,是因为编译器将整个方法转换为使用状态机实现IEnumerable(或IEnumerator)的单独类-最终结果是,直到有人通过返回值枚举时,方法本身的主体才被执行。这适用于任何类型,绝对没有什么特别的WWW,而是它的特殊的包含方法。

看看C#yield关键字的幕后花絮,以更深入地了解C#编译器生成的是哪种代码,或者只是使用IL Spy之类的东西自己进行实验和检查代码


更新:澄清

  • 当Unity调用包含一条yield return语句的协程时,所有发生的事情就是返回了一个枚举数-此时,方法体均未执行
  • 要使方法体执行,Unity必须调用MoveNext迭代器才能获得序列中的第一个值。这将导致该方法执行到第一个yeild return语句,然后调用者继续执行(大概是Unity继续渲染其余帧)
  • 据我了解,Unity通常会继续调用 MoveNext每个后续帧中一次在迭代器方法,从而导致该方法在yield return每个帧中再次执行直到下一个语句,直到yield break到达方法或语句的末尾(指示序列的结尾)

这里(并且在唯一的特殊位夫妇其他情况),是团结不进这个特殊的迭代器的下一个框架,而不是只前进迭代(其方法继续执行),当下载完成。尽管似乎确实存在一个基本的YieldInstruction类,该类大概包含一个通用机制,用于在应提高迭代器时向Unity发出信号,但WWW该类似乎没有继承自此类,因此我只能假设存在一个特殊情况Unity引擎中的此类。

请注意- yield关键字对WWW,而是Unity对返回的枚举的成员进行的特殊处理,从而导致这种行为。


更新第二个:至于用于WWW异步下载网页的机制,它可能使用HttpWebRequest.BeginGetResponse方法,该方法将在内部使用异步IO,或者可以使用线程(创建专用线程或使用线程池)。


5
实际上,在Unity中,生成WWW对象时会发生一些特殊的事情,请参见WWW参考
埃里克(Eric)

9

yield似乎主要用于协程环境中的Unity。要了解有关协程以及它们为什么使用C#的更多信息,yield我推荐以下博客文章: Unity3D协程详细信息。该答案的大部分研究都来自该文章。

Unity中的协程用于封装以下任务:

  1. 可能要花费比渲染一帧更长的时间(从而导致速度变慢),并且
  2. 可以与游戏循环分开执行(因为结果不需要用于当前帧)。

这些任务的示例包括寻路(重新)计算,或者像您所遇到的问题一样,从网站获取数据。

要回答您的子问题(以稍微修改的顺序):

正在调用什么方法(如果有,在WWW类上)?是“上部” Unity层掌握了www实例并做了什么?

Unity的WWW类旨在从协程中产生。根据上面链接的博客文章的评论,关于YieldInstructions 的推测性代码块(“上层”)实际上包含一个开关,该开关也检查产生的WWWs。然后,此代码可确保协程在下载完成后自动完成,如WWW参考资料中所述

Unity是否使用线程?

在这种情况下,“在不阻止游戏其余部分的情况下”下载数据:是的,最有可能的。(如所示,线程肯定用于解压缩下载的数据WWW.threadPriority。)


真好!我看到了altdevblogaday.com/2011/07/07/unity3d-coroutines-in-detail/…的评论,并且似乎对WWW进行了特殊处理。因此,我想知道这是如何完成的,以便可以在不使用线程的情况下跨多个框架进行下载?
thyandrecardoso 2012年

好点子!我想毕竟必须在该级别进行一些线程处理,我将编辑答案以反映这一点。
埃里克

1
@thyandrecardoso我猜想它使用HttpWebRequest.BeginGetResponse或类似的东西,但是如果对您来说真的很重要,您可以反编译该程序集以确认这一点。
贾斯汀

现在,我真的只在等待“最佳解释” ...如果Unity开发团队的任何人给出“正确答案”,那将是很棒的。8-)...最终,我认为真正的实现不会与这里已经给出的内容相去甚远...我实际上并不需要反编译程序集,但可以肯定地了解所有这些内容。但我可能稍后再试:)
thyandrecardoso 2012年

7

不幸的是,WWW在内部作为本机代码实现,这意味着我们无法查看该代码。从实验中我可以说

  1. WWW不是来源于YieldInstruction,所以无论发生什么事,当你yield必须通过特殊代码来处理。
  2. 我从来没有观察到之间的任何区别

    yield return www;

    while(!www.isDone)
        yield return null;

    我认为这是实现它的最合乎逻辑的方式,而且很有可能就是幕后故事。但是我不确定。

  3. Unity 至少在某些平台(iOS,网络播放器)上不会启动新的下载线程。否则,它将WWW.isDone在主线程上设置。我知道这是因为此代码:

    while(!www.isDone)
        Thread.Sleep(500);

    不起作用。

我认为,除非有人可以访问Unity3d的源代码,否则您将没有更具体的答案。


对。真不错的见解!谢谢!即使本身不启动线程,也可能发生的最可能的事情是使用HttpWebRequest.BeginGetResponse(或类似的东西)的WWW(或引擎层)...对吗?无论哪种方式,都必须发生完全异步的事情……无法“暂停”下载。
thyandrecardoso 2012年

**不能在帧之间“暂停”,我的意思是。
thyandrecardoso 2012年

2

由于Unity3D使用C#作为其脚本引擎,因此我假设它是C#中内置的标准yield关键字。基本上,这意味着它已经返回www的值,以便您可以继续进行,而下一次迭代将返回下一个值,等等。。。Yield基本上在后台创建了一个状态机和迭代器。


是的,我想我对yield关键字有一些基本概念。但是,在允许该“状态机”的WWW的构造函数中发生了什么?我的意思是,“ yield return www”似乎没有在WWW类中调用任何东西……当您说“这意味着它已经返回www的值”时,www实例的值是多少?该实例在下一个“迭代”中将做什么?
thyandrecardoso 2012年

1
在Unity协程中,产生WWW是特例,请参见WWW参考资料。此外,我不确定yield会创建任何东西。通过实现迭代器上下文IEnumerable或将其用作返回类型来创建迭代器上下文。“状态机”似乎也关闭了。当然,有状态,但是光是状态还不够,对吗?也许您可以对此进行详细说明。
埃里克(Eric)

1
这是正常C#行为的良好参考。shadowcoding.blogspot.nl/2009/01/yield-and-c-state-machine.html。Yield生成大量代码来跟踪它在迭代器中的位置。关于Unity产生WWW的特殊情况,我不知道,但是根据文档,它与普通的C#关键字没有任何关系,这非常令人困惑,他们可能只是将其设为异步方法。
Roy T.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.