技术解析 | 适用于TeamCity的Unreal Engine支持插件,提升游戏构建效率
龙智是JetBrains授权合作伙伴、Perforce授权合作伙伴,为您提供TeamCity、Perforce Helix Core等热门的游戏开发工具及一站式服务

TeamCity 是游戏开发的热门选择,大家选择它的原因包括支持 Perforce、可以进行本地安装,并提供了多种配置选项。除了将它主要用作通用 CI/CD 解决方案之外,JetBrains 还努力为各种构建工具提供专门支持。JetBrains 已提供 Unity 插件数年,去年也正式推出了 Unreal Engine 支持插件!
在这款新插件的开发过程中,JetBrains 一直与多位游戏开发客户保持密切联系,以确保它符合从事实际 Unreal Engine 项目工作的 DevOps 团队的需求。
为 Unreal Engine 游戏设置合适的构建管道可能是一项艰巨的任务,特别是对于那些在平台独特性方面经验有限的人来说。本文,我们将带您了解基本设置,同时会展示该插件的功能,并演示分布式 BuildGraph 支持等高级功能。

新 Unreal Engine 插件生成的示例构建管道
主要功能
以下是此版插件所提供功能的快速概览:
-
专为最常见用例(BuildCookRun、BuildGraph 和自动化测试)定制的专用运行程序。
-
基于 BuildGraph 描述构建发行版。
-
即时自动化测试报告。
-
自动发现构建计算机上的 Unreal Engine 安装。
-
兼容最新的 5.x 版 Unreal Engine,还支持 4.x 版本。
演示
在本演示中,我们将为随 Unreal 提供的两款入门级游戏 Cropout 和 Lyra 设置管道:我们将为构建代理和安装了 Unreal Engine 插件的 TeamCity Cloud 使用 AWS 基础架构(EC2 和用于 FSx for OpenZFS)。
我们将介绍两个场景:引擎已安装在代理上,以及引擎与游戏一起通过源代码构建。目前,TeamCity Cloud 不为代理提供预装的 Unreal Engine,但您可以随时添加您自己的包含所有必需 SDK 的自托管代理。我们将在这篇博文中采用这种方式。
使用 BuildCookRun 构建 Cropout
构建 Unreal 项目通常涉及多个步骤,例如:
-
采用指定配置针对目标平台进行编译。
-
资源烘焙(将所有资源转换为可在目标平台上读取的资源)。
-
将项目封装为合适的分发格式。
-
等等。
当然,之后还要进行测试!
首先,我们来看一个简单的场景,并使用标准 BuildCookRun 命令在一台计算机上按顺序运行所有阶段。
对于此场景,我们将使用撰写这篇博文时可以获取的最新普通版 Unreal Engine,并通过 EGL(Epic Games 启动器)进行安装。
将代理成功连接到 TeamCity Cloud 服务器后,我们可以在 Agents(代理)标签页中看到它。

现在,我想简单探讨一下代理的一些属性。

