2015年8月17日

使用slate的休眠和“激活的计时器”功能

作者 Dan Hertzka

Slate是一种“立即模式”的用户界面架构,这意味着它每帧会重新描画整个用户界面,这对于具有丰富图形及动画的动态界面是非常好的,但是当用户界面不需要任何修改时这会导致不必要的处理器占用。这通常也是启动程序和编辑器中所发生的情形。

该功能向Slate引入了一个新的“Active Timer(激活计时器)”系统,当没有用户界面需要更新时,它会进入“Sleep(休眠)”状态。

这个修改会影响您吗?

如果您正在制作编辑器或启动程序的的用户界面,那么这个修改会影响到您。如果您正在从实时视口中制作游戏用户界面,那么这个修改对您则没有影响。

Slate什么时候进入休眠状态?

当特定游戏满足以下两个条件时:

  • 没有用户操作。
  • 没有要执行的“激活的计时器”。

什么算“用户操作”?

鼠标移动、点击及按下键盘。

以下流程图解释了Slate应用程序每帧如何进行更新:

DH1

休眠有用吗?

确实有用。以下图表显示了结果:

 

DH2

什么是“激活的计时器”?

激活的计时器是一个控件显式注册的代理函数,一旦执行会导致Slate进行一遍 更新/描画,即使没有用户动作也会执行(因此这就是计时器的“激活”本性)。激活的计时器将以其执行周期确定的频率继续无限地执行,直到它取消注册为止。

这对旧版的Tick()有何影响?

旧版本的Tick功能仍然存在,但现在请把它当成“被动”更新。和以前一样,它将由Slate调用,但仅当Slate是唤醒状态的时候。我们仍在考虑是否或如何将其重命名为PassiveTick(),然后废弃Tick()。

我怎样才能实现一个激活的计时器?

注册-三步

  1. 使用这个签名定义一个函数:"EActiveTimerReturnType Foo(double InCurrentTime, float InDeltaTime)"。
  2. 将其绑定到FwidgetActiveTimerDelegate。
  3. 传入该代理及计时器执行时间周期(0表示每帧都调用)给SWidget::RegisterActiveTimer()函数。

 

取消注册 – 三种方法

  1. 从代理中返回EActiveTimerReturnType::Stop。
  2. 传入由SWidget::RegisterActiveTimer()返回的FActiveTimerHandle 给SWidget::UnRegisterActiveTick()。
  3. 销毁该“激活的计时器”注册到的控件。

 

当心重复的注册!

目前,对于Slate来说“激活的计时器”是一种极端的实现,这意味着如果一个激活的计时器需要执行,所有的Slate都将更新。进一步讲,就是对于一个控件可以同时注册的“激活的计时器”的数量没有限制。这可能非常有用,但也可能会导致重复注册。要想防止重复注册,请使用以下其中一种方法来跟踪注册状态:

  1. 在控件中保留一个标志来跟踪“激活的计时器”是否已经注册。
    1. 比如,搜索“bIsActiveTimerRegistered”。
  2. 存一个到由RegisterActiveTimer()返回的FActiveTimerHandle 的弱指针,仅当它无效时注册。
    1. 搜索“TWeakPtr<FActiveTimerHandle> ActiveTimerHandle”。
    2. 为了节约内存,仅当需要显式取消注册“激活的计时器”时才考虑这个问题。

 

常见用例

  • 触发某个操作
    • 以前,我们会存储一个标志,并在每次更新时判断它是否为“真”。
    • 现在,我们注册一个“激活的计时器”,并设置周期为0,这将总是返回EActiveTimerReturnType::Stop。
  • 执行不是由FcurveSequence控制的某种动画或插值(对于示例,请参照下方)。
    • 示例:惯性滚动 – 请参照SscrollBox
      • 当惯性滚动开始,注册一个“激活的计时器”并设置周期为0,来在每帧都更新滚动。
      • 直到目的地到达,返回EActiveTimerReturnType::Continue。
      • 一旦到达目标目的地,则返回EActiveTimerReturnType::Stop来取消注册。
  • 一段延迟后进行动作。
    • 示例:打开另一个子菜单,停靠一个选卡。
      • 使用一个正值的非零延迟注册。
      • 一旦注册,“激活的计时器”上的周期就不能再修改。
        • 要想在执行之前重置该延迟,则必须显式地取消注册该“激活的注册器”,然后重新注册。
  • 定期执行一个动作及无限地执行一个动作。
    • 使用非零正值周期注册,保持返回EActiveTimerReturnType::Continue。

更新FCurveSequence API

FCurveSequence 的API已经更新,当控件正在进行动画时简化注册更新的过程。这里是一些修改:

  • 播放序列现在需要一个到它正在使其进行动画的控件的引用。
    • 会自动为传入的控件注册一个空的激活更新,持续时间为该序列的播放时间。
  • 现在当播放该序列时指定该序列是否循环。
    • 这样做将会无限期地注册该激活的更新,所以一定要小心该处理!
  • 无论是否循环,当调用Pause()、JumpToStart()或JumpToEnd()时,“激活的更新”将会取消注册。

一切都能正常运作吗?

是的!…我们认为是的。激活的更新系统位于Main函数中,所有需要一个更新系统的编辑器控件(我可以找到大约90个)都进行了相应更新。一定会有一些负面影响,但在编辑器中我们有两个参数可以处理一些潜在问题。

  1. Slate.AllowSlateToSleep控制Slate是否可以进入到休眠状态,默认启用。
  2. Slate.SleepBufferPostInput指出了自上次用户操作后Slate继续保持唤醒状态多长时间。该值默认为0,目前仅用于调试,以后将会在系统中删除该功能。

 

我们怎么识别Slate正在休眠?

因为目标是使得可以休眠的Slate和永不休眠的Slate没有区别,所以这很难辨别。

目前,检测该问题的最好方法是显示编辑器帧速率(编辑器设置->其他->显示帧速率和内存)。当该帧速率冻结时,Slate就正处于休眠状态。

我应该怎么做?

如果您过去制作过非常复杂的控件或者目前正在创建一个新控件,那么您应该在没有打开永久性实时窗口的情况下启用休眠状态测试您的控件(保持后输入缓冲为0),这很重要。

如果您发现了不能正常工作的控件 和/或 对于更新您的控件有问题,那么请在论坛上告诉我们。