UE-Live Coding
<p>[TOC]</p>
<table>
<thead>
<tr>
<th>作者</th>
<th>QFord</th>
</tr>
</thead>
<tbody>
<tr>
<td>更新日期</td>
<td>2024-6-8</td>
</tr>
</tbody>
</table>
<h1>背景</h1>
<p>我在使用<strong>Live Coding(实时编码)</strong>的时候,发现有时候修改的代码不会生效,是时候更为深入地了解Live Coding这块的相关知识了。
如果你也有相关的困惑,或者下列的问题,那么这篇资料就值得你详细看看。</p>
<ol>
<li>Live Coding 的最佳实践</li>
<li>什么时候适合使用Live Coding?</li>
<li>使用VS等IDE编译后,代码改动仍然不生效该怎么办?</li>
<li>什么是实时编码?</li>
</ol>
<h1>热重载和实时编码(Hot Reload & Live Coding)</h1>
<p>在<strong>Unreal编辑器</strong>打开时编译的注意事项
> Unreal编辑器 后文 简称为 <strong>编辑器</strong></p>
<h2>什么是“热重载”?</h2>
<p><strong>热重载</strong>是指在编辑器打开时编译新的 <strong>DLL</strong> 文件并加载它们的过程。虽然热重载通常能工作一段时间,但它不可靠,且经常导致蓝图损坏或其他问题(详情见下文)。大多数用户建议完全避免热重载,这意味着你需要关闭编辑器才能安全地编译。
> 有时候,你会遇到即使关闭了编辑器又成功编译了代码后功能代码仍然没有生效的情况,这时候你可以尝试删除UE工程文件目录中非版本管理的所有内容,如下图所示(然后再重新生成sln文件并重新编译):
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=35f14ce5ca979c40bd219cf10403f7e2&amp;file=file.png" alt="" />
在上图中,Unreal工程中通常不需要放入版本控制系统的文件和文件夹包括:
1. .vs 文件夹:Visual Studio的临时文件。
2. Binaries 文件夹:包含编译后的二进制文件。
3. DerivedDataCache 文件夹:包含派生数据缓存。
4. Intermediate 文件夹:包含临时文件,可以在需要时重新生成。
5. Saved 文件夹:包含自动保存、备份和日志文件。
6. .vsconfig 文件:Visual Studio配置文件。
7. VLabUE.sln 文件:Visual Studio的解决方案文件。
这些文件和文件夹通常会在.gitignore或其他版本控制忽略文件中被排除,以减少版本库的大小和提交的频率。</p>
<p>默认情况下,如果你在以下情况下编译,会触发热重载:</p>
<ul>
<li>在项目/Unreal 编辑器打开时从 IDE 编译</li>
<li>使用 Unreal 编辑器中的编译按钮进行编译</li>
<li>在 Unreal 编辑器中创建新的 C++ 类
你可以在编辑器首选项中禁用此设置:自动编译新添加的 C++ 类</li>
</ul>
<p>如果你触发了热重载,别慌!只需确保在不保存任何内容的情况下关闭编辑器,在 IDE 中运行构建,然后继续。</p>
<h2>什么是“实时编码”?</h2>
<p>实时编码(Live Coding)是 <strong>UE 4.22</strong> 版本引入的一个功能,旨在最终取代热重载。默认情况下,它是禁用的。你可以通过点击 Unreal 编辑器中<strong>编译按钮</strong>旁边的下拉箭头并打开实时编码复选框来启用实时编码,如下图所示:
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=09ff37c7fc7a20df286393a597d88778&amp;file=file.png" alt="" /></p>
<p>你可以通过按 <strong>Ctrl + Alt + F11</strong> 来启动实时编码构建。这个快捷键在任何地方都有效(无论是 IDE 还是 Unreal 编辑器处于焦点)。</p>
<p>实时编码仍有一些限制:</p>
<ul>
<li>
<p>通常应关闭编辑器以编辑 <strong>.h </strong>文件中的任何内容</p>
</li>
<li>应关闭编辑器以编辑 .cpp 文件中的<strong>构造函数</strong></li>
</ul>
<p>实时编码在修改 .cpp 文件中的大多数其他函数时是可靠的,甚至在游戏过程中(在编辑器中播放时)使用也是安全的。它也不会像热重载那样在保存时导致蓝图损坏。</p>
<h2>更深入的技术讨论</h2>
<p>以下是一些额外的技术信息,这可能有助于你理解遇到的问题,或帮助你决定如何在无法使用实时编码时使用热重载。</p>
<p>在编辑器启动时,类和资产会以 UMACRO 级别(UCLASS、UPROPERTY、UFUNCTION、USTRUCT、UENUM)的 CDO(类默认对象)设置加载。这包括:</p>
<ul>
<li>类、成员和函数声明</li>
<li>构造函数、析构函数和指令集</li>
</ul>
<p>对非宏包装元素(如非 U 函数、非 U 属性和非 U 类)的更改有时可以通过热重载或实时编码重新加载。</p>
<p>应避免对由 UHT 扫描的头文件进行更改。因此,任何对 UCLASS/USTRUCT/UENUM 类型布局/签名的更改目前不应由这些系统处理。通常可以对实现部分(.cpp)进行更改。对 UCLASS/USTRUCT 构造函数的更改不会追溯应用于任何已构造的实例(CDO 不会重新实例化),因此建议关闭编辑器并执行完整的重新编译。
> 上述的情况出现在UE4.x的情况,而在5的版本,有了新的变化:<strong>Reinstancing 重实例化</strong>。</p>
<h2>使用Live Coding进行对象重实例化</h2>
<p><strong>对象重实例化(Object Reinstancing)</strong> 是一个额外的系统,用于在应用程序和编辑器中替代现有的对象实例。这意味着这些对象能够立刻参与Live Coding编译中大型的结构上的改变。重实例化的对象包括使用UCLASS、UFUNCTION、USTRUCT、UENUM和UDELEGATE宏,以及 <strong>虚幻标题工具(Unreal Header Tool)</strong> 用这些宏所生成的对象,比如蓝图节点。</p>
<p>对象重实例化默认启用。如果将其停用,Live Coding仍然能够轻松处理小型的代码修改,比如修改变量数值或者对现有函数进行小型变动。然而,如果对代码进行大型修改,比如增加新函数、新的变量组,或者进行重构,都会在不使用对象重实例化的情况下重新编译时产生不可预测的结果。这样通常会导致崩溃。</p>
<p>但是,启用对象重实例化时,你还需要执行额外的步骤来清理指针。如果你的代码仍然包含可以重实例化的对象上的指针,你需要使用 <code>ReloadReinstancingCompleteDelegate</code> 和 <code>ReloadCompleteDelegate</code> 来更新这些指针或者将缓存无效化,这样可以之后再重新使用它们。</p>
<p>热重载更能够承受重实例化的对象被错误地取消引用,但是Live Coding会在编辑器关闭时导致崩溃。这是由于任何已有类的析构函数都只能有一个版本,而没有清理干净的对象实例会导致旧版本的析构函数与重实例化的对象的新版本析构函数产生冲突。</p>
<p>在本文中,我们建议使用实时编码,因为它可以作为防止潜在损坏的保护屏障。此外,热重载被认为是一个遗留功能。这两个系统在更新 CDO 相关生成代码时面临相同的限制(UE4.x的时候 )。</p>
<h2>在Unreal Engine 5.x版本中,Live Coding(实时编译)不支持以下类型的代码更改:</h2>
<p>1. 构造函数和析构函数的修改:包括添加、删除或修改类的构造函数和析构函数。</p>
<p>2. 虚函数表(vtable)相关的更改:包括添加、删除或修改虚函数。</p>
<p>3. 类的继承结构的更改:包括改变类的继承关系。</p>
<p>4. 模板类的更改:包括模板类的定义和实现的修改。</p>
<p>5. 宏定义的更改:包括预处理器宏的定义和使用的修改。</p>
<p>6. 全局变量和静态变量的更改:包括添加、删除或修改全局变量和静态变量。</p>
<p>7. 内联函数的更改:包括内联函数的定义和实现的修改。</p>
<ol>
<li>编译器选项的更改:包括更改编译器选项或编译器标志。</li>
</ol>
<p>这些更改通常需要完全重新编译整个项目,而不仅仅是增量编译。
> <strong>注意</strong> 上述的几项内容待验证。</p>
<h2>为什么热重载会导致损坏?</h2>
<p>由于在编辑器打开时无法完全重新加载资产,因此会临时创建资产掩码。这是问题可能发生的地方。如果你热重载一个资产,然后保存它,资产目录、凭证和内容将会被热重载的内容更改。</p>
<h2>热重载的限制</h2>
<p>如果你对 Unreal Engine 宏内部的定义进行更改,然后进行热重载,生成的代码本身应该没问题。然而,引用这些类的编辑器资产不会从头加载。因此,派生自/引用更改类的资产可能会:</p>
<ul>
<li>
<p>丢失值——它们会重置为 C++ 类的默认值</p>
</li>
<li>
<p>在蓝图编辑器中损坏函数/引脚</p>
</li>
<li>
<p>损坏指针变量</p>
</li>
<li>
<p>变成 HOTRELOADED_Classes。如果文件没有损坏,有时可以通过重新启动编辑器解决。</p>
</li>
<li>完全损坏,在这种情况下,VCS/重新制作资产是唯一的解决方法。</li>
</ul>
<h2>替代方案?实时编码!</h2>
<p>热重载的好处是不必每次更改时关闭编辑器、从 IDE 编译然后重新打开。从 Unreal Engine 4.22 开始,实时编码被引入到引擎中,适用于 Windows 平台!(现在Mac是否能用了,未去了解)</p>
<p>以下是一些关于使用实时编码的重要信息:</p>
<ul>
<li>
<p>它不会覆盖 CDO 加载默认值。</p>
</li>
<li>它通过内存补丁迭代更改。如果你在使用实时编码的项目中关闭项目并在 IDE 中按下编译,它实际上会编译代码更改。</li>
</ul>
<p>综上所述,实时编码确实有很多好处!</p>
<ul>
<li>
<p>启用时,它会阻止你的 IDE 启动热重载。</p>
</li>
<li>如果你对涉及 UE 类型/宏的头文件进行更改,它不会完全处理它们。虽然这可能会导致奇怪的错误,但不会导致数据损坏。要修复它,关闭编辑器,从 IDE 编译,然后重新启动!</li>
</ul>
<p>在Unreal Engine 5.x版本中,Live Coding(实时编译)不支持以下类型的代码更改:</p>
<p>1. 构造函数和析构函数的修改:包括添加、删除或修改类的构造函数和析构函数。</p>
<p>2. 虚函数表(vtable)相关的更改:包括添加、删除或修改虚函数。</p>
<p>3. 类的继承结构的更改:包括改变类的继承关系。</p>
<p>4. 模板类的更改:包括模板类的定义和实现的修改。</p>
<p>5. 宏定义的更改:包括预处理器宏的定义和使用的修改。</p>
<p>6. 全局变量和静态变量的更改:包括添加、删除或修改全局变量和静态变量。</p>
<p>7. 内联函数的更改:包括内联函数的定义和实现的修改。</p>
<ul>
<li>编译器选项的更改:包括更改编译器选项或编译器标志。</li>
</ul>
<p>这些更改通常需要完全重新编译整个项目,而不仅仅是增量编译。
> <strong>待验证</strong></p>
<h2>故障排除</h2>
<p>在热重载后进行正确构建,</p>
<ul>
<li>关闭引擎,在你的 IDE 中运行清理命令,然后构建。</li>
</ul>
<p>处理损坏时,首先运行正确的重新构建。</p>
<p>HOTRELOADED_ Classes / Invalid Variable Corruption / Invalid Class Corruption</p>
<ul>
<li>
<p>首先尝试重新启动编辑器。有时,这足以让链接器纠正它。</p>
</li>
<li>
<p>对于无效变量损坏,有时注释掉变量、编译、打开引擎、关闭、取消注释并重新编译就足够了。</p>
</li>
<li>在某些情况下,处理此问题可能按照损坏的 UAssets来处理。</li>
</ul>
<p>损坏的 UAssets:</p>
<ul>
<li>不幸的是,对于损坏的 UAssets,通常只有两种方法:从源代码管理中恢复以前的版本或重新创建类。</li>
</ul>
<p>热重载的资产特别是蓝图经常会崩溃。检测蓝图类是否已热重载到无法修复的一个简单方法是它会在“this”是 nullptr 时崩溃。要进行故障排除,请在调试模式下从 IDE 启动引擎。</p>
<h2>工作流程</h2>
<p>对于混合开发方法的项目,建议的工作流程如下:</p>
<ul>
<li>对于可能涉及 CDO 的任何更改,关闭编辑器,从 IDE 编译并重新启动(有时可能需要删除临时文件)。</li>
<li>对于非 CDO 相关代码的小迭代,则Live Coding 可以随时为您服务!</li>
</ul>
<h1>参考资料</h1>
<p>[热重载和实时编码](<a href="https://unrealcommunity.wiki/live-compiling-in-unreal-projects-tp14jcgs">https://unrealcommunity.wiki/live-compiling-in-unreal-projects-tp14jcgs</a> "热重载和实时编码")
[实时编码入门指南](<a href="https://dev.epicgames.com/community/learning/knowledge-base/GDdl/unreal-engine-live-coding-primer">https://dev.epicgames.com/community/learning/knowledge-base/GDdl/unreal-engine-live-coding-primer</a> "实时编码入门指南")
[实施编码-官方文档](<a href="https://dev.epicgames.com/documentation/zh-cn/unreal-engine/using-live-coding-to-recompile-unreal-engine-applications-at-runtime?application_version=5.4">https://dev.epicgames.com/documentation/zh-cn/unreal-engine/using-live-coding-to-recompile-unreal-engine-applications-at-runtime?application_version=5.4</a> "实施编码-官方文档")</p>