123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- // <copyright file="ISavedGameClient.cs" company="Google Inc.">
- // Copyright (C) 2014 Google Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // </copyright>
- namespace GooglePlayGames.BasicApi.SavedGame
- {
- using System;
- using System.Collections.Generic;
- /// <summary>
- /// An enum for the different strategies that can be used to resolve saved game conflicts (i.e.
- /// conflicts produced by two or more separate writes to the same saved game at once).
- /// </summary>
- public enum ConflictResolutionStrategy
- {
- /// <summary>
- /// Choose which saved game should be used on the basis of which one has the longest recorded
- /// play time. In other words, in the case of a conflicting write, the saved game with the
- /// longest play time will be considered cannonical. If play time has not been provided by the
- /// developer, or in the case of two saved games with equal play times,
- /// <see cref="UseOriginal"/> will be used instead.
- /// </summary>
- UseLongestPlaytime,
- /// <summary>
- /// Choose the version of the saved game that existed before any conflicting write occurred.
- /// Consider the following case:
- /// - An initial version of a save game ("X") is written from a device ("Dev_A")
- /// - The save game X is downloaded by another device ("Dev_B").
- /// - Dev_A writes a new version of the save game to the cloud ("Y")
- /// - Dev_B does not see the new save game Y, and attempts to write a new save game ("Z").
- /// - Since Dev_B is performing a write using out of date information, a conflict is generated.
- ///
- /// In this situation, we can resolve the conflict by declaring either keeping Y as the
- /// canonical version of the saved game (i.e. choose "original" aka <see cref="UseOriginal"/>),
- /// or by overwriting it with conflicting value, Z (i.e. choose "unmerged" aka
- /// <see cref="UseUnmerged"/>).
- /// </summary>
- ///
- UseOriginal,
- /// <summary>
- /// See the documentation for <see cref="UseOriginal"/>
- /// </summary>
- UseUnmerged,
- /// <summary>
- /// Manual resolution, no automatic resolution is attempted.
- /// </summary>
- UseManual,
- /// <summary>
- /// The use last known good snapshot to resolve conflicts automatically.
- /// </summary>
- UseLastKnownGood,
- /// <summary>
- /// The use most recently saved snapshot to resolve conflicts automatically.
- /// </summary>
- UseMostRecentlySaved
- }
- public enum SavedGameRequestStatus
- {
- Success = 1,
- /// <summary>
- /// The request failed due to a timeout.
- /// </summary>
- ///
- TimeoutError = -1,
- /// <summary>
- /// An unexpected internal error. Check the log for error messages.
- /// </summary>
- ///
- InternalError = -2,
- /// <summary>
- /// A error related to authentication. This is probably due to the user being signed out
- /// before the request could be issued.
- /// </summary>
- ///
- AuthenticationError = -3,
- /// <summary>
- /// The request failed because it was given bad input (e.g. a filename with 200 characters).
- /// </summary>
- ///
- BadInputError = -4
- }
- public enum SelectUIStatus
- {
- /// <summary>
- /// The user selected a saved game.
- /// </summary>
- SavedGameSelected = 1,
- /// <summary>
- /// The user closed the UI without selecting a saved game.
- /// </summary>
- ///
- UserClosedUI = 2,
- /// <summary>
- /// An unexpected internal error. Check the log for error messages.
- /// </summary>
- ///
- InternalError = -1,
- /// <summary>
- /// There was a timeout while displaying the UI.
- /// </summary>
- ///
- TimeoutError = -2,
- /// <summary>
- /// A error related to authentication. This is probably due to the user being signed out
- /// before the request could be issued.
- /// </summary>
- ///
- AuthenticationError = -3,
- /// <summary>
- /// The request failed because it was given bad input (e.g. a filename with 200 characters).
- /// </summary>
- ///
- BadInputError = -4,
- UiBusy = -5
- }
- ///
- /// <summary>
- /// A delegate that is invoked when we encounter a conflict during execution of
- /// <see cref="ISavedGameClient.OpenWithAutomaticConflictResolution"/>. The caller must resolve the
- /// conflict using the passed <see cref="IConflictResolver"/>. All passed metadata is open.
- /// If <see cref="ISavedGameClient.OpenWithAutomaticConflictResolution"/> was invoked with
- /// <c>prefetchDataOnConflict</c> set to <c>true</c>, the <paramref name="originalData"/> and
- /// <paramref name="unmergedData"/> will be equal to the binary data of the "original" and
- /// "unmerged" saved game respectively (and null otherwise). Since conflict files may be generated
- /// by other clients, it is possible that neither of the passed saved games were originally written
- /// by the current device. Consequently, any conflict resolution strategy should not rely on local
- /// data that is not part of the binary data of the passed saved games - this data will not be
- /// present if conflict resolution occurs on a different device. In addition, since a given saved
- /// game may have multiple conflicts, this callback must be designed to handle multiple invocations.
- /// </summary>
- public delegate void ConflictCallback(IConflictResolver resolver, ISavedGameMetadata original,
- byte[] originalData, ISavedGameMetadata unmerged, byte[] unmergedData);
- /// <summary>
- /// The main entry point for interacting with saved games. Saved games are persisted in the cloud
- /// along with several game-specific properties (<see cref="ISavedGameMetadata"/> for more
- /// information). There are several core concepts involved with saved games:
- ///
- /// <para><strong>Filenames</strong> - act as unique identifiers for saved games. Two devices
- /// performing a read or write using the same filename will end up reading or modifying the same
- /// file (i.e. filenames are not device specific).
- /// </para>
- ///
- /// <para><strong>Saved Game Metadata</strong> are represented by <see cref="ISavedGameMetadata"/>.
- /// The instances allow access to metadata properties about the underlying saved game (e.g.
- /// description). In addition, metadata functions as a handle that are required to read and
- /// manipulate saved game contents. Lastly, metadata may be "Open". Open metadata instances are
- /// required to manipulate the underlying binary data of the saved game. See method comments to
- /// determine whether a specific method requires or returns an open saved game.
- /// </para>
- ///
- /// <para><strong>Conflicts</strong> occur when multiple devices attempt to write to the same file
- /// at the same time. The saved game system guarantees that no conflicting writes will be lost or
- /// silently overwritten. Instead, they must be handled the next time the file with a conflict is
- /// Opened. Conflicts can be handled automatically (
- /// <see cref="OpenWithAutomaticConflictResolution"/>) or can be manuallyhandled by the developer
- /// (<see cref="OpenWithManualConflictResolution"/>). See the Open methods for more discussion.
- /// </para>
- ///
- /// <para>Saved games will generally be used in the following workflow:</para>
- /// <list type="number">
- /// <item><description>Determine which saved game to use (either using a hardcoded filename or
- /// ShowSelectSavedGameUI)</description></item>
- /// <item><description>Open the file using OpenWithManualConflictResolution or
- /// OpenWithAutomaticConflictResolution</description></item>
- /// <item><description>Read the binary data of the saved game using ReadBinaryData handle it
- /// as appropriate for your game.</description></item>
- /// <item><description>When you have updates, persist them in the cloud using CommitUpdate. Note
- /// that writing to the cloud is relatively expensive, and shouldn't be done frequently.
- /// </description></item>
- /// </list>
- ///
- /// <para>See online <a href="https://developers.google.com/games/services/common/concepts/savedgames">
- /// documentation for Saved Games</a> for more information.</para>
- /// </summary>
- public interface ISavedGameClient
- {
- /// <summary>
- /// Opens the file with the indicated name and data source. If the file has an outstanding
- /// conflict, it will be resolved using the specified conflict resolution strategy. The
- /// metadata returned by this method will be "Open" - it can be used as a parameter for
- /// <see cref="CommitUpdate"/> and <see cref="ResolveConflictByChoosingMetadata"/>.
- /// </summary>
- /// <param name="filename">The name of the file to open. Filenames must consist of
- /// only non-URL reserved characters (i.e. a-z, A-Z, 0-9, or the symbols "-", ".", "_", or "~")
- /// be between 1 and 100 characters in length (inclusive).</param>
- /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
- /// of the available options here.</param>
- /// <param name="resolutionStrategy">The conflict resolution that should be used if any
- /// conflicts are encountered while opening the file.
- /// <see cref="ConflictResolutionStrategy"/> for a description of these strategies.</param>
- /// <param name="callback">The callback that is invoked when this operation finishes. The
- /// returned metadata will only be non-null if the open succeeded. This callback will always
- /// execute on the game thread and the returned metadata (if any) will be "Open".</param>
- void OpenWithAutomaticConflictResolution(string filename, DataSource source,
- ConflictResolutionStrategy resolutionStrategy,
- Action<SavedGameRequestStatus, ISavedGameMetadata> callback);
- /// <summary>
- /// Opens the file with the indicated name and data source. If there is a conflict that
- /// requires resolution, it will be resolved manually using the passed conflict callback. Once
- /// all pending conflicts are resolved, the completed callback will be invoked with the
- /// retrieved data. In the event of an error, the completed callback will be invoked with the
- /// corresponding error status. All callbacks will be executed on the game thread.
- /// </summary>
- /// <param name="filename">The name of the file to open. Filenames must consist of
- /// only non-URL reserved characters (i.e. a-z, A-Z, 0-9, or the symbols "-", ".", "_", or "~")
- /// be between 1 and 100 characters in length (inclusive).</param>
- /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
- /// of the available options here.</param>
- /// <param name="prefetchDataOnConflict">If set to <c>true</c>, the data for the two
- /// conflicting files will be automatically retrieved and passed as parameters in
- /// <paramref name="conflictCallback"/>. If set to <c>false</c>, <c>null</c> binary data
- /// will be passed into <paramref name="conflictCallback"/> and the caller will have to fetch
- /// it themselves.</param>
- /// <param name="conflictCallback">The callback that will be invoked if one or more conflict is
- /// encountered while executing this method. Note that more than one conflict may be present
- /// and that this callback might be executed more than once to resolve multiple conflicts.
- /// This callback is always executed on the game thread.</param>
- /// <param name="completedCallback">The callback that is invoked when this operation finishes.
- /// The returned metadata will only be non-null if the open succeeded. If an error is
- /// encountered during conflict resolution, that error will be reflected here. This callback
- /// will always execute on the game thread and the returned metadata (if any) will be "Open".
- /// </param>
- void OpenWithManualConflictResolution(string filename, DataSource source,
- bool prefetchDataOnConflict, ConflictCallback conflictCallback,
- Action<SavedGameRequestStatus, ISavedGameMetadata> completedCallback);
- /// <summary>
- /// Reads the binary data of the passed saved game. The passed metadata must be opened (i.e.
- /// <see cref="ISavedGameMetadata.IsOpen"/> returns true). The callback will always be executed
- /// on the game thread.
- /// </summary>
- /// <param name="metadata">The metadata for the saved game whose binary data we want to read.
- /// This metadata must be open. If it is not open, the method will immediately fail with status
- /// <see cref="SelectUIStatus.BadInputError"/>.
- /// </param>
- /// <param name="completedCallback">The callback that is invoked when the read finishes. If the
- /// read completed without error, the passed status will be <see cref="SavedGameRequestStatus.Success"/> and the passed
- /// bytes will correspond to the binary data for the file. In the case of
- /// </param>
- void ReadBinaryData(ISavedGameMetadata metadata,
- Action<SavedGameRequestStatus, byte[]> completedCallback);
- /// <summary>
- /// Shows the select saved game UI with the indicated configuration. If the user selects a
- /// saved game in that UI, it will be returned in the passed callback. This metadata will be
- /// unopened and must be passed to either <see cref="OpenWithManualConflictResolution"/> or
- /// <see cref="OpenWithAutomaticConflictResolution"/> in order to retrieve the binary data.
- /// The callback will always be executed on the game thread.
- /// </summary>
- /// <param name="uiTitle">The user-visible title of the displayed selection UI.</param>
- /// <param name="maxDisplayedSavedGames">The maximum number of saved games the UI may display.
- /// This value must be greater than 0.</param>
- /// <param name="showCreateSaveUI">If set to <c>true</c>, show UI that will allow the user to
- /// create a new saved game.</param>
- /// <param name="showDeleteSaveUI">If set to <c>true</c> show UI that will allow the user to
- /// delete a saved game.</param>
- /// <param name="callback">The callback that is invoked when an error occurs or if the user
- /// finishes interacting with the UI. If the user selected a saved game, this will be passed
- /// into the callback along with the <see cref="SelectUIStatus.SavedGameSelected"/> status. This saved game
- /// will not be Open, and must be opened before it can be written to or its binary data can be
- /// read. If the user backs out of the UI without selecting a saved game, this callback will
- /// receive <see cref="UserClosedUI"/> and a null saved game. This callback will always execute
- /// on the game thread.</param>
- void ShowSelectSavedGameUI(string uiTitle, uint maxDisplayedSavedGames, bool showCreateSaveUI,
- bool showDeleteSaveUI, Action<SelectUIStatus, ISavedGameMetadata> callback);
- /// <summary>
- /// Durably commits an update to the passed saved game. When this method returns successfully,
- /// the data is durably persisted to disk and will eventually be uploaded to the cloud (in
- /// practice, this will happen very quickly unless the device does not have a network
- /// connection). If an update to the saved game has occurred after the metadata was retrieved
- /// from the cloud, this update will produce a conflict (this commonly occurs if two different
- /// devices are writing to the cloud at the same time). All conflicts must be handled the next
- /// time this saved game is opened. See <see cref="OpenWithManualConflictResolution"/> and
- /// <see cref="OpenWithAutomaticConflictResolution"/> for more information.
- /// </summary>
- /// <param name="metadata">The metadata for the saved game to update. This metadata must be
- /// Open (i.e. <see cref="ISavedGameMetadata.IsOpen"/> returns true)."/> If it is not open, the
- /// method will immediately fail with status <see cref="SelectUIStatus.BadInputError"/></param>
- /// <param name="updateForMetadata">All updates that should be applied to the saved game
- /// metadata.</param>
- /// <param name="updatedBinaryData">The new binary content of the saved game</param>
- /// <param name="callback">The callback that is invoked when this operation finishes.
- /// The returned metadata will only be non-null if the commit succeeded. If an error is
- /// encountered during conflict resolution, that error will be reflected here. This callback
- /// will always execute on the game thread and the returned metadata (if any) will NOT be
- /// "Open" (i.e. commiting an update closes the metadata).</param>
- void CommitUpdate(ISavedGameMetadata metadata, SavedGameMetadataUpdate updateForMetadata,
- byte[] updatedBinaryData, Action<SavedGameRequestStatus, ISavedGameMetadata> callback);
- /// <summary>
- /// Returns the metadata for all known saved games for this game. All returned saved games are
- /// not open, and must be opened before they can be used for writes or binary data reads. The
- /// callback will always occur on the game thread.
- /// </summary>
- /// <param name="source">The data source to use. <see cref="DataSource"/> for a description
- /// of the available options here.</param>
- /// <param name="callback">The callback that is invoked when this operation finishes.
- /// The returned metadata will only be non-empty if the commit succeeded. If an error is
- /// encountered during the fetch, that error will be reflected here. This callback
- /// will always execute on the game thread and the returned metadata (if any) will NOT be
- /// "Open".</param>
- void FetchAllSavedGames(DataSource source,
- Action<SavedGameRequestStatus, List<ISavedGameMetadata>> callback);
- /// <summary>
- /// Delete the specified snapshot.
- /// This will delete the data of the snapshot locally and on the server.
- /// </summary>
- /// <param name="metadata">the saved game metadata identifying the data to
- /// delete.</param>
- void Delete(ISavedGameMetadata metadata);
- }
- /// <summary>
- /// An interface that allows developers to resolve metadata conflicts that may be encountered while
- /// opening saved games.
- /// </summary>
- public interface IConflictResolver
- {
- /// <summary>
- /// Resolves the conflict by choosing the passed metadata to be canonical. The passed metadata
- /// must be one of the two instances passed as parameters into <see cref="ConflictCallback"/> -
- /// this instance will be kept as the cannonical value in the cloud.
- /// </summary>
- /// <param name="chosenMetadata">The chosen metadata. This metadata must be open. If it is not
- /// open, the invokation of <see cref="NativeSavedGameClient.OpenWithManualConflictResolution"/> that produced this
- /// ConflictResolver will immediately fail with <see cref="SelectUIStatus.BadInputError"/>.</param>
- void ChooseMetadata(ISavedGameMetadata chosenMetadata);
- /// <summary>
- /// Resolves the conflict and updates the data.
- /// </summary>
- /// <param name="chosenMetadata">Metadata for the chosen version. This is either the
- /// original or unmerged metadata provided when the callback is invoked.</param>
- /// <param name="metadataUpdate">Metadata update, same as when committing changes.</param>
- /// <param name="updatedData">Updated data to use when resolving the conflict.</param>
- void ResolveConflict(ISavedGameMetadata chosenMetadata, SavedGameMetadataUpdate metadataUpdate,
- byte[] updatedData);
- }
- }
|