XGameSave 技术概述

用户可通过 XGameSave 将游戏数据保存到云端。 本主题将介绍在主机与云存储之间更新或同步保存的数据时在幕后会发生什么情况。

XGameSaveSubmitUpdateAsync 行为

若要使用 XGameSave 保存某些数据,然后将它存储在云端,你需要调用 XGameSaveSubmitUpdateAsync 方法来添加、删除或更新数据。 调用 XGameSaveSubmitUpdateAsync 时,XGameSaveSubmitBlobWriteXGameSaveSubmitBlobDelete 为调用提供的缓冲区会从应用分区快速复制到系统分区的专用内存空间中。 在内存成功复制到系统分区后,会返回 XGameSaveSubmitUpdateAsync 结果,指示应用可安全释放在本地为该数据分配的内存。

然后,系统会将 Blob 保存到主机的硬盘中,并通过在该容器上提交整个操作的最终容器更新来完成操作。

共享分区中最多有 16 MB 的内存用于接收 XGameSaveSubmitUpdateAsync 数据。 如果由于 16 MB 专用缓冲区中没有足够的可用内存,导致系统无法立即为 XGameSaveSubmitUpdateAsync 的调用提供服务,则该调用将排队等待服务。 系统会不断将数据从 16 MB 的缓冲区转移到硬盘中。 当 16 MB 的缓冲区中有可用空间时,已排队的空间按照请求的顺序获得服务。

将更新后的数据保存到主机的硬盘中后,该数据可供 XGameSave 系统选取,也可上传到云中。 若要完成此上传,系统将获取当前容器状态的快照,并将更改上传到云。 可在游戏期间定期上传,也可在游戏暂停或终止时上传。

云上传过程类似于将所更改的数据复制到主机的硬盘中。 上传到服务中的是单个 Blob。 由对容器文件的最终更新来提交更新操作,其中该文件引用了所有其他已上传的 Blob。 在上传到云的过程中,这种整合到一个最终更新的行为可确保 XGameSaveSubmitUpdateAsync 调用中引用的所有数据完整地提交,或者容器保持不变。 这样一来,即使系统在上传操作期间脱机或断电,用户也可转到其他 Xbox One 主机、从云端下载数据,然后通过所有容器的一致视图继续玩游戏。

重要

容器之间的数据依赖关系不安全。 单个 XGameSaveSubmitUpdateAsync 调用的结果被确保要不完全应用、要不完全不应用。

XGameSaveSubmitUpdateAsync 调用不得假定将来的 XGameSaveSubmitUpdateAsync 调用会完全成功来将容器保持在有效状态。 换言之,应用无法依赖多个 XGameSaveSubmitUpdateAsync 调用来将所有必需的数据保存到容器中。 每个 XGameSaveSubmitUpdateAsync 调用必须将指定容器的内容保持为有效状态,以便应用稍后读取。

为了说明此问题,请考虑这样一个场景:容器跟踪名叫 Bob 的人物持有的黄金数量和食物数量。 游戏可存储两个 Blob,分别名为“黄金”和“食物”。 如下图所示,Bob 的库存中一开始有 100 单位的黄金,没有食物。

图 1. Bob 一开始有 100 单位的黄金

示例图。Bob 有 100 单位的黄金。

他现在花了 50 单位。 游戏准备进行 XGameSaveSubmitUpdateAsync 调用,这会将黄金 Blob 的值更新为 50。

系统会将更新后的 Blob 和容器更新信息上传到更新缓冲区。 随后,系统将新 Blob 的值复制到硬盘中,如下图所示。

图 2. 系统捕获已更新的信息并将值复制到硬盘中

示例图。Bob 花掉了 50 单位的黄金。

系统会更新硬盘上的容器文件来引用新 Blob,如下图所示。 最终,系统会在垃圾回收操作中删除未引用的 Blob。

图 3. 系统更新硬盘上的容器文件并删除未引用的 Blob

示例图。在连接存储中覆盖 Bob 的新黄金值。

注意

每次 XGameSaveSubmitUpdateAsync 调用使用的 Blob 越多,完成文件系统操作的必要原子操作来可靠地存储数据所需的时间就越长。 上例中数据存储的细节太小,但可用于清楚地演示一个容器中多个 Blob 的原子更新行为。

更新多个 blob—错误方法

思考一下 Bob 想要买些食物的场景。 1 单位的黄金购买 1 单位的食物,Bob 想要买 25 单位的食物。

应用可发出一个 XGameSaveSubmitUpdateAsync 调用来添加 25 单位的食物,然后再调用一次,从 Bob_Inventory 容器中减去 25 单位的黄金。 即使两个 XGameSaveSubmitUpdateAsync 调用已完成的处理程序都被调用,也可能因断电等事件而产生错误结果,这可能会阻止数据写入到硬盘中或导致与云的同步不完整。

