【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 falseUCLASS()
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.mzph.cn/diannao/66820.shtml

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

相关文章

<C++> XlsxWriter写EXCEL

XlsxWriter XlsxWriter是一个用于创建和写入Excel 2007及以上版本&#xff08;.xlsx文件格式&#xff09;的C库。以下是对XlsxWriter的详细介绍&#xff1a; 主要功能 文本、数字和公式写入&#xff1a;可以向多个工作表中写入文本、数字和公式。格式设置&#xff1a;支持丰…

linux 设置mysql 外网访问

1、修改 MySQL 配置文件 找到并编辑配置文件&#xff1a;在Linux系统中&#xff0c;MySQL的配置文件通常是/etc/mysql/my.cnf&#xff0c;使用命令sudo vim /etc/mysql/my.cnf打开文件。 注释或修改 bindaddress&#xff1a;找到bindaddress 127.0.0.1&#xff0c;将其注释掉…

JavaEE之定时器及自我实现

在生活当中&#xff0c;有很多事情&#xff0c;我们不是立马就去做&#xff0c;而是在规定了时间之后&#xff0c;在到该时间时&#xff0c;再去执行&#xff0c;比如&#xff1a;闹钟、定时关机等等&#xff0c;在程序的世界中&#xff0c;有些代码也不是立刻执行&#xff0c;…

国产3D CAD将逐步取代国外软件

在工业软件的关键领域&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;软件对于制造业的重要性不言而喻。近年来&#xff0c;国产 CAD 的发展态势迅猛&#xff0c;展现出巨大的潜力与机遇&#xff0c;正逐步改变着 CAD 市场长期由国外软件主导的格局。 国产CAD发展现状 …

【HarmonyOS Next NAPI 深度探索1】Node.js 和 CC++ 原生扩展简介

【HarmonyOS Next NAPI 深度探索1】Node.js 和 CC 原生扩展简介 如果你用过 Node.js&#xff0c;应该知道它强大的地方在于能处理各种场景&#xff0c;速度还很快。但你有没有想过&#xff0c;Node.js 的速度秘密是什么&#xff1f;今天我们来聊聊其中一个幕后英雄——原生扩展…

new[]创建对象数组会怎样?

当你使用 new[] 操作符来创建一个对象数组时&#xff0c;构造函数会被调用与数组大小相等的次数。每个数组元素都会通过构造函数进行初始化。 以下是一个例子&#xff1a; #include <iostream> class MyClass { public:MyClass() {std::cout << "MyClass con…

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

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

数据结构C语言描述11(图文结合)--二叉搜索树(BST树)的实现(数据采用KV存储形式进行封装)

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

Chrome_60.0.3112.113_x64 单文件版 下载

单文件&#xff0c;免安装&#xff0c;直接用~ Google Chrome, 免費下載. Google Chrome 60.0.3112.113: Chrome 是 Google 開發的網路瀏覽器。它的特點是速度快,功能多。 下载地址: https://blog.s3.sh.cn/thread-150-1-1.htmlhttps://blog.s3.sh.cn/thread-150-1-1.html

概率论与数理统计总复习

复习课本&#xff1a;中科大使用的教辅《概率论和数理统计》缪柏其、张伟平版本 目录 0.部分积分公式 1.容斥原理 2.条件概率 3.全概率公式 4.贝叶斯公式 5.独立性 6.伯努利分布&#xff08;两点分布&#xff09; 7.二项分布 8.帕斯卡分布&#xff08;负二项分布&am…

xml简介

目录 基本语法特点及应用场景一个简单示例 xml&#xff08;全称eXtensible Markup Language&#xff09;是一种用于存储和传输数据的标记语言&#xff0c;跨平台并且跨语言&#xff0c;xml内容较多&#xff0c;这篇文章会介绍一些基础的内容。 基本语法 xml文档通常以xml声明开…

【线性代数】通俗理解特征向量与特征值

这一块在线性代数中属于重点且较难理解的内容&#xff0c;下面仅个人学习过程中的体会&#xff0c;错误之处欢迎指出&#xff0c;有更简洁易懂的理解方式也欢迎留言学习。 文章目录 概念计算几何直观理解意义PS.适用 概念 矩阵本身就是一个线性变换&#xff0c;对一个空间中的…

Conmi的正确答案——Cordova使用“src-cordova/config.xml”编辑“Android平台”的“uses-permission”

Cordova版本&#xff1a;12.0.0 (cordova-lib12.0.1) 1、配置例程&#xff1a; <platform name"android"><config-file target"AndroidManifest.xml" parent"/manifest"><uses-permission android:name"android.permission…

React 元素渲染

React 元素渲染 React 是一个用于构建用户界面的 JavaScript 库&#xff0c;它允许开发人员创建大型应用程序&#xff0c;这些应用程序可以随着时间的推移而高效地更新和渲染。React 的核心概念之一是元素渲染&#xff0c;它描述了如何将 JavaScript 对象转换为 DOM&#xff0…

IDEA中创建maven项目

1. IDEA中创建maven项目 在IDEA中创建Maven项目&#xff0c;前提是已经安装配置好Maven环境。如还未配置安装Maven的&#xff0c;请先下载安装。如何下载安装&#xff0c;可参考我另外篇文章&#xff1a;maven的下载与安装教程本篇教程是以创建基于servlet的JavaWeb项目为例子&…

windows记事本不显示下划线

问题已解决 问题复现 解决方法 原因特定情况下默认的字体大小会导致下划线不可见&#xff0c;只需crtl加号的快捷键或者ctrl前或者后滚动鼠标滚轮以改变字体大小即可

Python爬虫应用领域

Python爬虫作为一种强大的数据获取工具&#xff0c;在多个领域发挥着重要作用。以下是Python爬虫在不同领域的应用情况&#xff1a; 一、数据采集与分析 &#xff08;一&#xff09;市场调研 产品信息收集&#xff1a;爬取电商平台的产品详情、价格、销量、用户评价等数据&am…

Android车机DIY开发之学习篇(五)默认应用修改

Android车机DIY开发之学习篇(五)默认应用修改 android默认应用位置 sdk/packages/apps InitRC配置 应用安装的目录 /system/priv-app 该路径存放一些系统底层的应用&#xff0c;比如Setting&#xff0c;systemUI等。该目录中的app拥有较高的系统权限&#xff0c;而且如果要使…

git问题

拉取项目代码后&#xff0c;出现 1、找回未commit的代码 2、记录不全&#xff0c;只是显示部分代码记录

Operation001-Install

操作001&#xff1a;RabbitMQ安装 一、安装 # 拉取镜像 docker pull rabbitmq:3.13-management# -d 参数&#xff1a;后台运行 Docker 容器 # --name 参数&#xff1a;设置容器名称 # -p 参数&#xff1a;映射端口号&#xff0c;格式是“宿主机端口号:容器内端口号”。5672供…