docs(editor): sync script assembly builder api docs

This commit is contained in:
2026-04-12 22:50:50 +08:00
parent a660fc489a
commit 89590242bd
24 changed files with 285 additions and 1813 deletions

View File

@@ -8,65 +8,100 @@
**描述**: 负责把 Editor 项目里的 C# 源码编译成 `Library/ScriptAssemblies` 下的脚本程序集。
## 概述
## 角色概述
`EditorScriptAssemblyBuilder` 是当前 Editor 脚本工作流里真正执行“编译”的那一层。
它会把两类源码分别编成:
`EditorScriptAssemblyBuilder` 是当前 Editor 托管脚本工作流里真正执行“编译”的那一层。它会把两组 C# 源码分别编成:
- `XCEngine.ScriptCore.dll`
- `GameScripts.dll`
并把结果统一落到:
并把输出统一落到:
- `<project>/Library/ScriptAssemblies/`
## 为什么这层设计重要
编辑器脚本系统要稳定工作,关键不只是“能调用 Roslyn”而是要把几件事情一起稳定下来
- 仓库根目录如何解析
- Mono 运行时依赖从哪里找
- `dotnet` / Roslyn / `.NET Framework 4.7.2` 引用程序集如何就位
- 输出目录里的 `mscorlib.dll` 如何避免被活动 Mono runtime 锁住后反复覆盖
`EditorScriptAssemblyBuilder` 正是在做这层工程收口。
## 公开方法
| 方法 | 说明 |
|------|------|
| [RebuildProjectAssemblies](RebuildProjectAssemblies.md) | 全量重建项目脚本程序集并返回结果消息。 |
这是当前唯一入口。返回 `EditorScriptAssemblyBuildResult`
当前这就是它唯一的公开入口。返回 `EditorScriptAssemblyBuildResult` 包含
- `succeeded`
- `message`
## 当前重建流程
`EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致
`EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致如下
1. 校验项目路径非空。
2. 解析仓库根与 Mono 根目录。
3. 创建 `<project>/Library/ScriptAssemblies`
4. `PATH` 上查找 `dotnet.exe`
5. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本
6. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`
7. 校验 `.NET Framework 4.7.2` 参考程序集存在
8. 收集 `managed/XCEngine.ScriptCore/**/*.cs`
9. 收集 `<project>/Assets/**/*.cs`
10. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件
11. 先编译 `XCEngine.ScriptCore.dll`
12. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件,避免在增量重建时覆盖仍可能被 Mono 映射的 corlib
13. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
2. 解析仓库根目录。
3. 解析 Mono 根目录
4. 创建 `<project>/Library/ScriptAssemblies`
5. `PATH` 上查找 `dotnet.exe`
6. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本
7. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`
8. 校验 `.NET Framework 4.7.2` 引用程序集存在
9. 收集 `managed/XCEngine.ScriptCore/**/*.cs`
10. 收集 `<project>/Assets/**/*.cs`
11. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件
12. 先编译 `XCEngine.ScriptCore.dll`
13. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用
14. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
## Mono 根目录解析顺序
这是本轮必须同步进文档的重点变化。
当前 `GetMonoRootDirectory()` 的真实优先级是:
1. 先从仓库根目录下找 bundled Mono
- 优先检查 `engine/third_party/mono/binary/mscorlib.dll`
- 若不存在,再扫描仓库一级子目录里的 `Fermion/Fermion/external/mono/binary/mscorlib.dll`
2. 如果没找到 bundled Mono再检查编译期配置的 `XCENGINE_EDITOR_MONO_ROOT_DIR`
3. 如果配置路径也无效,最后回退到 `<repositoryRoot>/managed/mono`
这意味着当前编辑器会优先使用项目内随仓库提供的 Mono而不是先依赖外部配置路径。这是一个非常实际的工程改进
- 减少对开发机环境的隐式依赖
- 让构建行为更可复现
- 更适合团队协作和 CI
## 仓库根目录解析
仓库根目录也不是写死的。当前顺序是:
1. 若定义了 `XCENGINE_EDITOR_REPO_ROOT`,直接使用它
2. 否则以可执行文件目录为起点,向上回退三级,得到 fallback repository root
这让同一套构建器既能支持配置化构建,也能支持开发环境里的相对部署。
## 真实使用位置
- `Application.cpp` 会在重建脚本程序集时调用它。
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了两类关键语义
- 成功路径:`XCEngine.ScriptCore.dll``GameScripts.dll` 与项目本地 `mscorlib.dll` 都会落到 `Library/ScriptAssemblies`
- 锁语义:如果活动 Mono runtime 仍持有已加载的 `GameScripts.dll`,直接重建会失败;释放 runtime 后再重建则可以成功并看到新增脚本类型
- `Application.cpp` 会在编辑器触发脚本重建时调用它。
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了成功路径与“活动 Mono runtime 持有程序集导致重建失败”的锁语义
## 当前实现边界
- 当前是全量重建,不是增量编译。
- 编译链明显依赖 Windows 路径和本机安装的 `dotnet` SDK。
- 参考程序集路径当前写死在 `.NET Framework 4.7.2` 目录
- 构建器本身不会主动卸载当前脚本运行时;如果调用方没有先释放 Mono app domain`GameScripts.dll` 仍可能因文件锁而建失败。
- 若外部环境缺少 `dotnet.exe`、Roslyn 或参考程序集,返回的只是一条失败消息,而不是更复杂的恢复策略。
- 编译链明显依赖 Windows本机安装的 `dotnet` SDK`.NET Framework 4.7.2` 引用程序集
- 它会优先消费仓库内 bundled Mono但不会主动管理 Mono runtime 生命周期
- 如果调用方没有先释放活动脚本运行时`GameScripts.dll` 仍可能因文件锁而建失败。
## 相关文档
- [Scripting](../Scripting.md)
- [RebuildProjectAssemblies](RebuildProjectAssemblies.md)
- [EditorScriptAssemblyBuilderUtils](../EditorScriptAssemblyBuilderUtils/EditorScriptAssemblyBuilderUtils.md)
- [EditorScriptRuntimeStatus](../EditorScriptRuntimeStatus/EditorScriptRuntimeStatus.md)

