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

news2025/1/14 20:44:54

目录

步骤

一、搭建基本同步框架 

二、添加委托 

三、添加蓝图互动框架 

四、修改为异步框架

完整代码


通过一个游戏初始化流程的示例来介绍“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 = {};
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WeakAuras NES Script(lua)

WeakAuras NES Script 修星脚本字符串 脚本1&#xff1a;NES !WA:2!TMZFWXX1zDxVAs4siiRKiBN4eV(sTRKZ5Z6opYbhQQSoPtsxr(K8ENSJtS50(J3D7wV3UBF7E6hgmKOXdjKsgAvZFaPTtte0mD60XdCmmecDMKruyykDcplAZiGPfWtSsag6myGuOuq89EVDV9wPvKeGBM7U99EFVVVV33VFFB8Z2TJ8azYMlZj7Ur3QDR(…

android进入fastboot

安装windows驱动。android进入fastboot模式后&#xff0c;需要Windows驱动来跟adb通信&#xff0c;所以需要预先安装Windows usb驱动&#xff0c;否则进入fastboot模式后&#xff0c;无法使用adb连接手机。 下载网址&#xff1a;https://developer.android.com/studio/run/win-…

LabVIEW光流跟踪算法

1. 光流跟踪算法的概述 光流&#xff08;Optical Flow&#xff09;是一种图像处理技术&#xff0c;用于估算图像中像素点的运动。通过比较连续帧图像&#xff0c;光流算法可以分析图像中的运动信息&#xff0c;广泛用于目标跟踪、运动检测和视频处理等场景。该示例使用了NI Vi…

系统看门狗配置--以ubuntu为例

linux系统配置看门狗 以 ubuntu 系统配置看门狗为例 配置看门狗使用的脚本文件&#xff0c;需要使用管理员权限来执行&#xff1a; 配置是&#xff1a;系统每 30S 喂一次狗&#xff0c;超过 60S 不进行投喂&#xff0c;就会自动重启。 1. 系统脚本内容&#xff1a; #!/bin/b…

Windows的Redis查看自己设置的密码并更改设置密码

查看密码 由于我的Redis安装很久了&#xff0c;所以忘记是否有设置密码&#xff0c;查看步骤如下&#xff1a; 启动redis&#xff0c;启动流程可以看这篇文章&#xff1a;https://blog.csdn.net/changyana/article/details/127679871 在redis安装目录下打开redis-cli.exe&…

E10.【C语言】练习:编写一个猜数字游戏

目录 1.规则 2.准备 3.游戏代码 1.规则 1.程序生成1-100间的随机数 2.用户猜数字 猜对了&#xff1a;游戏结束 猜错了&#xff1a;程序会告知猜大了或猜小了&#xff0c;继续进行游戏&#xff0c;直到猜对 3.游戏可以一直玩除非退出游戏 2.准备 1.框架&#xff1a;循…

《异步编程之美》— 全栈修仙《Java 8 CompletableFuture 对比 ES6 Promise 以及Spring @Async》

哈喽&#xff0c;大家好&#xff01;在平常开发过程中会遇到许多意想不到的坑&#xff0c;本篇文章就记录在开发过程中遇到一些常见的问题&#xff0c;看了许多博主的异步编程&#xff0c;我只能说一言难尽。本文详细的讲解了异步编程之美&#xff0c;是不可多得的好文&#xf…

kalilinux - msf和永恒之蓝漏洞

Kali最强渗透工具 - metasploit metasploit是什么&#xff1f; msf是一款开源安全漏洞利用和测试工具&#xff0c;集成了各种平台上常见的溢出漏洞和流行的sheelcode&#xff0c;并持续保持更新。 具体操作 1、先切换到root用户&#xff0c;使用msfdb init命令初始化metaspl…

【大模型入门指南 11】大模型自动评估理论和实战

【大模型入门指南】系列文章&#xff1a; 【大模型入门指南 01】深度学习入门【大模型入门指南 02】LLM大模型基础知识【大模型入门指南 03】提示词工程【大模型入门指南 04】Transformer结构【大模型入门指南 05】LLM技术选型【大模型入门指南 06】LLM数据预处理【大模型入门…

