SDL3_API分类参考_存储(CategoryStorage)

2026-3-6 / 0 评论 / 6 阅读

存储子系统(CategoryStorage)

SDL 的存储 API 是一套高层级接口,旨在屏蔽底层文件操作的跨平台兼容性问题(在 SDL 架构中,该模块基于 FilesystemIOStream 子系统实现)。与传统文件系统 API 相比,它的使用限制更多,主要原因如下:

  1. 访问对象限制:传统文件系统 API 通常假设所有存储是统一的整体,但许多平台(尤其是游戏主机)对存储类型的访问有严格区分。例如,游戏资源文件和用户数据往往存储在两个独立的存储设备中,具备完全不同的特性(甚至底层 API 都不同)。

  2. 访问方式限制:应用程序常错误地认为所有存储都可写入——但多数平台将游戏资源和用户数据分离,仅用户数据区可写入,游戏资源区为只读。

  3. 访问时机限制:文件系统访问最常见的兼容性问题是「时机」——不能假定存储设备始终可用,也不能假定对某设备的访问时长无限制。

反面示例(传统文件操作)

传统文件操作代码因上述假设,存在严重的跨平台兼容性问题:

void ReadGameData(void)
{
    extern char** fileNames;
    extern size_t numFiles;
    for (size_t i = 0; i < numFiles; i += 1) {
        FILE *data = fopen(fileNames[i], "rwb");
        if (data == NULL) { /* 错误处理 */ }
        else { /* 读取逻辑 */ fclose(data); }
    }
}

void ReadSave(void)
{
    FILE *save = fopen("saves/save0.sav", "rb");
    if (save == NULL) { /* 错误处理 */ }
    else { /* 读取逻辑 */ fclose(save); }
}

void WriteSave(void)
{
    FILE *save = fopen("saves/save0.sav", "wb");
    if (save == NULL) { /* 错误处理 */ }
    else { /* 写入逻辑 */ fclose(save); }
}

该代码的问题:

  • 访问对象:假设游戏资源和存档都在当前工作目录(可能并非游戏安装目录);
  • 访问方式:假设资源路径可写入,且存档与资源同目录也可写入;
  • 访问时机:假设任何时候都能调用文件操作,存储设备始终可用。

这些问题会导致:

  • 游戏安装在只读设备时,资源加载和存档操作均会失败/崩溃;
  • 部分平台需显式挂载存储设备,未挂载时无法找到任何文件;
  • I/O 未刷新/验证,程序异常可能导致存档丢失/损坏。

正面示例(SDL_Storage 实现)

使用 SDL_Storage 可避免上述问题:

void ReadGameData(void)
{
    extern char** fileNames;
    extern size_t numFiles;

    SDL_Storage *title = SDL_OpenTitleStorage(NULL, 0);
    if (title == NULL) { /* 错误处理 */ }
    while (!SDL_StorageReady(title)) { SDL_Delay(1); }

    for (size_t i = 0; i < numFiles; i += 1) {
        void* dst;
        Uint64 dstLen = 0;

        if (SDL_GetStorageFileSize(title, fileNames[i], &dstLen) && dstLen > 0) {
            dst = SDL_malloc(dstLen);
            if (SDL_ReadStorageFile(title, fileNames[i], dst, dstLen)) {
                /* 读取逻辑 */
            } else { /* 错误处理 */ }
            SDL_free(dst);
        } else { /* 错误处理 */ }
    }

    SDL_CloseStorage(title);
}

// 读取/写入存档的实现类似,使用 SDL_OpenUserStorage 访问用户存储区

SDL_Storage 的改进点:

  1. 明确访问对象:根据场景显式访问游戏资源存储(TitleStorage)或用户存储(UserStorage);
  2. 明确访问方式:根据场景使用读/写函数,区分只读/可写存储区;
  3. 明确访问时机:使用时打开存储设备,用完关闭,且通过 SDL_StorageReady 检查设备可用性。

路径规则说明

存储 API 对路径有严格规范,确保跨平台兼容性:

  • 所有路径使用 Unix 风格分隔符(/),不支持其他分隔符(如 \);
  • 禁止使用相对目录(...);
  • 文件名支持合法 UTF-8 字符串(排除 NULL 终止符和 /),但底层实现可能限制特殊字符。

