【UE5 C++课程系列笔记】27——多线程基础——ControlFlow插件的基本使用

目录

步骤

一、搭建基本同步框架 

二、添加委托 

三、添加蓝图互动框架 

四、修改为异步框架

完整代码


通过一个游戏初始化流程的示例来介绍“ControlFlows”的基本使用。

步骤

一、搭建基本同步框架 

1. 勾选“ControlFlows”插件

2. 新建一个空白C++类,这里命名为“ControlFlowSubSystem” 

让“ControlFlowSubSystem” 继承“GameInstanceSubsystem”,然后添加反射所需代码

重写父类“ShouldCreateSubsystem”、“Initialize”和“Deinitialize”方法

3. 在Build.cs中添加“ControlFlows”模块

引入所需库

定义一个蓝图可调用的方法“InitLevel”,表示要执行的初始化流程;定义一个布尔类型变量“bIniting”用于表示当前是否处于初始化流程;再定义5个函数用于表示初始化流程的各个步骤。

“InitLevel”实现如下:

        首先,通过检查 bIniting 来判断当前是否已经处于初始化过程中。如果 bIniting 为 true,意味着初始化正在进行或者已经执行过了,此时输出一条日志信息然后直接返回,避免重复执行初始化流程。只有当 bIniting 为 false 时,才会将其设置为 true,表示即将开始初始化流程。

        第31行代码通过调用 FControlFlowStatics::Create 静态函数创建一个 FControlFlow 类型的控制流实例 Flow。在创建过程中,传入了 this 指针和一个字符串,这个字符串作为控制流的唯一标识符。 

        第33~37行代码通过多次调用 Flow.QueueStep 函数,向刚创建的控制流实例中依次添加了多个需要按顺序执行的步骤。

        第40行代码调用 Flow.ExecuteFlow() 函数启动控制流的执行。此时,FControlFlow 实例会按照之前添加步骤的顺序,依次调用对应的成员函数,确保整个初始化流程按照预定的顺序有条不紊地进行,直到所有步骤都执行完毕,完成整个初始化过程。

        用于表示初始化流程步骤的5个函数实现如下,当执行到最后一个步骤时。将 bIniting 改为 false,表示初始化流程已经结束。

4. 在关卡蓝图中调用“InitLevel”函数

执行结果如下,可以通过日志信息看到完整执行了整个初始化流程。

5. 为了观察每个步骤在哪一帧执行,可以通过添加如下代码实现:

运行结果如下,可以看到所有表示流程步骤的函数都是在同一帧执行的,这可能会造成游戏帧率下降,因此这并不符合我们的需求。

二、添加委托 

6. 下面先创建两个委托,通过委托来向外界传递任务进度等信息。

申明两个动态多播委托类型

        在第11行代码中,FControlFlows_InitProgress是要声明的委托类型的名称,FGuid 是UE中用于表示全局唯一标识符(GUID)的类型,在这里它作为委托参数的类型,而 InitAsyncID 是给这个参数起的变量名。当委托被调用时,会传递一个 FGuid 类型的全局唯一标识符。该委托在被调用时,还会传递一个表示进度值的浮点型数据,这个值可以用来直观地展示当前异步初始化任务已经完成的比例或者进度情况。

        在第12行代码中,FControlFlows_InitResult代表所声明的委托类型名称,FGuid 与InitAsyncID和前面的委托类似,bool 与 bResult用于指示异步初始化任务最终是成功还是失败,直观地告知结果状态。Message 表示委托调用时还会附带更详细的关于初始化结果的说明。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FControlFlows_InitProgress, FGuid, InitAsyncID, float, ProgressValue);  //进度更新
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FControlFlows_InitResult, FGuid, InitAsyncID, bool, bResult, FString, Message);  //初始化结果

声明两个委托类型的成员变量,并且通过 UPROPERTY(BlueprintAssignable) 元数据标签对这两个属性进行修饰,使其具备了在蓝图系统中可被绑定具体的回调函数的特性。

 7. 为表示初始化步骤的5个成员函数添加进度值输入参数

8. 设置进行“InitLocalAsset”步骤时,初始化进度为10%;进行“InitNetInfo”步骤时,初始化进度为20%;进行“InitUserInfo”步骤时,初始化进度为50%;进行“NotifyMainUI”步骤时,初始化进度为80%;进行“FinishThisInit”步骤时,初始化进度为100%;

9. 声明InitAsyncID用于唯一标识某次初始化的异步任务

执行“InitLevel”函数进行初始化时为InitAsyncID赋值

