UE4 文件系统

类图

类图
类图

IPlatformFile

文件类的最基础接口,定义了对文件进行操作相关的方法,从这个接口派生的类又分为两大类:

  • 物理文件类
    • 这个就是我们通常理解的文件类,就是各个平台下直接对文件进行操作的类
  • 包装(Wrapper)文件类
    • 这个类型的文件类不会直接对文件进行操作,它对文件的操作是通过自己持有的底层对象进行的,最终各个类型的包装文件类会构成一个文件操作链,这个链上的最后一个对象就是物理文件类对象
    • 这个类型的子类实现中都会有个 IPlatformFile* 类型的成员变量,这个其实就是类似我们链表数据结构中的 Next 指针对象,指向链表的下一个对象。不过不明白这里为什么不抽象一个类似 IPhysicalPlatformFile 的接口,把这个成员变量放到这个基类接口里,省得每个子类里都得写一遍

这个类需要注意一下这些接口:

  • Initialize: 初始化接口,对于包装文件类来说,第一个参数是他所指向的下一个文件类对象。对于物理文件类,第一个参数只能是空的; 第二个参数是命令行,部分文件类会从这里去解析一些参数
  • GetPlatformPhysical: 这个接口直接返回当前平台对应的物理文件类对象,这个函数的实现在各个平台的物理文件类封装中。一般我们通过 FPlatformFileManager::GetPlatformFile 返回的是文件操作链的链头。
  • ShouldBeUsed: 每个包装文件类是否生效的逻辑在这里实现

物理文件类: IPhysicalPlatformFile

这个类是物理文件类的基础接口,主要对 SetLowerLevel 这个接口进行屏蔽,因为物理文件类对象肯定是文件操作链上的最后一个对象,所以这个接口是不让用的

FLinuxPlatformFile

Linux 平台的文件操作进行封装

FWindowsPlatformFile

Windows 平台的文件操作进行封装

FHTML5PlatformFile

HTML5 平台的文件操作进行封装

IAndroidPlatformFile/FAndroidPlatformFile

  • IAndroidPlatformFile: 这个类也是个接口类,主要是扩展了几个 Android 平台上的接口
  • FAndroidPlatformFile: 继承自 IAndroidPlatformFile,对 Android 平台的文件操作进行封装

FApplePlatformFile/FIOSPlatformFile

  • FApplePlatformFile: 对 OSX 平台的文件操作进行封装
  • FIOSPlatformFile: 继承自 FApplePlatformFile,对 iOS 平台的文件操作进行封装

包装文件类

入口函数 ConditionallyCreateFileWrapper

FCachedReadPlatformFile

这个包装文件类实现了预读的优化,具体文件的预读逻辑在文件读写类 FCachedFileHandle 中,简单说就是假如读一个文件,读100字节,经过这个类的包装,其实是会读最多64KB的文件内容到内存中,下次再读这个文件时其实就是内存拷贝

FLoggedPlatformFile

这个类会将每一次文件操作的耗时打印到 log 中,并且输入命令 LogFileDump 会将当前打开的文件列表打印到 log

FPlatformFileOpenLog

  • 记录游戏或者编辑器的读文件历史,结果保存到 EditorOpenOrder.log 或者 GameOpenOrder.log
  • 不能用在 Shipping 版本中

FNetworkPlatformFile/FCookedIterativeNetworkFile/FStreamingNetworkPlatformFile

  • 这几个包装器会从服务器上下载文件,比如打开某个文件,这个文件如果不存在,则先会从文件服务器上把这个文件下载到本地,然后再打开
  • 优先级是 FStreamingNetworkPlatformFile > FCookedIterativeNetworkFile > FNetworkPlatformFile
  • 不能用在 Shipping 版本中

FSandboxPlatformFile

  • 如果启用了沙盒,那么操作的文件将会转到沙盒内的文件访问
  • 注意这个函数 OkForInnerAccess 里面有两个列表,一个是文件夹匹配列表,一个是文件匹配列表,不在这个列表中文件或者文件夹,在沙盒访问失败后,还会用原始路径访问一次

FPakPlatformFile

  • 这个包装类很重要, 因为这个就是虚幻中对 pak 文件的读取封装了,了解虚幻中怎么读取 pak 文件,可以详细阅读这个类的实现

  • IsNonPakFilenameAllowed,这个函数控制当从 pak 中访问文件失败时,是否继续让底层文件类直接访问目标路径

  • 猜想: 这个是不是可以用来做一件事,比如某些资源不打包到 pak 中,然后游戏开始的加载环节中,把这些资源动态地下载下来

  • 测试

    • 先按正常流程打包游戏,Content 目录如图

    content-paks.png

    • 然后从另一个游戏工程里复制了一个 cook 好的特效资源,然后放到 Content/Test 目录下,如图

    content-test.png

    • 然后在代码里直接加载测试资源
    IPlatformFile& StartNode = FPlatformFileManager::Get().GetPlatformFile();
    IPlatformFile* PlatformFile = &StartNode;
    IPlatformFile* PakPlatformFile = nullptr;
    
    do
    {
        // 查找 PakPlatformFile
        UE_LOG(LogTest, Warning, TEXT("Platform file name[%s]."), PlatformFile->GetName());
    
        if (FCString::Strcmp(PlatformFile->GetName(), FPakPlatformFile::GetTypeName()) == 0)
        {
            PakPlatformFile = PlatformFile;
        }
    
        PlatformFile = PlatformFile->GetLowerLevel();
    } while (PlatformFile);
    
    // 确保当前使用了 pak
    check(PakPlatformFile);
    
    // 测试是否能加载到独立的资源
    UParticleSystem* PS = LoadObject<UParticleSystem>(nullptr, TEXT("/Game/Test/PS_ForLoadTest.PS_ForLoadTest"));
    if (PS)
    {
        UE_LOG(LogTest, Warning, TEXT("Found PS_ForLoadTest"));
    }
    else
    {
        UE_LOG(LogTest, Warning, TEXT("Not Found PS_ForLoadTest"));
    }
    
    • 测试结果如下图:

    Result.png

FPlatformFileReadStats

  • 文件读取速度统计

FProfiledPlatformFile

  • 文件操作速度统计,类似 FLoggedPlatformFile