查看上图,我们可以看到代理发现了引擎及其版本(如您所见,我们的计算机上安装了多个引擎)。请记下此信息,因为稍后我们会将此信息用于构建步骤配置。
现在,常用的方式是将您所有的配置以代码形式存储在源代码控制系统下(也就是“配置即代码”)。在 TeamCity 中,您可以使用 Kotlin DSL 执行此操作。当然,您还可以使用传统 UI 配置,但今天我们将使用第一种方式(由于 YAML 已得到广泛使用,并且已成为事实上的标准,我们已将它添加到最近发布的 TeamCity Pipelines – 如果您还没有查看,请查看)。
用于在 TeamCity 中构建 Cropout 的 Kotlin DSL 配置如下所示:
unrealEngine {engineDetectionMode = automatic {identifier = "5.4"}command = buildCookRun {project = "cropout/CropoutSampleProject.uproject"buildConfiguration = standaloneGame {configurations = "Development+Shipping"platforms = "Mac"}cook = cookConfiguration {maps = "Village+MainMenu"cultures = "en"unversionedContent = true}stage = stageConfiguration {directory = "./staged"}archive = archiveConfiguration {directory = "./archived"}pak = truecompressed = trueprerequisites = true}additionalArguments = "-utf8output -buildmachine -unattended -noP4 -nosplash -stdout -NoCodeSign"
}
所有设置的作用都一目了然,但有几项设置需要进一步说明:
-
engine detection mode我们可以在此处指定“automatic”或“manual”。前者假定您已经在代理上安装引擎并在系统中注册。
我借此机会澄清一下“注册 ”的含义:当您通过 EGL 安装 Unreal Engine 或通过源代码构建 Unreal Engine 时,此操作会写入目标计算机上的某些文件(如果使用的是 Windows,则会写入注册表)。
这基本上就是自动检测模式的用途。我们读取这些文件并发布标识符作为代理属性,这样一来,您稍后便可使用这些属性为构建选择合适的代理。
“手动”模式允许您设置 Unreal Engine 根文件夹的准确路径,当您通过源代码构建时可能会用到该路径。稍后我们将介绍这部分内容。
-
build configuration通过调整此参数,我们可以指定所创建构建的类型,它是单机游戏、客户端、服务器,还是既是客户端又是服务器组件。
根据所选的值,插件将应用
-client、-server、-noserver标志,并相应地管理-clientconfig、-serverconfig、-config、-targetplatform和-servertargetplatform参数。
到现在为止都没问题。现在,该添加一些自动化测试并运行最终管道了。
unrealEngine {engineDetectionMode = automatic {identifier = "5.4"}command = runAutomation {project = "cropout/CropoutSampleProject.uproject"execCommand = runTests {tests = """StartsWith:JsonConfigInput.Triggers.Released""".trimIndent()}nullRHI = true}additionalArguments = "-utf8output -buildmachine -unattended -noP4 -nosplash -stdout -NoCodeSign"
}
如您所知,使用 Unreal Engine 自动化框架时,通常使用以下自动化“子命令”之一执行测试:
-
RunAll– 这是一个非常简单的命令,能够运行所有必需的测试。 -
RunFilter– 借助此命令,可以执行使用一个指定筛选器(包括Engine、Stress、Smoke等值)标记的测试。 -
RunTests– 这是最通用的命令,因为您可以使用该命令指定要运行的测试列表(包括使用StartsWith的前缀筛选器、运行组筛选器和简单的子字符串匹配)。
我们尝试在 Kotlin DSL 中保留相同的用词,以便 Unreal 用户对此感到熟悉。
还要指出的是,插件会即时解析测试结果,并以 TeamCity 自带的正确显示的格式呈现测试结果。

以下是构建结果示例:

在下方,我们可以看到发布到工件存储的游戏二进制文件。默认情况下,工件会发布到内置存储;但 TeamCity 还支持多种外部存储选项。在云环境中,可以配置发布到 S3。

UI 中的最终配置将如下所示:

下面是测试运行的步骤:

由于我们已启用 Kotlin DSL 配置并禁止在 UI 中对其进行编辑,所有设置均被禁用。
使用 BuildGraph 构建 Lyra
我们将逐步过渡到使用 Lyra 的更复杂示例。由于它是一款多人游戏,我们来构建 Linux 服务器(据我们所知,一切高性能游戏都应在 Linux 上运行!)以及 Windows 和 Linux 游戏客户端。
在前面的示例中,我们在一台计算机上按顺序运行了所有步骤。这一过程通常需要大量时间。但还有一种更好的方式,即 BuildGraph。这是一种 Unreal 方法,以声明方式(或几乎以声明方式)将构建描述为一组具有依赖关系的任务。随后,构件图的不同部分可以共同执行,或拆分到不同的计算机上。后一种方式可以显著加快整个构建过程的速度,特别适合大型复杂项目。
BuildGraph 的优点是它会自动管理作业之间的所有中间工件。您只需要共享存储。构建 Lyra 时,我们会将此共享存储用于两个目的:在单个构建中的节点之间共享数据,以及维护共享派生数据缓存 (DDC)。
我们之前提到过,我们还将通过源代码构建引擎。您通过源代码构建引擎可能是出于多种原因,但就我们的特定情况而言,我们这样做是因为无法使用任何方式通过引擎(从 EGL 安装)构建 Linux 服务器(至少在撰写这篇博文时无法实现)。这是因为此特定引擎版本未提供相应文件和 SDK。
好的,序言介绍完毕 — 我们来看看代码。
项目结构和 Perforce 中的相应流如下所示(假设您已从 Epic 的 Perforce 获取 Unreal Engine 源代码):