当进行各初始化的步骤时,通过InitProgress委托广播当前的初始化进度,传入对应的InitAsyncID和本步骤的当前进度值InProgressValue,这样外部绑定了该委托的对象就能接收到进度更新情况。

在初始化流程的最后一个步骤对应的函数中,添加如下一行代码,通过InitResult委托广播当前的初始化结果。

三、添加蓝图互动框架 

10. 添加一个控件蓝图,用于显示当前初始化进度,这里命名为“WBP_ControlFlowsMainUI”

打开“WBP_ControlFlowsMainUI”,添加如下控件,主要是添加一个文本控件用于显示进度值,按钮用于调用“InitLevel”从而开始初始化

在事件构造和结构时分别绑定和解绑“InitProgress”、“InitResult”委托

重命名委托绑定的匹配函数为“UpdateProgress”和“InitResult”

匹配函数“UpdateProgress”和“InitResult”函数逻辑如下,将委托传递的GUID、进度值和初始化结果信息打印出来,并显示进度值。如果初始化成功后就将界面隐藏,如果失败就显示进度值为0.00

 当按钮点击后调用“InitLevel”开始初始化

 11. 在关卡蓝图中让界面显示出来

此时运行后界面如下

点击初始化按钮后打印信息如下:

此时就完成了初始化流程的同步框架实现,接下来我们希望将同步改为异步实现。

四、修改为异步框架

12. 引入所需头文件

13. 更改“InitLocalAsset”、“InitNetInfo”、“InitUserInfo”函数逻辑如下

主要通过使用AsyncTask函数创建了一个外层的异步任务,并指定其可以在任意线程上执行。在这个异步任务的 lambda 表达式内部首先调用FPlatformProcess::Sleep(0.2)模拟一个耗时操作,接着又创建了一个内层的异步任务,指定在游戏线程上执行后续的耗时操作,以及向外广播初始化的进度信息。

完善“InitUserInfo”逻辑如下

        第81行首先获取位于项目保存目录下的文件名为 ControlFlowsUserInfo.txt的目标文件相对路径,然后将相对路径转换为绝对路径。

        第82行将初始化任务的唯一标识 InitAsyncID 转换为字符串形式赋值给 UserInitAsyncID。

        第83行获取当前的日期时间并转换为 HTTP 日期格式赋值给 UserInitDataTime 。

        第84~86行创建一个 TArray<FString> 类型的数组 MyStringInfo,并将前面获取的UserInitAsyncID 和 UserInitDataTime 字符串添加进去,准备将这些信息保存到文件中。

        第87行使用 FFileHelper::SaveStringArrayToFile 函数将包含用户初始化相关信息的字符串数组 MyStringInfo 保存到指定路径 UserInfoPath 的 ControlFlowsUserInfo.txt中,并且指定了编码选项为ForceUTF8WithoutBOM,确保文件内容以指定的编码格式存储。

        编译后运行,此时当我们点击初始化按钮后,看到输出日志信息如下,可以发现初始化流程的步骤不再是一帧内执行的了。

并且在“Saved”文件夹中多了一个名为 ControlFlowsUserInfo.txt的文件

 ControlFlowsUserInfo.txt的内容如下,包括了初始化任务的唯一标识和文件存储时间。

如果初始化流程中的某一步失败了,通过 InitResult 委托向外广播初始化失败的结果信息,第113行调用 SubFlow->CancelFlow()标识取消当前正在执行的控制流,从而及时终止整个初始化流程。将 bIniting 变量设置为 false,表示当前不再处于初始化过程中,同时将 InitAsyncID 重置为默认值,为下一次可能的初始化操作做好准备。

完整代码

“ControlFlowSubSystem.h”

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "ControlFlow.h"
#include "ControlFlowManager.h"
#include "ControlFlowNode.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Async/Async.h"
#include "ControlFlowSubSystem.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FControlFlows_InitProgress, FGuid, InitAsyncID, float, ProgressValue);  //进度更新
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FControlFlows_InitResult, FGuid, InitAsyncID, bool, bResult, FString, Message);  //初始化结果

#define INITRESULT false

UCLASS()
class STUDY_API UControlFlowSubSystem : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	UFUNCTION(BlueprintCallable)
	void InitLevel();

public:
	UPROPERTY(BlueprintAssignable)
	FControlFlows_InitProgress InitProgress;

	UPROPERTY(BlueprintAssignable)
	FControlFlows_InitResult InitResult;