下图说明了系统采取的操作,以及在任一步骤中断电时发生的情况。

假定两个 XGameSaveSubmitUpdateAsync 调用中的数据都已在系统的更新缓冲区中,并且两个调用的游戏完成处理程序都已被调用。

首先,系统会将食物 Blob 的新值的数据写入硬盘,如下图所示。

图 4. 系统将食物 Blob 的值写入硬盘

示例图。系统将食物 blob 的值写入磁盘。

随后,系统会更新容器来引用新写入的值。 如下图所示,如果在该步骤完成后下个步骤之前断电,Bob 的食物将添加 25 单位,但库存中的黄金未扣减相应的单位。

图 5. 系统更新容器来引用新写入的值

示例图。系统更新容器以引用新写入的值。

接下来,系统会将黄金 Blob 的新值的数据写入硬盘,如下图所示。 Bob_Inventory 容器引用的黄金的值仍未更新。 Bob 比应持有的黄金数量多 25 单位,但我们离所需的结果更近了一步。

图 6. 系统将黄金 Blob 的新值的数据写入硬盘

示例图。系统将黄金 blob 的新值的数据写入硬盘。

最后,系统会更新容器文件来引用新写入的黄金 Blob - 这是预期结果,如下图所示。

图 7. 系统更新容器文件来引用新写入的黄金 Blob

示例图。系统更新容器文件来引用新写入的黄金 blob。

更新多个 blob—正确方法

若要确保 Bob 库存中黄金和食物的数量以原子方式更新,而且不可能因断电造成中间状态有误,正确的方式是在一个 XGameSaveSubmitUpdateAsync 调用中更新这两个 Blob。 系统随后会执行以下步骤。

首先,系统会将食物 Blob 的新值的数据写入硬盘,如下图所示。

图 8. 系统写入食物 Blob 的新值的数据

示例图。系统写入食物 blob 的新值的数据。

接下来,系统会将黄金 Blob 的新值的数据写入硬盘,如下图所示。

图 9. 系统写入黄金 Blob 的新值的数据

示例图。系统写入黄金 blob 的新值的数据。

最后,系统会更新容器文件来引用这两个新的 Blob,如下图所示。

图 10. 系统更新容器文件来引用这两个新的 Blob

示例图。系统更新容器文件以引用这两个新 blob。

虽然这个例子很简单,但它说明了在容器中进行各项数据修改的重要性,这必须通过我们需要的所有更新发出单个 XGameSaveSubmitUpdateAsync 调用以原子的方式实施。 在用黄金买食物的场景中,应用通过这样做避免了可能出现的争用情况,争用会导致只更新一个值(这是不恰当的),而使人物留有过多的黄金。

同步 XGameSave 存储空间

同步 XGameSave 存储空间时,XGameSave 服务将经历 4 个过程:

  • 检查连接
  • 获取锁定
  • 容器列出、比较和合并逻辑
  • 下载容器

当应用请求访问 XGameSave 存储空间时,系统会执行同步过程,使用户保存的数据在各 Xbox One 主机中状态一致,并使其数据可在脱机游戏时使用。 由于同步所耗时间可能不同,且可能需要用户做决策,因此系统可能会在该过程的不同阶段向用户显示 UI。

即使同步 UI 处于活动状态,用户也可随时按 Xbox 按钮离开应用。 离开后,系统会隐藏 UI,但会在没有用户交互的情况下尽可能地继续同步。

当用户回到应用后,UI 将重新显示,除非同步已完成。 当 UI 隐藏时,系统绝不会假定用户已做出选择。

当用户在主屏幕上时,系统不会显示同步 UI,因此应用在 XGameSaveInitializeProvider 调用即将完成时呈现具有适当上下文的视觉对象这一点非常重要。 继续呈现会向用户指出应用仍是交互式且正在等待数据加载。

下图概述了当应用请求 XGameSave 存储空间时系统采用的大致序列。 如果整个序列耗时超过几秒,则显示系统绘制的同步 UI。

图 11. 当应用请求 XGameSave 存储空间时系统采用的序列

当应用请求连接存储空间时系统采用的序列。

系统在处理 XGameSaveInitializeProvider 请求时会经历 4 个阶段。

  • 检查连接
  • 获取锁定
  • 容器列出、比较和合并逻辑
  • 下载容器

检查连接

开始处理 XGameSaveInitializeProvider 请求时,系统会先检查连接情况。 如果主机脱机,则跳过整个同步过程, 并在当前会话中将指定用户的 XGameSave 存储空间标记为脱机。

在应用下次访问该用户的 XGameSave 存储空间,且系统可访问游戏存储服务时,将与云存储核对已修改的所有数据。 这种情况下从不显示 UI。

获取锁定

验证连接后,系统会尝试获取对与你的应用和提供的用户关联的云存储空间的独占访问权。 该操作通过在游戏存储的 XGameSave 存储区域中放置锁定文件来完成。 如果主机连接、可访问服务且能够在很短的时间内获取锁定,则不显示 UI,且继续同步过程。

