Skip to content

Commit

Permalink
大幅重构旗舰版本文档
Browse files Browse the repository at this point in the history
  • Loading branch information
pirunxi committed Aug 19, 2024
1 parent 0294e5c commit e6fe089
Show file tree
Hide file tree
Showing 17 changed files with 627 additions and 1,708 deletions.
26 changes: 26 additions & 0 deletions docs/business/ultimate/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 构建和热更新

DHE技术中与构建相关的文件为dhe dll文件和对应的dhao文件。

## 构建游戏

由于首包中所有DHE程序集中代码没有发生改变,因为在构建游戏工作流中并不需要任何DHE相关的dll或者dhao文件。

- 运行`HybridCLR/Generate/All`
- 构建游戏
- 备份`HybridCLRData\AssembliesPostIl2CppStrip\{buildTarget}`目录下的所有程序集,加入版本管理系统。将来热更新生成dhao文件时需要用到这些原始的dll文件


## 热更新

- 使用 `HybridCLR/CompileDll/ActivedBuildTarget` 生成热更新dll。
- 使用`HybridCLR.Editor.DHE.BuildUtils.GenerateDHAODatas`生成最新的热更新dll的dhao文件。如果需要加密dhe程序集,则使用`HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateDHAODatas`生成加密dll及相应的dhao文件
- 将最新的热更新dll(或者加密后的dll)和dhao文件加入热更新资源管理系统。注意,运行时不需要原始dll文件,请不要将备份的原始dll加入热更新资源管理系统。

:::caution

如果构建游戏使用 **development build 选项**,请一定要对应使用`HybridCLR/CompileDll/ActivedBuildTarget_Development`编译Development模式的热更新dll。
否则由于developemnt与非development编译的dll差异较大,会导致几乎所有函数都被计算为变化。

:::

52 changes: 52 additions & 0 deletions docs/business/ultimate/dhao.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# DHAO 文件


dhao文件是DHE技术的核心概念。dhao文件中包含了离线计算好的最新的热更新dll中变化的类型和函数的信息,运行时直接根据dhao文件中信息决定执行某个热更新函数时,应该使用最新的解释版本还是直接调用原始的AOT函数。
离线计算好的dhao文件对于DHE技术极为关键,如果没有dhao文件,需要额外携带原始AOT dll,并且计算函数变化的代价极其高昂。

通过对比最新的热更新dll与打包时生成的AOT dll,离线计算出变化的类型与函数,保存成dhao文件。因此DHE机制要正常工作,必须依赖于dhao文件的正确性,而dhao文件的正确性
则依赖精确提供最新的热更新dll和打包时生成的AOT dll。


`HybridCLR.Editor.DHE.BuildUtils`提供了多个生成dhao文件相关的函数。

|函数名|描述|
|-|-|
|GenerateDHAODatas|生成热更新包(即有代码发生改变时)的dhao文件|
|EncryptDllAndGenerateDHAODatas|当开启初级代码加固时,生成热更新包(即有代码发生改变时)加密后的dll和dhao文件|


## 标记变化的函数信息

目前已经可以自动通过对比最新的热更新dll和打包时生成的aot dll,计算出变化的函数,绝大多数情况下不需要手动操作。但事实上并不存在完美的能够判断逻辑等价性的代码,
工具只是简单地一一对比IL来判断等价性。有时候可能发生函数是等价的但IL发生变化的情况(如调换了两行不相关代码的顺序),则会被判定为函数发生变换而切到解释执行。
如果发生了这种情况,**并且对该函数有极其严苛的性能要求**,开发者可以手动使用UnchangedAttribute特性标注函数的变化性。
`[Unchanged]``[Unchanged(true)]`表示未变化,`[Unchanged(false)]`表示变化,未标记特性则由工具自动计算。

错误地将未变化函数标记为已变化,不会影响运行的正确性,只会影响性能。就算将所有热更新函数都标记为变化,也能正常运行。但错误地将变化函数标记为未变化,不仅会导致运行逻辑出错,
严重情况下还会导致崩溃!

