ISavedGameClient.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // <copyright file="ISavedGameClient.cs" company="Google Inc.">
  2. // Copyright (C) 2014 Google Inc.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. // </copyright>
  16. namespace GooglePlayGames.BasicApi.SavedGame
  17. {
  18. using System;
  19. using System.Collections.Generic;
  20. /// <summary>
  21. /// An enum for the different strategies that can be used to resolve saved game conflicts (i.e.
  22. /// conflicts produced by two or more separate writes to the same saved game at once).
  23. /// </summary>
  24. public enum ConflictResolutionStrategy
  25. {
  26. /// <summary>
  27. /// Choose which saved game should be used on the basis of which one has the longest recorded
  28. /// play time. In other words, in the case of a conflicting write, the saved game with the
  29. /// longest play time will be considered cannonical. If play time has not been provided by the
  30. /// developer, or in the case of two saved games with equal play times,
  31. /// <see cref="UseOriginal"/> will be used instead.
  32. /// </summary>
  33. UseLongestPlaytime,
  34. /// <summary>
  35. /// Choose the version of the saved game that existed before any conflicting write occurred.
  36. /// Consider the following case:
  37. /// - An initial version of a save game ("X") is written from a device ("Dev_A")
  38. /// - The save game X is downloaded by another device ("Dev_B").
  39. /// - Dev_A writes a new version of the save game to the cloud ("Y")
  40. /// - Dev_B does not see the new save game Y, and attempts to write a new save game ("Z").
  41. /// - Since Dev_B is performing a write using out of date information, a conflict is generated.
  42. ///
  43. /// In this situation, we can resolve the conflict by declaring either keeping Y as the
  44. /// canonical version of the saved game (i.e. choose "original" aka <see cref="UseOriginal"/>),
  45. /// or by overwriting it with conflicting value, Z (i.e. choose "unmerged" aka
  46. /// <see cref="UseUnmerged"/>).
  47. /// </summary>
  48. ///
  49. UseOriginal,
  50. /// <summary>
  51. /// See the documentation for <see cref="UseOriginal"/>
  52. /// </summary>
  53. UseUnmerged,
  54. /// <summary>
  55. /// Manual resolution, no automatic resolution is attempted.
  56. /// </summary>
  57. UseManual,
  58. /// <summary>
  59. /// The use last known good snapshot to resolve conflicts automatically.
  60. /// </summary>
  61. UseLastKnownGood,
  62. /// <summary>
  63. /// The use most recently saved snapshot to resolve conflicts automatically.
  64. /// </summary>
  65. UseMostRecentlySaved
  66. }
  67. public enum SavedGameRequestStatus
  68. {
  69. Success = 1,
  70. /// <summary>
  71. /// The request failed due to a timeout.
  72. /// </summary>
  73. ///
  74. TimeoutError = -1,
  75. /// <summary>
  76. /// An unexpected internal error. Check the log for error messages.
  77. /// </summary>
  78. ///
  79. InternalError = -2,
  80. /// <summary>
  81. /// A error related to authentication. This is probably due to the user being signed out
  82. /// before the request could be issued.
  83. /// </summary>
  84. ///
  85. AuthenticationError = -3,
  86. /// <summary>
  87. /// The request failed because it was given bad input (e.g. a filename with 200 characters).
  88. /// </summary>
  89. ///
  90. BadInputError = -4
  91. }
  92. public enum SelectUIStatus
  93. {
  94. /// <summary>
  95. /// The user selected a saved game.
  96. /// </summary>
  97. SavedGameSelected = 1,
  98. /// <summary>
  99. /// The user closed the UI without selecting a saved game.
  100. /// </summary>
  101. ///
  102. UserClosedUI = 2,
  103. /// <summary>
  104. /// An unexpected internal error. Check the log for error messages.
  105. /// </summary>
  106. ///
  107. InternalError = -1,
  108. /// <summary>
  109. /// There was a timeout while displaying the UI.
  110. /// </summary>
  111. ///
  112. TimeoutError = -2,
  113. /// <summary>
  114. /// A error related to authentication. This is probably due to the user being signed out
  115. /// before the request could be issued.
  116. /// </summary>
  117. ///
  118. AuthenticationError = -3,
  119. /// <summary>
  120. /// The request failed because it was given bad input (e.g. a filename with 200 characters).
  121. /// </summary>
  122. ///
  123. BadInputError = -4,
  124. UiBusy = -5
  125. }
  126. ///
  127. /// <summary>
  128. /// A delegate that is invoked when we encounter a conflict during execution of
  129. /// <see cref="ISavedGameClient.OpenWithAutomaticConflictResolution"/>. The caller must resolve the
  130. /// conflict using the passed <see cref="IConflictResolver"/>. All passed metadata is open.
  131. /// If <see cref="ISavedGameClient.OpenWithAutomaticConflictResolution"/> was invoked with
  132. /// <c>prefetchDataOnConflict</c> set to <c>true</c>, the <paramref name="originalData"/> and
  133. /// <paramref name="unmergedData"/> will be equal to the binary data of the "original" and
  134. /// "unmerged" saved game respectively (and null otherwise). Since conflict files may be generated
  135. /// by other clients, it is possible that neither of the passed saved games were originally written
  136. /// by the current device. Consequently, any conflict resolution strategy should not rely on local
  137. /// data that is not part of the binary data of the passed saved games - this data will not be
  138. /// present if conflict resolution occurs on a different device. In addition, since a given saved
  139. /// game may have multiple conflicts, this callback must be designed to handle multiple invocations.
  140. /// </summary>
  141. public delegate void ConflictCallback(IConflictResolver resolver, ISavedGameMetadata original,
  142. byte[] originalData, ISavedGameMetadata unmerged, byte[] unmergedData);
  143. /// <summary>
  144. /// The main entry point for interacting with saved games. Saved games are persisted in the cloud
  145. /// along with several game-specific properties (<see cref="ISavedGameMetadata"/> for more
  146. /// information). There are several core concepts involved with saved games:
  147. ///
  148. /// <para><strong>Filenames</strong> - act as unique identifiers for saved games. Two devices
  149. /// performing a read or write using the same filename will end up reading or modifying the same
  150. /// file (i.e. filenames are not device specific).
  151. /// </para>
  152. ///
  153. /// <para><strong>Saved Game Metadata</strong> are represented by <see cref="ISavedGameMetadata"/>.
  154. /// The instances allow access to metadata properties about the underlying saved game (e.g.
  155. /// description). In addition, metadata functions as a handle that are required to read and
  156. /// manipulate saved game contents. Lastly, metadata may be "Open". Open metadata instances are
  157. /// required to manipulate the underlying binary data of the saved game. See method comments to
  158. /// determine whether a specific method requires or returns an open saved game.
  159. /// </para>
  160. ///
  161. /// <para><strong>Conflicts</strong> occur when multiple devices attempt to write to the same file
  162. /// at the same time. The saved game system guarantees that no conflicting writes will be lost or
  163. /// silently overwritten. Instead, they must be handled the next time the file with a conflict is
  164. /// Opened. Conflicts can be handled automatically (
  165. /// <see cref="OpenWithAutomaticConflictResolution"/>) or can be manuallyhandled by the developer
  166. /// (<see cref="OpenWithManualConflictResolution"/>). See the Open methods for more discussion.
  167. /// </para>
  168. ///
  169. /// <para>Saved games will generally be used in the following workflow:</para>
  170. /// <list type="number">
  171. /// <item><description>Determine which saved game to use (either using a hardcoded filename or
  172. /// ShowSelectSavedGameUI)</description></item>
  173. /// <item><description>Open the file using OpenWithManualConflictResolution or
  174. /// OpenWithAutomaticConflictResolution</description></item>
  175. /// <item><description>Read the binary data of the saved game using ReadBinaryData handle it
  176. /// as appropriate for your game.</description></item>
  177. /// <item><description>When you have updates, persist them in the cloud using CommitUpdate. Note
  178. /// that writing to the cloud is relatively expensive, and shouldn't be done frequently.
  179. /// </description></item>
  180. /// </list>
  181. ///
  182. /// <para>See online <a href="https://developers.google.com/games/services/common/concepts/savedgames">
  183. /// documentation for Saved Games</a> for more information.</para>
  184. /// </summary>
  185. public interface ISavedGameClient
  186. {
  187. /// <summary>
  188. /// Opens the file with the indicated name and data source. If the file has an outstanding
  189. /// conflict, it will be resolved using the specified conflict resolution strategy. The
  190. /// metadata returned by this method will be "Open" - it can be used as a parameter for
  191. /// <see cref="CommitUpdate"/> and <see cref="ResolveConflictByChoosingMetadata"/>.
  192. /// </summary>
  193. /// <param name="filename">The name of the file to open. Filenames must consist of
  194. /// only non-URL reserved characters (i.e. a-z, A-Z, 0-9, or the symbols "-", ".", "_", or "~")
  195. /// be between 1 and 100 characters in length (inclusive).</param>
  196. /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
  197. /// of the available options here.</param>
  198. /// <param name="resolutionStrategy">The conflict resolution that should be used if any
  199. /// conflicts are encountered while opening the file.
  200. /// <see cref="ConflictResolutionStrategy"/> for a description of these strategies.</param>
  201. /// <param name="callback">The callback that is invoked when this operation finishes. The
  202. /// returned metadata will only be non-null if the open succeeded. This callback will always
  203. /// execute on the game thread and the returned metadata (if any) will be "Open".</param>
  204. void OpenWithAutomaticConflictResolution(string filename, DataSource source,
  205. ConflictResolutionStrategy resolutionStrategy,
  206. Action<SavedGameRequestStatus, ISavedGameMetadata> callback);
  207. /// <summary>
  208. /// Opens the file with the indicated name and data source. If there is a conflict that
  209. /// requires resolution, it will be resolved manually using the passed conflict callback. Once
  210. /// all pending conflicts are resolved, the completed callback will be invoked with the
  211. /// retrieved data. In the event of an error, the completed callback will be invoked with the
  212. /// corresponding error status. All callbacks will be executed on the game thread.
  213. /// </summary>
  214. /// <param name="filename">The name of the file to open. Filenames must consist of
  215. /// only non-URL reserved characters (i.e. a-z, A-Z, 0-9, or the symbols "-", ".", "_", or "~")
  216. /// be between 1 and 100 characters in length (inclusive).</param>
  217. /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
  218. /// of the available options here.</param>
  219. /// <param name="prefetchDataOnConflict">If set to <c>true</c>, the data for the two
  220. /// conflicting files will be automatically retrieved and passed as parameters in
  221. /// <paramref name="conflictCallback"/>. If set to <c>false</c>, <c>null</c> binary data
  222. /// will be passed into <paramref name="conflictCallback"/> and the caller will have to fetch
  223. /// it themselves.</param>
  224. /// <param name="conflictCallback">The callback that will be invoked if one or more conflict is
  225. /// encountered while executing this method. Note that more than one conflict may be present
  226. /// and that this callback might be executed more than once to resolve multiple conflicts.
  227. /// This callback is always executed on the game thread.</param>
  228. /// <param name="completedCallback">The callback that is invoked when this operation finishes.
  229. /// The returned metadata will only be non-null if the open succeeded. If an error is
  230. /// encountered during conflict resolution, that error will be reflected here. This callback
  231. /// will always execute on the game thread and the returned metadata (if any) will be "Open".
  232. /// </param>
  233. void OpenWithManualConflictResolution(string filename, DataSource source,
  234. bool prefetchDataOnConflict, ConflictCallback conflictCallback,
  235. Action<SavedGameRequestStatus, ISavedGameMetadata> completedCallback);
  236. /// <summary>
  237. /// Reads the binary data of the passed saved game. The passed metadata must be opened (i.e.
  238. /// <see cref="ISavedGameMetadata.IsOpen"/> returns true). The callback will always be executed
  239. /// on the game thread.
  240. /// </summary>
  241. /// <param name="metadata">The metadata for the saved game whose binary data we want to read.
  242. /// This metadata must be open. If it is not open, the method will immediately fail with status
  243. /// <see cref="SelectUIStatus.BadInputError"/>.
  244. /// </param>
  245. /// <param name="completedCallback">The callback that is invoked when the read finishes. If the
  246. /// read completed without error, the passed status will be <see cref="SavedGameRequestStatus.Success"/> and the passed
  247. /// bytes will correspond to the binary data for the file. In the case of
  248. /// </param>
  249. void ReadBinaryData(ISavedGameMetadata metadata,
  250. Action<SavedGameRequestStatus, byte[]> completedCallback);
  251. /// <summary>
  252. /// Shows the select saved game UI with the indicated configuration. If the user selects a
  253. /// saved game in that UI, it will be returned in the passed callback. This metadata will be
  254. /// unopened and must be passed to either <see cref="OpenWithManualConflictResolution"/> or
  255. /// <see cref="OpenWithAutomaticConflictResolution"/> in order to retrieve the binary data.
  256. /// The callback will always be executed on the game thread.
  257. /// </summary>
  258. /// <param name="uiTitle">The user-visible title of the displayed selection UI.</param>
  259. /// <param name="maxDisplayedSavedGames">The maximum number of saved games the UI may display.
  260. /// This value must be greater than 0.</param>
  261. /// <param name="showCreateSaveUI">If set to <c>true</c>, show UI that will allow the user to
  262. /// create a new saved game.</param>
  263. /// <param name="showDeleteSaveUI">If set to <c>true</c> show UI that will allow the user to
  264. /// delete a saved game.</param>
  265. /// <param name="callback">The callback that is invoked when an error occurs or if the user
  266. /// finishes interacting with the UI. If the user selected a saved game, this will be passed
  267. /// into the callback along with the <see cref="SelectUIStatus.SavedGameSelected"/> status. This saved game
  268. /// will not be Open, and must be opened before it can be written to or its binary data can be
  269. /// read. If the user backs out of the UI without selecting a saved game, this callback will
  270. /// receive <see cref="UserClosedUI"/> and a null saved game. This callback will always execute
  271. /// on the game thread.</param>
  272. void ShowSelectSavedGameUI(string uiTitle, uint maxDisplayedSavedGames, bool showCreateSaveUI,
  273. bool showDeleteSaveUI, Action<SelectUIStatus, ISavedGameMetadata> callback);
  274. /// <summary>
  275. /// Durably commits an update to the passed saved game. When this method returns successfully,
  276. /// the data is durably persisted to disk and will eventually be uploaded to the cloud (in
  277. /// practice, this will happen very quickly unless the device does not have a network
  278. /// connection). If an update to the saved game has occurred after the metadata was retrieved
  279. /// from the cloud, this update will produce a conflict (this commonly occurs if two different
  280. /// devices are writing to the cloud at the same time). All conflicts must be handled the next
  281. /// time this saved game is opened. See <see cref="OpenWithManualConflictResolution"/> and
  282. /// <see cref="OpenWithAutomaticConflictResolution"/> for more information.
  283. /// </summary>
  284. /// <param name="metadata">The metadata for the saved game to update. This metadata must be
  285. /// Open (i.e. <see cref="ISavedGameMetadata.IsOpen"/> returns true)."/> If it is not open, the
  286. /// method will immediately fail with status <see cref="SelectUIStatus.BadInputError"/></param>
  287. /// <param name="updateForMetadata">All updates that should be applied to the saved game
  288. /// metadata.</param>
  289. /// <param name="updatedBinaryData">The new binary content of the saved game</param>
  290. /// <param name="callback">The callback that is invoked when this operation finishes.
  291. /// The returned metadata will only be non-null if the commit succeeded. If an error is
  292. /// encountered during conflict resolution, that error will be reflected here. This callback
  293. /// will always execute on the game thread and the returned metadata (if any) will NOT be
  294. /// "Open" (i.e. commiting an update closes the metadata).</param>
  295. void CommitUpdate(ISavedGameMetadata metadata, SavedGameMetadataUpdate updateForMetadata,
  296. byte[] updatedBinaryData, Action<SavedGameRequestStatus, ISavedGameMetadata> callback);
  297. /// <summary>
  298. /// Returns the metadata for all known saved games for this game. All returned saved games are
  299. /// not open, and must be opened before they can be used for writes or binary data reads. The
  300. /// callback will always occur on the game thread.
  301. /// </summary>
  302. /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
  303. /// of the available options here.</param>
  304. /// <param name="callback">The callback that is invoked when this operation finishes.
  305. /// The returned metadata will only be non-empty if the commit succeeded. If an error is
  306. /// encountered during the fetch, that error will be reflected here. This callback
  307. /// will always execute on the game thread and the returned metadata (if any) will NOT be
  308. /// "Open".</param>
  309. void FetchAllSavedGames(DataSource source,
  310. Action<SavedGameRequestStatus, List<ISavedGameMetadata>> callback);
  311. /// <summary>
  312. /// Delete the specified snapshot.
  313. /// This will delete the data of the snapshot locally and on the server.
  314. /// </summary>
  315. /// <param name="metadata">the saved game metadata identifying the data to
  316. /// delete.</param>
  317. void Delete(ISavedGameMetadata metadata);
  318. }
  319. /// <summary>
  320. /// An interface that allows developers to resolve metadata conflicts that may be encountered while
  321. /// opening saved games.
  322. /// </summary>
  323. public interface IConflictResolver
  324. {
  325. /// <summary>
  326. /// Resolves the conflict by choosing the passed metadata to be canonical. The passed metadata
  327. /// must be one of the two instances passed as parameters into <see cref="ConflictCallback"/> -
  328. /// this instance will be kept as the cannonical value in the cloud.
  329. /// </summary>
  330. /// <param name="chosenMetadata">The chosen metadata. This metadata must be open. If it is not
  331. /// open, the invokation of <see cref="NativeSavedGameClient.OpenWithManualConflictResolution"/> that produced this
  332. /// ConflictResolver will immediately fail with <see cref="SelectUIStatus.BadInputError"/>.</param>
  333. void ChooseMetadata(ISavedGameMetadata chosenMetadata);
  334. /// <summary>
  335. /// Resolves the conflict and updates the data.
  336. /// </summary>
  337. /// <param name="chosenMetadata">Metadata for the chosen version. This is either the
  338. /// original or unmerged metadata provided when the callback is invoked.</param>
  339. /// <param name="metadataUpdate">Metadata update, same as when committing changes.</param>
  340. /// <param name="updatedData">Updated data to use when resolving the conflict.</param>
  341. void ResolveConflict(ISavedGameMetadata chosenMetadata, SavedGameMetadataUpdate metadataUpdate,
  342. byte[] updatedData);
  343. }
  344. }