protected:
	bool bIniting = false;

	FGuid InitAsyncID = FGuid();

	void InitLocalAsset(FControlFlowNodeRef SubFlow, double InProgressValue);
	void InitNetInfo(FControlFlowNodeRef SubFlow, double InProgressValue);
	void InitUserInfo(FControlFlowNodeRef SubFlow, double InProgressValue);
	void NotifyMainUI(FControlFlowNodeRef SubFlow, double InProgressValue);
	void FinishThisInit(FControlFlowNodeRef SubFlow, double InProgressValue);
};

“ControlFlowSubSystem.cpp” 

// Fill out your copyright notice in the Description page of Project Settings.


#include "ControlFlowSubSystem.h"

bool UControlFlowSubSystem::ShouldCreateSubsystem(UObject* Outer) const
{
	return true;
}

void UControlFlowSubSystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
}

void UControlFlowSubSystem::Deinitialize()
{
	Super::Deinitialize();
}

void UControlFlowSubSystem::InitLevel()
{
	if (bIniting)
	{
		UE_LOG(LogTemp, Warning, TEXT("Initing..."));
		return;
	}
	InitAsyncID = FGuid::NewGuid();
	bIniting = true;

	uint64 FrameIndex = GFrameCounter;

	UE_LOG(LogTemp, Warning, TEXT("InitFlow -- FrameIndex: %d"), FrameIndex);
	FControlFlow& Flow = FControlFlowStatics::Create(this, TEXT("ControlFlow_InitLevel"));

	Flow.QueueStep(TEXT("InitLocalAsset"), this, &UControlFlowSubSystem::InitLocalAsset, 0.1);
	Flow.QueueStep(TEXT("InitNetInfo"), this, &UControlFlowSubSystem::InitNetInfo, 0.2);
	Flow.QueueStep(TEXT("InitUserInfo"), this, &UControlFlowSubSystem::InitUserInfo, 0.5);
	Flow.QueueStep(TEXT("NotifyMainUI"), this, &UControlFlowSubSystem::NotifyMainUI, 0.8);
	Flow.QueueStep(TEXT("FinishThisInit"), this, &UControlFlowSubSystem::FinishThisInit, 1.0);

	UE_LOG(LogTemp, Warning, TEXT("ExecuteFlow -- FrameIndex: %d"), FrameIndex);
	Flow.ExecuteFlow();
}

void UControlFlowSubSystem::InitLocalAsset(FControlFlowNodeRef SubFlow, double InProgressValue)
{
	uint64 FrameIndex = GFrameCounter;
	UE_LOG(LogTemp, Warning, TEXT("InitLocalAsset -- FrameIndex: %d"), FrameIndex);

	AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {
		FPlatformProcess::Sleep(0.2);
		AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {
			FPlatformProcess::Sleep(0.2);
			InitProgress.Broadcast(InitAsyncID, InProgressValue);
			SubFlow->ContinueFlow();
		});
	});
}

void UControlFlowSubSystem::InitNetInfo(FControlFlowNodeRef SubFlow, double InProgressValue)
{
	uint64 FrameIndex = GFrameCounter;
	UE_LOG(LogTemp, Warning, TEXT("InitNetInfo -- FrameIndex: %d"), FrameIndex);
	AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {
		FPlatformProcess::Sleep(0.2);
		AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {
			FPlatformProcess::Sleep(0.2);
			InitProgress.Broadcast(InitAsyncID, InProgressValue);
			SubFlow->ContinueFlow();
		});
	});
}

void UControlFlowSubSystem::InitUserInfo(FControlFlowNodeRef SubFlow, double InProgressValue)
{
	uint64 FrameIndex = GFrameCounter;
	UE_LOG(LogTemp, Warning, TEXT("InitUserInfo -- FrameIndex: %d"), FrameIndex);
	AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {
		FPlatformProcess::Sleep(0.2);
		FString UserInfoPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()/TEXT("ControlFlowsUserInfo.txt"));
		FString UserInitAsyncID = InitAsyncID.ToString();
		FString UserInitDataTime = FDateTime::Now().ToHttpDate();
		TArray<FString> MyStringInfo;
		MyStringInfo.Add(UserInitAsyncID);
		MyStringInfo.Add(UserInitDataTime);
		FFileHelper::SaveStringArrayToFile(MyStringInfo, *UserInfoPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);

		AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {
			FPlatformProcess::Sleep(0.2);
			InitProgress.Broadcast(InitAsyncID, InProgressValue);
			SubFlow->ContinueFlow();
		});
	});
}