函数

  • SDL_CloseStorage:关闭已打开的存储设备(释放资源,刷新未完成的 I/O 操作)
  • SDL_CopyStorageFile:在存储设备内复制文件(支持跨目录复制,保留文件属性)
  • SDL_CreateStorageDirectory:在存储设备中创建目录(仅创建单层,失败返回具体错误)
  • SDL_EnumerateStorageDirectory:枚举存储设备中指定目录的所有项(文件/子目录),通过回调处理
  • SDL_GetStorageFileSize:获取存储设备中指定文件的大小(字节),目录返回 0
  • SDL_GetStoragePathInfo:获取存储设备中指定路径的详细信息(类型、大小、修改时间等)
  • SDL_GetStorageSpaceRemaining:获取存储设备的剩余可用空间(字节),用于检查存档写入空间
  • SDL_GlobStorageDirectory:按通配符(*、? 等)枚举存储设备中指定目录的文件
  • SDL_OpenFileStorage:打开基于本地文件系统的存储设备(适配传统文件路径)
  • SDL_OpenStorage:通用存储设备打开函数(自定义存储接口时使用)
  • SDL_OpenTitleStorage:打开游戏资源存储设备(只读,用于加载游戏资源)
  • SDL_OpenUserStorage:打开用户数据存储设备(可写,用于保存存档/配置,需传入厂商/应用名)
  • SDL_ReadStorageFile:从存储设备读取文件内容到内存(原子操作,确保数据完整性)
  • SDL_RemoveStoragePath:删除存储设备中的文件/空目录(非空目录删除失败)
  • SDL_RenameStoragePath:重命名/移动存储设备中的文件/目录(目标路径已存在则失败)
  • SDL_StorageReady:检查存储设备是否就绪(返回非0表示可用,需循环等待直到就绪)
  • SDL_WriteStorageFile:将内存数据写入存储设备的文件(原子操作,自动刷新确保数据持久化)

数据类型

  • SDL_Storage:存储设备句柄类型(标识已打开的存储设备,所有操作均基于此句柄)

结构体

  • SDL_StorageInterface:存储接口结构体(自定义存储实现时使用,包含各类操作的函数指针)

枚举

  • (无)

  • (无)

FreeBASIC 示例代码

' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"

' 补充类型/函数声明(FreeBASIC 绑定可能缺失)
Type SDL_Storage As Any Ptr ' 存储设备句柄

Declare Function SDL_OpenTitleStorage CDecl (ByVal path As ZString Ptr, ByVal flags As Integer) As SDL_Storage
Declare Function SDL_OpenUserStorage CDecl (ByVal org As ZString Ptr, ByVal app As ZString Ptr, ByVal flags As Integer) As SDL_Storage
Declare Function SDL_StorageReady CDecl (ByVal storage As SDL_Storage) As Integer
Declare Function SDL_GetStorageFileSize CDecl (ByVal storage As SDL_Storage, ByVal path As ZString Ptr, ByVal size As ULLong Ptr) As Integer
Declare Function SDL_ReadStorageFile CDecl (ByVal storage As SDL_Storage, ByVal path As ZString Ptr, ByVal buffer As Any Ptr, ByVal size As ULLong) As Integer
Declare Function SDL_WriteStorageFile CDecl (ByVal storage As SDL_Storage, ByVal path As ZString Ptr, ByVal buffer As Any Ptr, ByVal size As ULLong) As Integer
Declare Function SDL_CreateStorageDirectory CDecl (ByVal storage As SDL_Storage, ByVal path As ZString Ptr) As Integer
Declare Function SDL_GetStorageSpaceRemaining CDecl (ByVal storage As SDL_Storage, ByVal remaining As ULLong Ptr) As Integer
Declare Sub SDL_CloseStorage CDecl (ByVal storage As SDL_Storage)
Declare Function SDL_RemoveStoragePath CDecl (ByVal storage As SDL_Storage, ByVal path As ZString Ptr) As Integer

' 读取游戏资源文件(只读,TitleStorage)
Sub ReadGameResource(ByVal resourceName As ZString Ptr)
    ' 打开游戏资源存储设备
    Dim As SDL_Storage titleStorage = SDL_OpenTitleStorage(NULL, 0)
    If (titleStorage = NULL) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "打开游戏资源存储失败:%s", SDL_GetError())
        Exit Sub
    End If

    ' 等待存储设备就绪
    While (SDL_StorageReady(titleStorage) = 0)
        SDL_Delay(1)
    Wend

    ' 获取文件大小
    Dim As ULLong fileSize = 0
    If (SDL_GetStorageFileSize(titleStorage, resourceName, @fileSize) = 0) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "获取资源文件大小失败:%s", SDL_GetError())
        SDL_CloseStorage(titleStorage)
        Exit Sub
    End If

    If (fileSize = 0) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "资源文件为空:%s", *resourceName)
        SDL_CloseStorage(titleStorage)
        Exit Sub
    End If

    ' 分配内存并读取文件
    Dim As Any Ptr buffer = SDL_malloc(fileSize)
    If (buffer = NULL) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "内存分配失败")
        SDL_CloseStorage(titleStorage)
        Exit Sub
    End If

    If (SDL_ReadStorageFile(titleStorage, resourceName, buffer, fileSize) = 0) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "读取资源文件失败:%s", SDL_GetError())
    Else
        SDL_LogInfo(SDL_LOG_CATEGORY_STORAGE, "成功读取资源文件:%s(大小:%llu 字节)", *resourceName, fileSize)
        ' 此处可处理读取到的资源数据
    End If

    ' 释放资源
    SDL_free(buffer)
    SDL_CloseStorage(titleStorage)
End Sub

