PlayerLoop简要流程
Posted on 周四 30 四月 2020 in 源码
[TOC]
PlayerLoop简介
PlayerLoop是Unity的主循环,每帧都会跑一次。Unity 2018添加了PlayerLoop相关的API, 我们可以利用如下代码来查看一个PlayerLoop干了那些事:
var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
foreach (var header in playerLoop.subSystemList)
{
Debug.LogFormat("------{0}------", header.type.Name);
foreach (var subSystem in header.subSystemList)
{
Debug.LogFormat("{0}.{1}", header.type.Name, subSystem.type.Name);
}
}
会得到一长串的东西:
------Initialization------
Initialization.PlayerUpdateTime
Initialization.AsyncUploadTimeSlicedUpdate
Initialization.SynchronizeInputs
Initialization.SynchronizeState
Initialization.XREarlyUpdate
------EarlyUpdate------
EarlyUpdate.PollPlayerConnection
//...略
------FixedUpdate------
FixedUpdate.ClearLines
//...略
------PreUpdate------
//...略
------Update------
//...略
------PreLateUpdate------
//...略
------PostLateUpdate------
//...略
在源码中,PlayerLoop的的入口为PlayerLoop
函数。
ReentrancyChecker
ReentrancyChecker checker(&s_InsidePlayerLoop);
if (!checker.IsOK())
{
//...
}
相当于判断s_InsidePlayerLoop
标志位的一个工具类,基本逻辑:
s_InsidePlayerLoop == false
则IsOk
返回true
, 并把`s_InsidePlayerLoop
置为true
s_InsidePlayerLoop == false
则IsOk
返回false
主要用来防止PlayerLoop出现递归情况。
IsWorldPlaying
这个函数用来判断当前是否是在运行状态。
s_defaultLoop
初始化:
static void InitDefaultPlayerLoop()
{
//...
s_defaultLoop.resize_initialized(1 +
(PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_EarlyUpdate_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_FixedUpdate_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_PreUpdate_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_Update_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_PreLateUpdate_COUNT + 1) +
(PlayerLoopCallbacks::PLAYER_LOOP_PostLateUpdate_COUNT + 1));
UpdateDefaultPlayerLoop();
s_currentLoop = s_defaultLoop.data();
//...
}
大致意思就是初始化这么多的回调,每个回调对应的是CoreScriptingClasses
中的相应函数。
PlayerLoopSystem组织方式
所有的PlayerLoopSystem保存在s_currentLoop
中,它内部是一个NativePlayerLoopSystem
的数组,大致如下:
-
列表头记录了所有PlayerLoopSystem的个数
-
每一个PlayerLoopSystem和它的subSystem存在一片连续的区域
-
PlayerLoopSystem有它的处理逻辑(updateFunction),并记录了它的subSystem的个数
-
subSystem中只有处理逻辑
PlayerLoopSystem的处理函数
定义于PlayerLoopCallbacks.h
:
UpdateFunc* RegisteredInitializationFunctions[PLAYER_LOOP_Initialization_COUNT];
UpdateFunc* RegisteredEarlyUpdateFunctions[PLAYER_LOOP_EarlyUpdate_COUNT];
UpdateFunc* RegisteredFixedUpdateFunctions[PLAYER_LOOP_FixedUpdate_COUNT];
UpdateFunc* RegisteredPreUpdateFunctions[PLAYER_LOOP_PreUpdate_COUNT];
UpdateFunc* RegisteredUpdateFunctions[PLAYER_LOOP_Update_COUNT];
UpdateFunc* RegisteredPreLateUpdateFunctions[PLAYER_LOOP_PreLateUpdate_COUNT];
UpdateFunc* RegisteredPostLateUpdateFunctions[PLAYER_LOOP_PostLateUpdate_COUNT];
具体回调函数通过宏REGISTER_PLAYERLOOP_CALL
来注册,例如Player.cpp
中:
void InitPlayerLoopCallbacks()
{
REGISTER_PLAYERLOOP_CALL(Initialization, AsyncUploadTimeSlicedUpdate,
{
GetAsyncUploadManager().TimeSlicedUpdate(); //真正的处理函数
});
...
}
或者ClusterInputModuleRegistration.cpp
中:
void InitClusterInput()
{
...
REGISTER_PLAYERLOOP_CALL(Initialization, SynchronizeInputs, GetClusterInputModule()->Update());
}
ExecutePlayerLoop
首先判断本个NativePlayerLoopSystem要不要再当前帧执行:
while (!system->loopConditionFunction || system->loopConditionFunction())
{...}
例如FixedUpdateCondition就限制了只有在与上次执行相隔大于一定时间才可以执行。
//如果有系统的处理函数
if (childSystem->updateFunction)
{
if (*childSystem->updateFunction)
(*childSystem->updateFunction)(); //Start,Update之类的就是从这里进来的
}
//再看是不是通过SetPlayerLoop设置了用户自定义的处理
else if (!childSystem->delegateInvokeMethod.IsNull())
{
//...
}
//执行下一个PlayerLoopSystem
else
{
// Execute all the child systems children recursivly and skip them in this loop
ExecutePlayerLoop(childSystem);
childSystemIndex += childSystem->numSubSystems;
childSystem += childSystem->numSubSystems;
}
使用如下代码可以在系统的update前加入我们自己的些处理:
[RuntimeInitializeOnLoadMethod]
static void OnRuntimeMethodLoad()
{
PlayerLoopSystem hackSystem = new PlayerLoopSystem()
{
type = typeof(MyUpdateHacker),
updateDelegate = () =>
{
Debug.Log("=========hi, I'm hacking");
},
};
PlayerLoopSystem playerLoop = PlayerLoop.GetDefaultPlayerLoop();
PlayerLoopSystem updateSystem = playerLoop.subSystemList[4];
List<PlayerLoopSystem> subSystem = new List<PlayerLoopSystem>(updateSystem.subSystemList);
subSystem.Insert(0, hackSystem);
playerLoop.subSystemList[4].subSystemList = subSystem.ToArray();
PlayerLoop.SetPlayerLoop(playerLoop);
}
public struct MyUpdateHacker { }