Tinko
Tinko

Reputation: 502

Android Google Saved Games unexpected conflicts

In my game I have in-game currency and I want to save its value to the cloud. I decided to use Google Saved Games API. Everything works great but when I'm saving data to the Snapshots and then reading it when the game launches again, I'm getting conflicts, even when I'm on the same device. Now I'm saving currency's state after every change, so when player spents or gets some "coins". I'm thinking that this could be very often and services can't handle it because when I'm offline (without connection to the network) everything works nice and fast but when I'm online (connected to Wi-fi) work with Snapshots is slower and as I said I'm getting conflicts with data last saved and previous data I saved (I'm loggging all values...). Sometimes I get even 5 conflicts. I have 3 functions to work with Saved Games. One for reading data, one for saving data and one for checking for conflicts:

Reading data:

private void readSavedGame(final String snapshotName) {
    AsyncTask<Void, Void, Boolean> readingTask = new AsyncTask<Void, Void, Boolean>() {
        @Override
        protected Boolean doInBackground(Void... params) {
            Snapshots.OpenSnapshotResult result = Games.Snapshots.open(mGoogleApiClient, snapshotName, false).await();

            Snapshot snapshot = processSnapshotOpenResult(result, 0);

            if(snapshot != null) {
                try {
                    updateGameData(snapshot.getSnapshotContents().readFully());
                    Log.d(TAG, "Updating game: "+String.valueOf(coins)+"...");
                    return true;
                } catch (IOException e) {
                    Log.d(TAG, "Error: " + e.getMessage());
                }
            }

            return false;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);

            if(result) Log.d(TAG, "Game state read successfully...");
            else Log.d(TAG, "Error while reading game state...");

            updateUi();
        }
    };

    readingTask.execute();
}

Saving data:

private void writeSavedGame(final String snapshotName, final byte[] data) {
    AsyncTask<Void, Void, Boolean> updateTask = new AsyncTask<Void, Void, Boolean>() {
        @Override
        protected Boolean doInBackground(Void... params) {
            Snapshots.OpenSnapshotResult result = Games.Snapshots.open(
                    mGoogleApiClient, snapshotName, false).await();

            Snapshot snapshot = processSnapshotOpenResult(result, 0);

            if(snapshot != null) {
                snapshot.getSnapshotContents().writeBytes(getGameData());
                Log.d(TAG, "Saving: "+String.valueOf(coins)+"...");

                Snapshots.CommitSnapshotResult commitSnapshotResult = Games.Snapshots.commitAndClose(mGoogleApiClient, snapshot, SnapshotMetadataChange.EMPTY_CHANGE).await();

                if(commitSnapshotResult.getStatus().isSuccess()) return true;
            }

            return false;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            if (result) Log.d(TAG, "Game was saved successfully....");
            else Log.d(TAG, "Error while saving game state...");
        }
    };

    updateTask.execute();
}

Checking for conflicts or handling OpenSnapshotResult

Snapshot processSnapshotOpenResult(Snapshots.OpenSnapshotResult result, int retryCount) {
    Snapshot mResolvedSnapshot = null;
    retryCount++;

    int status = result.getStatus().getStatusCode();
    Log.i(TAG, "Save Result status: " + status);

    if (status == GamesStatusCodes.STATUS_OK) {
        Log.d(TAG, "No conflict, SNAPSHOT is OK");
        return result.getSnapshot();
    } else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE) {
        return result.getSnapshot();
    }
    else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) {
        Log.d(TAG, "Conflict: "+String.valueOf(retryCount));

        Snapshot snapshot = result.getSnapshot();
        Snapshot conflictSnapshot = result.getConflictingSnapshot();

        // Resolve between conflicts by selecting the newest of the conflicting snapshots.
        mResolvedSnapshot = snapshot;

        if (snapshot.getMetadata().getLastModifiedTimestamp() <
                conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
            mResolvedSnapshot = conflictSnapshot;
        }

        try {
            Log.d(TAG, "Snapshot data: "+new String(snapshot.getSnapshotContents().readFully()));
            Log.d(TAG, "Conflicting data: "+new String(conflictSnapshot.getSnapshotContents().readFully()));
        } catch (IOException e) {
            Log.e(TAG, "ERROR WHILE READING SPAPSHOTS CONTENTS...");
        }


        Snapshots.OpenSnapshotResult resolveResult = Games.Snapshots.resolveConflict(
                mGoogleApiClient, result.getConflictId(), mResolvedSnapshot).await();

        if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
            // Recursively attempt again
            return processSnapshotOpenResult(resolveResult, retryCount);
        } else {
            // Failed, log error and show Toast to the user
            String message = "Could not resolve snapshot conflicts";
            Log.e(TAG, message);
            //Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
        }

    }

    // Fail, return null.
    return null;
}

Conflict principes are explained nicely here. My code is based on official docs implementations and samples.

So when offline, everything works excellent but when connected, I'm getting conflicts on the same device... Maybe I'm updating my saved game very often and services can't handle it. Any ideas? Thanks.

Upvotes: 1

Views: 675

Answers (1)

Teyam
Teyam

Reputation: 8092

As discussed in Saved Games - Conflict resolution

Typically, data conflicts occur when an instance of your application is unable to reach the Saved Games service while attempting to load data or save it. In general, the best way to avoid data conflicts is to always load the latest data from the service when your application starts up or resumes, and save data to the service with reasonable frequency.

In addition to that, it is also recommended to follow Best practices for implementing saved games to deliver the best possible product to your players.

Also, to learn how to implement Saved Games for your platform, see the resources given in Client implementations.

Upvotes: 0

Related Questions