UE5.7:StorageServerClient 接管机制导致的路径偏移与文件存在性误判
前言
在虚幻引擎项目中使用 Zen Store 进行数据存储与流送时,底层文件系统的行为模式会发生显著变化。当项目目录下存在特定标记文件时,引擎会激活 StorageServerClient 机制,重组Platform File Wrapper Chain。这种接管行为不仅会改变运行时的文件读写路径落点,还会影响文件存在性的判定逻辑。本文通过分析源码,记录该机制对 SandboxFile 挂载的影响以及由此引发的文件判定不一致问题。
问题和解决方案
问题场景
在构建流水线的时候,在尝试加入AS jit环节的时候,本地测试时发现AS_JITTED_CODE文件夹偶现没有产生在Cooked目录里。但是没有引起足够的重视,初步认为是BuildGraph写的有问题,也怀疑过是和Zen Storage有关系,为了快速先把pipeline搭建起来,选择了将ue.projectstore文件先转移之后进行jit代码生成和重编译。当进展到这一步之后就没有复现AS_JITTED_CODE文件夹生成到非预期位置的情况,随后又发现PrecompiledScript.Cache从打包机下载到本地执行的时候warning不匹配的问题,调试后惊人的发现在本地磁盘中删除了且确认完全不存在的情况下,判断PrecompiledScript.Cache文件存在性的逻辑依然是True,最终确认问题所在,
路径偏移问题
在启用 Zen Store 的环境下,原本预期生成在 Saved/Cooked/[Platform]/Sandbox 目录下的缓存文件(如 AngelScript 预编译代码或 JIT 缓存)会直接出现在项目根目录。
其根本原因在于 StorageServerClient 的优先级高于 SandboxFile。当引擎检测到 ue.projectstore 文件后,会直接将 StorageServerClient 作为网络平台文件挂载,从而跳过了 SandboxFile 的创建流程。由于缺失了 Sandbox 的路径重定向映射,所有使用 FPaths::ProjectDir() 或 FPaths::RootDir() 等接口生成的路径均回归至物理磁盘真实路径,造成偏移现象。
解决方案:
- 启动项目时添加命令行参数
-SkipZenStore强制禁用接管逻辑,恢复 Sandbox 挂载。 - 将受影响的缓存生成路径移出 Cooked 目录体系,改用
Intermediate等不被 Zen 扫描的目录。
文件存在性误判
手动删除磁盘上的缓存文件后,调用 IPlatformFile::FileExists 依然返回 true,导致逻辑错误。
这是由于 Zen 模式下的文件存在性判定优先查询服务器端的 TOC 指令。在 Cook 阶段,Cooked/[Platform] 下的非排除文件会被写入 zenfs.manifest 清单。运行时,FStorageServerPlatformFile 会根据该清单填充本地 ServerToc。只要清单中存在记录,即使物理文件已被删除,底层判定仍会维持原有的存在性状态。
解决方案:
- 在 Cook 阶段排除特定的缓存扩展名,修改
ZenFileSystemManifest.cpp中的过滤规则。 - 确保在执行 Cook 任务前清理 Cooked 目录,避免残留的旧缓存被编入清单。
相关源代码
ue.projectstore 的查找与触发点
文件:F:\UE_Release\Engine\Source\Runtime\StorageServerClient\Private\StorageServerPlatformFile.cpp
关键函数:FStorageServerPlatformFile::TryFindProjectStoreMarkerFile
1 | |
含义:当 cooked 目录下(或 root/相对路径)存在 ue.projectstore,后续 ShouldBeUsed() 就可能启用 StorageServerClient。
挂载冲突
文件:F:\UE_Release\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp
LaunchCheckForFileOverride 中明确说明:
1 | |
并且代码体现:
1 | |
含义:
- 一旦
StorageServerClient成为NetworkPlatformFile,SandboxFile不会被创建。 - 这会导致写入目录 不再被 sandbox 重定向。
Sandbox 的路径还原示例:
1 | |
Sandbox 是通过 wrapper 做路径映射;它 不会改变 FPaths::RootDir(),但会改变实际读写路径的落点。
Zen 文件清单生成逻辑
文件: F:\UE_Release\Engine\Source\Developer\IoStoreUtilities\Private\ZenFileSystemManifest.cpp
关键函数
FZenFileSystemManifest::Generate 里会扫描 Cooked 输出目录并生成清单:
1 | |
Cooked/<Platform> 下的所有文件(除了明确排除的扩展)都会进入 Zen 文件清单。
运行时 FileExists 优先查 Zen 端 TOC
文件: F:\UE_Release\Engine\Source\Runtime\StorageServerClient\Private\StorageServerPlatformFile.cpp
关键函数
FStorageServerPlatformFile::FileExists:
1 | |
只要 ServerToc(Zen 文件列表)里有记录,就直接返回 true。ServerToc 的内容来自 Zen 服务器上的文件清单,与本地磁盘是否存在无关。
TOC来自 Zen 服务器
文件: F:\UE_Release\Engine\Source\Runtime\StorageServerClient\Private\StorageServerPlatformFile.cpp
SendGetFileListMessage() 会向 Zen 请求文件清单并填充 TOC:
1 | |
运行时的 ServerToc 由 Zen 端清单驱动。
-SkipZenStore 参数与启用条件
文件:F:\UE_Release\Engine\Source\Runtime\StorageServerClient\Private\StorageServerPlatformFile.cpp
关键位置在 ShouldBeUsed 的开头:
1 | |
启动参数 -SkipZenStore 会直接让 StorageServerClient 的 ShouldBeUsed() 返回 false。这意味着 即使 ue.projectstore 存在,也不会接管平台文件链,平台文件链会继续走本地文件系统(Pak/CachedRead/Sandbox 等照常挂载)。
同一函数后续会尝试读取 ue.projectstore:
1 | |
总结和建议
- ZenFileSystem会扫描 Cooked 输出目录并生成清单
ue.projectstore存在会触发StorageServerClient的启用ue.projectstore的存在性检查的范围比预计要广,甚至会检查../../../- 可以通过
-SkipZenStore避免pfc被接管
当然,如果一开始没有选择将workspace改为incremental就不会遇到这个问题(当然也有别的代价)。pipeline还是应该做好清理工作……
