|
发表于 2023-1-8 10:52:12
|
显示全部楼层
前言:
就UE4 LevelStreaming机制做个简单剖析,快速了解一下LevelStreaming的过程。 本文主要就当前状态,目标状态的更新做个简单介绍。 本人亦在学习过程中,有理解错误望指正,希望共同进步。
先来看看StreamingLevel的状态和状态机:
// 当前状态
enum class ECurrentState : uint8
{
Removed,
Unloaded,
FailedToLoad,
Loading,
LoadedNotVisible,
MakingVisible,
LoadedVisible,
MakingInvisible
};
// 目标状态
enum class ETargetState : uint8
{
Unloaded,
UnloadedAndRemoved,
LoadedNotVisible,
LoadedVisible,
};

状态机
接下来我们展开看看各个状态会处理什么事情。
一,当前状态更新处理
(ULevelStreaming::UpdateStreamingState)
1,ECurrentState::MakingVisible(将要显示状态)

当前状态:即将可见
在该状态下,我们主要是处理将Level关联到World中去,使用UWorld::AddToWorld方法。该方法可以异步切片处理,避免耗时过大。当Level被设置显示后,将旧的Level移除并请求卸载;同时在显示后,更新当前状态为ECurrentState::LoadedVisible(已加载且显示),并需要接着继续处理一下当前状态,且也需要重新更新一下目标状态。
/**
* Associates the passed in level with the world. The work to make the level visible is spread across several frames and this
* function has to be called till it returns true for the level to be visible/ associated with the world and no longer be in
* a limbo state.
*
* @param Level Level object we should add
* @param LevelTransform Transformation to apply to each actor in the level
*/
void AddToWorld( ULevel* Level, const FTransform& LevelTransform = FTransform::Identity, bool bConsiderTimeLimit = true );

当前状态:即将可见-AddToWorld
主要处理:
(1),标记正在关联中 bIsAssociatingLevel ;表示Level是否正在处理与World的关联。
(2),更新记录正在处理将要显示的Level,CurrentLevelPendingVisibility,同时将Level加入到World-Levels数组中。
(3),处理Level上所有的Actor:修正Actor位置;注册所有的组件,包括UModelComponents和所有Actor上的组件。
(4),处理Level上所有Actor的初始化;
(5),以上都处理结束后,最后一步,我们需要还原各个状态,以允许其他Level可以被加入到该World中。同时在此时,才可以将该Level的变量bIsVisible 置为true,开始处理该Level的渲染资源,通知texture streaming该Level已就绪,最后广播LevelAddedToWorld事件。
2,ECurrentState::MakingInvisible(将要隐藏状态)

当前状态:即将隐藏
在该状态下,就需要去隐藏该Level,主要是处理将Level从World中移除,使用UWorld::RemoveFromWorld方法。在从World中移除Level时,可以使用渐进式移除方式异步移除,即单次限时操作,每次只Unregister少量组件,避免在随后统一清理组件时渲染线程和游戏线程的尖峰。如果非立刻移除,即渐进式移除,则该World不可存在即将显示但还未显示Level,否则不可以执行移除操作,等待AddToWorld时替换移除;如果是立刻移除,则要么不存在即将显示但还未显示的Level,要么已显示的Level和即将显示的新Level不同,并且会调用ULevel::ClearLevelComponents统一清除组件,可能造成尖峰。
同时在隐藏后,更新当前状态为ECurrentState::LoadedNotVisible(已加载且隐藏),并且需要接着继续更新处理一下当前状态,也需要重新更新一下目标状态。
/**
* Dissociates the passed in level from the world. The removal is blocking.
*
* @param Level Level object we should remove
*/
void RemoveFromWorld( ULevel* Level, bool bAllowIncrementalRemoval = false );
主要是处理:
(1),标记bIsDisassociatingLevel,表示是否正在接触与World的关联。
(2),是否为渐进式移除,如果渐进式移除,则会限时多次Unregister组件;非渐进式移除,则直接等待随后统一清除组件。
(3),渐进式移除组件完成之后,才可以继续处理之后的逻辑。
(4),处理该Level上所有Actor的EndPlay流程。
(5),释放渲染资源,注销所有组件,也会还原在WorldCompoistion的位置,便于下次使用。
(6),从World-Levels数组中移除,而在编辑器下则会保留;广播LevelRemovedFromWorld事件。
3,ECurrentState::Loading(正在加载中)
当前状态不做任何处理,只是等待加载完成。
4,ECurrentState::Unloaded(已卸载)

当前状态:已卸载
当前状态为Unloaded时,目标状态只能为两个状态:
(1),目标状态为ETargetState::LoadedNotVisible时,就要去请求加载Level;
(2),目标状态为ETargetState::UnloadedAndRemoved时,就要将该Level从World的数组记录中清除,比如Consider数组。同时改变当前状态为ECurrentState::Removed。然后不需要再次更新处理当前状态,但需要更新目标状态。
现在来看下请求加载Level的流程:

请求加载流程
(1),使用异步加载方法LoadPackageAsync。正常加载中,会将当前状态设置为ECurrentState::Loading。
(2),如果要加载的Package不存在,当前状态会置为ECurrentState::FailedToLoad。
(3),检测StreamingLevel的唯一性。如果不唯一,且没有请求卸载,则当前状态置为ECurrentState::FailedToLoad。
(4),请求加载后,如果当前状态不在Loading状态,则需要更新目标状态。
(5),请求加载前后,当前状态发生改变,则需要接着继续更新处理一次当前状态。
(6),如果当前还未加载出新的Level,可以考虑把旧的Level移除了,这里针对的是LOD的切换。
5,ECurrentState::LoadedNotVisible(已加载且未显示)