下面是我们将用于构建的 BuildGraph XML 脚本的节选。脚本的这一特定部分给出了编辑器的构建方式以及编译所需的工具, 其中还包含几项测试。在我们的简单示例中,这些测试采用硬编码。在真实场景中,您可能会将测试列表传递给脚本并采用更复杂的逻辑。
...
<!-- Editors --><ForEach Name="Platform" Values="$(EditorPlatforms)" Separator="+"><Property Name="Compiler" Value="$(AgentPrefixCompile)$(Platform)" /><Agent Name="Build Editor and tools $(Platform)" Type="$(Compiler)">...<Property Name="ExtraToolCompileArguments" Value="$(ExtraToolCompileArguments) -architecture=arm64" If="'$(Platform)' == 'Mac'" /><Node Name="$(ToolsNodeName) $(Platform)" Requires="Setup Toolchain $(Platform)" Produces="#$(Platform)ToolBinaries"><Compile Target="CrashReportClient" Platform="$(Platform)" Configuration="Shipping" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries"/><Compile Target="CrashReportClientEditor" Platform="$(Platform)" Configuration="Shipping" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries"/><Compile Target="ShaderCompileWorker" Platform="$(Platform)" Configuration="Development" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries"/><Compile Target="UnrealLightmass" Platform="$(Platform)" Configuration="Development" Tag="#$(Platform)ToolBinaries"/><Compile Target="InterchangeWorker" Platform="$(Platform)" Configuration="Development" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries"/><Compile Target="UnrealPak" Platform="$(Platform)" Configuration="Development" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries"/><Compile Target="BootstrapPackagedGame" Platform="$(Platform)" Configuration="Shipping" Arguments="$(ExtraToolCompileArguments)" Tag="#$(Platform)ToolBinaries" If="'$(Platform)' == 'Win64'"/></Node>...<Node Name="$(EditorNodeName) $(Platform)" Requires="$(ToolsNodeName) $(Platform)" Produces="#$(Platform)EditorBinaries"><Compile Target="$(ProjectName)Editor" Project="$(UProject)" Platform="$(Platform)" Configuration="Development" Arguments="$(ExtraEditorCompileArguments)" Tag="#$(Platform)EditorBinaries"/></Node><Property Name="AutomationTestsNodeName" Value="Run Tests $(Platform)" /><Node Name="$(AutomationTestsNodeName)" Requires="$(EditorNodeName) $(Platform)"><Property Name="TestArgs" Value="-Project=$(UProject) -NullRHI -Deterministic" /><Property Name="TestArgs" Value="$(TestArgs) -Test=UE.EditorAutomation -RunTest="StartsWith:Input"" /><Property Name="TestArgs" Value="$(TestArgs) -Build=Editor -UseEditor" /><Command Name="RunUnreal" Arguments="$(TestArgs)" /></Node></Agent><Property Name="BuildNodes" Value="$(BuildNodes);$(EditorNodeName) $(Platform);$(AutomationTestsNodeName);" /></ForEach>...
有关完整语法的描述,请参阅 Epic 网站。这里,我们本质上要做的是通过选项 EditorPlatforms 迭代传递给脚本的平台列表,并为每个平台构建编辑器。这部分代码的一个有趣的功能是代理节点的 Type 属性。
<Agent ... Type="">
在文档中查看此表:

因此,为了在特定代理上运行一组任务,您可以修改此字段。撰写这篇博文时,为了确保一切正常运行,本部分需要对构建代理进行一些配置。也就是说,您应当在代理配置文件中定义两个属性:
-
unreal-engine.build-graph.agent.type:此属性可以是任意值(或由 ; 分隔的值列表)。相应的任务集随后将仅在至少有一个匹配项的代理上运行。 -
unreal-engine.build-graph.agent.shared-dir:前文中讨论过,为了运行分布式 BuildGraph 构建,我们需要一个共享存储位置。通过此属性,我们可以在特定代理上设置该存储位置的路径。
以下是我们的 EC2 构建代理设置:
-
对于 Linux:
unreal-engine.build-graph.agent.type = CompileLinux;CookLinux;Linux
unreal-engine.build-graph.agent.shared-dir = /mnt/agent-shared-dir/intermediate
- 对于 Windows(由于某些原因,将文件夹作为单独的驱动器进行装载导致一次 Windows 系统调用失败,因此我们在配置中选择了网络共享):
unreal-engine.build-graph.agent.type = CompileWin64;CookWin64;Win64
unreal-engine.build-graph.agent.shared-dir = \\\\fs-040b8d6dab476baf1.fsx.eu-west-1.amazonaws.com\\fsx\\
在真实场景中,可能需要区分执行烘焙的代理和执行编译的代理,并确保它们采用合适的规范。
我们来快速浏览一下游戏的编译、烘焙和封装过程。在我们的脚本中,我们已将构建客户端的逻辑和构建服务器的逻辑分开(因为它们传递的标志不同):
<ForEach Name="Platform" Values="$(ClientPlatforms)" Separator="+"><!-- COMPILATION --><Property Name="Compiler" Value="$(AgentPrefixCompile)$(Platform)" /><Agent Name="Compile $(Platform) Client" Type="$(Compiler)"><Property Name="CompileNodeName" Value="Compile $(Platform) Client" /><Node Name="$(CompileNodeName)" Requires="$(ToolsNodeName) $(Platform)" Produces="#$(Platform) Client Binaries"><ForEach Name="TargetConfiguration" Values="$(TargetConfigurations)" Separator="+"><Compile Target="$(ProjectName)Client" Project="$(UProject)" Platform="$(Platform)" Configuration="$(TargetConfiguration)" Arguments="$(ExtraProjectCompileArguments)" /></ForEach></Node><Property Name="BuildNodes" Value="$(BuildNodes);$(CompileNodeName);" /></Agent><!-- COOKING --><Property Name="Cooker" Value="$(AgentPrefixCook)$(Platform)" /><Property Name="CookPlatformNodeName" Value="Cook $(Platform) Client" /><Agent Name="Cook $(Platform) Client" Type="$(Cooker)"><Property Name="CookPlatform" Value="$(Platform)" /><Property Name="CookPlatform" Value="Windows" If="'$(Platform)' == 'Win64'" /><Node Name="$(CookPlatformNodeName)" Requires="$(EditorNodeName) $(Platform)" Produces="#Cook $(Platform) Client Complete"><Cook Project="$(UProject)" Platform="$(CookPlatform)Client"/></Node></Agent><Property Name="BuildNodes" Value="$(BuildNodes);$(CookPlatformNodeName);" /><!-- PACKAGING --><Agent Name="Package $(Platform) Client" Type="$(Platform)"><Property Name="BCRArgs" Value="-Project='$(UProject)' -Platform=$(Platform) -NoCodeSign -Client" /><!-- Stage --><Node Name="Stage $(Platform) Client" Requires="Compile $(Platform) Client;Cook $(Platform) Client"><ForEach Name="TargetConfiguration" Values="$(TargetConfigurations)" Separator="+"><Command Name="BuildCookRun" Arguments="$(BCRArgs) -Configuration=$(TargetConfiguration) -SkipBuild -SkipCook -Stage -Pak" /></ForEach></Node><!-- Package --><Node Name="Package $(Platform) Client" Requires="Stage $(Platform) Client"><ForEach Name="TargetConfiguration" Values="$(TargetConfigurations)" Separator="+"><Command Name="BuildCookRun" Arguments="$(BCRArgs) -Configuration=$(TargetConfiguration) -SkipBuild -SkipCook -SkipStage -Package" /></ForEach></Node><!-- Publish (Packages) --><Node Name="Archive $(Platform) Client" Requires="Package $(Platform) Client"><ForEach Name="TargetConfiguration" Values="$(TargetConfigurations)" Separator="+"><Command Name="BuildCookRun" Arguments="$(BCRArgs) -Configuration=$(TargetConfiguration) -SkipBuild -SkipCook -SkipStage -SkipPak -SkipPackage -Archive" /></ForEach></Node><Node Name="Publish $(Platform) Client" Requires="Archive $(Platform) Client"><Property Name="PublishPlatform" Value="$(Platform)" /><Property Name="PublishPlatform" Value="Windows" If="'$(Platform)' == 'Win64'" /><Log Message="##teamcity[publishArtifacts 'game/ArchivedBuilds/$(PublishPlatform)Client=>$(PublishPlatform)Client.zip']" /></Node></Agent><Property Name="BuildNodes" Value="$(BuildNodes);Archive $(Platform) Client;Publish $(Platform) Client" />
</ForEach>
脚本本身不需要过多解释。我们会迭代希望客户端在其上运行的平台列表,该列表会再次作为选项传递。我们针对每个平台进行编译、烘焙资源,最后将一切封装起来。作为封装过程的最后一步,我们使用服务消息将构建的客户端作为 TeamCity 构建工件发布。
您可能已经注意到,在这部分脚本中,我们有三个代理代表构建过程的三个阶段(编译、烘焙和封装)。编译需要使用属于编辑器的工具,但不需要编辑器本身:
<Node Name="$(CompileNodeName)" Requires="$(ToolsNodeName) $(Platform)" Produces="#$(Platform) Client Binaries">
同时,烘焙过程需要实际的编辑器:
<Node Name="$(CookPlatformNodeName)" Requires="$(EditorNodeName) $(Platform)" Produces="#Cook $(Platform) Client Complete">
由于这两个过程之间没有依赖关系,它们可以在不同的计算机上并行运行(在有足够多的计算机可用的情况下)。这一简单示例演示了使用 BuildGraph 的强大功能:您只需描述共享依赖项的各项工作,然后它们就会同时运行。
为了简洁起见,我们不会显示包含服务器部分的脚本的其余部分,因为这部分内容与我们刚才描述的内容非常相似,只有标志集合不同。
最后,我们要介绍 TeamCity 中的插件配置。由于大部分工作已在 BuildGraph XML 脚本中描述,DSL 配置相当简单:
params {param("env.UE_SharedDataCachePath", "/mnt/agent-shared-dir/ddc")param("env.UE-SharedDataCachePath", "\\\\fs-040b8d6dab476baf1.fsx.eu-west-1.amazonaws.com\\fsx\\ddc")
}
steps {unrealEngine {id = "Unreal_Engine"name = "Build"engineDetectionMode = manual {rootDir = "engine"}command = buildGraph {script = "game/BuildProject.xml"targetNode = "BuildProject"options = """ProjectPath=%teamcity.build.checkoutDir%/gameProjectName=LyraClientPlatforms=Linux+Win64ServerPlatforms=LinuxEditorPlatforms=Linux+Win64TargetConfigurations=Shipping""".trimIndent()mode = UnrealEngine.BuildGraphMode.Distributed}additionalArguments = "-utf8output -buildmachine -unattended -noP4 -nosplash -stdout -NoCodeSign"}
}
在这里,指定我们希望在分布式模式下运行提供的 BuildGraph 脚本。但我们可以根据自己的意愿随时选择在一台计算机上按顺序运行所有节点。我们还指定了一些环境变量,如果引擎要启用共享 DDC,则需要使用这些变量。这些文件夹应当已装载到连接至 TeamCity 的代理。
现在,您可以为我们的游戏传递以下包含三个平台选项的列表:ClientPlatforms、ServerPlatforms 和 EditorPlatforms。
UI 中的显示如下:

启动构建时,我们会获得此构建链:

因此,如我们所见,插件已将构建图转换为适当的 TeamCity 构建链。
我们可以在指定构建的相应标签页上查看已发布的工件。Linux 服务器的显示如下:

在结束之前,我们想强调一些要点,请您务必牢记:
-
为了确保 BuildGraph 分发过程正常运行,您的构建配置应仅包含一个有效构建步骤。
-
目前,该插件不会以任何方式管理共享存储中生成的工件的保留期。您负责正确设置保留期。
-
这篇博文中的示例绝不可用于生产。您的真实场景可能会更加复杂。但这些示例可作为一个良好的起点。
您可以从 GitHub 仓库中获取这篇博文中展示的演示代码。如果您想尝试用于 TeamCity 的 Unreal Engine 插件,可从 JetBrains Marketplace 下载,并在 TeamCity On-Premises 服务器上安装。
对于 TeamCity Cloud 用户,我们已预装了 Unreal Engine 插件,您只需添加 Unreal Engine 构建步骤便可使用此插件。需要提醒您的是,TeamCity Cloud 代理没有预装 Unreal Engine,因此您需要使用自托管代理运行 Unreal Engine 构建。
针对一直使用预览版插件(从 0.x.x 开始的任何版本)的用户的重要说明:请注意,由于我们在 1.0.0 版中进行了更改并重新设计了多个功能,您的现有配置将被破坏。您可能需要使用新版插件重新创建这些配置。
后续计划
首先,我们期待收到您的反馈。您可以随时联系我们:通过我们的问题跟踪器提交工单或对这篇博文发表评论。
我们对今后的工作还有一些想法,包括:
-
提供更加深入的构建日志分析。
-
包含与 UnrealGameSync (UGS) 的集成,添加构建状态发布等功能,以及提供开箱即用的元数据服务器。
-
添加对构建计算机上可用 SDK 的检测,可能会使用整体解决方案。
-
了解 Gauntlet 自动化框架以及我们可以在其中执行的操作。
-
了解我们如何利用 Epic Games 推出的构建过程中的最新进展,即查看 Unreal Build Accelerator (UBA) 并通过 TeamCity 在代理上进行协调。
-
将插件开源。我们相信这将提高透明度,并从整体上创建更好的插件。此外,开源会带来很多乐趣。
本博文英文原作者:Vladislav Grinin
关于 TeamCity
TeamCity 是一款强大的持续集成和部署服务器,面向以 DevOps 为中心的团队提供开箱即用的测试智能、构建问题的实时报告以及无与伦比的可扩展性。安装和部署 TeamCity,几分钟之内即可开始构建您的 DevOps 管道。TeamCity 提供本地部署和基于云的版本。
进一步了解 TeamCity:
JetBrains中国授权合作伙伴-龙智
官网:www.shdsd.com
电话:400-666-7732
邮箱:marketing@shdsd.com
相关文章:
技术解析 | 适用于TeamCity的Unreal Engine支持插件,提升游戏构建效率
龙智是JetBrains授权合作伙伴、Perforce授权合作伙伴,为您提供TeamCity、Perforce Helix Core等热门的游戏开发工具及一站式服务 TeamCity 是游戏开发的热门选择,大家选择它的原因包括支持 Perforce、可以进行本地安装,并提供了多种配置选项。…...
Ubuntu22.04 - brpc的安装和使用
目录 介绍安装使用 介绍 brpc 是用 c语言编写的工业级 RPC 框架,常用于搜索、存储、机器学习、广告、推荐等高性能系统 安装 先安装依赖 apt-get install -y git g make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev libgflags-d…...
网络运维学习笔记 018 HCIA-Datacom综合实验02
文章目录 综合实验2sw3:sw4:gw:core1(sw1):core2(sw2):ISP 综合实验2 sw3: vlan 2 stp mode stp int e0/0/1 port link-type trunk port trunk allow-pass v…...
Vulhub靶机 Apache Druid(CVE-2021-25646)(渗透测试详解)
一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 1、漏洞范围 在Druid0.20.0及更低版本中 二、访问靶机IP 8888端口 1、点击Load data进入新界面后,再点击local disk按钮。 2、进入新界面后,在标红框的Base directory栏写上…...
VSCode配置自动生成头文件
一、配置步骤: 1.打开命令面板(CtrlShiftp): 2.输入snippets 选择配置代码片段 3. 选择新建全局代码片段 输入文件名,比如header_cpp(随便定义),然后点击键盘回车按钮,得到下面这个文件。 增加配置文…...
Xcode如何高效的一键重命名某个关键字
1.选中某个需要修改的关键字; 2.右击,选择Refactor->Rename… 然后就会出现如下界面: 此时就可以一键重命名了。 还可以设置快捷键。 1.打开Settings 2.找到Key Bindings 3.搜索rename 4.出现三个,点击一个地方设置后其…...
React 高阶组件的优缺点
React 高阶组件的优缺点 优点 1. 代码复用性高 公共逻辑封装:当多个组件需要实现相同的功能或逻辑时,高阶组件可以将这些逻辑封装起来,避免代码重复。例如,多个组件都需要在挂载时进行数据获取操作,就可以创建一个数…...
(五)趣学设计模式 之 建造者模式!
目录 一、 啥是建造者模式?二、 为什么要用建造者模式?三、 建造者模式怎么实现?四、 建造者模式的应用场景五、 建造者模式的优点和缺点六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方…...
香橙派/树莓派 利用Wiring库 使用GPIO模拟PWM
香橙派或者树莓派 等开发板,本身带有硬件PWM,比如香橙派3 lts版,但是这个引脚不符合我的项目需求,我需要外接一个电机,在检测到人脸的时候 转动,但是这个硬件引脚,只要上电就开始输出pwm 信号,导…...
全面收集中间件Exporter适配:从Redis到ActiveMQ,掌握监控数据采集的最佳实践
#作者:任少近 文章目录 说明:一 Redis的适配exporter版1.1 Redis的exporter源码版本1.2 Redis的exporter的releases版1.3 Redis_exporter版本选择理由1.4 Redis_exporter docer镜像 二 Zookeeper的适配exporter版2.1 Zookeeper的exporter源码版本2.2 Zo…...
机器学习数学通关指南——链式法则
前言 本文隶属于专栏《机器学习数学通关指南》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见《机器学习数学通关指南》 正文 一、定义与公式 链式法则&a…...
JavaScript函数-arguments的使用
在JavaScript编程语言中,函数是构建复杂逻辑和实现代码复用的关键组件。虽然现代JavaScript(尤其是ES6及之后版本)提供了更多灵活的方式来处理函数参数(如剩余参数、默认参数等),但arguments对象仍然是一个…...
千峰React:函数组件使用(2)
前面写了三千字没保存,恨! 批量渲染 function App() {const list [{id:0,text:aaaa},{id:1,text:bbbb},{id:2,text:cccc}]// for (let i 0; i < list.length; i) {// list[i] <li>{list[i]}</li>// }return (<div><…...
DPVS-3: 双臂负载均衡测试
测试拓扑 双臂模式, 使用两个网卡,一个对外,一个对内。 Client host是物理机, RS host都是虚拟机。 LB host是物理机,两个CX5网卡分别在两个子网。 配置文件 用dpvs.conf.sample作为双臂配置文件,其中…...
2016年下半年试题二:论软件设计模式及其应用
论文库链接:系统架构设计师论文 论文题目 软件设计模式(Software DesignPatter)是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式是为了重用代码以提高编码效率增加代码的可理解性、保证代码的可靠性。软件设计模式是软件开发中的…...
深入理解 SQL 中的 DATEDIFF 函数
深入理解 SQL 中的 DATEDIFF 函数 DATEDIFF 函数在 SQL 中是一个用于计算两个日期之间差值的重要工具。不同数据库实现了不同版本的 DATEDIFF,它们在功能和语法上有所不同。本文将详细解析 DATEDIFF 的用法、数据库间差异、复杂场景中的应用,以及替代方…...
【第二节】C++设计模式(创建型模式)-抽象工厂模式
目录 引言 一、抽象工厂模式概述 二、抽象工厂模式的应用 三、抽象工厂模式的适用场景 四、抽象工厂模式的优缺点 五、总结 引言 抽象工厂设计模式是一种创建型设计模式,旨在解决一系列相互依赖对象的创建问题。它与工厂方法模式密切相关,但在应用…...
【学习资料】嵌入式人工智能Embedded AI
图片来源: Embedded Artificial Intelligence for Business Purposes | DAC.digital 随着AI在设备端的应用,我们看到越来越多的可穿戴设备出现以及自动驾驶汽车的发展,可以看到嵌入式人工智能是新的发展方向。我为大家介绍嵌入式人工智能的…...
【Python爬虫(60)】解锁社交媒体数据宝藏:Python爬虫实战攻略
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
C++ 继承,多态
看前须知: 本篇博客是作者听课时的笔记,不喜勿喷,若有疑问可以评论区一起讨论。 继承 定义: 继承机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有 类特性的基础上进⾏扩展,增…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