【SOC 芯片设计 DFT 学习专栏 -- DFT 接管 clock 和 reset】

文章目录 OverviewDFT 接管 Clock 和 Reset 的方法Clock 接管方法Reset 接管方法 什么场景下需要 DFT 来接管 Clock 和 Reset&#xff1f;制造测试&#xff08;Manufacturing Test&#xff09;静态路径扫描测试&#xff08;Scan Testing&#xff09;调试与故障定位&#xff08;…

从 Conda 到 Pip-tools:Python 依赖管理全景探索20250113

从 Conda 到 Pip-tools&#xff1a;Python 依赖管理全景探索 引言 在 Python 开发中&#xff0c;依赖管理是一个"常见但复杂"的问题&#xff1a;一次简单的版本冲突可能让团队调试数小时&#xff1b;一次不受控的依赖升级可能让生产环境瘫痪。随着项目规模的增加和…

【数学】概率论与数理统计(五)

文章目录 [toc] 二维随机向量及其分布随机向量离散型随机向量的概率分布律性质示例问题解答 连续型随机向量的概率密度函数随机向量的分布函数性质连续型随机向量均匀分布 边缘分布边缘概率分布律边缘概率密度函数二维正态分布示例问题解答 边缘分布函数 二维随机向量及其分布 …

《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识

目录 2.1 几何学 向量的内积和外积 旋转矩阵 旋转向量 四元数 李群和李代数 SO(3)上的 BCH 线性近似式 2.2 运动学 李群视角下的运动学 SO(3) t 上的运动学 线速度和加速度 扰动模型和雅可比矩阵 典型算例&#xff1a;对向量进行旋转 典型算例&#xff1a;旋转的复合 2.3 …

30_Redis哨兵模式

在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性…

苹果手机(IOS系统)出现安全延迟进行中如何关闭?

苹果手机&#xff08;IOS系统&#xff09;出现安全延迟进行中如何关闭&#xff1f; 一、设置二、隐私与安全性三、失窃设备保护关闭 一、设置 二、隐私与安全性 三、失窃设备保护关闭

【Oracle专栏】group by 和distinct 效率

Oracle相关文档&#xff0c;希望互相学习&#xff0c;共同进步 风123456789&#xff5e;-CSDN博客 1.背景 查阅资料&#xff1a; 1&#xff09;有索引情况下&#xff0c;group by和distinct都能使用索引&#xff0c;效率相同。 2&#xff09;无索引情况下&#xff0c;distinct…

linux:文件的创建/删除/复制/移动/查看/查找/权限/类型/压缩/打包,文本处理sed,awk

关于文件的关键词 创建 touch 删除 rm 复制 cp 权限 chmod 移动 mv 查看内容 cat(全部); head(前10行); tail(末尾10行); more,less,grep 查找 find 压缩 gzip ; bzip 打包 tar 编辑 sed 文本处理 awk 创建文件 格式&#xff1a; touch 文件名 删除文件 复制文…

day01-HTML-CSS——基础标签样式表格标签表单标签

目录 此篇为简写笔记下端1-3为之前笔记&#xff08;强迫症、保证文章连续性&#xff09;完整版笔记代码模仿新浪新闻首页完成审核不通过发不出去HTMLCSS1 HTML1.1 介绍1.1.1 WebStrom中基本配置 1.2 快速入门1.3 基础标签1.3.1 标题标签1.3.2 hr标签1.3.3 字体标签1.3.4 换行标…

哥大开发AI模型助力癌症和遗传病研究,近屿智能专注培养AI人才

近日&#xff0c;哥伦比亚大学瓦格洛斯医学院的研究团队在《自然》杂志上发表了一项重大研究成果。他们开发出一种名为“通用表达转换器”&#xff08;GET&#xff09;的新型AI模型&#xff0c;能够准确预测任何人类细胞中的基因活性&#xff0c;从而揭示细胞的内部工作机制。 …

9.4 visualStudio 2022 配置 cuda 和 torch (c++)

一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客&#xff0c;作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为&#xff1a; 2.vs…