void UControlFlowSubSystem::NotifyMainUI(FControlFlowNodeRef SubFlow, double InProgressValue)
{
	uint64 FrameIndex = GFrameCounter;
	UE_LOG(LogTemp, Warning, TEXT("NotifyMainUI -- FrameIndex: %d"), FrameIndex);

	if (INITRESULT)
	{
		InitProgress.Broadcast(InitAsyncID, InProgressValue);
		SubFlow->ContinueFlow();
	}
	else
	{
		AsyncTask(ENamedThreads::GameThread, [this]() {
			InitResult.Broadcast(InitAsyncID, false, TEXT("Init Failed"));
		});

		SubFlow->CancelFlow();
		bIniting = false;
		InitAsyncID = {};
	}
}

void UControlFlowSubSystem::FinishThisInit(FControlFlowNodeRef SubFlow, double InProgressValue)
{
	uint64 FrameIndex = GFrameCounter;
	UE_LOG(LogTemp, Warning, TEXT("FinishThisInit -- FrameIndex: %d"), FrameIndex);
	InitProgress.Broadcast(InitAsyncID, InProgressValue);
	InitResult.Broadcast(InitAsyncID, true, TEXT("Init Success"));
	SubFlow->ContinueFlow();
	bIniting = false;
	InitAsyncID = {};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/953703.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端性能优化方面

页面加载过程 网页资源的加载通常需要以下基本步骤&#xff1a; 地址栏输入网页服务器地址 浏览器获取网页html文件 解析html文件中存在的js、css、图片等资源&#xff0c;通过网络线程加载 在特定时机执行js代码&#xff0c;可以在js中动态加载需要的静态资源 执行js里存在的f…

解锁“搭子小程序”开发新机遇,助力企业数字化转型

搭子作为一种新型的社交方式&#xff0c;逐渐进入到了年轻人的生活中&#xff0c;在日常旅游、学习、逛街等&#xff0c;年轻人都可以找到志同道合的“搭子”&#xff0c;提高生活的幸福指数。 随着搭子市场的发展&#xff0c;通过互联网寻找搭子已经成为了年轻人的必备方式。…

Open FPV VTX开源之ardupilot配置

Open FPV VTX开源之ardupilot配置 1. 源由2. 配置3. 总结4. 参考资料5. 补充5.1 飞控固件版本5.2 配置Ardupilot的BF OSD5.3 OSD偏左问题 1. 源由 飞控嵌入式OSD - ardupilot配置使用ardupliot配套OSD图片。 Choose correct font depending on Flight Controller SW. ──>…

Harmony NEXT开发ArkUI框架速成二基础语法

程序员Feri一名12年的程序员,做过开发带过团队创过业,擅长Java、嵌入式、鸿蒙、人工智能等,专注于程序员成长那点儿事,希望在成长的路上有你相伴&#xff01;君志所向,一往无前&#xff01; 1.ArkUI基础语法 1.1 ArkTS页面组成 在创建的时候&#xff0c;可以创建Page也可以直…

【树莓派3B】香瓜树莓派3B之语音识别机器人

本文最后修改时间&#xff1a;2018年04月03日 11:27 一、本节简介 本节用树莓派3代B型开发板做一个语音识别机器人&#xff0c;实现基础的语音对话功能。 注&#xff1a;转载原文路径 https://github.com/WhisperHear/Voice_Recognition_Control_Robot#userconsent# 上文个…

雷达流量监测系统:精准监控水流,确保水资源安全

水是生命之源&#xff0c;水资源的有效管理和保护直接关系到人类的生存与发展。随着全球气候变化和人口增加&#xff0c;水资源的短缺问题日益严重&#xff0c;如何高效监控和管理水资源&#xff0c;成为了水利、环保、农业等多个领域亟待解决的重要问题。而在这一过程中&#…

战场物联网:通信挑战与最新解决方案综述

论文标题 The Internet of Battle Things: A Survey on Communication Challenges and Recent Solutions 作者信息 Rachel Kufakunesu, Herman Myburgh, Allan De Freitas 论文出处 Discover Internet of Things (2025) 5:3 | The internet of battle things: a survey on…

GitLab 国际站中国大陆等地区停服,如何将数据快速迁移到云效

代码托管平台 GitLab 国际站&#xff08;GitLab.com&#xff09;近日发布公告&#xff0c;官宣即将停止对中国大陆、香港、澳门地区的用户账号提供服务&#xff0c;并提供 60 天过渡期自行迁移账户数据&#xff0c;超期未迁移的账号可能会被 GitLab 清除。这一重要决策引起了全…

React方向:react中5种Dom的操作方式

1、通过原生JS获取Dom去操作 通过document.querySelector(#title)原生js的方式去拿到dom节点&#xff0c;然后去进行操作。 import {Component} from "react";class App extends Component {//定义获取Dom的函数handleGetDom(){let title document.querySelector(#t…

更灵活的对象之间的联动 - 观察者模式(Observer Pattern)

观察者模式&#xff08;Observer Pattern&#xff09; 观察者模式&#xff08;Observer Pattern&#xff09;观察者模式&#xff08;Observer Pattern&#xff09;概述观察者模式&#xff08;Observer Pattern&#xff09; 结构图观察者模式&#xff08;Observer Pattern&#…

Webpack 5 混淆插件terser-webpack-plugin生命周期作用时机和使用注意事项

参考案例代码 海南酷森科技有限公司/webpack-simple-demo Terser&#xff08;简要的/简短的&#xff09; 混淆依据 混淆是发生在代码已经 bundle 之后的事情 变量或者函数在被引用或赋值时才能被混淆 孤立的函数或者变量可能会被移除&#xff0c;但不会被混淆&#xff0c;要…

‌OCP英文全称是什么

在数据库领域&#xff0c;OCP全称为Oracle Certified Professional&#xff0c;是Oracle公司提供的Oracle数据库中级认证&#xff0c;专门针对数据库管理员(Database Administrator&#xff0c;简称DBA)和数据库开发人员。以下是关于OCP认证的详细介绍&#xff1a; 认证领域与…

MyBatis实现数据库的CRUD

本文主要讲解使用MyBatis框架快速实现数据库中最常用的操作——CRUD。本文讲解的SQL语句都是MyBatis基于注解的方式定义的&#xff0c;相对简单。 Mybatis中#占位符和$拼接符的区别 “#”占位符 在使用MyBatis操作数据库的时候&#xff0c;可以直接使用如下SQL语句删除一条数…

微调神经机器翻译模型全流程

MBART: Multilingual Denoising Pre-training for Neural Machine Translation 模型下载 mBART 是一个基于序列到序列的去噪自编码器&#xff0c;使用 BART 目标在多种语言的大规模单语语料库上进行预训练。mBART 是首批通过去噪完整文本在多种语言上预训练序列到序列模型的方…

RTX 5090 加持,科研服务器如何颠覆 AI 深度学习构架?

RTX 5090作为英伟达旗舰级GPU&#xff0c;凭借Ada Lovelace架构&#xff0c;融合创新的SM多单元流处理器、第三代RT Core与第四代Tensor Core&#xff0c;打造出极为强劲的计算体系。其24GB GDDR6X显存搭配1TB/s带宽&#xff0c;能以极低延迟和超高吞吐量处理大规模张量数据&am…

【2025最新】机器学习类计算机毕设选题80套,适合大数据,人工智能

【2025最新】机器学习类型计算机毕设选题 1-10套 基于Spring Boot的物流管理系统的设计与实现 基于机器学习的虚假招聘信息的分析与预测 基于机器学习的影响数据科学家职业变动因素的分析与预测 基于Spring Boot的历史文物交流平台的设计与实现 基于机器学习的肥胖影响因素的分…

【PPTist】幻灯片放映

放映功能的代码都在 src/hooks/useScreening.ts&#xff0c;我们看一下 从当前页开始 放映的功能。 // 进入放映状态&#xff08;从当前页开始&#xff09; const enterScreening () > {enterFullscreen()screenStore.setScreening(true) }首先是 enterFullscreen()&#…

MySQL 16 章——变量、流程控制和游标

一、变量 在MySQL数据库的存储过程和存储函数中&#xff0c;可以使用变量来存储查询或计算的中间结果数据&#xff0c;或者输出最终的结果数据 在MySQL数据库中&#xff0c;变量分为系统变量和用户自定义变量 &#xff08;1&#xff09;系统变量 1.1.1系统变量分类 变量由…

T-SQL编程

目录 1、T-SQL的元素 1.1 标识符 1. 常规标识符 2. 分隔标识符 1.2 变量 1. 全局变量 2. 局部变量 1.3 运算符 1. 算数运算符 2. 赋值运算符 3. 位运算符 4. 比较运算符 5. 逻辑运算符 6. 字符串连接运算符 7. 一元运算符 8. 运算符的优先级和结合性 1.4 批处…

2024 China Collegiate Programming Contest (CCPC) Zhengzhou Onsite 基础题题解

L. Z-order Curve 思路&#xff1a;这题目说了&#xff0c;上面那一行&#xff0c;只有在偶数位才有可能存在1&#xff0c;那么一定存在这样的数&#xff0c;0 ,1,100, 10000,那么反之&#xff0c;我们的数列是行的二倍&#xff0c;因此会出现10,1000,100000这样的数&#xff0…