RandomGFGoStop/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/BasicApi/SavedGame/ISavedGameClient.cs

379 lines
21 KiB
C#
Raw Normal View History

2025-09-07 01:22:33 +00:00
// <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
}
/// <summary>
/// An enum for the different statuses that can be returned by the saved game client.
/// </summary>
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
}
/// <summary>
/// An enum for the different UI statuses that can be returned by the saved game client.
/// </summary>
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>
/// An error related to authentication. This error could be due to the user being signed out
/// before the request could be issued.
/// </summary>
///
AuthenticationError = -3,
/// <summary>
/// The request failed due to invalid input. For example, the filename exceeded the 200 character limit..
/// </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 invocation 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);
}
}