Skip to content

Commit

Permalink
更新 hotreload 文档
Browse files Browse the repository at this point in the history
  • Loading branch information
pirunxi committed Jun 16, 2024
1 parent 38c13af commit 1cb77e7
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 40 deletions.
5 changes: 2 additions & 3 deletions docs/business/reload/hotreloadassembly.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
- 被卸载程序集中定义的异步Task
- 其他

实际工程可能很复杂,开发者找出所有非法引用是很困难和不切实际的。我们已经实现了非法引用检查,在调用`RuntimeApi.UnloadAssembly`
会打印出所有非法引用的日志。开发者根据打印的日志清除所有非法引用即可。
实际工程可能很复杂,开发者找出所有非法引用是很困难和不切实际的。我们已经实现了非法引用检查,卸载过程中会打印出所有非法引用的日志。开发者根据打印的日志清除所有非法引用即可。


由于非法引用检查会遍历所有存活对象,比较耗时,正式版本可以可以使用`RuntimeApi.EnableLiveObjectValidation(false)`禁用这个检查。

65 changes: 56 additions & 9 deletions docs/business/reload/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,55 @@

## 代码中使用

调用 `RuntimeApi.UnloadAssembly` 卸载程序集,使用`Assembly.Load`重新加载程序集。当前不支持在未卸载该程序集的情况下再次加载该程序集,示例代码如下:
调用 `RuntimeApi.TryUnloadAssembly或者RuntimeApi.ForceUnloadAssembly` 卸载程序集,使用`Assembly.Load`重新加载程序集。必须成功卸载程序集后才能再次加载该程序集。

当前有两种卸载工作流:

- TryUnloadAssembly
- ForceUnloadAssembly

### TryUnloadAssembly

尝试卸载,如果AppDomain中存在对被卸载程序集中对象的引用,则保持现状,返回失败,否则返回成功。

示例代码如下:

```csharp

// 卸载程序集时默认会扫描整个运行时,检查有没有持有对被卸载程序集中对象的引用。
// 对于正式发布的版本,可以使用以下语句禁用检查,缩短卸载时间。
// 开发期间强烈推荐不要禁用它
// 第一次加载
Assembly ass = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// 第一次卸载
// printObjectReferenceLink参数为true表示当卸载失败时,会打印出详细的非法对象的引用链日志,方便开发者定位出哪儿保持了非法引用。
// 建议只在开发期为true,正式上线后改为false
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

// 第二次加载
Assembly newAss = Assembly.Load(yyy);

// 执行一些代码
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// RuntimeApi.EnableLiveObjectValidation(false);
// 第二次卸载
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}
```

### ForceUnloadAssembly

强制卸载,即使AppDomain中存在对被卸载程序集中对象的引用。返回true表示没有问题,返回false表示卸载过程中检查出非法引用。如果返回false,在运行一段时间后**有可能**会崩溃。此操作慎用,建议与官方技术支持详细沟通。

```csharp

// 第一次加载
Assembly ass = Assembly.Load(yyy);
Expand All @@ -33,7 +73,12 @@
mainType.GetMethod("Main").Invoke(null, null);

// 第一次卸载
RuntimeApi.UnloadAssembly(ass);
// ignoreObjectReferenceValidation参数为true表示卸载过程中不检查非法对象引用,可以缩短卸载时间。但建议无论开发期还是正式发布都取false
// printObjectReferenceLink参数为true表示当卸载失败时,会打印出详细的非法对象的引用链日志,方便开发者定位出哪儿保持了非法引用。建议只在开发期为true,正式上线后改为false
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

// 第二次加载
Assembly newAss = Assembly.Load(yyy);
Expand All @@ -43,13 +88,15 @@
mainType.GetMethod("Main").Invoke(null, null);

// 第二次卸载
RuntimeApi.UnloadAssembly(ass);
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}
```

## 注意事项

- async或者协程很容易隐式地在其他线程保持了对卸载程序集代码的引用,卸载前请务必清理所有异步或者协程函数
- UI的OnClick或者各种回调事件很容易导致保持了对卸载程序集的引用,一定要清理干净
- 注册到全局的事件或者其他加高,容易意外保持了对卸载程序集的引用,一定要清理干净
- 根据`RuntimeApi.UnloadAssembly`中打印的非法引用的日志,清理掉代码中的非法引用
- 正式发布的项目可以使用`RuntimeApi.EnableLiveObjectValidation(false)`禁用非法引用检查以缩短卸载耗时
- 根据卸载过程中打印的非法引用的日志,清理掉代码中的非法引用
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,5 @@ Hot reload technology requires that metadata of unloaded assembly U cannot be he
- Tasks defined in the unloaded assembly
- Others

Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when calling `RuntimeApi.UnloadAssembly`, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs.
Real-world projects can be complex, and it is difficult and impractical for developers to find all illegal references. We have implemented illegal reference checks, and when unloading, logs of all illegal references will be printed. Developers can clear all illegal references based on the printed logs.