' 保存用户存档(可写,UserStorage)
Sub SaveUserData(ByVal saveName As ZString Ptr, ByVal data As ZString Ptr)
    ' 打开用户数据存储设备(厂商名/应用名)
    Dim As SDL_Storage userStorage = SDL_OpenUserStorage("SDL_Example", "StorageDemo", 0)
    If (userStorage = NULL) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "打开用户存储失败:%s", SDL_GetError())
        Exit Sub
    End If

    ' 等待存储设备就绪
    While (SDL_StorageReady(userStorage) = 0)
        SDL_Delay(1)
    Wend

    ' 检查剩余空间
    Dim As ULLong remainingSpace = 0
    If (SDL_GetStorageSpaceRemaining(userStorage, @remainingSpace) = 0) Then
        SDL_LogWarn(SDL_LOG_CATEGORY_STORAGE, "无法获取剩余空间,继续操作...")
    Else
        Dim As ULLong dataSize = Len(*data)
        If (remainingSpace < dataSize) Then
            SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "存储空间不足(需要:%llu 字节,剩余:%llu 字节)", dataSize, remainingSpace)
            SDL_CloseStorage(userStorage)
            Exit Sub
        End If
    End If

    ' 创建存档目录(如需)
    SDL_CreateStorageDirectory(userStorage, "saves")

    ' 拼接存档路径(必须使用 / 分隔符)
    Dim As String savePath = "saves/" & *saveName
    Dim As ULLong dataSize = Len(*data)

    ' 写入存档数据
    If (SDL_WriteStorageFile(userStorage, StrPtr(savePath), data, dataSize) = 0) Then
        SDL_LogError(SDL_LOG_CATEGORY_STORAGE, "写入存档失败:%s", SDL_GetError())
    Else
        SDL_LogInfo(SDL_LOG_CATEGORY_STORAGE, "成功写入存档:%s(大小:%llu 字节)", savePath, dataSize)
    End If

    ' 关闭存储设备
    SDL_CloseStorage(userStorage)
End Sub

' 主程序
Sub Main()
    ' 初始化 SDL
    If (SDL_Init(0) < 0) Then
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
        Exit Sub
    End If

    ' ========== 1. 读取游戏资源 ==========
    SDL_LogInfo(SDL_LOG_CATEGORY_STORAGE, "【1. 读取游戏资源】")
    ReadGameResource(StrPtr("assets/config.txt")) ' 假设存在该资源文件

    ' ========== 2. 保存用户存档 ==========
    SDL_LogInfo(SDL_LOG_CATEGORY_STORAGE, vbCrLf & "【2. 保存用户存档】")
    Dim As ZString * 100 saveData = "玩家存档数据:等级=10,金币=9999,进度=50%"
    SaveUserData(StrPtr("save01.sav"), @saveData)

    ' 清理 SDL
    SDL_Quit()

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "程序正常退出")
End Sub

' 运行主程序
Main()

核心知识点补充

  1. 存储设备类型区分

    • TitleStorage:游戏资源存储区,只读,用于加载游戏内置资源(如贴图、音效、配置);
    • UserStorage:用户数据存储区,可写,用于保存存档、用户配置等,需传入厂商名/应用名以区分不同应用;
    • 两者完全隔离,避免误写游戏资源导致的兼容性问题。
  2. 设备就绪检查

    • 必须通过 SDL_StorageReady 循环等待存储设备就绪,尤其是游戏主机/移动平台,存储设备可能需要挂载时间;
    • 就绪检查通过后,才能执行文件操作,避免「设备未就绪」导致的操作失败。
  3. 路径规范强制要求

    • 仅支持 / 分隔符,不支持 \(即使 Windows 平台);
    • 禁止 ./.. 相对路径,所有路径必须是存储设备内的绝对路径;
    • 示例:saves/save01.sav 合法,./saves/save01.sav 非法。
  4. 原子操作保障

    • SDL_ReadStorageFile/SDL_WriteStorageFile 是原子操作,确保数据读写的完整性;
    • 写入操作会自动刷新缓存,避免程序异常导致的数据丢失(传统 fopen/fwrite 需手动 fflush)。

总结

  1. 核心优势

    • 严格区分只读/可写存储区,避免跨平台写入权限问题;
    • 强制检查存储设备就绪状态,适配需挂载的平台(如游戏主机);
    • 原子化读写操作,保障数据完整性,避免存档损坏;
    • 统一路径规范,彻底解决跨平台路径分隔符/相对路径问题。
  2. 使用建议

    • 游戏资源加载使用 SDL_OpenTitleStorage,存档/配置保存使用 SDL_OpenUserStorage
    • 所有存储操作前必须调用 SDL_StorageReady 等待设备就绪;
    • 写入文件前检查剩余空间(SDL_GetStorageSpaceRemaining),避免空间不足导致失败;
    • 存储设备使用完毕后必须调用 SDL_CloseStorage,释放资源并刷新缓存。
  3. 关键接口

    • 存储设备管理:SDL_OpenTitleStorage()/SDL_OpenUserStorage()/SDL_StorageReady()/SDL_CloseStorage()
    • 文件操作:SDL_GetStorageFileSize()/SDL_ReadStorageFile()/SDL_WriteStorageFile()
    • 辅助操作:SDL_CreateStorageDirectory()/SDL_GetStorageSpaceRemaining()/SDL_RemoveStoragePath()

评论一下?

OωO
取消