当前状态:已加载未显示
(1),目标状态为ETargetState::LoadedVisible时,则当前状态更新为ECurrentState::MakingVisible,并再次处理当前状态。
(2),目标状态为ETargetState::Unloaded时,则丢弃旧Level,清除当前LoaedLevel,当前状态更新为ECurrentState::Unloaded,同时需要重新确定目标状态,以及是否还需要加入到Consider数组。之后需要再次处理当前状态。
(3),目标状态为ETargetState::LoadedNotVisible时,如果当前已加载的Level,不是当前LOD对应所期望加载的Level,则需要丢弃旧Level,并重新加载正确的Level。
6,ECurrentState::LoadedVisible(已加载且显示)

当前状态:已加载已显示
(1),目标状态为ETargetState::LoadedNotVisible时,则当前状态更新为ECurrentState::MakingInvisible,并再次处理当前状态。
(2),目标状态为ETargetState::LoadedVisible时,则再次请求加载。可能当前已加载的Level,并不是当前LOD期望加载的Level,则需要重新加载正确的Level。可参考后面目标状态的更新。
7,ECurrentState::FailedToLoad(加载失败)

当前状态:加载失败
加载失败后,会执行更新目标状态逻辑,把该Level从Consider数组中移除。
二,目标状态更新处理
(ULevelStreaming::DetermineTargetState)
目标状态的更新,依然是依据当前状态来决定最终目标状态,同时会决定是否还需要继续在Cosider数组中继续考虑处理。
1,ECurrentState::MakingVisible(即将显示)

当前状态:即将显示
目标状态更新为ETargetState::LoadedVisible,并需要继续考虑处理,不会从Consider数组中移除。
2,ECurrentState::MakingInvisible(即将隐藏)

当前状态:即将隐藏
目标状态更新为ETargetState::LoadedNotVisible,并需要继续考虑处理。
3,ECurrentState::Loading(正在加载中)

当前状态:正在加载中
目标状态更新为ETargetState::LoadedNotVisible,并需要继续考虑处理。
4,ECurrentState::Unloaded(已卸载)

当前状态:已卸载
(1),如果需要卸载和移除,则更新目标状态为ETargetState::UnloadedAndRemoved,并需要继续考虑处理。结合上面当前状态的更新处理,在此当前和目标状态下,我们将该Level从World数组中移除,同时从Consider数组中移除,并不再继续处理。
(2),如果该Level是应该强制卸载的,则直接从Consider数组中移除。
(3),如果非运行时,比如编辑预览状态下,则目标状态更新为ETargetState::LoadedNotVisible,同时需要继续处理。
(4),如果该Level是应该被加载的,则目标状态更新为ETargetState::LoadedNotVisible,同时需要继续处理。
(5),其他情况,就不需要再考虑该Level了,需要从Consider数组中移除。
5,ECurrentState::LoadedNotVisible(已加载且未显示)

当前状态:已加载未显示
(1),如果该Level需要卸载和移除,或者应该强制卸载的,则目标状态更新为ETargetState::Unloaded,同时需要继续考虑处理。结合上面当前状态的处理,等再次处理时,会更新当前状态并卸载。
(2),如果在运行时,且该Level不应该被加载,则目标状态更新为ETargetState::Unloaded。后续处理同上。
(3),如果当前已加载的Level,并不是当前LOD期望加载的Level,则目标状态更新为ETargetState::LoadedNotVisible,同时需要继续考虑处理。后续会处理重新加载正确的Level。
(4),如果该Level应该被显示,比如在编辑器下默认会考虑显示出来,则目标状态更新为ETargetState::LoadedVisible,同时需要继续考虑处理。结合上面当前状态处理,该Level随后就会被显示出来。
(5),其他情况下,均不再考虑处理,会从Consider数组中移除。
6,ECurrentState::LoadedVisible(已加载且显示)

当前状态:已加载已显示
(1),如果该Level需要卸载和移除,或者应该强制卸载,则目标状态更新为ETargetState::LoadedNotVisible,并继续考虑处理。
(2),如果在运行时,并且该Level不应该被加载,则目标状态更新为ETargetState::LoadedNotVisible,并继续考虑处理。结合上面当前状态的处理,后续会卸载该Level的。
(3),如果该Level不应该被显示,则目标状态更新为ETargetState::LoadedNotVisible,并继续处理。后续处理隐藏。
(4),如果当前已加载的Level,不是当前LOD期望加载的Level,则目标状态更新为ETargetState::LoadedVisible,并继续考虑处理。后续会重新请求加载正确的Level。
(5),其他情况下,均不再考虑,会从Consider数组中移除。
7,ECurrentState::FailedToLoad(加载失败)

当前状态:加载失败
从Consider数组中移除。
8,ECurrentState::Removed(已移除)

当前状态:已移除
从Consider数组中移除。
小结:
结合当前状态和目标状态,我们就可以知道接下来会进行什么操作,可更明确地了解StreamingLevel的整个生命周期。
请求加载后,会从Loading状态到LoadedNotVisible状态,这个是在异步加载回调中处理的,会在后续文章中继续分析。 |
|