diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/ClearMesh.md b/docs/api/XCEngine/Components/MeshFilterComponent/ClearMesh.md index 6a63b19e..f474dc7d 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/ClearMesh.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/ClearMesh.md @@ -1,6 +1,6 @@ # MeshFilterComponent::ClearMesh -清空当前 mesh。 +清空当前 mesh 绑定状态。 ```cpp void ClearMesh(); @@ -8,10 +8,18 @@ void ClearMesh(); ## 行为说明 -当前实现会重置内部 handle,并清空缓存路径。 +当前实现会同时重置: + +- `m_pendingMeshLoad` +- `m_asyncMeshLoadRequested` +- `m_mesh` +- `m_meshPath` +- `m_meshRef` + +因此它不仅移除当前 mesh,也会清掉 deferred scene load 的待完成状态和资产引用元数据。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) - [SetMesh](SetMesh.md) -- [GetMeshPath](GetMeshPath.md) +- [SetMeshPath](SetMeshPath.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/Deserialize.md b/docs/api/XCEngine/Components/MeshFilterComponent/Deserialize.md index 210b8e31..79f110a8 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/Deserialize.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/Deserialize.md @@ -1,26 +1,89 @@ # MeshFilterComponent::Deserialize -反序列化 mesh 信息。 +反序列化 mesh 绑定信息。 ```cpp void Deserialize(std::istream& is) override; ``` -## 行为说明 +## 当前实现行为 -当前实现会: +当前实现会先清空: -1. 先清空现有 handle 和路径。 -2. 按 `;` 分割 token。 -3. 识别 `mesh=`。 -4. 若路径非空,则调用 `ResourceManager::Get().Load()` 尝试重新加载。 +- `m_pendingMeshLoad` +- `m_asyncMeshLoadRequested` +- `m_mesh` +- `m_meshPath` +- `m_meshRef` -## 当前实现说明 +然后只解析两项键值: -- 如果资源加载失败,`m_mesh` 可能仍然为空,但 `m_meshPath` 会保留反序列化得到的路径。 -- 当前序列化格式很轻量,适合人类可读文本,但没有更强的容错与版本化机制。 +- `meshRef=` +- `meshPath=` + +## 恢复顺序 + +### 1. `meshRef` 有效 + +这是当前项目资产恢复的主路径。 + +当前会先保存 `m_meshRef = pendingMeshRef`,然后分两种情况: + +- 若 `ResourceManager::IsDeferredSceneLoadEnabled()` 为真 + - 先尝试 `TryResolveAssetPath(meshRef, resolvedPath)` + - 成功时只恢复 `m_meshPath = resolvedPath` + - 不立即同步加载 mesh +- 若不在 deferred 模式 + - 直接 `Load(pendingMeshRef)` + - 加载成功则从资源句柄反写 `m_meshPath` + - 失败时才回退到文本里读到的 `pendingMeshPath` + +### 2. `meshRef` 无效,但 `meshPath` 是 virtual scheme + +当前只接受这类路径回退: + +- 若处于 deferred scene load,直接保留 `m_meshPath` +- 否则调用 [SetMeshPath](SetMeshPath.md) 走同步加载 + +### 3. `meshRef` 无效,且 `meshPath` 只是普通项目路径 + +当前会直接忽略。 + +也就是说,像下面这种旧数据: + +```text +meshPath=Assets/runtime.obj;meshRef=; +``` + +当前不会再被当成正式项目资产绑定恢复。 + +## 已删除的旧心智 + +这页最需要明确的一点是: + +- 当前不再兼容 `mesh=` +- 当前也不再把“没有 `AssetRef` 的普通项目路径”当成长期场景协议 + +只有这两类数据还能稳定恢复: + +- 有效 `meshRef` +- 带 virtual scheme 的 `meshPath` + +## 与 deferred scene load 的关系 + +在 deferred 模式下,反序列化阶段更像是恢复绑定元数据,而不是立刻拿到 mesh 句柄。 + +真正的兑现一般留到后续: + +- [GetMesh](GetMesh.md) +- [GetMeshHandle](GetMeshHandle.md) + +第一次访问这些接口时,内部才会启动或收口异步加载。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) - [Serialize](Serialize.md) +- [GetMesh](GetMesh.md) +- [GetMeshHandle](GetMeshHandle.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/GetMesh.md b/docs/api/XCEngine/Components/MeshFilterComponent/GetMesh.md index 2b43312d..8e1f439f 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/GetMesh.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/GetMesh.md @@ -1,17 +1,33 @@ # MeshFilterComponent::GetMesh -获取当前 mesh 指针。 +获取当前可用的 mesh 指针。 ```cpp Resources::Mesh* GetMesh() const; ``` +## 行为说明 + +当前实现不是单纯返回 `m_mesh.Get()`,还会在内部额外执行两步: + +1. `EnsureDeferredAsyncMeshLoadStarted()`:在 deferred scene load 模式下,如果当前只有路径还没有实际 mesh,会按 `m_meshPath` 发起一次异步加载。 +2. `ResolvePendingMesh()`:如果异步回调已经完成,则把结果写回 `m_mesh`、`m_meshPath` 和 `m_meshRef`。 + +因此,这个访问器既是读取接口,也是 deferred mesh 解析流程的推进点。 + ## 返回值 -- 当前 mesh 指针;如果没有设置则返回 `nullptr`。 +- 如果当前已经有可用 mesh,返回对应指针。 +- 如果没有 mesh,或异步加载尚未完成,返回 `nullptr`。 +- 异步加载失败时也返回 `nullptr`,同时保留已有路径信息。 + +## 注意事项 + +- 在延迟场景加载模式下,第一次调用可能只是发起异步加载,本次仍返回空指针。 +- 渲染提取逻辑会直接依赖这个返回值;返回 `nullptr` 时,对象不会进入可见渲染列表。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) - [GetMeshHandle](GetMeshHandle.md) -- [SetMesh](SetMesh.md) +- [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshAssetRef.md b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshAssetRef.md new file mode 100644 index 00000000..1e2d7601 --- /dev/null +++ b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshAssetRef.md @@ -0,0 +1,31 @@ +# MeshFilterComponent::GetMeshAssetRef + +获取当前 mesh 的资产引用。 + +```cpp +const Resources::AssetRef& GetMeshAssetRef() const; +``` + +## 返回值 + +- 返回内部保存的 `AssetRef` 常量引用。 + +## 行为说明 + +`m_meshRef` 会在以下路径里更新: + +- [SetMeshPath](SetMeshPath.md) 成功从路径反查资产引用时 +- [SetMesh](SetMesh.md) 成功从 mesh 路径反查资产引用时 +- [Deserialize](Deserialize.md) 直接读到 `meshRef=` 字段时 +- 异步 mesh 兑现后,再次按最终路径回填资产引用时 + +## 注意事项 + +- 这是只读元数据访问器,不会触发加载。 +- 当路径无法映射到项目资产,或反序列化数据里没有有效 `meshRef` 时,返回值可能是无效引用。 + +## 相关文档 + +- [返回类型总览](MeshFilterComponent.md) +- [GetMeshPath](GetMeshPath.md) +- [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshHandle.md b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshHandle.md index bec74c8e..64f2dd24 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshHandle.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshHandle.md @@ -6,11 +6,25 @@ const Resources::ResourceHandle& GetMeshHandle() const; ``` +## 行为说明 + +当前实现和 [GetMesh](GetMesh.md) 一样,会先尝试推进 deferred scene load: + +- 如果还没请求过异步加载,会按 `m_meshPath` 发起一次 `LoadAsync()`。 +- 如果异步结果已经回到 `m_pendingMeshLoad`,会先把结果兑现到 `m_mesh` 再返回句柄引用。 + ## 返回值 -- 当前保存的 mesh `ResourceHandle` 常量引用。 +- 返回内部 `m_mesh` 的常量引用。 +- 在 mesh 尚未加载完成时,返回的句柄可能为空。 + +## 注意事项 + +- 这是“可能带副作用”的访问器,而不是纯 getter。 +- 如果调用方只想读取序列化主数据而不触发加载,应优先看 [GetMeshPath](GetMeshPath.md) 或 [GetMeshAssetRef](GetMeshAssetRef.md)。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) - [GetMesh](GetMesh.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshPath.md b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshPath.md index 6f078733..e47b28f0 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshPath.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/GetMeshPath.md @@ -8,14 +8,22 @@ const std::string& GetMeshPath() const; ## 返回值 -- 当前缓存的 mesh 路径。 +- 返回组件当前保存的路径字符串。 + +## 行为特点 + +- 这是当前场景文本序列化的主数据之一。 +- 当 mesh 通过句柄设置或异步加载成功后,路径可能被更新为资源对象实际返回的 `GetPath()`。 +- 即使 mesh 当前还没加载成功,路径也可能已经存在。 ## 注意事项 -- 当前路径只是组件内部缓存值,不保证文件一定存在。 +- 它不会触发异步加载,也不会等待待完成的 mesh 解析。 +- 路径存在不代表 `GetMesh()` 一定非空。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) - [Serialize](Serialize.md) - [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/MeshFilterComponent.md b/docs/api/XCEngine/Components/MeshFilterComponent/MeshFilterComponent.md index 85b295ce..058ad7b8 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/MeshFilterComponent.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/MeshFilterComponent.md @@ -6,97 +6,150 @@ **头文件**: `XCEngine/Components/MeshFilterComponent.h` -**描述**: 保存网格资源引用及其路径,让场景对象能够声明“我使用哪一个 Mesh”。 +**描述**: 保存单个 mesh 绑定状态,把运行时 `Mesh` 句柄、项目资产 `AssetRef` 和少量 virtual path 协议收口到同一个组件里。 ## 角色概述 -`MeshFilterComponent` 负责“几何数据来自哪里”,而不是“怎么绘制”。在当前对象模型里: +`MeshFilterComponent` 回答的是一个很具体的问题: -- `MeshFilterComponent` 提供网格 -- [MeshRendererComponent](../MeshRendererComponent/MeshRendererComponent.md) 提供材质和渲染附加配置 -- 两者一起被 [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md) 消费 +- 这个对象当前引用哪一个 mesh? -这和 Unity 里 `MeshFilter` / `MeshRenderer` 分工的思路是一致的,优点是几何与渲染配置解耦,便于编辑器或脚本单独替换。 +它不处理材质,也不直接发起绘制。当前真正的职责有三层: -## 当前实现行为 +- 保存运行时 `Mesh` 句柄,供渲染提取和运行时逻辑直接使用 +- 保存项目资产 `AssetRef`,让场景在资源移动或重命名后仍能稳定恢复 +- 仅在没有可用 `AssetRef` 时,为 `builtin://` 这类 virtual path 保留文本路径 -### 1. 同时保存资源句柄和资源路径 +这意味着当前文档里最需要纠正的一点是: -内部状态有两份: +- 项目资产的正式序列化协议已经不再是普通路径字符串 +- 对项目 mesh,当前主身份是 `meshRef` +- `meshPath` 主要保留给 `builtin://` 或其它 virtual scheme -- `m_mesh`:`ResourceHandle` -- `m_meshPath`:`std::string` +## 当前状态模型 -这样设计的直接收益是: +当前实现维护 5 份与 mesh 绑定相关的状态: -- 运行时可以快速拿到已加载资源 -- 序列化时又能稳定写出路径 +| 状态 | 类型 | 作用 | +|------|------|------| +| `m_mesh` | `ResourceHandle` | 当前已兑现的运行时 mesh 句柄。 | +| `m_meshPath` | `std::string` | 当前内存里的路径缓存;对项目资产通常会是 `Assets/...`,对 virtual 资源则可能是 `builtin://...`。 | +| `m_meshRef` | `Resources::AssetRef` | 项目资产的稳定身份。 | +| `m_pendingMeshLoad` | `std::shared_ptr<...>` | deferred async load 的挂起结果。 | +| `m_asyncMeshLoadRequested` | `bool` | 防止重复发起异步 mesh 加载。 | -### 2. `SetMeshPath()` 会尝试加载,但即使加载失败也保留路径 +这几份状态不是完全等价的,尤其要区分: -按当前实现: +- `m_meshRef` 是项目资产正式协议 +- `m_meshPath` 是运行时缓存与 virtual path 回退字段 -- 传入空路径时会清空 handle 和 path -- 传入非空路径时会调用 `ResourceManager::Get().Load(...)` -- 无论资源是否成功加载,`m_meshPath` 都会先被更新 +## 绑定与恢复流程 -这是一条很重要的行为约定。它意味着: +### 1. 运行时按路径设置 -- 文档页或编辑器可以先持有“用户想要的路径” -- 即使资源此刻没加载出来,序列化信息也不会丢 +[SetMeshPath](SetMeshPath.md) 当前仍然是“立即尝试同步加载”的接口: -这一点已经被 `tests/Components/test_mesh_render_components.cpp` 覆盖。 +1. 清掉旧的 pending async load 状态 +2. 直接写入 `m_meshPath` +3. 若路径为空,则清空句柄与 `AssetRef` +4. 否则同步 `Load(path)` +5. 再尝试按路径回填 `m_meshRef` -### 3. 直接设置 handle 时会反向推导路径 +因此对项目路径 `Assets/...` 来说,运行时是允许直接设置路径的;但这不等于序列化后仍靠路径当正式协议。 -`SetMesh(const ResourceHandle&)` 会: +### 2. 运行时按句柄设置 -- 保存 handle -- 如果 handle 非空,则从 `mesh->GetPath()` 反推出 `m_meshPath` -- 如果 handle 为空,则清空路径 +[SetMesh](SetMesh.md) 会保存句柄,并从 `mesh->GetPath()` 反推路径,再按路径尝试回填 `AssetRef`。 -这保证了路径和运行时资源引用不会长期脱节。 +这保证了“直接塞一个已加载 mesh”与“后续还能序列化出稳定项目身份”这两件事可以同时成立。 + +### 3. 反序列化恢复 + +[Deserialize](Deserialize.md) 当前只识别: + +- `meshRef=` +- `meshPath=` + +恢复顺序是: + +1. 如果 `meshRef` 有效,优先按 `AssetRef` 恢复 +2. 若当前处于 deferred scene load,先尝试 `TryResolveAssetPath(meshRef, path)` 恢复 `Assets/...` 路径,但不立即同步加载 +3. 若不在 deferred 模式,则直接按 `AssetRef` 加载 mesh +4. 若没有有效 `meshRef`,只接受带 virtual scheme 的 `meshPath` +5. 没有 `AssetRef` 的普通项目路径会被忽略 + +这里是当前实现最容易被旧文档写错的地方: + +- `mesh=...` 已经不是当前协议 +- `meshPath=Assets/...` 且没有 `meshRef`,当前不会被当成正式项目资产绑定恢复 ## 序列化语义 -当前序列化只写一个字段: +当前 [Serialize](Serialize.md) 的真实输出规则是: -- `mesh=` +- 总是写 `meshRef=...;` +- 只有在没有有效 `AssetRef` 且 `m_meshPath` 是 virtual scheme 时,才额外写 `meshPath=...;` -反序列化时会先清空旧状态,再通过 `SetMeshPath()` 恢复路径并尝试加载资源。 +因此: -这意味着: +- 项目资产 mesh 常见输出是只有 `meshRef` +- `builtin://meshes/cube` 这类路径才会稳定出现在 `meshPath` -- 场景文件的真实持久化载体是路径 -- 运行时 handle 是可重建缓存,而不是主数据 +这和商业引擎里“项目资产走稳定内部引用,builtin/虚拟资源走协议路径”的思路是一致的。 -## 与渲染提取的关系 +## 与 deferred scene load 的关系 -`RenderSceneExtractor` 只有在以下条件都满足时才会把对象当成可见渲染项: +[GetMesh](GetMesh.md) 和 [GetMeshHandle](GetMeshHandle.md) 都不是纯 getter。 -- `GameObject` 处于激活层级 -- 同时存在 `MeshFilterComponent` 和 `MeshRendererComponent` -- 两个组件都已启用 -- `MeshFilterComponent::GetMesh()` 返回非空且 `mesh->IsValid()` +当前它们会内部触发: -因此,只有 `MeshFilterComponent` 还不够,它必须和 `MeshRendererComponent` 配对使用。 +1. `EnsureDeferredAsyncMeshLoadStarted()` +2. `ResolvePendingMesh()` -## 线程语义 +因此在 deferred scene load 模式下,反序列化阶段往往只恢复: -- 当前实现没有内部加锁。 -- 资源加载调用走 `ResourceManager`,但本组件文档不提供额外线程安全保证。 +- `m_meshRef` +- `m_meshPath` -## 当前实现限制 +真正 mesh 句柄的兑现,通常发生在后续访问器首次被调用时。 -- 它只负责引用网格,不处理子网格裁剪、LOD 或网格 streaming 策略。 -- 场景保存后真正持久化的是路径,不是已经加载好的资源实例。 -- 当前目录下只有 [SetMesh](SetMesh.md) 方法页,`SetMeshPath()` 也是 public API,应优先参考本页的行为说明。 +## 与渲染链路的关系 + +`RenderSceneUtility::AppendRenderItemsForGameObject()` 只有在以下条件同时满足时,才会把对象加入可见渲染项: + +- 对象激活 +- `MeshFilterComponent` 与 `MeshRendererComponent` 都存在且启用 +- `meshFilter->GetMesh()` 返回非空并且 mesh 有效 + +所以当前 `MeshFilterComponent` 的真实影响是: + +- 它决定对象什么时候拥有几何体 +- 但 deferred load 下,“路径和 `AssetRef` 已恢复”不等于“这一帧已经可绘制” + +## 测试与锚点 + +- `tests/Components/test_mesh_render_components.cpp` + - 覆盖 builtin virtual path 的序列化 / 反序列化 + - 覆盖“没有 `AssetRef` 的普通路径会被忽略” + - 覆盖项目 mesh 按 `AssetRef` 序列化与恢复 + - 覆盖 deferred async load +- `tests/Scene/test_scene.cpp` + - 覆盖 builtin mesh path 的场景保存 / 加载 + - 覆盖样例场景里项目 mesh `Assets/Models/backpack/backpack.obj` 的恢复和延迟加载 + +## 当前实现边界 + +- 当前只绑定单个 mesh,不处理 LOD、mesh streaming 或多几何切换 +- 项目 mesh 的正式序列化协议已经收口到 `AssetRef`,不再鼓励把普通项目路径当长期场景格式依赖 +- 没有 `AssetRef` 的普通项目路径在反序列化时当前会被忽略;只有 `builtin://` / 其它 virtual scheme 还能单独靠路径存活 +- `GetMesh()` / `GetMeshHandle()` 是带副作用的访问器;只读元数据时应优先用 [GetMeshPath](GetMeshPath.md) 和 [GetMeshAssetRef](GetMeshAssetRef.md) ## 相关方法 - [GetMesh](GetMesh.md) - [GetMeshHandle](GetMeshHandle.md) - [GetMeshPath](GetMeshPath.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) - [SetMesh](SetMesh.md) - [SetMeshPath](SetMeshPath.md) - [ClearMesh](ClearMesh.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/Serialize.md b/docs/api/XCEngine/Components/MeshFilterComponent/Serialize.md index 66531084..cffb03b3 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/Serialize.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/Serialize.md @@ -1,22 +1,67 @@ # MeshFilterComponent::Serialize -序列化当前 mesh 信息。 +序列化当前 mesh 绑定信息。 ```cpp void Serialize(std::ostream& os) const override; ``` -## 行为说明 +## 当前实现行为 -当前实现会输出: +当前实现会先准备一份待写出的 `meshRef`: + +- 优先使用当前内存中的 `m_meshRef` +- 如果 `m_meshRef` 还无效,但 `m_meshPath` 是普通项目路径而不是 virtual scheme,则会再尝试一次 `TryGetAssetRef(...)` + +随后输出规则只有两条: + +1. 总是写出 `meshRef=<...>;` +2. 只有当 `meshRef` 仍无效,且 `m_meshPath` 是 virtual scheme 时,才额外写出 `meshPath=<...>;` + +## 当前序列化结果 + +### 项目资产 mesh + +例如当前组件绑定的是 `Assets/runtime.obj` 且能解析出有效 `AssetRef`,输出通常会像: ```text -mesh=; +meshRef=; ``` -这里只序列化路径,不会序列化 mesh 二进制内容本身。 +注意: + +- 这时不会再写 `meshPath=Assets/runtime.obj;` + +### builtin / virtual path mesh + +例如当前组件绑定的是 `builtin://meshes/cube`,而它没有项目资产 `AssetRef`,输出会像: + +```text +meshRef=; +meshPath=builtin://meshes/cube; +``` + +## 关键语义 + +- 当前已经不再输出旧的 `mesh=` 键 +- 项目资产的正式场景身份是 `meshRef`,不是普通路径字符串 +- `meshPath` 现在主要是 virtual path 回退字段 + +## 设计说明 + +这套设计的价值在于: + +- 项目资源移动、改名或重新导入后,场景仍可按稳定身份恢复 +- builtin / 虚拟资源又不必强行塞进项目资产数据库 + +因此它不是“路径 + `AssetRef` 两份主数据并列”,而是: + +- 项目资产优先 `AssetRef` +- virtual resource 回退 `meshPath` ## 相关文档 - [返回类型总览](MeshFilterComponent.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) +- [GetMeshPath](GetMeshPath.md) - [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/SetMesh.md b/docs/api/XCEngine/Components/MeshFilterComponent/SetMesh.md index cee01665..a41d5d78 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/SetMesh.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/SetMesh.md @@ -1,6 +1,6 @@ # MeshFilterComponent::SetMesh -设置当前 mesh。 +直接设置当前 mesh 资源。 ```cpp void SetMesh(const Resources::ResourceHandle& mesh); @@ -9,18 +9,26 @@ void SetMesh(Resources::Mesh* mesh); ## 行为说明 -无论使用哪种重载,当前实现都会: +两个重载最终都会走 `ResourceHandle` 版本。当前实现会: -- 更新内部 `ResourceHandle` -- 如果 mesh 非空,则把其 `GetPath()` 转成 `std::string` 写入 `m_meshPath` -- 否则清空路径 +1. 清掉待完成的异步加载状态。 +2. 保存传入的 mesh 句柄。 +3. 如果句柄非空,则从 `mesh->GetPath()` 反推出 `m_meshPath`。 +4. 再尝试按该路径反查 `m_meshRef`。 +5. 如果句柄为空或路径为空,则清空 `m_meshRef`。 ## 参数 -- `mesh` - 要设置的 mesh 句柄或裸指针。 +- `mesh` - 要绑定的 mesh 句柄或裸指针。 + +## 设计含义 + +这让运行时代码可以直接复用已加载 mesh,同时仍保留后续序列化和场景保存所需的路径 / 资产引用信息。 ## 相关文档 - [返回类型总览](MeshFilterComponent.md) - [GetMesh](GetMesh.md) +- [GetMeshPath](GetMeshPath.md) +- [GetMeshAssetRef](GetMeshAssetRef.md) - [ClearMesh](ClearMesh.md) diff --git a/docs/api/XCEngine/Components/MeshFilterComponent/SetMeshPath.md b/docs/api/XCEngine/Components/MeshFilterComponent/SetMeshPath.md index c99697c1..f61fc75c 100644 --- a/docs/api/XCEngine/Components/MeshFilterComponent/SetMeshPath.md +++ b/docs/api/XCEngine/Components/MeshFilterComponent/SetMeshPath.md @@ -1,30 +1,35 @@ -# SetMeshPath +# MeshFilterComponent::SetMeshPath -**所属类型**: [MeshFilterComponent](MeshFilterComponent.md) - -## 签名 +按资源路径声明当前组件应引用哪个 mesh。 ```cpp void SetMeshPath(const std::string& meshPath); ``` -## 作用 +## 行为说明 -按资源路径声明当前组件应引用哪个 `Mesh`,并尝试通过 `ResourceManager` 立即加载该资源。 +当前实现会按以下顺序执行: -## 当前实现行为 +1. 清掉 `m_pendingMeshLoad`,并把 `m_asyncMeshLoadRequested` 复位为 `false`。 +2. 把 `m_meshPath` 直接更新为传入值。 +3. 如果路径为空,则清空 `m_mesh` 和 `m_meshRef` 后返回。 +4. 如果路径非空,则立即调用 `ResourceManager::Load(meshPath)` 尝试同步加载 mesh。 +5. 无论 mesh 是否加载成功,都会再尝试按路径反查 `AssetRef`。 -- 先把 `m_meshPath` 更新为传入值。 -- 如果路径为空,清空当前 `m_mesh` handle 并返回。 -- 如果路径非空,调用 `ResourceManager::Get().Load(meshPath.c_str())`。 -- 即使加载失败,路径也会被保留下来。 +## 参数 -## 使用建议 +- `meshPath` - 要绑定的 mesh 资源路径。 -这是编辑器或序列化恢复路径更应该使用的接口,因为它保留了“目标资源路径”这一主数据。 +## 关键语义 + +- 这个 setter 会把传入路径直接写进运行时缓存,即使加载失败也会保留路径文本。 +- 这个 API 是“立即同步尝试加载”的语义;deferred scene load 只影响 [Deserialize](Deserialize.md) 后的恢复路径,不会改变这里的行为。 +- 如果资产数据库能识别这条路径,`m_meshRef` 可能有效,即使 `m_mesh` 仍为空。 +- 对项目资产来说,当前场景正式协议仍优先写 `AssetRef`;这里保留的路径更像运行时输入和缓存,而不是长期持久化格式本体。 ## 相关文档 -- [MeshFilterComponent](MeshFilterComponent.md) +- [返回类型总览](MeshFilterComponent.md) +- [SetMesh](SetMesh.md) - [Serialize](Serialize.md) - [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshRendererComponent/Deserialize.md b/docs/api/XCEngine/Components/MeshRendererComponent/Deserialize.md index 4069ba50..fc4cc5cf 100644 --- a/docs/api/XCEngine/Components/MeshRendererComponent/Deserialize.md +++ b/docs/api/XCEngine/Components/MeshRendererComponent/Deserialize.md @@ -6,44 +6,76 @@ void Deserialize(std::istream& is) override; ``` -## 行为说明 +## 当前实现行为 -当前实现会: +当前实现会先: -1. 先清空材质并重置默认标志。 -2. 解析 `materialPaths`、`materialRefs`、`castShadows`、`receiveShadows` 和 `renderLayer`。 -3. 兼容读取历史 `materials=` 键,并把它当作 `materialPaths=` 处理。 -4. 让 `m_materials`、`m_materialPaths`、`m_materialRefs`、pending 数组和 async 标记数组在槽位数量上重新对齐。 +1. 调用 `ClearMaterials()` +2. 把默认值重置为: + - `castShadows = true` + - `receiveShadows = true` + - `renderLayer = 0` -随后每个槽位按下面的优先级恢复: +然后只解析: -1. 如果 `materialRef` 有效,先尝试按 `AssetRef` 恢复。 -2. 如果当前处于 deferred scene load,优先尝试把 `AssetRef` 重新解析回路径,但不立即同步加载材质。 -3. 如果没有有效 `AssetRef`,或者按 `AssetRef` 恢复失败,再退回到路径恢复。 -4. 只有在非 deferred 路径下,才会直接调用 [SetMaterialPath](SetMaterialPath.md) 做同步加载。 +- `materialPaths=` +- `materialRefs=` +- `castShadows` +- `receiveShadows` +- `renderLayer` -这意味着“反序列化完成”并不等于“所有材质句柄都已经可用”。 +## 恢复顺序 -## 当前实现说明 +### 1. 某槽位 `materialRef` 有效 -- 默认值是:`castShadows = true`、`receiveShadows = true`、`renderLayer = 0`。 -- 如果某个槽位最终只恢复出了路径或 `AssetRef`,对应 `m_materials[index]` 仍可能为空。 -- 在 deferred scene load 模式下,真正的兑现通常要等首次 [GetMaterial](GetMaterial.md) 或 [GetMaterialHandle](GetMaterialHandle.md) 调用时才会触发。 +这是当前项目材质恢复的主路径。 -## 项目资产恢复语义 +当前会先保存 `m_materialRefs[i] = pendingMaterialRefs[i]`,然后分两种情况: -当前实现特别强调项目资产的稳定恢复: +- 若 `ResourceManager::IsDeferredSceneLoadEnabled()` 为真 + - 先尝试 `TryResolveAssetPath(materialRef, resolvedPath)` + - 成功时只恢复 `m_materialPaths[i] = resolvedPath` + - 不立即同步加载材质 +- 若不在 deferred 模式,或 deferred 下未成功解析路径 + - 直接 `Load(pendingMaterialRef)` + - 成功时从句柄反写 `m_materialPaths[i]` -- 当场景文本里有有效 `materialRef=` 时,组件会尽量依赖它,而不是单纯依赖字符串路径。 -- 如果 `AssetRef` 能解析到当前项目中的材质,路径会被重新规范化回 `Assets/...`。 -- 如果解析失败,仍会回退到文本路径。 +### 2. 某槽位 `materialRef` 无效 -这和商业引擎常见的“文本可读路径 + 稳定内部引用”双轨设计一致,优点是既便于调试,也能提高资源重命名后的恢复成功率。 +这时只剩路径回退分支: -## 兼容性说明 +- 若 `m_materialPaths[i]` 不是 virtual scheme,当前会直接清空该路径 +- 若是 virtual scheme 且处于 deferred 模式,则只保留路径 +- 若是 virtual scheme 且不在 deferred 模式,则调用 [SetMaterialPath](SetMaterialPath.md) 走同步加载 -- 仍兼容历史 `materials=` 文本。 -- 新版 [Serialize](Serialize.md) 不再输出 `materials=`。 +## 已删除的旧心智 + +这页当前必须明确: + +- 不再读取历史 `materials=` 键 +- 不再把“没有 `AssetRef` 的普通项目路径”当作正式项目材质恢复协议 + +因此像下面这种旧式数据: + +```text +materialPaths=Assets/runtime.material;materialRefs=; +``` + +当前恢复后,这个普通路径会被清掉,而不是继续被保留。 + +## 与 deferred scene load 的关系 + +在 deferred 模式下,反序列化阶段通常只恢复: + +- `m_materialRefs` +- `m_materialPaths` + +真正材质句柄的兑现,往往留到后续: + +- [GetMaterial](GetMaterial.md) +- [GetMaterialHandle](GetMaterialHandle.md) + +这些访问器第一次被调用时,内部才会启动或收口异步材质加载。 ## 相关文档 diff --git a/docs/api/XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md b/docs/api/XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md index eb693e1b..33b513de 100644 --- a/docs/api/XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md +++ b/docs/api/XCEngine/Components/MeshRendererComponent/MeshRendererComponent.md @@ -6,164 +6,156 @@ **头文件**: `XCEngine/Components/MeshRendererComponent.h` -**描述**: 保存材质槽、资产引用、阴影开关和渲染层等绘制配置,把场景对象与具体 `Material` 资源绑定起来。 +**描述**: 保存材质槽、项目资产 `AssetRef`、运行时材质句柄以及少量渲染附加状态,把对象的“怎么画”这一侧数据收口到一个组件里。 ## 角色概述 -`MeshRendererComponent` 负责回答“这个对象上的 mesh 应该用什么材质、以什么附加状态被渲染”。它不提供几何来源,也不直接发起 draw call;当前链路里它主要扮演四个角色: +`MeshRendererComponent` 回答的问题是: -- 为运行时保存可直接使用的 `Material` 句柄。 -- 为场景文本保存稳定的材质路径数组。 -- 为项目资产保存 `AssetRef`,让场景在资源移动或重命名后仍有机会恢复材质绑定。 -- 为脚本层、编辑器和渲染提取层提供统一的材质槽视图。 +- 这个对象的 mesh 该用哪些材质、带哪些附加渲染状态去画? -和它配套工作的主要对象是: +当前它的职责可以分成四层: -- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md):负责 mesh 资源本身。 -- `ResourceManager`:负责按路径或 `AssetRef` 加载 `Material`,也负责延迟异步加载。 -- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md):只有 mesh 和材质都可用时,才会把对象整理成可见渲染项。 +- 保存运行时 `Material` 句柄数组 +- 保存项目材质的稳定 `AssetRef` +- 仅对 `builtin://` 这类 virtual path 保留 `materialPaths` +- 保存 `castShadows / receiveShadows / renderLayer` -这种拆分很接近商业引擎里常见的 `MeshFilter + MeshRenderer` 思路。好处是“几何来源”和“绘制配置”可以分别序列化、分别编辑,也更适合后续做资源热更新、材质复用和编辑器 Inspector。 +和 [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md) 一样,当前最需要纠正的文档心智是: + +- 对项目资产材质,正式序列化协议已经是 `materialRefs` +- `materialPaths` 主要保留给 virtual path,而不是给普通项目路径长期兜底 ## 当前状态模型 -当前实现维护了五份与材质槽相关的状态: +当前实现维护 5 组与材质槽相关的状态: | 状态 | 类型 | 作用 | |------|------|------| | `m_materials` | `std::vector>` | 当前已兑现的运行时材质句柄。 | -| `m_materialPaths` | `std::vector` | 可序列化、可调试的路径数组。 | -| `m_materialRefs` | `std::vector` | 项目资产数据库可解析的稳定引用。 | -| `m_pendingMaterialLoads` | `std::vector>` | 异步加载尚未收口时的挂起结果。 | -| `m_asyncMaterialLoadRequested` | `std::vector` | 防止同一槽位重复发起异步加载。 | +| `m_materialPaths` | `std::vector` | 槽位路径缓存;项目资产通常会是 `Assets/...`,virtual 资源可能是 `builtin://...`。 | +| `m_materialRefs` | `std::vector` | 项目资产稳定身份。 | +| `m_pendingMaterialLoads` | `std::vector>` | deferred async material load 的挂起结果。 | +| `m_asyncMaterialLoadRequested` | `std::vector` | 防止重复发起异步加载。 | -这几组数组会尽量保持同一槽位语义对齐。换句话说,`slot 3` 的 handle、path、`AssetRef`、pending state 都是在描述“第 3 个材质槽”。 +所有数组都按槽位对齐;`slot 2` 的 path、ref、handle 和 pending state 都在描述同一材质槽。 -## 绑定与解析流程 +## 绑定与恢复流程 -### 1. 路径驱动的绑定 +### 1. 运行时按路径设置 -[SetMaterialPath](SetMaterialPath.md) 会: +[SetMaterialPath](SetMaterialPath.md) 当前仍然是同步接口: -- 先确保槽位存在。 -- 清掉该槽位旧的异步挂起状态。 -- 写入新的 `m_materialPaths[index]`。 -- 立即调用 `ResourceManager::Load(path)` 做一次同步加载尝试。 -- 再调用 `TryGetAssetRef(path, ResourceType::Material, ...)` 尝试回填 `AssetRef`。 +1. 自动扩容到目标槽位 +2. 清掉旧 pending async load 状态 +3. 写入 `m_materialPaths[index]` +4. 若路径为空,则清掉 handle 和 `AssetRef` +5. 若路径非空,则同步 `Load(path)` +6. 再尝试按路径回填 `m_materialRefs[index]` -即使同步加载失败,路径仍会保留;如果这条路径能映射到项目资产,`m_materialRefs[index]` 也可能仍然有效。 +因此对项目路径 `Assets/runtime.material` 来说,运行时是允许直接按路径设置的;但场景文本正式协议已经不是“只存路径”。 -要注意一个很容易误解的点:按当前源码,`SetMaterialPath()` 本身仍是“立即同步加载”的接口,它不会因为 deferred scene load 模式而自动改成纯记录路径。 +### 2. 运行时按句柄设置 -### 2. 句柄驱动的绑定 +[SetMaterial](SetMaterial.md) / [SetMaterials](SetMaterials.md) 会从句柄反推出路径,再按路径尝试回填 `AssetRef`。 -[SetMaterial](SetMaterial.md) 会: +这让“运行时直接塞句柄”和“后续还能稳定序列化回项目身份”保持一致。 -- 保存句柄。 -- 通过 `material->GetPath()` 反推路径。 -- 再按路径尝试回填 `AssetRef`。 +### 3. 反序列化恢复 -这让“运行时直接塞一个已经加载好的材质”与“场景序列化仍能恢复路径/资产引用”两件事可以同时成立。 - -### 3. 反序列化后的恢复策略 - -[Deserialize](Deserialize.md) 现在会同时处理: +[Deserialize](Deserialize.md) 当前只识别: - `materialPaths=` - `materialRefs=` -- 历史兼容键 `materials=` +- `castShadows` +- `receiveShadows` +- `renderLayer` -恢复优先级大致是: +恢复顺序是: -1. 如果 `materialRef` 有效,优先尝试按 `AssetRef` 恢复。 -2. 如果当前是 deferred scene load,尽量先把 `AssetRef` 解析回路径,但不立即同步兑现材质。 -3. 如果没有可用 `AssetRef`,再按路径恢复。 +1. 某槽位若有有效 `materialRef`,优先按 `AssetRef` 恢复 +2. 若处于 deferred scene load,先尝试 `TryResolveAssetPath(materialRef, path)` 恢复 `Assets/...` +3. 若不在 deferred 模式,则直接按 `AssetRef` 加载材质 +4. 若没有有效 `AssetRef`,只接受带 virtual scheme 的 `materialPaths` +5. 没有 `AssetRef` 的普通项目路径会被清空 -### 4. 首次访问时的异步兑现 +也就是说,下面这种旧式数据: -[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 虽然是 `const`,但当前实现会通过 `const_cast` 内部触发: +```text +materialPaths=Assets/runtime.material;materialRefs=; +``` -1. `EnsureDeferredAsyncMaterialLoadStarted(index)` -2. `ResolvePendingMaterials()` - -也就是说,这两个访问器不只是“读缓存”,还会推动路径恢复后的材质兑现流程向前走。 - -从行为上看,它们的主用途是支持 deferred scene load,但按当前源码,只要某个槽位“有路径、还没有 material、也还没发起过请求”,首次访问就可能触发一次异步加载尝试。 +当前不会再被当成正式项目材质绑定恢复。 ## 序列化语义 -当前序列化会输出五段键值: +当前 [Serialize](Serialize.md) 会写出: ```text -materialPaths=; -materialRefs=; +materialPaths=; +materialRefs=; castShadows=1; receiveShadows=1; renderLayer=0; ``` -其中最关键的设计点是: +但其中最关键的规则是: -- `materialRefs` 是主身份信息,面向项目资产恢复。 -- `materialPaths` 更像回退字段,只在该槽位没有有效 `AssetRef` 时才真正写出路径。 +- 对有有效 `AssetRef` 的项目材质槽,`materialPaths` 会被清空 +- 对没有 `AssetRef` 且是 virtual scheme 的槽位,`materialPaths` 才保留真实路径 -例如一个项目资产材质槽,当前文本很可能是: +因此: -```text -materialPaths=; -materialRefs=<有效 guid,localId,type>; -``` +- 项目材质的主协议是 `materialRefs` +- `builtin://materials/default-primitive` 这类虚拟路径才长期留在 `materialPaths` -这不是数据丢失,而是说明当前序列化更信任 `AssetRef` 作为稳定身份。 +## 与 deferred scene load 的关系 + +[GetMaterial](GetMaterial.md) 和 [GetMaterialHandle](GetMaterialHandle.md) 当前都带副作用。 + +它们会内部触发: + +1. `EnsureDeferredAsyncMaterialLoadStarted(index)` +2. `ResolvePendingMaterials()` + +所以在 deferred 场景里,反序列化后常见状态是: + +- `m_materialRefs[index]` 已恢复 +- `m_materialPaths[index]` 可能已恢复 +- `m_materials[index]` 仍然为空 + +直到后续第一次访问材质句柄时,异步兑现才真正开始或收口。 ## 与渲染链路的关系 -当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 直接挂进可见渲染对象,再由后续材质提取逻辑读取材质槽。 +当前 `RenderSceneUtility` 会把 `MeshRendererComponent*` 挂进可见项,再由后续材质提取逻辑读取具体槽位材质。 -这意味着: +因此当前对 renderer 的真实影响是: -- `MeshRendererComponent` 是否“有槽位”不重要。 -- 重要的是在真正提取材质时,对应槽位能否拿到有效 `Material`。 -- 在 deferred / async 场景里,对象可能已经有 `MeshFilterComponent + MeshRendererComponent`,但某个时刻材质仍为空,直到异步结果被 `ResolvePendingMaterials()` 收口。 +- 组件是否存在并不等于材质已经可用 +- deferred load 下,材质槽元数据可能先恢复,材质句柄后到 -## 阴影与渲染层的现实状态 +另外要继续明确一个实现边界: -当前组件公开了: +- `castShadows`、`receiveShadows` 和 `renderLayer` 当前已可保存、序列化、脚本读写 +- 但还不能简单等同于“这些标志已经完整进入当前 renderer 主路径” -- `GetCastShadows()` / `SetCastShadows()` -- `GetReceiveShadows()` / `SetReceiveShadows()` -- `GetRenderLayer()` / `SetRenderLayer()` +## 测试与锚点 -但按当前源码检索,这几个字段目前主要被: +- `tests/Components/test_mesh_render_components.cpp` + - 覆盖 builtin virtual path 的序列化 / 反序列化 + - 覆盖“没有 `AssetRef` 的普通项目路径不会作为正式协议保留” + - 覆盖项目材质按 `AssetRef` 序列化与恢复 + - 覆盖 deferred async material load +- `tests/Scene/test_scene.cpp` + - 覆盖 builtin material path 的场景保存 / 加载 -- 组件测试、场景序列化测试 -- Mono scripting internal call +## 当前实现边界 -消费;还没有看到它们被当前已取证的渲染提取路径真正读取。也就是说: - -- 它们当前可以保存。 -- 可以被序列化。 -- 也可以被脚本层读写。 -- 但不能简单等同于“阴影层级和 render layer 已完整进入 renderer 主路径”。 - -## 测试与真实使用点 - -- `tests/Components/test_mesh_render_components.cpp` 覆盖了槽位扩容、双轨序列化、历史键兼容、项目材质 `AssetRef` 恢复,以及 deferred async material load。 -- `tests/Scene/test_scene.cpp` 覆盖了场景序列化后对 `castShadows` / `receiveShadows` / `renderLayer` 的恢复。 -- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` 暴露了阴影和 render layer 的脚本读写入口。 - -## 线程与访问语义 - -- 当前实现没有内部加锁。 -- 路径写入、序列化和槽位编辑默认按主线程使用。 -- `GetMaterial()` / `GetMaterialHandle()` 是带副作用的读访问器;如果调用方只想查看元数据而不触发兑现,应优先使用 [GetMaterialPath](GetMaterialPath.md)、[GetMaterialPaths](GetMaterialPaths.md) 或 [GetMaterialAssetRefs](GetMaterialAssetRefs.md)。 - -## 当前实现限制 - -- 当前只维护“平铺材质槽数组”,不处理 submesh 级独立绑定策略、材质实例化策略或 streaming 策略。 -- `GetMaterial()` / `GetMaterialHandle()` 可能在首次访问时触发异步加载,因此它们不是纯只读 getter。 -- `materialPaths` 在序列化文本里只作为 fallback 字段输出,不应把“文本里 path 为空”误解成“组件内存里没有路径缓存”。 -- `castShadows`、`receiveShadows` 和 `renderLayer` 还没有完整接入当前已取证的 renderer 主路径。 +- 当前只维护平铺材质槽数组,不处理更复杂的实例化或 streaming 策略 +- 项目材质的正式序列化协议已经收口到 `materialRefs` +- 没有 `AssetRef` 的普通项目路径在反序列化时当前会被清掉;只有 virtual scheme 仍可单独依赖路径 +- `GetMaterial()` / `GetMaterialHandle()` 是带副作用的读访问器;只读元数据时应优先使用 [GetMaterialPath](GetMaterialPath.md)、[GetMaterialPaths](GetMaterialPaths.md) 和 [GetMaterialAssetRefs](GetMaterialAssetRefs.md) ## 相关方法 diff --git a/docs/api/XCEngine/Components/MeshRendererComponent/Serialize.md b/docs/api/XCEngine/Components/MeshRendererComponent/Serialize.md index 74cd1bb2..2b6dbbbe 100644 --- a/docs/api/XCEngine/Components/MeshRendererComponent/Serialize.md +++ b/docs/api/XCEngine/Components/MeshRendererComponent/Serialize.md @@ -6,33 +6,71 @@ void Serialize(std::ostream& os) const override; ``` -## 行为说明 +## 当前实现行为 -当前实现不再输出旧的 `materials=` 键,而是并行写出材质路径 fallback 和稳定资产引用: +当前实现会先准备两份待写出的槽位数组: + +- `serializedRefs` +- `serializedPaths` + +对每个槽位,处理规则是: + +1. 若当前 `AssetRef` 无效、`materialPath` 非空、且路径不是 virtual scheme,则再尝试一次 `TryGetAssetRef(...)` +2. 若该槽位最终有有效 `AssetRef`,或路径本来就不是 virtual scheme,则把 `serializedPaths[i]` 清空 + +然后按槽位顺序输出: ```text -materialPaths=|builtin://materials/default; -materialRefs=|; +materialPaths=; +materialRefs=; castShadows=1; -receiveShadows=0; -renderLayer=3; +receiveShadows=1; +renderLayer=0; ``` -其中: +## 当前序列化结果 -- `materialPaths` 和 `materialRefs` 都按材质槽顺序用 `|` 分隔。 -- 对于能稳定映射到项目资产的槽位,当前会优先写 `materialRefs`,并把同槽位 `materialPaths` 留空。 -- 对于没有有效 `AssetRef` 的槽位,例如 `builtin://` 这类虚拟路径,才会把路径写进 `materialPaths`。 -- 会保留尾部空槽位,避免编辑器材质槽数量在反序列化后缩水。 +### 项目材质槽 -## 当前实现含义 +如果某槽位绑定的是项目材质 `Assets/runtime.material`,且能解析出有效 `AssetRef`,常见输出会像: -- 场景文本现在同时保存“人类可读路径 fallback”和“项目资产稳定身份”。 -- 项目资产改名或移动后,更应依赖 `materialRefs` 恢复绑定,而不是依赖旧路径字符串。 -- 旧版依赖 `materials=` 的读取逻辑仍由 [Deserialize](Deserialize.md) 保留兼容。 +```text +materialPaths=; +materialRefs=; +``` + +也就是说: + +- 不会再把普通项目路径当正式场景数据保留下来 + +### builtin / virtual path 槽 + +如果某槽位绑定的是 `builtin://materials/default-primitive`,而它没有项目资产 `AssetRef`,则常见输出会像: + +```text +materialPaths=builtin://materials/default-primitive; +materialRefs=; +``` + +## 关键语义 + +- 当前不再输出旧的 `materials=` 键 +- 项目资产材质的正式场景身份是 `materialRefs` +- `materialPaths` 现在主要承担 virtual path 回退职责 +- 会保留尾部空槽位,避免反序列化后材质槽数量缩水 + +## 设计说明 + +这种设计和 `MeshFilterComponent` 的协议方向一致: + +- 项目资源走稳定 `AssetRef` +- builtin / 虚拟资源走显式协议路径 + +这样既能提高资源改名、移动后的恢复成功率,也不会强迫 virtual 资源进入项目资产数据库。 ## 相关文档 - [返回类型总览](MeshRendererComponent.md) -- [Deserialize](Deserialize.md) - [GetMaterialAssetRefs](GetMaterialAssetRefs.md) +- [GetMaterialPaths](GetMaterialPaths.md) +- [Deserialize](Deserialize.md) diff --git a/docs/api/XCEngine/Components/MeshRendererComponent/SetMaterialPath.md b/docs/api/XCEngine/Components/MeshRendererComponent/SetMaterialPath.md index c96641fc..70a387fc 100644 --- a/docs/api/XCEngine/Components/MeshRendererComponent/SetMaterialPath.md +++ b/docs/api/XCEngine/Components/MeshRendererComponent/SetMaterialPath.md @@ -21,14 +21,15 @@ void SetMaterialPath(size_t index, const std::string& materialPath); - 这是一个同步设置接口,不是“只记录路径、等以后再加载”的接口。 - 虚拟路径或内置路径也允许写入,但通常拿不到有效 `AssetRef`。 - 如果后续重新设置同一槽位,旧的异步状态会被显式丢弃。 +- 对项目材质来说,这个 API 写入的是运行时路径缓存;真正序列化时只要能得到有效 `AssetRef`,普通项目路径就不会作为正式场景数据长期保留。 ## 使用建议 这比直接设置 handle 更适合: - 编辑器材质面板 -- 场景文本反序列化后的路径恢复 -- 希望保留稳定文本路径的工具链 +- 运行时或工具侧按路径即时指定材质 +- virtual path 资源输入 如果调用方已经手里拿到了一个确定可用的材质句柄,并且希望以运行时对象为主写入,则可以考虑 [SetMaterial](SetMaterial.md)。