:::caution
除非特殊情况以及你是有经验的专家,不要手动标记。因为编译器经常生成一些隐藏类或字段,这些类名并不是稳定的。表面看起来一样的C#代码,实际生成的代码未必一样,强行标注为`[Unchanged]`会导致不正确的运行逻辑,甚至崩溃。
:::


## 合并dhao文件

基于相同或者相似源码发布的游戏包,它们的原始dhe程序集在不同平台之间仅有微小区分,热更新时生成的dhao文件也只有微小区别。
导致需要为每个平台都计算单独的dhao文件(即使是相同平台,由于代码编译的不稳定性,生成的原始dll也可能有微小区别),这导致维护dhao工作变得复杂易错。当多个新旧游戏包同时存在时,这个问题尤为严重。
可以考虑将同一个dhe程序集对应的多个平台的dhao文件合并,不影响运行正确性的同时对性能的影响也很小。

我们提供了`HybridCLR.Editor.DHE.BuildUtil::MergeDHAOFiles`函数实现合并dhao文件目标。

注意,带校验的工作流无法使用合并dhao文件的方式,因为带校验的工作流会检查原始dll的md5码,这个肯定是不匹配的。


## 注意事项

### 外部dll引发的计算dhao的结果有巨量差异

如果有外部dll被标记为DHE程序集,由于外部dll打包时会被裁剪,而计算dhao文件时,取的是原始的外部dll,导致产生巨量的差异,这不是所期望的。解决办法有几个:

1. 在link.xml里`<assembly fullname="YourExternDll" preserve="all"/>` 完全保留外部dll
2. 不用最新的热更新dll去计算差异,而是使用最新代码重新打包时生成的aot dll去计算差异
2 changes: 1 addition & 1 deletion docs/business/ultimate/freetrial.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@

## 使用

除了安装与设置如上面文档说明有少许调整外,剩余完全参照[快速上手](./quickstartchecked)即可。
除了安装与设置如上面文档说明有少许调整外,剩余完全参照[快速上手](./quickstartunchecked)即可。
171 changes: 0 additions & 171 deletions docs/business/ultimate/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,113 +57,6 @@

`Assets/link.xml`(或者其他自定义的link.xml)中为你的所有dhe程序集添加类似配置`<assembly fullname="YourExternDll" preserve="all"/>`

## dhao文件

dhao文件是DHE技术的核心概念。dhao文件中包含了离线计算好的最新的热更新dll中变化的类型和函数的信息,运行时直接根据dhao文件中信息决定执行某个热更新函数时,应该使用最新的解释版本还是直接调用原始的AOT函数。
离线计算好的dhao文件对于DHE技术极为关键,如果没有dhao文件,需要额外携带原始AOT dll,并且计算函数变化的代价极其高昂。

通过对比最新的热更新dll与打包时生成的AOT dll,离线计算出变化的类型与函数,保存成dhao文件。因此DHE机制要正常工作,必须依赖于dhao文件的正确性,而dhao文件的正确性
则依赖精确提供最新的热更新dll和打包时生成的AOT dll。


`HybridCLR.Editor.DHE.BuildUtils`提供了多个生成dhao文件相关的函数。

|函数名|描述|
|-|-|
|GenerateUnchangedDHAODatas|生成首包(即没有发生任何改变时)的dhao文件|
|GenerateDHAODatas|生成热更新包(即有代码发生改变时)的dhao文件|
|EncryptDllAndGenerateUnchangedDHAODatas|当开启初级代码加固时,生成首包(即没有发生任何改变时)加密后的dll和dhao文件|
|EncryptDllAndGenerateDHAODatas|当开启初级代码加固时,生成热更新包(即有代码发生改变时)加密后的dll和dhao文件|


## 标记变化的函数信息

