UE4 ActionGame知識點總結1-FLatentActionInfo以及FLatentActionManager使用

寫這篇文字之前一直有兩個問題:

1. Delay操作是如何實現的?

2. 是否可以使用Delay的實現機制來處理一些需要異步處理的東西,比如一些讀寫數據?

我們先看下Delay的使用和實現:


按下1鍵,5秒後會打印出文字。需要說明的是:調用完Delay節點後,Completed的pin在5秒後纔會調用,所以應該把這一幀需要執行的操作放在delay節點前。

注意Delay和Window編程中的Sleep(5000)操作完全不同,Sleep是掛起當前的線程,把執行權交給其他線程,大約5秒後再喚醒這個線程。Delay並沒有掛起本線程,而是立即返回,5秒後再來執行Completed Pin後的節點。

瞭解了Delay的使用後,我們看下Delay的實現:

/** 
	 * Perform a latent action with a delay (specified in seconds).  Calling again while it is counting down will be ignored.
	 * 
	 * @param WorldContext	World context.
	 * @param Duration 		length of delay (in seconds).
	 * @param LatentInfo 	The latent action.
	 */
	UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep"))
	static void	Delay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );
void UKismetSystemLibrary::Delay(UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo )
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
	{
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		if (LatentActionManager.FindExistingAction<FDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
		{
			LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayAction(Duration, LatentInfo));
		}
	}
}

Delay的內部邏輯很簡單,向LatentActionManager實例中Add了一個FDelayAction的實例。我們看下FDelayAction的實現:

// FDelayAction
// A simple delay action; counts down and triggers it's output link when the time remaining falls to zero
class FDelayAction : public FPendingLatentAction
{
public:
	float TimeRemaining;
	FName ExecutionFunction;
	int32 OutputLink;
	FWeakObjectPtr CallbackTarget;

	FDelayAction(float Duration, const FLatentActionInfo& LatentInfo)
		: TimeRemaining(Duration)
		, ExecutionFunction(LatentInfo.ExecutionFunction)
		, OutputLink(LatentInfo.Linkage)
		, CallbackTarget(LatentInfo.CallbackTarget)
	{
	}

	virtual void UpdateOperation(FLatentResponse& Response) override
	{
		TimeRemaining -= Response.ElapsedTime();
		Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget);
	}

#if WITH_EDITOR
	// Returns a human readable description of the latent operation's current state
	virtual FString GetDescription() const override
	{
		static const FNumberFormattingOptions DelayTimeFormatOptions = FNumberFormattingOptions()
			.SetMinimumFractionalDigits(3)
			.SetMaximumFractionalDigits(3);
		return FText::Format(NSLOCTEXT("DelayAction", "DelayActionTimeFmt", "Delay ({0} seconds left)"), FText::AsNumber(TimeRemaining, &DelayTimeFormatOptions)).ToString();
	}
#endif
};

Delay的實現都在這裏了,我們是Delay了5秒並且是在關卡藍圖裏調用的,構建FDelayAction傳入的參數中,TimeRemaining就是5.0f,ExecutionFunction和OutputLink是Completed Pin後面節點的相關信息。CallbackTarget是指這個Delay操作是在哪個UObject裏面的,因爲我們是在關卡藍圖裏調用的,所以這裏的CallbackTarget是ALevelScriptActor實例。FDelayAction有個UpdateOperation操作,我們可以猜測到是遊戲線程每幀都會更新的代碼,通過每次減去DeltaTime並且判斷時間TimeRemaining是否已經小於0,如果是則執行後面的節點。目前就剩下最後一個疑惑了,UpdateOperation是如何調用的呢?通過調試我們可以知道是FLatentActionManager::ProcessLatentActions進行調用的,我們全局搜索發現調用ProcessLatentActions的有下面幾個:

  D:\Software\UE4\Engine\UE_4.19\Engine\Source\Runtime\Engine\Private\Components\ActorComponent.cpp(909):			ComponentWorld->GetLatentActionManager().ProcessLatentActions(this, ComponentWorld->GetDeltaSeconds());
  D:\Software\UE4\Engine\UE_4.19\Engine\Source\Runtime\Engine\Private\Actor.cpp(889):	MyWorld->GetLatentActionManager().ProcessLatentActions(this, MyWorld->GetDeltaSeconds());
  D:\Software\UE4\Engine\UE_4.19\Engine\Source\Runtime\Engine\Private\LevelTick.cpp(1468):				CurrentLatentActionManager.ProcessLatentActions(NULL, DeltaSeconds);
  D:\Software\UE4\Engine\UE_4.19\Engine\Source\Runtime\UMG\Private\UserWidget.cpp(1347):		World->GetLatentActionManager().ProcessLatentActions(this, InDeltaTime);
上面幾個調用都是在各自的Tick函數中執行的。

現在整個框架已經知道了,當某個Object中有Delay操作執行時,會向FLatentActionManager中添加一個對應的Action並且立即返回,後續Level或者這個Object的Tick函數就會執行到這個Action的UpdateOperation中,FLatentActionManager的實現機制保證了UpdateOperation在本幀只會執行一次。Action的UpdateOperation就是Delay的相關邏輯判斷,如果條件達到了就通知Delay後面的節點執行即可。

那麼我們現在看下上面提到的兩個問題:

1. Delay操作是如何實現的?這個上面一段話已經解釋了。

2. 是否可以使用Delay的實現機制來處理一些需要異步處理的東西,比如一些讀寫數據?這個得看下具體要實現什麼,如果Action的UpdateOperation中執行一些需要時間的邏輯(磁盤讀數據,網絡等數據,計算時間很長的),那麼是不可以的,因爲UpdateOperation阻塞或者等待其實會導致遊戲線程的Tick阻塞和等待,會導致遊戲畫面卡死等問題,UpdateOperation應該執行一些不會等待的東西,比如可以查看某些狀態是否已經完成了等等。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章