View File

@@ -14,53 +14,123 @@ static EditorScriptAssemblyBuildResult RebuildProjectAssemblies(const std::strin
## 作用
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败消息
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败结果
## 当前实现行为
## 当前实现流程
### 1. 解析路径与外部工具
### 1. 解析路径与基础环境
- 要求 `projectPath` 非空,否则直接返回失败结果
- 解析
- 仓库根目录
- Mono 根目录
- `managed/XCEngine.ScriptCore`
- `<project>/Library/ScriptAssemblies`
- 当前依赖系统 `PATH` 上可找到 `dotnet.exe`,并通过 `dotnet --list-sdks` 解析最新 SDK 版本,再定位对应 `Roslyn/bincore/csc.dll`
- 要求 `projectPath` 非空,否则直接返回失败。
- 解析 `projectRoot``repositoryRoot``monoRoot``managedRoot` 与输出目录。
- 创建 `<project>/Library/ScriptAssemblies`
### 2. 校验引用与源文件
### 2. 解析仓库根目录
- 校验 `.NET Framework 4.7.2` 参考程序集存在
- `mscorlib.dll`
- `System.dll`
- `System.Core.dll`
- 校验 Mono 自带 `binary/mscorlib.dll` 存在。
- 收集两组 C# 源文件:
- `managed/XCEngine.ScriptCore/**/*.cs`
- `<project>/Assets/**/*.cs`
- 若项目脚本为空,会生成 `Generated/EmptyProjectGameScripts.cs` 占位源码。
仓库根目录顺序如下
### 3. 编译输出
1.`XCENGINE_EDITOR_REPO_ROOT` 非空,优先使用该配置。
2. 否则以可执行文件目录为起点向上回退三级,得到 fallback repository root。
- 先编译 `XCEngine.ScriptCore.dll`
- 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件。
- 然后引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
- 成功时返回:
- `succeeded = true`
- `message = "Rebuilt script assemblies in ..."`
### 3. 解析 Mono 根目录
### 4. 失败语义
当前 Mono 根目录的真实查找顺序是:
- 任意路径校验、进程启动、编译失败、首次 `mscorlib.dll` 复制失败,或目标程序集仍被活动 Mono runtime 持有时,都会返回 `succeeded = false`
- 这个函数本身不负责关闭现有脚本运行时;如果调用方在同一进程里仍持有已加载的 `GameScripts.dll`,重建可能因为文件锁失败。
- 函数内部还包了一层 `try/catch`,用于把标准异常和未知异常转成失败消息。
1. 优先查找仓库内 bundled Mono
- `<repositoryRoot>/engine/third_party/mono`
- 若未命中,再扫描仓库一级子目录下的 `Fermion/Fermion/external/mono`
2. 若未找到 bundled Mono则尝试 `XCENGINE_EDITOR_MONO_ROOT_DIR`
3. 若配置路径也无效,则回退到 `<repositoryRoot>/managed/mono`
## 当前实现边界
判定依据都是目标目录下是否存在 `binary/mscorlib.dll`
- 当前是全量重建,不做增量分析
- 平台和工具链路径明显偏向 Windows + 本机安装 `.NET SDK`
- 参考程序集版本当前固定为 `.NET Framework 4.7.2`
- 输出目录里的 `mscorlib.dll` 当前采用“首次复制、后续复用”的策略,不会在每次重建时强制覆盖。
这条顺序很关键,因为它决定了编辑器现在优先消费项目内置的 Mono 依赖,而不是先依赖开发机外部配置
### 4. 解析外部编译工具
-`PATH` 上查找 `dotnet.exe`
- 运行 `dotnet --list-sdks`
- 取输出中的最后一条 SDK 版本
- 组装 `C:\Program Files\dotnet\sdk\<sdkVersion>\Roslyn\bincore\csc.dll`
如果任一步失败,直接返回失败结果。
### 5. 校验引用程序集
当前固定要求 `.NET Framework 4.7.2` 引用程序集存在:
- `mscorlib.dll`
- `System.dll`
- `System.Core.dll`
同时还会校验 `monoRoot/binary/mscorlib.dll` 存在。
### 6. 收集源文件
- 收集 `managed/XCEngine.ScriptCore/**/*.cs`
- 收集 `<project>/Assets/**/*.cs`
- 若项目侧没有任何脚本,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件,确保仍能产出 `GameScripts.dll`
### 7. 执行两阶段编译
先编译:
- `XCEngine.ScriptCore.dll`
再编译:
- `GameScripts.dll`
第二阶段会把 `XCEngine.ScriptCore.dll` 放进引用列表。
### 8. 处理项目本地 mscorlib.dll
这是当前实现里一个很关键的工程细节:
- 输出目录里的 `mscorlib.dll` 只会在“第一次不存在”时,从 `monoRoot/binary/mscorlib.dll` 复制进来
- 如果该文件已经存在,则直接复用,不再每次重建都覆盖
源码里的注释已经说明原因Mono 可能在进程生命周期内持续映射项目本地 corlib。如果每次重建都强行覆盖文件锁会让流程更加脆弱。
## 返回值语义
成功时:
- `succeeded = true`
- `message` 类似 `Rebuilt script assemblies in ...`
失败时:
- `succeeded = false`
- `message` 会包含更具体的失败原因,例如缺少 `dotnet.exe`、Roslyn、引用程序集、Mono corlib或编译器进程本身失败
函数内部还包了一层 `try/catch`,会把标准异常与未知异常也转换成失败消息。
## 失败边界
以下情况都会导致失败:
- 项目路径为空
- 输出目录创建失败
- `dotnet.exe` 不在 `PATH`
- `dotnet --list-sdks` 无法执行或无法解析出 SDK 版本
- `csc.dll` 不存在
- `.NET Framework 4.7.2` 引用程序集缺失
- Mono corlib 缺失
- `ScriptCore` 源码为空
- 占位脚本生成失败
- 任意一次 C# 编译失败
- 目标程序集仍被活动 Mono runtime 持有导致写入失败
## 设计理解
这个函数不是一个通用 C# 构建系统,而是一个面向编辑器脚本热重建的工程化入口。它的目标是:
- 把编辑器脚本依赖解析收拢到一处
- 让项目内 bundled Mono 可以直接工作
- 让脚本重建失败时返回可读的诊断信息
- 尽量避免 `mscorlib.dll` 因重复覆盖而触发额外锁冲突
从商业级工具链视角看,这属于“先把本地开发和编辑器热重建跑稳,再逐步演进成更完整脚本构建系统”的典型路线。
## 相关文档

View File

@@ -1,6 +1,6 @@
# API 文档重构状态
**生成时间**: `2026-04-10 18:41:38`
**生成时间**: `2026-04-12 22:45:25`
**来源**: `docs/api/_tools/audit_api_docs.py`
@@ -8,10 +8,10 @@
- Markdown 页面数(全部): `4006`
- Markdown 页面数canonical: `3978`
- Public headers 数: `384`
- `XCEditor` public headers 数: `64`canonical 已覆盖 `64`
- `XCEngine` public headers 数: `320`canonical 已覆盖 `320`
- Editor source headers 数: `144`
- Public headers 数: `393`
- `XCEditor` public headers 数: `66`canonical 已覆盖 `64`
- `XCEngine` public headers 数: `327`canonical 已覆盖 `320`
- Editor source headers 数: `145`
- 有效头文件引用数(全部): `384`
- 有效头文件引用数canonical: `384`
- 无效头文件引用数: `0`
@@ -41,20 +41,20 @@
|------|----------------|--------|--------|
| `XCEditor/Collections` | `9` | `9` | `0` |
| `XCEditor/Fields` | `23` | `23` | `0` |
| `XCEditor/Foundation` | `4` | `4` | `0` |
| `XCEditor/Foundation` | `6` | `4` | `2` |
| `XCEditor/Shell` | `24` | `24` | `0` |
| `XCEditor/Widgets` | `4` | `4` | `0` |
| `XCEngine/Audio` | `11` | `11` | `0` |
| `XCEngine/Components` | `11` | `11` | `0` |
| `XCEngine/Core` | `48` | `48` | `0` |
| `XCEngine/Components` | `12` | `11` | `1` |
| `XCEngine/Core` | `49` | `48` | `1` |
| `XCEngine/Debug` | `10` | `10` | `0` |
| `XCEngine/Input` | `5` | `5` | `0` |
| `XCEngine/Memory` | `5` | `5` | `0` |
| `XCEngine/Platform` | `11` | `11` | `0` |
| `XCEngine/RHI` | `88` | `88` | `0` |
| `XCEngine/Rendering` | `42` | `42` | `0` |
| `XCEngine/Resources` | `29` | `29` | `0` |
| `XCEngine/Scene` | `4` | `4` | `0` |
| `XCEngine/Rendering` | `44` | `42` | `2` |
| `XCEngine/Resources` | `31` | `29` | `2` |
| `XCEngine/Scene` | `5` | `4` | `1` |
| `XCEngine/Scripting` | `7` | `7` | `0` |
| `XCEngine/Threading` | `10` | `10` | `0` |
| `XCEngine/UI` | `39` | `39` | `0` |
@@ -67,7 +67,7 @@
| `Actions` | `9` | `9` | `0` |
| `Commands` | `4` | `4` | `0` |
| `ComponentEditors` | `11` | `11` | `0` |
| `Core` | `20` | `20` | `0` |
| `Core` | `21` | `20` | `1` |
| `Layers` | `1` | `1` | `0` |
| `Layout` | `1` | `1` | `0` |
| `Managers` | `3` | `3` | `0` |
@@ -88,3 +88,19 @@
| `描述` | `618` |
| `头文件` | `1975` |
| `源文件` | `520` |
## 未覆盖的 public headers
- `XCEditor/Foundation/UIEditorRuntimeTrace.h`
- `XCEditor/Foundation/UIEditorTextMeasurement.h`
- `XCEngine/Components/GaussianSplatRendererComponent.h`
- `XCEngine/Core/Asset/ArtifactContainer.h`
- `XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h`
- `XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h`
- `XCEngine/Resources/Shader/ShaderCompilationCache.h`
- `XCEngine/Resources/Texture/TextureImportHeuristics.h`
- `XCEngine/Scene/ModelSceneInstantiation.h`
## 未覆盖的 Editor 源文件页
- `editor/src/Core/ProjectAssetWatcher.h`