目前已经可以自动通过对比最新的热更新dll和打包时生成的aot dll,计算出变化的函数,绝大多数情况下不需要手动操作。但事实上并不存在完美的能够判断逻辑等价性的代码,
工具只是简单地一一对比IL来判断等价性。有时候可能发生函数是等价的但IL发生变化的情况(如调换了两行不相关代码的顺序),则会被判定为函数发生变换而切到解释执行。
如果发生了这种情况,**并且对该函数有极其严苛的性能要求**,开发者可以手动使用UnchangedAttribute特性标注函数的变化性。
`[Unchanged]``[Unchanged(true)]`表示未变化,`[Unchanged(false)]`表示变化,未标记特性则由工具自动计算。

错误地将未变化函数标记为已变化,不会影响运行的正确性,只会影响性能。就算将所有热更新函数都标记为变化,也能正常运行。但错误地将变化函数标记为未变化,不仅会导致运行逻辑出错,
严重情况下还会导致崩溃!

:::caution
除非特殊情况以及你是有经验的专家,不要手动标记。因为编译器经常生成一些隐藏类或字段,这些类名并不是稳定的。表面看起来一样的C#代码,实际生成的代码未必一样,强行标注为`[Unchanged]`会导致不正确的运行逻辑,甚至崩溃。
:::

## 代码中使用

运行时,完成热更新后,对于每个dhe程序集,调用 `RuntimeApi::LoadDifferentialHybridAssembly``RuntimeApi::LoadDifferentialHybridAssemblyUnchecked` 加载热更新assembly。

注意事项:

- 要按照assembly的依赖顺序加载 差分混合执行 assembly。
- 如果某个程序集未发生改变,dhao字段可以传null,但此时一定要使用打包时生成的AOT dll,而不能使用通过`HybridCLR/CompileDll/xxx`命令生成的热更新dll。
- DHE程序集本身已经包含了元数据,即使未开启完全泛型共享时也**不要对DHE程序集进行补充元数据**,补充了也会失败,其他非DHE的AOT程序集可以照常补充元数据。


`RuntimeApi::LoadDifferentialHybridAssembly`为带校验的工作流,需要传入原始dhe dll的md5及当前dhe dll的md5,与dhao文件中保存的md5进行对比。
为了originalDllMd5和currentDllMd5参数,极大增加了工作流的复杂度。

:::tip

推荐初学者在demo项目中使用带校验的工作流,熟悉工作流后在正式项目中使用不带校验的工作流。
:::

### 带校验的 `RuntimeApi::LoadDifferentialHybridAssembly`

```csharp title="加载DHE程序集"

public static string CreateMD5Hash(byte[] bytes)
{
return BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "").ToUpperInvariant();
}

///
/// originalDllMd5 从构建时生成的`{manifest}`清单文件中获得,此清单文件由开发者自己生成
///
void LoadDifferentialHybridAssembly(string assemblyName, string originalDllMd5)
{
// currentDllMd5 既可以运行时生成,也可以发布热更新包时离线提前生成
string currentDllMd5 = CreateMD5Hash(dllBytes);
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssembly(dllBytes, dhaoBytes, originalDllMd5, currentDllMd5);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}
```

### 不带校验的 `RuntimeApi::LoadDifferentialHybridAssemblyUnchecked`

:::warning

使用不带校验的工作流,请务必保证原始dhe dll、当前dhe dll和dhao文件的一致性。如果不一致,轻则运行出错,重则进程崩溃。

:::

```csharp title="加载DHE程序集"

///
/// originalDllMd5 从构建时生成的`{manifest}`清单文件中获得,此清单文件由开发者自己生成
///
void LoadDifferentialHybridAssembly(string assemblyName)
{
LoadImageErrorCode err = RuntimeApi.LoadDifferentialHybridAssemblyUnchecked(dllBytes, dhaoBytes);
if (err == LoadImageErrorCode.OK)
{
Debug.Log($"LoadDifferentialHybridAssembly {assName} OK");
}
else
{
Debug.LogError($"LoadDifferentialHybridAssembly {assName} failed, err={err}");
}
}
```