Since illegal reference checks traverse all live objects, they are time-consuming. Therefore, You can use `RuntimeApi.EnableLiveObjectValidation(false)` to forcibly disable this check in Release mode.
Original file line number Diff line number Diff line change
@@ -1,46 +1,101 @@
# Quick Start

Almost identical to the [Quick Start](../../beginner/quickstart.md) of the Community Edition, this document only highlights the differences.
This is almost identical to the community version of the [Quick Start](../../beginner/quickstart.md), and this document only describes the differences.

## Installation

- Extract hybridclr_unity and place it in the project's Packages directory, renaming it to com.code-philosophy.hybridclr.
- Extract the corresponding il2cpp_plus-{version}.zip based on your Unity version.
- Extract hybridclr.zip.
- Place the hybridclr directory from the extracted hybridclr.zip into the libil2cpp directory from the extracted il2cpp-{version}.zip.
- Open `HybridCLR/Installer`, enable the `Copy libil2cpp from local` option, select the libil2cpp directory that was just extracted, and proceed with the installation.
- Unzip hybridclr_unity and put it in the project Packages directory, rename it to com.code-philosophy.hybridclr
- Unzip the corresponding `il2cpp_plus-{version}.zip` according to your unity version
- Unzip `hybridclr.zip`
- Put the hybridclr directory after unzipping `hybridclr.zip` into the libil2cpp directory after unzipping `il2cpp-{version}.zip`
- Open `HybridCLR/Installer`, turn on the `Copy libil2cpp from local` option, select the libil2cpp directory just unzipped, and install it

![installer](/img/hybridclr/ultimate-installer.jpg)

## Usage in Code
## Use in code

Use `RuntimeApi.UnloadAssembly` to unload the assembly and `Assembly.Load` to reload the assembly. Currently, reloading the same assembly without unloading it first is not supported. Example code:
Call `RuntimeApi.TryUnloadAssembly or RuntimeApi.ForceUnloadAssembly` Unload the assembly and reload it using `Assembly.Load`. The assembly must be successfully unloaded before it can be loaded again.

There are currently two unloading workflows:

- TryUnloadAssembly
- ForceUnloadAssembly

### TryUnloadAssembly

Try to unload. If there is a reference to the object in the unloaded assembly in the AppDomain, keep the status quo and return failure, otherwise return success.

The sample code is as follows:

```csharp
// First load
Assembly ass = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);
// First load
Assembly ass = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// First unload
// The printObjectReferenceLink parameter is true, which means that when the unloading fails, a detailed reference chain log of illegal objects will be printed, which is convenient for developers to locate where illegal references are maintained.
// It is recommended to set it to true only during the development period and change it to false after the official launch
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}

// Second load
Assembly newAss = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// Second uninstall
if (!RuntimeApi.TryUnloadAssembly(ass, true))
{
throw new Exception("unload fail");
}
```

### ForceUnloadAssembly

Force uninstallation, even if there are references to objects in the uninstalled assembly in the AppDomain. Returning true means there is no problem, and returning false means that an illegal reference was detected during the uninstallation process. If false is returned, it may crash after running for a period of time. Use this operation with caution, and it is recommended to communicate with official technical support in detail.

```csharp

// Load for the first time
Assembly ass = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// First unload
RuntimeApi.UnloadAssembly(ass);
// Uninstall for the first time
// The ignoreObjectReferenceValidation parameter is true, which means that illegal object references are not checked during the uninstall process, which can shorten the uninstall time. However, it is recommended to use false regardless of the development period or the official release
// The printObjectReferenceLink parameter is true, which means that when the uninstall fails, a detailed illegal object reference chain log will be printed, which is convenient for developers to locate where the illegal reference is maintained. It is recommended to set it to true only during the development period and change it to false after the official launch
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}

// Second load
Assembly newAss = Assembly.Load(yyy);
// Second load
Assembly newAss = Assembly.Load(yyy);

// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);
// Execute some code
Type mainType = ass.GetType("Entry");
mainType.GetMethod("Main").Invoke(null, null);

// Second unload
RuntimeApi.UnloadAssembly(ass);
// Second uninstall
if (!RuntimeApi.ForceUnloadAssembly(ass, false, true))
{
throw new Exception("unload fail");
}
```

## Notes

- async or coroutine functions easily hold references to unloaded assembly code on other threads, so make sure to clean up all async or coroutine functions before unloading.
- UI OnClick or various callback events easily hold references to unloaded assembly, so make sure to clean them up properly.
- Events or other registrations to the global scope can accidentally hold references to unloaded assembly, so make sure to clean them up properly.
- Based on the logs of illegal references printed in RuntimeApi.UnloadAssembly, clean up all illegal references in the code.
- Async or coroutines can easily implicitly keep references to the uninstalled assembly code in other threads. Please be sure to clean up all asynchronous or coroutine functions before uninstalling
- UI OnClick or various callback events can easily keep references to the uninstalled assembly, so be sure to clean them up
- Registering to global events or other heightening can easily accidentally keep references to uninstalled assemblies, so be sure to clean them up.
- Clean up illegal references in the code according to the illegal reference logs printed during the uninstall process

0 comments on commit 1cb77e7

Please sign in to comment.