在系统已获取特定 XGameSave 存储空间的锁,并将 XGameSave 存储空间的实例返回给应用之后,应用对该 XGameSave 存储空间中的数据执行的任何 API 调用在 Web 请求成功时都不会受阻。 锁可提供足够的保护,即使用户要在你的应用获取 XGameSave 存储空间后拔掉系统的网线,也会根据本地可用的数据进行 API 调用。

在获取锁定的步骤期间,可能有几种错误情况。

正在同步 UI

如果主机联机,但未在短时间内从服务获取锁,则显示“正在同步”UI。

中断锁定

如果用户先在当前主机上运行应用,然后一直换了台主机运行,则后面一台主机可能对存储空间有独占访问权且正在上传数据。 还有种可能是后面一台主机已开始上传数据,但在完成前断开连接或断电。

这两种情况都称为锁定争用;在任何一种情况下,系统都会显示 UI,来说明另一台主机正在上传数据。 用户可等到上传过程完成,也可使用云中当前可用的数据。

如果用户选择使用云数据,则系统会将锁定给自己用(中断锁定),这使得用户和应用对云存储具有独占访问权。 其他主机上的上传会取消,而同步过程会继续。

容器列出、比较和合并逻辑

获取锁定后,系统会请求所有云端容器的列表供指定的应用和用户使用。 然后,它会将本地硬盘的内容与云中的数据进行比较,根据比较结果继续操作。

本地数据与云端匹配

如果其他主机中没有更改,且云端和本地硬盘中的数据相同,则同步完成。 XGameSaveInitializeProvider 此时会返回并显示结果,应用可继续加载和保存。

没有本地数据

如果云中有数据,但本地主机上没有,则会在本地下载云端数据。 例如,当用户第一次在朋友家玩游戏时可能会发生这种情况。

相同的容器在本地和云端都进行了修改

如果用户通过在另一台主机上玩游戏修改了云中的容器,还在脱机使用当前主机时修改了这些容器,那么数据无法自动合并。 系统将要求用户选择要保留哪一个数据。 如果两者有冲突,用户可选择使用替换策略:始终保留本地数据或云数据,或者用户可选择取消,稍后再做选择。 如果用户选择将云数据或本地数据作为替换策略,则名称相同但内容不同的容器会相应地得到解决。

如果用户选择取消,则游戏有权访问处于未解决状态的保存系统,就像用户脱机玩游戏一样。 在这种情况下,如果主机脱机,则当应用下次请求访问 XGameSave 存储空间时,会再次显示冲突解决 UI。

下载容器

解决所有冲突后,系统就有了确定需要从云端下载哪些容器所必需的全部信息。 所需容器全部下载,如果 XGameSaveInitializeProvider 被调用,则从它返回结果;如果调用的是 XGameSaveInitializeProviderAsync,则通过 XGameSaveInitializeProviderResult 返回结果。 然后,应用可继续加载和保存。

在下载容器的步骤期间,可能有几种错误情况。

本地存储不足

如果本地硬盘空间对所需容器来说不够,系统会向用户显示 UI,要求他们删除本地保存的数据来释放硬盘空间。 为了帮助他们避免永久删除未在云中备份的重要数据,UI 会清楚地指出仅删除本地缓存数据和当前主机的独有数据。

向用户显示 UI 时,会发生以下情况。

  • 如果用户释放的空间充足,则继续并完成同步。

  • 如果用户关闭 UI 但不释放足够的空间,则从 XGameSave 初始化提供程序返回的值为 E_GS_OUT_OF_LOCAL_STORAGE。 用户必须确认用户是否打算在没法保存数据的情况下玩游戏。 如果用户同意,则应用该继续但不保存该用户的数据。 如果用户指出想要在玩游戏时保存数据,则应用必须重复 XGameSaveInitializeProvider 调用,这又会显示要求释放空间的 UI。

不同的设备类型具有可用于游戏保存的不同数量的本地存储。

设备 游戏保存的最大本地存储空间
开发工具包 1 GB
零售版 Xbox One、Xbox One S、Xbox One X 9 GB
零售版 Xbox Series S、Xbox Series X、xCloud 4 GB
零售版桌面电脑 限制为电脑上的可用空间

用户取消同步

如果用户不想等到同步完成并选择了取消,则系统会通知用户部分保存的数据将不可用。 但是,不会通知游戏。 此时会返回 XGameSaveInitializeProvider 调用的结果,并且应用可继续加载和保存。

网络超时

如果数据下载因网络连接或服务可用性问题而出现超时,则用户可重新尝试同步。 如果用户选择不重试,系统会通知他们:部分保存的数据不可用且游戏不知道缺失的数据。 此时会返回 XGameSaveInitializeProvider 调用的结果,并且应用可继续加载和保存。