## 配置函数注入策略

Expand All @@ -181,73 +74,9 @@ void LoadDifferentialHybridAssembly(string assemblyName)
`HybridCLR Settings``InjectRuleFiles`字段中填写注入策略文件路径,文件的相对路径为项目根目录(如`Assets/InjectRules/DefaultInjectRules.xml`)。


## 打包

DHE技术中与构建相关的文件为dhe dll文件和对应的dhao文件。

### 非加密工作流

#### 构建主包

- 将构建后生成的裁剪AOT dll作为 首包(没有任何改动)的dhe dll
- 使用`HybridCLR.Editor.DHE.BuildUtils.GenerateUnchangedDHAODatas`生成首包的dhao文件

如果使用带校验的工作流,则执行以下操作:

- 为dhe dll生成一个至少包含 assemblyName,md5的`{manifest}`清单文件(由开发者自由决定怎么实现),因为`RuntimeApi.LoadDifferentialHybridAssembly`需要提供dhe dll的原始md5
- 将 dhe dll、dhao文件及`{manifest}`文件加入热更新资源管理系统

如果使用不带校验的工作流,则执行以下操作:

- 将 dhe dll、dhao文件加入热更新资源管理系统

如果想随包携带首包的dhe dll和dhao文件,请先导出工程,再按照上面的步骤生成dhe dll和dhao文件,再将它们加入到导出工程中。


#### 热更新

- 使用 `HybridCLR/CompileDll/ActivedBuildTarget` 生成热更新dll。
- 使用`HybridCLR.Editor.DHE.BuildUtils.GenerateDHAODatas`生成最新的热更新dll的dhao文件
- 将最新的热更新dll和dhao文件加入热更新资源管理系统

:::caution

如果打包使用 **development build 选项**,请一定要对应使用`HybridCLR/CompileDll/ActivedBuildTarget_Development`编译Development模式的热更新dll,否则对比结果为几乎所有函数都被判定为发生变化。

:::

### 加密工作流

#### 构建主包

- 将构建后生成的裁剪AOT dll作为 原始dhe dll
- 使用`HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateUnchangedDHAODatas`生成首包的dhao文件及加密后的dhe dll文件

如果使用带校验的工作流,则执行以下操作:

- 为dhe dll生成一个至少包含 assemblyName,md5的`{manifest}`清单文件(由开发者自由决定怎么实现),因为`RuntimeApi.LoadDifferentialHybridAssembly`需要提供dhe dll的原始md5
- 将 加密后的dhe dll、dhao文件及`{manifest}`文件加入热更新资源管理系统

如果使用不带校验的工作流,则执行以下操作:

- 将 加密后的dhe dll、dhao文件加入热更新资源管理系统


#### 热更新

- 使用 `HybridCLR/CompileDll/ActivedBuildTarget` 生成热更新dll。
- 使用`HybridCLR.Editor.DHE.BuildUtils.EncryptDllAndGenerateDHAODatas`生成最新的dhe dll的加密后的文件及对应的dhao文件
- 将加密后的dhe dll和dhao文件加入热更新资源管理系统

## 不支持特性

- 不支持开启 `script debugging` 构建选项

## 注意事项

### 外部dll引发的计算dhao的结果有巨量差异

如果有外部dll被标记为DHE程序集,由于外部dll打包时会被裁剪,而计算dhao文件时,取的是原始的外部dll,导致产生巨量的差异,这不是所期望的。解决办法有几个:

1. 在link.xml里`<assembly fullname="YourExternDll" preserve="all"/>` 完全保留外部dll
2. 不用最新的热更新dll去计算差异,而是使用最新代码重新打包时生成的aot dll去计算差异
Loading

0 comments on commit e6fe089

Please sign in to comment.