Files
XCEngine/docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/MonoScriptRuntime.md

228 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MonoScriptRuntime
**命名空间**: `XCEngine::Scripting`
**类型**: `class`
**头文件**: `XCEngine/Scripting/Mono/MonoScriptRuntime.h`
**描述**: 当前唯一的托管脚本运行时后端,实现程序集加载、脚本类发现、字段元数据/默认值查询、实例创建、字段同步和生命周期调用。
## 概览
`MonoScriptRuntime``IScriptRuntime` 的当前唯一实现。它把 `ScriptEngine` 需要的抽象操作落到 Mono 世界中:
- 初始化和关闭 Mono 运行时上下文
- 加载核心程序集与应用程序集
- 发现可绑定脚本类
- 缓存字段元数据和生命周期方法
- 创建/销毁托管实例
- 读写托管字段
- 把托管 API 通过 internal call 接回原生系统
这层分工的关键价值,在于让 `ScriptEngine` 负责“什么时候调度”,让 `MonoScriptRuntime` 负责“如何在 Mono 里执行”。这样后续即使更换后端,场景生命周期和字段同步模型也仍然可以保持一致。
## Settings
构造函数接收一个 `Settings` 结构体。当前重要字段如下:
| 字段 | 说明 |
|------|------|
| `assemblyDirectory` | 程序集目录;若显式路径为空,会以它推导 DLL 路径。 |
| `corlibDirectory` | Mono 查找 `mscorlib.dll` 的目录。 |
| `coreAssemblyPath` | `XCEngine.ScriptCore.dll` 的路径。 |
| `appAssemblyPath` | `GameScripts.dll` 的路径。 |
| `coreAssemblyName` | 核心程序集名,默认 `XCEngine.ScriptCore`。 |
| `appAssemblyName` | 应用程序集名,默认 `GameScripts`。 |
| `baseNamespace` | 托管基础 API 命名空间,默认 `XCEngine`。 |
| `baseClassName` | 作为脚本基类入口的类型名,默认 `MonoBehaviour`。 |
`ResolveSettings()` 会补全这些路径,因此当前既可以显式指定 DLL也可以只指定程序集目录。
## 程序集与类发现模型
当前脚本类发现遵循两个核心边界:
- 只从 `m_appImage`,也就是应用程序集这一侧发现可绑定脚本类
- 只接受非抽象的 `MonoBehaviour` 子类
这意味着:
- `XCEngine.ScriptCore` 负责基础 API不直接成为可绑定游戏脚本来源
- `GameScripts.dll` 才是当前脚本类列表、字段元数据和默认值读取的共同事实来源
## 字段发现规则
字段发现发生在 `DiscoverScriptClassesInImage(...)` 阶段。当前真实筛选顺序是:
1. 排除 `static`
2. 排除 `literal / const`
3. 排除 `init-only / readonly`
4. 接受 `public` 字段,或者带 `[SerializeField]``private` 字段
5. 再由 `BuildFieldMetadata(...)` 检查字段类型是否受支持
这让当前字段模型和 Unity 风格更加接近:既支持公开字段,也支持“保持封装但允许序列化”的 `[SerializeField] private` 字段。
## 当前支持的字段类型
`BuildFieldMetadata(...)` 当前支持以下字段类型:
- `float`
- `double`
- `bool`
- `int32`
- `uint64`
- `string`
- `enum`
- 当前以 `ScriptFieldType::Int32` 暴露
- 仅支持基础类型为 `I1 / U1 / I2 / U2 / I4 / U4` 的枚举
- `Vector2`
- `Vector3`
- `Vector4`
- `GameObject`
- 具体组件引用
- `Transform`
- `Camera`
- `Light`
- `MeshFilter`
- `MeshRenderer`
- 具体脚本组件类型
以下类型当前不会进入字段模型:
- 未标注的 `private` 字段
- `readonly` 字段
- `Quaternion`
- `Component` / `MonoBehaviour` 这类过于宽泛的基类引用
- 数组、列表、泛型容器等当前未桥接类型
- 64 位底层枚举等未列入支持范围的枚举底层类型
## `TryGetClassFieldMetadata()` 与 `TryGetClassFieldDefaultValues()`
[TryGetClassFieldMetadata](TryGetClassFieldMetadata.md) 和 [TryGetClassFieldDefaultValues](TryGetClassFieldDefaultValues.md) 都只操作“已经通过上述规则进入字段缓存”的字段集合。
其中默认值查询当前会:
1. 切到 app domain
2. 临时创建一个托管对象
3. 执行默认构造和字段初始化
4. 逐个读取字段值
因此它返回的是“真实 C# 初始化后”的默认值,而不是单纯的 CLR 零值。
这也是为什么测试里可以直接读到:
- `FieldMetadataProbe.State == 2`
- `FieldMetadataProbe.HiddenFlag == true`
- `ProjectScriptProbe.Label == "ProjectScriptProbe"`
## 实例创建与字段同步
当前实例与字段同步模型是:
- [CreateScriptInstance](CreateScriptInstance.md)
- 根据 `ScriptComponent` 找类
- 创建托管对象
- 注入 `gameObjectUUID` / `scriptComponentUUID`
-`ScriptFieldStorage` 里的同名且类型匹配字段写回托管实例
- [SyncManagedFieldsToStorage](SyncManagedFieldsToStorage.md)
- 把运行中的托管字段同步回本地存储
这一模型和商业引擎的常见做法一致:类默认值提供初始模板,场景存储提供覆盖值,运行时再在同一字段集合上做双向同步。
## 为什么支持 `[SerializeField] private`
这是当前设计里非常关键的一点。支持它不是为了“多兼容一个语法糖”,而是为了兼顾两件事:
- 脚本作者需要字段能进入序列化和 Inspector 体系
- 但不希望因此把类的外部 API 全部暴露为 `public`
这带来的直接好处是:
- 封装边界更清晰
- Inspector / 场景序列化体验更接近 Unity
- 重构字段访问级别时更安全
而继续忽略“未标注的 private 字段”,则能避免把纯内部实现状态误持久化。
## internal call 桥接
当前 internal call 已经接通一套相对完整的托管 API
- `Debug.Log*`
- `Time`
- `Input`
- `GameObject`
- `Transform`
- `Camera`
- `Light`
- `MeshFilter`
- `MeshRenderer`
- `Object.Destroy(...)`
这些托管 API 当前并不是各自维护一套独立状态,而是直接桥接回原生系统。
## 测试锚点
当前行为直接受到以下测试约束:
- `ClassFieldMetadataListsSupportedPublicInstanceFields`
- `ClassFieldDefaultValueQueryReturnsSerializeFieldPrivateInitializers`
- `SerializeFieldPrivateFieldsApplyStoredValuesAndPersistAcrossSceneRoundTrip`
- `test_project_script_assembly.cpp` 中的项目程序集发现与默认值读取用例
## 当前实现边界
- 当前只发现应用程序集里的非抽象 `MonoBehaviour` 子类。
- `[SerializeField] private` 已纳入字段模型,但“未标注 private”依旧被忽略。
- 默认值查询依赖真实托管对象实例化,因此类缺失、实例构造失败或字段读取失败都会让整次查询失败。
- 当前没有程序集热重载、增量刷新或调试器集成。
## 公开方法
| 方法 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 创建运行时对象并解析设置。 |
| [Destructor](Destructor.md) | 析构时执行 `Shutdown()`。 |
| [Initialize](Initialize.md) | 初始化 Mono 域并发现脚本类。 |
| [Shutdown](Shutdown.md) | 关闭当前 Mono 运行时。 |
| [GetLastError](GetLastError.md) | 读取最近一次错误描述。 |
| [IsClassAvailable](IsClassAvailable.md) | 查询脚本类是否已发现。 |
| [GetScriptClassNames](GetScriptClassNames.md) | 返回已发现脚本类全名列表。 |
| `IsInitialized()` | 判断运行时是否完成初始化。 |
| `GetSettings()` | 返回已解析的设置。 |
| [TryGetAvailableScriptClasses](TryGetAvailableScriptClasses.md) | 返回脚本类描述列表。 |
| [TryGetClassFieldMetadata](TryGetClassFieldMetadata.md) | 返回当前字段模型中的字段元数据。 |
| [TryGetClassFieldDefaultValues](TryGetClassFieldDefaultValues.md) | 返回当前字段模型中的真实默认值。 |
| [HasManagedInstance](HasManagedInstance.md) | 判断脚本组件是否已有托管实例。 |
| [GetManagedInstanceCount](GetManagedInstanceCount.md) | 返回当前托管实例数。 |
| [GetManagedInstanceObject](GetManagedInstanceObject.md) | 获取托管对象裸指针。 |
| [CreateManagedComponentWrapper](CreateManagedComponentWrapper.md) | 为原生组件创建托管包装对象。 |
| [DestroyManagedObject](DestroyManagedObject.md) | 销毁托管对象映射到的原生对象或组件。 |
| [TryGetFieldValue](TryGetFieldValue.md) | 直接读取托管实例字段。 |
| [OnRuntimeStart](OnRuntimeStart.md) | 启动当前场景的托管上下文。 |
| [OnRuntimeStop](OnRuntimeStop.md) | 停止当前场景的托管上下文。 |
| [TrySetManagedFieldValue](TrySetManagedFieldValue.md) | 向托管实例写字段。 |
| [TryGetManagedFieldValue](TryGetManagedFieldValue.md) | 从托管实例读字段。 |
| [SyncManagedFieldsToStorage](SyncManagedFieldsToStorage.md) | 把托管字段同步回存储。 |
| [CreateScriptInstance](CreateScriptInstance.md) | 创建脚本实例。 |
| [DestroyScriptInstance](DestroyScriptInstance.md) | 销毁脚本实例。 |
| [InvokeMethod](InvokeMethod.md) | 调用生命周期方法。 |
## 真实行为依据
- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp`
- `managed/XCEngine.ScriptCore/SerializeField.cs`
- `managed/GameScripts/FieldMetadataProbe.cs`
- `managed/GameScripts/SerializeFieldProbe.cs`
- `tests/Scripting/test_mono_script_runtime.cpp`
- `tests/Scripting/test_project_script_assembly.cpp`
## 相关文档
- [Mono](../Mono.md)
- [IScriptRuntime](../../IScriptRuntime/IScriptRuntime.md)
- [ScriptEngine](../../ScriptEngine/ScriptEngine.md)
- [Scripting Runtime And Field Model](../../../../_guides/Scripting/Scripting-Runtime-And-Field-Model.md)
- [Project Script Assembly And Field Sync](../../../../_guides/Scripting/Project-Script-Assembly-And-Field-Sync.md)