Unreal为何不为FDefaultGameMoviePlayer支持UMG动画Tick?
问题背景
众所周知,在Unreal引擎中执行LoadMap时,主线程会被阻塞,UI会出现卡顿。因此Unreal提供了FDefaultGameMoviePlayer,它通过另一个线程(SlateLoadingThread)为指定的WidgetLoadingScreen调用Paint方法,这一操作在FMoviePlayerWidgetRenderer::DrawWindow中完成。
但我无法理解为何Unreal不在FMoviePlayerWidgetRenderer::DrawWindow中调用FSlateApplication::Get().Tick(ESlateTickType::TimeAndWidgets)来触发UMG动画Tick。在SlateLoadingThread的生命周期内,主线程始终处于LoadMap阻塞状态,主线程与SlateLoadingThread之间理应不存在竞态条件。
请问在非主线程中调用UMG动画Tick是否存在问题?
解答
核心风险:UMG与Slate的单线程设计约束
即使主线程在LoadMap期间处于阻塞状态,非主线程调用UMG动画Tick依然存在不可忽视的问题,根源在于UMG和Slate框架的单线程绑定设计:
Slate API缺乏线程安全性
Slate的核心API(包括FSlateApplication::Tick)从未设计为线程安全。内部的Widget布局缓存、输入状态、渲染资源引用等数据结构,默认只允许主线程操作。SlateLoadingThread被严格限制为仅执行Paint操作——这是Unreal特意做的隔离,避免触发完整Tick流程带来的未知内存访问冲突或状态错乱。UMG动画的依赖链隐患
UMG动画的Tick不仅会更新动画进度,还可能触发Widget属性变更、布局重计算,甚至通过动画通知间接调用Gameplay逻辑。这些逻辑严重依赖主线程维护的Gameplay状态(比如世界上下文、Actor生命周期),即使主线程阻塞,部分内部状态可能处于加载的中间不一致态,非主线程调用极易引发逻辑异常或崩溃。渲染资源的线程所有权冲突
Slate的渲染资源(如纹理、顶点缓存)由主线程负责创建和管理,SlateLoadingThread仅被允许临时读取这些资源完成绘制。如果在该线程触发UMG动画Tick,可能会触发资源的创建/修改操作,违反Unreal渲染资源的线程安全规则,导致渲染异常或程序崩溃。
Unreal不这么做的原因
Unreal选择让SlateLoadingThread仅执行Paint而非完整Tick,是为了以最小的线程交互保证稳定性:
LoadMap的阻塞是临时状态,加载屏仅需展示静态或预渲染的动画即可满足需求,无需复杂的UMG动态动画逻辑;- 完整的Slate Tick流程涉及大量内部状态更新,即使主线程阻塞,也无法保证所有依赖状态的安全性,Unreal优先保证加载过程的稳定性而非动画的丰富性。
内容的提问来源于stack exchange,提问作者pan64271

