# 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)